Displaying Terraform Plan as a comment in Azure DevOps Repo PRs with Azure DevOps Pipelines

In this blog post – we will displaying Terraform plans directly within Azure DevOps Repository Pull Requests as a comment using Azure DevOps Pipelines.

Prerequisites

Please read this blog post prior, it has some prerequisites that are required before this pipeline will work successfully. Ensure you have:

  • Enabled the service connection Contribute to pull requests to Allow within the Azure repos settings
  • Configured a branch policy so system variable System.PullRequest.PullRequestId works

Azure DevOps Pipeline

The Azure DevOps pipeline is going to be in a simple state, I want to show how you can display a terraform plan as a comment as part of your pull request process using the Azure DevOps Pipeline.

The pipeline will be:

  • Single stage called terraform
  • With the following tasks:
    • install: Terraform installer
    • init: Terraform initialisation
    • validate: Runs terraform validate
    • plan: Runs terraform plan
    • show: Runs terraform show , used to convert the terraform plan into a readable format. (Can be done using terraform plan, but it was erroring when attempting to use with task TerraformTaskV4@4 )
    • terraform plan to azure devops : Uploads terraform plan output to Azure DevOps as a pull request comment

Lets look at the task: terraform plan to azure devops

      - task: Bash@3
        displayName: 'Terraform Plan to Azure DevOps'
        inputs:
          targetType: 'inline'
          script: |
            TF_PLAN=$(cat $workingDirectory/tf_plan.txt)

            ADO_API=$(echo "$(System.CollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/pullRequests/$(System.PullRequest.PullRequestId)/threads?api-version=7.1-preview.1")

            TF_PLAN_comment=$(jq --arg comment "$TF_PLAN" '.comments[0].content = $comment' <<< '{"comments": [{"parentCommentId": 0,"content": "","commentType": 1}],"status": 1}')

            curl --request POST "$ADO_API" \
            --header "Authorization: Bearer $SYSTEM_ACCESSTOKEN" \
            --header "Accept: application/json" \
            --header "Content-Type: application/json" \
            --data "$TF_PLAN_comment" \
            --verbose
        env:
          workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'
          SYSTEM_ACCESSTOKEN: $(System.AccessToken)

I have outlined this task in a previous blog post, similar setup but this time with the TF_PLAN variable, it will be including the terraform plan output

The full pipeline:

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

trigger: none

pr: none

variables:
  - name: backendServiceArm
    value: 'thomasthorntonautomatic'
  - name: backendAzureRmResourceGroupName
    value: 'thomasthorntoncloud'
  - name: backendAzureRmStorageAccountName
    value: 'thomasthorntontfstate'
  - name: backendAzureRmContainerName
    value: 'ado-terraform-pr-plan-output'
  - name: backendAzureRmKey
    value: 'terraform.tfstate'
  - name: deployment_subscription_id
    value: '04109105-f3ca-44ac-a3a7-66b4936112c3'

stages :
  - stage: terraform
    jobs:
    - job: validate
      continueOnError: false
      steps:
      - task: TerraformInstaller@0
        displayName: 'install'
        inputs:
          terraformVersion: '1.6.0'

      - task: TerraformTaskV4@4
        displayName: 'init'
        inputs:
          provider: 'azurerm'
          command: 'init'
          backendServiceArm: '${{ variables.backendServiceArm}}'
          backendAzureRmResourceGroupName: '${{ variables.backendAzureRmResourceGroupName}}'
          backendAzureRmStorageAccountName: '${{ variables.backendAzureRmStorageAccountName}}'
          backendAzureRmContainerName: '${{ variables.backendAzureRmContainerName }}'
          backendAzureRmKey: 'terraform1.tfstate'
          workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'

      - task: TerraformTaskV4@4
        displayName: 'validate'
        inputs:
          provider: 'azurerm'
          command: 'validate'

      - task: TerraformTaskV4@4
        displayName: 'plan'
        inputs:
          provider: 'azurerm'
          command: 'plan'
          commandOptions: '-input=false -var deployment_subscription_id=$(deployment_subscription_id) -out=plan.tfplan'
          environmentServiceNameAzureRM: '${{ variables.backendServiceArm}}'
          workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'

      - task: Bash@3
        displayName: 'show'
        inputs:
          targetType: 'inline'
          script: |
            cd $workingDirectory
            terraform show -no-color plan.tfplan > tf_plan.txt
        env:
          workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'

      - task: Bash@3
        displayName: 'Terraform Plan to Azure DevOps'
        inputs:
          targetType: 'inline'
          script: |
            TF_PLAN=$(cat $workingDirectory/tf_plan.txt)

            ADO_API=$(echo "$(System.CollectionUri)$(System.TeamProject)/_apis/git/repositories/$(Build.Repository.Name)/pullRequests/$(System.PullRequest.PullRequestId)/threads?api-version=7.1-preview.1")

            TF_PLAN_comment=$(jq --arg comment "$TF_PLAN" '.comments[0].content = $comment' <<< '{"comments": [{"parentCommentId": 0,"content": "","commentType": 1}],"status": 1}')

            curl --request POST "$ADO_API" \
            --header "Authorization: Bearer $SYSTEM_ACCESSTOKEN" \
            --header "Accept: application/json" \
            --header "Content-Type: application/json" \
            --data "$TF_PLAN_comment" \
            --verbose
        env:
          workingDirectory: '$(System.DefaultWorkingDirectory)/terraform/'
          SYSTEM_ACCESSTOKEN: $(System.AccessToken)

Below shows the example comment of the Terraform plan:

GitHub repository containing the example code references above

Leave a Reply

Discover more from Thomas Thornton Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading

Discover more from Thomas Thornton Blog

Subscribe now to keep reading and get access to the full archive.

Continue reading