Azure Devops pipelines: Condition inside loop not working? - azure-devops

In trying to run a script on a loop only if a variable is true but the script never runs :(
It works if i set it as "condition:" to the script but i would like for the task to not even be printed if not to run, therefore would like to use the ${{if...}}
(tried with boolean and string)
parameters:
charts: []
outputChartsDebugInfo: false
steps:
- ${{ each chart in parameters.charts }}:
- script: |
echo "TEST ${{parameters.outputChartsDebugInfo}}" #this outputs true
echo "Deployment values:"
echo "chart name - ${{chart.Name}}"
#...
displayName: Deploy ${{chart.Name}}
- ${{ if eq(parameters.outputChartsDebugInfo, true) }}:
- script: |
echo "DEBUG:"
#...
displayName: Show ${{chart.Name}} debug info
Should this be working?

The first thing that you need to change is how you set your parameters. Then, you should loop over an object that contains some values. Your charts object is empty in your code.
parameters:
- name: charts
type: object
default: [one,two]
- name: outputChartsDebugInfo
default: false
steps:
- ${{ each chart in parameters.charts }}:
- script: |
echo "TEST ${{parameters.outputChartsDebugInfo}}" #this outputs true
echo "Deployment values:"
echo "chart name - ${{chart}}"
displayName: Deploy ${{chart}}
- ${{ if eq(parameters.outputChartsDebugInfo, true ) }}:
- script: |
echo "DEBUG:"
displayName: Show ${{chart }} debug info

Just it case its handy for anyone, the if inside the loop works fine, the parameters outputChartsDebugInfo was being read from a variable, but the syntax used $(var), expands only at runtime before a task executes, and the necessary is that it expands at compile time, so the fix in this case was change the syntax on the variable in the calling script:
steps:
- template: charts-pipeline-template.yml
parameters:
outputChartsDebugInfo: ${{variables.outputChartsDebugInfo}}
charts:
- Name: xxx
- Name: yyy
instead of
steps:
- template: charts-pipeline-template.yml
parameters:
outputChartsDebugInfo: $(outputChartsDebugInfo)
charts:
- Name: xxx
- Name: yyy

Related

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".

Dynamic variable group in Azure DevOps pipeline

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'

Azure Yaml DevOps variable handling

So I have a YAML file where I am trying to do something like the following:
# top of the file, declare global variables
variables:
- name: MyName
value: 'apple'
...
parameters:
- name: SwitchName
type: boolean
default: false
...
stages:
- stage: Build
displayName: 'Build'
...
jobs:
variables:
- ${{ if eq(parameters.SwitchName, true) }}:
- name: MyName
value: '$(MyName)_pie'
...
steps:
- task: PowerShell#1
inputs:
scriptType: inlineScript
inlineScript: |
Write-Output $(MyName)
...
#end of script
The goal is to control the pipeline to print apple in default run and apple_pie when the user selects that parameter in the UI to be true.
I understand that parameters are compile-time and variables are run-time. I know that to overwrite a global variable you can create a variable at a job level and change it as yo see fit. Sadly, the way how template works do not let me redeclare that variable properly and I end up with $(MyName)_pie. For some reason, Yaml fails to see that inside that template there is a runtime variable that is needed to be defined.
What are my options here to achieve desired behavior?
Anything missing in my understanding here?
You can change to use the ${{ variables.MyName }} to call the variable in root level.
For example:
variables:
- name: MyName
value: 'apple'
parameters:
- name: SwitchName
type: boolean
default: false
stages:
- stage: Build
displayName: 'Build'
jobs:
- job: A
variables:
- ${{ if eq(parameters.SwitchName, true) }}:
- name: MyName
value: '${{ variables.MyName }}_pie'
steps:
- script: "echo $(MyName)"
Result:
Since you used an if expression to reassign value, the variable will be assigned at compile time.
Refer to this doc: Runtime expression syntax
You can use the template expression format: ${{ variables.var }}

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.

Checking for null object type parameter in Azure YAML

I'm setting up a build template and can't figure out the syntax for an optional object type parameter. In my pipeline I'm calling the template like this:
stages:
- template: aspnet-core.yml#templates
parameters:
database:
name: 'SomeDatabase'
server: 'SomeServer'
I have the parameter defined like this in the template:
parameters:
database: null
I want to do a check like this in the template so I can run a task conditionally:
- ${{ if ne('${{ parameters.database }}', null) }}:
However, it's not liking the keyword null in the if statement, and I don't know how to represent the fact that it wasn't passed in. What are my options here?
You can use below expression to check if a parameter is empty. For below example
- ${{if parameters.database}}:
Below is my testing template and azure-pipeline.yml.
the script task will only get executed when database is evaluated to true. I tested and found database: "" and database: will be evalutated to false. If it is defined as database: {}, it will be evaluated to true.
Template: deploy-jobs.yaml
parameters:
database: {}
stages:
- stage: buildstage
pool: Hosted VS2017
jobs:
- job: secure_buildjob
steps:
- ${{if parameters.database}}:
- script: echo "will run if database is not empty"
displayName: 'Base: Pre-build'
azure-pipeline.yml:
stages:
- template: deploy-jobs.yaml
parameters:
database: ""
To execute some tasks if database is empty you can use below statement:
steps:
- ${{if not(parameters.database)}}:
- script: echo "will run if database is empty"
displayName: 'Base: Pre-build'
It looks like an alternative to the if syntax is to use conditions instead. This allows you to skip over the step. I had to check a property of the object though to see if it was actually passed, so not super ideal.
condition: and(succeeded(), ne('${{ parameters.database.name }}', ''))
I've found another solution to that, you can work with the length of the incoming object.
If the object is empty, it's length is 0
parameters:
- name: myObject
type: object
default: []
steps:
- ${{ if not(eq(length(parameters.myObject), 0)) }}:
- script: |
echo "hello world"
displayName: "next task"
Try to define the default as:
parameters:
database:
name: ''
server: ''
then check:
- ${{ if ne(parameters.database.name, '') }}:
<run-your-task>