2

I am trying to create a predefined set of IAM roles.

locals {
    default_iam_roles = {
        group1 = {
            name = "group:group1-group@mydomain.com"
            roles = toset([
                "roles/viewer"
            ])
        }
        group2 = {
            name = "group:group2-group@mydomain.com"
            roles = toset([
                "roles/owner"
            ])
        }
    }

    formatted_iam_roles = [ for member in local.default_iam_roles : setproduct([member.name], member.roles) ]
}

If I print local.formatted_iam_roles I get the following:

[
  toset([
    [
      "group:group1-group@mydomain.com",
      "roles/viewer",
    ],
  ]),
  toset([
    [
      "group:group2-group@mydomain.com",
      "roles/owner",
    ],
  ]),
]

Now I want to create a single set which contains all of the combinations contained in the list, so that I can feed it to a resource on a for_each statement, but I am not able to find the logic for this.

The expected output would be:

toset([
  [
    "group:group1-group@mydomain.com",
    "roles/viewer",
  ],
  [
    "group:group2-group@mydomain.com",
    "roles/owner",
  ]
])
Luiscri
  • 913
  • 1
  • 13
  • 40

3 Answers3

2

Your solution in the question was close, you just have to apply a flatten function to the setproduct output.

locals {
  # ...
  formatted_iam_roles = [for member in local.default_iam_roles : flatten(setproduct([member.name], member.roles))]
}

The output will be the following:

formatted_iam_roles = [
  [
    "group:group1-group@mydomain.com",
    "roles/viewer",
  ],
  [
    "group:group2-group@mydomain.com",
    "roles/owner",
  ],
]

Now, if you really want it to make a set for the final result, you can use toset, most like it will be pointless:

formatted_iam_roles = toset([for member in local.default_iam_roles : flatten(setproduct([member.name], member.roles))])
Ervin Szilagyi
  • 14,274
  • 2
  • 25
  • 40
2

The operation of taking multiple sets and deriving a new set which contains all of the elements across all of the input sets is called union and so Terraform's function for it is called setunion to follow that.

locals {
  all_iam_roles = setunion(local.formatted_iam_roles...)
}

The ... symbol after the argument tells Terraform that it should use each element of local.formatted_iam_roles as a separate argument to setunion, because that function is defined as taking an arbitrary number of arguments that are all sets, rather than as taking a list of sets.

It might be helpful to think of setunion as being a similar sort of function as concat. The concat function joins multiple lists together, preserving each list's element order and the order the lists are given. Set elements don't have any particular order and contain each distinct value only once, and so the behavior of setunion is different but its purpose is related.

Martin Atkins
  • 62,420
  • 8
  • 120
  • 138
  • That was exactly what I was asking for and quite simpler than my map + merge approach. Thanks for your time – Luiscri Jun 27 '22 at 17:05
0

I finally faced the problem using maps instead of lists, because I needed to use a for_each argument and it doesn't accept a set of tuples as value.

If anyone knows a better approach, post it and I will be pleased of marking it as correct.

locals {
  formatted_iam_roles = merge([ for member in local.default_iam_roles :
                                    { for pair in setproduct([member.name], member.roles) :
                                          "${pair[0]}${pair[1]}" => { "name": pair[0], "role": pair[1] }
                                    }
                              ]...)
}
resource "google_project_iam_member" "team_access" {
  for_each = local.formatted_iam_roles

  project = var.project_id
  member  = each.value["name"]
  role    = each.value["role"]
}
Luiscri
  • 913
  • 1
  • 13
  • 40