Azure DevOps custom pipeline task - Template

 A few weeks ago, working for a new customer, I was asked to create a custom task. Having done this before (a couple of times) I started up Visual Studio Code and then immediately got a feeling of déjà-vu.

Yes, there is documentation and some examples of how to create a custom pipeline task and there are some simple explanations of how to use the mock runner to unit-test your code, but it is spread all over the place, some documentation is several years old so things have changed slightly and even in the standard Microsoft GitHub repository for Azure DevOps pipeline-tasks, not every task is set up the same way.

So, since I was going to set up a complete pipeline for building, testing and packaging a custom task I decided that this time, I would at least capture all the basics so that a next time, I have everything ready out of the box and can focus on implementing the functionality instead of setting up all the plumbing.

TLDR, here: https://github.com/fgiele/AzureDevOpsTasks

 Multiple versions

The example shows how to allow two major versions of the same task within the extension package, should you at some point need to make a breaking change to the task. This allows you to maintain and update these versions should certain users of your task need to remain on the older version.

Typescript

Of course, the task is going to be written in TypeScript. Since tasks can be written in either PowerShell of Node.js and JavaScript is better guaranteed to work cross platform, using TypeScript and the transpiler is the obvious choice. And TypeScript will catch program errors in Visual Studio Code and during transpiling instead of JavaScript just giving unexpected results.
As with any Node.js project, we need some separation between production code and code needed for testing.

Corporate firewall workaround

When an Azure DevOps server runs on-premise, build servers are often (justifiably) disconnected from the internet. However, unfortunately the library in use for creating azure devops pipeline tasks tries to download a specific version of node.exe from the internet unless it is already cached. So if this is the case, the first step in the build pipeline is placing the correct file in the cache location and set the downloaded flag.
If your development environment is behind a corporate firewall, you might have to do the same here to run the tests locally.
The following location needs to exist/be created
%home%\azure-pipelines-task-lib_download\Node10

Place the following version of Node in that directory:
https://nodejs.org/dist/v10.21.0/win-x64/node.exe

Create an empty file named node10.completed and place this in:
%home%\azure-pipelines-task-lib_download

So your home location should look like this:
%home%
  azure-pipelines-task-lib
    _download
      node10.completed
      Node10
        node.exe

Restore packages: npm ci

Use npm cito restore the packages, to insure you get the same versions during the build phase as you were using during development. (Hence the package-lock.json file is part of the code repository). Since we build the versions of the task and the tests separately, this needs to be called three times (once for V1, once for V2 and once for the tests).

Static code analysis

In the pipeline, both SonarCloud and Eslint are used to analyse the code. Of course, SonarCloud takes more aspects into account such as code coverage and maintainability, but Eslint is readily available for the developer from the command-line.
Apart from that, we also run npm audit which will scan for (some) vulnerable node packages.

Unit tests and code coverage

To catch (regression) bugs early and especially when using TDD, testing is done with mocha and the mockrunner from the azure-pipelines-task-lib. Since Sonar uses lcov and Azure DevOps uses cobertura, both outputs are generated when the test is run.

Two transpiler files

Since the TypeScript transpiler needs to produce two different results, once with everything needed to run the unittests and once with the minimal needed set for the release version, there is a tsconfig.json and a tsconfig-build.json file. The build version excludes the test code and places everything in the packaging (dist) location.

Packaging

Since node tasks need their node_modules sub-directory to function once deployed, these are specifically included in the azure-devops-extension.json file. This ensures that the JavaScript files transpiled from TypeScript and the node_modules directory of the respective sources are bundled in the resulting vsix file. The packagePath instruction is used to place everything in the right location for use once it is installed on an Azure DevOps environment.

Source repo

Everything described here is available on GitHub, in an open source repository: https://github.com/fgiele/AzureDevOpsTasks

Comments

Popular posts from this blog

Using Azure Devops Service Connections in dashboard widgets

Running Azure DevOps container agents on OpenShift

NuGet Release and Pre-Release pipeline