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_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.
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.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."
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.