Howto: Dynamic variable name resolution in Azure DevOps YAML

Viewed 7974

The consistency of Variable support & the syntax vary wildly in Azure DevOps YAML. Case in point:

trigger:
- master

# Variable Group has $(testCategory1) with value
# 'TestCategory=bvttestonly | TestCategory=logintest'
variables:
  - group: DYNAMIC_VG

jobs:
  - job:
    pool: 'MyPool' #Has about 10+ self hosted agents

    strategy:
      parallel: $[ variables['noOfVMsDynamic']]

    variables:
      indyx: '$(testCategories$(System.JobPositionInPhase))'
      indyx2: $[ variables['indyx'] ] 
      testCategories: $[ variables[ 'indyx2' ] ]

    steps:
    - script: |
        echo "indyx2 - $(indyx2)"
        echo "testCategories $(testCategories)"
      displayName: 'Display Test Categories'

The step prints:

"indyx2 - $(testCategories1)"
"testCategories $(testCategories1)"

I need to print the value of $(testCategories1) defined in the Variable Group:

'TestCategory=bvttestonly | TestCategory=logintest'

4 Answers

This may work to you:

variables
   indyx: $[ variables[format('{0}{1}', 'testCategories', variables['System.JobPositionInPhase'])] ]

It worked for me, in a slightly different situation which also required some dynamic variable names.

Howto: Dynamically resolve a nested variable in Azure DevOps YAML

That because the value of nested variables (like $(testCategories$(System.JobPositionInPhase))) are not yet supported in the build pipelines at this moment.

That the reason why you always get the value $(testCategories1) rather than the real value of variable testCategories1.

I encountered this issue many times in my past posts and we do not have a perfect solution before Azure Devops supports this feature.

For the convenience of testing, I simplified your yaml like following:

jobs:
  - job: ExecCRJob
    timeoutInMinutes: 800

    pool:
      name: MyPrivateAgent

    displayName: 'Execute CR'


    variables:
      testCategories1: 123456
      testCategoriesSubscripted: $(testCategories$(System.JobPositionInPhase))

    strategy:
      parallel: $[variables['noOfVMs']]     
    steps:
    - template: execute-cr.yml
      parameters:
        testCategories: $(testCategoriesSubscripted)

The execute-cr.yml:

steps:
    - script: echo ${{ parameters.testCategories }}

We always get the $(testCategories1)NOT the value of it.

If I change the $(testCategories$(System.JobPositionInPhase)) to $(testCategories1), everything work fine.

Since nested variables are not yet supported, As workaround, we need to expand the nested variables for each value of testCategories, like:

- job: B
  condition: and(succeeded(), eq(dependencies.A.outputs['printvar.skipsubsequent'], 'Value1'))
  dependsOn: A
  steps:
  - script: echo hello from B

Check the Expressions Dependencies for some more details.

Hope this helps.

If I'm understanding your issue correctly, the problem is that the pipeline evaluates all variables at the runtime of the job. The solution in this scenario is to split your tasks into separate jobs with dependencies.

Have a look at my answer in this post and let me know if it's what you're after : YAML pipeline - Set variable and use in expression for template

I manage to get dynamic name resolution by using get-item to read the corresponding environment variable, allowing construction of the name of the variable and then getting the value.

In our case we save the name of an autogenerated branch into a variable group and each repository will have its own variable.

$branchVarName = "$(Build.Repository.Name).BranchName".replace(".","_")
$branchName = (get-item -Path Env:$branchVarName).value
write-host "/$(System.TeamProject)/_apis/build/builds?&repositoryId=$(Build.Repository.ID)&repositoryType=TFSGit&branchName=refs/heads/$branchName&api-version=6.0"

Notice in the second line that I reference the variable content using .value because get-item returns a name/value key pair.

This extraction has to be done in script but could be exposed as an output variable if needed in another context.

Related