Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
1 task done
martin566 opened this issue Nov 10, 2022 · 9 comments · Fixed by #926
Closed
1 task done

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

martin566 opened this issue Nov 10, 2022 · 9 comments · Fixed by #926

Comments

@martin566
Copy link

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" {
@github-actions
Copy link

This issue has been automatically marked as stale because it has been open 30 days
with no activity. Remove stale label or comment or this issue will be closed in 10 days

@github-actions github-actions bot added the stale label Dec 11, 2022
@untcha
Copy link

untcha commented Dec 15, 2022

This issue is still open.

@github-actions github-actions bot removed the stale label Dec 16, 2022
@SungHyeon
Copy link

I have same issue.
When this issue fix release?

@github-actions
Copy link

github-actions bot commented Feb 9, 2023

This issue has been automatically marked as stale because it has been open 30 days
with no activity. Remove stale label or comment or this issue will be closed in 10 days

@github-actions github-actions bot added the stale label Feb 9, 2023
@bryantbiggs
Copy link
Member

I am afraid this is a core Terraform issue, that is coming from here

for_each = {
    for route in local.routes_to_transit_gateway_per_table : route.route_entry_name => route
  }

You cannot use for_each over a map where the key is unknown; Hashi recommends using a static value and not a computed value as the key for this reason hashicorp/terraform#30937

@github-actions
Copy link

This issue has been automatically marked as stale because it has been open 30 days
with no activity. Remove stale label or comment or this issue will be closed in 10 days

@github-actions github-actions bot added the stale label Mar 13, 2023
@github-actions
Copy link

This issue was automatically closed because of stale in 10 days

@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Mar 24, 2023
henworth added a commit to henworth/terraform-aws-vpc that referenced this issue Apr 20, 2023
On initial plan the `database_route_table_ids` output is not
available due to the values not being known until after apply.
Switching the logic to test the length of the array fixes this
issue. Credit to @martin566 for discovering the solution.

fixes terraform-aws-modules#857
@github-actions
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Apr 23, 2023
bryantbiggs added a commit that referenced this issue Jul 25, 2023
* fix: ensure database route table output works

On initial plan the `database_route_table_ids` output is not
available due to the values not being known until after apply.
Switching the logic to test the length of the array fixes this
issue. Credit to @martin566 for discovering the solution.

fixes #857

* Update outputs.tf

* Update outputs.tf

---------

Co-authored-by: Bryant Biggs <bryantbiggs@gmail.com>
@antonbabenko
Copy link
Member

This issue has been resolved in version 5.1.1 🎉

waddamski pushed a commit to hmrc/terraform-aws-vpc that referenced this issue Jul 5, 2024
)

* fix: ensure database route table output works

On initial plan the `database_route_table_ids` output is not
available due to the values not being known until after apply.
Switching the logic to test the length of the array fixes this
issue. Credit to @martin566 for discovering the solution.

fixes terraform-aws-modules#857

* Update outputs.tf

* Update outputs.tf

---------

Co-authored-by: Bryant Biggs <bryantbiggs@gmail.com>
waddamski pushed a commit to hmrc/terraform-aws-vpc that referenced this issue Jul 5, 2024
)

* fix: ensure database route table output works

On initial plan the `database_route_table_ids` output is not
available due to the values not being known until after apply.
Switching the logic to test the length of the array fixes this
issue. Credit to @martin566 for discovering the solution.

fixes terraform-aws-modules#857

* Update outputs.tf

* Update outputs.tf

---------

Co-authored-by: Bryant Biggs <bryantbiggs@gmail.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants