Azure pipeline stage that depends on mutually exclusive stages

10 Jun 2023

Azure DevOps YAML pipelines provide flexibility for creating complex build and deployment flows. They allow you to store these pipelines as Pipeline as Code (PaC) within the codebase, which improves version control and collaboration.

In this article, we will discuss how to sequence stages in YAML pipelines using the "dependsOn" property. This property allows you to define dependencies between stages, enabling different deployment paths based on these dependencies. You can easily create the following types of flows:

  1. Parallel deployments
  2. Sequential deployments
  3. Parallel deployments where all parallel stages must succeed to proceed to the next stage
  4. Parallel deployments where success in some parallel stages allows moving to the next stage

Furthermore, we will address a common scenario that often leads to the creation of separate pipelines. Specifically, we will explore how to handle 

a stage that depends on mutually exclusive previous stages

In the example pipeline design below, it is straightforward to create two stages, Stage2 and Stage3, that depend on Stage1.

azure pipeline stage that depends on mutually exclusive stages

stages:
  stage: Stage2
    dependsOn: Stage1
    # stage2 steps

  stage: Stage3
    dependsOn: Stage1
    # stage3 steps

Similarly, if deployment to Stage2 and Stage3 is required to move to Stage4. Then you can build as shown below

stages:
  stage: Stage4
    dependsOn:
      - Stage2
      - Stage3
    # stage4 steps

Till now everything was quite easy!

Now for the tricky part, if you are tasked to allow deployment to Stage4 - when any one of the previous mutual stages executes successfully.

Just having dependsOn as shown in previous example will block proceeding to Stage4, if one of the previous stages didn’t run.

In this case, dependsOn should be coupled with pipeline conditions as shown below to achieve a mutually exclusive flow.

variables:
  executeStage3: $[eq(variables['Build.SourceBranch'], 'refs/head/hotfix')]

stages:
  - stage: Stage2
    condition: and(succeeded(), eq(variables.executeStage3, false))
    # stage 2 steps

  - stage: Stage3
    condition: and(succeeded(), eq(variables.executeStage3, true))
    # stage 3 steps

  - stage: Stage4
     dependsOn:
     - Stage2
     - Stage3
     condition: or(succeeded('Stage2'), and(eq(variables.executeStage3, true), succeeded('Stage3'))
    # stage 4 steps

In this approach, when you have a flow that should pass through Stage3, the condition causes Stage2 to be skipped instead of blocking it. Likewise, when you have a flow that should pass through Stage2, the condition causes Stage3 to be skipped.

By using this method, you can build a stage that depends on mutually exclusive stages, thus avoiding the need to create separate YAML pipelines. However, it is worth noting that this logic can become cumbersome when dealing with three or more stages in exclusion or in complex combinations.

Based on my situation, the above solution worked effectively. I hope this explanation clarifies the approach and proves helpful to you.