diff --git a/action.yml b/action.yml new file mode 100644 index 0000000..6521197 --- /dev/null +++ b/action.yml @@ -0,0 +1,73 @@ +name: "Terraform/OpenTofu PR Commenter" +description: "Posts Terraform/OpenTofu `fmt`, `init`, `plan`, or `validate` results as PR comments on GitHub or Gitea." +author: "Charish Patel" + +# Define the inputs +inputs: + command: + description: "The command type: fmt | init | plan | validate" + required: true + commenter_input: + description: "Stdout or file list from previous Terraform/OpenTofu step" + required: true + commenter_exitcode: + description: "Exit code from previous Terraform/OpenTofu step" + required: true + pr_number: + description: "PR number" + required: false + pr_comments_url: + description: "URL for PR comments API" + required: false + pr_comment_uri: + description: "URI for individual PR comment API" + required: false + github_token: + description: "GitHub token (if running in GitHub Actions)" + required: false + gitea_token: + description: "Gitea token (if running in Gitea Actions)" + required: false + gitea_action: + description: "Set to true if running in Gitea Actions" + required: false + default: "false" + gitea_api_url: + description: "Gitea API URL" + required: false + gitea_repository: + description: "Gitea repository in format owner/repo" + required: false + highlight_changes: + description: "Set to false to disable diff highlighting" + required: false + default: "true" + tf_workspace: + description: "Terraform/OpenTofu workspace (default: 'default')" + required: false + default: default + + +# Define the action type +runs: + using: "composite" + steps: + - name: Run TF/Tofu PR Commenter + shell: bash + run: | + chmod +x ${{ github.action_path }}/tf-pr-comment.sh + ${{ github.action_path }}/tf-pr-comment.sh "${{ inputs.command }}" "${{ inputs.commenter_input }}" "${{ inputs.commenter_exitcode }}" + env: + # PR Environment Variables + PR_NUMBER: ${{ inputs.pr_number }} + PR_COMMENTS_URL: ${{ inputs.pr_comments_url }} + PR_COMMENT_URI: ${{ inputs.pr_comment_uri }} + GITHUB_TOKEN: ${{ inputs.github_token }} + GITEA_TOKEN: ${{ inputs.gitea_token }} + GITEA_ACTION: ${{ inputs.gitea_action }} + GITEA_API_URL: ${{ inputs.gitea_api_url }} + GITEA_REPOSITORY: ${{ inputs.gitea_repository }} + # Optional highlighting + HIGHLIGHT_CHANGES: ${{ inputs.highlight_changes }} + TF_WORKSPACE: ${{ inputs.tf_workspace }} + diff --git a/tf-pr-comment.sh b/tf-pr-comment.sh new file mode 100644 index 0000000..75fe08f --- /dev/null +++ b/tf-pr-comment.sh @@ -0,0 +1,148 @@ +#!/usr/bin/env bash +set -e + +################## +# Inputs +################## +COMMAND="$1" # fmt | init | plan | validate +COMMENTER_INPUT="$2" # stdout/stderr output OR newline-separated file list (for fmt) +COMMENTER_EXITCODE="$3" # exit code from previous step +WORKSPACE="${TF_WORKSPACE:-default}" + +################## +# CI Detection +################## +if [[ -n "$GITHUB_ACTIONS" ]]; then + CI_PLATFORM="github" +elif [[ -n "$GITEA_ACTION" ]]; then + CI_PLATFORM="gitea" +else + CI_PLATFORM="local" +fi +echo "Detected CI platform: $CI_PLATFORM" + +################## +# PR Variables +################## +if [[ "$CI_PLATFORM" == "github" ]]; then + : "${PR_NUMBER:?PR_NUMBER not set}" + : "${PR_COMMENTS_URL:?PR_COMMENTS_URL not set}" + : "${PR_COMMENT_URI:?PR_COMMENT_URI not set}" + AUTH_HEADER="Authorization: token $GITHUB_TOKEN" +elif [[ "$CI_PLATFORM" == "gitea" ]]; then + : "${PR_NUMBER:?PR_NUMBER not set}" + : "${PR_COMMENTS_URL:?PR_COMMENTS_URL not set}" + : "${PR_COMMENT_URI:?PR_COMMENT_URI not set}" + : "${GITEA_TOKEN:?GITEA_TOKEN not set}" + AUTH_HEADER="Authorization: token $GITEA_TOKEN" +else + echo "Local mode: PR variables must be set manually." + exit 1 +fi + +ACCEPT_HEADER="Accept: application/vnd.github.v3+json" +CONTENT_HEADER="Content-Type: application/json" + +################## +# Shared Settings +################## +DETAILS_STATE=" open" +COLOURISE=${HIGHLIGHT_CHANGES:-true} + +################## +# Helper Functions +################## +handle_pr_comment() { + local type="$1" + local body="$2" + + PR_COMMENT_ID=$(curl -sS -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -L "$PR_COMMENTS_URL" \ + | jq ".[] | select(.body|test(\"### tofu \\\`$type\\\`\")) | .id") + + if [[ "$PR_COMMENT_ID" ]]; then + echo "Updating existing $type PR comment: $PR_COMMENT_ID" + PR_COMMENT_URL="$PR_COMMENT_URI/$PR_COMMENT_ID" + PR_PAYLOAD=$(jq -n --arg body "$body" '{body: $body}') + curl -sS -X PATCH -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -H "$CONTENT_HEADER" \ + -d "$PR_PAYLOAD" -L "$PR_COMMENT_URL" > /dev/null + else + echo "Creating new $type PR comment" + PR_PAYLOAD=$(jq -n --arg body "$body" '{body: $body}') + curl -sS -X POST -H "$AUTH_HEADER" -H "$ACCEPT_HEADER" -H "$CONTENT_HEADER" \ + -d "$PR_PAYLOAD" -L "$PR_COMMENTS_URL" > /dev/null + fi +} + +format_diff_block() { + local content="$1" + local codeblock="" + if [[ $CI_PLATFORM == "github" && $COLOURISE == "true" ]]; then + content=$(echo "$content" | sed -r 's/^~/!/g') + codeblock="diff" + fi + echo "```$codeblock +$content +```" +} + +################## +# Command Handlers +################## +case "$COMMAND" in + +fmt) + STATUS="Succeeded" + [[ "$COMMENTER_EXITCODE" -ne 0 ]] && STATUS="Failed" + + ALL_DIFFS="" + # COMMENTER_INPUT is expected to be a newline-separated file list + while IFS= read -r file; do + if [[ -n "$file" && -f "$file" ]]; then + FILE_DIFF=$(tofu fmt -no-color -write=false -diff "$file" 2>/dev/null || true) + DIFF_BLOCK=$(format_diff_block "$FILE_DIFF") + ALL_DIFFS="$ALL_DIFFS +$file + +$DIFF_BLOCK +" + fi + done <<< "$COMMENTER_INPUT" + + # Fallback if no valid files found + [[ -z "$ALL_DIFFS" ]] && ALL_DIFFS=$(format_diff_block "$COMMENTER_INPUT") + + PR_COMMENT="### tofu \`fmt\` $STATUS for Workspace: \`$WORKSPACE\` +$ALL_DIFFS" + handle_pr_comment "$COMMAND" "$PR_COMMENT" + ;; + +plan) + STATUS="Succeeded" + [[ "$COMMENTER_EXITCODE" -ne 0 ]] && STATUS="Failed" + PR_OUTPUT=$(format_diff_block "$COMMENTER_INPUT") + PR_COMMENT="### tofu \`plan\` $STATUS for Workspace: \`$WORKSPACE\` +Show Output + +$PR_OUTPUT +" + handle_pr_comment "$COMMAND" "$PR_COMMENT" + ;; + +init|validate) + STATUS="Succeeded" + [[ "$COMMENTER_EXITCODE" -ne 0 ]] && STATUS="Failed" + PR_COMMENT="### tofu \`$COMMAND\` $STATUS for Workspace: \`$WORKSPACE\` +Show Output + +\`\`\` +$COMMENTER_INPUT +\`\`\` +" + handle_pr_comment "$COMMAND" "$PR_COMMENT" + ;; + +*) + echo "Unsupported command: $COMMAND" + exit 1 + ;; +esac