Adds opinionated comments to PR's based on Terraform fmt
, init
, plan
and validate
outputs.
This project was forked from https://github.com/robburger/terraform-pr-commenter project, originally created by Rob Burger.
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.
The original project was not being maintained, and I wanted to add some features and improvements to it. I also wanted to make it easier to use and more flexible.
The main differences between this fork and the original project are:
- Terraform has been upgraded to v1.9.0, solving the
curl
problem. - The output from Terraform is now written to a file, instead of being passed as an argument. This is to overcome the
Argument list too long
error. - The output is now truncated in a more consistent way.
- The logs link is now added to all types of comments.
- The logs link can be more accurate by setting the
commenter_job_name
andcommenter_step_name
inputs.
This action can only be run after a Terraform fmt
, init
, plan
or validate
has completed, and the output has been captured into a file. Terraform rarely writes to stdout
and stderr
in the same action, so we concatenate them:
jobs:
my_workflow:
name: 'My Workflow'
...
permissions:
pull-requests: write # Required to post a comment
actions: read # Optional. Required to discover the Job ID required to generate the link (set only if commenter_job_name [and commenter_step_name] is set)
...
- name: Terraform Plan
id: plan
run: terraform plan
- name: Write TF output to file
run: echo "${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}" > ${GITHUB_WORKSPACE}/tf.out
- uses: juan-vg/terraform-pr-commenter@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commenter_type: fmt/init/plan/validate # Choose one
commenter_input_file: tf.out
commenter_exitcode: ${{ steps.plan.outputs.exitcode }}
commenter_job_name: 'My Workflow' # Optional
commenter_step_name: 'Terraform Plan' # Optional
Name | Requirement | Description |
---|---|---|
commenter_type |
required | The type of comment. Options: [fmt , init , plan , validate ] |
commenter_input_file |
required | The file name containing the TF output from a previous step |
commenter_exitcode |
required | The exit code from a previous step output. |
commenter_job_name |
optional | The job name where Terraform is run in. Set this to make the logs link more accurate. |
commenter_step_name |
optional | The step name where Terraform is run in. Set this to make the logs link more accurate. |
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: juan-vg/terraform-pr-commenter@v2
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_file: tf.out
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@v2
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
terraform_version: 1.9.0
- name: Terraform Format
id: fmt
run: terraform fmt -check -recursive
continue-on-error: true
- name: Write TF output to file
run: echo "${{ format('{0}{1}', steps.fmt.outputs.stdout, steps.fmt.outputs.stderr) }}" > ${GITHUB_WORKSPACE}/tf.out
- name: Post Format
if: always() && github.ref != 'refs/heads/master' && (steps.fmt.outcome == 'success' || steps.fmt.outcome == 'failure')
uses: juan-vg/terraform-pr-commenter@v2
with:
commenter_type: fmt
commenter_input_file: tf.out
commenter_exitcode: ${{ steps.fmt.outputs.exitcode }}
- name: Terraform Init
id: init
run: terraform init
- name: Write TF output to file
run: echo "${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }}" > ${GITHUB_WORKSPACE}/tf.out
- name: Post Init
if: always() && github.ref != 'refs/heads/master' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure')
uses: juan-vg/terraform-pr-commenter@v2
with:
commenter_type: init
commenter_input_file: tf.out
commenter_exitcode: ${{ steps.init.outputs.exitcode }}
- name: Terraform Validate
id: validate
run: terraform validate
- name: Write TF output to file
run: echo "${{ format('{0}{1}', steps.validate.outputs.stdout, steps.validate.outputs.stderr) }}" > ${GITHUB_WORKSPACE}/tf.out
- name: Post Validate
if: always() && github.ref != 'refs/heads/master' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure')
uses: juan-vg/terraform-pr-commenter@v2
with:
commenter_type: validate
commenter_input_file: tf.out
commenter_exitcode: ${{ steps.validate.outputs.exitcode }}
- name: Terraform Plan
id: plan
run: terraform plan -out workspace.plan
- name: Write TF output to file
run: echo "${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}" > ${GITHUB_WORKSPACE}/tf.out
- name: Post Plan
if: always() && github.ref != 'refs/heads/master' && (steps.plan.outcome == 'success' || steps.plan.outcome == 'failure')
uses: juan-vg/terraform-pr-commenter@v2
with:
commenter_type: plan
commenter_input_file: tf.out
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@v2
with:
cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
terraform_version: 1.9.0
- name: Terraform Init - ${{ matrix['workspace'] }}
id: init
run: terraform init
- name: Write TF output to file
run: echo "${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }}" > ${GITHUB_WORKSPACE}/tf.out
- name: Post Init - ${{ matrix['workspace'] }}
if: always() && github.ref != 'refs/heads/master' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure')
uses: juan-vg/terraform-pr-commenter@v2
with:
commenter_type: init
commenter_input_file: tf.out
commenter_exitcode: ${{ steps.init.outputs.exitcode }}
- name: Terraform Plan - ${{ matrix['workspace'] }}
id: plan
run: terraform plan -out ${{ matrix['workspace'] }}.plan
- name: Write TF output to file
run: echo "${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}" > ${GITHUB_WORKSPACE}/tf.out
- name: Post Plan - ${{ matrix['workspace'] }}
if: always() && github.ref != 'refs/heads/master' && (steps.plan.outcome == 'success' || steps.plan.outcome == 'failure')
uses: juan-vg/terraform-pr-commenter@v2
with:
commenter_type: plan
commenter_input_file: tf.out
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.