Skip to content

Fails to Update Pull Request with Terraform Plan Output (Manually executed) #475

@gowgopal83

Description

@gowgopal83

I'm using the op5dev/tf-via-pr@v13 action in a GitHub Actions workflow to run Terraform plans and post the output to pull requests (PRs). The workflow runs successfully for Terraform plans, but it fails to update the PR with the plan output when triggered manually via workflow_dispatch. The error message is: [insert exact error message here]. This issue occurs consistently when using custom tf_directory and tf_workspace inputs. Any suggestions?

Workflow details

name: Terraform Plan on PR
on:
  pull_request:
    types: [opened, synchronize]
  workflow_dispatch:
    inputs:
      #  checkov:skip=CKV_GHA_7
      #  reason="This workflow manages team members by design and inputs are validated strictly.
      tf_directory:
        description: "Directory of the terraform code to plan"
        required: true
        default: "terraform/production"
        type: string
      tf_workspace:
        description: "Terraform workspace to use"
        required: true
        default: "default"
        type: string

permissions:
  actions: read
  checks: write
  contents: read
  pull-requests: write
  id-token: write

jobs:
  terraform_plan_on_pr:
    runs-on: dcloud-runner
    env:
      AWS_REGION: ${{ vars.DCLOUD_MANAGE_AWS_REGION }}
      TERRAFORM_IAM_ROLE_ARN: ${{ vars.DCLOUD_MANAGE_TF_IAM_ASSUME_ROLE }}
      GITHUB_APP_CREDS_SECRET_ARN: ${{ vars.DCLOUD_MANAGE_GH_RUNNER_SECRET_ARN }} # GH Actions org/repo vars

    steps:
      - name: Checkout PR code
        uses: actions/checkout@v4

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: ${{ env.TERRAFORM_IAM_ROLE_ARN }}
          role-session-name: "gh-tf-plan-pr-${{ github.run_id }}"
          aws-region: ${{ env.AWS_REGION }}

      - name: Generate GitHub App Token (for PR commenting and TF GitHub Provider)
        id: generate_app_token
        uses: actions/create-github-app-token@v2.0.6
        with:
          app-id: ${{ secrets.GH_RUNNER_IAC_APP_ID }}
          private-key: ${{ secrets.GH_RUNNER_APP_PEM }}
          owner: cisco-dcloud
          repositories: dcloud-vsphere-infra,tf_vsphere_vm

      - name: Configure Git for Private Modules
        run: |
          GHTOKEN="${{ steps.generate_app_token.outputs.token }}"
          git config --global \
          url."https://x-access-token:'${GHTOKEN}'@github.com/".insteadOf "https://github.com/"

      - name: Set Terraform Directory and Workspace
        id: set-vars
        run: |
          if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
            echo "Workflow triggered manually. Using inputs."
            echo "TF_DIR=${{ inputs.tf_directory }}" >> "$GITHUB_OUTPUT"
            echo "TF_WORKSPACE=${{ inputs.tf_workspace }}" >> "$GITHUB_OUTPUT"
          else
            echo "Workflow triggered by a Pull Request. Using defaults."
            echo "TF_DIR=terraform/production" >> "$GITHUB_OUTPUT"
            echo "TF_WORKSPACE=default" >> "$GITHUB_OUTPUT"
          fi

      - name: Find Pull Request Number
        id: find-pr
        env:
          GITHUB_TOKEN: ${{ github.token }}
        run: |
          if [ "${{ github.event_name }}" == "pull_request" ]; then
            PR_NUMBER=${{ github.event.pull_request.number }}
          else
            BRANCH_NAME="${{ github.ref_name }}"
            PR_NUMBER=$(gh pr list --head "$BRANCH_NAME" --json number --jq '.[0].number')
          fi
          echo "Found PR number: #${PR_NUMBER:-"none"}"
          echo "pr_number=$PR_NUMBER" >> "$GITHUB_OUTPUT"

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_version: "1.10.5"

      - name: Provision TF
        uses: op5dev/tf-via-pr@v13
        with:
          token: ${{ steps.generate_app_token.outputs.token }}
          command: ${{ github.event_name == 'push' && 'apply' || 'plan' }}
          # arg-lock: ${{ github.event_name == 'push' }}
          arg-backend-config: manage-account.ini
          arg-workspace: ${{ steps.set-vars.outputs.TF_WORKSPACE }}
          working-directory: ${{ steps.set-vars.outputs.TF_DIR }}
          comment-pr: on-change

Steps to Reproduce

  • Trigger the workflow via workflow_dispatch with inputs tf_directory=terraform/production and tf_workspace=default.
  • The Terraform plan executes successfully in the specified directory and workspace.
  • The tf-via-pr action attempts to post the plan output to the PR using comment-pr: on-change.
  • The PR update fails with the error: [insert exact error message].

Expected Behavior

  • The tf-via-pr action should post the Terraform plan output as a comment or check run on the associated PR.

Actual Behavior

The PR is not updated, and the workflow logs show: [insert error message or describe behavior, e.g., "failed to update check run" or "no PR comment posted"].

Run # TF show.
  # TF show.
  terraform -chdir=dcloud_core show tfplan > tf.console.txt
  
  # Diff of changes.
  # Filter lines starting with "  # " and save to tf.diff.txt, then prepend diff-specific symbols based on specific keywords.
  grep '^  # ' tf.console.txt | sed \
    -e 's/^  # \(.* be created\)/+ \1/' \
    -e 's/^  # \(.* be destroyed\)/- \1/' \
    -e 's/^  # \(.* be updated\|.* be replaced\)/! \1/' \
    -e 's/^  # \(.* be read\)/~ \1/' \
    -e 's/^  # \(.*\)/# \1/' > tf.diff.txt || true
  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
  env:
    AWS_REGION: us-east-1
    TERRAFORM_IAM_ROLE_ARN: arn:aws:iam::126237642437:role/tf_gh_actions_oidc_role
    GITHUB_APP_CREDS_SECRET_ARN: arn:aws:secretsmanager:us-east-1:126237642437:secret:/manage/default/gh_iac_runner_keys-WyKXsK
    AWS_DEFAULT_REGION: us-east-1
    AWS_ACCESS_KEY_ID: ***
    AWS_SECRET_ACCESS_KEY: ***
    AWS_SESSION_TOKEN: ***
    TERRAFORM_CLI_PATH: /home/runner/work/_temp/203f5c35-04eb-4cde-89d0-0608ef725e7c
    GH_API: X-GitHub-Api-Version:2022-11-28
    GH_TOKEN: ***
    TF_CLI_ARGS: -no-color
    TF_IN_AUTOMATION: true
    TF_INPUT: false
    TF_WORKSPACE: sng-dev
Run actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
  with:
    name: terraform--f965ee3c92a835683729bfa68b135048.tfplan
    path: dcloud_core/tfplan
    overwrite: true
    if-no-files-found: warn
    compression-level: 6
    include-hidden-files: false
  env:
    AWS_REGION: us-east-1
    TERRAFORM_IAM_ROLE_ARN: arn:aws:iam::126237642437:role/tf_gh_actions_oidc_role
    GITHUB_APP_CREDS_SECRET_ARN: arn:aws:secretsmanager:us-east-1:126237642437:secret:/manage/default/gh_iac_runner_keys-WyKXsK
    AWS_DEFAULT_REGION: us-east-1
    AWS_ACCESS_KEY_ID: ***
    AWS_SECRET_ACCESS_KEY: ***
    AWS_SESSION_TOKEN: ***
    TERRAFORM_CLI_PATH: /home/runner/work/_temp/203f5c35-04eb-4cde-89d0-0608ef725e7c
    GH_API: X-GitHub-Api-Version:2022-11-28
    GH_TOKEN: ***
    TF_CLI_ARGS: -no-color
    TF_IN_AUTOMATION: true
    TF_INPUT: false
    TF_WORKSPACE: sng-dev
With the provided path, there will be 1 file uploaded
Artifact name is valid!
Root directory input is valid!
Beginning upload of artifact content to blob storage
Uploaded bytes 36091
Finished uploading artifact content to blob storage!
SHA256 digest of uploaded artifact zip is 2909a528f8818f37cb1b6cc6ea5c1a1db97f2d3386755993e2aa176cb1a9b747
Finalizing artifact upload
Artifact terraform--f965ee3c92a835683729bfa68b135048.tfplan.zip successfully finalized. Artifact ID 3465434429
Artifact terraform--f965ee3c92a835683729bfa68b135048.tfplan has been successfully uploaded! Final size is 36091 bytes. Artifact ID is 3465434429
Artifact download URL: https://github.com/cisco-dcloud/dcloud-vsphere-infra/actions/runs/16072504626/artifacts/3465434429
Run # Post output.
  # Post output.
  # Parse the "tf.command.txt" file.
  command=$(cat tf.command.txt)
  
  # Remove each comma-delemited "hide-args" argument from the command.
  IFS=',' read -ra hide_args <<< "detailed-exitcode,parallelism,lock,out,var="
  for arg in "${hide_args[@]}"; do
    command=$(echo "$command" | grep --invert-match "^ -${arg}" || true)
  done
  
  # Conversely, show each comma-delemited "show-args" argument in the command.
  command_append=""
  IFS=',' read -ra show_args <<< "workspace"
  for arg in "${show_args[@]}"; do
    command_append+=$(echo " -workspace=sng-dev -backend-config=manage-account.ini -check -detailed-exitcode -diff -recursive" | sed 's/ -/\n -/g' | grep "^ -${arg}" || true)
  done
  
  # Consolidate 'command', taking both "hide-args" and "show-args" into account.
  command=$(echo "$command" | tr -d '\n')$command_append
  echo "command=$command" >> "$GITHUB_OUTPUT"
  
  # Parse the "tf.console.txt" file, truncated for character limit.
  console=$(head --bytes=42000 tf.console.txt)
  if [[ ${#console} -eq 42000 ]]; then console="${console}"$'\n…'; fi
  echo "result<<EORESULTTFVIAPR"$'\n'"$console"$'\n'EORESULTTFVIAPR >> "$GITHUB_OUTPUT"
  
  # Parse the "tf.console.txt" file for the summary.
  summary=$(awk '/^(Error:|Plan:|Apply complete!|No changes.|Success)/ {line=$0} END {if (line) print line; else print "View output."}' tf.console.txt)
  echo "summary=$summary" >> "$GITHUB_OUTPUT"
  
  # If "steps.format.outcome" failed, set syntax highlighting to "diff", otherwise set it to "hcl".
  syntax="hcl"
  if [[ "skipped" == "failure" ]]; then syntax="diff"; fi
  
  # Add summary to the job status.
  check_run=$(gh api /repos/cisco-dcloud/dcloud-vsphere-infra/check-runs/45360128229 --header "$GH_API" --method PATCH --field "output[title]=${summary}" --field "output[summary]=${summary}")
  
  # 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:9:1)
  echo "run_url=$run_url" >> "$GITHUB_OUTPUT"
  
  # If "tf.diff.txt" exists, display it within a "diff" block, truncated for character limit.
  if [[ -s tf.diff.txt ]]; then
    # Get count of lines in "tf.diff.txt" which do not start with "# ".
    diff_count=$(grep --invert-match '^# ' tf.diff.txt | wc --lines)
    if [[ $diff_count -eq 1 ]]; then diff_change="change"; else diff_change="changes"; fi
  
    # Parse diff of changes, truncated for character limit.
    diff_truncated=$(head --bytes=24000 tf.diff.txt)
    if [[ ${#diff_truncated} -eq 24000 ]]; then diff_truncated="${diff_truncated}"$'\n…'; fi
    echo "diff<<EODIFFTFVIAPR"$'\n'"$diff_truncated"$'\n'EODIFFTFVIAPR >> "$GITHUB_OUTPUT"
  
    diff="
  <details><summary>Diff of ${diff_count} ${diff_change}.</summary>
  
  \`\`\`diff
  ${diff_truncated}
  \`\`\`
  </details>"
  else
    diff=""
  fi
  
  # Set flags for creating PR comment and tagging actor.
  create_comment=""
  tag_actor=""
  if [[ $exitcode -ne 0 ]]; then
    if [[ "on-change" == "on-change" ]]; then create_comment="true"; fi
    if [[ "always" == "on-change" ]]; then tag_actor="true"; fi
  fi
  if [[ "on-change" == "always" ]]; then create_comment="true"; fi
  if [[ "always" == "always" ]]; then tag_actor="true"; fi
  if [[ "$tag_actor" == "true" ]]; then handle="@"; else handle=""; fi
  
  # Collate body content.
  body=$(cat <<EOBODYTFVIAPR
  <!-- placeholder-1 -->
  \`\`\`fish
  ${command}
  \`\`\`
  <!-- placeholder-2 -->
  ${diff}
  <!-- placeholder-3 -->
  <details><summary>${summary}</br>
  
  <!-- placeholder-4 -->
  ###### By ${handle}gowgopal83 at  [(view log)](${run_url}).
  </summary>
  
  \`\`\`${syntax}
  ${console}
  \`\`\`
  </details>
  <!-- placeholder-5 -->
  <!-- terraform--f965ee3c92a835683729bfa68b135048.tfplan -->
  <!-- placeholder-6 -->
  EOBODYTFVIAPR
  )
  
  # Post output to job summary.
  echo "$body" >> $GITHUB_STEP_SUMMARY
  echo "comment_body<<EOCOMMENTTFVIAPR"$'\n'"$body"$'\n'EOCOMMENTTFVIAPR >> "$GITHUB_OUTPUT"
  
  # Post PR comment if configured and PR exists.
  if [[ "$create_comment" == "true" && "" != "0" ]]; then
    # Check if the PR contains a bot comment with the same identifier.
    list_comments=$(gh api /repos/cisco-dcloud/dcloud-vsphere-infra/issues//comments --header "$GH_API" --method GET --field per_page=100)
    bot_comment=$(echo "$list_comments" | jq --raw-output --arg identifier "terraform--f965ee3c92a835683729bfa68b135048.tfplan" '.[] | select(.user.type == "Bot") | select(.body | contains($identifier)) | .id' | tail -n 1)
  
    if [[ -n "$bot_comment" ]]; then
      if [[ "update" == "recreate" ]]; then
        # Delete previous comment before posting a new one.
        gh api /repos/cisco-dcloud/dcloud-vsphere-infra/issues/comments/${bot_comment} --header "$GH_API" --method DELETE
        pr_comment=$(gh api /repos/cisco-dcloud/dcloud-vsphere-infra/issues//comments --header "$GH_API" --method POST --field "body=${body}")
        echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT"
      elif [[ "update" == "update" ]]; then
        # Update existing comment.
        pr_comment=$(gh api /repos/cisco-dcloud/dcloud-vsphere-infra/issues/comments/${bot_comment} --header "$GH_API" --method PATCH --field "body=${body}")
        echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT"
      fi
    else
      # Post new comment.
      pr_comment=$(gh api /repos/cisco-dcloud/dcloud-vsphere-infra/issues//comments --header "$GH_API" --method POST --field "body=${body}")
      echo "comment_id=$(echo "$pr_comment" | jq --raw-output '.id')" >> "$GITHUB_OUTPUT"
    fi
  elif [[ "on-change" == "on-change" && "" != "0" ]]; then
    # Delete previous comment due to no changes.
    list_comments=$(gh api /repos/cisco-dcloud/dcloud-vsphere-infra/issues//comments --header "$GH_API" --method GET --field per_page=100)
    bot_comment=$(echo "$list_comments" | jq --raw-output --arg identifier "terraform--f965ee3c92a835683729bfa68b135048.tfplan" '.[] | select(.user.type == "Bot") | select(.body | contains($identifier)) | .id' | tail -n 1)
  
    if [[ -n "$bot_comment" ]]; then
      gh api /repos/cisco-dcloud/dcloud-vsphere-infra/issues/comments/${bot_comment} --header "$GH_API" --method DELETE
    fi
  fi
  
  # Optionally delete the plan file.
  if [[ "$PRESERVE_PLAN" != "true" ]]; then
    rm --force "dcloud_core/tfplan"
  fi
  
  # Clean up files.
  rm --force tf.command.txt tf.console.txt tf.diff.txt
  shell: /usr/bin/bash --noprofile --norc -e -o pipefail {0}
  env:
    AWS_REGION: us-east-1
    TERRAFORM_IAM_ROLE_ARN: arn:aws:iam::126237642437:role/tf_gh_actions_oidc_role
    GITHUB_APP_CREDS_SECRET_ARN: arn:aws:secretsmanager:us-east-1:126237642437:secret:/manage/default/gh_iac_runner_keys-WyKXsK
    AWS_DEFAULT_REGION: us-east-1
    AWS_ACCESS_KEY_ID: ***
    AWS_SECRET_ACCESS_KEY: ***
    AWS_SESSION_TOKEN: ***
    TERRAFORM_CLI_PATH: /home/runner/work/_temp/203f5c35-04eb-4cde-89d0-0608ef725e7c
    GH_API: X-GitHub-Api-Version:2022-11-28
    GH_TOKEN: ***
    TF_CLI_ARGS: -no-color
    TF_IN_AUTOMATION: true
    TF_INPUT: false
    TF_WORKSPACE: sng-dev
    exitcode: 0
    PRESERVE_PLAN: false
gh: Resource not accessible by integration (HTTP 403)
Error: Process completed with exit code 1.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions