Displaying Terraform Plans in GitHub PRs with GitHub Actions

In this blog post, I’ll show you a cool trick – how to share your Terraform Plan right in the comments of your GitHub Pull Request using GitHub Actions. This comes with some handy benefits for your workflow:

  • Streamlined Review Process: Easily share the Terraform Plan within the PR comments, saving reviewers from digging into additional logs.
  • Enhanced Visibility: Users get a clear view of the plan without the need for extra steps, making the review more efficient.
  • User-Friendly Integration: GitHub Actions automates the process, seamlessly incorporating the Terraform Plan into your PR, enhancing collaboration.

How to achieve this

As part of my Action workflow, I have used a step that is part of this marketplace action

Prior to running the GitHub Action

You will need to create some GitHub secrets that will be used:

  • CLIENT_ID: Client ID of the Azure AD Service Principal responsible for Terraform deployment.
  • CLIENT_SECRET: Client Secret linked to the Azure AD Service Principal for Terraform deployment.
  • DEPLOYMENT_SUBSCRIPTION_ID: Azure subscription ID where the Terraform storage account is located for storing the .tfstate file.
  • SUBSCRIPTION_ID: Azure subscription ID where the Terraform code is intended to be deployed.
  • TENANT_ID: Azure AD tenant ID associated with the deployment.
  • USER_TOKEN: A GitHub Personal Access Token (PAT) is required. This can be created here. Feel free to fine-grain the permissions only for the repository.

Once your secrets are configured, and the workflow is customised, the GitHub Action will trigger automatically on each push or pull request to the specified branches. The Terraform Plan will be updated in the PR comments, providing an overview. (More on this below)

Updating the Pull-Request step

In your GitHub Actions workflow, customize the Update Pull Request step to suit your environment and requirements. This step essentially captures the essential information about the Terraform Plan and updates the PR comments.

    - name: Update Pull Request
      uses: actions/github-script@v7
      with:
        github-token: ${{ secrets.USER_TOKEN }}
        script: |
          const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
          #### Terraform Initialization 🤖\`${{ steps.init.outcome }}\`
          #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
          #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
          <details><summary>Show Plan</summary>

          \`\`\`\n
          ${{ steps.plan.outputs.stdout }}
          \`\`\`

          </details>

          *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;

          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: output
          })

Lets breakdown this step, it will be used to take the Terraform outputs and update the pull-request comments, the main areas to point out:

  • It will comment the step outcomes for your terraform init, terraform fmt and terraform validate
  • terraform plan outcome and if successful, it will also show a terraform plan
  • Will also include which PAT user account it will push the comment

Looking at the below screenshot – this will be how the comment will look in GitHub, awesome!

How to run the full workflow

Update the env values according to your requirements:

  • tf_resource_group_name – Resource Group Name of the Terraform state Storage account
  • tf_storage_account_name – Storage Account name to where the Terraform state file will be stored
  • tf_state_container – Storage Account Container in which the Terraform state file will be stored
  • tf_state_key – Name of the Terraform state file
name: Terraform Plan
on: 
  push:
    branches:
     - main
  pull_request:
    branches:
     - main
  workflow_dispatch:

env:
  tf_resource_group_name: "thomasthorntoncloud"
  tf_storage_account_name: "thomasthorntontfstate"
  tf_state_container: "github-tfplan-output-to-pr"
  tf_state_key: "terraform.tfstate"
      
jobs: 
  terraform-plan-output:
    if: github.event_name == 'pull_request'
    defaults:
      run:
        working-directory: terraform-example-deploy
    name: Terraform
    environment: production
    runs-on: ubuntu-latest
    env:
      ARM_CLIENT_ID: "${{ secrets.AZURE_CLIENT_ID }}"
      ARM_SUBSCRIPTION_ID: "${{ secrets.AZURE_SUBSCRIPTION_ID }}"
      ARM_TENANT_ID: "${{ secrets.AZURE_TENANT_ID }}"
    steps:
    - name: Checkout Code
      uses: actions/checkout@v2.5.0

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v3
      with:
        terraform_wrapper: true

    - name: Terraform Init
      id: init
      run: terraform init -backend-config="resource_group_name=$tf_resource_group_name" -backend-config="storage_account_name=$tf_storage_account_name" -backend-config="container_name=$tf_state_container" -backend-config="key=$tf_state_key"
      env:
        ARM_CLIENT_ID: ${{ secrets.CLIENT_ID }}
        ARM_CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
        ARM_SUBSCRIPTION_ID: ${{ secrets.SUBSCRIPTION_ID }}
        ARM_TENANT_ID: ${{ secrets.TENANT_ID }}
      working-directory: ./terraform
        
    - name: Terraform Fmt
      id: fmt
      run: terraform fmt -check
      working-directory: ./terraform

    - name: Terraform Validate
      id: validate
      run: terraform validate -no-color
      working-directory: ./terraform
      
    - name: Terraform Plan  
      id: plan
      run: terraform plan -no-color -var deployment_subscription_id=$DEPLOYMENT_SUBSCRIPTION_ID
      env:
        ARM_CLIENT_ID: ${{ secrets.CLIENT_ID }}
        ARM_CLIENT_SECRET: ${{ secrets.CLIENT_SECRET }}
        ARM_SUBSCRIPTION_ID: ${{ secrets.SUBSCRIPTION_ID }}
        ARM_TENANT_ID: ${{ secrets.TENANT_ID }}
        DEPLOYMENT_SUBSCRIPTION_ID: ${{ secrets.SUBSCRIPTION_ID }}
      working-directory: ./terraform
      continue-on-error: false
      
    - name: Update Pull Request
      uses: actions/github-script@v7
      with:
        github-token: ${{ secrets.USER_TOKEN }}
        script: |
          const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
          #### Terraform Initialization 🤖\`${{ steps.init.outcome }}\`
          #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
          #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
          <details><summary>Show Plan</summary>

          \`\`\`\n
          ${{ steps.plan.outputs.stdout }}
          \`\`\`

          </details>

          *Pushed by: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;

          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: output
          })
    - name: Terraform Plan Status
      if: steps.plan.outcome == 'failure'
      run: exit 1

Also note, the above will only run when its as pull request as the below setting is configured.

if: github.event_name == 'pull_request'

With these steps in place, your GitHub Pull Requests will now have an automated update of your Terraform Plan, providing an overview directly in the comments. This not only streamlines your review process but also enhances collaboration and visibility.

GitHub repository with the 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