1

I'm trying to create a map in locals where a key is created dynamically based on the value of another variable, and can't find a functional way of doing this in Terraform.

The map contains subnet configuration parameters, in the format:

  <subnet-name> = {
    subnet_id = <subnet-id> 
    nsg_id    = <nsg-id>
    rtbl_id   = <rtbl-id>
  }

And I'm creating it like this:

  snet_mappings = {
    for key in keys(module.subnet.snet-ids) : key => { 
      snet_id = module.subnet.snet-ids[key], 
      nsg_id  = module.nsg.nsg-ids[var.snet_config[key]["nsg"]], 
      rtbl_id = module.route-table.rtbl-ids[var.snet_config[key]["route_table"]]
    }
  }

With the following variable acting as the lookup for the NSG/Route Table to add from the ones created:

snet_config = {
    "testsub" = {
        "address_space" = ["10.10.0.0/24"]
        "nsg"           = "nsg-001"
        "route_table"   = "rtbl-001"
    }
}

This works perfectly so long as I define an NSG and a Route Table every time in the snet_config map. It doesn't work for a case like this however where I don't want the Route Table to be set, so I leave it blank:

snet_config = {
    "testsub" = {
        "address_space" = ["10.10.0.0/24"]
        "nsg"           = "nsg-001"
        "route_table"   = ""
    }
}

This is because it tries to lookup the Route Table ID with an empty string, which therefore fails as it doesn't match a key in the route-table.rtbl-ids map. In the instance where route table isn't set, I actually want my map to look like this:

  <subnet-name> = {
    subnet_id = <subnet-id> 
    nsg_id    = <nsg-id>
  }

i.e. the rtbl_id key isn't set at all. Is it possible to conditionally create keys? I've tried playing around with Terraform conditionals but can't find a working solution.

Albina
  • 1,901
  • 3
  • 7
  • 19
AndyB
  • 29
  • 7
  • Would `rtbl_id = try(module.route-table.rtbl-ids[var.snet_config[key]["route_table"]], null)` work? – Marko E Jul 21 '23 at 08:51
  • This actually still creates the `rtbl_id` key, but with a value of `null`. While not what I explicitly wanted, this gets me over the hurdle of the local creation - I can put further conditional logic in my module to handle the null value in the map so thanks! – AndyB Jul 21 '23 at 10:23

1 Answers1

1

If you are optionally specifying the key, then you can use the merge, keys, and contains functions with a ternary to accomplish this:

snet_mappings = {
  for key in keys(module.subnet.snet-ids) : key => merge(
    { 
      snet_id = module.subnet.snet-ids[key], 
      nsg_id  = module.nsg.nsg-ids[var.snet_config[key]["nsg"]],
    },
    contains(keys(var.snet_config[key]), "route_table") ? { rtbl_id = module.route-table.rtbl-ids[var.snet_config[key]["route_table"]] } : {}
  )
}

alternatively with can:

snet_mappings = {
  for key in keys(module.subnet.snet-ids) : key => merge(
    { 
      snet_id = module.subnet.snet-ids[key], 
      nsg_id  = module.nsg.nsg-ids[var.snet_config[key]["nsg"]],
    },
    can(module.route-table.rtbl-ids[var.snet_config[key]["route_table"]]) ? { rtbl_id = module.route-table.rtbl-ids[var.snet_config[key]["route_table"]] } : {}
  )
}

The null coealescing functionality with coalesce cannot be used here as it pertains to an optional key.

If instead you specify an empty string for route_table when unused and always specify it as a key, then you would conditional off the length of the string:

snet_mappings = {
  for key in keys(module.subnet.snet-ids) : key => merge(
    { 
      snet_id = module.subnet.snet-ids[key], 
      nsg_id  = module.nsg.nsg-ids[var.snet_config[key]["nsg"]],
    },
    len(module.route-table.rtbl-ids[var.snet_config[key]["route_table"]]) > 0 ? { rtbl_id = module.route-table.rtbl-ids[var.snet_config[key]["route_table"]] } : {}
  )
}

In all three situations the merge function ensure the extra key-value pair only exists in the returned map based on the conditional, and therefore this satisfies the desired functionality.

Matthew Schuchard
  • 25,172
  • 3
  • 47
  • 67