Swapping my Azure DevOps Pipeline Extensions release process to use Multistage YAML pipelines
In the past I have documented the build and release process I use for my Azure DevOps Pipeline Extensions and also detailed how I have started to move the build phases to YAML.
Well now I consider that multistage YAML pipelines are mature enough to allow me to do my whole release pipeline in YAML, hence this post.
My pipeline performs a number of stages, you can find a sample pipeline here. Note that I have made every effort to extract variables into variable groups to aid reuse of the pipeline definition. I have added documentation as to where variable are stored and what they are used for.
The stages are as follows
Build
The build phase does the following
Updates all the TASK.JSON files so that the help text has the correct version number
Calls a YAML template (build-Node-task) that performs all the tasks to transpile a TypeScript based task – if my extension contained multiple tasks this template would be called a number of time
Get NPM packages
Run Snyk to check for vulnerabilities – if any vulnerabilities are found the build fails
Lint and Transpile the TypeScript – if any issue are found the build fails
Run any Unit test and publish results – if any test fail the build fails
Package up the task (remove dev dependencies)
Download the TFX client
Package up the Extension VSIX package and publish as a pipeline artifact.
Private
The private phase does the following
Using another YAML template (publish-extension) publish the extension to the Azure DevOps Marketplace, but with flags so it is private and only assessible to my account for testing
Download the TFX client
Publishes the Extension to the Marketplace
This phase is done as a deployment job and is linked to an environment,. However, there are no special approval requirements are set on this environment. This is because I am happy for the release to be done to the private instance assuming the build phase complete without error.
Test
This is where the pipeline gets interesting. The test phase does the following
- Runs any integration tests. These could be anything dependant on the extension being deployed. Unfortunately there is no option at present in multistage pipeline for a manual task to say ‘do the manual tests’, but you could simulate similar by sending an email or the like.
The clever bit here is that I don’t want this stage to run until the new private version of the extension has been published and is available; there can be a delay between TFX saying the extension is published and it being downloadable by an agent. This can cause a problem in that you think you are running tests against a different version of the extension to one you have. To get around this problem I have implemented a check on the environment this stage’s deployment job is linked to. This check runs an Azure Function to check the version of the extension in the Marketplace. This is exactly the same Azure Function I already used in my UI based pipelines to perform the same job.
The only issue here is that this Azure Function is used as an exit gate in my UI based pipelines; to not allow the pipeline to exit the private stage until the extension is publish. I cannot do this in a multistage YAML pipeline as environment checks are only done on entry to the environment. This means I have had to use an extra Test stage to associate the entry check with. This was setup as follows
Create a new environment
Click the ellipse (…) and pick ‘approvals and checks’
Add a new Azure Function check
Provide the details, documented in my previous post, to link to your Azure Function. Note that you can, in the ’control options’ section of the configuration, link to a variable group. This is a good place to store all the values, you need to provide
URL of the Azure Function
Key to us the function
The function header
The body – this one is interesting. You need to provide the build number and the GUID of a task in the extension for my Azure Function. It would be really good if both of these could be picked up from the pipeline trying to use the environment. This would allow a single ‘test’ environment to be created for use by all my extensions, in the same way there are only a single ‘private’ and ‘public’ environment. However, there is a problem, the build number is picked up OK, but as far as I can see I cannot access custom pipeline variables, so cannot get the task GUID I need dynamically. I assume this is because this environment entry check is run outside of the pipeline. The only solution can find is to place the task GUID as a hard coded value in the check declaration (or I suppose in the variable group). The downside of this is it means I have to have an environment dedicated to each extension, each with a different task GUID. Not perfect, but not too much of a problem
In the Advanced check the check logic
In control options link to the variable group contain any variables used.
Documentation
The documentation stage again uses a template (generate-wiki-docs) and does the following
- Use the extension and task manifest files to generate YAML usage documentation using one of my tasks
- Uploads the extension readme file to a WIKI using another of my tasks
- Uploads the extension YAML usage file to a WIKI
Public
The public stage is also a deployment job and linked to an environment. This environment has an approval set so I have to approve any release of the public version of the extension.
As well as doing the same as private stage this stage does the following
- Same a private stage
- Send a Tweet saying I have release a new version of the extension using my Artifact Description extension to get the text of the PR
- Update the pipelines minor build number variable to make sure then I next release I have a higher build number
Summary
It took a bit of trial and error to get this going, but I think I have a good solution now. The fact that the bulk of the work is done using shared templates means I should get good reuse of the work I have done. I am sure I will be able to improve the template as time goes on but it is a good start