Conditionally Deploying YAML Pipelines In Azure Devops By Branch

Apologies for the absolute word soup of a title above, but I wasn’t sure how else to describe it! So instead, let me explain the problem I’ve been trying to solve lately.

Like many organizations using Azure Devops, we are slowly switching our pipelines to use YAML instead of the GUI editor. As part of this, I’ve been investigating the best way to conditionally deploy our CI build to environments. Notably, I want our CI build to run for every check in, on every branch, but only move to the “release” stage if we are building the develop branch and/or the main trunk. As we’ll find out later, there also needs to be an override mechanism for this because while it’s a general rule, it’s also something that may need to be flexed at times.

YAML pipelines documentation can be a bit shaky at times, so most of this came from trial and error, but here’s what I found to solve the problem of conditionally deploying Azure Pipelines based on a branch.

Using Environments

Your first option is to use Environments inside Azure Devops. You can add an “Approval and Check” to an environment, and then select Branch Control.

You can then specify a comma seperated list of branches that are allowed to pass this environment gate, and be deployed to the environment :

But here’s the problem I had with this approach. Environment gates such as the above are not in source control. Meaning that it’s hard to roll out across multiple projects at once (Compared to copy and pasting a YAML file). Now that’s a small issue, but the next one is a big one for me.

These checks based on a branch actually *fail* the build, they don’t “skip” it. So for example, if a branch does not match the correct pattern, you will see this :

This can be incredibly frustrating on some screens because it’s unclear whether your build/release pipeline actually failed, or it just failed the “check”. There is also no way to override this check on an adhoc basis. Maybe that’s something you desire, but there are rare cases where I actually need to deploy a feature branch to an environment to test something, and going through the GUI to disable branch control, release, then add it back just doesn’t make sense.

Using Pipeline Variables

A more explicit way I found to control this was to use variables inside the YAML itself. For example, every project of mine currently has the following variables utilized :

variables:
  isPullRequest: $[eq(variables['Build.Reason'], 'PullRequest')]
  isDevelopment: $[eq(variables['Build.SourceBranch'], 'refs/heads/develop')]

Now anywhere in my pipeline, I can use either variables.isPullRequest or variables.isDevelopment and make conditional deployments based on these. For example, I can edit my YAML to read like so for releasing to my development environment :

- stage: Development
  condition: and(succeeded(), eq(variables.isDevelopment, true))

This basically says, the previous steps must have succeeded *and* we must be using the development branch. When these conditions are not met, instead of a failure we see :

This is so much nicer than a failure and actually makes more sense given the context we are adding these gates. I don’t want the CI build to “fail”, I just want it to skip being released.

Adding Overrides

Remember how earlier I said that on occasion, we may want to deploy a feature branch even though it’s not in develop? Well we can actually add an override variable that when set, will push through the release.

First we must go to our YAML pipeline in Azure Devops, and edit it. Up the top right, you should see a button labelled Variables. Click it and add a new variable called “forceRelease” like so :

Unfortunately, we have to do this via the GUI for every build we wish to add this variable. At this time, there is no way to add it in the YAML and have Azure Devops recognize it (But there is hope for the future!).

In our YAML, we don’t need to declare the variable ourselves, instead it’s just available for use immediately. We can just modify our Development stage to look like so :

- stage: Development
  condition: and(succeeded(), or(eq(variables.isDevelopment, true), eq(variables.forceRelease, 'true')))

Now we are saying, if the branch is development or the variable forceRelease is set to true, then push through the release. If we try and kick off a build, we can now set the runtime variable at build time to push things through, no matter the branch.

Leave a Reply

Your email address will not be published. Required fields are marked *