Invalid for_each argument

resource "aws_iam_role" "this" {
  name = var.role_name
  assume_role_policy = data.aws_iam_policy_document.AWSGlueTrustPolicy.json
  description = "The Glue role for APC project"
}

resource "aws_iam_role_policy_attachment" "this" {
  depends_on = [aws_iam_policy.ESource_S3_Trove_LADWP,  aws_iam_policy.esource_s3_int_esource_client_apc, aws_iam_policy.ESource_S3_Glue_Delete]
  role       = aws_iam_role.this.name
  for_each = toset([  data.aws_iam_policy.AWSGlueRole.arn,
                    aws_iam_policy.ESource_S3_Trove_LADWP.arn,
                    aws_iam_policy.esource_s3_int_esource_client_apc.arn,
                    aws_iam_policy.ESource_S3_Glue_Delete.arn
            ])
  policy_arn = each.key
}

during

terraform plan

causes

Error: Invalid for_each argument
│ 
│   on role.tf line 57, in resource "aws_iam_role_policy_attachment" "this":
│   57:   for_each = toset([  data.aws_iam_policy.AWSGlueRole.arn,
│   58:                     aws_iam_policy.ESource_S3_Trove_LADWP.arn,
│   59:                     aws_iam_policy.esource_s3_int_esource_client_apc.arn,
│   60:                     aws_iam_policy.ESource_S3_Glue_Delete.arn
│   61:             ])
│     ├────────────────
│     │ aws_iam_policy.ESource_S3_Glue_Delete.arn is "arn:aws:iam::994851769564:policy/ESource_S3_Glue_Delete"
│     │ aws_iam_policy.ESource_S3_Trove_LADWP.arn is a string, known only after apply
│     │ aws_iam_policy.esource_s3_int_esource_client_apc.arn is a string, known only after apply
│     │ data.aws_iam_policy.AWSGlueRole.arn is "arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole"
│ 
│ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will
│ be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.
╵

The only way around is to disable the policy attachment. Get the policies created and then run with the policy attachment.

any help is appreciated at this point.

Have you tried dropping depends_on. Actually terraform should be able to build the dependency tree without as all of those are referred in for_each.

Well, actually the documentation is clear.

@tbugfinder :
the original code version did not have depends_on. I added it in an attempt to resolve the problem. Adding depends_on makes no difference.

I found quite a few references to this seemingly widespread issue with for_each.
Here user Mariux suggests breaking up the code into separate stacks. That is essentially what I resorted to doing, granted in a brutal way of commenting out the policy_attachement and then re-running the code with the code been present… :face_with_diagonal_mouth:

Hi @AZZ,

What the error message is trying to say here is that the the values are unknown at planning time, and therefore can’t be used to address the resource instances. Part of the requirements of the for_each value is is that the keys must be known.

Since the values are hard-coded in this configuration, you could create a map rather than a set of values here

resource "aws_iam_role_policy_attachment" "this" {
  role       = aws_iam_role.this.name
  for_each = {
    "glue_role"   = data.aws_iam_policy.AWSGlueRole.arn,
    "LADWP"       = aws_iam_policy.ESource_S3_Trove_LADWP.arn,
    "client"      = aws_iam_policy.esource_s3_int_esource_client_apc.arn,
    "glue_delete" = aws_iam_policy.ESource_S3_Glue_Delete.arn
  }
  policy_arn = each.key
}
1 Like

Thank you @jbardin … this is interesting… And how creating a hardcoded map is different in this case from specifying to_set?

The difference is that the key values for the map are known, so Terraform can properly plan each resource instance. For example, the value aws_iam_policy.esource_s3_int_esource_client_apc.arn cannot be known until after that resource has been created, so we can’t use that value to name a aws_iam_role_policy_attachment instance. We can however name that instance aws_iam_role_policy_attachment["client"], and use the arn only for the policy_arn attribute.

1 Like

ah , i see
aws_iam_group_policy_attachment.grp_policy_att[" DS.arn:aws:iam::aws:policy/IAMUserChangePassword "] was before and now
aws_iam_policy.ESource_S3_Trove_LADWP

thank you !

Yes, the trick here is that assigning a set to for_each is really just a shortcut for writing out a map with the same keys as values…

  for_each = toset(["a", "b"])

…is the same as…

  for_each = {
    "a" = "a"
    "b" = "b"
  }

That convenience comes at a cost, though: it means that all of the values must be known and non-sensitive, so that they can be used as map keys.

The advantage of writing it out in full, as a real map, is that you can choose different keys than values, and therefore it’s fine for the values to be unknown or sensitive, as long as the keys still meet the constraints to allow Terraform to use them as stable identifiers between runs.

FWIW, we recently merged a change to this error message which has an additional suggestion to use a map with known keys, since the current error message you saw here does have the drawback that it only presents a workaround and not an actual solution. It’s not really practical to get into all of the underlying details and reasoning in a concise error message, but from the next minor release of Terraform it will at least still prompt to think about using a map instead.

1 Like

im very much in the camp of “doing it one way and one way only”. So your comment and the fix makes the old guy happy :slight_smile: