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)
}
}
}
}
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"
}
}