Azure DevOps YAML conditional Stages, Jobs and Steps

Dec 17, 2021 3:33 PM

Personal Blog
Microsoft
YAML
Azure DevOps
Azure Pipelines

Previously

In one of my previous blogs I talked about deploying .Bicep files the easy way with YAML based Azure Pipelines. Now let's have look at YAML a bit more, specifically focusing on the conditional part of Stages, Jobs and Steps.

Hierarchies within YAML

When writing a YAML for your CI/CD within Azure DevOps with Azure Pipelines for example. You might want to use multiple Stages, such as Build & Deploy, or something more advanced as a DTAP (Development, Test, Acceptance, Production). Each of these Stages might have multiple Jobs that needs to happen before you can continue to the next and each of these Jobs might have multiple Steps (via tasks) to execute specific code or steps.

It basically comes down to the following:

YAML can have multiple Stages Stages can have multiple Jobs Jobs can have multiple Steps

Below you will find an example of the hierarchy within YAML.

stages:
- stage: StageA #Highest in the hierarchy 
  jobs:
    - job: JobA #Step lower
      steps:
      - task: Bash@3 #Lowest in the hierarchy
        name: Task_A
        inputs:
         targetType: inline
         script: echo 'Task A'

Dependencies and conditions

Stages can depend on each other, the same goes for Jobs and Tasks. Conditions can go paired with dependencies, but are not required. Within conditions you can do certain checks, such as checking whether something was successful or failed or you can just always execute it.

Below a simple example of dependencies and conditions for Stages, Jobs and Steps:

trigger:
- none

pool:
  vmImage: ubuntu-latest

stages:
- stage: StageA 
  jobs:
    - job: JobA
      steps:
       - task: Bash@3
         name: Task_A
         inputs:
          targetType: inline
          script: echo 'Task A'

      
- stage: StageB
  dependsOn: StageA
  condition: succeeded() #failed(') #Always()
  jobs:
    - job: JobB
      steps:
       - task: Bash@3 
         name: Task_B
         inputs:
          targetType: inline
          script: echo 'Task B'

    - job: JobB2
      dependsOn: JobB
      condition: succeeded() #failed() #Always()
      steps:
       - task: Bash@3
         name: Task_B2
         inputs:
          targetType: inline
          script: echo 'Task B2'

- stage: StageC
  jobs:
    - job: JobC
      steps:
       - task: Bash@3 
         name: Task_C
         inputs:
          targetType: inline
          script: echo 'Task C'
       - task: Bash@3 
         name: Task_C2
         condition: succeeded() #failed() #Always()
         inputs:
          targetType: inline
          script: echo 'Task C2'

In the above example I make reference to a single dependency, but it is also possible for a Stage, Job or Step to depend on multiple others. This can simply be done by adding them underneath each other as such:

dependsOn: 
 - StageA #JobA 
 - StageB #JobB

For more information on dependencies and conditions you can check here.

Variables

Conditions can also be more complex, for example when they check variables or even variables from a different Stages, Jobs or Steps and vice versa. In the following examples we will look at some of these:

Stage to Stage variable in conditions

trigger:
- none

pool:
  vmImage: ubuntu-latest

stages:
- stage: StageA 
  jobs:
    - job: JobA
      steps:
       - task: Bash@3
         name: Task_A
         inputs:
          targetType: inline
          script: |
           myVar=true
           echo "##vso[task.setvariable variable=myVar;isOutput=true]$myVar"

      
- stage: StageB
  dependsOn: StageA
  condition: eq(dependencies.StageA.outputs['JobA.Task_A.myVar'], 'true')
  jobs:
    - job: JobB
      steps:
       - task: Bash@3
         name: Task_B
         inputs:
          targetType: inline
          script: |
           echo 'I may run now!'

Job to Job variables in conditions

trigger:
- none

pool:
  vmImage: ubuntu-latest

stages:
- stage: StageA 
  jobs:
    - job: JobA
      steps:
       - task: Bash@3
         name: Task_A
         inputs:
          targetType: inline
          script: |
           myVar=true
           echo "##vso[task.setvariable variable=myVar;isOutput=true]$myVar"
      
    - job: JobB
      dependsOn: JobA
      condition: eq(dependencies.JobA.outputs['Task_A.myVar'], 'true')
      steps:
       - task: Bash@3
         name: Task_B
         inputs:
          targetType: inline
          script: |
           echo 'I may run now!'

Step to Step variable in conditions

trigger:
- none

pool:
  vmImage: ubuntu-latest

stages:
- stage: StageA 
  jobs:
    - job: JobA
      steps:
       - task: Bash@3
         name: Task_A
         inputs:
          targetType: inline
          script: |
           myVar=true
           echo "##vso[task.setvariable variable=myVar;isOutput=true]$myVar"

       - task: Bash@3
         name: Task_B
         condition: eq(variables['Task_A.myVar'], 'true') 
         inputs:
          targetType: inline
          script: |
           echo 'I may run now!'

In the above examples, it is obvious that I use the eq (equal) expression, but there are a lot of expressions possible. For more information about expressions you can look here.

What's next?

The holidays are about to start, let's see if an interesting subject comes to mind that I can write about.