Having release notes for software might seem like a thing from the past for many teams. A lot of software is web-based and released continuously, making release notes less relevant. However, there are still many projects that are released less frequently and require tracking of what changes are part of a release.
The problem
On a recent project I worked on we needed to provide an overview of changes to a solution that is released to remote customers. This information was relevant to the DevOps team as well as the internal first line support team.
So, release notes it is, but obviously we were not overly excited by the idea of writing these by hand.
The solution in question is build and released using Azure YAML (multi-stage) pipelines. YAML pipelines are a great improvement over Classic Pipelines but are still lacking in some areas, especially when it comes to having a good overview of changes per release.
The multi-stage pipeline is responsible for a build and releases to test, acceptance and production environments. Not every build will make it to production, so we only want release notes for production releases and it should contain all changes since the previous release to production.
The release notes should contain the following information:
- Release number / version number
- Release date
- List of work items and bugs that are part of the release
- List of pull requests that are part of the release (for the development team)
Possible solutions
It makes sense to solve the problem using the pipeline we already have because all information should be available from there. We can gather all required information from the DevOps REST API but that’s not trivial. Luckily, there is an awesome DevOps extension called Generate Release Notes available that will do all the hard work for us.
The extension supports both Classic Build and Release Pipelines as well as single and multi-stage YAML Pipelines on both Windows and Linux.
It uses Handlebars templates to generate the release notes and can access all fields from the build and release REST API.
Generating a release notes file
The first step is to generate a release notes file using the extension. It’s very simple to format it using markdown so we’ll use that, but we could just as easily have generated HTML.
For now, we’ll just generate a markdown file and publish it as an artifact of the pipeline.
In our existing pipeline, directly in the product release stage, we’re going to add the following task and define an inline template for create the release notes.
- task: XplatGenerateReleaseNotes@4
inputs:
outputfile: '$(Build.ArtifactStagingDirectory)/releasenotes.md'
templateLocation: 'InLine'
inlinetemplate: |
# {{buildDetails.buildNumber}}
{{buildDetails.startTime}}
**Work Items**
|Id|Type|Title|
|-|-|-|
{{#forEach this.workItems}}
|{{this.id}}|{{lookup this.fields 'System.WorkItemType'}}|[{{lookup this.fields 'System.Title'}}]({{replace this.url "_apis/wit/workItems" "_workitems/edit"}})|
{{/forEach}}
**Pull Requests**
{{#forEach pullRequests}}
* [{{this.title}}]({{replace (replace this.url "_apis/git/repositories" "_git") "pullRequests" "pullRequest"}})
{{/forEach}}
checkStage: true
replaceFile: false
getParentsAndChildren: false
getAllParents: false
getIndirectPullRequests: false
stopOnError: false
considerPartiallySuccessfulReleases: false
checkForManuallyLinkedWI: false
wiqlFromTarget: 'WorkItems'
- publish: $(Build.ArtifactStagingDirectory)
artifact: ReleaseNotes
Note the checkStage: true
parameter, which ensures that only the changes since the last successful release to the production deployment stage are included.
The template is fairly straightforward but does contain some string replacement to make the links to work items and pull requests work in the generated markdown file.
There is a lot more possible in the template than what we’re using here, but this simple setup should suit our needs.
The generated release notes artifact that is published looks something like this:
Distributing the release notes
So now we have a markdown file as an artifact in the pipeline, but we still need to distribute it somehow.
There are several options:
- we could send release notes by mail
- publish it to some website
- publish it to a DevOps wiki
For this example, we’ll publish it to a DevOps (Services) wiki since that’s the quickest to setup, distribute and maintain.
Since DevOps wiki’s are just git repositories, we could push to it from a script task. However, there’s a nice extension for that as well: Wiki Updater Task (from the same author as the Generate Release Notes extension).
We can supply the markdown file we generated in the previous step and feed it the the Wiki Updater Task. The configuration looks like this:
- task: WikiUpdaterTask@2
inputs:
repo: 'https://dev.azure.com/m-w/MyProduct/_git/ExampleWiki'
branch: 'main'
filename: 'Root-page/MyProduct-Versions.md'
replaceFile: false
dataIsFile: true
sourceFile: '$(Build.ArtifactStagingDirectory)/releasenotes.md'
message: 'Automatic commit from pipeline'
gitname: 'pipeline'
gitemail: '[email protected]'
useAgentToken: true
localpath: '$(System.DefaultWorkingDirectory)\repo'
In order to use the file we generated in the previous step, we need to set dataIsFile: true
and sourceFile: '$(Build.ArtifactStagingDirectory)/releasenotes.md'
.
To keep adding release notes to the same page, set dataIsFile: true
. This way every release is added to the bottom of the same wiki page.
Finding the URL of the wiki repo is a bit tricky. It’s not directly available in the DevOps UI for Project wiki’s but can be found by selecting ‘clone wiki’ and copying the HTTPS URL:
Authorizing the pipeline to push to the wiki
By default, the pipeline will not be authorized to push to the wiki. There are 3 things we need to fix for that:
- Authenticate
- Allow build agents to push to the wiki repo
- Bypassing the Limit job authorization scope
Authentication can be done in several ways but the most maintainable way is to use the Build Agents OAUTH token. For YAML pipelines this token is always available and can be accessed using the $(System.AccessToken)
variable.
Luckily, the Wiki Updater Task supports this out of the box and we do not need to do anything for it.
Give the buid agent user access to the Wiki repo from the security tab of the repo in Project settings -> Repositories -> Your-Wiki-Repo -> Security. Add the user Your-Project-Name Build Service and allow ‘Contribute’ access.
By default build agents are not authorized to push to other repos other than the one they are building. This is controlled by the organization and project setting Protect access to repositories in YAML pipelines, which is enabled by default for all organisations and projects created after May 2020. For more information, see the official Microsoft Docs.
We could disable this setting, but there’s a better way. Especially in larger organizations a developer will probably not be allowed to modify organizational settings.
Fortunately the work-around is simple: we can reference the wiki repo in the pipeline as a resource and the build agent will be authorized to push to it.
Add the following piece of YAML to the top of the pipeline to give the build agent access to the wiki repo:
resources:
repositories:
- repository: wiki
type: git
name: MyProduct/ExampleWiki
ref: main
Since March 2023 it’s required to reference or checkout the repo otherwise the access token is not scoped to it. Since the extension will clone the repo we just need to add a uses
statement to the job:
- job:
uses:
repositories: [ wiki ]
If for some reason the authorization fails the following error will be shown:
The Git repository with name or identifier ExampleWiki does not exist or you do not have permissions for the operation you are attempting.
Putting everything together
Now we have all pieces in place we can gather the changes, build a markdown file and push it to our wiki. The final pipeline looks like this:
trigger:
- main
pool:
vmImage: ubuntu-latest
resources:
repositories:
- repository: wiki
type: git
name: MyProduct/ExampleWiki
ref: main
stages:
- stage: build
jobs:
- job:
displayName: build website
steps:
- bash: echo "Dummy build step"
# other stages not included in example
- stage:
displayName: deploy prod
jobs:
- deployment:
displayName: deploy website
environment: my-prod-environment
strategy:
runOnce:
deploy:
steps:
- bash: echo "Dummy deployment step"
- job:
uses:
repositories: [ wiki ] # reference to the git repo
steps:
- task: XplatGenerateReleaseNotes@4
inputs:
outputfile: '$(Build.ArtifactStagingDirectory)/releasenotes.md'
templateLocation: 'InLine'
inlinetemplate: |
# {{buildDetails.buildNumber}}
{{buildDetails.startTime}}
**Work Items**
|Id|Type|Title|
|-|-|-|
{{#forEach this.workItems}}
|{{this.id}}|{{lookup this.fields 'System.WorkItemType'}}|[{{lookup this.fields 'System.Title'}}]({{replace this.url "_apis/wit/workItems" "_workitems/edit"}})|
{{/forEach}}
**Pull Requests**
{{#forEach pullRequests}}
* [{{this.title}}]({{replace (replace this.url "_apis/git/repositories" "_git") "pullRequests" "pullRequest"}})
{{/forEach}}
checkStage: true
replaceFile: false
getParentsAndChildren: false
getAllParents: false
getIndirectPullRequests: false
stopOnError: false
considerPartiallySuccessfulReleases: false
checkForManuallyLinkedWI: false
wiqlFromTarget: 'WorkItems'
- task: WikiUpdaterTask@2
inputs:
repo: 'https://dev.azure.com/m-w/MyProduct/_git/ExampleWiki'
branch: 'main'
filename: 'Root-page/MyProduct-Versions.md'
replaceFile: false
dataIsFile: true
sourceFile: '$(Build.ArtifactStagingDirectory)/releasenotes.md'
message: 'Automatic commit from pipeline'
gitname: 'pipeline'
gitemail: '[email protected]'
useAgentToken: true
localpath: '$(System.DefaultWorkingDirectory)\repo'
If the specified wiki page does not exist, it will be created. If the page already exists, the new content will be appended to the bottom of the page.
To have a nice overview of all releases, we can add a table of contents to the top of the page:
[[_TOC_]]
The wiki page will look like this:
Conclusion
With a few simple steps we now automatically add release notes to our wiki with every release to production, giving us a nice overview of all releases.
There are a few things that could be improved, such as the formatting of the release date, which does not seem to be possible at the moment.
Another thing which I did not include in this post is using Gitversion to automatically generate the semantic version number. It’s relatively easy to add it to our build pipeline, but I will leave that for another time.
To conclude, a big thank you to Richard Fennell for creating these great free DevOps extensions.
Update March 07, 2023: Added uses
statement to ensure access token is still scoped to wiki repo.