How to set git tags in an Azure DevOps pipeline using templates
I recently started creating Azure DevOps templates to create more extensible pipelines. I figured setting tags would be a good place to start with templates.
Giving your pipeline permissions to tag
Your pipeline by default does not have permissions to:
- Use the git credentials it initially used to checkout your code
- Cannot by default push the tags
If you try to just straight up run a git tag
you’ll get an error along the lines of:
Set the build service permissions on the project
By setting the create tag
and contribute
permissions on the project as a whole, each repo will have access to push tags. If you want to limit it by repo, you can do that as well. For our needs, we are going to set it at the project level. Again, since we are setting a project level setting, select project settings
then repositories
. Notice we select the [project-name] Build Service under Users.
Allow the pipeline to use the git credential
We need to explicitly use the checkout
task to allow the pipeline to persistCredentials
. This will most likely be the first step in the steps
section of the template.
- checkout: self
clean: true
persistCredentials: true
Once we’ve set both of these permissions. We are ready to code away!
Writing Templates
For my needs, I want to tag to be the latest version from the CHANGELOG.md
. I decided to use the pwsh
task because I am using Microsoft hosted agents and PowerShell Core is available by default on both Linux and Windows agents.
I am making the assumption for my code that the CHANGELOG.md
is at the root of the repo.
My get-changelog.template.yml
looks like:
steps:
- pwsh: |
$cl = Get-Content 'CHANGELOG.md'
$version = ($cl | Sort-Object { [version] ($_ -replace '^.*?(\d+(\.\d+){1,3})$', '$1') } -Descending -ErrorAction SilentlyContinue)[0]
$version = $version | Select-String -Pattern "\d.\d.\d" | foreach {$_.Matches.Value}
echo "##vso[task.setvariable variable=cl_version]$version"
write-output "latest version is: $version"
workingDirectory: $(Build.SourcesDirectory)
displayName: get-changelog-version
I am using the logging command syntax to set a variable called cl_version
to equal the $version
returned from the CHANGELOG.md
. This syntax shouldn’t be utilized in all scenarios.
When writing templates it is important to understand how Azure DevOps pipelines run and execute. I am not going to cover this here, it is a completely different topic and there are other blogs that cover this, as well as Microsoft.
Notice, I didn’t specify setting tags in my get-changelog.template.yml
because I want it to be a separate template. I can just call my get-changelog.template.yml
from my set-tag.template.yml
.
I’ve also accounted for if the user wants to overwrite an existing tag. In my case, I don’t want this to occur by default so i’ll set overwrite_existing_tag
to false
. I am also able to access the cl_version
variable in my set-tag.template.yml
.
My set-tag.template.yml
looks like:
parameters:
- name: overwrite_existing_tag
type: boolean
default: false
steps:
- checkout: self
clean: true
persistCredentials: true
- template: get-changelog-version.template.yml
- ${{ if eq(parameters.overwrite_existing_tag, true) }}:
- pwsh: |
git config --global user.name "AzureDevOps Agent"
git tag "$(cl_version)" --force
git push origin "$(cl_version)" --force
Write-Output "Tagging $(Build.Repository.Name) with $(cl_version)"
displayName: set-tag
- ${{ if eq(parameters.overwrite_existing_tag, false) }}:
- pwsh: |
git config --global user.name "AzureDevOps Agent"
if (git tag | select-string "$(cl_version)") {
Write-Output "tag already exists for $(cl_version). Set overwrite_existing_tag to true if you want to override it"
exit 1
}
else {
git tag "$(cl_version)"
git push origin "$(cl_version)"
Write-Output "Tagging $(Build.Repository.Name) with $(cl_version)"
}
displayName: set-tag
The azure-pipelines.yml file
Now in my regular pipelines file, I only want to set a tag if we’re running on the master branch, so it looks like:
trigger:
branches:
include:
- '*'
name: $(BuildID)
pool:
vmImage: 'ubuntu-latest'
steps:
- ${{ if eq(variables['Build.SourceBranchName'], 'master') }}:
- template: set-tag.template.yml
Where do I store my templates?
For testing and noodling around, you can put your templates next to the regular azure-pipelines.yml
but this isn’t scalable.
For a more scalable approach, put all templates in their own repo and publish them as an artifact. As long as the projects are located in the same organization, it is very easy to pull artifacts from other projects and repos.