Azure DevOps YAML Pipelines: What I’ve Learned & Best Practices
Templating is a powerful tool to accelerate development of pipelines in Azure Devops (Azure Pipelines). Templates are reusable pieces of logic that can be shared with multiple pipelines, in different Projects. (Heck, these pipelines dont even have to in the same Organisation). This comes with a risk though: Imagine you have 10 Pipelines depending on 1 template. You want to add a feature to that template, but how do you test it? The only way to test a Pipeline is … well run it. Make a change to a template, hit Run and hope for the best.
❌ A change to a template might work for 1 pipeline but not for the other.
Once you got a pipeline running without any errors (yay), after you made the template change, you still have to validate all other pipelines that are depending on this template.
❌ A change to a template won’t trigger the depended pipelines.
In fact it’s quite frustrating if a pipeline suddenly fails, and you can’t find any changes in the commit history, and only to find out somebody changed the referenced template.
👇 Follow this article for how to remediate these risks.
External Templates
When you store your pipeline templates in a separate repository, you can pin a specific version. When you pin a version, you can rest asure that the consumed templates will not change, even thought the upstream repository has changed.
# azure-pipelines.yml
# Add a link to the repo
resources:
repositories:
- repository: devops
type: git
name: ProjectName/devops
ref: release/1
Templates and Parameters
Template parameters are the essence of a template. Using parameters you can re-use templates in different pipelines, and in different projects. These pipelines can re-use the same shared logic, and by using parameters, still be able to customize to their own needs.
Example:
Let’s say we have a template.yml that looks like this
# template.yml
parameters:
environment: development
stages:
- stage: ${{ parameters.environment }}
jobs:
- job: deploy
steps:
- script: echo 'Do some deployment stuff'
You can re-use the above template in a pipeline:
# azure-pipelines.yml
...
stages:
- template: template.yml
parameters:
environment: test- template: template.yml
parameters:
environment: acceptance- template: template.yml
parameters:
environment: production
💡 Unlike Classic UI Task Groups, YAML Templates can be used across Devops Projects! To achieve this, store the templates in a separate git repo, and link it by declaring it in the repositories
: section
versus Variables
Here is nice detailed explanation of the inner workings of Variables in Azure Devops Pipelines. It says that it only focuses on YAML Templates, but the overall concepts like precedence still apply on the classic UI style pipelines.
Now Parameters do only exist in YAML Templates, and like Variables they are settable by the User.
Here is a 👉Gotcha: Unlike variables, parameters are only set once, at the beginning of the build run. Parameters are used to dynamically construct the entire Pipeline based on custom logic. Once it is set, it cannot be changed during runtime. Variables, on the other hand, can change during the pipeline run, for example you can have the deployPath
variable change in each Stage.
Link: https://docs.microsoft.com/en-us/azure/devops/pipelines/process/templates?view=azure-devops
Another 👉Gotcha is that you can’t simple reference to a Parameter in an inline script like a Variable, you’ll have to use a clunky format expression to get the value in the script as a normal variable:
#You can't do this
Write-Host $(MyParameter)#But do this
${{ format('$MyParameter = "{0}"', parameters.MyParameter) }}
Write-Host $MyParameter
Another way to avoid the format expression is to pass that parameter as an environment variable in the script task:
- powershell: |
Write-Host "This is my $env:PARAMETER"
env:
PARAMETER: ${{ parameter.MyParameter }}
💡You can pass a Variable as a Template parameter
❌ But remember the value of the Variable has to be set before the Pipeline run (so it has to be declared in the variables:
section).
❌Also take note that pipeline variables (the one you set in the UI) cannot be used to pass as Parameters.
# azure-piplines.yml
...
- template: templates/mytemplate.yml@repo
parameters:
myParameter: $(MyVariable)
One Template to rule them all
Actually don’t do this. Avoid big bang templates, like with any code.
You can leverage Nested Templates to keep keep templates small and modular. Group logical connected tasks together. Then create a higher level templates using several smaller templates. This way you can keep your changes more isolated.
NOTE: When nesting templates, use the relative path of the caller template instead of the usual syntax — template: name@repo
Release/Deployment Pipelines
In Classic UI Pipelines we had a separate menu item Pipelines — Releases in the sidebar. With YAML Templates, this is not the case anymore. CI and CD are all located in Pipelines.
Because of this, you can’t reuse existing Deployment Groups. To have a similar functionality using YAML, you need to create Environments in each Project. And if you want to target a VM inside that Environment, you have to register it first.
As a result there will be an agent installed on the VM specifically for that Azure Devops project/Environment.
😠You have to do this for every Environment, for every Project, which is very tedious when doing this manually.
Also if your VM is hosting multiple products, you might end up with a lot of agents listeners (they don’t take that much resources when idle, but still something to consider)
👌 The Azure Devops team recently added manual Approvals to Environments. When I started to adopt the YAML templates, it was a huge dealbreaker for me when it didn’t have it at that time. Other pre-stage checks like Azure Functions, Business Hours etc.. are also available by now, but post-deployment gates are still not available.
Key Vault Integration
Now let’s talk about Variables and Variable Groups.
It’s a good thing that Azure Devops supports Secrets out-of-the-box in Variable Groups. Here are some lacking features:
- You can’t view a Secret once set
- You can’t clone a Secret
- When somebody makes a change, it’s not audited
You can integrate an Azure Key Vault to solve the above issues. It’s quite cheap if you only use it for this purpose.
I can’t explain it better how to setup one as here.
Testable Scripts
I started convert inline scripts into standalone scripts, because the logic was getting more complicated and running pipelines as a debugging tool can waste a lot of time. Remember I said the only way to test a Pipeline is to run it? Well having portions of logic converted as stand alone scripts will allow you the test them separately, and in a local environment.
So I converted these inline script logic as actual script files (mostly .ps1
) stored in the same Azure Repo where the YAML Templates are residing.
To use these scripts in a Pipeline you need to checkout the linked repo in the main pipeline like so:
# azure-pipelines.yml
# Add a link to the repo
resources:
repositories:
- repository: devops
type: git
name: ProjectName/devops
steps:
- checkout: self
path: s
# This is important, because when checking out multiple repos,
# if you don't set the path, the source
# code will be move to a subfolder named after the repo instead of
# the usual $(Build.SourcesDirectory)
- checkout: devops
....
Then call the script as any other script:
# using the script in the main or template.yml is done like this
# the linked repo is checked out as a sub folder, so it is just a
# file path with the linked repo name (devops) as root- steps:
- task: PowerShell@2
inputs:
filePath: devops/scripts/myscript.ps1
✅ A nice feature about this is that you can now pass ${{ parameters.MyParam }}
as a script argument when using a template, as with an inline script you would have to use the format expression first @
⚡ If you liked this, check out my other Azure Devops article demonstrating how to do Terraform tasks.
⚡ Or if you want to setup an IIS Blue/Green environment, check out this article
⚡ Example template on how to setup an Azure Devops Artifact feed as an Powershell Repository
⚡Want to run a Selenium PyTest in AzDo? Check these snippets out