Evaluate module variable in another module when variable is in list of objects

I have an issue with Terraform module which should pass the name of resource to another module to retrieve the ID of the resource, but the name of resource is contained in list of objects.

The module should create Azure application gateway.

Terraform Registry reference:

https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/application_gateway

The block in question from Registry is:

A gateway_ip_configuration block supports the following:

  • name - (Required) The Name of this Gateway IP Configuration.
  • subnet_id - (Required) The ID of the Subnet which the Application Gateway should be connected to.

So I need the ID of the subnet and I can have multiple gateway_ip_configuration blocks. To be able to evaluate multiple blocks, I need to use a list of objects.

Code:

Child module contains following code (I’ll include only the relevant part of the code):

resource "azurerm_application_gateway" "appgw" {

...

dynamic "gateway_ip_configuration" {

for_each = try(var.settings.gateway_ip_configuration, null) = null? [] : [1]

content {

name = var.settings.gateway_ip_configuration.name

subnet_id = var.subnet.id

}

}

So subnet_id should be retrieved as a var.subnet.id from parent module.

Parent module contains following code (I’ll include only the relevant part of the code):

module "application_gateway" {

for_each = var.application_gateways

name = each.key

settings = each.value

subnet_id = module.subnet[each.value.gateway_ip_configuration.subnet_name].id //This line is the issue

}

So subnet_id should be retrieved from module.subnet which references the value from list of objects. I’m sure that each.value.gateway_ip_configuration.subnet_name line cannot retrieve the subnet_name value from list of objects - the syntax is wrong.

Tfvars file contains following code (I’ll include only the relevant part of the code):

application_gateways = {

appgw_name = {

gateway_ip_configuration = [

{

name = "gateway_config_name"

subnet_name = "subnet_name"

}

]

}

}

So “subnet_name” should be retrieved from gateway_ip_configuration list of objects and used in parent module to evaluate the subnet_name.id in child module module.subnet.

The error I get during plan stage is:

Error: Unsupported attribute

on terraform.tf line xx, in module “application_gateway”:

subnet_id = module.subnet[each.value.gateway_ip_configuration.subnet_name].id

each.value.gateway_ip_configuration is tuple with 1 element

This value does not have any attributes.

So the issue is that I don’t know how to fetch the subnet_name from list of objects and pass it to subnet module to get the ID back.

This is the only scenario where I’m unable to retrieve such an information. If I rewrite the code to not use the parent/child module scenario, just azurerm resource scenario, I can fetch the information, because I’m not trying to pass it from one module to another. But I’d really like to get to the bottom of this scenario.

Any suggestions?

It appears most of your problems come from your gateway_ip_configuration in your .tfvars being a list yet most/all of the expressions you’ve written accessing it, not treating it as a list.

I can’t figure out what your intent is here, as at one point you say

but you then pair that with code which appears deliberately structured to only handle zero or one:

You also say:

which is puzzling, as the issue doesn’t appear to relate to the usage of modules, and would still be an issue if they were all flattened away.

Showing your rewritten code not using modules here, may help communicate what you’re trying to achieve more clearly.

Also, please take a look at Welcome to the forum - please reformat your message regarding the formatting of your code.

Hi maxb, sure, let me explain in more detail:

Rewritten code which works (not using modules) looks like this:

resource "azurerm_application_gateway" "appgw" {
for_each = var.application_gateways
name=each.key
...
dynamic “gateway_ip_configuration” {

for_each = try(each.value.gateway_ip_configuration, null) = null? [] : each.value.gateway_ip_configuration

content {

name = gateway_ip_configuration.value.name

subnet_id = module.subnet[gateway_ip_configuration.value.subnet_name].id

}

}

So this will work, and I can get multiple blocks from list.

My mistake in typing the code that doesn’t work was that I didn’t correctly write the example that I was working on, this would be correct:

resource “azurerm_application_gateway” “appgw” {

…

dynamic “gateway_ip_configuration” {

**for_each = try(var.settings.gateway_ip_configuration, null) = null? [] : var.settings.gateway_ip_configuration** //(so it should loop through all instances of list)

content {

name = var.settings.gateway_ip_configuration.name

subnet_id = var.subnet.id

}

}

Does this make it more clear? The above code will work if I don’t have to retrieve the variable name from list of objects (as mentioned, the retrieval method must be wrong).

EDIT: I apologize for multiple edits, I was trying to get the post to look according to forum rules.

OK, that really clears things up.

First a quick aside about:

You can simplify that to just

for_each = try(var.settings.gateway_ip_configuration, [])

In your non-module scenario, this expression:

is benefiting from two levels of enclosing for_each:

In your module case, you’ve tried to use roughly the same form of expression, despite the fact that it’s in a context where only for_each = var.application_gateways is in effect.

The key thing you have to realise here, is that you cannot pass just a single subnet ID here, because one application_gateway might have a list of more than one gateway_ip_configuration.

Instead, you might want to pass something like:

  subnet_ids = module.subnet[*].id

so that inside the module, you can do something like:

    content {
      name      = gateway_ip_configuration.value.name
      subnet_id = var.subnet_ids[gateway_ip_configuration.value.subnet_name]
    }

Always make sure to pay close attention to exactly what the data structure you are iterating over is - whether it’s each from a top level block, or the name of a dynamic block.

Unfortunately, using splat expression to evaluate id in root module doesn’t work, it errors out with:

Error: Unsupported attribute
subnet_ids =module.subnet[*].id
This object does not have an attribute named "id".

I think using splat expression doesn’t target the particular item that can be evaluated for ID. I think I should target specific value within tuples to get the ID out, which if I understood you correctly, should be passed into list of IDs and then within child module should be targeted for specific ID from the list? But I’m not sure how to write the expression in root module.

You are correct, I forgot (again) that [*] only works with lists and doesn’t generalise to maps in the way one might intuitively expect.

It would have to be

  subnet_ids = { for name, outputs in module.subnet : name => outputs.id }
1 Like

Hi maxb,

OK, yeah, this one worked :slight_smile: But I don’t really understand how and why - I think I understand this line in root module, but how does it relate to child module - how do you find the correct subnet name in a list of IDs?

If you can please explain a bit the process?

And really - thanks a LOT! This is a very elegant solution!

Ah, but it’s not a list, it’s a map - a map of names (the names you assigned as keys in module.subnet’s for_each) to subnet IDs. So, it’s trivial to index into the map, using the relevant name.

1 Like