From 7af4cb1c1b8e186e1431c0de536e015288209198 Mon Sep 17 00:00:00 2001 From: Frederik Nielsen Date: Wed, 1 Apr 2026 10:48:17 +0200 Subject: [PATCH 1/7] added functionality to add a prefix on the comment body --- README.md | 1 + action.yml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index 7b6045b2..5ac52901 100644 --- a/README.md +++ b/README.md @@ -189,6 +189,7 @@ All supported CLI argument inputs are [listed below](#arguments) with accompanyi | UI | `tag-actor` | Tag the workflow triggering actor: `always`, `on-diff`, or `never`.4
Default: `always` | | UI | `hide-args` | Hide comma-separated list of CLI arguments from the command input.6
Default: `detailed-exitcode,parallelism,lock,out,var=` | | UI | `show-args` | Show comma-separated list of CLI arguments in the command input.6
Default: `workspace` | +| UI | `body-prefix` | Optional content to prepend to the default comment |
diff --git a/action.yml b/action.yml index e39d4a1e..fa6a7954 100644 --- a/action.yml +++ b/action.yml @@ -332,6 +332,7 @@ runs: INPUTS_PRESERVE_PLAN: ${{ inputs.preserve-plan }} INPUTS_SHOW_ARGS: ${{ inputs.show-args }} INPUTS_TAG_ACTOR: ${{ inputs.tag-actor }} + INPUTS_BODY_PREFIX: ${{ inputs.body-prefix }} path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} shell: bash run: | @@ -442,6 +443,7 @@ runs: # Collate body content. body=$(cat < \`\`\`fish ${command} @@ -644,6 +646,10 @@ inputs: default: "" description: "Specify the working directory of TF code, alias of `arg-chdir` (e.g., `stacks/dev`)." required: false + body-prefix: + default: "" + description: "Prefix for the PR comment body (e.g., '# Terraform Plan for ')." + required: false # CLI arguments. arg-auto-approve: From 8bec1215b342c516684fa3fe80e5626517ea66b5 Mon Sep 17 00:00:00 2001 From: Frederik Nielsen Date: Wed, 1 Apr 2026 11:02:05 +0200 Subject: [PATCH 2/7] switch naming from 'body' to 'comment' --- README.md | 2 +- action.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5ac52901..d0677412 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ All supported CLI argument inputs are [listed below](#arguments) with accompanyi | UI | `tag-actor` | Tag the workflow triggering actor: `always`, `on-diff`, or `never`.4
Default: `always` | | UI | `hide-args` | Hide comma-separated list of CLI arguments from the command input.6
Default: `detailed-exitcode,parallelism,lock,out,var=` | | UI | `show-args` | Show comma-separated list of CLI arguments in the command input.6
Default: `workspace` | -| UI | `body-prefix` | Optional content to prepend to the default comment | +| UI | `comment-prefix` | Optional content to prepend to the default comment |
diff --git a/action.yml b/action.yml index fa6a7954..e3e47322 100644 --- a/action.yml +++ b/action.yml @@ -332,7 +332,7 @@ runs: INPUTS_PRESERVE_PLAN: ${{ inputs.preserve-plan }} INPUTS_SHOW_ARGS: ${{ inputs.show-args }} INPUTS_TAG_ACTOR: ${{ inputs.tag-actor }} - INPUTS_BODY_PREFIX: ${{ inputs.body-prefix }} + INPUTS_COMMENT_PREFIX: ${{ inputs.comment-prefix }} path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} shell: bash run: | @@ -443,7 +443,7 @@ runs: # Collate body content. body=$(cat < \`\`\`fish ${command} @@ -646,7 +646,7 @@ inputs: default: "" description: "Specify the working directory of TF code, alias of `arg-chdir` (e.g., `stacks/dev`)." required: false - body-prefix: + comment-prefix: default: "" description: "Prefix for the PR comment body (e.g., '# Terraform Plan for ')." required: false From eca71e3759d2501f0119a107ca5d28ad450c874a Mon Sep 17 00:00:00 2001 From: Frederik Nielsen Date: Wed, 1 Apr 2026 12:45:09 +0200 Subject: [PATCH 3/7] added optional input to add suffix to pr comment body --- README.md | 1 + action.yml | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/README.md b/README.md index d0677412..04a8149f 100644 --- a/README.md +++ b/README.md @@ -190,6 +190,7 @@ All supported CLI argument inputs are [listed below](#arguments) with accompanyi | UI | `hide-args` | Hide comma-separated list of CLI arguments from the command input.6
Default: `detailed-exitcode,parallelism,lock,out,var=` | | UI | `show-args` | Show comma-separated list of CLI arguments in the command input.6
Default: `workspace` | | UI | `comment-prefix` | Optional content to prepend to the default comment | +| UI | `comment-suffix` | Optional content to append to the default comment |
diff --git a/action.yml b/action.yml index e3e47322..41111183 100644 --- a/action.yml +++ b/action.yml @@ -333,6 +333,7 @@ runs: INPUTS_SHOW_ARGS: ${{ inputs.show-args }} INPUTS_TAG_ACTOR: ${{ inputs.tag-actor }} INPUTS_COMMENT_PREFIX: ${{ inputs.comment-prefix }} + INPUTS_COMMENT_SUFFIX: ${{ inputs.comment-suffix }} path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} shell: bash run: | @@ -455,6 +456,7 @@ runs: ###### By ${tag_actor}${{ github.triggering_actor }} at ${{ github.event.pull_request.updated_at || github.event.comment.created_at || github.event.head_commit.timestamp || github.event.merge_group.head_commit.timestamp }} [(view log)](${run_url}). + ${INPUTS_COMMENT_SUFFIX} \`\`\`${syntax} @@ -650,6 +652,10 @@ inputs: default: "" description: "Prefix for the PR comment body (e.g., '# Terraform Plan for ')." required: false + comment-suffix: + default: "" + description: "Suffix for the PR comment body (e.g., '### Triggered by on aws-account ')." + required: false # CLI arguments. arg-auto-approve: From fe20ee03485327f2e7ead5dd67bd0bfa3dd40f73 Mon Sep 17 00:00:00 2001 From: Frederik Nielsen Date: Wed, 1 Apr 2026 13:04:17 +0200 Subject: [PATCH 4/7] moved suffix into the details line on comment body --- README.md | 2 +- action.yml | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 04a8149f..0f668f81 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ All supported CLI argument inputs are [listed below](#arguments) with accompanyi | UI | `hide-args` | Hide comma-separated list of CLI arguments from the command input.6
Default: `detailed-exitcode,parallelism,lock,out,var=` | | UI | `show-args` | Show comma-separated list of CLI arguments in the command input.6
Default: `workspace` | | UI | `comment-prefix` | Optional content to prepend to the default comment | -| UI | `comment-suffix` | Optional content to append to the default comment | +| UI | `details-suffix` | Optional content to append to the details line in the default comment |
diff --git a/action.yml b/action.yml index 41111183..ac083d43 100644 --- a/action.yml +++ b/action.yml @@ -333,7 +333,7 @@ runs: INPUTS_SHOW_ARGS: ${{ inputs.show-args }} INPUTS_TAG_ACTOR: ${{ inputs.tag-actor }} INPUTS_COMMENT_PREFIX: ${{ inputs.comment-prefix }} - INPUTS_COMMENT_SUFFIX: ${{ inputs.comment-suffix }} + INPUTS_DETAILS_SUFFIX: ${{ inputs.details-suffix }} path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} shell: bash run: | @@ -455,8 +455,7 @@ runs: ${summary}
- ###### By ${tag_actor}${{ github.triggering_actor }} at ${{ github.event.pull_request.updated_at || github.event.comment.created_at || github.event.head_commit.timestamp || github.event.merge_group.head_commit.timestamp }} [(view log)](${run_url}). - ${INPUTS_COMMENT_SUFFIX} + ###### By ${tag_actor}${{ github.triggering_actor }} at ${{ github.event.pull_request.updated_at || github.event.comment.created_at || github.event.head_commit.timestamp || github.event.merge_group.head_commit.timestamp }} [(view log)](${run_url}). ${INPUTS_DETAILS_SUFFIX}
\`\`\`${syntax} @@ -652,9 +651,9 @@ inputs: default: "" description: "Prefix for the PR comment body (e.g., '# Terraform Plan for ')." required: false - comment-suffix: + details-suffix: default: "" - description: "Suffix for the PR comment body (e.g., '### Triggered by on aws-account ')." + description: "Suffix for the details line in the PR comment (e.g., 'Executed on AWS account ')." required: false # CLI arguments. From 2db30be4fdac6bb4433ac0c8c60b910af83c6203 Mon Sep 17 00:00:00 2001 From: Frederik Nielsen Date: Wed, 1 Apr 2026 13:23:30 +0200 Subject: [PATCH 5/7] removed details-suffix --- README.md | 1 - action.yml | 7 +------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 0f668f81..d0677412 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,6 @@ All supported CLI argument inputs are [listed below](#arguments) with accompanyi | UI | `hide-args` | Hide comma-separated list of CLI arguments from the command input.6
Default: `detailed-exitcode,parallelism,lock,out,var=` | | UI | `show-args` | Show comma-separated list of CLI arguments in the command input.6
Default: `workspace` | | UI | `comment-prefix` | Optional content to prepend to the default comment | -| UI | `details-suffix` | Optional content to append to the details line in the default comment |
diff --git a/action.yml b/action.yml index ac083d43..e3e47322 100644 --- a/action.yml +++ b/action.yml @@ -333,7 +333,6 @@ runs: INPUTS_SHOW_ARGS: ${{ inputs.show-args }} INPUTS_TAG_ACTOR: ${{ inputs.tag-actor }} INPUTS_COMMENT_PREFIX: ${{ inputs.comment-prefix }} - INPUTS_DETAILS_SUFFIX: ${{ inputs.details-suffix }} path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} shell: bash run: | @@ -455,7 +454,7 @@ runs: ${summary}
- ###### By ${tag_actor}${{ github.triggering_actor }} at ${{ github.event.pull_request.updated_at || github.event.comment.created_at || github.event.head_commit.timestamp || github.event.merge_group.head_commit.timestamp }} [(view log)](${run_url}). ${INPUTS_DETAILS_SUFFIX} + ###### By ${tag_actor}${{ github.triggering_actor }} at ${{ github.event.pull_request.updated_at || github.event.comment.created_at || github.event.head_commit.timestamp || github.event.merge_group.head_commit.timestamp }} [(view log)](${run_url}).
\`\`\`${syntax} @@ -651,10 +650,6 @@ inputs: default: "" description: "Prefix for the PR comment body (e.g., '# Terraform Plan for ')." required: false - details-suffix: - default: "" - description: "Suffix for the details line in the PR comment (e.g., 'Executed on AWS account ')." - required: false # CLI arguments. arg-auto-approve: From 5894f7b5a849927df3c41f0de2efd269aa7557df Mon Sep 17 00:00:00 2001 From: Rishav Dhar <19497993+rdhar@users.noreply.github.com> Date: Fri, 1 May 2026 19:58:26 +0100 Subject: [PATCH 6/7] Merge from #557 --- action.yml | 143 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 93 insertions(+), 50 deletions(-) diff --git a/action.yml b/action.yml index e3e47322..aa51bfc5 100644 --- a/action.yml +++ b/action.yml @@ -23,6 +23,7 @@ runs: - id: arg env: + GH_REPO: ${{ github.repository }} INPUTS_ARG_AUTO_APPROVE: ${{ inputs.arg-auto-approve }} INPUTS_ARG_BACKEND_CONFIG: ${{ inputs.arg-backend-config }} INPUTS_ARG_BACKEND: ${{ inputs.arg-backend }} @@ -66,12 +67,13 @@ runs: # Populate variables. # Environment variables. echo "GH_API=X-GitHub-Api-Version:2022-11-28" >> "$GITHUB_ENV" + echo "GH_HOST=${GITHUB_SERVER_URL#*://}" >> "$GITHUB_ENV" + echo "GH_REPO=$GH_REPO" >> "$GITHUB_ENV" echo "GH_TOKEN=$INPUTS_TOKEN" >> "$GITHUB_ENV" echo "TF_CLI_ARGS=$([[ -n "${{ env.TF_CLI_ARGS }}" ]] && echo "${{ env.TF_CLI_ARGS }} -no-color" || echo "-no-color")" >> "$GITHUB_ENV" echo "TF_IN_AUTOMATION=true" >> "$GITHUB_ENV" echo "TF_INPUT=false" >> "$GITHUB_ENV" echo "TF_WORKSPACE=$TF_WORKSPACE" >> "$GITHUB_ENV" - echo "GH_HOST=$(echo $GITHUB_SERVER_URL | sed 's/.*:\/\///')" >> "$GITHUB_ENV" # CLI arguments. echo arg-auto-approve=$([[ "${INPUTS_ARG_AUTO_APPROVE,,}" == "true" ]] && echo " -auto-approve" || echo "") >> "$GITHUB_OUTPUT" @@ -114,6 +116,10 @@ runs: - id: identifier env: + GH_COMMIT_SHA: ${{ github.event.pull_request.head.sha || github.event.workflow_run.head_sha || github.sha }} + GH_EVENT_NUMBER: ${{ github.event.number || github.event.issue.number }} + GH_HEAD_REF: ${{ github.event.pull_request.head.label || github.ref_name || github.head_ref || github.ref || '0' }} + GH_WORKFLOW_RUN_HEAD_BRANCH: ${{ github.event.workflow_run.head_branch }} INPUTS_PR_NUMBER: ${{ inputs.pr-number }} INPUTS_TOOL: ${{ inputs.tool }} shell: bash @@ -125,20 +131,25 @@ runs: pr_number="$INPUTS_PR_NUMBER" elif [[ "$GITHUB_EVENT_NAME" == "push" || "$GITHUB_EVENT_NAME" == "repository_dispatch" || "$GITHUB_EVENT_NAME" == "workflow_call" || "$GITHUB_EVENT_NAME" == "workflow_dispatch" || "$GITHUB_EVENT_NAME" == "workflow_run" ]]; then # List PRs associated with the commit, then get the PR number from the head ref or the latest PR. - associated_prs=$(gh api /repos/${{ github.repository }}/commits/${{ github.event.pull_request.head.sha || github.sha }}/pulls --header "$GH_API" --method GET --paginate) - pr_number=$(echo "$associated_prs" | jq --raw-output '(.[] | select(.head.ref == env.GITHUB_REF_NAME) | .number) // .[0].number // 0') + associated_prs=$(gh api /repos/$GH_REPO/commits/$GH_COMMIT_SHA/pulls --header "$GH_API" --method GET --paginate) + pr_number=$(echo "$associated_prs" | jq --raw-output '(.[] | select(.head.ref == env.GITHUB_REF_NAME or .head.ref == env.GH_WORKFLOW_RUN_HEAD_BRANCH) | .number) // .[0].number // 0') elif [[ "$GITHUB_EVENT_NAME" == "merge_group" ]]; then # Get the PR number by parsing the ref name. - pr_number=$(echo "${{ github.ref_name }}" | sed -n 's/.*pr-\([0-9]*\)-.*/\1/p') + pr_number=$(echo "$GITHUB_REF_NAME" | sed -n 's/.*pr-\([0-9]*\)-.*/\1/p') else - # Get the PR number from branch name, otherwise fallback on 0 if the PR number is not found. - pr_number=${{ github.event.number || github.event.issue.number }} || $(gh api /repos/${{ github.repository }}/pulls --header "$GH_API" --method GET --paginate --field head="${{ github.ref_name || github.head_ref || github.ref || '0' }}" | jq '.[0].number // 0') + # Get the PR number from the event or issue if available; otherwise, look it up by head ref and fallback on 0 if not found. + if [[ -n "$GH_EVENT_NUMBER" ]]; then + pr_number="$GH_EVENT_NUMBER" + else + pr_number=$(gh api /repos/$GH_REPO/pulls --header "$GH_API" --method GET --paginate --field head="$GH_HEAD_REF" | jq '.[0].number // 0') + fi fi echo "pr=${pr_number:-0}" >> "$GITHUB_OUTPUT" # Generate identifier for the workflow run using MD5 hashing algorithm for concise and unique naming. identifier="${{ steps.arg.outputs.arg-chdir }}${{ steps.arg.outputs.arg-workspace }}${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-var-file }}${{ steps.arg.outputs.arg-var }}${{ steps.arg.outputs.arg-replace }}${{ steps.arg.outputs.arg-target }}${{ steps.arg.outputs.arg-destroy }}" identifier=$(echo -n "$identifier" | md5sum | awk '{print $1}') + echo "GH_IDENTIFIER_NAME=${INPUTS_TOOL}-${pr_number}-${identifier}.tfplan" >> "$GITHUB_ENV" echo "name=${INPUTS_TOOL}-${pr_number}-${identifier}.tfplan" >> "$GITHUB_OUTPUT" - if: ${{ inputs.format == 'true' }} @@ -151,7 +162,7 @@ runs: trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT 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 }}" echo "$INPUTS_TOOL fmt${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt - $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} fmt${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) + $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} fmt${args} 2>&1 | tee tf.console.txt - if: ${{ contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} id: initialize @@ -163,7 +174,7 @@ runs: trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT args="${{ steps.arg.outputs.arg-backend-config }}${{ steps.arg.outputs.arg-backend }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && 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 }}" echo "$INPUTS_TOOL init${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt - $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) + $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} init${args} 2>&1 | tee tf.console.txt - if: ${{ inputs.validate == 'true' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} id: validate @@ -175,7 +186,7 @@ runs: trap 'exit_code="$?"; echo "exit_code=$exit_code" >> "$GITHUB_OUTPUT"' EXIT args="${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var-file || '' }}${{ env.INPUTS_TOOL == 'tofu' && steps.arg.outputs.arg-var || '' }}${{ steps.arg.outputs.arg-no-tests }}${{ steps.arg.outputs.arg-test-directory }}" echo "$INPUTS_TOOL validate${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt - $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) + $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} validate${args} 2>&1 | tee tf.console.txt - if: ${{ inputs.command == 'plan' }} id: plan @@ -190,7 +201,7 @@ runs: 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=tfplan" echo "$INPUTS_TOOL plan${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt if [[ -n "$INPUTS_PLAN_FILE" ]]; then mv --force --verbose "$INPUTS_PLAN_FILE" "$path" 2>/dev/null && exit 0; fi - $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) + $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} plan${args} 2>&1 | tee tf.console.txt - if: ${{ inputs.command == 'apply' && inputs.arg-auto-approve != 'true' && inputs.plan-file == '' }} id: download @@ -200,13 +211,13 @@ runs: run: | # Download plan file. # Get the artifact ID of the latest matching plan files for download. - artifact_id=$(gh api /repos/${{ github.repository }}/actions/artifacts --header "$GH_API" --method GET --field "name=${{ steps.identifier.outputs.name }}" --jq '.artifacts[0].id' 2>/dev/null) - if [[ -z "$artifact_id" ]]; then echo "Unable to locate plan file: ${{ steps.identifier.outputs.name }}." && exit 1; fi - gh api /repos/${{ github.repository }}/actions/artifacts/${artifact_id}/zip --header "$GH_API" --method GET > "${{ steps.identifier.outputs.name }}.zip" + artifact_id=$(gh api /repos/$GH_REPO/actions/artifacts --header "$GH_API" --method GET --field "name=$GH_IDENTIFIER_NAME" --jq '.artifacts[0].id' 2>/dev/null) + if [[ -z "$artifact_id" ]]; then echo "Unable to locate plan file: $GH_IDENTIFIER_NAME." && exit 1; fi + gh api /repos/$GH_REPO/actions/artifacts/${artifact_id}/zip --header "$GH_API" --method GET > "$GH_IDENTIFIER_NAME.zip" # Unzip the plan file to the working directory, then clean up the zip file. - unzip "${{ steps.identifier.outputs.name }}.zip" -d "$INPUTS_ARG_CHDIR" - rm --force "${{ steps.identifier.outputs.name }}.zip" + unzip "$GH_IDENTIFIER_NAME.zip" -d "$INPUTS_ARG_CHDIR" + rm --force "$GH_IDENTIFIER_NAME.zip" - if: ${{ inputs.plan-encrypt != '' && steps.download.outcome == 'success' }} env: @@ -256,7 +267,7 @@ runs: - if: ${{ inputs.command == 'plan' && inputs.upload-plan == 'true' && (github.server_url == 'https://github.com' || contains(github.server_url, '.ghe.com')) }} id: upload - uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: ${{ steps.identifier.outputs.name }} path: ${{ format('{0}{1}tfplan{2}', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '', inputs.plan-encrypt != '' && '.encrypted' || '') }} @@ -265,7 +276,7 @@ runs: - if: ${{ inputs.command == 'plan' && inputs.upload-plan == 'true' && !(github.server_url == 'https://github.com' || contains(github.server_url, '.ghe.com')) }} id: upload-v3 - uses: actions/upload-artifact@c24449f33cd45d4826c6702db7e49f7cdb9b551d # v3.2.1-node20 + uses: actions/upload-artifact@c6a366c94c3e0affe28c06c8df20a878f24da3cf # v3.2.2 with: name: ${{ steps.identifier.outputs.name }} path: ${{ format('{0}{1}tfplan{2}', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '', inputs.plan-encrypt != '' && '.encrypted' || '') }} @@ -316,15 +327,27 @@ runs: fi 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}" echo "$INPUTS_TOOL apply${{ steps.arg.outputs.arg-chdir }}${args}" | sed 's/ -/\n -/g' > tf.command.txt - $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} apply${args} 2> >(tee tf.console.txt) > >(tee tf.console.txt) + $INPUTS_TOOL${{ steps.arg.outputs.arg-chdir }} apply${args} 2>&1 | tee tf.console.txt - if: ${{ !cancelled() && steps.identifier.outcome == 'success' && contains(fromJSON('["plan", "apply", "init"]'), inputs.command) }} id: post env: - exitcode: ${{ steps.apply.outputs.exit_code || steps.plan.outputs.exit_code || steps.validate.outputs.exit_code || steps.initialize.outputs.exit_code || steps.format.outputs.exit_code }} + GH_ACTIONS_TOKEN: ${{ github.token }} + GH_EVENT_TIMESTAMP: ${{ github.event.pull_request.updated_at || github.event.comment.created_at || github.event.head_commit.timestamp || github.event.merge_group.head_commit.timestamp }} GH_MATRIX: ${{ toJSON(matrix) }} + GH_PR_NUMBER: ${{ steps.identifier.outputs.pr }} + GH_RUN_ATTEMPT: ${{ github.run_attempt }} + GH_RUN_ID: ${{ github.run_id }} + GH_SERVER_URL: ${{ github.server_url }} + GH_TRIGGERING_ACTOR: ${{ github.triggering_actor }} INPUTS_COMMAND: ${{ inputs.command }} INPUTS_COMMENT_METHOD: ${{ inputs.comment-method }} + INPUTS_COMMENT_POS_1: ${{ inputs.comment-pos-1 }} + INPUTS_COMMENT_POS_2: ${{ inputs.comment-pos-2 }} + INPUTS_COMMENT_POS_3: ${{ inputs.comment-pos-3 }} + INPUTS_COMMENT_POS_4: ${{ inputs.comment-pos-4 }} + INPUTS_COMMENT_POS_5: ${{ inputs.comment-pos-5 }} + INPUTS_COMMENT_POS_6: ${{ inputs.comment-pos-6 }} INPUTS_COMMENT_PR: ${{ inputs.comment-pr }} INPUTS_EXPAND_DIFF: ${{ inputs.expand-diff }} INPUTS_EXPAND_SUMMARY: ${{ inputs.expand-summary }} @@ -332,7 +355,7 @@ runs: INPUTS_PRESERVE_PLAN: ${{ inputs.preserve-plan }} INPUTS_SHOW_ARGS: ${{ inputs.show-args }} INPUTS_TAG_ACTOR: ${{ inputs.tag-actor }} - INPUTS_COMMENT_PREFIX: ${{ inputs.comment-prefix }} + exitcode: ${{ steps.apply.outputs.exit_code || steps.plan.outputs.exit_code || steps.validate.outputs.exit_code || steps.initialize.outputs.exit_code || steps.format.outputs.exit_code }} path: ${{ format('{0}{1}tfplan', inputs.arg-chdir || inputs.working-directory, (inputs.arg-chdir || inputs.working-directory) && '/' || '') }} shell: bash run: | @@ -371,7 +394,7 @@ runs: if [[ "${{ steps.format.outcome }}" == "failure" ]]; then syntax="diff"; fi # List jobs from the current workflow run. - workflow_run=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}/jobs --header "$GH_API" --method GET --paginate) + workflow_run=$(gh api /repos/$GH_REPO/actions/runs/$GH_RUN_ID/attempts/$GH_RUN_ATTEMPT/jobs --header "$GH_API" --method GET --paginate) # Get the current job ID from the workflow run using different query methods for matrix and regular jobs. if [[ "$GH_MATRIX" == "null" ]]; then @@ -392,22 +415,23 @@ runs: echo "Waiting to locate job ID; will try again in $retry_interval seconds." sleep "$retry_interval" retry_interval=$((retry_interval * 2)) - workflow_run=$(gh api /repos/${{ github.repository }}/actions/runs/${{ github.run_id }}/attempts/${{ github.run_attempt }}/jobs --header "$GH_API" --method GET --paginate) + workflow_run=$(gh api /repos/$GH_REPO/actions/runs/$GH_RUN_ID/attempts/$GH_RUN_ATTEMPT/jobs --header "$GH_API" --method GET --paginate) job_id=$(echo "$workflow_run" | jq --raw-output --arg matrix "$matrix" '.jobs[] | select((.name | contains("(")) and ((.name | split("(")[1]) | rtrimstr(")") | rtrimstr("...") | inside($matrix))) | .id' | tail -n 1) done fi echo "job=$job_id" >> "$GITHUB_OUTPUT" # Add summary to the job status. - check_run=$(GH_TOKEN=${{ github.token }} gh api /repos/${{ github.repository }}/check-runs/${job_id} --header "$GH_API" --method PATCH --field "output[title]=${summary}" --field "output[summary]=${summary}") + check_run=$(GH_TOKEN=$GH_ACTIONS_TOKEN gh api /repos/$GH_REPO/check-runs/${job_id} --header "$GH_API" --method PATCH --field "output[title]=${summary}" --field "output[summary]=${summary}") || true # Get the step number that has status "in_progress" from the current job. 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') # From "check_run", echo "html_url". - check_url=$(echo "$check_run" | jq --raw-output '.html_url') - echo "check_id=$(echo "$check_run" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT" - run_url=$(echo ${check_url}#step:${workflow_step}:1) + check_url=$(echo "$check_run" | jq --raw-output '.html_url // empty' 2>/dev/null) || true + echo "check_id=$(echo "$check_run" | jq --raw-output '.id // empty' 2>/dev/null)" >> "$GITHUB_OUTPUT" + run_url=${check_url:+${check_url}#step:${workflow_step}:1} + run_url=${run_url:-$GH_SERVER_URL/$GH_REPO/actions/runs/$GH_RUN_ID} echo "run_url=$run_url" >> "$GITHUB_OUTPUT" # If "tf.diff.txt" exists, display it within a "diff" block, truncated for character limit. @@ -443,27 +467,26 @@ runs: # Collate body content. body=$(cat < + ${INPUTS_COMMENT_POS_1} \`\`\`fish ${command} \`\`\` - + ${INPUTS_COMMENT_POS_2} ${diff} - + ${INPUTS_COMMENT_POS_3} ${summary}
- - ###### By ${tag_actor}${{ github.triggering_actor }} at ${{ github.event.pull_request.updated_at || github.event.comment.created_at || github.event.head_commit.timestamp || github.event.merge_group.head_commit.timestamp }} [(view log)](${run_url}). + ${INPUTS_COMMENT_POS_4} + ###### By ${tag_actor}${GH_TRIGGERING_ACTOR} at ${GH_EVENT_TIMESTAMP} [(view log)](${run_url}).
\`\`\`${syntax} ${console} \`\`\` - - - + ${INPUTS_COMMENT_POS_5} + + ${INPUTS_COMMENT_POS_6} EOBODYTFVIAPR ) @@ -480,13 +503,13 @@ runs: rm --force tf.command.txt tf.console.txt tf.diff.txt # Exit early if there is no PR to comment on. - if [[ "${{ steps.identifier.outputs.pr }}" -eq 0 ]]; then + if [[ "$GH_PR_NUMBER" -eq 0 ]]; then exit 0 fi # Check if the PR contains a bot comment with the same identifier. - list_comments=$(gh api /repos/${{ github.repository }}/issues/${{ steps.identifier.outputs.pr }}/comments --header "$GH_API" --method GET --paginate) - bot_comment=$(echo "$list_comments" | jq --raw-output --arg identifier "${{ steps.identifier.outputs.name }}" '.[] | select(.user.type == "Bot") | select(.body | contains($identifier)) | .id' | tail -n 1) + list_comments=$(gh api /repos/$GH_REPO/issues/$GH_PR_NUMBER/comments --header "$GH_API" --method GET --paginate) + bot_comment=$(echo "$list_comments" | jq --raw-output --arg identifier "$GH_IDENTIFIER_NAME" '.[] | select(.user.type == "Bot") | select(.body | contains($identifier)) | .id' | tail -n 1) # Determine if a PR comment should be created. if [[ "$INPUTS_COMMENT_PR" == "always" ]]; then @@ -496,7 +519,7 @@ runs: elif [[ ("$INPUTS_COMMENT_PR" == "on-diff" || "$INPUTS_COMMENT_PR" == "on-change") && "$INPUTS_COMMAND" != "plan" && -n "$bot_comment" ]]; then create_comment="true" elif [[ ("$INPUTS_COMMENT_PR" == "on-diff" || "$INPUTS_COMMENT_PR" == "on-change") && "$exitcode" -eq 0 && -n "$bot_comment" ]]; then - gh api /repos/${{ github.repository }}/issues/comments/${bot_comment} --header "$GH_API" --method DELETE + gh api /repos/$GH_REPO/issues/comments/${bot_comment} --header "$GH_API" --method DELETE || true exit 0 fi @@ -509,18 +532,18 @@ runs: if [[ -n "$bot_comment" ]]; then if [[ "$INPUTS_COMMENT_METHOD" == "update" ]]; then # Update existing comment. - pr_comment=$(gh api /repos/${{ github.repository }}/issues/comments/${bot_comment} --header "$GH_API" --method PATCH --field "body=${body}") - echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT" + pr_comment=$(gh api /repos/$GH_REPO/issues/comments/${bot_comment} --header "$GH_API" --method PATCH --field "body=${body}") || true + echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id // empty' 2>/dev/null)" >> "$GITHUB_OUTPUT" elif [[ "$INPUTS_COMMENT_METHOD" == "recreate" ]]; then # Delete previous comment before posting a new one. - gh api /repos/${{ github.repository }}/issues/comments/${bot_comment} --header "$GH_API" --method DELETE - pr_comment=$(gh api /repos/${{ github.repository }}/issues/${{ steps.identifier.outputs.pr }}/comments --header "$GH_API" --method POST --field "body=${body}") - echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT" + gh api /repos/$GH_REPO/issues/comments/${bot_comment} --header "$GH_API" --method DELETE || true + pr_comment=$(gh api /repos/$GH_REPO/issues/$GH_PR_NUMBER/comments --header "$GH_API" --method POST --field "body=${body}") || true + echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id // empty' 2>/dev/null)" >> "$GITHUB_OUTPUT" fi else # Post new comment. - pr_comment=$(gh api /repos/${{ github.repository }}/issues/${{ steps.identifier.outputs.pr }}/comments --header "$GH_API" --method POST --field "body=${body}") - echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT" + pr_comment=$(gh api /repos/$GH_REPO/issues/$GH_PR_NUMBER/comments --header "$GH_API" --method POST --field "body=${body}") || true + echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id // empty' 2>/dev/null)" >> "$GITHUB_OUTPUT" fi outputs: @@ -574,6 +597,30 @@ inputs: default: "update" description: "PR comment by: `update` existing comment or `recreate` and delete previous one (e.g., `update`)." required: false + comment-pos-1: + default: "" + description: "Markdown content to render at various positions in the PR comment (e.g., `Before command`)." + required: false + comment-pos-2: + default: "" + description: "Markdown content to render at various positions in the PR comment (e.g., `Before diff`)." + required: false + comment-pos-3: + default: "" + description: "Markdown content to render at various positions in the PR comment (e.g., `Before summary`)." + required: false + comment-pos-4: + default: "" + description: "Markdown content to render at various positions in the PR comment (e.g., `Before footer`)." + required: false + comment-pos-5: + default: "" + description: "Markdown content to render at various positions in the PR comment (e.g., `Before identifier`)." + required: false + comment-pos-6: + default: "" + description: "Markdown content to render at various positions in the PR comment (e.g., `After identifier`)." + required: false comment-pr: default: "always" description: "Add a PR comment: `always`, `on-diff`, or `never` (e.g., `always`)." @@ -646,10 +693,6 @@ inputs: default: "" description: "Specify the working directory of TF code, alias of `arg-chdir` (e.g., `stacks/dev`)." required: false - comment-prefix: - default: "" - description: "Prefix for the PR comment body (e.g., '# Terraform Plan for ')." - required: false # CLI arguments. arg-auto-approve: From 321ba72b47f7e4aecb44ccd333729cea810b5cec Mon Sep 17 00:00:00 2001 From: Rishav Dhar <19497993+rdhar@users.noreply.github.com> Date: Fri, 1 May 2026 19:58:58 +0100 Subject: [PATCH 7/7] Merge from #557 --- README.md | 227 ++++++++++++++++++++++++++---------------------------- 1 file changed, 109 insertions(+), 118 deletions(-) diff --git a/README.md b/README.md index d0677412..6951b712 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ * [![GitHub repository stargazers](https://img.shields.io/github/stars/op5dev/tf-via-pr)](https://github.com/op5dev/tf-via-pr "Become a stargazer.") -# Terraform/OpenTofu via Pull Request (TF-via-PR) +# Terraform/OpenTofu via Pull Request (tf-via-pr) @@ -33,13 +33,13 @@
-
+
### View: [Usage Examples](#usage-examples) · [Inputs](#inputs) · [Outputs](#outputs) · [Security](#security) · [Changelog](#changelog) · [License](#license) [![PR comment of plan output with "Diff of changes" section expanded.](/.github/assets/comment.png)](https://raw.githubusercontent.com/op5dev/tf-via-pr/refs/heads/main/.github/assets/comment.png "View full-size image.") -
+
## Usage Examples @@ -62,9 +62,9 @@ jobs: pull-requests: write # Required to add PR comment. steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - - uses: hashicorp/setup-terraform@v3 + - uses: hashicorp/setup-terraform@v4 with: terraform_wrapper: false @@ -86,7 +86,7 @@ jobs: > - Environment variables can be passed in for cloud platform authentication (e.g., [configure-aws-credentials](https://github.com/aws-actions/configure-aws-credentials "Configuring AWS credentials for use in GitHub Actions.") for short-lived credentials via OIDC). > - Recommend setting `terraform_wrapper`/`tofu_wrapper` to `false` in order to output the [detailed exit code](https://developer.hashicorp.com/terraform/cli/commands/plan#detailed-exitcode) for better error handling. -
+
### Where to find more examples? @@ -97,41 +97,41 @@ The following workflows showcase common use cases, while a comprehensive list of

#1 example ⤴

Runs on pull_request (plan) and push (apply) events with Terraform, AWS authentication and cache.

-
+

#2 example ⤴

Runs on pull_request (plan) and merge_group (apply) events with OpenTofu in matrix strategy.

-
+

#3 example ⤴

Runs on pull_request (plan) and push (apply) events with fmt/validate checks and TFLint.

-
+

#4 example ⤴

Runs on pull_request (plan) and push (apply) events with conditional jobs based on plan file.

-
+

#5 example ⤴

Runs on labeled and workflow_dispatch manual events on GitHub Enterprise (GHE) self-hosted runner.

-
+

#6 example ⤴

Runs on schedule cron event with -refresh-only to open an issue on configuration drift.

-
+
-
+
### Are there online references? @@ -142,7 +142,7 @@ The following workflows showcase common use cases, while a comprehensive list of 1. Awesome OpenTofu - [CI Tools](https://awesome-opentofu.com/#ci) 1. LinkedIn - [GitOps with Terraform](https://www.linkedin.com/posts/chris3ware_gitops-terraform-activity-7259857653781217280-ozl2?utm_source=share&utm_medium=member_desktop&rcm=ACoAABUNaiIBf1vPir8Ef4ONnj2ohruezX5pFPo) -
+
### How does encryption work? @@ -159,7 +159,7 @@ openssl enc -d -aes-256-ctr -pbkdf2 -salt \ show tfplan.decrypted ``` -
+
## Inputs @@ -167,100 +167,100 @@ All supported CLI argument inputs are [listed below](#arguments) with accompanyi ### Configuration -| Type | Name | Description | -| -------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | -| CLI | `working-directory` | Specify the working directory of TF code, alias of `arg-chdir`.
Example: `path/to/directory` | -| CLI | `command` | Command to run between: `plan` or `apply`.1
Example: `plan` | -| CLI | `tool` | Provisioning tool to use between: `terraform` or `tofu`.
Default: `terraform` | -| CLI | `plan-file` | Supply existing plan file path instead of the auto-generated one.
Example: `path/to/file.tfplan` | -| CLI | `pr-number` | Specify PR number in case of unsupported workflow trigger.
Example: `123` | -| Check | `format` | Check format of TF code.
Default: `false` | -| Check | `validate` | Check validation of TF code.
Default: `false` | -| Check | `plan-parity` | Replace plan file if it matches a newly-generated one to prevent stale apply.2
Default: `false` | -| Security | `plan-encrypt` | Encrypt plan file artifact with the given input.3
Example: `${{ secrets.PASSPHRASE }}` | -| Security | `preserve-plan` | Preserve plan file "tfplan" in the given working directory after workflow execution.
Default: `false` | -| Security | `upload-plan` | Upload plan file as GitHub workflow artifact.
Default: `true` | -| Security | `retention-days` | Duration after which plan file artifact will expire in days.
Example: `90` | -| Security | `token` | Specify a GitHub token.
Default: `${{ github.token }}` | -| UI | `expand-diff` | Expand the collapsible diff section.
Default: `false` | -| UI | `expand-summary` | Expand the collapsible summary section.
Default: `false` | -| UI | `comment-pr` | Add a PR comment: `always`, `on-diff`, or `never`.4
Default: `always` | -| UI | `comment-method` | PR comment by: `update` existing comment or `recreate` and delete previous one.5
Default: `update` | -| UI | `tag-actor` | Tag the workflow triggering actor: `always`, `on-diff`, or `never`.4
Default: `always` | -| UI | `hide-args` | Hide comma-separated list of CLI arguments from the command input.6
Default: `detailed-exitcode,parallelism,lock,out,var=` | -| UI | `show-args` | Show comma-separated list of CLI arguments in the command input.6
Default: `workspace` | -| UI | `comment-prefix` | Optional content to prepend to the default comment | - -
- -1. Both `command: plan` and `command: apply` include: `init`, `fmt` (with `format: true`), `validate` (with `validate: true`), and `workspace` (with `arg-workspace`) commands rolled into it automatically.
- To separately run checks and/or generate outputs only, `command: init` can be used.

-1. Originally intended for `merge_group` event trigger, `plan-parity: true` input helps to prevent stale apply within a series of workflow runs when merging multiple PRs.

-1. The secret string input for `plan-encrypt` can be of any length, as long as it's consistent between encryption (plan) and decryption (apply).

-1. The `on-diff` option is true when the exit code of the last TF command is non-zero (ensure `terraform_wrapper`/`tofu_wrapper` is set to `false`).

-1. The default behavior of `comment-method` is to update the existing PR comment with the latest plan/apply output, making it easy to track changes over time through the comment's revision history.

- [![PR comment revision history comparing plan and apply outputs.](/.github/assets/revisions.png)](https://raw.githubusercontent.com/op5dev/tf-via-pr/refs/heads/main/.github/assets/revisions.png "View full-size image.")

+| Type | Name | Description | +| -------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | +| CLI | `working-directory` | Specify the working directory of TF code, alias of `arg-chdir`.
Example: `path/to/directory` | +| CLI | `command` | Command to run between: `plan` or `apply`.1
Example: `plan` | +| CLI | `tool` | Provisioning tool to use between: `terraform` or `tofu`.
Default: `terraform` | +| CLI | `plan-file` | Supply existing plan file path instead of the auto-generated one.
Example: `path/to/file.tfplan` | +| CLI | `pr-number` | Specify PR number in case of unsupported workflow trigger.
Example: `123` | +| Check | `format` | Check format of TF code.
Default: `false` | +| Check | `validate` | Check validation of TF code.
Default: `false` | +| Check | `plan-parity` | Replace plan file if it matches a newly-generated one to prevent stale apply.2
Default: `false` | +| Security | `plan-encrypt` | Encrypt plan file artifact with the given input.3
Example: `${{ secrets.PASSPHRASE }}` | +| Security | `preserve-plan` | Preserve plan file "tfplan" in the given working directory after workflow execution.
Default: `false` | +| Security | `upload-plan` | Upload plan file as GitHub workflow artifact.
Default: `true` | +| Security | `retention-days` | Duration after which plan file artifact will expire in days.
Example: `90` | +| Security | `token` | Specify a GitHub token.
Default: `${{ github.token }}` | +| UI | `expand-diff` | Expand the collapsible diff section.
Default: `false` | +| UI | `expand-summary` | Expand the collapsible summary section.
Default: `false` | +| UI | `comment-pr` | Add a PR comment: `always`, `on-diff`, or `never`.4
Default: `always` | +| UI | `comment-method` | PR comment by: `update` existing comment or `recreate` and delete previous one.5
Default: `update` | +| UI | `comment-pos-N` | Markdown content to render at various positions in the PR comment.
Example: `> [!NOTE]\n> Reviewed by security.` | +| UI | `tag-actor` | Tag the workflow triggering actor: `always`, `on-diff`, or `never`.4
Default: `always` | +| UI | `hide-args` | Hide comma-separated list of CLI arguments from the command input.6
Default: `detailed-exitcode,parallelism,lock,out,var=` | +| UI | `show-args` | Show comma-separated list of CLI arguments in the command input.6
Default: `workspace` | + +
+ +1. Both `command: plan` and `command: apply` include: `init`, `fmt` (with `format: true`), `validate` (with `validate: true`), and `workspace` (with `arg-workspace`) commands rolled into it automatically.
+ To separately run checks and/or generate outputs only, `command: init` can be used.

+1. Originally intended for `merge_group` event trigger, `plan-parity: true` input helps to prevent stale apply within a series of workflow runs when merging multiple PRs.

+1. The secret string input for `plan-encrypt` can be of any length, as long as it's consistent between encryption (plan) and decryption (apply).

+1. The `on-diff` option is true when the exit code of the last TF command is non-zero (ensure `terraform_wrapper`/`tofu_wrapper` is set to `false`).

+1. The default behavior of `comment-method` is to update the existing PR comment with the latest plan/apply output, making it easy to track changes over time through the comment's revision history.

+ [![PR comment revision history comparing plan and apply outputs.](/.github/assets/revisions.png)](https://raw.githubusercontent.com/op5dev/tf-via-pr/refs/heads/main/.github/assets/revisions.png "View full-size image.")

1. It can be desirable to hide certain arguments from the last run command input to prevent exposure in the PR comment (e.g., sensitive `arg-var` values). Conversely, it can be desirable to show other arguments even if they are not in last run command input (e.g., `arg-workspace` or `arg-backend-config` selection). -
+
### Arguments > [!NOTE] > -> - Arguments are passed to the appropriate TF command(s) automatically, whether that's `fmt`, `init`, `validate`, `plan`, or `apply`.
+> - Arguments are passed to the appropriate TF command(s) automatically, whether that's `fmt`, `init`, `validate`, `plan`, or `apply`.
> - For repeated arguments like `arg-var`, `arg-var-file`, `arg-backend-config`, `arg-replace` and `arg-target`, use commas to separate multiple values (e.g., `arg-var: key1=value1,key2=value2`). -
- -Applicable to respective "plan" and "apply" `command` inputs ("init" included). - -| Name | CLI Argument | -| ------------------------- | ---------------------------------------- | -| `arg-auto-approve` | `-auto-approve` | -| `arg-backend-config` | `-backend-config` | -| `arg-backend` | `-backend` | -| `arg-backup` | `-backup` | -| `arg-chdir` | `-chdir`
Alias: `working-directory` | -| `arg-compact-warnings` | `-compact-warnings` | -| `arg-concise` | `-concise` | -| `arg-destroy` | `-destroy` | -| `arg-detailed-exitcode` | `-detailed-exitcode`
Default: `true` | -| `arg-force-copy` | `-force-copy` | -| `arg-from-module` | `-from-module` | -| `arg-generate-config-out` | `-generate-config-out` | -| `arg-get` | `-get` | -| `arg-lock-timeout` | `-lock-timeout` | -| `arg-lock` | `-lock` | -| `arg-lockfile` | `-lockfile` | -| `arg-migrate-state` | `-migrate-state` | -| `arg-parallelism` | `-parallelism` | -| `arg-plugin-dir` | `-plugin-dir` | -| `arg-reconfigure` | `-reconfigure` | -| `arg-refresh-only` | `-refresh-only` | -| `arg-refresh` | `-refresh` | -| `arg-replace` | `-replace` | -| `arg-state-out` | `-state-out` | -| `arg-state` | `-state` | -| `arg-target` | `-target` | -| `arg-upgrade` | `-upgrade` | -| `arg-var-file` | `-var-file` | -| `arg-var` | `-var` | -| `arg-workspace` | `-workspace`
Alias: `TF_WORKSPACE` | - -
+
+ +Applicable to respective "plan" and "apply" `command` inputs (including "init"). + +| Name | CLI Argument | +| ------------------------- | --------------------------------------- | +| `arg-auto-approve` | `-auto-approve` | +| `arg-backend-config` | `-backend-config` | +| `arg-backend` | `-backend` | +| `arg-backup` | `-backup` | +| `arg-chdir` | `-chdir`
Alias: `working-directory` | +| `arg-compact-warnings` | `-compact-warnings` | +| `arg-concise` | `-concise` | +| `arg-destroy` | `-destroy` | +| `arg-detailed-exitcode` | `-detailed-exitcode`
Default: `true` | +| `arg-force-copy` | `-force-copy` | +| `arg-from-module` | `-from-module` | +| `arg-generate-config-out` | `-generate-config-out` | +| `arg-get` | `-get` | +| `arg-lock-timeout` | `-lock-timeout` | +| `arg-lock` | `-lock` | +| `arg-lockfile` | `-lockfile` | +| `arg-migrate-state` | `-migrate-state` | +| `arg-parallelism` | `-parallelism` | +| `arg-plugin-dir` | `-plugin-dir` | +| `arg-reconfigure` | `-reconfigure` | +| `arg-refresh-only` | `-refresh-only` | +| `arg-refresh` | `-refresh` | +| `arg-replace` | `-replace` | +| `arg-state-out` | `-state-out` | +| `arg-state` | `-state` | +| `arg-target` | `-target` | +| `arg-upgrade` | `-upgrade` | +| `arg-var-file` | `-var-file` | +| `arg-var` | `-var` | +| `arg-workspace` | `-workspace`
Alias: `TF_WORKSPACE` | + +
Applicable only when `format: true`. -| Name | CLI Argument | -| --------------- | -------------------------------- | -| `arg-check` | `-check`
Default: `true` | -| `arg-diff` | `-diff`
Default: `true` | -| `arg-list` | `-list` | -| `arg-recursive` | `-recursive`
Default: `true` | -| `arg-write` | `-write` | +| Name | CLI Argument | +| --------------- | ------------------------------- | +| `arg-check` | `-check`
Default: `true` | +| `arg-diff` | `-diff`
Default: `true` | +| `arg-list` | `-list` | +| `arg-recursive` | `-recursive`
Default: `true` | +| `arg-write` | `-write` | -
+
Applicable only when `validate: true`. @@ -269,7 +269,7 @@ Applicable only when `validate: true`. | `arg-no-tests` | `-no-tests` | | `arg-test-directory` | `-test-directory` | -
+
## Outputs @@ -289,46 +289,37 @@ Applicable only when `validate: true`. | Workflow | `run-url` | URL of the workflow run. | | Workflow | `identifier` | Unique name of the workflow run and artifact. | -
+
## Security -View [security policy and reporting instructions](SECURITY.md). Integrating security in your CI/CD pipeline is critical to practicing DevSecOps. This action aims to be secure by default, and it should be complemented with your own review to ensure it meets your (organization's) security requirements. - -- Restrict changes to certain environments with [deployment protection rules](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules "Configuring environment deployment protection rules."). -- Integrate with [OpenID Connect](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers "Configuring OpenID Connect in cloud providers.") by passing short-lived credentials as environment variables. +View [security reporting policy](https://github.com/OP5dev/TF-via-PR?tab=security-ov-file). This project aims to be secure by default, and it should be complemented with one's own review to ensure it meets security requirements. > [!TIP] > -> Pin your GitHub Action to a [commit SHA](https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#using-third-party-actions "Security hardening for GitHub Actions.") to harden your CI/CD **pipeline security** against supply chain attacks. +> - Restrict changes to certain environments with [deployment protection rules](https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules "Configuring environment deployment protection rules."). +> - Integrate [OpenID Connect](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-cloud-providers "Configuring OpenID Connect in cloud providers.") to pass short-lived credentials as environment variables. -
+
## Changelog -View [all notable changes](https://github.com/op5dev/tf-via-pr/releases "Releases.") to this project in [Keep a Changelog](https://keepachangelog.com "Keep a Changelog.") format, which adheres to [Semantic Versioning](https://semver.org "Semantic Versioning."). +View [all notable changes](https://github.com/op5dev/tf-via-pr/releases "Releases.") to this project in [Keep a Changelog](https://keepachangelog.com/) format. > [!TIP] > -> All forms of **contribution are very welcome** and deeply appreciated for fostering open-source projects. -> -> - [Create a PR](https://github.com/op5dev/tf-via-pr/pulls "Create a pull request.") to contribute changes you'd like to see. -> - [Raise an issue](https://github.com/op5dev/tf-via-pr/issues "Raise an issue.") to propose changes or report unexpected behavior. -> - [Open a discussion](https://github.com/op5dev/tf-via-pr/discussions "Open a discussion.") to discuss broader topics or questions. -> - [Become a stargazer](https://github.com/op5dev/tf-via-pr/stargazers "Become a stargazer.") if you find this project useful. +> All forms of [contribution are welcome](https://github.com/OP5dev/TF-via-PR?tab=contributing-ov-file) and deeply appreciated for fostering open-source projects. -
+
### To-Do - Handling of inputs which contain space(s) (e.g., `working-directory: path to/directory`). - Handling of comma-separated inputs which contain comma(s) (e.g., `arg-var: token=1,2,3`); workaround with `TF_CLI_ARGS` [environment variable](https://developer.hashicorp.com/terraform/cli/config/environment-variables#tf_cli_args-and-tf_cli_args_name). -- Handling of interim build artifact(s) between `plan` and `apply` commands (e.g., zip archive); workaround with `arg-auto-approve: true` so that `apply` rebuilds artifact(s) for provisioning ([join discussion](https://github.com/OP5dev/TF-via-PR/issues/517)). +- Handling of interim build artifact(s) between `plan` and `apply` commands (e.g., zip archive); workaround with `arg-auto-approve: true` so that `apply` rebuilds artifact(s) for provisioning ([join discussion](https://github.com/op5dev/tf-via-pr/issues/517)). -
+
-## License +--- -- This project is licensed under the permissive [Apache License 2.0](LICENSE "Apache License 2.0."). -- All works herein are my own, shared of my own volition, and [contributors](https://github.com/op5dev/tf-via-pr/graphs/contributors "Contributors."). -- Copyright 2016-present [Rishav Dhar](https://github.com/rdhar "Rishav Dhar's GitHub profile.") — All wrongs reserved. +[tf-via-pr](https://github.com/op5dev/tf-via-pr) © 2016–present by [OP5.dev](https://github.com/OP5dev).