Dynamic Loops In Azure DevOps Pipelines

Scenario Link to heading

Being used to programming, loops feel very basic, like, for each one of these, do that. However, doing this in AzDevOps pipelines turned out to be not as easy.

It is trivial, for a previously know set, like an array of environments [‘dev,‘qa’,‘prod’], no problem there, but what if the set is determined dynamically, while the pipeline is running?

This is exactly my need. I have a container app, publicly exposed to the internet, with a set of IP rules so that it can only be called by a Logic App. The thing is, the outgoing IP of the Logic App is not a single one and can change from deployment to deployment, so I need to obtain the list of IPs, then run a loop on those IP, to add them as rules.

Of course I can deploy both the container app and the logic app in a VNET to avoid the need of these rules, not there yet unfortunately, other requirements and limitations make it necessary to keep these public.

So why is not as easy?

The Azure Devops loop support relies on the each keyword explained here,

parameters:
- name: listOfStrings
  type: object
  default:
  - one
  - two

steps:
- ${{ each value in parameters.listOfStrings }}:
  - script: echo ${{ value }}

However, the ${{ }} syntax is not for runtime. Those expressions are expanded before running the workflow as explained here,

Using bash inside the task to run the loop Link to heading

The way I worked around this is by using bash loops, and the same can be accomplished with PowerShell,

My code:

- task: AzureCLI@2
  displayName: Add Ip Security Allow Logic Apps
  name: AddIpSecurityRestrictionsAllowLAs
  inputs:
    azureSubscription: myServiceConnectionName
    scriptType: 'bash'
    scriptLocation: 'inlineScript'
    inlineScript: |
      MSYS_NO_PATHCONV=1
      echo "Full Ip List is $(GetLogicAppsOutgoingIps.logicApps_outgoing)"
      IFS=',' read -ra la_ips <<< "$(GetLogicAppsOutgoingIps.logicApps_outgoing)"
      for i in "${la_ips[@]}";
      do
        DATE=$(date '+%Y%m%d%H%M%S')
        echo "Adding rule for $i"
        az containerapp ingress access-restriction set \
          --name theNameOfMyContainerApp \
          --resource-group theNameOfTheResourceGroup \
          --rule-name "Allow My Logic App $DATE" \
          --ip-address "$i/32" \
          --action Allow
      done      

I’m adding the date to make the name unique, and the syntax to split the string and the loop in bash is explained here.

The GetLogicAppsOutgoingIps.logicApps_outgoing variable is set by a template that runs another AzureCLI bash script, that queries the outgoing IPs, and creates an array with that information,

logicAppIps=$( az logicapp show -n theLogicAppName -g theResourceGroupName --query 'outboundIpAddresses' -o tsv)
if [[ $logicAppIps == "" ]]; then
    exit 1
fi
echo "##vso[task.setvariable variable=logicApps_outgoing;isoutput=true;isreadonly=true;]$logicAppIps"

And there it is!, a dynamic loop in an Azure Devops pipeline.