GitHub Action
Terraform PR Commenter
Adds opinionated comments to PR's based on Terraform fmt
, init
, plan
and validate
outputs.
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_wrapper
needs to be set totrue
(which is already the default) for thehashicorp/setup-terraform
step as it enables the capturing ofstdout
,stderr
and theexitcode
.
Support (for now) is limited to Linux as Docker-based GitHub Actions can only be used on Linux runners.
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 }}
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. |
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 }}
...
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.plan
Multi-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 amaster
branch. 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_id
has either asuccess
orfailed
outcome.
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."
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.