Terraform のfor_each は data でも使えます。
だから何だって感じですが、data でよく aws_iam_policy
を拾うことがあるかと思いますが、for_each
が使えれば 複数のpolicy を aws_iam_group
、 aws_iam_user
、 aws_iam_role
といったリソースに紐づけるあるあるがシンプルに定義できそうです。(できます)
一個一個書いてもいいんですけど、attach 程度は for_each でまとめてしまいたいですよね。
for_each
くせ強くてうっかり忘れがちなのでメモしておきます。
目次
TL;DR
単純に書くか、あるいは型をうまく使えるか、どっちらか書きやすい方で書けばいいでしょう。
- おとなしく
[*]
経由でアクセスして primitive な list として認識させる
- for_each の中の型を for を介することで認識させる
環境
- terraform: 0.12.19
- aws provider: 2.43.0
やりたいこと
IAM GroupにPolicy をアタッチすることを考えます。
aws_iam_group_policy_attachment
を使うと複数のポリシーをいい感じに Group にアタッチできるのでこれを使うのが鉄板かと思います。
では Groupに data から取得した policy arnをいい感じに当てられてないか考えましょう。
事前定義
group と policy を次のように定義しておきます。
resource "aws_iam_group" "Administrators" {
name = "Administrators"
path = "/"
lifecycle {
prevent_destroy = true
}
}
data "aws_iam_policy" "AdministratorAccess" {
arn = "arn:aws:iam::aws:policy/AdministratorAccess"
}
data "aws_iam_policy" "ReadonlyAccess" {
arn = "arn:aws:iam::aws:policy/ReadonlyAccess"
}
この group と policy のアタッチをどう書くといいでしょうか。
うまくいく方法
うまくいく方法から、2つほど思いつきます。
[*]
経由で指定する
シンプルに [*].arn
を使って、複数リソースからのarnプロパティを指定します。
resource "aws_iam_group_policy_attachment" "Administrators" {
for_each = toset(data.aws_iam_policy.AdministratorAccess[*].arn)
group = aws_iam_group.Administrators.name
policy_arn = each.value
}
複数の data リソースを取得するなら concat()
すればできます。
resource "aws_iam_group_policy_attachment" "Administrators" {
for_each = toset(concat(data.aws_iam_policy.AdministratorAccess[*].arn, data.aws_iam_policy.ReadonlyAccess[*].arn))
group = aws_iam_group.Administrators.name
policy_arn = each.value
}
[*] 外して toset([data.aws_iam_policy.AdministratorAccess.arn, data.aws_iam_policy.ReadonlyAccess.arn])
ではだめなのがムズカシイ
The given "for_each" argument value is unsuitable: "for_each" supports maps
and sets of strings, but you have provided a set containing type dynamic.
for で型を指定する
arn は 型が stringと決まり切っているけど terraform 的には dyanmic になるので困ります。
GitHubに似たようなことで困っている人がいます。
github.com
ここで示されているように、set
や map
でdynamic として認識されているときに、型を明示するために一度 for を回しています。
この方法をつかうのは、次のようなエラーの場合です。
Error: The given "for_each" argument value is unsuitable: "for_each" supports maps and sets of strings, but you have provided a set containing type dynamic.
示されている方法は今回のケースでもうまくいきます。
resource "aws_iam_group_policy_attachment" "Administrators" {
for_each = {
for k in [
data.aws_iam_policy.AdministratorAccess.arn,
] : k => k
}
group = aws_iam_group.Administrators.name
policy_arn = each.value
}
記述が冗長なのがいやですが、for を介することで型が明確に決まるので、単純に for_each にプロパティを充てただけだと dynamic
などと言われてエラーの時はこの方法で突破できます。
複数の data を並べるのも問題ありません。
resource "aws_iam_group_policy_attachment" "Administrators" {
for_each = {
for k in [
data.aws_iam_policy.AdministratorAccess.arn,
data.aws_iam_policy.ReadonlyAccess.arn,
] : k => k
}
group = aws_iam_group.Administrators.name
policy_arn = each.value
}
ダメな方法
幾つか思いつく方法がやってみるとだめなことありませんか?
ちょっと直感と反しててむむっとなります。
data.aws_iam_policy.AdministratorAccess
安直にやるとだめ。まぁそれはそうです。
resource "aws_iam_group_policy_attachment" "Administrators" {
for_each = data.aws_iam_policy.AdministratorAccess
group = aws_iam_group.Administrators.name
policy_arn = each.value.arn
}
Error: Unsupported attribute
on modules/iam/group.tf line 27, in resource "aws_iam_group_policy_attachment" "Administrators":
27: policy_arn = each.value.arn
|----------------
| each.value is "AdministratorAccess"
This value does not have any attributes.
Error: Unsupported attribute
on modules/iam/group.tf line 27, in resource "aws_iam_group_policy_attachment" "Administrators":
27: policy_arn = each.value.arn
|----------------
| each.value is "{\n \"Version\": \"2012-10-17\",\n \"Statement\": [\n {\n \"Effect\": \"Allow\",\n \"Action\": \"*\",\n \"Resource\": \"*\"\n }\n ]\n}"
This value does not have any attributes.
Error: Unsupported attribute
on modules/iam/group.tf line 27, in resource "aws_iam_group_policy_attachment" "Administrators":
27: policy_arn = each.value.arn
|----------------
| each.value is "/"
This value does not have any attributes.
Error: Unsupported attribute
on modules/iam/group.tf line 27, in resource "aws_iam_group_policy_attachment" "Administrators":
27: policy_arn = each.value.arn
|----------------
| each.value is "arn:aws:iam::aws:policy/AdministratorAccess"
This value does not have any attributes.
Error: Unsupported attribute
on modules/iam/group.tf line 27, in resource "aws_iam_group_policy_attachment" "Administrators":
27: policy_arn = each.value.arn
|----------------
| each.value is "Provides full access to AWS services and resources."
This value does not have any attributes.
Error: Unsupported attribute
on modules/iam/group.tf line 27, in resource "aws_iam_group_policy_attachment" "Administrators":
27: policy_arn = each.value.arn
|----------------
| each.value is "arn:aws:iam::aws:policy/AdministratorAccess"
This value does not have any attributes.
arn を指定してみる
複数の property が入ってくるなら arn を指定すればよさそうと思うと、string なのでダメです。
resource "aws_iam_group_policy_attachment" "Administrators" {
for_each = toset(data.aws_iam_policy.AdministratorAccess.arn)
group = aws_iam_group.Administrators.name
policy_arn = each.value
}
Error: Invalid function argument
on modules/iam/group.tf line 25, in resource "aws_iam_group_policy_attachment" "Administrators":
25: for_each = toset(data.aws_iam_policy.AdministratorAccess.arn)
|----------------
| data.aws_iam_policy.AdministratorAccess.arn is "arn:aws:iam::aws:policy/AdministratorAccess"
Invalid value for "v" parameter: cannot convert string to set of any single
type.
setかmapでないとダメなのでそれはそうです。
toset([])
を使って指定してもダメなのが、はじめにむむっと思うのではないでしょうか。
resource "aws_iam_group_policy_attachment" "Administrators" {
for_each = toset([data.aws_iam_policy.AdministratorAccess.arn])
group = aws_iam_group.Administrators.name
policy_arn = each.value
}
Error: Invalid for_each set argument
on modules/iam/group.tf line 10, in resource "aws_iam_group_policy_attachment" "Administrators":
10: for_each = toset([data.aws_iam_policy.AdministratorAccess.arn])
The given "for_each" argument value is unsuitable: "for_each" supports maps
and sets of strings, but you have provided a set containing type dynamic.