github actions(w/z composite) + setup-terraform in 2022

# tl;dr

- [terraform Advent Calendar 2022](https://qiita.com/advent-calendar/2022/terraform)の2日目の記事です
- [去年の記事](https://qiita.com/raki/items/30124512f45d79ae02d8)では setup-terraform v1 を使用していましたが、2022年4月に v2 になり、GitHub Actions も composite action のバージョンアップがあったので、最新化してみました
- [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) の個人用（兼業務用）ワークアラウンドです

# 注意

- （今のところ現職では）actions から apply はしてないので書いてない
- 一応動いてるけど、無保証です
- コードの手直し、質問等歓迎です
- CI/CD で apply や fmt の適用までやりたいケースは先日の [Retty さんの記事](https://engineer.retty.me/entry/2022/11/18/130000) がよさげです

# composite action yaml

``` 　.github/actions/terraform/action.yml
name: "setup-terraform"
description: 'terraform ci. fmt,init,validate,plan. not in apply'
# see: https://github.com/hashicorp/setup-terraform

inputs:
  terraform_version:
    description: 'use terraform version'
    required: false
    default: latest
  secrets_github_token:
    description: 'secrets.GITHUB_TOKEN'
    required: true
    default: ""
  working-directory:
    description: 'run directory'
    required: true
    default: ""
  # cli_config_credentials_token: 必要なら。

runs:
  using: "composite"
  steps:

    - uses: hashicorp/setup-terraform@v2
      with:
        terraform_version: ${{ inputs.terraform_version }}

    - id: fmt
      run: terraform fmt -check -diff
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      continue-on-error: true
      # see: https://developer.hashicorp.com/terraform/cli/commands/fmt

    - id: init
      run: terraform init -no-color
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      # see: https://developer.hashicorp.com/terraform/cli/commands/init

    - id: validate
      run: terraform validate -no-color
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      # see: https://developer.hashicorp.com/terraform/cli/commands/validate

    - id: plan
      run: terraform plan -no-color -detailed-exitcode -input=false
      shell: bash
      working-directory: ${{ inputs.working-directory }}
      # see: https://developer.hashicorp.com/terraform/cli/commands/plan

    - uses: actions/github-script@v6
      if: always()
      # エラーについても PR に書き出したいので常に実行する
      with:
        github-token: ${{ inputs.secrets_github_token }}
        script: |
          // 見やすいようにアイコンを表示する
          const icon_result_fmt      = "${{ steps.fmt.outcome      }}" == "success" ? ":blue_heart:" : ":broken_heart:";
          const icon_result_init     = "${{ steps.init.outcome     }}" == "success" ? ":blue_heart:" : ":broken_heart:";
          const icon_result_validate = "${{ steps.validate.outcome }}" == "success" ? ":blue_heart:" : ":broken_heart:";

          // plan 差分を判断するため exitcode を使用する
          const icon_result_plan     = "${{ steps.plan.outputs.exitcode }}" == 0 ? ":blue_heart:"  : ":broken_heart:";
          const plan_exitcode_str    = "${{ steps.plan.outputs.exitcode }}" == 0 ? "& No changes"  : "but any changes !!";

          // PR にコメントする内容
          const output = `${{ matrix.dir }} ${ icon_result_fmt } ${ icon_result_init } ${ icon_result_validate } ${ icon_result_plan }

          <details><summary>terraform fmt : ${{ steps.fmt.outcome }}</summary>

          \`\`\`
          ${{ steps.fmt.outputs.stdout }}
          ${{ steps.fmt.outputs.stderr }}
          \`\`\`

          </details>

          <details><summary>terraform init : ${{ steps.init.outcome }}</summary>

          \`\`\`
          ${{ steps.init.outputs.stdout }}
          ${{ steps.init.outputs.stderr }}
          \`\`\`

          </details>

          <details><summary>terraform validate : ${{ steps.validate.outcome }}</summary>

          \`\`\`
          ${{ steps.validate.outputs.stdout }}
          ${{ steps.validate.outputs.stderr }}
          \`\`\`

          </details>

          <details><summary>terraform plan : ${{ steps.plan.outcome }} ${ plan_exitcode_str }</summary>

          \`\`\`
          ${{ steps.plan.outputs.stdout }}
          ${{ steps.plan.outputs.stderr }}
          \`\`\`

          </details>

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

          // PR 以外で呼ばれた場合は書き込みしない
          if (!context.issue.number) { return }

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

    - shell: bash
      # success でない場合にワークフローをエラーにさせる
      run: |
        if [[ "${{steps.fmt.outcome}}" != "success" ]];      then exit 1 ; fi
        if [[ "${{steps.init.outcome}}" != "success" ]];     then exit 1 ; fi
        if [[ "${{steps.validate.outcome}}" != "success" ]]; then exit 1 ; fi
        if [[ "${{steps.plan.outputs.exitcode}}" != "0" ]];  then exit 1 ; fi

# note:
# - step の continue-on-error: true でステップを継続する。付けてないところでエラーになると
#   後続のステップはスキップされる。なので fmt だけに適用している。
#   fmt エラーでも他のチェックはできるが init や validate がエラーだと plan は実行できない。
# - if: always() でエラーやスキップがあっても常にコメント作成する処理をする
# - createComment の body 部は（ドキュメントに記載はないが）サイズ制限があるので
#   大きな出力結果の時にエラーになるため substring() で抑止
# - 最後の bash でワークフローのエラーを決めている。fmt がエラーでもチェックOKにするなら
#   （すべきではないと思うが） exit 1 をコメントにすればよい
# - plan 差分ありでもワークフローをエラーにさせないならもう一工夫要る


```

# workflow yaml

``` 　.github/workflows/pr.yml
name: workflow_test

on:
  workflow_dispatch:
  schedule:
    - cron: '0 0 * * SUN'
  pull_request:
    paths:
    - ".github/workflows/pr.yml"
    - "workflow_test/dir1/*.tf"

jobs:
  on-pull-request:
    name: terraform
    runs-on: ubuntu-latest
    timeout-minutes: 5

    strategy:
      # matrix のジョブのどれかがエラーになっても他のジョブをキャンセルしない（false）
      fail-fast: false
      matrix:
        dir:
        - "workflow_test/dir1"

    steps:
    - uses: actions/checkout@v3
      # 必要なら各種クラウド等への接続はここで書く

    - uses: ./.github/actions/terraform
      with:
        secrets_github_token: ${{ secrets.GITHUB_TOKEN }}
        working-directory: ${{ matrix.dir }}

```

# まとめ

- コメントと書いてあること自体でだいたいわかって欲しい。丸コピーしてディレクトリだけ書き換えて使える。
- 今年は PR へのコメント部分も記載した
    - PR の push 毎に回してコメントとして記録したいので、正常時も異常時も処理している
    - fmt や validate のエラー時に止めるかは任意
    - 各ステップの結果を出力するかどうかは任意
    - コメントの先頭にディレクトリと結果の emoji だけを出力しているのは、notification でメール、slack の github インテグレーション等で見やすくするため（やってみたらわかる）
- GitHub Actions の[バージョンアップ](https://github.com/actions/runner/releases/tag/v2.290.1)で composite action でも continue-on-error が使えるようになったの万歳
    - continue-on-error:true にした step はエラーになっても check は正常ステータスにできる
    - 以前は composite action では使えなくて不便だった
- setup-terraform の README.md にあるように、PR のコメントをアップデートするのって、履歴としてどうなの？って思ってるんだけど、実際どうなの？
- 使用する terraform のバージョンを固定するのはプラクティス的にいいのかもしれないんだけど、setup-terraform を使う場合は dependabot が使えない（この書き方ではバージョン処理できないから）のが気になる（とは言え書いてあるとおり latest 使ってて今のところ問題ない）