terraformciscocisco-dnac

How to deal with "Error: Duplicate object key" in Terraform


I am working on a scripts for creating a site hierarchy in Cisco DNA/Catalyst Center. I used an existing repo, that I've modified. I have structure that looks like this:

├── main.tf
├── variables.tf
├── modules 
│ └── site_settings 
│   └── site.tf 
│   └── variables.tf
├── Site-1
│ └── site-1.tfvars

In ./main.tf I have:

provider "dnacenter" {

    username = var.dnac_username
    password = var.dnac_password
    base_url = var.dnac_url
    debug = "true"
    ssl_verify = "false"
}

module "site_settings" {
  # Using m0_general_settings module to configure general settings
  source = "../modules/site_settings"

  global_name = var.global_name
  region_name = var.region_name
  country_name = var.country_name
  city_name = var.city_name
  site_name = var.site_name
  buildings = var.buildings
  building_name = var.building_name
}

The ./modules/site_settings/site.tf has:

  resource "dnacenter_area" "site" {
    depends_on = [ dnacenter_area.city ]
    provider = dnacenter
    parameters {
      site {
        area {
          name = var.site_name
          parent_name = "${var.parent_name}/${var.global_name}/${var.country_name}/${var.city_name}"
        }
      }
    type = "area"
    }
  }
  
resource "dnacenter_building" "building" {
  depends_on = [ dnacenter_area.site ]
  for_each = { for building in var.buildings : building.name => building }
  provider = dnacenter
  parameters {
    site {
      building {
        name        = "${each.value.name}"
        parent_name = "${var.global_name}/${var.region_name}/${var.country_name}/${var.city_name}/${each.value.parent_name}"
        latitude    = "${each.value.latitude}"
        longitude   = "${each.value.longitude}"
      }
    }
  type = "building"
  }
}

resource "dnacenter_floor" "floor" {
  depends_on = [ dnacenter_building.building ]
  for_each = { for floor in var.floors : floor.name => floor }
  provider = dnacenter
  parameters {
    site {
      floor {
        name        = "${each.value.name}"
        parent_name = "${var.global_name}/${var.region_name}/${var.country_name}/${var.city_name}/${each.value.parent_name}/${each.value.parent_building_name}"
      }
    }
  type = "floor"
  }
}

These are the varialbe values in ./site-1.tfvars :

dnac_username = "Cisco"
dnac_password = "Cisco123"
dnac_url = "https://my-dna-lab.test.net"
site_name = "Site-1"
buildings = [
{
building_name = "B01"
parent_site_name = "Site-1"
latitude = "xx.xxxx"
longitude = "xx.xxxx"
},
{
building_name = "B02"
parent_site_name = "Site-1"
latitude = "xx.xxxx"
longitude = "xx.xxxx"
},
]
floors = [
{
name = "F02"
parent_name = "Site-1"
parent_building_name = "B01"
},
{
name = "F03"
parent_name = "Site-1"
parent_building_name = "B01"
},
{
name = "F04"
parent_name = "Site-1"
parent_building_name = "B01"
},
{
name = "F03"
parent_name = "Site-1"
parent_building_name = "B02"
},
]

These are the variables defined in ./variables.tf

variable "dnac_username" {
    sensitive = true
}
variable "dnac_password" {
    sensitive = true
}
variable "dnac_url" {
  type = string
}
variable "site_name" {
  type = string
}
variable "global_name" {}
variable "buildings" {
  description = "List of buildings"
  type = list(object({
    name    = string
    parent_name = string
    latitude         = number
    longitude        = number
  }))
}
variable "floors" {
  description = "List of Floors"
  type = list(object({
    name    = string
    parent_name = string
    parent_building_name = string
  }))
}

And here are the vars in ./modules/site_settings/variables.tf:

variable "region_name" {
  type = string
}
variable "country_name" {
  type = string
}
variable "city_name" {
  type = string
}
variable "site_name" {
  type = string
}
variable "global_name" {}
variable "ip_pools" {}
variable "floors" {}
variable "buildings" {}

When I try to run the plan command, I get an error for duplicate values, since I have the same Floor name, which is in two different buildings.

Error: Duplicate object key
│ 
│   on modules/site_settings/site.tf line 90, in resource "dnacenter_floor" "floor":
│   90:   for_each = { for floor in var.floors : floor.name => floor }
│     ├────────────────
│     │ floor.name is "F03"
│ 
│ Two different items produced the key "F03" in this 'for' expression. If duplicates are expected, use the ellipsis (...) after the value expression to enable grouping by key.

How can I make this work, and create the floors as below? Can I somehow reference also the building in the floors for loop, or refence floor by ID (which is still not known)?

  # module.site_settings.dnacenter_building.building["B01"] will be created
  + resource "dnacenter_building" "building" {
      + id           = (known after apply)
      + item         = (known after apply)
      + last_updated = (known after apply)

      + parameters {
          + type = "building"

          + site {
              + building {
                  + address     = (known after apply)
                  + country     = (known after apply)
                  + latitude    = xx.xxxx
                  + longitude   = xx.xxxx
                  + name        = "B01"
                  + parent_name = "Global/EMEA/Spain/Madrid/Site-1"
                }
            }
        }
    }

  # module.site_settings.dnacenter_building.building["B02"] will be created
  + resource "dnacenter_building" "building" {
      + id           = (known after apply)
      + item         = (known after apply)
      + last_updated = (known after apply)

      + parameters {
          + type = "building"

          + site {
              + building {
                  + address     = (known after apply)
                  + country     = (known after apply)
                  + latitude    = xx.xxxx
                  + longitude   = xx.xxxx
                  + name        = "B02"
                  + parent_name = "Global/EMEA/Spain/Madrid/Site-1"
                }
            }
        }
    }



  # module.site_settings.dnacenter_floor.floor["F02"] will be created
  + resource "dnacenter_floor" "floor" {
      + id           = (known after apply)
      + item         = (known after apply)
      + last_updated = (known after apply)

      + parameters {
          + type = "floor"

          + site {
              + floor {
                  + floor_number = (known after apply)
                  + height       = (known after apply)
                  + length       = (known after apply)
                  + name         = "F02"
                  + parent_name  = "Global/EMEA/Spain/Madrid/Site-1/B01"
                  + rf_model     = (known after apply)
                  + width        = (known after apply)
                }
            }
        }
    }

  # module.site_settings.dnacenter_floor.floor["F03"] will be created
  + resource "dnacenter_floor" "floor" {
      + id           = (known after apply)
      + item         = (known after apply)
      + last_updated = (known after apply)

      + parameters {
          + type = "floor"

          + site {
              + floor {
                  + floor_number = (known after apply)
                  + height       = (known after apply)
                  + length       = (known after apply)
                  + name         = "F03"
                  + parent_name  = "Global/EMEA/Spain/Madrid/Site-1/B01"
                  + rf_model     = (known after apply)
                  + width        = (known after apply)
                }
            }
        }
    }

  # module.site_settings.dnacenter_floor.floor["F03"] will be created
  + resource "dnacenter_floor" "floor" {
      + id           = (known after apply)
      + item         = (known after apply)
      + last_updated = (known after apply)

      + parameters {
          + type = "floor"

          + site {
              + floor {
                  + floor_number = (known after apply)
                  + height       = (known after apply)
                  + length       = (known after apply)
**                  + name         = "F03"
                  + parent_name  = "Global/EMEA/Spain/Madrid/Site-1/B02"**
                  + rf_model     = (known after apply)
                  + width        = (known after apply)
                }
            }
        }
    }

Solution

  • as Marko said, keys in a map must be unique. If you think how a map works, the purpose is to be able to use a key to look up a value in a map. If the key existed more than once then the map wouldnt know which value to return. So for that reason all key names must be unique. We can simplify your issue using a local var an an output.

    Given that you are not using the key its self in the resource other than to form a unique name for the resource you can use a combination of the floor and building

    locals {
    
      floors = [
        {
          name                 = "F02"
          parent_name          = "Site-1"
          parent_building_name = "B01"
        },
        {
          name                 = "F03"
          parent_name          = "Site-1"
          parent_building_name = "B01"
        },
        {
          name                 = "F04"
          parent_name          = "Site-1"
          parent_building_name = "B01"
        },
        {
          name                 = "F03"
          parent_name          = "Site-1"
          parent_building_name = "B02"
        },
      ]
      floors_map = { for floor in local.floors : "${floor.name}-${floor.parent_building_name}" => floor }
    }
    
    output "floors" {
      value = local.floors_map
    }
    

    OUTPUT

    
    Outputs:
    
    floors = {
      "F02-B01" = {
        "name" = "F02"
        "parent_building_name" = "B01"
        "parent_name" = "Site-1"
      }
      "F03-B01" = {
        "name" = "F03"
        "parent_building_name" = "B01"
        "parent_name" = "Site-1"
      }
      "F03-B02" = {
        "name" = "F03"
        "parent_building_name" = "B02"
        "parent_name" = "Site-1"
      }
      "F04-B01" = {
        "name" = "F04"
        "parent_building_name" = "B01"
        "parent_name" = "Site-1"
      }
    }