Dynamic variable group in Azure DevOps pipeline - azure-devops

I want to be able to use a variable group in Azure DevOps Pipeline based upon a generated name.
Like so: (where dtapName is a letter like d or t.)
variables:
- group: 'project-${{ dtapName }}'
I've seen it work in other pipelines but creating one myself has been a pain.
My current pipeline is as follows:
The variable group "global-d" contains a variable 'dtapName' with value 'd'.
And the variable group "project-t" contains URLs and such specific for our 'develop' landscape.
File "pipeline-infra.yaml":
trigger:
branches:
include:
- main
- develop
paths:
include:
- Project/pipelines
stages:
- stage: Develop
displayName: Develop deployment of Project
variables:
- name: environmentName
value: 'develop-o'
- group: 'global-o'
jobs:
- deployment: 'Debug_001'
environment: ${{ variables.environmentName }}
strategy:
runOnce:
deploy:
steps:
- script: echo [Testing job]
- script: echo v.environment = ${{ variables.environmentName }} # Does work
- script: echo v.dtapName = ${{ variables.dtapName }} #❗Fails, why? It is defined in the variable group 'global-o'
# script: echo environment = ${{ environmentName }} #❗Failed, why does it work in the template call below?
# script: echo dtapName = ${{ dtapName }} #❗Failed, why does it work in the template call below?
- script: echo env ENVIRONMENTNAME = $ENVIRONMENTNAME # Does work
- script: echo env DTAPNAME = $DTAPNAME # Does work
- template: pipeline-infra.extra.yaml
parameters:
environment: $(environmentName)
dtapName: $(dtapName)
File "pipeline-infra.extra.yaml"
parameters:
- name: environment
type: string
- name: dtapName
type: string
jobs:
- deployment: 'Debug_002'
# environment: ${{ parameters.environment }} #❗Fails with: Environment $(environmentName) could not be found. WHY?
environment: 'develop-o'
pool:
vmImage: 'ubuntu-latest'
variables:
- name: placeholder
value: 'none'
# - group: 'project-${{ parameters.dtapName }}' #❗Fails with: Variable group 'project-$(dtapName)' could not be found. How to do this?
strategy:
runOnce:
deploy:
steps:
- script: echo [ pipeline-infra.extra.yaml ]
- script: echo Environment = ${{ parameters.environment }} # WORKS
- script: echo "Environment (v) = ${{ variables.environmentName }}" #❗FAILS, this variable is declare on the stage of this job... why can't it be accessed?
- script: echo Parameter = ${{ parameters.dtapName }} # WORKS
- script: echo PARAMETER = $DTAPNAME # WORKS
- script: echo Variable = ${{ variables.dtapName }} #❗FAILS, would think this comes available when the problem in the calling stage is fixed
- task: Bash#3
displayName: VariableListing 2
inputs:
targetType: inline
script: 'env | sort'
Can anyone explain me (or point me to a description) why these problems occur?
I've read (most of) Define variables and documents linked from here.
It's probably a little thing, but for the past couple of days I'm not seeing it.

You have two errors in your main yaml:
You cannot invoke variables from Variable group with template expression ${{ variables.var }}. Template variables are processed at compile time, and are replaced before runtime starts. It cannot get variable value from Group, it can get value from predefined variable.
Template expression should have format as ${{ variables.var }} not ${{ var }}.
Please check doc: Understand variable syntax for the details. I add the comment on your yaml below:
trigger:
branches:
include:
- main
- develop
paths:
include:
- Project/pipelines
stages:
- stage: Develop
displayName: Develop deployment of Project
variables:
- name: environmentName
value: 'develop-o'
- group: 'global-o'
jobs:
- deployment: 'Debug_001'
environment: ${{ variables.environmentName }}
strategy:
runOnce:
deploy:
steps:
- script: echo [Testing job]
- script: echo v.environment = ${{ variables.environmentName }} # Does work, it use template expression syntax and can get the value as it's predefined variable.
- script: echo v.dtapName = ${{ variables.dtapName }} #❗Not work, Template variables are processed at compile time, and are replaced before runtime starts, it cannot get the Test Group variable value, should use macro syntax $(dtapName).
# script: echo environment = ${{ environmentName }} #❗Failed, syntax error, should be ${{ variables.environmentName }} or $(environmentName)
# script: echo dtapName = ${{ dtapName }} #❗Failed, syntax error, should be $(dtapName)
- script: echo env ENVIRONMENTNAME = $ENVIRONMENTNAME # Does work, the variable is set as environment value, you can use it with syntax $NAME.
- script: echo env DTAPNAME = $DTAPNAME # Does work,the variable is set as environment value, you can use it with syntax $NAME.
- template: pipeline-infra.extra.yaml
parameters:
environment: $(environmentName) # Environment creation happens at compile time, if you pass marco syntax to template, it will cause the error, should use template expreesion ${{ variables.environmentName }}.
dtapName: $(dtapName) # not work here, should define it out of variable group, and transfer with parameter expression same as above.
For your template yaml:
Environment creation happens at compile time, so if you define $(environmentName) in main yaml for the template, it cannot get the value, you should use template expression ${{ variables.environmentName }} in main yaml to transfer the value.
No variable defined in template, you cannot use ${{ variables.var }}, you should use parameters expression ${{ parameters.var }}.
parameters:
- name: environment
type: string
- name: dtapName
type: string
jobs:
- deployment: 'Debug_002'
# environment: ${{ parameters.environment }} #❗Fails, should use ${{ variables.environmentName }} in main yaml for template.
environment: 'develop-o'
pool:
vmImage: 'ubuntu-latest'
variables:
- name: placeholder
value: 'none'
# - group: 'project-${{ parameters.dtapName }}' #❗Fails, same as above, it cannot get the value
strategy:
runOnce:
deploy:
steps:
- script: echo [ pipeline-infra.extra.yaml ]
- script: echo Environment = ${{ parameters.environment }} # WORKS
- script: echo "Environment (v) = ${{ variables.environmentName }}" #❗FAILS, should use parameter expression as no variable defined.
- script: echo Parameter = ${{ parameters.dtapName }} # WORKS
- script: echo PARAMETER = $DTAPNAME # WORKS
- script: echo Variable = ${{ variables.dtapName }} #❗FAILS, same as above.
- task: Bash#3
displayName: VariableListing 2
inputs:
targetType: inline
script: 'env | sort'

Related

Not able to set variable based on parameters in YAML

I'm trying to build a new yaml file that reads keyvault secrets based on the parameters at the runtime and declared variables with the condition as per the parameters, but this isn't working.
- name: azure_subscription
displayName: " Select subscription "
type: string
default: "service-connection-dev"
values:
- 'service-connection-dev'
- 'service-connection-sit'
- 'service-connection-tes'
- 'service-connection-prd'
variables:
- ${{ if eq('${{ parameters.azure_subscription }}', 'service-connection-sit') }}:
name: key_vault
value: 'core-kv-sit'
- ${{ if eq('${{ parameters.azure_subscription }}', 'service-connection-dev') }}:
name: key_vault
value: 'core-kv-dev'
stages:
- stage: Validate
${{ if eq(parameters.azure_subscription, 'service-connection-dev') }}:
pool:
name: agent-pool-win-dev
${{ if eq(parameters.azure_subscription, 'service-connection-sit') }}:
pool:
name: agent-pool-win-sit
jobs:
- job: Validate
steps:
- task: AzureKeyVault#2
inputs:
KeyVaultName: "${{variables.key_vault}}"
SecretsFilter: "*"
RunAsPreJob: false
azureSubscription: ${{ parameters.azure_subscription }}
I've tried using variables inside jobs, but that is also not working. Can someone please help?
Also, I'll have to declare 2 more variables as per the parameters input, Is it possible ?
Thanks in advance
- ${{ if eq('${{ parameters.azure_subscription }}', 'service-connection-sit') }}:
You're using a literal value of "${{ parameters.azure_subscription }}" for the left side of the comparison. The comparison should just be parameters.azure_subscription.
So: - ${{ if eq(parameters.azure_subscription, 'service-connection-sit') }}:

It doesn't work pass variable from one stage to another and iterate with an each loop

I need to do an each from a variable obtained with stageDependencies previously.
It doesn't work Pass variable from one stage to another and iterate with an each loop using a split function
What am I doing wrong?
Code:
task.yml
steps:
- task: Bash#3
name: env_string
inputs:
targetType: 'inline'
script: |
environmentsStr='dev,prd'
echo "##vso[task.setvariable variable=environmentsString;isOutput=true]$environmentsStr"
pipeline
- stage: stage1
jobs:
- job: job_stage1
steps:
- template: tasks.yml
- stage: stage2
dependsOn:
- stage1
variables:
- name: envFromVar
value: 'dev,prd'
- name: envFromStageDependencies
value: $[ stageDependencies.stage1.job_stage1.outputs['env_string.environmentsString'] ]
jobs:
- job: job_stage2
steps:
- template: stage3.yml
parameters:
envFromStageDependencies: $(envFromStageDependencies)
environmentsFromVar: ${{ variables.envFromVar }}
stage3.yml
parameters:
- name: envFromStageDependencies
type: string
- name: environmentsFromVar
type: string
steps:
- ${{ each environment in split(parameters.envFromStageDependencies, ',') }}:
- bash: |
echo "env ${{ environment }}" # OUTPUT FAIL; (env dev,prd)
- ${{ each environment in split(parameters.environmentsFromVar, ',') }}:
- bash: |
echo "envVar ${{ environment }}" # OUTPUT OK, 2 iterations envVar [dev,prd]
OUTPUT
BASH -> env dev,prd
BASH -> envVar dev
BASH -> envVar prd
You should change your pipeline YAML like as below:
. . .
jobs:
- job: job_stage2
steps:
- template: stage3.yml
parameters:
environments: ${{ variables.envFromStageDependencies }}
environmentsFromVar: ${{ variables.envFromVar }}
As #Daniel Mann has mentioned, the template is processed at compile time which is before runtime. So, to pass the variable values into the template, you should use the compile time variable syntax (${{ variables.varName }}) instead of runtime variable syntax ($(varName)).
For more details, you can reference the document "Understand variable syntax".

Using if else conditions within a Azure devops yaml pipeline template

Essentially I am trying to use a if else logic inside a yaml template instead of the caller pipeline.
I have following two pipelines azure-caller.yml and template.yaml
azure-caller.yml
parameters:
- name: test
displayName: 'select true or false'
values:
- true
- false
variables:
- name: test-true
${{ if eq(parameters.test, 'true') }}:
value: false
${{ elseif eq(parameters.test, 'false') }}:
value: true
stages:
- template: job-templates/template.yml
parameters:
testrue: $(test-true)
template.yml
parameters:
testrue: test_true
stages:
- stage: A
jobs:
- job: JA
steps:
- script: |
echo "Reverted value is" ${{ parameters.testrue }}
name: DetermineResult
How can I move the if else logic in the template.yml instead of azure-caller.yml? Your input will be helpful. thx
Below pipeline should do the trick with a limitation that the scope of variable test-true in the template will be limited only for stage: A
azure-caller.yml
parameters:
- name: test
displayName: 'select true or false'
values:
- true
- false
stages:
- template: job-templates/template.yml
parameters:
testrue: ${{ parameters.test }}
template.yml
parameters:
testrue: default
stages:
- stage: A
variables:
- name: test-true
${{ if eq(parameters.testrue, 'true') }}:
value: NewValueForTrueParam
${{ elseif eq(parameters.testrue, 'false') }}:
value: NewValueForFalseParam
jobs:
- job: JA
steps:
- script: |
echo "Echo value is" $(test-true)
Result

AzureDevops set variables based on parameter and pass to template at next stage

In Azure DevOps, I'm trying to create a pipeline which offers a simple selection of pre-set options to the user running it. These options will be converted into different combinations of parameters as specified by a templated stage (the definition of which, I have no control over). The idea of my pipeline is that frequently-used build configurations are easy to select correctly, rather than having to manually set 3 or 4 different parameters.
I need the "Build.Setup" from immutable_pipeline to print config_one, profile_one when the first radio is selected (buildType=type1), config_two, profile_two when buildType=type2, and so on.
Unfortunately I'm really struggling to get any variable value into the templated stage other than the defaults. Are ADO variables even mutable variables at all - or just constants?
I've read the MS docs extensively and understand the meaings of the different macro declaration types. I've tried many different combinations of syntaxes ${{...}}, $(...) and $[...], all behave differently but none seems able to deliver what's needed. Is this even possible? Is there a simple solution someone can suggest?
Pipeline:
name: $(Date:yyyyMMdd).$(Rev:r)
parameters:
- name: buildType
displayName: 'Type of build'
type: string
default: 'type3'
values: ['type1', 'type2', 'type3']
pool:
name: default
variables:
- name: config
value: 'defaultConfig'
- name: profile
value: 'defaultProfile'
stages:
- stage: Stage1
displayName: Prepare build config
jobs:
- job: Job1_1
steps:
- checkout: none
- task: Bash#3
name: SetVariables
inputs:
targetType: inline
script: |
p1='${{ parameters.buildType }}'
v1='$(config)'
v2='$(profile)'
echo -e "BEFORE: p1='${p1}'\n v1='${v1}'\n v2='${v2}'"
case ${p1} in
type1)
v1='config_one'
v2='profile_one'
;;
type2)
v1='config_two'
v2='profile_two'
;;
type3)
v1='config_three'
v2='profile_three'
;;
esac
echo -e "AFTER: p1='${p1}'\n v1='${v1}'\n v2='${v2}'"
echo "##vso[task.setvariable variable=config]${v1}"
echo "##vso[task.setvariable variable=profile;isOutput=True]${v2}"
- job: Job1_2
dependsOn: Job1_1
variables:
- name: variable1
value: $(config)
- name: variable2
value: $[ dependencies.Job1_1.outputs['SetVariables.profile']]
steps:
- task: Bash#3
name: GetVariables2
inputs:
targetType: inline
script: |
echo -e 'SAME STAGE: v1="$(variable1)"\n v2="$(variable2)"'
# Next stage - use computed values for "config" and "profile"
- template: templates/immutable_pipeline.yml
parameters:
config: $(config)
profile: ${{ variables.profile }}
templates/immutable_pipeline.yml:
Note that I don't have access to change this, I can't make it dependsOn: Stage1.Job1_1.
parameters:
- name: config
displayName: 'Config'
type: string
default: 'unset'
- name: profile
displayName: 'Profile'
type: string
default: 'unset'
stages:
- stage: Build
displayName: Templated build
jobs:
- job: Setup
pool:
name: default
demands:
- Agent.OS -equals Linux
steps:
- checkout: none
- script: |
echo '##[info] parameters.config=${{ parameters.config }}'
echo '##[info] parameters.profile=${{ parameters.profile }}'
I just found one solution (which is arguably simpler than using variables) using the ${{ if eq(...) }}: syntax:
name: $(Date:yyyyMMdd).$(Rev:r)
parameters:
- name: buildType
displayName: 'Type of build'
type: string
default: 'type3'
values: ['type1', 'type2', 'type3']
pool:
name: default
stages:
- template: templates/immutable_pipeline.yml
${{ if eq(parameters.buildType, 'type1') }}:
parameters:
config: config_one
profile: profile_one
${{ if eq(parameters.buildType, 'type2') }}:
parameters:
config: config_two
profile: profile_two
${{ if eq(parameters.buildType, 'type3') }}:
parameters:
config: config_three
profile: profile_three
Still interested in whether the original approach of setting variables is even possible, if only beause I've spent so much time on it.

Default parameters in nested yaml pipeline Azure DevOps

I have multiple resusable yaml templates to build my project using Azure DevOps. I want to let the default template values when I don't want to override it. The problem I have is when I nest multiple templates, it's not working as I expected, here is an example of my issue:
task_template1.yml:
parameters:
foo: 'data'
steps:
- task: Bash#3
inputs:
targetType: 'inline'
script: echo ${{ parameters.foo }}
Inside a job_template.yml I call this task_template1.yml like this:
job_template.yml:
parameters:
fooTemplate: 'data'
jobs:
- job:
steps:
- template: task_template1.yml
parameters:
${{ if variables[parameters.fooTemplate] }}:
foo: ${{ parameters.fooTemplate }}
And then my global template:
stage_build.yml:
stages:
- stage: Build
jobs:
- template: job_template.yml
parameters:
fooTemplate: 'hello'
So in this case, by default I expect that my variable fooTemplate will set the value of the variable foo inside my task_template1.yml to hello because I specify it.
But it's not the case, because it looks like ${{ if variables[parameters.fooTemplate] }}: is evaluated before the build start so foo is set to data instead of hello. Conversely if I would like to keep the default value of fooTemplate (data) I just want to not specify it like this:
stages:
- stage: Build
jobs:
- template: job_template.yml
It looks like a problem of nested templates. How can I do to fit this two rules? Feel free to ask me some more details if needed.
EDIT:
I always have this message : 'foo has no value: undefined'
Other case that I have, instead of a simple parameter:
parameters:
foo:'data'
foo1:'data1'
steps:
- task: Bash#3
inputs:
targetType: 'inline'
script: echo ${{ parameters.foo }}
So for the job I have:
parameters:
fooTemplate:
foo:'data'
foo1:'data1'
jobs:
- job:
steps:
- template: task_template1.yml
parameters:
${{ if ne(parameters.fooTemplate.foo, ' ') }}:
foo: ${{ parameters.fooTemplate.foo }}
${{ if ne(parameters.fooTemplate.foo1, ' ') }}:
foo: ${{ parameters.fooTemplate.foo1 }}
And then same case if I don't specify the foo and foo1 value I have the undefined error, instead of taking the default values:
stages:
- stage: Build
jobs:
- template: job_template.yml
I think something is wrong with your syntax here:
parameters:
${{ if variables[parameters.fooTemplate] }}:
foo: ${{ parameters.fooTemplate }}
Try this way(according to this):
parameters:
${{ if eq(parameters.fooTemplate, true) }}:
foo: ${{ parameters.fooTemplate }}
It works on my side.
Notes:
You can have something like this:
parameters:
fooTemplate: 'data'
jobs:
- job:
steps:
- template: task_template1.yml
parameters:
${{ if ne(parameters.fooTemplate, ' ') }}:
foo: ${{ parameters.fooTemplate }}
Now if you specify 'hello' in stage_build.yml, the task_template1.yml will output hello. And if you don't specify the parameter in stage_build.yml like this:
pool:
vmImage: 'ubuntu-latest'
jobs:
- template: job_template.yml
It will output the default data. Here're more details about Expressions and Conditions.