Skip to content

tuple vs list problem with output database_route_table_ids during first plan #857

@martin566

Description

@martin566

Description

Usage of output database_route_table_ids in another module causes error "Invalid for_each argument" during first plan/apply.

Cause seems to be that the output delivers a tuple during first plan/apply, because the route_table is still not existing, but a list after the first apply.
This seems to be related to hashicorp/terraform#31102, but the usage of tolist() function, did not help in the exampe below.

  • ✋ I have searched the open/closed issues and my issue is not listed.

Versions

  • Module version [Required]: 3.16.1
  • Terraform version: Terraform v0.15.5
  • Provider version(s): v4.38.0

Reproduction Code [Required]

provider "aws" { region = "eu-central-1" } locals { name = "ex-${replace(basename(path.cwd), "_", "-")}" region = "eu-central-1" # Normaly this is a variable containg a more complex map with multiple cidrs # routed to different tgws routes_to_transit_gateway = { "example_with_simple_routing" = { "tgw_id" = "tgw-example-id" # Normaly a real existing tgw id "destination_cidrs" = ["0.0.0.0/0"] "name" = "example" "subnet_name" = "app" } } ########################################################### # Generate a list containing all vpc route_table_ids with # associated subnet names route_table_ids_list = flatten(concat([ for route_table_id in module.vpc.public_route_table_ids : { "route_table_id" = route_table_id, "subnet_name" = "web" } ], [ for route_table_id in module.vpc.private_route_table_ids : { "route_table_id" = route_table_id, "subnet_name" = "app" } ], [ for route_table_id in module.vpc.database_route_table_ids : { "route_table_id" = route_table_id, "subnet_name" = "data" } ], [ ])) ##################################################### # Generate a list of maps of all required routes to # transit gateways in this vpc routes_to_transit_gateway_per_table = flatten([ for tgw_entry in local.routes_to_transit_gateway : [ for destination_cidr in tgw_entry.destination_cidrs : [ for rtb_value in local.route_table_ids_list : [ { "route_entry_name" = "${tgw_entry.name}_${rtb_value.subnet_name}_${destination_cidr}" # Add route table id and destination CIDR to each tgw attachment entry "route_table_id" = rtb_value.route_table_id, "destination_cidr" = destination_cidr "tgw_id" = tgw_entry.tgw_id "routes_to_transit_gateway_subnet_name" = tgw_entry.subnet_name # for debugging "route_table_ids_map_subnet_name" = rtb_value.subnet_name # for debugging } ] if contains([rtb_value.subnet_name], tgw_entry.subnet_name) || ( contains(["default"], tgw_entry.subnet_name) && rtb_value.subnet_name != "dmz" ) ] ] ]) } ################################################################################ # VPC Module ################################################################################ module "vpc" { source = "terraform-aws-modules/vpc/aws" version = "3.16.1" name = local.name cidr = "10.0.0.0/16" azs = ["${local.region}a", "${local.region}b"] private_subnets = ["10.0.1.0/24", "10.0.2.0/24"] database_subnets = ["10.0.3.0/24", "10.0.4.0/24"] enable_ipv6 = false enable_nat_gateway = false single_nat_gateway = true public_subnet_tags = { Name = "overridden-name-public" } vpc_tags = { Name = "vpc-name" } } ##################################################### # Generate all route to all tgws from one map resource "aws_route" "dynamic" { for_each = { for route in local.routes_to_transit_gateway_per_table : route.route_entry_name => route } route_table_id = each.value.route_table_id destination_cidr_block = each.value.destination_cidr transit_gateway_id = each.value.tgw_id } output "debug_routes_to_transit_gateway_per_table" { value = local.routes_to_transit_gateway_per_table } 

Steps to reproduce the behavior:

terraform init & terraform plan

Expected behavior

terraform plan shows, that the following resource will be created

 # aws_route.dynamic["example_app_0.0.0.0/0"] will be created + resource "aws_route" "dynamic" { + destination_cidr_block = "0.0.0.0/0" + id = (known after apply) + instance_id = (known after apply) + instance_owner_id = (known after apply) + network_interface_id = (known after apply) + origin = (known after apply) + route_table_id = (known after apply) + state = (known after apply) + transit_gateway_id = "tgw-example-id" } 

Actual behavior

terraform plan shows the following error message:

$ terraform plan ╷ │ Error: Invalid for_each argument │ │ on main.tf line 110, in resource "aws_route" "dynamic": │ 110: for_each = { │ 111: for route in local.routes_to_transit_gateway_per_table : route.route_entry_name => route │ 112: } │ ├──────────────── │ │ local.routes_to_transit_gateway_per_table is tuple with 1 element │ │ The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the │ -target argument to first apply only the resources that the for_each depends on. 

Solution

Can be solved by the following change:

diff --git a/outputs.tf b/outputs.tf index 9d93dda..c5c0200 100644 --- a/outputs.tf +++ b/outputs.tf @@ -245,7 +245,7 @@ output "private_route_table_ids" { output "database_route_table_ids" { description = "List of IDs of database route tables" - value = try(coalescelist(aws_route_table.database[*].id, aws_route_table.private[*].id), []) + value = length(aws_route_table.database[*].id) > 0 ? aws_route_table.database[*].id : aws_route_table.private[*].id } output "redshift_route_table_ids" { 

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions