How to generate a map to use with for_each

Hello there!

I have the following input variable for my module:

gcp_sa_roles = [
  {
    project_id = "test_project1"
    roles = [
      "a",
      "b"
    ]
  },
  {
    project_id = "test_project2"
    roles = [
      "a",
      "c"
    ]
  }
]

I’d like to use that in the google_project_iam_member resource. For that, I think I should achieve something like that so I can use for_each within it:

# desired structure (unable to generate so far)
{
  "test_project_1.a" = ["test_project1", "a"]
  "test_project_1.b" = ["test_project1", "b"]
  "test_project_2.a" = ["test_project2", "a"]
  "test_project_2.c" = ["test_project2", "c"]
}

This way I can create the resource as follow:

resource "google_project_iam_member" "gcp_sa_roles" {
  for_each = var.desired_structure

  project = each.value[0]
  role    = each.value[1]
  member  = "foo"
}

Is that possible?

I can only make the following structure:

# [ for k in var.gcp_sa_roles: { for role in k.roles: "${k.project_id}.${role}" => [role, k.project_id] } ]
[
  {
    "test_project1.a" = [
      "a",
      "test_project1",
    ]
    "test_project1.b" = [
      "b",
      "test_project1",
    ]
  },
  {
    "test_project2.a" = [
      "a",
      "test_project2",
    ]
    "test_project2.b" = [
      "b",
      "test_project2",
    ]
  },
]

Here’s something that I think does what you’re looking for:

locals {
  gcp_sa_roles = [
    {
      project_id = "test_project1"
      roles = [
        "a",
        "b"
      ]
    },
    {
      project_id = "test_project2"
      roles = [
        "a",
        "c"
      ]
    }
  ]
  project_roles = {
    for gsr in local.gcp_sa_roles: gsr.project_id => gsr.roles
  }
  project_role_maps = [
    for project, roles in local.project_roles: {
      for role in roles: "${project}.${role}" => [project, role]
    }
  ]
  result = merge(local.project_role_maps...)
}

This makes use of the ... argument expansion operator, and uses several intermediate steps to make debugging easier. It can be collapsed into one expression if that’s clearer to you:

  result = merge([
    for project, roles in { for gsr in local.gcp_sa_roles: gsr.project_id => gsr.roles }: {
      for role in roles: "${project}.${role}" => [project, role]
    }
  ]...)

Hope this helps!

@alisdair Hi! Thanks for the reply!

I ended up using the following to generate something usable to the for_each:

locals {
  all_gcp_sa_roles = flatten([
    for permission in var.gcp_sa_roles :
    [for role in permission.roles : { project_id = permission.project_id, role = role }]
  ])
}

The local.all_gcp_sa_roles has the following output (as example):

    [
      {
        "project_id" = "project-1"
        "role" = "role-1"
      },
      {
        "project_id" = "project-1"
        "role" = "role-2"
      },
      {
        "project_id" = "project-2"
        "role" = "role-1"
      },
      ...
      ...
      ...
    ]

That way, I can use the for_each to iterate over the all_gcp_sa_roles:

resource "google_project_iam_member" "workload_identity_sa_bindings" {
  for_each = { for role in local.all_gcp_sa_roles : "${role.project_id}.${role.role}" => role }

  project = each.value.project_id
  role    = each.value.role
  member  = "serviceAccount:${google_service_account.cluster_service_account.email}"
}