diff --git a/CHANGELOG.md b/CHANGELOG.md index 94ec04b6..b099dce2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v1.2.0 + +- Bump to Terraform v0.14.5 internally (only affects `fmt`) +- Change to leave `fmt` output as-is +- Add colourisation to `plan` diffs where there are changes (on by default, controlled with `HIGHLIGHT_CHANGES` environment variable) +- Update readme + +## v1.1.0 + +- Adds better parsing for Terraform v0.14 + ## v1.0.0 - Initial release. diff --git a/Dockerfile b/Dockerfile index 057da245..2b6f7c8d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM hashicorp/terraform:0.12.28 +FROM hashicorp/terraform:0.14.5 LABEL repository="https://github.com/robburger/terraform-pr-commenter" \ homepage="https://github.com/robburger/terraform-pr-commenter" \ diff --git a/README.md b/README.md index ff585038..3ea82bdf 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,17 @@ # Terraform PR Commenter + Adds opinionated comments to PR's based on Terraform `fmt`, `init` and `plan` outputs. ## Summary + This Docker-based GitHub Action is designed to work in tandem with [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) with the wrapper enabled, taking the output from a `fmt`, `init` or `plan`, formatting it and adding it to a pull request. Any previous comments from this Action are removed to keep the PR timeline clean. Support (for now) is [limited to Linux](https://help.github.com/en/actions/creating-actions/about-actions#types-of-actions) as Docker-based GitHub Actions can only be used on Linux runners. ## Usage + This action can only be run after a Terraform `fmt`, `init`, or `plan` 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`: + ```yaml - uses: robburger/terraform-pr-commenter@v1 env: @@ -19,20 +23,24 @@ This action can only be run after a Terraform `fmt`, `init`, or `plan` has compl ``` ### Inputs -| Name | Requirement | Description | -| -------------------- | ----------- | ----------- | -| `commenter_type` | _required_ | The type of comment. Options: [`fmt`, `init`, `plan`] | -| `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 | +| -------------------- | ----------- | ----------------------------------------------------- | +| `commenter_type` | _required_ | The type of comment. Options: [`fmt`, `init`, `plan`] | +| `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. | -Both of these environment variables can be set at `job` or `step` level. For example, you could collapse all outputs but expand on a `plan`: +| 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`: + ```yaml jobs: terraform: @@ -57,7 +65,9 @@ jobs: ``` ## Examples + Single workspace build, full example: + ```yaml name: 'Terraform' @@ -71,17 +81,18 @@ 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 - env: - TF_IN_AUTOMATION: true with: cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} - terraform_version: 0.12.28 + terraform_version: 0.14.5 - name: Terraform Format id: fmt @@ -91,8 +102,6 @@ jobs: - name: Post Format if: always() && github.ref != 'refs/heads/master' && (steps.fmt.outcome == 'success' || steps.fmt.outcome == 'failure') uses: robburger/terraform-pr-commenter@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: commenter_type: fmt commenter_input: ${{ format('{0}{1}', steps.fmt.outputs.stdout, steps.fmt.outputs.stderr) }} @@ -105,8 +114,6 @@ jobs: - name: Post Init if: always() && github.ref != 'refs/heads/master' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure') uses: robburger/terraform-pr-commenter@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: commenter_type: init commenter_input: ${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }} @@ -119,8 +126,6 @@ jobs: - name: Post Plan if: always() && github.ref != 'refs/heads/master' && (steps.plan.outcome == 'success' || steps.plan.outcome == 'failure') uses: robburger/terraform-pr-commenter@v1 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: commenter_type: plan commenter_input: ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }} @@ -133,76 +138,78 @@ jobs: ``` Multi-workspace matrix/parallel build: + ```yaml ... jobs: terraform: name: 'Terraform' - runs-on: ubuntu-latest - strategy: - matrix: - workspace: [audit, staging] - env: - TF_WORKSPACE: ${{ matrix['workspace'] }} - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup Terraform - uses: hashicorp/setup-terraform@v1 - env: - TF_IN_AUTOMATION: true - with: - cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }} - terraform_version: 0.12.28 - - - 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 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - 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 - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + 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.14.5 + + - 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: plan - commenter_input: ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }} - commenter_exitcode: ${{ steps.plan.outputs.exitcode }} + 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: -1) `always()` - ensures that the step is run regardless of the outcome in any previous steps -2) `github.ref != 'refs/heads/master'` - prevents the step running on a `master` branch -3) `(steps.step_id.outcome == 'success' || steps.step_id.outcome == 'failure')` - limits the run to the specific `step_id` only when there's a `success` or `failed` outcome. + +1. `always()` - ensures that the step is run regardless of the outcome in any previous steps +2. `github.ref != 'refs/heads/master'` - prevents the step running on a `master` branch +3. `(steps.step_id.outcome == 'success' || steps.step_id.outcome == 'failure')` - limits the run to the specific `step_id` only when there's a `success` or `failed` outcome. In English: "Always run this step, but only on a pull request and only when the previous step succeeds or fails." ## Screenshots ### fmt + ![fmt](images/fmt-output.png) ### plan + ![fmt](images/plan-output.png) ## Troubleshooting & Contributing + Feel free to head over to the [Issues](https://github.com/robburger/terraform-pr-commenter/issues) tab to see if the issue you're having has already been reported. If not, [open a new one](https://github.com/robburger/terraform-pr-commenter/issues/new) and be sure to include as much relevant information as possible, including code-samples, and a description of what you expect to be happening. ## License + [MIT](LICENSE) diff --git a/entrypoint.sh b/entrypoint.sh index c4bbabf5..463bc365 100644 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -44,6 +44,9 @@ else DETAILS_STATE="" fi +# Read HIGHLIGHT_CHANGES environment variable or use "true" +COLOURISE=${HIGHLIGHT_CHANGES:-true} + ACCEPT_HEADER="Accept: application/vnd.github.v3+json" AUTH_HEADER="Authorization: token $GITHUB_TOKEN" CONTENT_HEADER="Content-Type: application/json" @@ -94,7 +97,7 @@ $INPUT if [[ $EXIT_CODE -eq 3 ]]; then ALL_FILES_DIFF="" for file in $INPUT; do - THIS_FILE_DIFF=$(terraform fmt -no-color -write=false -diff "$file" | sed -n '/@@.*/,//{/@@.*/d;p}') + THIS_FILE_DIFF=$(terraform fmt -no-color -write=false -diff "$file") ALL_FILES_DIFF="$ALL_FILES_DIFF $file @@ -178,11 +181,15 @@ if [[ $COMMAND == 'plan' ]]; then # Exit Code: 0, 2 # Meaning: 0 = Terraform plan succeeded with no changes. 2 = Terraform plan succeeded with changes. - # Actions: Strip out the refresh section (everything before the 72 '-' characters) and build PR comment. + # Actions: Strip out the refresh section, ignore everything after the 72 dashes, format, colourise and build PR comment. if [[ $EXIT_CODE -eq 0 || $EXIT_CODE -eq 2 ]]; then CLEAN_PLAN=$(echo "$INPUT" | sed -n '/Refreshing state\.\.\./!p') # Strip refresh section + CLEAN_PLAN=$(echo "$CLEAN_PLAN" | sed -nr '/-{72}/q;p') # Ignore everything after the 72 dashes (happens when saving a plan to file) CLEAN_PLAN=${CLEAN_PLAN::65300} # GitHub has a 65535-char comment limit - truncate plan, leaving space for comment wrapper - CLEAN_PLAN=$(echo "$CLEAN_PLAN" | sed -E 's/^([[:blank:]]*)([-+~])/\2\1/g') # Move any diff characters to start of line + CLEAN_PLAN=$(echo "$CLEAN_PLAN" | sed -r 's/^([[:blank:]]*)([-+~])/\2\1/g') # Move any diff characters to start of line + if [[ $COLOURISE == 'true' ]]; then + CLEAN_PLAN=$(echo "$CLEAN_PLAN" | sed -r 's/^~/!/g') # Replace ~ with ! to colourise the diff in GitHub comments + fi PR_COMMENT="### Terraform \`plan\` Succeeded for Workspace: \`$WORKSPACE\` Show Output