hashicorp / setup-terraform

Sets up Terraform CLI in your GitHub Actions workflow.
https://developer.hashicorp.com/terraform/tutorials/automation/github-actions
Mozilla Public License 2.0
1.36k stars 236 forks source link

Migrating from terraform-github-actions with PR comment support is hard #7

Open mdobosz-isp opened 4 years ago

mdobosz-isp commented 4 years ago

I am trying to migrate from terraform-github-actions to take advantage of the speed increase of running Javascript-based actions and native commands over Docker-based actions. I am running into some trouble to replicate the functionality of automatic PR comments.

The Readme provides this as an equivalent example:

steps:
- uses: hashicorp/setup-terraform@v1

- run: terraform init

- id: plan
  run: terraform plan -no-color

- uses: actions/github-script@0.9.0
  if: github.event_name == 'pull_request'
  env:
    STDOUT: "```${{ steps.plan.outputs.stdout }}```"
  with:
    github-token: ${{ secrets.GITHUB_TOKEN }}
    script: |
      github.issues.createComment({
        issue_number: context.issue.number,
        owner: context.repo.owner,
        repo: context.repo.repo,
        body: process.env.STDOUT
      })

However this is not correct, as far as I can tell, due to a number of issues

  1. To be able to detect that a plan results in a change, you need to invoke run: terraform plan -no-color -detailed-exitcode.
  2. Then to ensure that a PR comment is emitted only if there's a change, you need to modify the condition to: if: github.event_name == 'pull_request' && steps.plan.outputs.exitcode == 2
  3. Of course, to be able to even run the PR comment step you need to make sure that the plan step doesn't return an error: run: terraform plan -no-color -detailed-exitcode || echo 0
  4. But you still want the job to fail if terraform plan returns code 1 in case there's a problem with the terraform files, maybe by adding if (${{ steps.plan.outputs.exitcode }} == 1) { core.setFailed("There's a problem with the plan"); } after the PR comment is created.

I can try to whip up a PR to try to at least improve the Readme, but I wanted to ask for some thoughts first on what the intended usage is.

sudomateo commented 4 years ago

The behavior in the README assumes a non-zero exit code for the plan step should fail the workflow job and not comment back on the pull request.

If you wish to used the -detailed-exitcode argument and comment on the pull request regardless of the exit code of the plan step, you can use this. Note, this will still mark the overall workflow job as failed when the plan step returns a non-zero exit code.

    - name: Terraform Plan
      id: plan
      run: terraform plan -no-color -detailed-exitcode

    - uses: actions/github-script@0.9.0
      if: ${{ github.event_name == 'pull_request' && (success() || failure()) }}

If you do not want the plan step to fail the entire job, you can use the following. This will effectively ignore the exit code of the plan step and continue with the subsequent steps, allowing you to decide what actions to take with the outputs from the plan step.

    - name: Terraform Plan
      id: plan
      run: terraform plan -no-color -detailed-exitcode
      continue-on-error: true

    - uses: actions/github-script@0.9.0
      if: ${{ github.event_name == 'pull_request' && (success() || failure()) }}

    - name: Check Terraform Plan
      if: ${{ steps.plan.outputs.exitcode == 1 }}
      run: |
        echo "Terraform plan returned ${{ steps.plan.outputs.exitcode }}"
        exit ${{ steps.plan.outputs.exitcode }}

You can also swap out the (success() || failure()) logic for your own logic. If you wished to only comment on the pull request when -detailed-exitcode is 2 but fail the workflow job with -detailed-exitcode is 1, you can use this.

    - name: Terraform Plan
      id: plan
      run: terraform plan -no-color -detailed-exitcode
      continue-on-error: true

    - uses: actions/github-script@0.9.0
      if: ${{ github.event_name == 'pull_request' && steps.plan.outputs.exitcode == 2 }}

    - name: Check Terraform Plan Exit Code
      if: ${{ steps.plan.outputs.exitcode == 1 }}
      run: |
        echo "Terraform plan returned ${{ steps.plan.outputs.exitcode }}"
        exit ${{ steps.plan.outputs.exitcode }}

Maybe we can add a separate example in the README for users of -detailed-exitcode?

Ameausoone commented 4 years ago

That's too bad, previously I just had to do this :

- name: 'Terraform Format'  
        uses: hashicorp/terraform-github-actions@master 
        with:   
          tf_actions_version: ${{ env.tf_version }} 
          tf_actions_subcommand: 'fmt'  
          tf_actions_comment: true

Which is far more clear. (awesome work BTW)

bchr73 commented 4 years ago

@sudomateo Is there any way to capture the output of run: terraform validate -no-color ?

I've tried the following:

  1.     - name: Terraform Validate
          id: validate
          run: echo "::set-output name=output::$(terraform validate -no-color)"
  2.     - name: Terraform Validate
          id: validate
          run: |
              TF_VAL=$(terraform validate -no-color)
              TF_VAL="${TF_VAL//'%'/'%25'}"
              TF_VAL="${TF_VAL//$'\n'/'%0A'}"
              TF_VAL="${TF_VAL//$'\r'/'%0D'}"
              echo "::set-output name=output::$TF_VAL"

In either case I try to access the output of the validate stage like so:

            - name: Terraform Comment
              if: always()
              uses: actions/github-script@v2
              env:
                  PLAN: "${{ steps.plan.outputs.stdout}}"
                  VALIDATE: "${{ steps.validate.outputs.output}}"
              ...

So that I can output the comments to the PR as a comment, but no luck.

sudomateo commented 4 years ago

@bchr73 you can try something like this.

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1

    - name: Terraform Init
      run: terraform init

    - name: Terraform Validate
      id: validate
      run: terraform validate -no-color

    - run: echo ${{ steps.validate.outputs.stdout }}
bchr73 commented 4 years ago

@sudomateo The output of that step is then just as follows:

Run Echo                                                                                                   0s
1 > Run echo 
2    echo 
3    shell: /bin/bash -e {0}
4    env:
5    TERRAFORM_CLI_PATH: /home/runner/work/_temp/037498ad-9268-421e-a014-f2b0d1c0b82f
6

And nothing in the comments.

My steps ..

steps:
            - name: Setup Terraform
              uses: hashicorp/setup-terraform@v1

            - name: Terraform fmt
              id: fmt
              run: terraform fmt

            - name: Terraform Init
              id: init
              run: terraform init

            - name: Terraform Validate
              id: validate
              run: terraform validate -no-color

            - id: validate_out
              if: always()
              run: echo ${{ steps.validate.outputs.stdout }}

            - name: Terraform Plan
              id: plan
              run: terraform plan -no-color -detailed-exitcode

            - name: Terraform Comment
              if: always()
              uses: actions/github-script@v2
              env:
                  PLAN: "${{ steps.plan.outputs.stdout }}"
                  VALIDATE: "${{ steps.validate_out.outputs.stdout }}"
              with:
                  github-token: ${{ secrets.GITHUB_TOKEN }}
                  script: |
                      const output = `#### Terraform Format and Style ๐Ÿ–Œ\`${{ steps.fmt.outcome }}\`
                      #### Terraform Initialization โš™๏ธ\`${{ steps.init.outcome }}\`
                      #### Terraform Validation ๐Ÿค–${{ steps.validate.outcome }}

                      <details><summary>Show</summary>\`\`\`${process.env.VALIDATE}\`\`\`</details>

                      #### Terraform Plan ๐Ÿ“–\`${{ steps.plan.outcome }}\`

                      <details><summary>Show</summary>\`\`\`${process.env.PLAN}\`\`\`</details>

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

                      github.issues.createComment({
                        issue_number: context.issue.number,
                        owner: context.repo.owner,
                        repo: context.repo.repo,
                        body: output
                      })

Any idea?

sudomateo commented 4 years ago

@bchr73 you have a step with id set to validate and another step with id set to validate_out. Only the validate step contains an output so you should probably use that inside your comment logic instead.

bchr73 commented 4 years ago

@sudomateo

@bchr73 you can try something like this.

    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v1

    - name: Terraform Init
      run: terraform init

    - name: Terraform Validate
      id: validate
      run: terraform validate -no-color

    - run: echo ${{ steps.validate.outputs.stdout }}

So what were you suggesting here? What is the purpose of - run: echo ${{ steps.validate.outputs.stdout }} and how would I reference it?

sudomateo commented 4 years ago

@bchr73 it was just meant to show you how to access the output of the validate step. The echo command isn't meaningful there, it was just used as an example.

bchr73 commented 4 years ago

@sudomateo

Oh I see thank you, I already knew that part, I tried ${{ steps.validate.outputs.stdout }} but that wasn't working so I tried to explicitly set an output variable with set-output (and it still didn't work).

My problem is when I try and access anything from my actions/github-script

            - name: Terraform Comment
              if: always()
              uses: actions/github-script@v2
              env:
                  PLAN: "${{ steps.plan.outputs.stdout }}"
                  VALIDATE: "${{ steps.validate.outputs.stdout }}"
              ...

The resulting comment is always empty

              <details><summary>Show</summary>\`\`\`${process.env.VALIDATE}\`\`\`</details>
sudomateo commented 4 years ago

@bchr73 This workflow works for me.

Show Workflow ``` name: 'Terraform' on: pull_request: jobs: terraform: name: 'Terraform' runs-on: ubuntu-latest defaults: run: shell: bash steps: - name: Checkout uses: actions/checkout@v2 - name: Setup Terraform uses: hashicorp/setup-terraform@v1 - name: Terraform Init run: terraform init - name: Terraform Validate id: validate run: terraform validate -no-color - run: echo ${{ steps.validate.outputs.stdout }} - uses: actions/github-script@v2 if: github.event_name == 'pull_request' env: VALIDATE: "terraform\n${{ steps.validate.outputs.stdout }}" with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const output = `#### Terraform Validate \`${{ steps.validate.outcome }}\`
Show Validate Output \`\`\`${process.env.VALIDATE}\`\`\`
*Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`; github.issues.createComment({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, body: output }) ```

It produces a comment like so.

Screenshot_2020-07-18 Testing by sudomateo ยท Pull Request #1 ยท sudomateo actions-testing

bchr73 commented 4 years ago

@sudomateo

So does mine, but only when the outcome of the validate step is success. I'm [also] interested in the error output of the validate step being output to the comments when it fails. I've been keeping my PR in a state where the validate stage is guaranteed to fail to test it.

sudomateo commented 4 years ago

You'll probably want to access the stderr output instead then. I'm not sure if the terraform validate command outputs to STDOUT or STDERR when there is a failure.

bchr73 commented 4 years ago

@sudomateo

Thank you, that has solved the issue for me, seems so obvious in retrospect. I appreciate you taking time on a Sat :)

I'm not sure of the terraform validate command outputs to STDOUT or STDERR when there is a failure.

Did you mean I'm not sure [if] the ...? Otherwise can you kindly clarify the above? Thanks.

sudomateo commented 4 years ago

Yep. I meant if. Sorry!

brettcurtis commented 3 years ago

Should this give me the plan output:

    # Generates an execution plan for Terraform
    - name: Terraform Plan
      id: plan
      run: terraform plan -no-color
      continue-on-error: true

    - name: Check Terraform Plan
      run: |
        echo "Terraform plan returned ${{ steps.plan.outputs.stdout }}"

I'm trying to follow this howto but I'm not getting any stdout.

bchr73 commented 3 years ago

@brettcurtis For what it's worth, I seem to remember I was having issues because I was forgetting that steps will only forward to stdout if they have exited successfully. If your plan stage has exited with an error, you should be checking the output for stderr

brettcurtis commented 3 years ago

@bchr73, in this case my plan is successful so I'd expect something back.

bchr73 commented 3 years ago

I mean that's essentially what mine looks like, except I'm getting actions to post a comment:


    - name: Terraform Plan
      id: plan
      run: terraform plan -refresh=true -no-color -detailed-exitcode
      continue-on-error: true

    - name: Terraform Comment
      if: always()
      uses: actions/github-script@v2
      env:
          PLAN: "${{ steps.plan.outputs.stderr }}\n${{ steps.plan.outputs.stdout }}"
      with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
               const output = ` ### Report

               #### Terraform Plan - \`${{ steps.plan-check.outputs.check }}\` - ๐Ÿ“–

                \`\`\`${process.env.PLAN}\`\`\` `;

                github.issues.createComment({
                    issue_number: context.issue.number,
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    body: output
                 })
brettcurtis commented 3 years ago

I made a public repo with a simple test: https://github.com/lzysh/test-plan-output/runs/1232224004?check_suite_focus=true

It's highly possible I'm missing something, but should I expect an output here from line 39/40 ??

https://github.com/lzysh/test-plan-output/blob/master/.github/workflows/terraform.yml#L39

clavinjune commented 2 years ago

Hi! I have my solution works for sending terraform plan -no-color output to the comment section

Here's my yaml file

...

      - name: Terraform Plan
        id: plan
        if: github.event_name == 'pull_request'
        run: |
          out="$(terraform plan -no-color)"

 # code below makes set-output keeps the multiline
 # refer to this https://github.community/t/set-output-truncates-multiline-strings/16852/3
          out="${out//'%'/'%25'}"
          out="${out//$'\n'/'%0A'}"
          out="${out//$'\r'/'%0D'}"
          echo "::set-output name=plan::$out"
        continue-on-error: true

      - uses: actions/github-script@v5
        if: github.event_name == 'pull_request'
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const output = `#### Terraform Format and Style ๐Ÿ–Œ\`${{ steps.fmt.outcome }}\`
            #### Terraform Initialization โš™๏ธ\`${{ steps.init.outcome }}\`
            #### Terraform Validation ๐Ÿค–\`${{ steps.validate.outcome }}\`
            #### Terraform Plan ๐Ÿ“–\`${{ steps.plan.outcome }}\`
            <details><summary>Show Plan</summary>

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

            </details>

            *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`*`;
            github.rest.issues.createComment({
              issue_number: context.issue.number,
              owner: context.repo.owner,
              repo: context.repo.repo,
              body: output
            })

...

I hope this helps! @mdobosz-isp

OceaneLonneux-CAL commented 2 years ago

Used @ClavinJune code, with terraform_wrapper set to false and got it working!