22

You can create a loop in devops pipelines YAML by using a syntax similiar to -${{ each x in y }}:. From my understanding, y can be an array.

However, I find that there is no documentation for each. The only page describing an example of it's usage is on the "Templates" page.

So my question is: how do I go about specifying an array? I know one way is to use -${{ each book in parameters.books }}: and then pass in a "list" of books like:

- template: template.yml
  parameters: 
    books:
      - book1
      - book2
      - book3

However, I'd also like to define an array as a variable:

variables:
  books:
    - book1
    - book2
    - book3

However, for this, ADO throws an error A sequence was not expected.

Is there no way to define an array like that? I'd imagine I'd then refer to these as -${{ each book in variables.books }}: If not, is there any other way to specify a list?

a3y3
  • 1,025
  • 2
  • 10
  • 34

3 Answers3

23

It's not supported defining an array as a variable, the variable syntax is variables: { string: string }. Check the following case:

https://developercommunity.visualstudio.com/content/problem/812728/variables-in-yaml-pipeline-are-not-allowing-to-def.html

Yaml variables have always been string: string mappings.

We are preparing to release a feature in the near future to allow you to pass more complex structures. Stay tuned!

Currently, you can only use parameters to pass and loop array:

parameters:
- name: 'param'
  type: object
  default: 
  - FOO
  - BAR
  - ZOO

steps:
- ${{ each p in parameters.param }}:
  - script: echo ${{ p }}
Cece Dong - MSFT
  • 29,631
  • 1
  • 24
  • 39
  • Thanks for answering, but I _have_ to use variables. Parameters is how I'm doing it right now, but using variables would make my pipeline much simpler. – a3y3 Jul 13 '20 at 13:20
  • If you can remove your parameters suggestion and add this link https://developercommunity.visualstudio.com/content/problem/812728/variables-in-yaml-pipeline-are-not-allowing-to-def.html to your answer, I will mark it as accepted. – a3y3 Jul 13 '20 at 13:25
  • 1
    @SohamDongargaonkar I have modified the answer. – Cece Dong - MSFT Jul 14 '20 at 01:30
  • the more I use MS azure I have to admit MS has come a very long way in supporting its products especially with folks like @CeceDong-MSFT who monitor and answer azure questions ... five stars !!!!!!!! – Scott Stensland Feb 05 '21 at 20:59
  • 1
    This works great, even inside a template called from main script, but I cannot get it to work with a list generated dynamically before being passed to the template as suggested in this answer https://stackoverflow.com/questions/63387037/looping-through-variables-in-azure-pipeline/63405679#63405679 I am getting the error: "Expected sequence or mapping. Actual value '$(VariableName)'" – MikeW Apr 22 '21 at 10:59
  • this https://developercommunity.visualstudio.com/t/azure-pipelines-passing-a-variable-as-a-parameter/429990 suggests its not possible to pas a list generated at run time :-( – MikeW Apr 22 '21 at 11:22
  • @leonhees posted a nice workaround, using `split()` to generate a list from a string and so allow usage of `${{each}}` over variable without the need for a template. That could be the accepted answer. – lpacheco Apr 13 '23 at 13:27
9

The way I overcame the limitations on variables arrays was to transform the variable value string into an array in the pipeline. Parameters are not an option as they are exposed to the user at Pipeline run.

in Variables.prod.yaml:

variables:
- name: prod_vmnames
  value: VM1, VM2, VM3

in the pipeline:

scriptType: bash
scriptLocation: inlineScript
inlineScript: |
  echo "$(date +"%Y-%m-%d %H:%M:%S") - INFO  - Script started"
  
  # "Generating Prod VMs array"
  IFS="," read -a vms_array <<< ${{ variables.prod_vmnames }}
  echo "VMs array is: $vms_array"

  # Run script on all VMs
  chmod +x AzVmRun.sh
  for vm in ${vms_array[@]}
  do
    echo "Launching script /Scripts/AzVmRun.sh on vm: $vm"
    ./AzVmRun.sh \
      -v $vm -k ${{ variables.kvname }}
  done

HTH.

Gopher62
  • 91
  • 1
  • 1
4

You don't need templates to have a for-loop. Just use the split function:

- stage:
  variables:
    - name: vars
      value: dev, nonprod, production

  jobs:
  - job: print_vars
    steps:
    - ${{ each var in split(variables.vars, ', ') }}:
      - script: |
          echo ${{ var}}
        displayName: Printing ${{ var }}

You could also use the variables object itself as a map but that can lead to issues.

- stage:
  variables:
    - name: DEV
      value: dev
    - name: NONPROD
      value: nonprod
    - name: PROD
      value: production

  jobs:
  - job: print_vars
    steps:
    - ${{ each var in variables }}:
      - script: |
          echo ${{ var.key }}
          echo ${{ var.value }}
        displayName: Printing ${{ var.key }}
Community
  • 1
  • 1
leonheess
  • 16,068
  • 14
  • 77
  • 112