I’ve been building a couple of Continuous Integration / Continuous Delivery (CI / CD) pipelines on Azure DevOps lately.
Azure DevOps is formerly known as Visual Studio Team Service (VSTS). As I mentioned recently, VSTS had nothing to do with Visual Studio and we’re very happy with the new brand name.
With CI / CD, as with automation in general, I want the least code possible for maximum impact.
Something was missing in my tool belt. I needed to carry information from a task to another. I needed to get the output of a task and use it as the input of other tasks.
It’s a simple trick but one that isn’t documented prominently, so I wanted to share that with you here.
A Use Case
Let’s give an example where that technique was needed. That was actually the scenario that triggered our search for an output variable technique.
We wanted to manage the version of a micro service with Semantic Versioning, i.e. MAJOR.MINOR.PATCH.
For us, the MAJOR & MINOR belong to the code as it relates to compatibility and feature set. The PATCH part didn’t and it would be error prone to expect programmers to update a patch number in the code base each time they deploy something. We were keen to use the build number of Azure Dev Ops. That number increments each time there is a build.
We also wanted the full version (i.e. MAJOR.MINOR.PATCH) to be injected in the code so it could be returned as a service version. This way we have an easy way to identify which version of a service we are talking to.
This is a schematized version of our build pipeline:
|Fetch Full Version||Python task taking the build number as an input. It fetches the MAJOR.MINOR version from the code files, append the build number and set the Full Version variable.|
|Write Full Version||We write the Full Version variable as a build artefact. This will be used at release time.|
|Inject Version in code||Python task taking the Full Version variable and performing a simple find / replace in the code. This will allow the service to return the full version.|
|Build Docker Image||Builds a Docker from the code. This doesn’t directly use the Full Version variable.|
|Push Docker Image||Pushes the Docker image to Docker Hub. We use the Full Version as the image tag.|
|Helm Package||Packages an Helm Chart. We use the Full Version as the chart version.|
It’s easy to see that without an output variable, this pipeline would be very tedious.
The solution is pretty straightforward. It doesn’t require calling an API or an SDK, just to output something on the console:
##vso[task.setvariable variable=<variable name>;]<variable value>
Azure DevOps will automatically pick it up. So we can do that from Python, PowerShell, Bash shell, anything that can output to the console.
For instance, our Python line was:
print('##vso[task.setvariable variable=full-version;]%s' % (fullVersion))
The variable we defined here is then instantiated as a variable in Azure DevOps and can be used by subsequent tasks in the same pipeline.
Here we used it in a build. The same thing can be done in a release pipeline.
Something simple that I found extremely useful in some pipeline.
It allows us to couple loosely tasks together. The alternative is having tasks do more while they hold the state of what should be a variable.