Release features and test on a separate environment

ARM ARM templates Azure Azure Devops Feature environment Powershel Pull-request YAML

In this blog I will talk about how we used the CI/CD environment in Azure DevOps to deploy a branch /feature to its own Azure environment.

Why

In my ideal scrum team, the Definition of Done says that the product owner or tester needs to approve/test the feature. The only way for her to do this properly is to see and test the feature on an environment as close as possible to production. You could merge and release the feature to the develop branch en deploy it but now other features might already have been started with the previous changes and it makes it hard revert the changes or release the other feature until the first one has been fixed.

Let me give an example, you have building a feature that connects to an external API. You’ve tested everything it seems to work, now you merge it into develop and the tester or product owner finds an issue that needs to be resolved in the external API, and it will take 1 month. What do you do, other features have already been branched of develop and maybe even be completed. Do you have feature toggles build in for every feature, do you manually remove the changes from develop or do you postpone the release for one month?

Would it not be great if we could fully test the feature on Azure, before anything gets merged to develop or master.

Infrastructure as code

For all this to work it’s that your whole environment is scripted so it can be deployed automatically when needed. Infrastructure as code makes deploying your environment so much easier and predictable. In Azure you can use the Azure Resource Manager (ARM) and specifically ARM Templates, these JSON templates can be checked in into source control and used in your Build and Release pipeline to create our environment.

json
{
  "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
  "contentVersion": "",
  "apiProfile": "",
  "parameters": {  },
  "variables": {  },
  "functions": [  ],
  "resources": [  ],
  "outputs": {  }
}

Pull-request

We need to decide we want to release to the new feature environment, we have chosen to do this when a pull-request is made. This has several advantages; you want to test before you complete the pull-request so it makes sense to release when a pull-request it is created. Besides that, a pull-request build, is merged with master and the build is invalidated if master changes. Because of this you are a 100% sure that if you complete the pull-request this is exactly the same code that will end up on your target branch (probably develop or master) as been released to the feature environment.

Release pipeline

Once you made sure you have a Build definition that has all the artifacts needed you can get started on the release. I’m using the preview yaml multi-stage pipelines.

We’ve created a stage trigger that builds every time the source branch is not equal the master branch, in our case these are the pull-request builds (because they are merged with master, they are not equal to master).

json
- stage: Deployment_To_Feature
    condition: and(succeeded(), ne(variables['Build.SourceBranch'], 'refs/heads/master'))
    dependsOn:
      - Build
    displayName: Deploying feature

Now that we have a stage that represents the feature release triggered by a pull-request. There is one more challenge, we need to make sure that every feature has its own unique environment in Azure so that they don’t interfere with each other. To accomplish this, we use our branching name convention to determine the id of the environment we need to roll out. For example when we have a feature with id 2223 the branch that contains the work would be called feature/2223_{name}, we want to create an environment 2223 in azure when this branch is build.

code
$variant = $env:variant
if (!$variant) {
    Write-Host "variant is null" 
    $sourcebranchname = $env:SYSTEM_PULLREQUEST_SOURCEBRANCH
    if (!$sourcebranchname) {
        Write-Host "Getting name from source branch"
        $sourcebranchname = $env:BUILD_SOURCEBRANCHNAME
        $sourcebranchname -match "([^\/]\d+)"
        Write-Host $sourcebranchname
        $variant = $Matches[0]
    }
    else {
        Write-Host "Getting name from pull-request source branch"
        $sourcebranchname -match "([^\/]\d+)"
        Write-Host $sourcebranchname
        $variant = $Matches[0]
    }
}
Write-Host "output variant is: $variant"
Write-Host "##vso[task.setvariable variable=variant;]$variant"

This script will determine this id that we call the variant. If you don’t provide one it gets it from either the pull-request source branch or from the actual source branch in the case of a non-pull-request build.
Now we have is id we can postfix it to our environment and deploy our environment and code. In the example below you see the 3 tasks that get the environment ready to deploy.

• pwsh runs the powershell script to determine the variant.
• AzureCLI tasks creates a resource group to deploy the infrastructure to.
• AzureResourceGroupDeployment task takes care of deploying the infrastructure as described in the ARM templates.

code
- pwsh: $(Agent.BuildDirectory)/${{parameters.artifactName}}/ps/determineVariant.ps1
name: Set_variables
displayName: Set variables
env:
    variant: ${{ parameters.variant }}

- task: AzureCLI@1
displayName: Create Resource Group
inputs:
    azureSubscription: ${{parameters.azureSubscription}}
    scriptLocation: "inlineScript"
    inlineScript: az group create -n res$(variant) -l ${{parameters.resourceGroupLocation}}

- task: AzureResourceGroupDeployment@2
displayName: deploy ARM template
inputs:
    azureSubscription: "${{parameters.azureSubscription}}"
    action: "Create Or Update Resource Group"
    resourceGroupName: "res$(variant)"
    location: "West Europe"
    csmFile: "$(Agent.BuildDirectory)/${{parameters.artifactName}}/ARM/azuredeploy.json"
    deploymentMode: "incremental"
    overrideParameters: -variant $(variant)

The only thing now is to make sure your tester or product owner gets the URL of the environment that contains the id, and make sure he approved the pull-request and (automatically) also completes the feature.

ARM ARM templates Azure Azure Devops Feature environment Powershel Pull-request YAML