Nested Loops with google provider

Hello I am using google provider to create projects

Runtime Input for project creation is like -

project_attributes = {
  "project_3" = {
    "cos"               = "dev"
    "region"            = ["us-central1","asia-south1"]
    "billing_account"   = 1234
    "org_id"            = 123
   }

   "project_4" = {
     "cos"               = "dev"
     "region"            = ["us-central1","europe-west1"]
     "billing_account"   = 1234
     "org_id"            = 123
   }
 }

Another variable map is for cmek_keys based on cos and region passed in project_attributes it will return the key to use.

cmek_keys = {
  "prod" = {
    "us-central1" = "key_prod_us-central1",
    "asia-south1" = "key_prod_asia-south1",
  }
  "dev" = {
    "us-central1" = "key_dev_us-central1",
    "asia-south1" = "key_dev_asia-south1",
    "europe-west2" = "key_dev_europe-west2",

  }
}

Problem is region in project_attributes i want to be as a list , i want to make sure this resource below runs for all regions, giving acccess to keys in all different regions.

I am able to run this resource below when region is just a string

resource "google_kms_crypto_key_iam_member" "cmek_auth_permissions" {
  for_each = var.project_attributes
  crypto_key_id = var.cmek_keys[each.value["cos"]][each.value["region"]]
  role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
  member = "serviceAccount:service-${google_project.project[each.key].number}@compute-system.iam.gserviceaccount.com"
}

What would be the best to do this , making sure my input given is concise .

Hi @riteshnanda09!

A good building block for this sort of situation is to use the flatten function to reduce your nested structure into a flat structure so that each element corresponds to one instance of google_kms_crypto_key_iam_member.cmek_auth_permissions.

I usually do this in two steps, doing the flattening first as a named local value:

local {
  project_regions = toset(flatten([
    for n, p in var.project_attributes : [
      for r in p.region : {
        project = n
        region  = r
        cos     = p.cos
        key_id  = var.cmek_keys[p.cos][p.region]
      }
    ]
  ]))
}

The nested for expressions produce a list of lists of objects, and then flatten reduces that into a single list of objects. Then I used toset just to be explicit that the ordering of this result isn’t significant and so it isn’t meaningful to use it as a list.

for_each requires a map where each object has a unique key though, so we’ll need to do one more transform in the for_each argument to meet that requirement:

resource "google_kms_crypto_key_iam_member" "cmek_auth_permissions" {
  for_each = {
    for pr in local.project_regions : "${pr.project}.${pr.region}" => pr
  }

  crypto_key_id = each.value.key_id
  role          = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
  member        = "serviceAccount:service-${google_project.project[each.value.project].number}@compute-system.iam.gserviceaccount.com"
}

This will produce instances with addresses like google_kms_crypto_key_iam_member.cmek_auth_permissions["project_3.us-central1"], so Terraform can track which instance belongs to each pairing of project and region.

Perfect @apparentlymart works like a charm .

@apparentlymart
I tested with the above solution you provided, however I am not sure if this works var.cmek_keys[p.cos][p.region] ?

locals {
project_attributes = {
  "project_3" = {
    "cos"               = "dev"
    "region"            = ["us-central1","asia-south1"]
    "billing_account"   = 1234
    "org_id"            = 123
   }

   "project_4" = {
     "cos"               = "dev"
     "region"            = ["us-central1","europe-west1"]
     "billing_account"   = 1234
     "org_id"            = 123
   }
 }

 cmek_keys = {
  "prod" = {
    "us-central1" = "key_prod_us-central1",
    "asia-south1" = "key_prod_asia-south1",
  }
  "dev" = {
    "us-central1" = "key_dev_us-central1",
    "asia-south1" = "key_dev_asia-south1",
    "europe-west2" = "key_dev_europe-west2",

  }
}
   project_regions = toset(flatten([
    for n, p in local.project_attributes : [
      for r in p.region : {
        project = n
        region  = r
        cos     = p.cos
        key_id  = local.cmek_keys[p.cos][p.region] # --> lookup(local.cmek_keys[p.cos], r) 
      }
    ]
  ]))


}

output "out" {
   value = local.project_regions
}
  36:         key_id  = local.cmek_keys[p.cos][p.region]
    |----------------
    | local.cmek_keys is object with 2 attributes
    | p.cos is "dev"
    | p.region is tuple with 2 elements

The given key does not identify an element in this collection value: string
required.


Error: Invalid index

  on tets.tf line 36, in locals:
  36:         key_id  = local.cmek_keys[p.cos][p.region]
    |----------------
    | local.cmek_keys is object with 2 attributes
    | p.cos is "dev"
    | p.region is tuple with 2 elements

The given key does not identify an element in this collection value: string
required.```

Also as per doc this may not be the right syntax, please correct me if I am mistaken here .

Hi @smitjainsj,

Sorry, it looks like I made an error. I intended to write var.cmek_keys[p.cos][r], where r is a single region. p.region is the collection of all regions, and so isn’t valid as a map key.