Search code examples
amazon-web-servicesterraformterraform-provider-aws

Terraform: "known only after apply" issue with for_each


Here's a code snippet I'm trying to fix:

resource "aws_vpc" "main" {
  cidr_block = "10.0.0.0/16"
}

# Find the corresponding routing table
data "aws_route_tables" "rts" {
  vpc_id = aws_vpc.main.id

  depends_on = [ aws_vpc.main ]
}

resource "aws_route" "rs" {
  for_each                  = { for index, entry in local.bar : "${entry.route_table_id}.${entry.zone_cidr}" => entry }
  route_table_id            = each.value.route_table_id
  destination_cidr_block    = each.value.zone_cidr
  vpc_peering_connection_id = data.aws_vpc_peering_connection.accepter.id
}

locals {
  bar = flatten([
    for route_table_id in toset(data.aws_route_tables.rts.ids) : [
      for zone_info in foo.main.zone_info : {
        route_table_id = route_table_id
        zone_cidr      = zone_info.cidr
      }
    ]
  ])
}

In short, it creates a VPC and then it loads its routing table and then tries to add new entries to it using for_each but since TF can't infer the size of data.aws_route_tables.rts.ids during terraform plan (since VPC is not created yet) it complains that local.bar will only be known after apply. It seems to be a well-known limitation of for_each but I'm wondering is there an easy way to fix it since it looks like a popular workflow / sequence of steps.

Here's a full error message:

Error: Invalid for_each argument

on .terraform/modules/abcde/main.tf line 88, in resource "aws_route" “
80: for_each = { for index, entry in local.bar :

${entry.route_table_id}.${e
| local.bar will be known only after apply

The "for_each" map includes keys derived from resource attributes that cannot
be determined until apply, and so Terraform cannot determine the full set of
keys that will identify the instances of this resource.

When working with unknown values in for_each, it's better to define the map
keys statically in your configuration and place apply-time results only in
the map values.

Alternatively, you could use the -target planning option to first apply only
the resources that the for_each value depends on, and then apply a second
time to fully converge.

Initializing: .. Done

Here's a related question I found but I'm not sure it helps since routing tables can't be defined statically beforehand.


Solution

  • A newly create VPC has only one route table - main route table. You do not need to use any data sources to get its ID. The main table has only local route, nothing else.

    Also you get its ID using main_route_table_id attribute of aws_vpc, not aws_route_tables data source:

    aws_vpc.main.main_route_table_id