Lookup from map(string) failed

Hello, I’ve created a route table, and I would like to use mapping, however some of values inside of map is list of strings, and couldn’t find a solution to manage it.

Here is the main.tf

resource "aws_route" "ipv4" {
   count                       = length(aws_route_table.route)
   depends_on                  = [ aws_route_table.route ]
   route_table_id              = aws_route_table.route[count.index].id

   gateway_id                  = lookup(var.ipv4,  "igw",    "")
   nat_gateway_id              = lookup(var.ipv4,  "nat",    "")
   destination_ipv6_cidr_block = lookup(var.ipv4,  "target", "")
}

resource "aws_route" "ipv6" {
   count                       = length(aws_route_table.route)
   depends_on                  = [ aws_route_table.route ]
   route_table_id              = aws_route_table.route[count.index].id

   gateway_id                  = lookup(var.ipv6,  "igw",    "")
   egress_only_gateway_id      = lookup(var.ipv6,  "egw",    "")
   destination_ipv6_cidr_block = lookup(var.ipv6,  "target", "")
}

variable "ipv4"          {
   type        = map(string)
   default     = null
}

variable "ipv6"          {
   type        = map(string)
   default     = null
}

Here is how I’m defining the values
Notice that, if value contains string only, no issue
The issue with nat gateway id, as I have multiple nat gateways

locals {
   ipv4  = {
     public  = {
       target = "0.0.0.0/0"
       igw    = module.internet_gateway.igw_ids # No Issue
     }
     private = {
       target = "0.0.0.0/0"
       nat    = module.translate_gateway.nat_ids # << Here is the list of strings
     }
   }

   ipv6  = {
     public  = {
       target = "::/0"
       igw    = module.internet_gateway.igw_ids # No Issue
     }
     private = {
       target = "::/0"
       nat    = compact(module.private_subnets.ipv6_cidrblock) != [] ? null : module.internet_gateway.egw_ids # No Issue
     }
   }
}

I think I need to somehow make count.index on that lookup block, but I don’t know how

What’s the error you’re getting?

As well, it seems like your troublesome line is output from a module … what does that output definition look like?

       nat    = module.translate_gateway.nat_ids # << Here is the list of strings
1 Like

@pselle Thank you for your response here the output

output "nat_ids"                     {
   description = "The id of created nat gateway"
   value       = aws_nat_gateway.translate.*.id
}

Your code lacks few details :slight_smile: Maybe terraform plan output would be beneficial, too.

As far as I understand you’d have to iterate on length(aws_route_table.route) (where is it?) and the number of nat gateways (rather use for each).
I assume your other might work as those are not lists.

1 Like

Hello @tbugfinder I’m glad to see you :slightly_smiling_face:
Here is the output, it fails

The given value is not suitable for child module variable "route_rules_ipvf"
defined at ../Resources/Network/Routings/variables.tf:93,1-28: element "nat":
string required.  

And here is the route table resource it self

resource "aws_route_table" "route"   {
   count   = var.create_route && length(var.public_subnet) > 0 ? length(var.private_subnet) : 0
   vpc_id  = var.vpc_id
}

@tbugfinder As you already said the others are working as expected, as they are not list of string
So I don’t know how to make something like count.index for lookup() function, believe me I’ve tried many options.

The other option is make a variable, like var.nat_gateway and count it with element like
element(var.nat_gateway, count.index) which will work for sure, but in my case I would like to do with lookup

Wouldn’t you have to create more route table entries than just the ones you’re looking for with count and your lookup?
Actually element(lookup(var.ipv4, "nat", []),count.index) should work technically.

@tbugfinder Nope it did not worked
Here is the plan output:

  58:    nat_gateway_id                    = element(lookup(var.ipv4, "nat", []), count.index)

Invalid value for "default" parameter: the default value must have the same
type as the map elements.

Indeed my bad.

On the other hand, the variable type isn’t correct either as there are cases when it’s map(list(string)) correct?

ind

@tbugfinder So as you can see, I’ve defined my routing rules inside of locals and it can only work with map(string) I’ve also tried with map(list(string)) but it did not work eventually.

So why in my case map(list(string)) does not work
I’m accessing to locals like:

...routing module block...
ipv4_public_route  = local.ipv4.public
ipv4_private_route = local.ipv4.private
... end ...

You have defined the variable to be a map of strings, so it isn’t allowed to pass in a list (as that would be a map of lists of strings).

So you have a few options.

You could keep everything as map(string), in which case anything that returns a list needs to be converted to a string (e.g. using join()). Equally anything which expects a list within the module would need the string converting to a list (e.g. using split()). While this would work it is fairly clunky and you don’t get any real type checking (nothing will validate the string can be split into a list).

The option I’d suggest would be to change from using map(string) to an object.

With this option you can individually define the datatypes for each of the different fields (target, nat, igw), with target and igw being string and nat being list(string). Ideally you’d defined both nat & igw as optional so that you don’t have to pass in null values.

1 Like

@stuart-c Thank you for your suggestion, could you please share the knowledge of how to implement optional nat, igw and etc.

I’ve changed from map to object as you suggested, however other resources should be null, and unfortunately they’re required

You can mark a field as optional by using the optional() entry - Type Constraints - Configuration Language - Terraform by HashiCorp

1 Like