11
11
# Check for required tools.
12
12
which gh > /dev/null 2>&1 || { echo "Please install GitHub CLI before running this action as it is required for interacting with GitHub."; exit 1; }
13
13
which jq > /dev/null 2>&1 || { echo "Please install jq before running this action as it is required for processing JSON outputs."; exit 1; }
14
+ which unzip > /dev/null 2>&1 || { echo "Please install unzip before running this action as it is required for unpacking the plan file artifact."; exit 1; }
14
15
which ${{ inputs.tool }} > /dev/null 2>&1 || { echo "Please install ${{ inputs.tool }} before running this action as it is required for provisioning TF code."; exit 1; }
15
16
if [[ "${{ inputs.plan-encrypt }}" ]]; then which openssl > /dev/null 2>&1 || { echo "Please install openssl before running this action as it is required for plan file encryption."; exit 1; }; fi
16
17
if [[ "${{ inputs.plan-parity }}" ]]; then which diff > /dev/null 2>&1 || { echo "Please install diff before running this action as it is required for comparing plan file parity."; exit 1; }; fi
75
76
if [[ "$GITHUB_EVENT_NAME" == "push" ]]; then
76
77
# List PRs associated with the commit, then get the PR number from the head ref or the latest PR.
77
78
associated_prs=$(gh api /repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls --header "$GH_API" --method GET --field per_page=100)
78
- pr_number=$(echo "$associated_prs" | jq -r '(.[] | select(.head.ref == env.GITHUB_REF_NAME) | .number) // .[0].number')
79
+ pr_number=$(echo "$associated_prs" | jq --raw-output '(.[] | select(.head.ref == env.GITHUB_REF_NAME) | .number) // .[0].number')
79
80
elif [[ "$GITHUB_EVENT_NAME" == "merge_group" ]]; then
80
81
# Get the PR number by parsing the ref name.
81
82
pr_number=$(echo "${GITHUB_REF_NAME}" | sed -n 's/.*pr-\([0-9]*\)-.*/\1/p')
@@ -97,16 +98,16 @@ runs:
97
98
if [[ "$GH_MATRIX" == "null" ]]; then
98
99
# For regular jobs, get the ID of the job with the same name as $GITHUB_JOB (lowercase and '-' or '_' replaced with ' ').
99
100
# Otherwise, get the ID of the first job in the list as a fallback.
100
- job_id=$(echo "$workflow_run" | jq -r '(.jobs[] | select((.name | ascii_downcase | gsub("-|_"; " ")) == (env.GITHUB_JOB | ascii_downcase | gsub("-|_"; " "))) | .id) // .jobs[0].id' | head -n 1)
101
+ job_id=$(echo "$workflow_run" | jq --raw-output '(.jobs[] | select((.name | ascii_downcase | gsub("-|_"; " ")) == (env.GITHUB_JOB | ascii_downcase | gsub("-|_"; " "))) | .id) // .jobs[0].id' | head -n 1)
101
102
else
102
103
# For matrix jobs, join the matrix values with comma separator into a single string and get the ID of the job which contains it.
103
- matrix=$(echo "$GH_MATRIX" | jq -r 'to_entries | map(.value) | join(", ")')
104
- job_id=$(echo "$workflow_run" | jq -r --arg matrix "$matrix" '.jobs[] | select(.name | contains($matrix)) | .id')
104
+ matrix=$(echo "$GH_MATRIX" | jq --raw-output 'to_entries | map(.value) | join(", ")')
105
+ job_id=$(echo "$workflow_run" | jq --raw-output --arg matrix "$matrix" '.jobs[] | select(.name | contains($matrix)) | .id')
105
106
fi
106
107
echo "job=$job_id" >> "$GITHUB_OUTPUT"
107
108
108
109
# Get the step number that has status "in_progress" from the current job.
109
- workflow_step=$(echo "$workflow_run" | jq -r --arg job_id "$job_id" '.jobs[] | select(.id == ($job_id | tonumber)) | .steps[] | select(.status == "in_progress") | .number')
110
+ workflow_step=$(echo "$workflow_run" | jq --raw-output --arg job_id "$job_id" '.jobs[] | select(.id == ($job_id | tonumber)) | .steps[] | select(.status == "in_progress") | .number')
110
111
echo "step=$workflow_step" >> "$GITHUB_OUTPUT"
111
112
112
113
- if : ${{ inputs.format == 'true' }}
@@ -117,7 +118,7 @@ runs:
117
118
trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT
118
119
args="${{ steps.arg.outputs.arg-check }}${{ steps.arg.outputs.arg-diff }}${{ steps.arg.outputs.arg-list }}${{ steps.arg.outputs.arg-recursive }}${{ steps.arg.outputs.arg-write }}"
119
120
echo "${{ inputs.tool }} fmt${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt
120
- ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} fmt${args} > >(tee tf.console.txt) 2> >(tee tf.console.txt >&2 )
121
+ ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} fmt${args} 2 > >(tee tf.console.txt 2>&1 )
121
122
122
123
- id : initialize
123
124
shell : bash
@@ -126,7 +127,7 @@ runs:
126
127
trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT
127
128
args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-force-copy }}${{ steps.arg.outputs.arg-from-module }}${{ steps.arg.outputs.arg-get }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-lockfile }}${{ steps.arg.outputs.arg-migrate-state }}${{ steps.arg.outputs.arg-plugin-dir }}${{ steps.arg.outputs.arg-reconfigure }}${{ steps.arg.outputs.arg-test-directory }}${{ steps.arg.outputs.arg-upgrade }}"
128
129
echo "${{ inputs.tool }} init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt
129
- ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} > >(tee tf.console.txt) 2> >(tee tf.console.txt >&2 )
130
+ ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} init${args} 2 > >(tee tf.console.txt 2>&1 )
130
131
131
132
- if : ${{ inputs.arg-workspace != '' }}
132
133
id : workspace
@@ -136,7 +137,7 @@ runs:
136
137
trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT
137
138
args="${{ steps.arg.outputs.arg-or-create }} ${{ inputs.arg-workspace }}"
138
139
echo "${{ inputs.tool }} workspace select${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt
139
- ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} workspace select${args} > >(tee tf.console.txt) 2> >(tee tf.console.txt >&2 )
140
+ ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} workspace select${args} 2 > >(tee tf.console.txt 2>&1 )
140
141
141
142
- if : ${{ inputs.validate == 'true' }}
142
143
id : validate
@@ -146,7 +147,7 @@ runs:
146
147
trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT
147
148
args="${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}"
148
149
echo "${{ inputs.tool }} validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt
149
- ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} validate${args} > >(tee tf.console.txt) 2> >(tee tf.console.txt >&2 )
150
+ ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} validate${args} 2 > >(tee tf.console.txt 2>&1 )
150
151
151
152
- if : ${{ inputs.label-pr == 'true' && steps.identifier.outputs.pr != 0 }}
152
153
continue-on-error : true
@@ -166,7 +167,7 @@ runs:
166
167
trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"; if [[ "$exit_code" == "2" ]]; then exit 0; fi' EXIT
167
168
args="${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-detailed-exitcode }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tf.plan"
168
169
echo "${{ inputs.tool }} plan${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt
169
- ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} plan${args} > >(tee tf.console.txt) 2> >(tee tf.console.txt >&2 )
170
+ ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} plan${args} 2 > >(tee tf.console.txt 2>&1 )
170
171
171
172
- if : ${{ inputs.command == 'apply' && inputs.arg-auto-approve != 'true' }}
172
173
id : download
@@ -179,7 +180,7 @@ runs:
179
180
180
181
# Unzip the plan file to the working directory, then clean up the zip file.
181
182
unzip "${{ steps.identifier.outputs.name }}.zip" -d "${{ inputs.arg-chdir || inputs.working-directory }}"
182
- rm -f "${{ steps.identifier.outputs.name }}.zip"
183
+ rm --force "${{ steps.identifier.outputs.name }}.zip"
183
184
184
185
- if : ${{ inputs.plan-encrypt != '' && steps.download.outcome == 'success' }}
185
186
env :
@@ -230,12 +231,12 @@ runs:
230
231
# Generate a new plan file, then compare it with the previous one.
231
232
# Both plan files are normalized by sorting JSON keys, removing timestamps and ${{ steps.arg.outputs.arg-detailed-exitcode }} to avoid false-positives.
232
233
${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} plan${{ steps.arg.outputs.arg-destroy }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-generate-config-out }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }} -out=tf.plan.parity
233
- ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }} -json tf.plan.parity | jq -S 'del(.timestamp)' > tf.plan.new
234
- ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }} -json tf.plan | jq -S 'del(.timestamp)' > tf.plan.old
234
+ ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }} -json tf.plan.parity | jq --sort-keys 'del(.timestamp)' > tf.plan.new
235
+ ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} show${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }} -json tf.plan | jq --sort-keys 'del(.timestamp)' > tf.plan.old
235
236
236
237
# If both plan files are identical, then replace the old plan file with the new one to prevent avoidable stale apply.
237
238
diff tf.plan.new tf.plan.old && mv "${{ format('{0}{1}tf.plan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }}" "${{ format('{0}{1}tf.plan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }}"
238
- rm -f tf.plan.new tf.plan.old "${{ format('{0}{1}tf.plan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }}"
239
+ rm --force tf.plan.new tf.plan.old "${{ format('{0}{1}tf.plan.parity', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }}"
239
240
240
241
- id : apply
241
242
if : ${{ inputs.command == 'apply' }}
@@ -255,7 +256,7 @@ runs:
255
256
fi
256
257
args="${{ steps.arg.outputs.arg-destroy }}${var_file}${var}${{ steps.arg.outputs.arg-backup }}${{ steps.arg.outputs.arg-compact-warnings }}${{ steps.arg.outputs.arg-concise }}${{ steps.arg.outputs.arg-lock-timeout }}${{ steps.arg.outputs.arg-lock }}${{ steps.arg.outputs.arg-parallelism }}${{ steps.arg.outputs.arg-refresh-only }}${{ steps.arg.outputs.arg-refresh }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-state-out }}${{ steps.arg.outputs.arg-state }}${{ steps.arg.outputs.arg-target }}${plan}"
257
258
echo "${{ inputs.tool }} apply${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt
258
- ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} apply${args} > >(tee tf.console.txt) 2> >(tee tf.console.txt >&2 )
259
+ ${{ inputs.tool }}${{ steps.arg.outputs.arg-chdir }} apply${args} 2 > >(tee tf.console.txt 2>&1 )
259
260
260
261
- id : post
261
262
if : ${{ !cancelled() && steps.identifier.outcome == 'success' }}
@@ -274,30 +275,30 @@ runs:
274
275
echo "command=$command" >> "$GITHUB_OUTPUT"
275
276
276
277
# Parse the tf.console.txt file, truncated for character limit.
277
- console=$(grep --invert-match '\.\.\.' tf.console.txt | head --bytes=42000 || true )
278
+ console=$(head --bytes=42000 tf.console.txt)
278
279
if [[ ${#console} -eq 42000 ]]; then console="${console}"$'\n…'; fi
279
280
{ echo 'result<<EOTFVIAPR'
280
281
echo "$console"
281
282
echo EOTFVIAPR
282
283
} >> "$GITHUB_OUTPUT"
283
284
284
285
# Parse the tf.console.txt file for the summary.
285
- summary=$(tac tf.console.txt | grep -m 1 -E ' ^(Error:|Plan:|Apply complete!|No changes.|Success)' | tac || echo "View output.")
286
+ summary=$(awk '/ ^(Error:|Plan:|Apply complete!|No changes.|Success)/ {line=$0} END {if (line) print line; else print "View output."}' tf.console.txt )
286
287
echo "summary=$summary" >> "$GITHUB_OUTPUT"
287
288
288
289
# Add summary to the job status.
289
290
check_run=$(gh api /repos/${GITHUB_REPOSITORY}/check-runs/${{ steps.identifier.outputs.job }} --header "$GH_API" --method PATCH --field "output[title]=${summary}" --field "output[summary]=${summary}")
290
291
291
292
# From check_run, echo html_url.
292
- check_url=$(echo "$check_run" | jq -r '.html_url')
293
- echo "check_id=$(echo "$check_run" | jq -r '.id')" >> "$GITHUB_OUTPUT"
293
+ check_url=$(echo "$check_run" | jq --raw-output '.html_url')
294
+ echo "check_id=$(echo "$check_run" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT"
294
295
run_url=$(echo ${check_url}#step:${{ steps.identifier.outputs.step }}:1)
295
296
echo "run_url=$run_url" >> "$GITHUB_OUTPUT"
296
297
297
298
# If tf.diff.txt exists, display it within a diff block, truncated for character limit.
298
299
if [[ -s tf.diff.txt ]]; then
299
300
# Get count of lines in tf.diff.txt which don't start with "# ".
300
- diff_count=$(grep --invert-match '^# ' tf.diff.txt | wc -l )
301
+ diff_count=$(grep --invert-match '^# ' tf.diff.txt | wc --lines )
301
302
if [[ $diff_count -eq 1 ]]; then diff_change="change"; else diff_change="changes"; fi
302
303
303
304
# Parse diff of changes, truncated for character limit.
@@ -350,28 +351,28 @@ runs:
350
351
if [[ "${{ inputs.comment-pr }}" != "none" && "${{ steps.identifier.outputs.pr }}" != "0" ]]; then
351
352
# Check if the PR contains a bot comment with the same identifier.
352
353
list_comments=$(gh api /repos/${GITHUB_REPOSITORY}/issues/${{ steps.identifier.outputs.pr }}/comments --header "$GH_API" --method GET --field per_page=100)
353
- bot_comment=$(echo "$list_comments" | jq -r --arg identifier "${{ steps.identifier.outputs.name }}" '.[] | select(.user.type == "Bot") | select(.body | contains($identifier)) | .id' | head -n 1)
354
+ bot_comment=$(echo "$list_comments" | jq --raw-output --arg identifier "${{ steps.identifier.outputs.name }}" '.[] | select(.user.type == "Bot") | select(.body | contains($identifier)) | .id' | head -n 1)
354
355
355
356
if [[ -n "$bot_comment" ]]; then
356
357
if [[ "${{ inputs.comment-pr }}" == "recreate" ]]; then
357
358
# Delete previous comment before posting a new one.
358
359
gh api /repos/${GITHUB_REPOSITORY}/issues/comments/${bot_comment} --header "$GH_API" --method DELETE
359
360
pr_comment=$(gh api /repos/${GITHUB_REPOSITORY}/issues/${{ steps.identifier.outputs.pr }}/comments --header "$GH_API" --method POST --field "body=${body}")
360
- echo "comment_id=$(echo "$pr_comment" | jq -r '.id')" >> "$GITHUB_OUTPUT"
361
+ echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT"
361
362
elif [[ "${{ inputs.comment-pr }}" == "update" ]]; then
362
363
# Update existing comment.
363
364
pr_comment=$(gh api /repos/${GITHUB_REPOSITORY}/issues/comments/${bot_comment} --header "$GH_API" --method PATCH --field "body=${body}")
364
- echo "comment_id=$(echo "$pr_comment" | jq -r '.id')" >> "$GITHUB_OUTPUT"
365
+ echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT"
365
366
fi
366
367
else
367
368
# Post new comment.
368
369
pr_comment=$(gh api /repos/${GITHUB_REPOSITORY}/issues/${{ steps.identifier.outputs.pr }}/comments --header "$GH_API" --method POST --field "body=${body}")
369
- echo "comment_id=$(echo "$pr_comment" | jq -r '.id')" >> "$GITHUB_OUTPUT"
370
+ echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT"
370
371
fi
371
372
fi
372
373
373
374
# Clean up files.
374
- rm -f tf.command.txt tf.console.txt tf.diff.txt "${{ format('{0}{1}tf.plan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }}"
375
+ rm --force tf.command.txt tf.console.txt tf.diff.txt "${{ format('{0}{1}tf.plan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }}"
375
376
376
377
outputs :
377
378
check-id :
0 commit comments