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に似たようなことで困っている人がいます。
ここで示されているように、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.