For_each value depends on resource attributes that cannot be determined until apply

Hi @vmorkunas,

Routing table ids are not good candidates to use as identifiers for instances in for_each because those ids cannot be predicted during planning: the remote system allocates them only during actual creation.

When working with for_each we must use keys built from values that are chosen in the configuration, not in the remote system. With what you’ve shared here I’m not sure exactly what values to suggest, but if your module "extapp_vpc" has some argument that somehow specifies where to create route tables – perhaps a set of availability zone names? – then I’d make that private_route_table_ids output instead be a map from whatever name the caller specified to the chosen ids.

Then you can use the keys from that map to identify your route instances, instead of the route table ids:

  for_each = {
    for rt_key, rt_id in var.routing.src_rt_ids :
    format("%s-%s", var.routing.name, rt_key) => rt_id
    if var.routing.src_provider == "aws.stack"
  }

I also have a side-note based on something else in your example: it looks like you’re building this module to allow the routes to be created between objects in different AWS provider instances aws.stack and aws.ops, and are using duplicated resource blocks to allow a switch between the two configurations based on an input variable.

The intended way to address that in Terraform is for your child module to declare its two proxy provider configurations (presumably you already have those for declaring aws.stack and aws.ops) using nomenclature that makes sense in the context of that module, such as src and dst in your case:

provider "aws" {
  alias = "src"
}

provider "aws" {
  alias = "dst"
}

Then in the calling module you can pass whichever provider configuration makes sense for each one, and remove the extra src_provider and dst_provider input variables and associated duplication in the module:

module "routing_extapp_data" {
    source = "../../modules/Stack/Routing"
    routing = { 
        src_rt_ids = module.extapp_vpc.private_route_table_ids
        dst_rt_ids = module.data_vpc.private_route_table_ids
        src_cidr = module.extapp_vpc.vpc.cidr_block
        dst_cidr = module.data_vpc.vpc.cidr_block
        peering_connection_id = module.peering_extapp_data.peering_connection_ids
        name = "extapp-data"
    }
    providers = {
        aws.src = aws.stack
        aws.dst = aws.stack
    }
}

In the above case, both aws.src and aws.dst should be pointed to aws.stack in the calling module, getting the desired effect. A different instance of this module could set them both to aws.ops instead, or set one to aws.stack and the other to aws.ops to route between them.

Your resource block for “src”'s route can then refer to aws.src:

resource "aws_route" "src" {
  provider = aws.src
  for_each = {
    for rt_key, rt_id in var.routing.src_rt_ids :
    format("%s-%s", var.routing.name, rt_key) => rt_id
  }

  # (and all the other arguments as before)
}