Am working on Azure DevOps templates lately. And came across this double curly braces syntax. Just want to know how to use double curly braces in Azure DevOps.
Have seen some posts regarding the same.
Assign conditional value to variables in Azure DevOps
Curly braces in Yaml files
If else in Azure DevOps
Conditional Variable Assignment in Azure DevOps
So lets say, I have a variable defined in a group as follows.
Also we can define variables as follows in a yaml file.
variables:
- name: BuildConfiguration
value: 'Release'
- name: finalBuildArtifactName
value: 'TempFolderName'
When should we use the double curly brace syntax?
I have idea about and using the following ways to reference variables.
variables['MyVar']
variables.MyVar
What can we accomplish with double curly braces in contrast to the above two?
Its very difficult to get things working in a yaml pipeline. Do the change, checkin, run the pipeline, see the result and circle back again. This is highly time consuming, and not smooth to say the least.
${{}} syntax is used for expressions. What's more there is another $[ <expression> ]. And here is the difference:
# Two examples of expressions used to define variables
# The first one, a, is evaluated when the YAML file is compiled into a plan.
# The second one, b, is evaluated at runtime.
# Note the syntax ${{}} for compile time and $[] for runtime expressions.
variables:
a: ${{ <expression> }}
b: $[ <expression> ]
Please check documentation here.
And as you may see here
steps:
- task: PublishPipelineArtifact#1
inputs:
targetPath: '$(Pipeline.Workspace)'
${{ if eq(variables['Build.SourceBranchName'], 'main') }}:
artifact: 'prod'
${{ else }}:
artifact: 'dev'
publishLocation: 'pipeline'
you can use variables['Build.SourceBranchName'] syntax for accessing variables. But,
variables:
- name: foo
value: fabrikam # triggers else condition
pool:
vmImage: 'ubuntu-latest'
steps:
- script: echo "start"
- ${{ if eq(variables.foo, 'adaptum') }}:
- script: echo "this is adaptum"
- ${{ elseif eq(variables.foo, 'contoso') }}:
- script: echo "this is contoso"
- ${{ else }}:
- script: echo "the value is not adaptum or contoso"
You may also use variables.foo.
Expressons are often used for conditional evaluation, dynamic steps/jobs/stages configuration etc.
I am trying to set variables based on a parameter value in a yaml pipeline. It seems that I've read many other posts which show examples like the one below that the authors have said worked, but I cannot get past issues when trying to do something like below.
I've tried many variations on this example as well, too many to list here. Sometimes it will show 'values' as a duplicate key. In other cases I've been able to try and start a run and get the prompt with environment selection, but then opening the stage dialog throws a parse error.
Is there some sort of difference between variable declaration at the top of the file vs in a stage or job? That seems to be the difference that I notice when reading through other examples.
Ultimately what I'm trying to do is set the ServiceConnection variable value based on the value of the environment parameter.
parameters:
- name: environment
displayName: Environment
type: string
values:
- DEV
- TEST
pr: none
trigger: none
pool: PrivateAgentPool
variables:
- name: 'isMain'
value: $[eq(variables['Build.SourceBranch'], 'refs/heads/main')]
- name: 'buildConfiguration'
value: 'Release'
- name: 'environment'
value: ${{ parameters.environment }}
- name: 'ServiceConnection'
${{ if eq(variables['environment'], 'DEV') }}:
value: 'svcConnectionDev'
${{ if eq(variables['environment'], 'TEST') }}:
value: 'svcConnectionTest'
Looks like your solution is almost correct. Consider the below example.
parameters:
- name: region
type: string
default: westeurope
values:
- westeurope
- northeurope
variables:
${{ if eq(parameters['region'], 'westeurope') }}:
ServiceConnection: "svcConnectionDev"
${{ else }}:
enter code here
if you want to used this ServiceConnectionvar across you can do it just by calling $ServiceConnection
you could use bash with conditions:
steps:
- bash: |
echo "##vso[task.setvariable variable=ServiceConnection]svcConnectionDev"
condition: eq('${{parameters.environment}}', 'DEV')
I am trying to do something like this
variables:
${{ if eq(variables['abc'], 'dev') }}:
someOtherVariable: '123'
with a variable defined via UI here:
It doesn't work. someOtherVariable is not defined after this.
Is there a way to use this variable in conditions? What should be the syntax?
thanks
The syntax is correct but doesn't seem to work for conditions that rely on variables within the block where variables are defined for custom variables.
It is one of the (many) quirks of the Azure Pipelines YAML processing pipeline. Some conditions, variables, templates, syntax is only available at specific stages of the YAML processing and it depends on whether you are in a pipeline, template, or decorator.
Simplest solution is to use a script step to set the variable and optionally make that step conditional:
${{ if eq(variables['condition'], 'true') }}:
script: echo '##vso[task.setvariable variable=someOtherVariable]123'
or rely on one of my tasks to do that on your behalf:
- task: SetVariable#1
inputs:
name: 'someOtherVariable'
value: '123'
condition: eq(variables['condition'], 'true')
or:
${{ if eq(variables['condition'], 'true') }}:
- task: SetVariable#1
inputs:
name: 'someOtherVariable'
value: '123'
In this scenario I want to combine job variables from jobs passed into a template with a list of common variables defined in the template (specifically output variables from a fixed job within the template).
To demonstrate this, in this example I've extended a Microsoft sample by attempting to combine a variable within the template to the passed in job. Assume for the sake of the example that bar.foo does exist and is outputted by steps in CredScan:
parameters:
- name: jobs
type: jobList
default: []
jobs:
- job: CredScan # Cred scan first
pool: MyCredScanPool
steps:
- task: MyCredScanTask#1
- ${{ each job in parameters.jobs }}: # Then each job
- ${{ each pair in job }}: # Insert all properties other than "dependsOn" and "variables"
${{ if ne(pair.key, 'dependsOn') }}:
${{ if ne(pair.key, 'variables') }}:
${{ pair.key }}: ${{ pair.value }}
dependsOn: # Inject dependency
- CredScan
- ${{if job.dependsOn}}:
- ${{ job.dependsOn }}
variables:
bar.foo: $[ dependencies.CredScan.outputs['bar.foo'] ]
${{ if job.variables }}:
${{ job.variables }}
This gives the error "Expected a mapping". Moving the indentation around does little to change this. Added a hyphen changes this to "While parsing a block mapping, did not find expected key".
I'm guessing that this is because of the various structures you can use for variables, e.g. key pair with a colon or explicitly defining name/value. Somehow combining the variable objects isn't working. Tried looping the contents of variables and then inserting the key and values by that also failed.
Also tried the variable insertion example
${{ insert }}: ${{ job.variables }}
What does at least compile is adding the job variables by themselves:
${{ if job.variables }}:
variables: ${{ job.variables }}
But then adding additional variables to this causes errors again, I assume because of the data type structures.
${{ if job.variables }}:
variables: ${{ job.variables }}
bar.foo: $[ dependencies.CredScan.outputs['bar.foo'] ]
Or
${{ if job.variables }}:
variables:
${{ job.variables }}
bar.foo: $[ dependencies.CredScan.outputs['bar.foo'] ]
Note: I actually wouldn't want the if job.variables check above the whole variables section, because I'd want to add my common variables regardless of if the job already has some of it's own. It's unclear if this can be removed entirely or live within the variables section.
Help greatly appreciated!
Cracked it finally.
Two issues were blocking previous attempts. Firstly, I needed to again iterate over the variables within job.variables rather then just trying to dump job.variables in, but when I'd tried this previously I could never get it to compile. This was due to using hyphens and them not being needed at all in this context.
Second issue was the name of the "key" in the variable pair. In the context of variables it's "name" and not "key", which is of course obvious when you realise as they are defined that way when setting them explicitly.
The working result is as follows, hope this helps someone else:
variables:
bar.foo: $[ dependencies.CredScan.outputs['bar.foo'] ]
${{ if job.variables }}:
${{ each var in job.variables }}:
${{var.name}}: ${{ var.value }}
I'm trying to assign one of 2 values to a variable in addition to variable group and can't find the reference that how to use IF ELSE.
Basically I need to convert this jerkins logic to azure DevOps.
Jenkins
if (branch = 'master') {
env = 'a'
} else if (branch = 'dev'){
env ='b'
}
I found 1 reference from the following one, but this one seems to work if the variables section doesn't have variable groups.
https://stackoverflow.com/a/57532526/5862540
But in my pipeline, I already have a variable group for secrets, so I have to use name/value convention and the example doesn't work with the errors like expected a mapping or A mapping was not expected or Unexpected value 'env'
variables:
- group: my-global
- name: env
value:
${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
env: a
${{ if eq(variables['Build.SourceBranchName'], 'dev') }}:
env: b
or
variables:
- group: my-global
- name: env
value:
${{ if eq(variables['Build.SourceBranchName'], 'master') }}: a
${{ if eq(variables['Build.SourceBranchName'], 'dev') }}: b
This code works.
I'm doing similar with parameters.
variables:
- name: var1
${{ if eq(parameters.var1, 'custom') }}:
value: $(var1.manual.custom)
${{ if ne(parameters.var1, 'custom') }}:
value: ${{ parameters.var1 }}
Update 09/09/2021
We have now natively if else expression and we can write it like
variables:
- group: PROD
- name: env
${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
value: a
${{ else }}:
value: b
steps:
- script: |
echo '$(name)'
echo '$(env)'
Original reply
Syntax with template expressions ${{ if ...... }} is not limited only to job/stage level. Both below pipeline does the same and produce the same output:
stages:
- stage: One
displayName: Build and restore
variables:
- group: PROD
- name: env
${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
value: a
${{ if eq(variables['Build.SourceBranchName'], 'dev') }}:
value: b
jobs:
- job: A
steps:
- script: |
echo '$(name)'
echo '$(env)'
variables:
- group: PROD
- name: env
${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
value: a
${{ if eq(variables['Build.SourceBranchName'], 'dev') }}:
value: b
steps:
- script: |
echo '$(name)'
echo '$(env)'
Microsoft a few weeks ago released a new feature for YAML pipeliens that just lets you do that: IF ELSE notation.
https://learn.microsoft.com/en-us/azure/devops/release-notes/2021/sprint-192-update#new-yaml-conditional-expressions
Writing conditional expressions in YAML files just got easier with the use of ${{ else }} and ${{ elseif }} expressions. Below are examples of how to use these expressions in YAML pipelines files.
steps:
- script: tool
env:
${{ if parameters.debug }}:
TOOL_DEBUG: true
TOOL_DEBUG_DIR: _dbg
${{ else }}:
TOOL_DEBUG: false
TOOL_DEBUG_DIR: _dbg
variables:
${{ if eq(parameters.os, 'win') }}:
testsFolder: windows
${{ elseif eq(parameters.os, 'linux' }}:
testsFolder: linux
${{ else }}:
testsFolder: mac
I wanted to have runtime condition evaluation, something similar to compile time:
variables:
VERBOSE_FLAG:
${{if variables['System.Debug']}}:
value: '--verbose'
${{else}}:
value: ''
but unfortunately Azure devops does not supports special kind of functions like if(condition, then case, else case) - so I've played around and find out that it's possible do double string replacement using replace function. It does looks bit hacky of course.
For example, one may want to tweak task inputs depending on whether system debugging is enabled or not. This cannot be done using "standard conditional insertion" (${{ if … }}:), because System.Debug isn't in scope in template expressions. So, runtime expressions to the rescue:
- job:
variables:
VERBOSE_FLAG: $[
replace(
replace(
eq(lower(variables['System.Debug']), 'true'),
True,
'--verbose'
),
False,
''
)
]
steps:
- task: cURLUploader#2
inputs:
# …
options: --fail --more-curl-flags $(VERBOSE_FLAG)
Note that using eq to check the value of System.Debug before calling replace is not redundant: Since eq always returns either True or False, we can then safely use replace to map those values to '--verbose' and '', respectively.
In general, I highly recommend sticking to boolean expressions (for example the application of a boolean-valued function like eq, gt or in) as the first argument of the inner replace application. Had we not done so and instead just written for example
replace(
replace(
lower(variables['System.Debug']),
'true',
'--verbose'
),
'false',
''
)
then, if System.Debug were set to e.g. footruebar, the value of VERBOSE_FLAG would have become foo--verbosebar.
I think for now you're going to need to use a task to customize with name/value syntax variables and conditional variable values. It looks like the object structure for name/value syntax breaks the parsing of expressions, as you have pointed out.
For me, the following is a reasonably clean implementation, and if you want to abstract it away from the pipeline, it seems that a simple template for your many pipelines to use should satisfy the desire for a central "global" location.
variables:
- group: FakeVarGroup
- name: env
value: dev
steps:
- powershell: |
if ($env:Build_SourceBranchName -eq 'master') {
Write-Host ##vso[task.setvariable variable=env;isOutput=true]a
return
} else {
Write-Host ##vso[task.setvariable variable=env;isOutput=true]b
}
displayName: "Set Env Value"
As far as I know, the best way to have conditional branch build is using "trigger" in your YAML, instead of implementing complex "if-else". It is also much safer, and you have more explicit controls on the branch triggers instead of relying on CI variables.
Example:
# specific branch build
jobs:
- job: buildmaster
pool:
vmImage: 'vs2017-win2016'
trigger:
- master
steps:
- script: |
echo "trigger for master branch"
- job: buildfeature
pool:
vmImage: 'vs2017-win2016'
trigger:
- feature
steps:
- script: |
echo "trigger for feature branch"
To have trigger with branches inclusion and exclusion, you could use more complex syntax of trigger with branches include and exclude.
Example:
# specific branch build
trigger:
branches:
include:
- master
- releases/*
exclude:
- releases/1.*
The official documentation of Azure DevOps Pipelines trigger in YAML is:
Azure Pipelines YAML trigger documentation
UPDATE 1:
I repost my comment here with additional notes:
I was thinking to have different pipelines because having the complexity of juggling between CI variables is not more maintainable than having multi jobs in one YAML with triggers. Having multijobs with triggers is also enforcing us to have clear distinction and provision on branch management. Triggers and conditional branches inclusions have been used for a year by my team because of these maintainability advantages.
Feel free to disagree, but to me having an embedded logic in any scripted in any steps to check which branch is currently in session and then does any further actions, are more like ad-hoc solutions. And this has given my team and me maintenance problems before.
Especially if the embedded logic tends to grow by checking other branches, the complexity is more complex later than having clear separations between branches. Also if the YAML file is going to be maintained for long time, it should have clear provisions and roadmaps across different branches. Redundancy is unavoidable, but the intention to separate specific logic will pay more in the long run for maintainability.
This is why I also emphasize branches inclusions and exclusions in my answer :)
Azure YAML if-else solution (when you have a group defined which required name/value notation use thereafter.
variables:
- group: my-global
- name: env
value: a # set by default
- name: env
${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
value: b # will override default
Of if you don't have a group defined:
variables:
env: a # set by default
${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
env: b # will override default