Skip to content

Terraform Resource Ordering Limitations

Terraform Update-in-Place and Deletion Ordering in Cisco Catalyst Center

Section titled “Terraform Update-in-Place and Deletion Ordering in Cisco Catalyst Center”

Applies to: Catalyst Center Provider >= 0.5.2, NAC Module >= 0.4.0

  • Site hierarchy and device deletion scenarios apply when use_bulk_api = true.
  • SSID / network profile scenario applies regardless of use_bulk_api.

This document describes Terraform resource ordering limitations in Cisco Catalyst Center that can cause terraform apply to fail during deletions. All scenarios share a common root cause: Terraform’s depends_on enforces a single ordering for CREATE, UPDATE, and DESTROY operations, and only reverses that order when all dependent resources are explicitly destroyed.

This behavior is most commonly observed when a user attempts to:

  • Partially remove hierarchical site entries managed via bulk resources in a single Terraform apply (use_bulk_api = true).
  • Delete a device that still has a fabric role assigned (use_bulk_api = true).
  • Delete a wireless SSID that is still mapped to a network profile (applies in all configurations).

When use_bulk_api = true, the Cisco Catalyst Center module manages sites using bulk resources:

  • catalystcenter_areas.bulk_areas
  • catalystcenter_buildings.bulk_buildings
  • catalystcenter_floors.bulk_floors

Each resource contains a map of all items at that hierarchy level. Removing an item from the configuration triggers an update-in-place operation on the parent resource, not an explicit destroy.


Terraform treats update-in-place operations with the same dependency ordering as CREATE, not DESTROY.

Operation TypeExecution Order
CreateAreas → Buildings → Floors
Update in placeAreas → Buildings → Floors
DestroyFloors → Buildings → Areas (reversed)

When removing site hierarchy elements, Terraform sees “update the Areas list” and processes it before Buildings and Floors, even though Catalyst Center requires children to be deleted first.


Module Resource Definitions for catalystcenter_areas, catalystcenter_buildings and catalystcenter_floors

Section titled “Module Resource Definitions for catalystcenter_areas, catalystcenter_buildings and catalystcenter_floors”

The module defines explicit depends_on relationships that enforce creation order:

# Areas have no site dependencies (created first)
resource "catalystcenter_areas" "bulk_areas" {
...
...
depends_on = [catalystcenter_discovery.discovery, ...]
}
# Buildings depend on Areas
resource "catalystcenter_buildings" "bulk_buildings" {
...
...
depends_on = [catalystcenter_areas.bulk_areas, ...] # <-- Buildings wait for Areas
}
# Floors depend on Buildings
resource "catalystcenter_floors" "bulk_floors" {
...
...
depends_on = [catalystcenter_building.building, catalystcenter_buildings.bulk_buildings, ...] # <-- Floors wait for Buildings
}

Key point: The depends_on relationships form a chain: Areas → Buildings → Floors. This ensures correct creation order, but during update-in-place operations, this same order is followed, which is against Catalyst Center’s deletion requirements.


sites.nac.yaml
catalyst_center:
sites:
areas:
- name: Global
- name: France
parent_name: Global
- name: Paris
parent_name: Global/France
- name: Poland
parent_name: Global
- name: Krakow
parent_name: Global/Poland
buildings:
- name: Paris_Bld_A
parent_name: Global/France/Paris
latitude: 48.8575
longitude: 2.3514
country: France
- name: Paris_Bld_B
parent_name: Global/France/Paris
latitude: 48.8575
longitude: 2.3514
country: France
- name: KRK_Bld_A
latitude: 50.0623225
longitude: 19.937975
country: Poland
parent_name: Global/Poland/Krakow
floors:
- name: Floor_1
floor_number: 1
parent_name: Global/France/Paris/Paris_Bld_A
- name: FLOOR_1
floor_number: 1
parent_name: Global/Poland/Krakow/KRK_Bld_A

This creates the following hierarchy in Catalyst Center:

Global
├── France (Area)
│ └── Paris (Area)
│ ├── Paris_Bld_A (Building)
│ │ └── Floor_1 (Floor)
│ └── Paris_Bld_B (Building)
└── Poland (Area)
└── Krakow (Area)
└── KRK_Bld_A (Building)
└── FLOOR_1 (Floor)

When removing France from the configuration while Poland/Krakow remains, Terraform sees:

  • The catalystcenter_areas.bulk_areas resource still exists (Poland areas remain in the map)
  • The catalystcenter_buildings.bulk_buildings resource still exists (KRK_Bld_A remains in the map)
  • The catalystcenter_floors.bulk_floors resource still exists (FLOOR_1 remains in the map)

Because items remain in each map, Terraform classifies this as update-in-place, not destroy. The bulk resources continue to exist; only their contents change (France entries removed, Poland entries unchanged).

If you were removing all sites (emptying the maps entirely), Terraform would destroy the bulk resources, which would trigger proper DESTROY ordering (Floors → Buildings → Areas). But partial removal from a map is always an update-in-place operation.


Goal: Remove the entire France site hierarchy in one operation.

Action: Remove all France-related entries from sites.nac.yaml and run terraform apply.

Terraform Plan Output:

# module.catalyst_center.catalystcenter_areas.bulk_areas[0] will be updated in-place
~ resource "catalystcenter_areas" "bulk_areas" {
~ areas = {
- "Global/France" = {
- id = "4d359396-59b8-4931-ac80-274cf6b3db65" -> null
- name = "France" -> null
- parent_name_hierarchy = "Global" -> null
}
- "Global/France/Paris" = {
- id = "c565b323-b3dc-4a67-ae95-fd34ce20f96d" -> null
- name = "Paris" -> null
- parent_name_hierarchy = "Global/France" -> null
}
}
}
# module.catalyst_center.catalystcenter_buildings.bulk_buildings[0] will be updated in-place
~ resource "catalystcenter_buildings" "bulk_buildings" {
~ buildings = {
- "Global/France/Paris/Paris_Bld_A" = {
- id = "53855e96-25aa-436a-95a2-56a7ab8c71ab" -> null
- name = "Paris_Bld_A" -> null
- parent_name_hierarchy = "Global/France/Paris" -> null
}
- "Global/France/Paris/Paris_Bld_B" = {
- id = "9b7550f4-f2ad-43bb-9834-3fad9e0e0b45" -> null
- name = "Paris_Bld_B" -> null
- parent_name_hierarchy = "Global/France/Paris" -> null
}
}
}
Plan: 0 to add, 4 to change, 0 to destroy.

Result: Terraform attempts to update Areas first (following CREATE order), but Catalyst Center rejects the request:

Error: Client Error
with module.catalyst_center.catalystcenter_areas.bulk_areas[0],
on .terraform/modules/catalyst_center/cc_sites.tf line 12, in resource "catalystcenter_areas" "bulk_areas":
12: resource "catalystcenter_areas" "bulk_areas" {
Failed to delete object (DELETE), got error: task failed:
"NCGR10012: Group cannot be deleted as there are child groups. Please delete them first."

Why it fails: Terraform processed the Areas update before Buildings and Floors because update-in-place follows CREATE ordering (Areas → Buildings → Floors), not DESTROY ordering (Floors → Buildings → Areas).


To align Terraform’s execution with Catalyst Center’s hierarchical constraints, deletion must be performed in multiple applies.

Edit sites.nac.yaml to remove only the France floor entries (Poland floors remain):

catalyst_center:
sites:
areas:
- name: Global
- name: France
parent_name: Global
- name: Paris
parent_name: Global/France
- name: Poland
parent_name: Global
- name: Krakow
parent_name: Global/Poland
buildings:
- name: Paris_Bld_A
parent_name: Global/France/Paris
latitude: 48.8575
longitude: 2.3514
country: France
- name: Paris_Bld_B
parent_name: Global/France/Paris
latitude: 48.8575
longitude: 2.3514
country: France
- name: KRK_Bld_A
latitude: 50.0623225
longitude: 19.937975
country: Poland
parent_name: Global/Poland/Krakow
floors:
# France Floor_1 removed
- name: FLOOR_1
floor_number: 1
parent_name: Global/Poland/Krakow/KRK_Bld_A

Run terraform apply. Only the Floors resource is updated (France floor removed), Areas and Buildings remain unchanged.

Edit sites.nac.yaml to remove France building entries (Poland buildings remain):

catalyst_center:
sites:
areas:
- name: Global
- name: France
parent_name: Global
- name: Paris
parent_name: Global/France
- name: Poland
parent_name: Global
- name: Krakow
parent_name: Global/Poland
buildings:
# France buildings removed
- name: KRK_Bld_A
latitude: 50.0623225
longitude: 19.937975
country: Poland
parent_name: Global/Poland/Krakow
floors:
- name: FLOOR_1
floor_number: 1
parent_name: Global/Poland/Krakow/KRK_Bld_A

Run terraform apply. France buildings are removed while Areas and Poland hierarchy remain.

Edit sites.nac.yaml to remove France area entries (Poland areas remain):

catalyst_center:
sites:
areas:
- name: Global
# France and Paris areas removed
- name: Poland
parent_name: Global
- name: Krakow
parent_name: Global/Poland
buildings:
- name: KRK_Bld_A
latitude: 50.0623225
longitude: 19.937975
country: Poland
parent_name: Global/Poland/Krakow
floors:
- name: FLOOR_1
floor_number: 1
parent_name: Global/Poland/Krakow/KRK_Bld_A

Run terraform apply. France areas are now safely removed. Poland hierarchy remains intact.


Terraform categorizes operations as:

  • Create: New resource
  • Update in place: Existing resource, modify attributes
  • Replace: Destroy + Create (ForceNew)
  • Destroy: Remove resource entirely

Removing items from a bulk resource’s map is an update-in-place, the resource continues to exist, just with fewer items. Terraform only reverses dependency order for explicit Destroy operations.

depends_on chain:
catalystcenter_areas.bulk_areas
└── catalystcenter_buildings.bulk_buildings
└── catalystcenter_floors.bulk_floors

For CREATE: Areas → Buildings → Floors (correct) For UPDATE: Areas → Buildings → Floors (problematic for deletions) For DESTROY: Floors → Buildings → Areas (would be correct, but not triggered)


Device Deletion: Fabric Role Must Be Removed First

Section titled “Device Deletion: Fabric Role Must Be Removed First”

The same update-in-place dependency issue affects device deletion when using use_bulk_api = true. When a device with fabric roles needs to be deleted (state changed to INIT), the fabric role must be removed in a separate apply before the device state is changed.

Module Resource Definitions for provision_devices and fabric_devices

Section titled “Module Resource Definitions for provision_devices and fabric_devices”

The module defines explicit depends_on relationships that enforce creation order:

# provision_devices (created first)
resource "catalystcenter_provision_devices" "provision_devices" {
...
...
depends_on = [catalystcenter_assign_device_to_site.devices_to_site, ...]
}
# Fabric devices depend on Provision devices
resource "catalystcenter_fabric_devices" "fabric_devices" {
...
...
depends_on = [catalystcenter_device_role.role, catalystcenter_provision_devices.provision_devices, catalystcenter_provision_device.provision_device, ...]
# <-- fabric_devices wait for provision_devices
}

When use_bulk_api = true, the following resources are managed as bulk collections:

  • catalystcenter_provision_devices — manages device provisioning state per site
  • catalystcenter_fabric_devices — manages fabric roles per fabric site
  • catalystcenter_fabric_port_assignments — manages port assignments per device
  • catalystcenter_device_role — manages device roles

Removing a device from the YAML configuration triggers update-in-place operations on these bulk resources rather than explicit destroys. Terraform processes these updates in CREATE order, meaning the device provisioning state removal (catalystcenter_provision_devices) will execute before the fabric role removal (catalystcenter_fabric_devices) completes.

Catalyst Center requires the fabric role to be removed from a device before the device can be deleted. If Terraform attempts to delete the device while the fabric role is still assigned, the operation fails.

Concrete Example: Removing an Edge Node Device

Section titled “Concrete Example: Removing an Edge Node Device”
devices.nac.yaml
catalyst_center:
network_devices:
- name: EDGE01.cisco.eu
fqdn_name: EDGE01.cisco.eu
device_ip: 198.18.130.1
pid: C9KV-UADP-8P
serial_number: CML12322UAD
state: PROVISION
device_role: ACCESS
site: Global/Poland/Krakow/Bld A
fabric_site: Global/Poland/Krakow
fabric_roles:
- EDGE_NODE
- name: EDGE02.cisco.eu
fqdn_name: EDGE02.cisco.eu
device_ip: 198.18.130.2
pid: C9KV-UADP-8P
serial_number: CML12322UAD
state: PROVISION
device_role: ACCESS
site: Global/Poland/Krakow/Bld A
fabric_site: Global/Poland/Krakow
fabric_roles:
- EDGE_NODE

Goal: Remove EDGE01.cisco.eu by removing it from the configuration (or changing its state to INIT).

Action: Remove the EDGE01 device entry from the YAML and run terraform apply.

Terraform Plan Output:

Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
~ update in-place
- destroy
Terraform will perform the following actions:
# module.catalyst_center.catalystcenter_device_role.role["EDGE01.cisco.eu"] will be destroyed
# (because key ["EDGE01.cisco.eu"] is not in for_each map)
- resource "catalystcenter_device_role" "role" {
- device_id = "7ef492ca-b008-479a-9de4-7e40438c7d10" -> null
- id = "7ef492ca-b008-479a-9de4-7e40438c7d10" -> null
- role = "ACCESS" -> null
- role_source = "MANUAL" -> null
}
# module.catalyst_center.catalystcenter_fabric_devices.fabric_devices["Global/Poland/Krakow"]
# will be updated in-place
~ resource "catalystcenter_fabric_devices" "fabric_devices" {
~ fabric_devices = [
- {
- device_roles = [
- "EDGE_NODE",
] -> null
- fabric_id = "f86c78b2-4b63-46c3-a62f-059cd56919bf" -> null
- id = "83826e71-48a8-41f1-a55b-dfeafbe89d1f" -> null
- network_device_id = "7ef492ca-b008-479a-9de4-7e40438c7d10" -> null
},
# (3 unchanged elements hidden)
]
id = "f86c78b2-4b63-46c3-a62f-059cd56919bf"
# (1 unchanged attribute hidden)
}
# module.catalyst_center.catalystcenter_fabric_port_assignments.port_assignments["EDGE01.cisco.eu"]
# will be destroyed
# (because key ["EDGE01.cisco.eu"] is not in for_each map)
- resource "catalystcenter_fabric_port_assignments" "port_assignments" {
- fabric_id = "f86c78b2-4b63-46c3-a62f-059cd56919bf" -> null
- id = "7ef492ca-b008-479a-9de4-7e40438c7d10" -> null
- network_device_id = "7ef492ca-b008-479a-9de4-7e40438c7d10" -> null
- port_assignments = [
- {
- authenticate_template_name = "No Authentication" -> null
- connected_device_type = "USER_DEVICE" -> null
- data_vlan_name = "CampusVN_VLAN" -> null
- fabric_id = "f86c78b2-4b63-46c3-a62f-059cd56919bf" -> null
- id = "0547a38c-f760-4b94-bbaf-a67ed63b9513" -> null
- interface_name = "GigabitEthernet1/0/2" -> null
- network_device_id = "7ef492ca-b008-479a-9de4-7e40438c7d10" -> null
},
] -> null
}
# module.catalyst_center.catalystcenter_provision_devices.provision_devices["Global/Poland/Krakow/Bld A"]
# will be updated in-place
~ resource "catalystcenter_provision_devices" "provision_devices" {
id = "9065de7e-57ee-4f8f-83b3-327fc8c0cacb"
~ provision_devices = [
- {
- id = "83826e71-48a8-41f1-a55b-dfeafbe89d1f" -> null
- network_device_id = "7ef492ca-b008-479a-9de4-7e40438c7d10" -> null
- reprovision = false -> null
- site_id = "9065de7e-57ee-4f8f-83b3-327fc8c0cacb" -> null
},
# (2 unchanged elements hidden)
]
# (1 unchanged attribute hidden)
}
Plan: 0 to add, 2 to change, 2 to destroy.

Notice the critical detail: both catalystcenter_fabric_devices (fabric role removal) and catalystcenter_provision_devices (device provisioning state removal) are both update-in-place operations. Because Terraform applies update-in-place changes in CREATE order, catalystcenter_provision_devices is processed first.

Terraform Apply Output:

module.catalyst_center.catalystcenter_fabric_port_assignments.port_assignments["EDGE01.cisco.eu"]: Destroying...
module.catalyst_center.catalystcenter_fabric_port_assignments.port_assignments["EDGE01.cisco.eu"]: Still destroying... [10s elapsed]
module.catalyst_center.catalystcenter_fabric_port_assignments.port_assignments["EDGE01.cisco.eu"]: Destruction complete after 13s
module.catalyst_center.catalystcenter_device_role.role["EDGE01.cisco.eu"]: Destroying...
module.catalyst_center.catalystcenter_device_role.role["EDGE01.cisco.eu"]: Destruction complete after 0s
module.catalyst_center.catalystcenter_provision_devices.provision_devices["Global/Poland/Krakow/Bld A"]: Modifying...
│ Error: Client Error
│ with module.catalyst_center.catalystcenter_provision_devices.provision_devices["Global/Poland/Krakow/Bld A"],
│ on .terraform/modules/catalyst_center/cc_device_provision.tf line 243,
│ in resource "catalystcenter_provision_devices" "provision_devices":
│ 243: resource "catalystcenter_provision_devices" "provision_devices" {
│ Failed to delete object (DELETE), got error: task '019cb2c6-c465-7375-b7f4-ddf102fed734' failed:
│ Network device deletion failed, NCIM10000: Error while deleting device 198.18.130.1.

Why it fails: The apply log shows that Terraform began modifying catalystcenter_provision_devices (which triggers the device deletion in Catalyst Center) before catalystcenter_fabric_devices had a chance to remove the fabric role. Catalyst Center cannot delete a device that still has an active fabric role assigned to it.

The execution order was:

  1. catalystcenter_fabric_port_assignments — destroyed (explicit destroy, correct)
  2. catalystcenter_device_role — destroyed (explicit destroy, correct)
  3. catalystcenter_provision_devices — updated in-place (attempted device deletion) — failed
  4. catalystcenter_fabric_devices — updated in-place (fabric role removal) — never executed

Because both catalystcenter_provision_devices and catalystcenter_fabric_devices are update-in-place operations, Terraform does not enforce that the fabric role removal happens before the device state change.

To properly delete a device when using use_bulk_api = true, perform the deletion in two separate terraform apply runs.

Edit the device configuration to remove only the fabric_roles and fabric_site entries. Keep the device in PROVISION state:

devices.nac.yaml
catalyst_center:
network_devices:
- name: EDGE01.cisco.eu
fqdn_name: EDGE01.cisco.eu
device_ip: 198.18.130.1
pid: C9KV-UADP-8P
serial_number: CML12322UAD
state: PROVISION
device_role: ACCESS
site: Global/Poland/Krakow/Bld A
# fabric_site and fabric_roles removed
- name: EDGE02.cisco.eu
fqdn_name: EDGE02.cisco.eu
device_ip: 198.18.130.2
pid: C9KV-UADP-8P
serial_number: CML12322UAD
state: PROVISION
device_role: ACCESS
site: Global/Poland/Krakow/Bld A
fabric_site: Global/Poland/Krakow
fabric_roles:
- EDGE_NODE

Run terraform apply. This removes the fabric role and port assignments for EDGE01 while keeping the device in provisioned state. Catalyst Center will remove the device from the fabric.

Now that the fabric role has been removed, change the device state to INIT to trigger deletion:

devices.nac.yaml
catalyst_center:
network_devices:
- name: EDGE01.cisco.eu
fqdn_name: EDGE01.cisco.eu
device_ip: 198.18.130.1
pid: C9KV-UADP-8P
serial_number: CML12322UAD
state: INIT # Changed from PROVISION to INIT
device_role: ACCESS
site: Global/Poland/Krakow/Bld A
- name: EDGE02.cisco.eu
fqdn_name: EDGE02.cisco.eu
device_ip: 198.18.130.2
pid: C9KV-UADP-8P
serial_number: CML12322UAD
state: PROVISION
device_role: ACCESS
site: Global/Poland/Krakow/Bld A
fabric_site: Global/Poland/Krakow
fabric_roles:
- EDGE_NODE

Run terraform apply. The device is now safely deleted from Catalyst Center since no fabric role is attached.


SSID Deletion: Network Profile Mapping Must Be Removed First

Section titled “SSID Deletion: Network Profile Mapping Must Be Removed First”

The same dependency-ordering problem affects wireless SSID deletion when an SSID is still mapped to a wireless network profile. Unlike the site and device scenarios above, this scenario is not specific to use_bulk_api — it occurs in all configurations because it stems from Terraform’s general depends_on semantics, not from bulk-resource update-in-place behavior.

When an SSID is removed from the configuration while still referenced by a network profile, Terraform attempts to destroy the SSID resource before updating the network profile to remove the mapping. Catalyst Center rejects this with HTTP 400 NCND00049 because an SSID cannot be deleted while it remains mapped to a network profile.

Module Resource Definitions for wireless_ssid and wireless_profile

Section titled “Module Resource Definitions for wireless_ssid and wireless_profile”

The module defines an explicit depends_on relationship that enforces creation order:

# wireless_ssid (created first)
resource "catalystcenter_wireless_ssid" "ssid" {
...
...
}
# wireless_profile depends on wireless_ssid
resource "catalystcenter_wireless_profile" "wireless_profile" {
...
...
depends_on = [catalystcenter_wireless_ssid.ssid, ...] # <-- Profile waits for SSIDs
}

Terraform’s depends_on is a coarse-grained ordering rule: when wireless_profile depends_on wireless_ssid, every operation on wireless_profile runs after every operation on wireless_ssid, regardless of operation type (CREATE, UPDATE, DESTROY).

When deleting an SSID from the configuration:

  • catalystcenter_wireless_ssid.ssid["<name>"] is marked for DESTROY (key removed from for_each map).
  • catalystcenter_wireless_profile.wireless_profile["<name>"] is marked for UPDATE-in-place (the ssid_details list shrinks).

Because the profile depends on the SSID, Terraform runs the SSID DESTROY before the profile UPDATE. Catalyst Center rejects the DELETE because the SSID is still mapped to the profile at that moment.

Terraform only reverses dependency order when all dependent resources are being explicitly destroyed. Here, only the SSID is destroyed (the profile is updated), so the ordering is not reversed.

Concrete Example: Removing an SSID Mapped to a Network Profile

Section titled “Concrete Example: Removing an SSID Mapped to a Network Profile”
wireless.nac.yaml
catalyst_center:
network_profiles:
wireless:
- name: REPRO_Profile
sites:
- Global/Poland/Krakow
ssid_details:
- name: REPRO_SSID_1
enable_fabric: false
interface_name: GigabitEthernet0/3
local_to_vlan: 100
- name: REPRO_SSID_2
enable_fabric: false
interface_name: GigabitEthernet0/3
local_to_vlan: 100
wireless:
ssids:
- name: REPRO_SSID_1
wlan_type: Enterprise
ssid_radio_type: "5GHz only"
enabled: true
broadcast_ssid: true
auth_type: WPA2_WPA3_ENTERPRISE
rsn_cipher_suite_ccmp128: true
auth_key8021x: true
auth_servers: ["198.18.133.27"]
- name: REPRO_SSID_2
wlan_type: Enterprise
ssid_radio_type: "5GHz only"
enabled: true
broadcast_ssid: true
auth_type: WPA2_WPA3_ENTERPRISE
rsn_cipher_suite_ccmp128: true
auth_key8021x: true
auth_servers: ["198.18.133.27"]

Goal: Remove REPRO_SSID_2 from both the wireless.ssids list and the network profile’s ssid_details.

Action: Remove the REPRO_SSID_2 entries from both sections and run terraform apply.

Terraform Plan Output:

Terraform used the selected providers to generate the following execution plan.
Resource actions are indicated with the following symbols:
~ update in-place
- destroy
Terraform will perform the following actions:
# module.catalyst_center.catalystcenter_wireless_profile.wireless_profile["REPRO_Profile"]
# will be updated in-place
~ resource "catalystcenter_wireless_profile" "wireless_profile" {
id = "b479233b-20b1-4bec-866b-2d93ea1aa99f"
~ ssid_details = [
- {
- enable_fabric = false -> null
- interface_name = "GigabitEthernet0/3" -> null
- ssid_name = "REPRO_SSID_2" -> null
},
# (1 unchanged element hidden)
]
# (1 unchanged attribute hidden)
}
# module.catalyst_center.catalystcenter_wireless_ssid.ssid["REPRO_SSID_2"] will be destroyed
# (because key ["REPRO_SSID_2"] is not in for_each map)
- resource "catalystcenter_wireless_ssid" "ssid" {
- auth_key8021x = true -> null
- auth_servers = [
- "198.18.133.27",
] -> null
- auth_type = "WPA2_WPA3_ENTERPRISE" -> null
- broadcast_ssid = true -> null
- enabled = true -> null
- id = "mdeYuKvq-uK9F-u1nj-rf8Y-uJbfmvaYuJnpnf81uZztn0K4rdLF" -> null
- rsn_cipher_suite_ccmp128 = true -> null
- ssid = "REPRO_SSID_2" -> null
- wlan_type = "Enterprise" -> null
}
Plan: 0 to add, 1 to change, 1 to destroy.

Terraform Apply Output:

module.catalyst_center.catalystcenter_wireless_ssid.ssid["REPRO_SSID_2"]: Destroying...
[id=mdeYuKvq-uK9F-u1nj-rf8Y-uJbfmvaYuJnpnf81uZztn0K4rdLF]
│ Error: Client Error
│ Failed to delete object (DELETE), got error: HTTP Request failed: StatusCode 400,
│ {"response":{"errorCode":"NCND00049","message":"NCND00049: Invalid request.
│ Please check the request parameters and resubmit","detail":"SSID is mapped to
│ Network Profile. Please remove the Network Profile mapping for SSID:
│ REPRO_SSID_2"},"version":"1.0"}

Why it fails: Because catalystcenter_wireless_profile depends_on catalystcenter_wireless_ssid, Terraform runs the SSID DESTROY before the network profile UPDATE. Catalyst Center rejects the SSID delete because the network profile still references it.

To properly delete an SSID that is mapped to a network profile, perform the operation in two separate terraform apply runs.

Step 1: Remove the SSID Reference from the Network Profile

Section titled “Step 1: Remove the SSID Reference from the Network Profile”

Edit the configuration to remove only the SSID reference from the network profile’s ssid_details. Keep the SSID entry in the wireless.ssids list intact:

wireless.nac.yaml
catalyst_center:
network_profiles:
wireless:
- name: REPRO_Profile
sites:
- Global/Poland/Krakow
ssid_details:
- name: REPRO_SSID_1
enable_fabric: false
interface_name: GigabitEthernet0/3
local_to_vlan: 100
# REPRO_SSID_2 reference removed from network profile
wireless:
ssids:
- name: REPRO_SSID_1
# ...
- name: REPRO_SSID_2 # SSID definition still present
# ...

Run terraform apply. The network profile is updated in-place to remove the REPRO_SSID_2 reference. The SSID itself remains intact in Catalyst Center.

Now that the network profile no longer references the SSID, remove the SSID definition from the wireless.ssids list:

wireless.nac.yaml
catalyst_center:
network_profiles:
wireless:
- name: REPRO_Profile
sites:
- Global/Poland/Krakow
ssid_details:
- name: REPRO_SSID_1
enable_fabric: false
interface_name: GigabitEthernet0/3
local_to_vlan: 100
wireless:
ssids:
- name: REPRO_SSID_1
# ...
# REPRO_SSID_2 definition removed

Run terraform apply. The SSID is now safely destroyed since no network profile references it.


  1. Terraform’s depends_on enforces a single ordering for CREATE, UPDATE, and DESTROY — only when all dependent resources are explicitly destroyed does Terraform reverse that order.
  2. Update-in-place follows CREATE ordering, not DESTROY ordering.
  3. Bulk resources (maps of items) treat partial item removal as an update-in-place.
  4. Catalyst Center enforces hierarchy: children must be deleted before parents.
  5. Multi-step deletion is required when partially removing entire site hierarchies (use_bulk_api = true).
  6. Fabric roles must be removed before device deletion: when using use_bulk_api = true, remove the fabric role in a separate apply before changing the device state to INIT.
  7. SSIDs must be unmapped from network profiles before deletion (applies in all configurations, with or without use_bulk_api): remove the SSID reference from the network profile in a separate apply before deleting the SSID definition.
  8. Site and device scenarios apply to Provider >= 0.5.2, Module >= 0.4.0 with use_bulk_api = true; the SSID / network profile scenario applies to all configurations on the same provider/module baseline.

Terraform’s depends_on enforces a single ordering for all operations; only when ALL dependent resources are explicitly destroyed does Terraform reverse it. When deletion mixes UPDATE-in-place with destroy (or with another update), CREATE order is preserved — requiring multi-step applies to satisfy Catalyst Center’s deletion constraints.

This behavior may also surface on other resources whenever a deletion plan mixes UPDATE-in-place with explicit DESTROY (or two UPDATE-in-place operations on bulk maps). When troubleshooting terraform apply failures during deletion, check whether the failing operation is preceded (or should be preceded) by a dependent UPDATE — and if so, split the change into multiple applies.