はじめに
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のワークフローコード
実際のコードはこちらのワークフローの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: | # git fetch echo "::group::git fetch" TARGET_BRANCH="${{ github.base_ref }}" echo "TAGET_BRANCE = ${TARGET_BRANCH}" git fetch --depth 1 origin ${TARGET_BRANCH} # If modules is changed, execute terraform to all . 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}" # Detect target directories 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 # Output target directories echo "::endgroup::" # Output results echo "::group::results" echo "TARGET_DIR = ${TARGET_DIR}" echo "::endgroup::" # End processing 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
セットアップ手順
具体的手順は、GitHubのリポジトリのREADMEに記載しています。