やりたいこと
業務で Terraform を使っている中で、2重ループ的な処理を実現する必要があった。
分かってしまえば簡単だが、わりと手こずったので備忘録兼他の方の参考になればと思い書いておく。
どういう処理をしたいか
複数のリソースに対して、複数のユーザ (AzureAD で管理) のアクセス権を割り当てる、というようなケース。
具体的には、5つのストレージアカウント内のコンテナへの Blob Data Contributor
ロールを2人のユーザに割り当てる、というケース。
本来は、グループを作成してそのグループに Blob Data Contributor
を割り当てる方が良い。
最初に上記の形で割り当てたのでその内容で話を進める。現在はグループに割り当てる形にしている。
通常の Terraform でのループ処理 (単一ループ)
以下のような感じ。for_each
を使う。st_names
内の値がストレージアカウント名として使用される。
local {
st_names = ["st1", "st2", "st3", "st4", "st5"]
}
# ストレージアカウントを作成
resource "auzrerm_storage_account" "storages" {
for_each = local.st_names
name = each.value
resource_group = "rg-sample"
location = "japaneast"
account_tier = "Standard"
account_replication_type = "GRS"
}
これらのストレージアカウントは Terraform 内では、以下のようなリソースとして管理されている。
それぞれのリソースは、コードを通して一意である必要があり重複は許されない。
auzrerm_storage_account.storages["st1"]
azurerm_storage_account.storages["st2"]
azurerm_storage_account.storages["st3"]
azurerm_storage_account.storages["st4"]
azurerm_storage_account.storages["st5"]
この状態で各ストレージアカウントの Blob Data Contributor
をユーザに割り当てようとする以下のような感じ (力技)。
local {
st_names = ["st1", "st2", "st3", "st4", "st5"]
users = ["user1@example.com", "user2@example.com"]
}
# ストレージアカウントを作成
resource "auzrerm_storage_account" "storages" {
for_each = local.st_names
name = each.value
resource_group = "rg-sample"
location = "japaneast"
account_tier = "Standard"
account_replication_type = "GRS"
}
# AzureAD からユーザ情報を取得
data "azuread_users" "blob_access_users" {
user_pricipal_names = local.users
}
# st1 の Blob Data Contributor に各ユーザを割り当てる
resource "azurerm_role_assignment" "st1" {
for_each = data.azuread_users.blob_access_users.object_ids
scope = azurerm_storage_account.sotrages["st1"]
role_definition_name = "Blob Data Contributor"
principal_id = each.value
}
# st2 の Blob Data Contributor に各ユーザを割り当てる
resource "azurerm_role_assignment" "st2" {
for_each = data.azuread_users.blob_access_users.object_ids
scope = azurerm_storage_account.sotrages["st2"]
role_definition_name = "Blob Data Contributor"
principal_id = each.value
}
# st3~5 も同様
# st1~5 をループで処理したくなる
数が少ないうちは上記のような書き方でも問題はないかもしれないが、数が多くなってくると大変になる。
その一方、可読性は高いのでそれはそれでありなのかも?
個人的には同じようなコードは何度も書きたくないので、ループで処理するようにしたい。その方がかっこ良いし!
2重ループを実現するには
こうする。
local {
st_names = ["st1", "st2", "st3", "st4", "st5"]
users = ["user1@example.com", "user2@example.com"]
}
# ストレージアカウントを作成
resource "auzrerm_storage_account" "storages" {
for_each = local.st_names
name = each.value
resource_group = "rg-sample"
location = "japaneast"
account_tier = "Standard"
account_replication_type = "GRS"
}
# AzureAD からユーザ情報を取得
data "azuread_users" "blob_access_users" {
user_pricipal_names = local.users
}
# st1~5 の Blob Data Contributor に各ユーザを割り当てる
# 2重ループ部分
resource "azurerm_role_assignment" "blob_data_contributor" {
for_each = {
for item in setproduct(
[for st_name in local.st_names : st_name],
[for user_id in data.azuread_users.object_ids]
) : join("-", item) => item
}
scope = each.value[0] # st_name
role_definition_name = "Blob Data Contributor"
principal_id = each.value[1] # user_id
# each.key は st_name と user_id を '-' で join した文字列 (下記参考)
}
上記で使っている setproduct
が今回のポイント。詳細は 公式ドキュメント を。要は、引数で与えられたリストの直積を作成する関数。
これによって、以下のような形の map
のリストを作成して、for_each
に与えている。
[
"st1-user1@example.com" = {"st1", "user1@example.com"},
"st1-user2@example.com" = {"st1", "user2@example.com"},
"st2-user1@example.com" = {"st2", "user1@example.com"},
"st2-user2@example.com" = {"st2", "user2@example.com"},
...
]
こうすることで、auzrerm_role_assignment
の各ストレージへのロールアサインのリソースは以下のようになり、重複しなくなるため実行できる。
azurerm_role_assignment.blob_data_contributor["st1-user1@example.com"]
azurerm_role_assignment.blob_data_contributor["st1-user2@example.com"]
azurerm_role_assignment.blob_data_contributor["st2-user1@example.com"]
azurerm_role_assignment.blob_data_contributor["st2-user2@example.com"]
azurerm_role_assignment.blob_data_contributor["st3-user1@example.com"]
...
3重ループとかも実現可能なはずだが、そこまではやっていない。そこまでいくと可読性が下がりそうなので、別の方法でやった方が良いのかも?
これが最良の方法なのかは分からないので、他の方法などあればコメントいただけると嬉しいです!
以上です。