terragrunt-action icon indicating copy to clipboard operation
terragrunt-action copied to clipboard

Plan output not properly formatted

Open Mmasson-01 opened this issue 2 years ago • 11 comments
trafficstars

Describe the bug Capturing the terragrunt plan output to use it into a comment is not properly formatted. The plan output is on a single line.

image

To Reproduce Steps to reproduce the behavior, code snippets and examples which can be used to reproduce the issue.

    - name: Check terragrunt HCL
      uses: gruntwork-io/terragrunt-action@v1
      id: fmt
      with:
        tf_version: ${{ inputs.tf_version }}
        tg_version: ${{ inputs.tg_version }}
        tg_dir: ${{ inputs.workdir }}
        tg_command: 'hclfmt --terragrunt-check --terragrunt-diff'

    - name: Plan
      id: plan
      uses: gruntwork-io/terragrunt-action@v1
      with:
        tg_comment: 1
        tf_version: ${{ inputs.tf_version }}
        tg_version: ${{ inputs.tg_version }}
        tg_dir: ${{ inputs.workdir }}
        tg_command: 'plan'

    - uses: actions/github-script@v6
      if: github.event_name == 'pull_request'
      env:
        PLAN: "terraform\n${{ steps.plan.outputs.tg_action_output }}"
      with:
        github-token: ${{ inputs.GITHUB_TOKEN }}
        script: |
          // 1. Retrieve existing bot comments for the PR
          const { data: comments } = await github.rest.issues.listComments({
            owner: context.repo.owner,
            repo: context.repo.repo,
            issue_number: context.issue.number,
          })
          const botComment = comments.find(comment => {
            return comment.user.type === 'Bot' && comment.body.includes('Terragrunt Format and Style')
          })

          // 2. Prepare format of the comment
          const output = `#### Terragrunt Format and Style 🖌\`${{ steps.fmt.outcome }}\`

          #### Terragrunt Plan 📖\`${{ steps.plan.outcome }}\`

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

          \`\`\`\n
          ${{ steps.plan.outputs.tg_action_output }}
          \`\`\`

          </details>

          *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Workflow: \`${{ github.workflow }}\`*`;

          // 3. If we have a comment, update it, otherwise create a new one
          if (botComment) {
            github.rest.issues.updateComment({
              owner: context.repo.owner,
              repo: context.repo.repo,
              comment_id: botComment.id,
              body: output
            })
          } else {
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })
          }

Expected behavior Should output the plan formatted as shown in the action log:

image

Nice to have

  • [X] Terminal output
  • [X] Screenshots

Versions

  • Terragrunt Action version: v1
  • Environment details (Terragrunt version, Terraform version, etc.):
    • tg version: 0.46.3
    • tf version: 1.5.6

Additional context I've tried a few things to output in the proper format but no luck so far.

Mmasson-01 avatar Sep 02 '23 14:09 Mmasson-01

@Mmasson-01 @denis256 Was having similar problem with terraform, and it was solved but reformatting the plan output. I think it should work here too

      # Sed is taking all lines that begin with one or more spaces followed by a `+` or `-`.
      # It stores the amount of spaces in `\1` and the +/- in `\2`.
      # Then replace that portion of the line with `\2\1` (+/- followed by the number of matched spaces).
      # cat is used instead of echo to avoid issues with quotes.
      # plan is limited initially to 65300 characters as its the GitHub env variable limit
      - name: Reformat Plan
        if: steps.plan.outcome == 'success'
        run: |
          plan=$(cat <<'EOF'
          ${{ format('{0}{1}', steps.plan.outputs.stdout, steps.plan.outputs.stderr) }}
          EOF
          )
          echo "PLAN<<EOF" >> $GITHUB_ENV
          echo "${plan:0:65300}" | sed -E 's/^([[:space:]]+)([-+])/\2\1/g' | grep -v 'Refreshing state' >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

vorotech avatar Oct 02 '23 15:10 vorotech

@vorotech I attempted to use the code you pasted and am getting the following error:

Error: Process completed with exit code 1.
Error: Unable to process file command 'env' successfully.
Error: Invalid value. Matching delimiter not found 'EOF'

The code I am using is the following:

      - name: Reformat Plan
        if: steps.plan.outcome == 'success'
        run: |
          plan=$(cat <<'EOF'
          ${{ format('{0}', steps.plan.outputs.tg_action_output) }}
          EOF
          )
          echo "PLAN<<EOF" >> $GITHUB_ENV
          echo "${plan:0:65300}" | sed -E 's/^([[:space:]]+)([-+])/\2\1/g' | grep -v 'Refreshing state' >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV


      - uses: actions/github-script@v6
        with:
          script: |
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: "${{ env.PLAN }}"
            })

Unless I am doing something glaringly stupid (possible since I am sleep deprived) it looks like it will have to be modified a bit. I have been looking for an excuse to learn sed so I can take it on when I have time. Just an FYI.

aiell0 avatar Oct 04 '23 06:10 aiell0

@aiell0 I haven't tested myself. I mentioned that this is a code I'm using with terraform, not terrugrunt yet. And to be precise my previous step with plan looks like. Maybe the show command does slightly different output than plan.

      - name: Plan Terraform
        id: plan
        continue-on-error: true
        run: |
          cd ${{ inputs.TF_PATH }}
          terraform plan -input=false -no-color -out=tfplan \
          && terraform show -no-color tfplan

I will give it a try and update here.

vorotech avatar Oct 04 '23 08:10 vorotech

Yo! in case you still have the same issue, here's my current workaround for this:

Note 1: I use a matrix to iterate through several project Note 2: For commenting the PR I use if: always() as I want to get the result regardless if the plan fails or not.

    - name: Terragrunt Check HCL Formatting
      id: terragrunt_fmt
      uses: ./.github/actions/terragrunt
      with:
        TG_COMMAND: 'hclfmt --terragrunt-check --terragrunt-diff'
        TG_VERSION: ${{ env.TG_VERSION }}
        TG_DIR:     ${{ matrix.tg_dirs }}
        TF_VERSION: ${{ env.TF_VERSION }}

    - name: Terraform ${{ env.TG_COMMAND }}
      uses: gruntwork-io/terragrunt-action@v1
      id: tg_action
      with:
        tg_command: ${{ env.TG_COMMAND }}
        tf_version: ${{ env.TF_VERSION }}
        tg_version: ${{ env.TG_VERSION }}
        tg_dir:     ${{ matrix.tg_dirs }}

    - name: Update result icon
      if: always()
      run: |
        if [[ ${{ steps.terragrunt_fmt.outcome }} == "success" ]] ; then echo "RESULT_VALIDATE_ICON=$(echo ✅)" >> $GITHUB_ENV  ; else echo "RESULT_VALIDATE_ICON=$(echo ❌)" >> $GITHUB_ENV ; fi
          if [[ ${{ steps.tg_action.outcome }} == "success" ]] ; then echo "RESULT_ACTION_ICON=$(echo ✅)" >> $GITHUB_ENV  ; else echo "RESULT_ACTION_ICON=$(echo ❌)" >> $GITHUB_ENV ; fi

    - name: Output Cleaning
      if: always()
      run: |
        TG_OUT=$(echo "${{ steps.tg_action.outputs.tg_action_output }}" | sed 's|%0A|\n|g')
        echo "TG_PLAN_OUTPUT<<EOF" >> $GITHUB_ENV
        echo "$TG_OUT" >> $GITHUB_ENV
        echo "EOF" >> $GITHUB_ENV

    - name: Comment PR
      uses: actions/github-script@v6
      if: always()
      with:
        script: |
          github.rest.issues.createComment({
            issue_number: context.issue.number,
            owner: context.repo.owner,
            repo: context.repo.repo,
            body: `### *${{ github.workflow }}* Action ([Run #${{ github.run_number }}](${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }})) Summary 🚀
            #### Target directory: ${{ matrix.tg_dirs }}
            #### Terragrunt Format and Style 🖌️ ${{ steps.terragrunt_fmt.outcome }} ${{ env.RESULT_VALIDATE_ICON }}
            #### Terraform Plan 📖 **${{ steps.tg_action.outcome }}** ${{ env.RESULT_PLAN_ICON }}

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

            \`\`\`\n
            ${{ env.TG_PLAN_OUTPUT }}
            \`\`\`

            </details>

            Pusher: *@${{ github.actor }}*, Action: *${{ github.event_name }}*`
          })

yiskaneto avatar Oct 09 '23 21:10 yiskaneto

@escanoru I would suggest add ${TG_OUT:0:65300} on your output cleaning step to avoid argument too long error from GitHub script.

kamontat avatar Oct 11 '23 08:10 kamontat

@escanoru I ended up needing to clarify the statement a bit but got this working:

      - name: Output Cleaning
        id: clean
        run: |
          TG_OUT=$(echo '${{ steps.plan.outputs.tg_action_output }}' | sed 's|%0A|\n|g ; s|%3C|<|g')
          echo "TG_PLAN_OUTPUT<<EOF" >> $GITHUB_ENV
          echo "${TG_OUT:0:65300}" >> $GITHUB_ENV
          echo "EOF" >> $GITHUB_ENV

Thanks so much to everyone in here!

aiell0 avatar Oct 13 '23 02:10 aiell0

This is still fix problem on the surface. The real solution should fix within Terragrunt action script.

kamontat avatar Oct 16 '23 08:10 kamontat

Found a bug here....if the plan fails, it doesn't show up. Will look into a fix.

aiell0 avatar Oct 20 '23 18:10 aiell0

@escanoru I would suggest add ${TG_OUT:0:65300} on your output cleaning step to avoid argument too long error from GitHub script.

Thanks for the tip @kamontat, I didn't know about the Github action character limitation.

yiskaneto avatar Oct 28 '23 00:10 yiskaneto

Found a bug here....if the plan fails, it doesn't show up. Will look into a fix.

I came across this today, the reason why this happening is because I forgot to add if: always() condition on the 'Output Cleaning' step, after adding this, I caused an error on purpose and was able to get the fail plan. Hope this helps you.

Cheers!

yiskaneto avatar Oct 28 '23 00:10 yiskaneto

If you look in the action code, it escapes the plan so it can be passed as the action output.

You can find how it is done here: clean_multiline_text.

To make it multiline again you can unescape it in your script:

` .... const multiline_plan = unescape('${{ inputs.terragrunt_plan_output }}'); .... Show plan

${multiline_plan}

.... `

It would be nicer if the output could be formatted as JSON instead.

YuriGusev avatar Jul 28 '24 19:07 YuriGusev