Terraform PR Commenter
Adds opinionated comments to PR's based on Terraform fmt, init, plan and validate outputs.
Summary
This Docker-based GitHub Action is designed to work in tandem with hashicorp/setup-terraform with the wrapper enabled, taking the output from a fmt, init, plan or validate, formatting it and adding it to a pull request. Any previous comments from this Action are removed to keep the PR timeline clean.
The
terraform_wrapperneeds to be set totrue(which is already the default) for thehashicorp/setup-terraformstep as it enables the capturing ofstdout,stderrand theexitcode.
Support (for now) is limited to Linux as Docker-based GitHub Actions can only be used on Linux runners.
Usage
This action can only be run after a Terraform fmt, init, plan or validate has completed, and the output has been captured. Terraform rarely writes to stdout and stderr in the same action, so we concatenate the commenter_input:
- uses: robburger/terraform-pr-commenter@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commenter_type: fmt/init/plan/validate # Choose one
commenter_input: ${{ format('{0}{1}', steps.step_id.outputs.stdout, steps.step_id.outputs.stderr) }}
commenter_exitcode: ${{ steps.step_id.outputs.exitcode }}Inputs
| Name | Requirement | Description |
|---|---|---|
commenter_type |
required | The type of comment. Options: [fmt, init, plan, validate] |
commenter_input |
required | The comment to post from a previous step output. |
commenter_exitcode |
required | The exit code from a previous step output. |
Environment Variables
| Name | Requirement | Description |
|---|---|---|
GITHUB_TOKEN |
required | Used to execute API calls. The ${{ secrets.GITHUB_TOKEN }} already has permissions, but if you're using your own token, ensure it has the repo scope. |
TF_WORKSPACE |
optional | Default: default. This is used to separate multiple comments on a pull request in a matrix run. |
EXPAND_SUMMARY_DETAILS |
optional | Default: true. This controls whether the comment output is collapsed or not. |
HIGHLIGHT_CHANGES |
optional | Default: true. This switches ~ to ! in plan diffs to highlight Terraform changes in orange. Set to false to disable. |
All of these environment variables can be set at job or step level. For example, you could collapse all outputs but expand on a plan:
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
env:
EXPAND_SUMMARY_DETAILS: 'false' # All steps will have this environment variable
steps:
- name: Checkout
uses: actions/checkout@v2
...
- name: Post Plan
uses: robburger/terraform-pr-commenter@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
EXPAND_SUMMARY_DETAILS: 'true' # Override global environment variable; expand details just for this step
with:
commenter_type: plan
commenter_input: ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}
commenter_exitcode: ${{ steps.plan.outputs.exitcode }}
...Examples
Single workspace build, full example:
name: 'Terraform'
on:
pull_request:
push:
branches:
- master
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TF_IN_AUTOMATION: true
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
terraform_version: 0.15.0
- name: Terraform Format
id: fmt
run: terraform fmt -check -recursive
continue-on-error: true
- name: Post Format
if: always() && github.ref != 'refs/heads/master' && (steps.fmt.outcome == 'success' || steps.fmt.outcome == 'failure')
uses: robburger/terraform-pr-commenter@v1
with:
commenter_type: fmt
commenter_input: ${{ format('{0}{1}', steps.fmt.outputs.stdout, steps.fmt.outputs.stderr) }}
commenter_exitcode: ${{ steps.fmt.outputs.exitcode }}
- name: Terraform Init
id: init
run: terraform init
- name: Post Init
if: always() && github.ref != 'refs/heads/master' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure')
uses: robburger/terraform-pr-commenter@v1
with:
commenter_type: init
commenter_input: ${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }}
commenter_exitcode: ${{ steps.init.outputs.exitcode }}
- name: Terraform Validate
id: validate
run: terraform validate
- name: Post Validate
if: always() && github.ref != 'refs/heads/master' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure')
uses: robburger/terraform-pr-commenter@v1
with:
commenter_type: validate
commenter_input: ${{ format('{0}{1}', steps.validate.outputs.stdout, steps.validate.outputs.stderr) }}
commenter_exitcode: ${{ steps.validate.outputs.exitcode }}
- name: Terraform Plan
id: plan
run: terraform plan -out workspace.plan
- name: Post Plan
if: always() && github.ref != 'refs/heads/master' && (steps.plan.outcome == 'success' || steps.plan.outcome == 'failure')
uses: robburger/terraform-pr-commenter@v1
with:
commenter_type: plan
commenter_input: ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}
commenter_exitcode: ${{ steps.plan.outputs.exitcode }}
- name: Terraform Apply
id: apply
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
run: terraform apply workspace.planMulti-workspace matrix/parallel build:
...
jobs:
terraform:
name: 'Terraform'
runs-on: ubuntu-latest
strategy:
matrix:
workspace: [audit, staging]
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TF_IN_AUTOMATION: true
TF_WORKSPACE: ${{ matrix['workspace'] }}
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
terraform_version: 0.15.0
- name: Terraform Init - ${{ matrix['workspace'] }}
id: init
run: terraform init
- name: Post Init - ${{ matrix['workspace'] }}
if: always() && github.ref != 'refs/heads/master' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure')
uses: robburger/terraform-pr-commenter@v1
with:
commenter_type: init
commenter_input: ${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }}
commenter_exitcode: ${{ steps.init.outputs.exitcode }}
- name: Terraform Plan - ${{ matrix['workspace'] }}
id: plan
run: terraform plan -out ${{ matrix['workspace'] }}.plan
- name: Post Plan - ${{ matrix['workspace'] }}
if: always() && github.ref != 'refs/heads/master' && (steps.plan.outcome == 'success' || steps.plan.outcome == 'failure')
uses: robburger/terraform-pr-commenter@v1
with:
commenter_type: plan
commenter_input: ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}
commenter_exitcode: ${{ steps.plan.outputs.exitcode }}
..."What's the crazy-looking if: doing there?" Good question! It's broken into 3 logic groups separated by &&, so all need to return true for the step to run:
always()- ensures that the step is run regardless of the outcome in any previous steps. i.e. We don't want the build to quit after the previous step before we can write a PR comment with the failure reason.github.ref != 'refs/heads/master'- prevents the step running on amasterbranch. PR comments are not possible when there's no PR!(steps.step_id.outcome == 'success' || steps.step_id.outcome == 'failure')- ensures that this step only runs whenstep_idhas either asuccessorfailedoutcome.
In English: "Always run this step, but only on a pull request and only when the previous step succeeds or fails...and then stop the build."
Screenshots
fmt
init
plan
validate
Troubleshooting & Contributing
Feel free to head over to the Issues tab to see if the issue you're having has already been reported. If not, open a new one and be sure to include as much relevant information as possible, including code-samples, and a description of what you expect to be happening.



