how can I use IF ELSE in variables of azure DevOps yaml pipeline with variable group?

Azure DevopsAzure PipelinesAzure Pipelines-Yaml

Azure Devops Problem Overview


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

Azure Devops Solutions


Solution 1 - Azure Devops

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 }}

Solution 2 - Azure Devops

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)'

Solution 3 - Azure Devops

Microsoft a few weeks ago released a new feature for YAML pipeliens that just lets you do that: IF ELSE notation.

https://docs.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

Solution 4 - Azure Devops

I have discovered a somewhat useful hack for some scenarios. 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.

Solution 5 - Azure Devops

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"

Solution 6 - Azure Devops

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

Solution 7 - Azure Devops

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 :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionkevmandoView Question on Stackoverflow
Solution 1 - Azure Devopsvi7aView Answer on Stackoverflow
Solution 2 - Azure DevopsKrzysztof MadejView Answer on Stackoverflow
Solution 3 - Azure DevopsSebastian SchützeView Answer on Stackoverflow
Solution 4 - Azure DevopsSimon AllingView Answer on Stackoverflow
Solution 5 - Azure DevopsJosh GustView Answer on Stackoverflow
Solution 6 - Azure DevopsSameer TechnomarkView Answer on Stackoverflow
Solution 7 - Azure DevopsEriawan KusumawardhonoView Answer on Stackoverflow