これまでTerraformに関するCIとしてはGitLab CI/CDを使っていました。 最近はプライベートでGitHubを利用しTerraform周りのコードを管理しており、GitHub上でのCIを色々試行錯誤しています。
ようやく以下のように、terraform plan
の結果等をPull Requestへ自動コメントできるようになりました。
完全なworkflowはこちらです。
- ルートモジュール毎の実行
- 差分があったルートモジュールに対してだけCIジョブを実行する
- AWS環境用の権限を得る
- Terraformのセットアップ
- fmtやvalidate、planの結果をコメント投稿する
- エラーが発生した場合にはワークフローをFailさせる
ルートモジュール毎の実行
Terraformでのベストプラクティスの1つとしてstateを分けるということはよく言われています。
結果として、ルートモジュールの数が多くなり、個々のモジュールにterraform fmt
やterraform validate
を実行すると時間がかかりすぎます。
この問題に対してはjobs.<job_id>.strategy.matrix
を利用することで、ルートモジュールそれぞれで並列実行するようにしました。
strategy: matrix: dir: - aws/backend - aws/budgets - aws/cloudtrail - aws/memories - aws/github_actions steps:
このテクニックは以下の記事を参考にしています。
差分があったルートモジュールに対してだけCIジョブを実行する
ルートモジュールがたくさんあっても、CIジョブを実行すべきなのは「差分があったルートモジュール」だけなはずです。
差分の有無についてはgit diff
を使えば良いのですが、せっかくGitHub Actionsを使うのでコマンドを羅列するのは避けたい。
get-diff-action
というActionsがあるので、これを使って差分を検出することにしました。
前述の通り、jobs.<job_id>.strategy.matrix
で並列実行していることを前提に、.tf
あるいは.tfvars
の差分を検出するようにしています。
# matrix.dir で指定されたサブディレクトリの中にある terraform ファイルの差分を確認する - name: Diff Terraform Scripts id: diff uses: technote-space/get-diff-action@v5 with: PATTERNS: | ${{ matrix.dir }}/**/*.tf ${{ matrix.dir }}/**/*.tfvars
差分があった場合はsteps.diff.outputs.diff
で検知できます。差分があった場合にのみ後続ジョブを実行するため、後続ジョブではif
を使用してジョブの実行可否を制御します。
- name: Configure aws credentials if: steps.diff.outputs.diff
AWS環境用の権限を得る
configure-aws-credentialsを使うことで、AWSのアクセスキーをハードコードすることなく権限を入手できます。
- name: Configure aws credentials if: steps.diff.outputs.diff uses: aws-actions/configure-aws-credentials@v1 with: role-to-assume: ${{ secrets.TERRAFORM_ROLE_TO_ASSUME }} role-duration-seconds: 900 aws-region: ${{ env.AWS_REGION }}
いくつか事前準備は必要ですが、それはこちらで記載しました。
Terraformのセットアップ
Terraformのセットアップについてはhashicorp公式のGitHub Actionsが存在するため、 こちらを利用すれば良いでしょう。
- name: Setup terraform if: steps.diff.outputs.diff uses: hashicorp/setup-terraform@v1 with: terraform_version: 1.1.2 terraform_wrapper: true
ポイントはterraform_wrapper
を有効化することでしょうか。これを有効化することで、terraform
を実行するときにSTDOUT
やSTDERR
等を後続ジョブで利用できます。
fmt
やvalidate
、plan
の結果をコメント投稿する
terraform fmt
等の実行については単にコマンドを実行するのみです。
一方で、その結果をコメント投稿するためにterraform-pr-commenterを使いました。terraaform-pr-commenterは、fmt
やvalidate
、plan
の実行結果を良い感じに整形してコメント投稿してるActionです。
- name: Check format id: fmt if: steps.diff.outputs.diff run: terraform fmt -check -recursive working-directory: ${{ matrix.dir }} continue-on-error: true - name: Comment format results uses: robburger/terraform-pr-commenter@v1 if: steps.fmt.outputs.diff env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: commenter_type: fmt commenter_input: ${{ format('{0}{1}', steps.fmt.outputs.stdout, steps.fmt.outputs.stderr) }} commenter_exitcode: ${{ steps.fmt.outputs.exitcode }}
注意すべきは、このコメント投稿にはコメント投稿可能な権限を持ったGITHUB_TOKEN
が必要であることです。
GITHUB_TOKEN
の持つ権限はpermissions
で設定できるので、ワークフローの設定ファイルで設定しましょう。
Pull Requestへのコメントはpull-requests
へのwrite
があれば良いので、以下のようになるでしょう。
jobs: plan: name: Plan runs-on: ubuntu-latest permissions: id-token: write pull-requests: write
AWSの権限をOIDC経由で入手しようとするとid-token: write
の明示的な設定が必要となり、結果としてpull-requests
の権限が落ちるという落とし穴があります。ぼくもここにハマりました。
以下の記事でpull-requests: write
の設定漏れに気づいた次第です。
あとは同様に、validate
やplan
等のジョブを設定すれば良いでしょう。
エラーが発生した場合にはワークフローをFailさせる
以下のように設定しました。正直、 if: {{ failure() }}
でよかったような気もします。
- name: Exit with appropriate status if: steps.fmt.outcome == 'failure' || steps.init.outcome == 'failure' || steps.validate.outcome == 'failure' || steps.plan.outcome == 'failure' run: exit 1