├── .gitignore
├── CHANGELOG.md
├── Dockerfile
├── LICENSE
├── README.md
├── action.yml
├── entrypoint.sh
└── images
├── fmt-output.png
├── init-output.png
├── plan-output.png
└── validate-output.png
/.gitignore:
--------------------------------------------------------------------------------
1 | # IDEs
2 | .idea
3 | .vscode
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## v1.5.0
4 |
5 | - Bump to Terraform v1.0.6 internally (only affects `fmt`)
6 | - Fix Terraform v1 `plan` output truncation
7 |
8 | ## v1.4.0
9 |
10 | - Bump to Terraform v0.15.0 internally (only affects `fmt`)
11 | - Change the way `plan`s are truncated after introduction of new horizontal break in TF v0.15.0
12 | - Add `validate` comment handling
13 | - Update readme
14 |
15 | ## v1.3.0
16 |
17 | - Bump to Terraform v0.14.9 internally (only affects `fmt`)
18 | - Fix output truncation in Terraform v0.14 and above
19 |
20 | ## v1.2.0
21 |
22 | - Bump to Terraform v0.14.5 internally (only affects `fmt`)
23 | - Change to leave `fmt` output as-is
24 | - Add colourisation to `plan` diffs where there are changes (on by default, controlled with `HIGHLIGHT_CHANGES` environment variable)
25 | - Update readme
26 |
27 | ## v1.1.0
28 |
29 | - Adds better parsing for Terraform v0.14
30 |
31 | ## v1.0.0
32 |
33 | - Initial release.
34 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM hashicorp/terraform:1.0.6
2 |
3 | LABEL repository="https://github.com/robburger/terraform-pr-commenter" \
4 | homepage="https://github.com/robburger/terraform-pr-commenter" \
5 | maintainer="Rob Burger" \
6 | com.github.actions.name="Terraform PR Commenter" \
7 | com.github.actions.description="Adds opinionated comments to a PR from Terraform fmt/init/plan output" \
8 | com.github.actions.icon="git-pull-request" \
9 | com.github.actions.color="purple"
10 |
11 | RUN apk add --no-cache -q \
12 | bash \
13 | curl \
14 | jq
15 |
16 | ADD entrypoint.sh /entrypoint.sh
17 | RUN chmod +x /entrypoint.sh
18 |
19 | ENTRYPOINT ["/entrypoint.sh"]
20 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Rob Burger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Terraform PR Commenter
2 |
3 | Adds opinionated comments to PR's based on Terraform `fmt`, `init`, `plan` and `validate` outputs.
4 |
5 | ## Summary
6 |
7 | 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`, `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.
8 |
9 | > The `terraform_wrapper` needs to be set to `true` (which is already the default) for the `hashicorp/setup-terraform` step as it enables the capturing of `stdout`, `stderr` and the `exitcode`.
10 |
11 | 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.
12 |
13 | ## Usage
14 |
15 | 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`:
16 |
17 | ```yaml
18 | - uses: robburger/terraform-pr-commenter@v1
19 | env:
20 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
21 | with:
22 | commenter_type: fmt/init/plan/validate # Choose one
23 | commenter_input: ${{ format('{0}{1}', steps.step_id.outputs.stdout, steps.step_id.outputs.stderr) }}
24 | commenter_exitcode: ${{ steps.step_id.outputs.exitcode }}
25 | ```
26 |
27 | ### Inputs
28 |
29 | | Name | Requirement | Description |
30 | | -------------------- | ----------- | ----------------------------------------------------------------- |
31 | | `commenter_type` | _required_ | The type of comment. Options: [`fmt`, `init`, `plan`, `validate`] |
32 | | `commenter_input` | _required_ | The comment to post from a previous step output. |
33 | | `commenter_exitcode` | _required_ | The exit code from a previous step output. |
34 |
35 | ### Environment Variables
36 |
37 | | Name | Requirement | Description |
38 | | ------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
39 | | `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. |
40 | | `TF_WORKSPACE` | _optional_ | Default: `default`. This is used to separate multiple comments on a pull request in a matrix run. |
41 | | `EXPAND_SUMMARY_DETAILS` | _optional_ | Default: `true`. This controls whether the comment output is collapsed or not. |
42 | | `HIGHLIGHT_CHANGES` | _optional_ | Default: `true`. This switches `~` to `!` in `plan` diffs to highlight Terraform changes in orange. Set to `false` to disable. |
43 |
44 | 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`:
45 |
46 | ```yaml
47 | jobs:
48 | terraform:
49 | name: 'Terraform'
50 | runs-on: ubuntu-latest
51 | env:
52 | EXPAND_SUMMARY_DETAILS: 'false' # All steps will have this environment variable
53 | steps:
54 | - name: Checkout
55 | uses: actions/checkout@v2
56 | ...
57 | - name: Post Plan
58 | uses: robburger/terraform-pr-commenter@v1
59 | env:
60 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61 | EXPAND_SUMMARY_DETAILS: 'true' # Override global environment variable; expand details just for this step
62 | with:
63 | commenter_type: plan
64 | commenter_input: ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}
65 | commenter_exitcode: ${{ steps.plan.outputs.exitcode }}
66 | ...
67 | ```
68 |
69 | ## Examples
70 |
71 | Single workspace build, full example:
72 |
73 | ```yaml
74 | name: 'Terraform'
75 |
76 | on:
77 | pull_request:
78 | push:
79 | branches:
80 | - master
81 |
82 | jobs:
83 | terraform:
84 | name: 'Terraform'
85 | runs-on: ubuntu-latest
86 | env:
87 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
88 | TF_IN_AUTOMATION: true
89 | steps:
90 | - name: Checkout
91 | uses: actions/checkout@v2
92 |
93 | - name: Setup Terraform
94 | uses: hashicorp/setup-terraform@v1
95 | with:
96 | cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
97 | terraform_version: 0.15.0
98 |
99 | - name: Terraform Format
100 | id: fmt
101 | run: terraform fmt -check -recursive
102 | continue-on-error: true
103 |
104 | - name: Post Format
105 | if: always() && github.ref != 'refs/heads/master' && (steps.fmt.outcome == 'success' || steps.fmt.outcome == 'failure')
106 | uses: robburger/terraform-pr-commenter@v1
107 | with:
108 | commenter_type: fmt
109 | commenter_input: ${{ format('{0}{1}', steps.fmt.outputs.stdout, steps.fmt.outputs.stderr) }}
110 | commenter_exitcode: ${{ steps.fmt.outputs.exitcode }}
111 |
112 | - name: Terraform Init
113 | id: init
114 | run: terraform init
115 |
116 | - name: Post Init
117 | if: always() && github.ref != 'refs/heads/master' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure')
118 | uses: robburger/terraform-pr-commenter@v1
119 | with:
120 | commenter_type: init
121 | commenter_input: ${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }}
122 | commenter_exitcode: ${{ steps.init.outputs.exitcode }}
123 |
124 | - name: Terraform Validate
125 | id: validate
126 | run: terraform validate
127 |
128 | - name: Post Validate
129 | if: always() && github.ref != 'refs/heads/master' && (steps.validate.outcome == 'success' || steps.validate.outcome == 'failure')
130 | uses: robburger/terraform-pr-commenter@v1
131 | with:
132 | commenter_type: validate
133 | commenter_input: ${{ format('{0}{1}', steps.validate.outputs.stdout, steps.validate.outputs.stderr) }}
134 | commenter_exitcode: ${{ steps.validate.outputs.exitcode }}
135 |
136 | - name: Terraform Plan
137 | id: plan
138 | run: terraform plan -out workspace.plan
139 |
140 | - name: Post Plan
141 | if: always() && github.ref != 'refs/heads/master' && (steps.plan.outcome == 'success' || steps.plan.outcome == 'failure')
142 | uses: robburger/terraform-pr-commenter@v1
143 | with:
144 | commenter_type: plan
145 | commenter_input: ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}
146 | commenter_exitcode: ${{ steps.plan.outputs.exitcode }}
147 |
148 | - name: Terraform Apply
149 | id: apply
150 | if: github.ref == 'refs/heads/master' && github.event_name == 'push'
151 | run: terraform apply workspace.plan
152 | ```
153 |
154 | Multi-workspace matrix/parallel build:
155 |
156 | ```yaml
157 | ...
158 | jobs:
159 | terraform:
160 | name: 'Terraform'
161 | runs-on: ubuntu-latest
162 | strategy:
163 | matrix:
164 | workspace: [audit, staging]
165 | env:
166 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
167 | TF_IN_AUTOMATION: true
168 | TF_WORKSPACE: ${{ matrix['workspace'] }}
169 | steps:
170 | - name: Checkout
171 | uses: actions/checkout@v2
172 |
173 | - name: Setup Terraform
174 | uses: hashicorp/setup-terraform@v1
175 | with:
176 | cli_config_credentials_token: ${{ secrets.TF_API_TOKEN }}
177 | terraform_version: 0.15.0
178 |
179 | - name: Terraform Init - ${{ matrix['workspace'] }}
180 | id: init
181 | run: terraform init
182 |
183 | - name: Post Init - ${{ matrix['workspace'] }}
184 | if: always() && github.ref != 'refs/heads/master' && (steps.init.outcome == 'success' || steps.init.outcome == 'failure')
185 | uses: robburger/terraform-pr-commenter@v1
186 | with:
187 | commenter_type: init
188 | commenter_input: ${{ format('{0}{1}', steps.init.outputs.stdout, steps.init.outputs.stderr) }}
189 | commenter_exitcode: ${{ steps.init.outputs.exitcode }}
190 |
191 | - name: Terraform Plan - ${{ matrix['workspace'] }}
192 | id: plan
193 | run: terraform plan -out ${{ matrix['workspace'] }}.plan
194 |
195 | - name: Post Plan - ${{ matrix['workspace'] }}
196 | if: always() && github.ref != 'refs/heads/master' && (steps.plan.outcome == 'success' || steps.plan.outcome == 'failure')
197 | uses: robburger/terraform-pr-commenter@v1
198 | with:
199 | commenter_type: plan
200 | commenter_input: ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}
201 | commenter_exitcode: ${{ steps.plan.outputs.exitcode }}
202 | ...
203 | ```
204 |
205 | "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:
206 |
207 | 1. `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.
208 | 2. `github.ref != 'refs/heads/master'` - prevents the step running on a `master` branch. PR comments are not possible when there's no PR!
209 | 3. `(steps.step_id.outcome == 'success' || steps.step_id.outcome == 'failure')` - ensures that this step only runs when `step_id` has either a `success` or `failed` outcome.
210 |
211 | 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."
212 |
213 | ## Screenshots
214 |
215 | ### `fmt`
216 |
217 | 
218 |
219 | ### `init`
220 |
221 | 
222 |
223 | ### `plan`
224 |
225 | 
226 |
227 | ### `validate`
228 |
229 | 
230 |
231 | ## Troubleshooting & Contributing
232 |
233 | 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.
234 |
235 | ## License
236 |
237 | [MIT](LICENSE)
238 |
--------------------------------------------------------------------------------
/action.yml:
--------------------------------------------------------------------------------
1 | name: 'Terraform PR Commenter'
2 | description: 'Adds opinionated comments to a PR from Terraform fmt/init/plan output'
3 | author: 'Rob Burger'
4 | branding:
5 | icon: 'git-pull-request'
6 | color: 'purple'
7 | inputs:
8 | commenter_type:
9 | description: 'The type of comment. Options: [fmt, init, plan]'
10 | required: true
11 | commenter_input:
12 | description: 'The comment to post from a previous step output'
13 | required: true
14 | commenter_exitcode:
15 | description: 'The exit code from a previous step output'
16 | required: true
17 | runs:
18 | using: 'docker'
19 | image: 'Dockerfile'
20 | args:
21 | - ${{ inputs.commenter_type }}
22 | - ${{ inputs.commenter_input }}
23 | - ${{ inputs.commenter_exitcode }}
24 |
--------------------------------------------------------------------------------
/entrypoint.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | #############
4 | # Validations
5 | #############
6 | PR_NUMBER=$(jq -r ".pull_request.number" "$GITHUB_EVENT_PATH")
7 | if [[ "$PR_NUMBER" == "null" ]]; then
8 | echo "This isn't a PR."
9 | exit 0
10 | fi
11 |
12 | if [[ -z "$GITHUB_TOKEN" ]]; then
13 | echo "GITHUB_TOKEN environment variable missing."
14 | exit 1
15 | fi
16 |
17 | if [[ -z $3 ]]; then
18 | echo "There must be an exit code from a previous step."
19 | exit 1
20 | fi
21 |
22 | if [[ ! "$1" =~ ^(fmt|init|plan|validate)$ ]]; then
23 | echo -e "Unsupported command \"$1\". Valid commands are \"fmt\", \"init\", \"plan\", \"validate\"."
24 | exit 1
25 | fi
26 |
27 | ##################
28 | # Shared Variables
29 | ##################
30 | # Arg 1 is command
31 | COMMAND=$1
32 | # Arg 2 is input. We strip ANSI colours.
33 | INPUT=$(echo "$2" | sed 's/\x1b\[[0-9;]*m//g')
34 | # Arg 3 is the Terraform CLI exit code
35 | EXIT_CODE=$3
36 |
37 | # Read TF_WORKSPACE environment variable or use "default"
38 | WORKSPACE=${TF_WORKSPACE:-default}
39 |
40 | # Read EXPAND_SUMMARY_DETAILS environment variable or use "true"
41 | if [[ ${EXPAND_SUMMARY_DETAILS:-true} == "true" ]]; then
42 | DETAILS_STATE=" open"
43 | else
44 | DETAILS_STATE=""
45 | fi
46 |
47 | # Read HIGHLIGHT_CHANGES environment variable or use "true"
48 | COLOURISE=${HIGHLIGHT_CHANGES:-true}
49 |
50 | ACCEPT_HEADER="Accept: application/vnd.github.v3+json"
51 | AUTH_HEADER="Authorization: token $GITHUB_TOKEN"
52 | CONTENT_HEADER="Content-Type: application/json"
53 |
54 | PR_COMMENTS_URL=$(jq -r ".pull_request.comments_url" "$GITHUB_EVENT_PATH")
55 | PR_COMMENT_URI=$(jq -r ".repository.issue_comment_url" "$GITHUB_EVENT_PATH" | sed "s|{/number}||g")
56 |
57 | ##############
58 | # Handler: fmt
59 | ##############
60 | if [[ $COMMAND == 'fmt' ]]; then
61 | # Look for an existing fmt PR comment and delete
62 | echo -e "\033[34;1mINFO:\033[0m Looking for an existing fmt PR comment."
63 | PR_COMMENT_ID=$(curl -sS -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENTS_URL" | jq '.[] | select(.body|test ("### Terraform `fmt` Failed")) | .id')
64 | if [ "$PR_COMMENT_ID" ]; then
65 | echo -e "\033[34;1mINFO:\033[0m Found existing fmt PR comment: $PR_COMMENT_ID. Deleting."
66 | PR_COMMENT_URL="$PR_COMMENT_URI/$PR_COMMENT_ID"
67 | curl -sS -X DELETE -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENT_URL" > /dev/null
68 | else
69 | echo -e "\033[34;1mINFO:\033[0m No existing fmt PR comment found."
70 | fi
71 |
72 | # Exit Code: 0
73 | # Meaning: All files formatted correctly.
74 | # Actions: Exit.
75 | if [[ $EXIT_CODE -eq 0 ]]; then
76 | echo -e "\033[34;1mINFO:\033[0m Terraform fmt completed with no errors. Continuing."
77 |
78 | exit 0
79 | fi
80 |
81 | # Exit Code: 1, 2
82 | # Meaning: 1 = Malformed Terraform CLI command. 2 = Terraform parse error.
83 | # Actions: Build PR comment.
84 | if [[ $EXIT_CODE -eq 1 || $EXIT_CODE -eq 2 ]]; then
85 | PR_COMMENT="### Terraform \`fmt\` Failed
86 | Show Output
87 |
88 | \`\`\`
89 | $INPUT
90 | \`\`\`
91 | "
92 | fi
93 |
94 | # Exit Code: 3
95 | # Meaning: One or more files are incorrectly formatted.
96 | # Actions: Iterate over all files and build diff-based PR comment.
97 | if [[ $EXIT_CODE -eq 3 ]]; then
98 | ALL_FILES_DIFF=""
99 | for file in $INPUT; do
100 | THIS_FILE_DIFF=$(terraform fmt -no-color -write=false -diff "$file")
101 | ALL_FILES_DIFF="$ALL_FILES_DIFF
102 | $file
103 |
104 | \`\`\`diff
105 | $THIS_FILE_DIFF
106 | \`\`\`
107 | "
108 | done
109 |
110 | PR_COMMENT="### Terraform \`fmt\` Failed
111 | $ALL_FILES_DIFF"
112 | fi
113 |
114 | # Add fmt failure comment to PR.
115 | PR_PAYLOAD=$(echo '{}' | jq --arg body "$PR_COMMENT" '.body = $body')
116 | echo -e "\033[34;1mINFO:\033[0m Adding fmt failure comment to PR."
117 | curl -sS -X POST -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -H "$CONTENT_HEADER" -d "$PR_PAYLOAD" -L "$PR_COMMENTS_URL" > /dev/null
118 |
119 | exit 0
120 | fi
121 |
122 | ###############
123 | # Handler: init
124 | ###############
125 | if [[ $COMMAND == 'init' ]]; then
126 | # Look for an existing init PR comment and delete
127 | echo -e "\033[34;1mINFO:\033[0m Looking for an existing init PR comment."
128 | PR_COMMENT_ID=$(curl -sS -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENTS_URL" | jq '.[] | select(.body|test ("### Terraform `init` Failed")) | .id')
129 | if [ "$PR_COMMENT_ID" ]; then
130 | echo -e "\033[34;1mINFO:\033[0m Found existing init PR comment: $PR_COMMENT_ID. Deleting."
131 | PR_COMMENT_URL="$PR_COMMENT_URI/$PR_COMMENT_ID"
132 | curl -sS -X DELETE -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENT_URL" > /dev/null
133 | else
134 | echo -e "\033[34;1mINFO:\033[0m No existing init PR comment found."
135 | fi
136 |
137 | # Exit Code: 0
138 | # Meaning: Terraform successfully initialized.
139 | # Actions: Exit.
140 | if [[ $EXIT_CODE -eq 0 ]]; then
141 | echo -e "\033[34;1mINFO:\033[0m Terraform init completed with no errors. Continuing."
142 |
143 | exit 0
144 | fi
145 |
146 | # Exit Code: 1
147 | # Meaning: Terraform initialize failed or malformed Terraform CLI command.
148 | # Actions: Build PR comment.
149 | if [[ $EXIT_CODE -eq 1 ]]; then
150 | PR_COMMENT="### Terraform \`init\` Failed
151 | Show Output
152 |
153 | \`\`\`
154 | $INPUT
155 | \`\`\`
156 | "
157 | fi
158 |
159 | # Add init failure comment to PR.
160 | PR_PAYLOAD=$(echo '{}' | jq --arg body "$PR_COMMENT" '.body = $body')
161 | echo -e "\033[34;1mINFO:\033[0m Adding init failure comment to PR."
162 | curl -sS -X POST -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -H "$CONTENT_HEADER" -d "$PR_PAYLOAD" -L "$PR_COMMENTS_URL" > /dev/null
163 |
164 | exit 0
165 | fi
166 |
167 | ###############
168 | # Handler: plan
169 | ###############
170 | if [[ $COMMAND == 'plan' ]]; then
171 | # Look for an existing plan PR comment and delete
172 | echo -e "\033[34;1mINFO:\033[0m Looking for an existing plan PR comment."
173 | PR_COMMENT_ID=$(curl -sS -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENTS_URL" | jq '.[] | select(.body|test ("### Terraform `plan` .* for Workspace: `'"$WORKSPACE"'`")) | .id')
174 | if [ "$PR_COMMENT_ID" ]; then
175 | echo -e "\033[34;1mINFO:\033[0m Found existing plan PR comment: $PR_COMMENT_ID. Deleting."
176 | PR_COMMENT_URL="$PR_COMMENT_URI/$PR_COMMENT_ID"
177 | curl -sS -X DELETE -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENT_URL" > /dev/null
178 | else
179 | echo -e "\033[34;1mINFO:\033[0m No existing plan PR comment found."
180 | fi
181 |
182 | # Exit Code: 0, 2
183 | # Meaning: 0 = Terraform plan succeeded with no changes. 2 = Terraform plan succeeded with changes.
184 | # Actions: Strip out the refresh section, ignore everything after the 72 dashes, format, colourise and build PR comment.
185 | if [[ $EXIT_CODE -eq 0 || $EXIT_CODE -eq 2 ]]; then
186 | CLEAN_PLAN=$(echo "$INPUT" | sed -r '/^(An execution plan has been generated and is shown below.|Terraform used the selected providers to generate the following execution|No changes. Infrastructure is up-to-date.|No changes. Your infrastructure matches the configuration.|Note: Objects have changed outside of Terraform)$/,$!d') # Strip refresh section
187 | CLEAN_PLAN=$(echo "$CLEAN_PLAN" | sed -r '/Plan: /q') # Ignore everything after plan summary
188 | CLEAN_PLAN=${CLEAN_PLAN::65300} # GitHub has a 65535-char comment limit - truncate plan, leaving space for comment wrapper
189 | CLEAN_PLAN=$(echo "$CLEAN_PLAN" | sed -r 's/^([[:blank:]]*)([-+~])/\2\1/g') # Move any diff characters to start of line
190 | if [[ $COLOURISE == 'true' ]]; then
191 | CLEAN_PLAN=$(echo "$CLEAN_PLAN" | sed -r 's/^~/!/g') # Replace ~ with ! to colourise the diff in GitHub comments
192 | fi
193 | PR_COMMENT="### Terraform \`plan\` Succeeded for Workspace: \`$WORKSPACE\`
194 | Show Output
195 |
196 | \`\`\`diff
197 | $CLEAN_PLAN
198 | \`\`\`
199 | "
200 | fi
201 |
202 | # Exit Code: 1
203 | # Meaning: Terraform plan failed.
204 | # Actions: Build PR comment.
205 | if [[ $EXIT_CODE -eq 1 ]]; then
206 | PR_COMMENT="### Terraform \`plan\` Failed for Workspace: \`$WORKSPACE\`
207 | Show Output
208 |
209 | \`\`\`
210 | $INPUT
211 | \`\`\`
212 | "
213 | fi
214 |
215 | # Add plan comment to PR.
216 | PR_PAYLOAD=$(echo '{}' | jq --arg body "$PR_COMMENT" '.body = $body')
217 | echo -e "\033[34;1mINFO:\033[0m Adding plan comment to PR."
218 | curl -sS -X POST -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -H "$CONTENT_HEADER" -d "$PR_PAYLOAD" -L "$PR_COMMENTS_URL" > /dev/null
219 |
220 | exit 0
221 | fi
222 |
223 | ###################
224 | # Handler: validate
225 | ###################
226 | if [[ $COMMAND == 'validate' ]]; then
227 | # Look for an existing validate PR comment and delete
228 | echo -e "\033[34;1mINFO:\033[0m Looking for an existing validate PR comment."
229 | PR_COMMENT_ID=$(curl -sS -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENTS_URL" | jq '.[] | select(.body|test ("### Terraform `validate` Failed")) | .id')
230 | if [ "$PR_COMMENT_ID" ]; then
231 | echo -e "\033[34;1mINFO:\033[0m Found existing validate PR comment: $PR_COMMENT_ID. Deleting."
232 | PR_COMMENT_URL="$PR_COMMENT_URI/$PR_COMMENT_ID"
233 | curl -sS -X DELETE -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENT_URL" > /dev/null
234 | else
235 | echo -e "\033[34;1mINFO:\033[0m No existing validate PR comment found."
236 | fi
237 |
238 | # Exit Code: 0
239 | # Meaning: Terraform successfully validated.
240 | # Actions: Exit.
241 | if [[ $EXIT_CODE -eq 0 ]]; then
242 | echo -e "\033[34;1mINFO:\033[0m Terraform validate completed with no errors. Continuing."
243 |
244 | exit 0
245 | fi
246 |
247 | # Exit Code: 1
248 | # Meaning: Terraform validate failed or malformed Terraform CLI command.
249 | # Actions: Build PR comment.
250 | if [[ $EXIT_CODE -eq 1 ]]; then
251 | PR_COMMENT="### Terraform \`validate\` Failed
252 | Show Output
253 |
254 | \`\`\`
255 | $INPUT
256 | \`\`\`
257 | "
258 | fi
259 |
260 | # Add validate failure comment to PR.
261 | PR_PAYLOAD=$(echo '{}' | jq --arg body "$PR_COMMENT" '.body = $body')
262 | echo -e "\033[34;1mINFO:\033[0m Adding validate failure comment to PR."
263 | curl -sS -X POST -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -H "$CONTENT_HEADER" -d "$PR_PAYLOAD" -L "$PR_COMMENTS_URL" > /dev/null
264 |
265 | exit 0
266 | fi
267 |
--------------------------------------------------------------------------------
/images/fmt-output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robburger/terraform-pr-commenter/72c6e45eced6641488a6cf3ff104b7b9bda9c66c/images/fmt-output.png
--------------------------------------------------------------------------------
/images/init-output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robburger/terraform-pr-commenter/72c6e45eced6641488a6cf3ff104b7b9bda9c66c/images/init-output.png
--------------------------------------------------------------------------------
/images/plan-output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robburger/terraform-pr-commenter/72c6e45eced6641488a6cf3ff104b7b9bda9c66c/images/plan-output.png
--------------------------------------------------------------------------------
/images/validate-output.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/robburger/terraform-pr-commenter/72c6e45eced6641488a6cf3ff104b7b9bda9c66c/images/validate-output.png
--------------------------------------------------------------------------------