Azure DevOps Pipelines Approval Gates

Deploying your pipeline from end-to-end is an automated process; but for some reason you want to have an approval before two stages or have hold between two stages, what can you do? You can add an approval gate to your pipeline! The approval and gates process helps you take further control of your pipeline; rather than it running from start to finish in an automated fashion, you take the control over the start of specific stages within the pipeline and decide when the pipeline finishes completely.

Why add an Approval Gate?

  • Manual approval for authorised teams/users
  • Can review the previous stage(s) to confirm the configuration/code/change is correct. An example is reviewing the Terraform plan stage prior to the approval of Terraform apply

The Approval Gate Process

I will create an example of the approval gate process by having an approval gate between two stages – terraform plan & terraform apply

A further detailed diagram from docs.microsoft.com on combining the approval gate process further into a release pipeline

Schematic view of approvals and gates in a stage
image ref:- docs.microsoft.com

Adding an Approval Gate to your Azure DevOps Pipeline

For an Approval Gate to be configured within your pipeline, you need to create an environment.

In Azure DevOps Pipelines -> Enviornments

Select New environment

Create new environment, in this example I am creating an environment called approvalgates-production

Within the newly created environment, select Approvals and checks

Now you can add your first check, I will be an myself as approver.

Now ready to edit a pipeline and make the addition(s) required.

Adding an Approval Gate to your Azure DevOps Pipeline

I am going to create a pipeline as below with two stages

Stages
    └──terraform_plan
            └──Git Checkout
            └──Terraform Init
            └──Terraform Plan
            └──Archive Files
            └──Publish Build Artifact
            └──Delete Files (Cleanup)
    └──terraform_apply
            └──Checkout: None
            └──Download Build Artifacts
            └──Extract Build Artifacts
            └──Terraform Init
            └──Terraform Plan
            └──Terraform Apply

I will add an approval gate prior to terraform_apply stage

I will add environment: ‘approvalgates-production’ to my pipeline along with some additional configuration

      continueOnError: false
      environment: 'approvalgates-production'
      timeoutInMinutes: 120
      strategy:
       runOnce:
        deploy:

Full pipeline

name: $(BuildDefinitionName)_$(date:yyyyMMdd)$(rev:.r)

trigger: none

pr: none

stages :        
  - stage: terraform_plan
    jobs:
      - job: terraform_plan
        steps:
              - checkout: self

              - task: TerraformInstaller@0
                displayName: 'install'
                inputs:
                  terraformVersion: '0.13.4'

              - task: TerraformTaskV1@0
                displayName: 'init'
                inputs:
                  provider: 'azurerm'
                  command: 'init'
                  backendServiceArm: 'tamopstf'
                  backendAzureRmResourceGroupName: 'tamopstfstates'
                  backendAzureRmStorageAccountName: 'tfstatedevops'
                  backendAzureRmContainerName: 'azureterraformbuildartifacts'
                  backendAzureRmKey: 'terraform.tfstate'
                  workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'

              - task: TerraformTaskV1@0
                displayName: 'plan'
                inputs:
                  provider: 'azurerm'
                  command: 'plan'
                  commandOptions: '-input=false -var-file="../vars/production/production.tfvars"'
                  environmentServiceNameAzureRM: 'tamopstf'
                  workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'
              
              - task: ArchiveFiles@2
                inputs:
                  rootFolderOrFile: '$(Build.SourcesDirectory)'
                  includeRootFolder: false
                  archiveType: 'tar'
                  tarCompression: 'gz'
                  archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.BuildId).tgz'
                  replaceExistingArchive: true
                  displayName: 'Create Plan Artifact'

              - task: PublishBuildArtifacts@1
                inputs:
                  PathtoPublish: '$(Build.ArtifactStagingDirectory)'
                  ArtifactName: '$(Build.BuildId)-tfplan'
                  publishLocation: 'Container'
                  displayName: 'Publish Plan Artifact'    

              - task: DeleteFiles@1
                displayName: 'Remove unneeded files'
                inputs:
                  contents: |
                    .terraform
                    tfplan

  - stage: terraform_apply
    dependsOn: [terraform_plan]
    condition: succeeded('terraform_plan')
    jobs:
    - deployment: terraform_apply
      continueOnError: false
      environment: 'approvalgates-production'
      timeoutInMinutes: 120
      strategy:
       runOnce:
        deploy:
            steps:
              - checkout: none

              - task: DownloadBuildArtifacts@0
                inputs:
                  artifactName: '$(Build.BuildId)-tfplan'
                  displayName: 'Download Plan Artifact'

              - task: ExtractFiles@1
                inputs:
                  archiveFilePatterns: '$(System.ArtifactsDirectory)/$(Build.BuildId)-tfplan/$(Build.BuildId).tgz'
                  destinationFolder: '$(System.DefaultWorkingDirectory)/'
                  cleanDestinationFolder: false
                  displayName: 'Extract Terraform Plan Artifact'

              - task: TerraformInstaller@0
                displayName: 'install'
                inputs:
                  terraformVersion: '0.13.4'

              - task: TerraformTaskV1@0
                displayName: 'init'
                inputs:
                  provider: 'azurerm'
                  command: 'init'
                  backendServiceArm: 'tamopstf'
                  backendAzureRmResourceGroupName: 'tamopstfstates'
                  backendAzureRmStorageAccountName: 'tfstatedevops'
                  backendAzureRmContainerName: 'azureterraformbuildartifacts'
                  backendAzureRmKey: 'terraform.tfstate'
                  workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'

              - task: TerraformTaskV1@0
                displayName: 'plan'
                inputs:
                  provider: 'azurerm'
                  command: 'plan'
                  commandOptions: '-input=false -var-file="../vars/production/production.tfvars"'
                  environmentServiceNameAzureRM: 'tamopstf'
                  workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'

              - task: TerraformTaskV1@0
                displayName: 'apply'
                inputs:
                  provider: 'azurerm'
                  command: 'apply'
                  commandOptions: '-input=false -auto-approve -var-file="../vars/production/production.tfvars"'
                  environmentServiceNameAzureRM: 'tamopstf'
                  workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'

Running the Pipeline

Notice the stage:- terraform_apply is in a waiting state with 0/1 checks passed?

Prior to approval, we can review the terraform plan task inside terraform_plan stage

Happy with the plan? Lets approve!

terraform_apply stage will then run

The plan has now been approved & the terraform_apply stage can now run.

GitHub Repo

Code here to allow you to create and run an approval gate; the code that has been used to create this blog post

1 comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s