for_each
は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.