terraform planなどのTerraformのコマンドがPull Requestごとに自動で実行されるとTerraformの運用が楽になります。
本記事では、GitHub ActionsでTerraformの運用を楽にする自動化をモノレポ構成で実現するための方法を紹介します。
なお、本内容はAWSを使用した例になります。ただ大枠の内容はAWS以外でも参考になるはずです。
内容
今回実装したGitHub Actionsのワークフローの内容です。
Terraformを実行する
GitHub ActionsでTerraformのコマンドを実行します。
以下を参考にして、Terraformのformat,validate,planの結果をPRにコメントするようにしています。
name: 'Terraform'
on:
pull_request:
jobs:
...
terraform:
...
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::XXXXXXXXXXXX:role/terraform-role
aws-region: ap-northeast-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
- name: Terraform Init
id: init
run: terraform init
working-directory: ${{ matrix.workdir }}
- name: Terraform Format
id: fmt
run: terraform fmt -check
continue-on-error: true
working-directory: ${{ matrix.workdir }}
- name: Terraform Validate
id: validate
run: terraform validate -no-color
continue-on-error: true
working-directory: ${{ matrix.workdir }}
- name: Terraform Plan
id: plan
if: github.event_name == 'pull_request'
run: terraform plan -no-color -input=false
continue-on-error: true
working-directory: ${{ matrix.workdir }}
- name: Update Pull Request
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN}
\`\`\`
</details>
*Pushed by: @${{ 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
})
- name: Terraform Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1
aws-actions/configure-aws-credentials@v1
でAWSの認証情報を取得しています。
role-to-assume
を使うのがセキュアでオススメです。
各Terraformのコマンドを実行しているStepにあるworking-directory
がモノレポ対応に関連します。次で説明します。
モノレポに対応する
モノレポ内にある複数のTerraform作業ディレクトリごとに上記で用意したTerraformのジョブを実行するようにします。
そのためにdorny/paths-filterというActionを使います。
このActionを使用すると、YAMLファイルの定義にしたがって対象ディレクトリ内のファイルに変更があったかどうかを判定します。GitHub ActionsのPath filter機能をジョブ内で実行することができるActionです。
...
jobs:
determine-workdir:
runs-on: ubuntu-20.04
permissions:
pull-requests: read
contents: read
timeout-minutes: 10
outputs:
workdirs: ${{ steps.filter.outputs.workdirs }}
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: dorny/paths-filter@v2
id: changes
with:
filters: .github/path-filter.yml
- name: filter
id: filter
run: |
WORKDIRS=$(echo '${{ toJSON(steps.changes.outputs) }}' | jq '. | to_entries[] | select(.value == "true") | .key')
echo "workdirs=$(echo $WORKDIRS | jq -sc '.')" >> $GITHUB_OUTPUT
terraform:
needs: determine-workdir
runs-on: ubuntu-20.04
permissions:
id-token: write
contents: read
pull-requests: write
timeout-minutes: 10
if: needs.determine-workdir.outputs.workdirs != '[]'
strategy:
matrix:
workdir: ${{ fromJSON(needs.determine-workdir.outputs.workdirs) }}
...
ディレクトリ内で変更があったかの条件を定義するYAMLファイルは以下の通りです。
keyの部分で作業ディレクトリ名を記載し、valueの部分で作業ディレクトリに対して変更対象とするPath filterの値を記載します。以下では、account-aディレクトリ配下とmodulesディレクトリ配下で変更があった場合にaccount-aディレクトリで変更があったとなります。
これを複数のディレクトリごとに定義することでモノレポ構成に対応します。
account-a:
- 'account-a/**'
- 'modules/**'
account-b:
- 'account-b/**'
- 'modules/**'
account-c:
- 'account-c/**'
- 'modules/**'
strategy.matrix
で渡した複数の値ごとに複数回のジョブを実行することができます。
dorny/paths-filter
で得られた複数の作業ディレクトリ名をstrategy.matrix
の値に指定することでモノレポ内で変更があった作業ディレクトリごとにジョブを実行できます。また、前述の各Terraformコマンドのworking-directory
にmatrixを通して得られた作業ディレクトリ名を指定します。
matrixについては以下のドキュメントに詳しく説明されています。
https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix
ここまででモノレポ構成のTerraformをGitHub Actionsで自動化することができます。
まとめ
GitHub Actionsのコードの全体は以下の通りです。
公式では自動化などのTerraform向けのプロダクトとしてTerraform Cloudがあります。
Terraform Cloudを使うのが諸々の事情で難しい場合、Terraformの自動化を行う別の方法としてGitHub Actionsを使うのが個人的には良いと思います。
name: 'Terraform'
on:
pull_request:
jobs:
determine-workdir:
runs-on: ubuntu-20.04
permissions:
pull-requests: read
contents: read
timeout-minutes: 10
outputs:
workdirs: ${{ steps.filter.outputs.workdirs }}
steps:
- name: Checkout
uses: actions/checkout@v3
- uses: dorny/paths-filter@v2
id: changes
with:
filters: .github/path-filter.yml
- name: filter
id: filter
run: |
WORKDIRS=$(echo '${{ toJSON(steps.changes.outputs) }}' | jq '. | to_entries[] | select(.value == "true") | .key')
echo "workdirs=$(echo $WORKDIRS | jq -sc '.')" >> $GITHUB_OUTPUT
terraform:
needs: determine-workdir
runs-on: ubuntu-20.04
permissions:
id-token: write
contents: read
pull-requests: write
timeout-minutes: 10
if: needs.determine-workdir.outputs.workdirs != '[]'
strategy:
matrix:
workdir: ${{ fromJSON(needs.determine-workdir.outputs.workdirs) }}
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::XXXXXXXXXXXX:role/terraform-role
aws-region: ap-northeast-1
- name: Setup Terraform
uses: hashicorp/setup-terraform@v1
- name: Terraform Init
id: init
run: terraform init
working-directory: ${{ matrix.workdir }}
- name: Terraform Format
id: fmt
run: terraform fmt -check
continue-on-error: true
working-directory: ${{ matrix.workdir }}
- name: Terraform Validate
id: validate
run: terraform validate -no-color
continue-on-error: true
working-directory: ${{ matrix.workdir }}
- name: Terraform Plan
id: plan
if: github.event_name == 'pull_request'
run: terraform plan -no-color -input=false
continue-on-error: true
working-directory: ${{ matrix.workdir }}
- name: Update Pull Request
uses: actions/github-script@v6
if: github.event_name == 'pull_request'
env:
PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const output = `#### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
#### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
#### Terraform Plan 📖\`${{ steps.plan.outcome }}\`
#### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
<details><summary>Show Plan</summary>
\`\`\`\n
${process.env.PLAN}
\`\`\`
</details>
*Pushed by: @${{ 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
})
- name: Terraform Plan Status
if: steps.plan.outcome == 'failure'
run: exit 1
参考
以下を参考にさせて頂きました。