tech.guitarrapc.cóm

Technical updates

terraform for_each で data をいい感じで渡す

Terraform のfor_each は data でも使えます。 だから何だって感じですが、data でよく aws_iam_policy を拾うことがあるかと思いますが、for_each が使えれば 複数のpolicy を aws_iam_groupaws_iam_useraws_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

ここで示されているように、setmap で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.