🐧

tfaction の導入ガイド

2022/02/06に公開

tfaction という、 GitHub Actions で良い感じの Terraform Workflow を構築するための Action を開発しています。

https://zenn.dev/shunsuke_suzuki/articles/tfaction-introduction

https://blog.studysapuri.jp/entry/2022/02/04/080000

今回は tfaction の導入ガイドのようなものを書こうかと思います。
AWS Account が必要です。

執筆時点で tfaction の最新バージョンは v0.4.5 です。

2022-02-07 追記

Getting Started を作成しました。

https://github.com/suzuki-shunsuke/tfaction-getting-started

README に従っていけば tfaction の workflow を動かして terraform を実行するのを体験できると思います。

IAM OpenID Connect provider の作成

https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services

IAM OpenID Connect provider を作成します。

data "tls_certificate" "github" {
  url = "https://token.actions.githubusercontent.com/.well-known/openid-configuration"
}

resource "aws_iam_openid_connect_provider" "github" {
  url             = "https://token.actions.githubusercontent.com"
  thumbprint_list = [data.tls_certificate.github.certificates[0].sha1_fingerprint]
  client_id_list  = ["sts.amazonaws.com"]
}

S3 Bucket の作成

tfaction は 3 種類の S3 Bucket を使います。

  • Terraform Backend
  • Plan File
  • tfmigrate history

これらは共通化しても tfaction の動作的には特に問題ありません。

IAM Role の作成

Assume Role する IAM Role を作成します。

module "aws" {
  source = "github.com/suzuki-shunsuke/terraform-aws-tfaction?ref=v0.1.2"

  name                               = "AWS" # Module のコードを見れば分かるが、 IAM Role や Policy の名前の一部に使われる
  repo                               = "suzuki-shunsuke/tfaction-example"
  main_branch                        = "main"
  s3_bucket_tfmigrate_history_name   = "<S3 Bucket Name for tfmigrate hisotry>"
  s3_bucket_terraform_plan_file_name = "<S3 Bucket Name for terraform plan file>"
  s3_bucket_terraform_state_name     = "<S3 Bucket Name for terraform state>"
}

tfaction は GitHub Actions の 4 種類の job に応じて IAM Role を使い分けます。

  • terraform plan
  • terraform apply
  • tfmigrate plan
  • tfmigrate apply

上記の Module はこれらの 4 つの job 用に IAM Role を 4 つ作ります。それらの IAM Role には job に応じて 3 つの S3 Bucket に対して必要な権限のみが付与されています。
AWS Provider 以外では IAM Role の権限的にはこれで十分なはずです。
AWS Provider の場合には適宜権限を付与します。

以下では雑に AdministratorAccess と ReadOnlyAccess を付与していますが、より権限を絞るのが望ましいでしょう。

resource "aws_iam_role_policy_attachment" "terraform_apply_admin" {
  role       = module.aws.aws_iam_role_terraform_apply_name
  policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}

resource "aws_iam_role_policy_attachment" "terraform_plan_readonly" {
  role       = module.aws.aws_iam_role_terraform_plan_name
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

resource "aws_iam_role_policy_attachment" "tfmigrate_plan_readonly" {
  role       = module.aws.aws_iam_role_tfmigrate_plan_name
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

resource "aws_iam_role_policy_attachment" "tfmigrate_apply_readonly" {
  role       = module.aws.aws_iam_role_tfmigrate_apply_name
  policy_arn = "arn:aws:iam::aws:policy/ReadOnlyAccess"
}

tfaction は setup Action で https://github.com/aws-actions/configure-aws-credentials を実行し、上記の IAM Role を Assume しています。

GCP の場合

tfaction では GCP にも Access Key なしでアクセスできます。
GCP を使わない人は無視して構いません。
GCP 関連の設定がしてある場合、 setup Action で https://github.com/google-github-actions/auth を実行します。

https://github.com/google-github-actions/auth#setup などに従って Workload Identity Pool や Provider, Service Account を作成してください。

Personal Access Token か GitHub App の作成

tfaction では secrets.GITHUB_TOKEN 以外に GitHub Access Token が必要です。
tfaction は自動でコードを修正して commit, push したり、自動で pull request を作成したりしますが、その際 secrets.GITHUB_TOKEN では workflow が起動しないからです。

https://suzuki-shunsuke.github.io/tfaction/docs/config/github-token

設定ファイルの追加

幾つかの設定ファイルが必要です。

tfaction-root.yaml

一旦空で作成しておきます。

target_groups: []

GitHub Secrets も追加してください。

working directory に tfaction.yaml を作成

tfaction は一つのリポジトリに複数の working directory がある Monorepo を想定しています。
working directory とは terraform init や plan, apply といった terraform を実行するディレクトリのことです。
それらの directory に tfaction.yaml というファイルを置いてください。
中身は空でも大丈夫です。

{}

tfaction では setup job で list-targets という action を実行して対象の working directory のリストを抽出し、後続の job (tfmigrate-plan, terraform-plan) に list を渡して
build matrix で working directory ごとに並列で terraform などを実行します。
対象の working directory は、 Pull Request でコードが変更された working directory 及び Pull Request Label によって指定された working directory です。
tfaction.yaml があるディレクトリから対象のディレクトリを絞り込むので、 tfaction.yaml がないとそもそも対象になりません。

# 一途抜粋。省略しているのでこのままでは動かない
jobs:
  setup:
    outputs:
      tfmigrate_targets: ${{ steps.list-targets.outputs.tfmigrate_targets }}
      terraform_targets: ${{ steps.list-targets.outputs.terraform_targets }}
    steps:
      # ...
      - uses: suzuki-shunsuke/tfaction/list-targets@v0.4.4
        id: list-targets

  tfmigrate-plan:
    needs: setup
    strategy:
      matrix:
        target: ${{fromJSON(needs.setup.outputs.tfmigrate_targets)}}

  terraform-plan:
    needs: setup
    strategy:
      matrix:
        target: ${{fromJSON(needs.setup.outputs.terraform_targets)}}

tfaction-root.yaml の target_groups を設定する

以下のような設定を書く必要があります。

target_groups:
- working_directory: aws/services/
  target: aws/
  aws_region: ap-northeast-1
  s3_bucket_name_plan_file: '<S3 Bucket Name for Terraform Plan File>'
  s3_bucket_name_tfmigrate_history: '<S3 Bucket Name for tfmigrate history files>'
  terraform_plan_config:
    aws_assume_role_arn: arn:aws:iam::000000000000:role/GitHubActions_Terraform_AWS_terraform_plan
  tfmigrate_plan_config:
    aws_assume_role_arn: arn:aws:iam::000000000000:role/GitHubActions_Terraform_AWS_tfmigrate_plan
  terraform_apply_config:
    aws_assume_role_arn: arn:aws:iam::000000000000:role/GitHubActions_Terraform_AWS_terraform_apply
  tfmigrate_apply_config:
    aws_assume_role_arn: arn:aws:iam::000000000000:role/GitHubActions_Terraform_AWS_tfmigrate_apply

tfaction は対象の working directory の設定を target_groups から探索します。
配列の要素を上から一つずつチェックし、マッチするものがあればそれを採用して探索を終了します。
マッチするか否かは、 working_directory ないし target で決まります。

tfaction には TFACTION_TARGET という環境変数があるように、 target という概念があります。
これは working directory のエイリアスのようなもので、 working directory と 1 対 1 に対応します。
working directory と同じでも構いません。
target は Pull Request Label や Pull Request Comment などにも含まれます。
working directory の path から余計な文字列を除外したり、置換したりするのに使えます。

target_groups の working_directory, target はそれぞれ working directory と target の prefix で、
これらの prefix にマッチするか否かで決まります。

prefix なので、末尾の / には気をつけてください。
aws/aws/foo にはマッチしますが、 aws にはマッチしません。
逆に awsaws/fooaws にマッチしますが、 aws-foo にもマッチします。

あとは見れば分かるかと思いますが、 Assume する IAM Role や S3 Bucket を指定しています。

一部の設定は target group 直下に設定も出来ますし、各 job ごとに設定をすることも出来ます。

tfaction.yaml で設定を上書きする

tfaction-root.yaml では target group という単位で設定をしますが、
working directory に配置する tfaction.yaml によってそれらの設定の一部は上書きができます。

https://suzuki-shunsuke.github.io/tfaction/config/tfaction.html

Secret を参照する

https://suzuki-shunsuke.github.io/tfaction/docs/config/secret

working directory に aqua.yaml を置く

tflint や tfsec を working directory ごとにバージョン管理します。
working directory ごとにバージョン管理する代わりに、リポジトリ直下の aqua.yaml で管理してしまう手もあります。

Pull Request を作成し、動作を確認する

上記のように設定したら、適当に working directory を作成して Pull Request を投げましょう。
tfaction の workflow がいい感じに動くはずです。

.github/workflows/
  test.yaml
  apply.yaml
  hide_comment.yaml
aqua.yaml
tfaction-root.yaml
foo/ # working directory
  tfaction.yaml
  main.tf
  ...

plan がいい感じに動いたら、マージして apply までしてみましょう。

tflint, tfsec などを使わない場合

tfaction の test action では tflint, tfsec などを実行し、 Reviewdog で結果を通知します。
tflint や tfsec を使わない場合は、 test action を使うのをやめてください。
test action を消しても問題なく workflow は動作するはずです。
そして test action の代わりに独自の step, action を追加すればよいでしょう。

scaffold 用の template を追加する

templates 配下に working directory を scaffold するための template を追加します。
template は tatget group ごとに変更できます。

https://github.com/suzuki-shunsuke/tfaction-example/tree/ec2acf53ac500155e0c33628b8f003597b887a60/templates/aws

templates/
  aws/
    foo/
      .tfsec/config.yml
      .tflint.hcl
      terraform.tf 

aqua.yaml や .tfmigrate.hcl などは template になくても自動で生成されます。
template 中の %%TARGET%% という文字列は target に置換されます。このへんの仕組みはあまり洗練されていないので、より改善したいとは思っています(そのうち変わるかもしれません)。

template を追加したら tfaction-root.yaml でその directory を指定します。

target_groups:
- working_directory: aws/services/
  target: aws/
  template_dir: templates/aws/foo

そして workflow を追加します。

https://github.com/suzuki-shunsuke/tfaction-example/blob/main/.github/workflows/scaffold-working-directory.yaml

追加したら workflow を実行してみましょう。
input として target を入力します。

上の例だと target を aws/zoo とすると template templates/aws/foo を使って aws/services/zoo に working directory を追加する Pull Request が作成されます。

scaffold をカスタマイズする

scaffold 時にコマンドを実行してコードを生成したい場合、 scaffold-working-dir action を実行したあとに
独自の step を追加すればよいでしょう。
ただし、 create-scaffold-pr は working directory 配下のコードしか commit, push しないので、
それ以外のコード (e.g. tfaction-root.yaml) を変更しても反映されません。

https://github.com/suzuki-shunsuke/tfaction-example/blob/main/.github/workflows/scaffold-working-directory.yaml

さいごに

以上、 tfaction を導入するための手順をざっと説明しました。
参考になれば幸いです。

Discussion