73

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

Krzysztof Madej
  • 32,704
  • 10
  • 78
  • 107
kevmando
  • 917
  • 1
  • 10
  • 17
  • One workaround I have now is I copy the variables secion with expression into all the jobs where I need. But I'd like to make it simple global variable. – kevmando Nov 12 '19 at 17:05
  • To anyone else looking for this, here are better answers: https://stackoverflow.com/questions/57532138/can-conditional-variable-assignment-be-done-in-azure-pipelines?noredirect=1&lq=1 – Bojan Kogoj Nov 02 '20 at 14:37
  • 1
    Did you try something like this `condition: ${{eq(parameters.target, 'BuildBackEnd') }}` ? – Сергей May 19 '21 at 07:35
  • with the above 'Build.SourceBranchName' you've got to be careful. If you use branching names such as feature/adding_a_new_widget, then the branch name will grab the string after the last '/'. ie, your SourceBranchName = adding_a_new_widget... in my experience the best practice is to use the ...(contains['Build.SourceBranch'], 'refs/heads/dev') - or feature, or testing, etc...'main & master' are not really prone to this issue, but most other branches ... – CamBeeler Sep 21 '22 at 20:39

7 Answers7

69

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 }}
vi7a
  • 719
  • 5
  • 10
  • Hi, do you mean it works with variable group as well? In this thread, original issue is to combine Variable group and IF-ELSE together, not IF-ELSE itself. – kevmando Sep 28 '20 at 01:14
  • Originally, author has been looking for implementation of conversion Jenkins logic to something similar. ``` Basically I need to convert this jerkins logic to azure DevOps. Jenkins if (branch = 'master') { env = 'a' } else if (branch = 'dev'){ env ='b' } ``` I.e. if/elif As far as i know there is no if/else logic or any kind of nested conditions available in azure devops, but using this construction we can achieve autor's goal. – vi7a Sep 29 '20 at 02:13
  • 1
    I'm the author. :) it's specific for if-else with variable group. You can see the if-else example link in the middle of OP. – kevmando Oct 01 '20 at 15:55
  • 2
    Hello! I am trying to achieve something similar to the author. This syntax gives me an `Unexpected property ${{ if .... }}` in Azure's online editor. When I save the file anyway and hit the `Validate` / `Run` button, I get a popup indicating `'value' is already defined` – Hellium Oct 05 '20 at 21:06
  • Nevermind, my two conditions were evaluated to true, hence the error when validating / running. The online editor still gives me the warning but that's not a big deal. – Hellium Oct 05 '20 at 21:47
  • Thank you! this is brilliant and a great way to add a conditional variable depending on a parameter – Mert Alnuaimi Feb 21 '22 at 09:29
  • @Hellium i just faced the same issue ('value' is already defined') and sorted it now, check each of ur 'if' has and ends with an 'else' block, if you have more than one condition then u need to use 'elseif' for each of your condition and end it with an 'else'. Hope this helps someone – stranger Sep 13 '22 at 09:28
  • What if we want to add a third case where in if the first two if don't match, then the value must be say abc. ? – Shivani Dec 08 '22 at 07:24
60

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)'
Krzysztof Madej
  • 32,704
  • 10
  • 78
  • 107
  • 1
    This is the best solution in my opinion. In my case, the lines are in red in visual studio, but everything is working perfectly – MaxThom Feb 25 '21 at 20:22
  • Need to mention the difference between the compile and runtime expressions https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions compile `a: ${{ }}` and runtime `b: $[ ]`. So for the branch reference you'd use the runtime one. – Serj Sep 29 '21 at 16:56
  • How can we do the same if its a local variable or a var defined in a var group? I am trying to do this- ${{ if eq(variables['shouldRunMigrations'], 'True') }}:. This is not woring. – VivekDev Nov 21 '21 at 14:46
  • what if we have more conditions with and operator? How do we write that? I have tried this `${{ if and(eq(parameters.environment, 'others'), ne(parameters.others_environment, 'prod')}}: ` and this does not work – inquisitive Dec 30 '21 at 11:52
  • [And](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/expressions?view=azure-devops#and) supports multiple pramaters. You missed here closing bracket `${{ if and(eq(parameters.environment, 'others'), ne(parameters.others_environment, 'prod'))}}:` – Krzysztof Madej Dec 31 '21 at 11:31
  • What is the reason for `echo $(name)`? Just to demonstrate that it is empty? Or does it contain some magic value? – Siete Jun 03 '22 at 13:17
14

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
Dharman
  • 30,962
  • 25
  • 85
  • 135
11

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.

TarmoPikaro
  • 4,723
  • 2
  • 50
  • 62
Simon Alling
  • 531
  • 7
  • 14
  • Great! Good solution and explaination for the problem that `system.debug` doesn't work with the standard condition. I searched long time for that trick and use it for setting my variable `$(buildConfiguration)` to _Debug_ or _Release_, dependend on the debug flag of the azure pipline. In short form: `buildConfiguration: $[ replace( replace(lower(variables['system.debug']), 'false', 'Release'), 'true', 'Debug' )]` – Beauty Jun 10 '21 at 09:09
  • Thank you! This is the only solution that somewhat works. https://github.com/MicrosoftDocs/azure-devops-docs/issues/6970#issuecomment-1119444334 – Tomáš Fejfar May 06 '22 at 12:38
  • Awesome, harder to read, but works fine for me – Benj Jan 25 '23 at 09:30
3

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"
Josh Gust
  • 4,102
  • 25
  • 41
  • 2
    Thanks for suggestion. I think I had seen similar solution from here, but haven't tried because I hope there would be simpler solution without adding a step. I asked MS support as well, so if they have no solution, then I'll try this. – kevmando Nov 13 '19 at 17:28
  • I had exchanged e-mails with MS support and they ended up with suggesting this workaround and asked me to enter feature suggestion for MS dev community. So I'll mark this as answer now. – kevmando Dec 07 '19 at 07:13
  • @kevmando can you post a link to the feature suggestion? – Nick Graham Sep 03 '20 at 12:27
  • 1
    @NickGraham here it is. https://developercommunity.visualstudio.com/content/idea/848155/support-conditional-expression-in-variables-even-w.html but the new reply on here seems it may work now? https://stackoverflow.com/a/64017113/5862540 – kevmando Sep 23 '20 at 20:05
1

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

Eriawan Kusumawardhono
  • 4,796
  • 4
  • 46
  • 49
  • Thanks for the suggestion, but my pipeline has already used trigger for different purpose as well. (To be honest, the trigger feature itself feel like less flexible as well comparing to Jenkins. Hard to implement the conditions like cancelling the duplicated pr/branch build and so on..) Also there will be too many redundant lines if I have to repeat same jobs again just to change a single value per branch. (I can also workaround to repeat variables sections, which would be less redundant code.) – kevmando Nov 13 '19 at 17:21
  • Maybe I need to think out of the 'jenkins' box since it's not Jenkins, but the team doesn't like to have a new pipeline with no apparent advantage. – kevmando Nov 13 '19 at 17:25
  • New pipeline? No. That YAML is in one file, therefore it is can be used on one pipeline that can serve many branches, – Eriawan Kusumawardhono Nov 13 '19 at 17:42
  • What I meant as new pipeline is, new workflow working different than previous workflow. Not related with this topic, but more of trigger options since it was mentioned here. – kevmando Nov 13 '19 at 17:49
  • OP is not asking how to trigger a build based on a branch value. The question asked was how to make a variable value conditional based on the context in which the pipeline is triggered. Your solution when applied to the actual question asked forces OP to create 2 pipelines 1) trigger = master & env = a 2) trigger = dev & env = b. – Josh Gust Nov 13 '19 at 18:23
  • @kevmando You could use the trigger concept to create 2 pipelines and have your job code in a template, but IMHO that's more complicated than forcing the variables down from the group, or than creating a template for a script task. – Josh Gust Nov 13 '19 at 18:28
  • @JoshGust Yes, I'd try to avoid a template feature in azure devops since it would be the repetition of self writing jenkins library hell again. Sometimes less reuse make things easier. :) – kevmando Nov 13 '19 at 18:37
  • @JoshGust I see. Makes sense. I was thinking to have different pipelines because having the complexity of juggling between CI variables is more maintainable than having multi jobs in one YAML with triggers. It 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. – Eriawan Kusumawardhono Nov 14 '19 at 03:01
  • I have updated my answer to reflect my concern in my previous comment above. – Eriawan Kusumawardhono Nov 14 '19 at 03:14
0

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
Sameer Technomark
  • 1,399
  • 1
  • 10
  • 12