Unable to map the value when i have nested map variable

Hi Team, I am facing issue fetching and looping the map value.
variable : variable “ipam_scopes” {
description = “Map of IPAM scopes, each with pools and CIDRs”
type = map(object({
description = string
pools = map(object({
description = string
cidrs = list(string)
locale = string
}))
}))
}

payload is(tfvars file) : ipam_scopes = {
“scope1” = {
description = “Scope 1 Description”
pools = {
“man1” = {
description = “Pool 1 in Scope 1”
cidrs = [“10.0.0.0/16”, “10.1.0.0/16”]
locale = “us-east-1”
}
“mig1” = {
description = “Pool 2 in Scope 1”
cidrs = [“10.2.0.0/16”, “10.3.0.0/16”]
locale = “us-west-1”
}
}
}
“scope2” = {
description = “Scope 2 Description”
pools = {
“pool1” = {
description = “Pool 1 in Scope 2”
cidrs = [“192.168.0.0/24”, “192.168.1.0/24”]
locale = “us-east-2”
}

}

}
}

code for ipam pool: resource “aws_vpc_ipam_pool” “pool” {
for_each = {
for scope_name, scope_data in var.ipam_scopes :
for pool_name, pool_data in scope_data.pools :
{scope_name}_{pool_name}” => {
scope_name = scope_name
pool_name = pool_name
description = pool_data.description
cidrs = pool_data.cidrs
locale = pool_data.locale

}

}

ipam_scope_id = aws_vpc_ipam_scope.scope[each.value.scope_name].id
description = each.value.description
locale = each.value.locale
address_family = “ipv4”

error : Error: Invalid ‘for’ expression

│ on main.tf line 18, in resource “aws_vpc_ipam_pool” “pool”:
│ 16: for_each = {
│ 17: for scope_name, scope_data in var.ipam_scopes :
│ 18: for pool_name, pool_data in scope_data.pools :
│ 19: “{scope_name}_{pool_name}” => {
│ 20: scope_name = scope_name
│ 21: pool_name = pool_name
│ 22: description = pool_data.description
│ 23: cidrs = pool_data.cidrs
│ 24: locale = pool_data.locale
│ 25:
│ 26:
│ 27: }
│ 28: }

│ Key expression is required when building an object.


│ Error: Invalid ‘for’ expression

│ on main.tf line 18, in resource “aws_vpc_ipam_pool” “pool”:
│ 16: for_each = {
│ 17: for scope_name, scope_data in var.ipam_scopes :
│ 18: for pool_name, pool_data in scope_data.pools :

│ Extra characters after the end of the ‘for’ expression.

Two suggestions:

  • If you are going to post Terraform code here (especially complicated / long snippets), make sure to wrap it in a code block.
  • With these kinds of data structure problems, I like to make a really simple and self-contained terraform file with just the data structure and an output and / or a null resource. This will also let you experiment to see if starting with a slightly different data structure will help simplify things or not.

IMO, that can make things a lot easier / faster to play around with and try different things. This may not be exactly what you’re trying to do, but does this get closer or give you some ideas?

variable "ipam_scopes" {
  description = "Map of IPAM scopes, each with pools and CIDRs"
  type = map(object({
    description = string
    pools = map(object({
      description = string
      cidrs       = list(string)
      locale      = string
    }))
  }))
  default = {
    scope1 = {
      description = "Scope 1"
      pools = {
        test1 = {
          description = "Pool 1 in Scope 1"
          cidrs = [
            "192.168.0.0/24",
            "192.168.1.0/24"
          ]
          locale = "us-east-2"
        }
        test2 = {
          description = "Pool 2 in Scope 1"
          cidrs = [
            "10.0.0.0/24",
            "10.0.1.0/24"
          ]
          locale = "us-east-2"
        }
      }
    }
    scope2 = {
      description = "Scope 2"
      pools = {
        pool1 = {
          description = "Pool 1 in Scope 2"
          cidrs = [
            "192.168.0.0/24",
            "192.168.1.0/24"
          ]
          locale = "us-east-2"
        }
      }
    }
  }
}

output "test" {
  value = merge([
    for scope_name, scope_data in var.ipam_scopes : {
      for pool_name, pool_data in scope_data.pools :
      "${scope_name}_${pool_name}" => {
        scope_name  = scope_name
        pool_name   = pool_name
        description = pool_data.description
        cidrs       = pool_data.cidrs
        locale      = pool_data.locale
      }
    }
  # That spread operator (`...` ) is needed here if you're using `merge()`
  ]...)
}

If you put this as test.tf in an empty directory, and run terraform apply, you should get something like this:

Outputs:

test = {
  "scope1_test1" = {
    "cidrs" = tolist([
      "192.168.0.0/24",
      "192.168.1.0/24",
    ])
    "description" = "Pool 1 in Scope 1"
    "locale" = "us-east-2"
    "pool_name" = "test1"
    "scope_name" = "scope1"
  }
  "scope1_test2" = {
    "cidrs" = tolist([
      "10.0.0.0/24",
      "10.0.1.0/24",
    ])
    "description" = "Pool 2 in Scope 1"
    "locale" = "us-east-2"
    "pool_name" = "test2"
    "scope_name" = "scope1"
  }
  "scope2_pool1" = {
    "cidrs" = tolist([
      "192.168.0.0/24",
      "192.168.1.0/24",
    ])
    "description" = "Pool 1 in Scope 2"
    "locale" = "us-east-2"
    "pool_name" = "pool1"
    "scope_name" = "scope2"
  }
}

For extra credit, build in validation that the the CIDR ranges within the object are valid :crazy_face: