Create output for any number of modules, created by for_Each

I know there’s many threads with exactly this topic on it, but I can’t seem to take those and modify it to work for me, so here I am making another one :wink:

I’m creating a number of gateway subnets in addition to our 5 default ones (local.defined_envs contains a map of the subnets, and some values used elsewhere):

module "gateway_subnet" {
  ## Build extra subnet if it isn't one of the standard 5
  for_each = { for k, v in local.defined_envs : k => v if !contains(["prod1","prod2","sandbox","test1","test2"], k)}
  source        = "./modules/terraform-gcp-subnet"
  name          = "${local.alias}-${each.key}-gateway-subnet"
  ip_cidr_range = each.value.gateway_subnet
  network       = "${local.alias}-${each.value.type}-backend-vpc"
}

So this creates a subnet, (and I’m creating another subnet using the same logic elsewhere), which I want to capture the output for:

    "test5": (try({
      "backend_vpc_name": module.test_backend_vpc.name,
      "backend_vpc_self_link": module.test_backend_vpc.self_link,
      "backend_subnet_name": module.backend_subnet["test5"].name,
      "backend_subnet_region": module.backend_subnet["test5"].region,
      "gateway_subnet_name": module.gateway_subnet["test5"].name,
      "gateway_subnet_self_link": module.gateway_subnet["test5"].self_link,
      "gateway_subnet_ip_cidr_range": module.gateway_subnet["test5"].ip_cidr_range,
      "gateway_subnet_region": module.gateway_subnet["test5"].region,
      "backend_subnet_self_link": module.backend_subnet["test5"].self_link,
      "dmz_subnet_name": module.test1_dmz_subnet.name,
      "dmz_subnet_region": module.test1_dmz_subnet.region,
      "dmz_subnet_self_link": module.test1_dmz_subnet.self_link,
      "dmz_subnet_ip_cidr_range": module.test1_dmz_subnet.ip_cidr_range
    }, {}))

This output works specifically, if I have a test5 env defined, and it creates a empty output map if I don’t. That’s great, but I’d like to have the output work for any number of env’s.

I’ve tried using the same for_each logic:

    "test_custom": ({for_each = { for k, v in local.defined_envs : k => v if !contains(["prod1","prod2","sandbox","test1","test2"], k)}
      "backend_vpc_name": module.test_backend_vpc.name,
      "backend_vpc_self_link": module.test_backend_vpc.self_link,
      "backend_subnet_name": module.backend_subnet[each.key].name,
      "backend_subnet_region": module.backend_subnet[each.key].region,
      "gateway_subnet_name": module.gateway_subnet[each.key].name,
      "gateway_subnet_self_link": module.gateway_subnet[each.key].self_link,
      "gateway_subnet_ip_cidr_range": module.gateway_subnet[each.key].ip_cidr_range,
      "gateway_subnet_region": module.gateway_subnet[each.key].region,
      "backend_subnet_self_link": module.backend_subnet[each.key].self_link,
      "dmz_subnet_name": module.test1_dmz_subnet.name,
      "dmz_subnet_region": module.test1_dmz_subnet.region,
      "dmz_subnet_self_link": module.test1_dmz_subnet.self_link,
      "dmz_subnet_ip_cidr_range": module.test1_dmz_subnet.ip_cidr_range
    })

However this brings up errors that the for_each can’t be used here,:

│ 
│   on outputs.tf line 147, in output "environment":
│  147:       "backend_subnet_name": module.backend_subnet[each.key].name,
│ 
│ The "each" object can be used only in "module" or "resource" blocks, and
│ only when the "for_each" argument is set.

So I presume I need to access the values in another way.

Hi @djsmiley2k,

In your for expression you’ve declared k as the symbol representing the key, so if you replace those each.key references with just k then it should behave as you wanted.

Hi again @apparentlymart,

I’ve tried your suggestion however it still errors:

│ Error: Invalid "each" attribute
│ 
│   on outputs.tf line 147, in output "environment":
│  147:       "backend_subnet_name": module.backend_subnet[each.k].name,
│ 
│ The "each" object does not have an attribute named "k". The supported
│ attributes are each.key and each.value, the current key and value pair of
│ the "for_each" attribute set.
╵

I was also wondering how I managed to screw that up so badly, so I checked how I was generating the subnets, and I do in fact use ‘key’ there, and it works (the subnets are generated and with hte correct names):

module "gateway_subnet" {
  ## Build extra subnet if it isn't one of the standard 5
  for_each = { for k, v in local.defined_envs : k => v if !contains(["prod1","prod2","sandbox","test1","test2"], k)}
  source        = "./modules/terraform-gcp-subnet"
  name          = "${local.alias}-${each.key}-gateway-subnet"
  project       = local.project
  region        = "prod1" == each.key ? local.region1 : local.region2
  ip_cidr_range = each.value.gateway_subnet
  network       = "${local.alias}-${each.value.type}-backend-vpc"
}

I suspect I simply shouldn’t be using the for_each in the output at all, and instead I should be capturing it from the existing module, but that’s where I fail to understand what I’m doing!

Hi @djsmiley2k,

Please note that I suggested just k, not each.k. This “each.” prefix is part of the resource for_each feature, so it isn’t relevant anywhere except in a resource or module block that had for_each set.