Using Nx Affected in Azure Pipelines
When trying to combine the concepts of an Affected Nx projects and building and deploying them in Azure Pipelines, there is no plugin or anything readily available to do so. Since it wasn’t trivial to find and compose all the bits and pieces together, I decided to write this down. Maybe it’ll help you, or maybe you can help me improve it.
One Step Further
By default, many solutions use the diff between HEAD
and HEAD~1
to calculate
the affected projects. Such as Nrwl’s own Example of setting up distributed
Azure build for Nx workspace.
Although this may work well, I think this isn’t always optimal. Mostly because the latest run(s) may have failed, which Nx isn’t aware of. This may require to manually re-run a pipeline, or it may take an unknown amount of time before the container will re-build.
However, Azure has an API to set and list pipeline run tags. We can use the Azure CLI to add a tag for each pipeline run having a successful Nx project build.
The main steps in this guide include:
- Find the latest successful build and the corresponding SHA-1.
- Use this SHA-1 as the
--base
for thenx affected
command. - Store the affected Nx project names in output variables.
- Use these output variables to conditionally execute the corresponding jobs or stages to build and deploy Nx projects.
- After a successful build, tag the current pipeline run (for step #1 in the next run).
Find The Latest Successful Build
When the list of pipeline runs is filtered by the Nx project’s tag and sorted by
time, we need only the latest result and we can further simplify the output by
returning only the SHA-1 (sourceVersion
) in the most concise TSV format:
This will return the SHA-1 associated to the latest pipeline run tagged with
my-app
. This is the run we are looking for, as we have tagged it only after a
successful build. Now, nx affected
can determine whether this Nx project is
currently affected or not compared with the latest SHA-1 a successful build for
this Nx project was made from.
Later we will see how to set this tag for a successful build.
Write It Down For Later
To set an output variable for use in a later stage in Azure pipelines, we need
use the task.setvariable
logging command (Azure docs: Set variables in
scripts). This writes the value AFFECTED
to the output variable
BUILD_MY_APP
:
Putting It Together
With the above ingredients, we can write a script to write the output variables. Initially I wrote a Bash script is-affected.sh as that made sense at the time. Here’s the gist:
As I think Bash scripts are not very robust and not easy to maintain, I ported this to a Node.js script is-affected.js with JSDoc/TypeScript annotations. The idea stays the same, and with both scripts the output looks like this:
When system diagnostics are enabled, also the other echo
commands that
actually set the variables are printed.
To see this script in perspective, here’s an example “Prepare” stage:
Conditional Builds
The condition
for the job in another (build) stage is based on the variable
that was written with the Bash script in an earlier stage. The pattern to read
it:
Also see Azure docs to Set a variable for future stages.
When this variable has a value of AFFECTED
or TAG_NOT_FOUND
the condition
will evaluate to true
and the job to build the Nx project will run. For
brevity, here is only the relevant part:
We can move the build job(s) to a template and reuse it with nxProjectName
as
a parameter. Here’s an example how to do that:
And then in the build-container.yaml
template:
Note that stages referred to in stageDependencies
must be part of the
dependsOn
option (Prepare
in this example). Otherwise, the value will
silently resolve to Null
without warning.
Tag Successful Builds
This task should follow the build step(s) from the job above. We can tag
successful build runs with the name of the Nx project ([nx-project-name]
):
Now this command from the Bash script should find the latest tag for this Nx project in the next pipeline run:
Again, you may need to set the organization
and project
first. Here’s a
complete step:
This writes the DEPLOY_MY_APP
variable. In a later (deployment) stage, the
same idea can be applied to read this variable and conditionally deploy the
build to any environment. An example chunk of the “production” stage:
And then in deploy-container.yaml
:
Again, stages referred to in stageDependencies
must be part of the dependsOn
option (Build
in this case).
I’ll update this article as I find improvements. Hopefully this guide has been of some help or inspiration.