これは何ですか
- GCPのCloud IAM設定をTerraformで書くときに
for_each
とdynamic
ブロックでいい感じに書けないかと試行錯誤したので、記録を残しておきます。 - Cloud IAM Conditions がベータ版だったので一部条件はまだ使えなさそうですが、そのうち使えるようになると思って残しておきます。
サンプルコード
ざっくり解説
サンプルコードの解説を簡単に書く。
tfファイルの概要
サービスアカウントに付与する役割と条件は、以下のように入れ子構造のmap型変数で表現する。
# 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メンバーと役割の紐付けを行う。
# サービスアカウント設定は役割との紐付けまで管理する
# 鍵は別管理とする
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_each
とdynamic
ブロックを活用することで、繰り返し構文によるリソース生成が簡単に書けるようになった。 - 0.12 でmap/list型の入れ子構造がサポートされたことで、
for_each
やdynamic
ブロックが使いやすくなった。- 0.11 以前の interporation syntax ではかなり厳しかった。。。
- 一方でぱっと見の可読性は落ちるので注意して使う必要がある。
- もしかしたら今回の書き方も複雑に見えるかもしれない。。。