Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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' }},
Expand Down
89 changes: 89 additions & 0 deletions scripts/terraform_plan.sh
Original file line number Diff line number Diff line change
@@ -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"
47 changes: 44 additions & 3 deletions terraform_comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 `

<details><summary>Show Plan</summary>

Terraform plan output exceeded size constraints, so this comment includes only a partial excerpt.

${excerptSourceNote}

View the full plan output in the ${workflowLogs}.

${excerptOutput}

</details>
`;
}

return `

<details><summary>Show Plan</summary>
Expand All @@ -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}) : ''}
` : '';
};

Expand Down
Loading