Pill: Create an environment in an AzDo pipeline

Thanks to REST api creating a pipeline that is capable to create a new environment is matter of few lines of PowerShell code.

Scenario: We have to create a new environment for a new customer, and an environment consists of some resources on Azure, plus an environment in azure DevOps to use with deploy pipeline. Since we are deploying with Azure DevOps pipeline, it makes sense to create everything for new customer environment with another pipeline.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
stages: 
 
  - stage: create_environment
    jobs:
      - job: create_environment
        displayName: "Create environment if not present"
        pool:
          vmImage: windows-latest
        steps:
          - powershell: |
              write-Host "We are about to create the environment with api if not present"
              
              # Need to create the token in basic auth
              $AuthHeaders = @{
                "Authorization" = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$(AccessToken)")) 
                "Content-Type" = "application/json"
              }
              $listEnvironment = Invoke-RestMethod -uri "https://dev.azure.com/org/teamproject/_apis/distributedtask/environments?api-version=7.1-preview.1" -Headers $AuthHeaders

              # Now check if some of the environment already was present in the list.
              $envExisting = $listEnvironment.value | Where-Object { $_.name -eq "$(customer_fullname)" }

              if ($envExisting) {
                Write-Host "The environment already exists"
                exit 0
              } 
              
              # Create an environment because it does not exists
              $createEnvPayload = @{
                  name = "$(customer_fullname)"
                  description = "Created by pipeline"
              } | ConvertTo-Json
              $createUri = "https://dev.azure.com/org/teamproject/_apis/distributedtask/environments?api-version=7.1-preview.1";
              Invoke-RestMethod -uri $createUri -method POST -Headers $AuthHeaders -Body $createEnvPayload

As you can see the stage contains a job that is used to create an environment. This is based on a secret contained in the pipeline called AccessToken that is used to authenticate to Azure DevOps REST API. The script is quite simple, it first retrieves the list of environments and then checks if the environment is already present. If it is not present, it creates a new environment with a simple POST request to the REST API of Azure DevOps.

The Personal Access Token you create for this pipeline must only have access to the environment. This is a good practice to limit the scope of the token and minimize the impact of a possible leak of the token.

Create a token that can only manage environments to be used in the pipeline

Figure 1: Create a token that can only manage environments to be used in the pipeline

Using a PAT in a pipeline is a standard technique to interact with the API

What about if I tell you that you can have a better way to solve this problem?

Whenever you need to use a PAT you should try to use the token contained in a special variable called $(System.AccessToken) that is automatically populated for the single execution. Just check Official Documentation to understand how to use.

First you you must explicitly map System.AccessToken into the pipeline using a variable. A typical solution is the following.

1
2
3
4
5
6
7
8
variables:
  system_accesstoken: $(System.AccessToken)
...
            $AuthHeaders = @{
                "Authorization" = 'Basic ' + [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(":$(system_accesstoken)")) 
                "Content-Type" = "application/json"
            }
            $listEnvironment = Invoke-RestMethod -uri "https://dev.azure.com/org/teamproject/_apis/distributedtask/environments?api-version=7.1-preview.1" -Headers $AuthHeaders

The advantage of this approach is that you do not need to create a special access token and store into the variables of the pipeline. You can set the scope of the token with Scoped Authorization, but the greatest advantage is that System Token is created when the execution of the pipeline starts, and gets revoked when the execution ends. This means not only that you do not need to manage the token, but also that the risk of a leak is virtually non-existent.

Each time you need to interact with Azure DevOps REST api from a pipeline, always try to use System.AccessToken before resorting to a custom PAT.

Gian Maria.