はじめに
GitHub ActionsによるTerraformのCIサンプルです。
実際のコードはGitHubのリポジトリを参照ください。
このサンプルの特徴は以下の通りです。
- OpenID Connect(OIDC)でのAWS認証によるActions実行により、アクセスキー&シークレットキー管理が不要
- Terraformのデファクトとなっているディレクトリ構成で作成
- その上でActionsでは、更新があったディレクトリを特定し、そのディレクトリのみTerraformを実行
- CIに、IaCコードの静的解析を行うcheckovを組み込み、Pull Requestで自動実行(最近流行りの、DevSecOpsぽいものを実現)
terrafrom/envs/xxx
のように環境面でTerraform実行先が固定されている場合(実行面数が増加しない場合)は、こんな面倒なやり方をせず
ワークフローを分けてon:
で、paths:
条件で実行する構成にした方がシンプルで望ましいです。
一方で、アカウントやプロジェクトなどTerraform実行ディレクトリが単純増加するユースケースで、GitHub ActionsでTerraformを実行したいケースでは、このサンプルのワークフローが効果を発揮すると思います。
前提とするTerraformのディレクトリ構成
terraformのモジュールの標準的なディレクトリ構成を前提としています。
/terraform/accounts/
配下にそれぞれのアカウント単位でterrafomを管理するイメージです。
アカウントを追加する場合は、_template
フォルダを任意の名称でコピーして適切に設定を変更してterraformでapplyします。
.
└── terraform
├── accounts
│ ├── _template
│ ├── user-a
│ │ ├── backend.tf
│ │ ├── main.tf
│ │ ├── provider.tf
│ │ └── terraform.tf
│ ├── user-b
│ └── user-c
└── modules
└── xxxxx
ActionsからのAWS認証
ActionsからAWS環境にアクセスする際の認証には、OIDCを利用しています。OIDC利用の詳細は下記ドキュメントを参照ください。
Actionsのワークフローコード
実際のコードはこちらのワークフローのYAMLファイルを確認ください。
ジョブは、(1)更新ディレクトリ特定ジョブと、(2)Terraform実行ジョブ、の2つで構成されています。
シェル芸の塊です。ここではざっくりとは以下の処理をしています。
git log
による更新一覧取得
sed
、awk
、uniq
などを駆使して、更新ディレクトリ情報のみ抽出
- 抽出した情報を
jp
コマンドでJSONに変換
- 変換したJSONは、次のTerraform実行ジョブでのmatrixに利用
具体的な処理の中身については、GitHubのREADMEやこちらの記事を参照ください。
detect_dirs:
name: "Detect modified directories"
runs-on: ubuntu-latest
defaults:
run:
shell: bash
outputs:
TARGET_DIR: ${{ steps.detectddir.outputs.TARGET_DIR }}
steps:
- name: Checkout repository
uses: actions/checkout@v2
- name: Detect modified project directories
id: detectddir
run: |
echo "::group::git fetch"
TARGET_BRANCH="${{ github.base_ref }}"
echo "TAGET_BRANCE = ${TARGET_BRANCH}"
git fetch --depth 1 origin ${TARGET_BRANCH}
echo "::group::check modules directory"
LINES=$( git diff origin/${TARGET_BRANCH} HEAD --name-only -- ${{env.TERRAFORM_MODULES_DIR}} | wc -l )
if [ ${LINES} -gt 0 ]; then
flag_all_envs='true'
else
flag_all_envs='false'
fi
echo "::group::flag_all_envs = ${flag_all_envs}"
echo "::group::detect target directories"
if [ "${flag_all_envs}" == 'true' ]; then
TARGET_DIR=$( \
find ${{env.TERRAFORM_TARGET_DIR}} -type d -not -name ${{env.TERRAFORM_ENVS_EXCLUDED_DIR}} -maxdepth 1 -mindepth 1 | \
jq -scR 'split("\n") | .[:-1]' \
);
else
TARGET_DIR=$( \
git diff origin/${TARGET_BRANCH} HEAD --name-only -- ${{ env.TERRAFORM_TARGET_DIR }} | \
sed -E 's:(^${{ env.TERRAFORM_TARGET_DIR }}/[^/]*/)(.*$):\1:' | \
sort | uniq | \
awk '{ if( system("[ -d "$1" ]") == 0 && $1 !~ /${{env.TERRAFORM_ENVS_EXCLUDED_DIR}}/ ){print $1} }' | \
jq -scR 'split("\n") | .[:-1]'
);
fi
echo "::endgroup::"
echo "::group::results"
echo "TARGET_DIR = ${TARGET_DIR}"
echo "::endgroup::"
echo "::set-output name=TARGET_DIR::${TARGET_DIR}"
exit 0
Terraform実行ジョブ
先の更新ディレクトリ特定ジョブから、jobのoutputsを利用し特定したディレクトリ一覧を取得し、Matrixを利用し並列実行しています。
checkovの詳細については、こちらの記事を参照ください。
run_terraform:
name: "Run terraform"
needs: detect_dirs
if: ${{ needs.detect_dirs.outputs.TARGET_DIR != '[]' }}
strategy:
fail-fast: false
max-parallel: 2
matrix:
target: ${{fromJson(needs.detect_dirs.outputs.TARGET_DIR)}}
runs-on: ubuntu-latest
defaults:
run:
shell: bash
env:
TERRAFORM_WORK_DIR: ${{ matrix.target }}
permissions:
id-token: write
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: ${{secrets.ASSUME_ROLE_ARN}}
role-session-name: pullrequest
aws-region: ap-northeast-1
- name: AWS Sts Get Caller Identity
run: aws sts get-caller-identity
- name: Setup terraform
uses: hashicorp/setup-terraform@v1
with:
terraform_version: ${{env.TERRAFORM_VERSION}}
- name: Set up Python 3.9
uses: actions/setup-python@v4
with:
python-version: 3.9
- name: Test with Checkov
id: checkov
uses: bridgecrewio/checkov-action@master
with:
directory: ${{env.TERRAFORM_ROOT_DIR}}
framework: terraform
quiet: true
skip_check: ${{env.CHECKOV_SKIP_CHECK}}
- name: Terraform Format
run: terraform fmt -recursive -check=true
- name: Terraform Init
run: terraform -chdir=${TERRAFORM_WORK_DIR} init
- name: Terraform Validate
run: terraform -chdir=${TERRAFORM_WORK_DIR} validate -no-color
- name: Terraform Plan
run: terraform -chdir=${TERRAFORM_WORK_DIR} plan