LoginSignup
12
5

More than 3 years have passed since last update.

いまさらTerraform 0.12のdynamicブロックを使ってみた

Last updated at Posted at 2020-01-28

これは何ですか

  • GCPのCloud IAM設定をTerraformで書くときに for_eachdynamic ブロックでいい感じに書けないかと試行錯誤したので、記録を残しておきます。
  • Cloud IAM Conditions がベータ版だったので一部条件はまだ使えなさそうですが、そのうち使えるようになると思って残しておきます。

サンプルコード

ざっくり解説

サンプルコードの解説を簡単に書く。

tfファイルの概要

サービスアカウントに付与する役割と条件は、以下のように入れ子構造のmap型変数で表現する。

variables.tf
# Cloud IAM Settings
variable "role_sa_ssh" {
  description = "Roles for sa-ssh account"

  default = {
    instanceadmin = {
      role = "roles/compute.instanceAdmin.v1"
    },
    compute_ssh = {
      role = "roles/compute.osAdminLogin"
    },
    iap_tunnelresourceaccessor = {
      role = "roles/iap.tunnelResourceAccessor"
      conditions = [
        {
          title       = "restrict_tcp_tunneling"
          description = "Allow access for destination port 22 in the request"
          expression  = "destination.port == 22"
        }
      ]
    },
    sauser = {
      role = "roles/iam.serviceAccountUser"
    }
  }
}

1階層目はlistでも良い。今回は追加削除する際のplanの視認性を考えてmapにした。
- role には役割のフルネームを指定する。
- conditions には、google_project_iam_member リソースの condition ブロックの要素を書く。複数設定することを想定してlistにする。description のようなOptionalな値を設定しない場合は null とする。

IAMメンバー(ここではサービスアカウント sa_ssh) と役割の紐付けには google_project_iam_member リソースを使う。 google_project_iam_member リソースは次のように書く。上で定義した role_sa_ssh 変数を使って、Cloud IAMメンバーと役割の紐付けを行う。

gcp_serviceaccount.tf
# サービスアカウント設定は役割との紐付けまで管理する
# 鍵は別管理とする
resource "google_service_account" "sa_ssh" {
  account_id   = "sa-ssh"
  display_name = "ssh"
}

resource "google_project_iam_member" "sa_ssh" {
  provider = google-beta
  for_each = var.role_sa_ssh

  project = var.gcp_project_id
  role    = each.value.role
  member  = "serviceAccount:${google_service_account.sa_ssh.email}"

  dynamic condition {
    for_each = contains(keys(each.value), "conditions") ? each.value["conditions"] : []
    content {
      title       = condition.value.title
      description = condition.value.description
      expression  = condition.value.expression
    }
  }
}

google_project_iam_member リソースのドキュメントは以下。
https://www.terraform.io/docs/providers/google/r/google_project_iam.html

以降、簡単に google_project_iam_member リソースの解説をする。

for_each を使って複数のリソースを生成する

google_project_iam_member リソース内では for_each を使う。

resource "google_project_iam_member" "sa_ssh" {
  provider = google-beta
  for_each = var.role_sa_ssh

Terraform 0.12.6 以降では、リソース設定の中で for_each に map/list 型の変数を指定することで、変数の各要素に対応した複数のリソースを生成できる。
https://www.terraform.io/docs/configuration/resources.html#for_each-multiple-resource-instances-defined-by-a-map-or-set-of-strings

for_eachで指定した変数の各要素は、リソース内で each.key(map型のみ), each_value として参照できる。たとえば、役割の指定では次のように変数内の要素を指定している。

  role    = each.value.role

role_sa_ssh 変数の各要素は role キーを持つ。each_value.role のようにキーを指定することで、role キーに対応する値が取り出せる。

なお、繰り返しリソースを生成する機能としてはもともと count が存在した。ただ、リソース管理の観点からいえば for_each の方がメリットが多いので、0.12.6以降のバージョンで新たにTerraformを書くときは for_each を使う方が良い。
https://dev.classmethod.jp/etc/terraform_count_delete/

dynamic ブロックで resource 内のブロックを動的に定義する

Cloud IAM Conditisons は、google_project_iam_member リソースの condition ブロックで設定する(ベータ版機能)。条件を設定しない場合もあるので、condition ブロックは0個以上の複数個が記述可能である。

map/list型の変数に応じて動的にブロックを記述する方法として、0.12 では dynamic ブロックが導入された。
https://www.terraform.io/docs/configuration/expressions.html#dynamic-blocks

google_project_iam_member リソース内では、次のように condition ブロックを記述している。

  dynamic condition {
    for_each = contains(keys(each.value), "conditions") ? each.value["conditions"] : []
    content {
      title       = condition.value.title
      description = condition.value.description
      expression  = condition.value.expression
    }
  }

ここでも for_each にmap/list型の変数を渡す。変数の要素を参照するときはブロック名を使う。ここでは condition ブロックを生成しているので、condition.value.title のように値が参照できる。

要素を生成しないときは空のリスト [] を渡す

dynamic ブロックでは、for_each に空のリスト [] を渡すことで、当該ブロックが0個の状態を表現できる。google_project_iam_member リソースでは、以下のように3項演算子を使って空のリストを渡している。

    for_each = contains(keys(each.value), "conditions") ? each.value["conditions"] : []

contains() はlist型の値に特定の要素が含まれていれば true を、そうでなければ false を返す。keys() はmap型の値のキーをlistとして返す。これを組み合わせることで、condition キーの有無を判定し、キーがある場合は対応する値を、無い場合は空のリスト [] を返している。

https://www.terraform.io/docs/configuration/functions/contains.html
https://www.terraform.io/docs/configuration/functions/keys.html

もっと素直な書き方があるような気がする……

まとめ

  • for_eachdynamicブロックを活用することで、繰り返し構文によるリソース生成が簡単に書けるようになった。
  • 0.12 でmap/list型の入れ子構造がサポートされたことで、for_eachdynamic ブロックが使いやすくなった。
    • 0.11 以前の interporation syntax ではかなり厳しかった。。。
  • 一方でぱっと見の可読性は落ちるので注意して使う必要がある。
    • もしかしたら今回の書き方も複雑に見えるかもしれない。。。
12
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
5