diff --git a/action.yml b/action.yml
index cc15025..3b716e6 100644
--- a/action.yml
+++ b/action.yml
@@ -277,13 +277,17 @@ runs:
id: plan
if: inputs.action == 'plan'
working-directory: ${{ inputs.directory }}
+ env:
+ TF_CLI: ${{ inputs.tf-cli }}
+ TF_PLAN_NO_COLOR: ${{ github.event_name == 'pull_request' && '-no-color' || '' }}
+ TF_PLAN_DESTROY: ${{ fromJSON(inputs.destroy) && '-destroy' || '' }}
+ TF_PLAN_OUT: ${{ inputs.plan-file-name && format('-out={0}', inputs.plan-file-name) || '' }}
+ TF_PLAN_ACTION_ARGS: ${{ inputs.action-args }}
+ PLAN_COMMENT_MAX_BYTES: '60000'
+ PLAN_COMMENT_EXCERPT_MAX_BYTES: '12288'
shell: bash
run: |
- ${{ inputs.tf-cli }} plan -input=false \
- ${{ github.event_name == 'pull_request' && '-no-color' || '' }} \
- ${{ fromJSON(inputs.destroy) && '-destroy' || '' }} \
- ${{ inputs.plan-file-name && format('-out={0}', inputs.plan-file-name) || '' }} \
- ${{ inputs.action-args }}
+ bash "${{ github.action_path }}/scripts/terraform_plan.sh"
- name: Encrypt Plan
if: inputs.action == 'plan' && inputs.plan-file-name && inputs.encrypted-plan-password
@@ -367,8 +371,11 @@ runs:
validateStderr: ${{ steps.validate.outputs.stderr }}
testStdout: ${{ steps.test.outputs.stdout }}
testStderr: ${{ steps.test.outputs.stderr }}
- planStdout: ${{ steps.plan.outputs.stdout }}
- planStderr: ${{ steps.plan.outputs.stderr }}
+ planCommentStdout: ${{ steps.plan.outputs.comment_stdout }}
+ planCommentStderr: ${{ steps.plan.outputs.comment_stderr }}
+ planCommentOverflow: ${{ steps.plan.outputs.comment_overflow }}
+ planCommentExcerptSource: ${{ steps.plan.outputs.comment_excerpt_source }}
+ workflowRunUrl: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ job.check_run_id }}
outputStdout: ${{ steps.output.outputs.stdout }}
outputStderr: ${{ steps.output.outputs.stderr }}
with:
@@ -410,8 +417,11 @@ runs:
plan: {
enabled: ${{ inputs.action == 'plan' }},
outcome: "${{ steps.plan.outcome }}",
- stdout: `${process.env.planStdout}`,
- stderr: `${process.env.planStderr}`
+ stdout: `${process.env.planCommentStdout}`,
+ stderr: `${process.env.planCommentStderr}`,
+ overflowed: `${process.env.planCommentOverflow}` === 'true',
+ excerptSource: `${process.env.planCommentExcerptSource}`,
+ logsUrl: `${process.env.workflowRunUrl}`
},
output: {
enabled: ${{ inputs.action == 'output' }},
diff --git a/scripts/terraform_plan.sh b/scripts/terraform_plan.sh
new file mode 100755
index 0000000..f0dc31a
--- /dev/null
+++ b/scripts/terraform_plan.sh
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+plan_stdout_file=$(mktemp)
+plan_stderr_file=$(mktemp)
+plan_excerpt_file=$(mktemp)
+plan_combined_file=$(mktemp)
+
+cleanup_plan_files() {
+ rm -f "$plan_stdout_file" "$plan_stderr_file" "$plan_excerpt_file" "$plan_combined_file"
+}
+trap cleanup_plan_files EXIT
+
+PLAN_COMMENT_MAX_BYTES="${PLAN_COMMENT_MAX_BYTES:-60000}"
+PLAN_COMMENT_EXCERPT_MAX_BYTES="${PLAN_COMMENT_EXCERPT_MAX_BYTES:-12288}"
+
+write_multiline_output() {
+ local output_name="$1"
+ local file_path="$2"
+ local delimiter="${output_name}_$(date +%s)_$RANDOM"
+
+ {
+ echo "${output_name}<<${delimiter}"
+ cat "$file_path"
+ printf '\n%s\n' "$delimiter"
+ } >> "$GITHUB_OUTPUT"
+}
+
+plan_command="${TF_CLI} plan -input=false"
+
+if [ -n "${TF_PLAN_NO_COLOR:-}" ]; then
+ plan_command="${plan_command} ${TF_PLAN_NO_COLOR}"
+fi
+
+if [ -n "${TF_PLAN_DESTROY:-}" ]; then
+ plan_command="${plan_command} ${TF_PLAN_DESTROY}"
+fi
+
+if [ -n "${TF_PLAN_OUT:-}" ]; then
+ plan_command="${plan_command} ${TF_PLAN_OUT}"
+fi
+
+if [ -n "${TF_PLAN_ACTION_ARGS:-}" ]; then
+ plan_command="${plan_command} ${TF_PLAN_ACTION_ARGS}"
+fi
+
+set +e
+eval "$plan_command" \
+ > >(tee "$plan_stdout_file") \
+ 2> >(tee "$plan_stderr_file" >&2)
+plan_exit_code=$?
+set -e
+
+plan_stdout_bytes=$(wc -c < "$plan_stdout_file" | tr -d '[:space:]')
+plan_stderr_bytes=$(wc -c < "$plan_stderr_file" | tr -d '[:space:]')
+plan_total_bytes=$((plan_stdout_bytes + plan_stderr_bytes))
+
+if [ "$plan_total_bytes" -le "$PLAN_COMMENT_MAX_BYTES" ]; then
+ {
+ echo "comment_overflow=false"
+ echo "comment_excerpt_source=full"
+ } >> "$GITHUB_OUTPUT"
+
+ write_multiline_output "comment_stdout" "$plan_stdout_file"
+ write_multiline_output "comment_stderr" "$plan_stderr_file"
+else
+ plan_summary_match=$(grep -nE -m 1 '^Plan: [0-9]+ to add, [0-9]+ to change, [0-9]+ to destroy\.$' "$plan_stdout_file" || true)
+
+ if [ -n "$plan_summary_match" ]; then
+ plan_summary_line="${plan_summary_match%%:*}"
+ tail -n +"$plan_summary_line" "$plan_stdout_file" > "$plan_combined_file"
+ head -c "$PLAN_COMMENT_EXCERPT_MAX_BYTES" "$plan_combined_file" > "$plan_excerpt_file"
+ comment_excerpt_source="plan-summary"
+ else
+ cat "$plan_stdout_file" "$plan_stderr_file" > "$plan_combined_file"
+ tail -c "$PLAN_COMMENT_EXCERPT_MAX_BYTES" "$plan_combined_file" > "$plan_excerpt_file"
+ comment_excerpt_source="tail"
+ fi
+
+ {
+ echo "comment_overflow=true"
+ echo "comment_excerpt_source=${comment_excerpt_source}"
+ echo "comment_stderr="
+ } >> "$GITHUB_OUTPUT"
+ write_multiline_output "comment_stdout" "$plan_excerpt_file"
+fi
+
+exit "$plan_exit_code"
diff --git a/terraform_comment.js b/terraform_comment.js
index 81d492a..279ffd8 100644
--- a/terraform_comment.js
+++ b/terraform_comment.js
@@ -110,7 +110,48 @@ ${outcome !== 'skipped' ? createTestDetails(stdout, stderr) : ''}
` : '';
}
-const createPlanDetails = (stdout, stderr) => {
+const createPlanExcerptSourceNote = (excerptSource) => {
+ if (excerptSource === 'plan-summary') {
+ return 'This excerpt starts at the Terraform summary line (`Plan: ...`).';
+ }
+
+ if (excerptSource === 'tail') {
+ return 'The Terraform summary line was not found, so this excerpt shows the tail of combined stdout/stderr output.';
+ }
+
+ return 'This excerpt shows a truncated portion of the Terraform plan output.';
+};
+
+const createPlanDetails = ({stdout, stderr, overflowed, excerptSource, logsUrl}) => {
+ if (overflowed) {
+ const workflowLogs = logsUrl ? `[workflow run logs](${logsUrl})` : 'workflow run logs';
+ const excerptSourceNote = createPlanExcerptSourceNote(excerptSource);
+ const excerptOutput = stdout
+ ? `
+\`\`\`hcl\n
+${stdout}
+\`\`\`
+`
+ : `
+_No plan excerpt was available in this comment._
+`;
+
+ return `
+
+Show Plan
+
+Terraform plan output exceeded size constraints, so this comment includes only a partial excerpt.
+
+${excerptSourceNote}
+
+View the full plan output in the ${workflowLogs}.
+
+${excerptOutput}
+
+
+ `;
+ }
+
return `
Show Plan
@@ -123,11 +164,11 @@ ${stdout}${stderr ? `\n${stderr}` : ''}
`;
};
-const createPlanOutput = ({enabled, outcome, stdout, stderr}) => {
+const createPlanOutput = ({enabled, outcome, stdout, stderr, overflowed, excerptSource, logsUrl}) => {
return enabled ? `
#### Terraform Plan 📖 \`${outcome}\`
-${outcome !== 'skipped' ? createPlanDetails(stdout, stderr) : ''}
+${outcome !== 'skipped' ? createPlanDetails({stdout, stderr, overflowed, excerptSource, logsUrl}) : ''}
` : '';
};