Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ’™ Cisco ACI Access Switch Policy Group Terraform Module

Manage a Cisco ACI access (leaf) switch policy group (class infraAccNodePGrp, DN uni/infra/funcprof/accnodepgrp-{name}) and its companion spine switch policy groups (class infraSpineAccNodePGrp, DN uni/infra/funcprof/spaccnodepgrp-{name}) as a typed, secure-by-default building block targeting CiscoDevNet/aci ~> 2.20.

Terraform Provider Module Version Type Resources

🧩 Overview

This module manages leaf and spine switch policy groups β€” the node-level policy bundles fabric access-policy selectors apply to switches β€” as one coherent, secure-by-default unit:

  • πŸ–§ The access switch policy group (aci_access_switch_policy_group.this) β€” the keystone: a reusable bundle of leaf-node policies (BFD, Fibre Channel, inbound ACL profile, CoPP, CDP/LLDP, monitoring, MST, NetFlow, PoE, forwarding-scale) addressed by the Distinguished Name uni/infra/funcprof/accnodepgrp-{name}.
  • 🌐 Companion spine switch policy groups (aci_spine_switch_policy_group.this) β€” the spine-node counterpart, managed with for_each over a keyed map since it is an independent object, not a child, of the keystone.
  • 🏷️ The ACI metadata tail β€” annotation (preserved as orchestrator:terraform so Terraform-managed objects are identifiable in APIC), name_alias, and description on every managed object.
  • πŸ”— Flat, classic policy relations β€” every relation_infra_rs_* attribute the live provider schema exposes for both resources, surfaced as descriptively named, independently settable DN inputs.
  • πŸ”‘ Scope, not credentials β€” both resources are fabric access-policy singletons with no parent DN; authentication and the APIC URL are the caller's provider concern and are never module variables.

πŸ’‘ Why it matters: a switch policy group is the reusable unit that a leaf or spine switch profile's node-block selector applies to physical switches. Bundling node-level policies here β€” rather than scattering them across individual switch configurations β€” keeps fabric access policy auditable, versioned, and consistent across the fabric.

❀️ Support this project

If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:

Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!

πŸ—ΊοΈ Where this fits in the family

graph LR
  apic["Cisco APIC fabric (provider auth, out of band)"]:::ext
  aspg["terraform-aci-access-switch-policy-group (this module)"]:::this
  fv["aci_access_switch_policy_group - class infraAccNodePGrp - DN uni/infra/funcprof/accnodepgrp-{name}"]:::keystone
  sp["aci_spine_switch_policy_group - class infraSpineAccNodePGrp - DN uni/infra/funcprof/spaccnodepgrp-{name}"]:::keystone
  lsp["terraform-aci-leaf-switch-profile"]:::sib
  ssp["terraform-aci-spine-switch-profile"]:::sib
  cdp["terraform-aci-cdp-interface-policy"]:::sib
  lldp["terraform-aci-lldp-interface-policy"]:::sib
  copp["terraform-aci-copp-interface-policy"]:::sib
  mon["terraform-aci-monitoring-policy"]:::sib

  apic -->|"provider configured by caller"| aspg
  aspg -->|"manages"| fv
  aspg -->|"manages"| sp
  fv -->|"id (selected by node-block)"| lsp
  sp -->|"id (selected by node-block)"| ssp
  cdp -->|"relation_to_cdp_interface_policy"| aspg
  lldp -->|"relation_to_lldp_interface_policy"| aspg
  copp -->|"relation_to_leaf_copp_profile / relation_to_spine_copp_profile"| aspg
  mon -->|"relation_to_monitoring_policy"| aspg

  classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
  classDef keystone fill:#0D274D,color:#fff,stroke:#0D274D;
  classDef sib fill:#f5f5f5,color:#333,stroke:#cccccc;
  classDef ext fill:#eeeeff,color:#333,stroke:#9999ff;
Loading

Both objects are fabric access-policy singletons living directly under the fixed uni/infra/funcprof container. They take no parent DN and emit their own DNs for leaf/spine switch profile node-block selectors to bind; the individual node policies they reference (CDP, LLDP, CoPP, monitoring, and so on) are owned by their own sibling modules and consumed here by DN.

🧬 What this module builds

graph TD
  n["access_switch_policy_group.name (required, immutable)"]:::in
  meta["annotation / name_alias / description"]:::in
  rel["18 relation_to_* policy relation DNs"]:::in
  this["aci_access_switch_policy_group.this (keystone, infraAccNodePGrp)"]:::this
  oid["output: id (DN uni/infra/funcprof/accnodepgrp-{name})"]:::out
  onm["output: name"]:::out

  sn["spine_switch_policy_groups (map, keyed by name)"]:::in
  srel["6 relation_to_* policy relation DNs per entry"]:::in
  spine["aci_spine_switch_policy_group.this[for_each] (infraSpineAccNodePGrp)"]:::this
  osp["output: spine_switch_policy_group_dns (map)"]:::out

  n --> this
  meta --> this
  rel --> this
  this --> oid
  this --> onm

  sn --> spine
  srel --> spine
  spine --> osp

  classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
  classDef in fill:#f5f5f5,color:#333,stroke:#cccccc;
  classDef out fill:#eeeeff,color:#333,stroke:#9999ff;
Loading

Resource inventory

Resource Name Cardinality Role
aci_access_switch_policy_group this 1 (keystone) The leaf-node switch policy group (infraAccNodePGrp).
aci_spine_switch_policy_group this 0..N (for_each) Companion spine-node switch policy groups (infraSpineAccNodePGrp), keyed by name.

βœ… Provider / Versions

Requirement Value
Terraform >= 1.3.0 (uses optional() object defaults)
Provider CiscoDevNet/aci ~> 2.20
Provider block None in this module β€” the caller configures and authenticates the provider (username/password, X.509 signature, or login domain) out of band.
Scope None β€” both resources are fabric access-policy objects living directly under the fixed uni/infra/funcprof container (no parent DN).

Schema notes that bite (verified against the live provider schema):

  • πŸ”’ name is expected immutable on both resources (it is a DN component). The provider's documentation for the sibling aci_leaf_access_bundle_policy_group resource states explicitly that its name cannot be changed after creation; treat a rename here the same way, as a replacement.
  • ℹ️ Both resources are classic (SDKv2) in this provider line: every policy relation is a flat relation_infra_rs_* attribute (a single target DN string) β€” there is no typed relation_to_* nested object and no protocol-v6 migration banner for either resource.
  • ⚠️ The two resources are siblings, not parent-child. A spine switch policy group's DN (uni/infra/funcprof/spaccnodepgrp-{name}) does not nest under an access switch policy group's DN (uni/infra/funcprof/accnodepgrp-{name}) β€” both sit directly under uni/infra/funcprof. spine_switch_policy_groups is a for_each collection wired independently of the keystone's id, never through it.
  • ℹ️ Most relations are optional,computed. Passing null leaves a relation at the provider's default rather than forcing an empty value.
  • ⚠️ validate_relation_dn (provider default true) fails apply if any referenced policy DN (BFD, CoPP, CDP/LLDP, monitoring, MST, NetFlow, PoE, forwarding-scale, inbound control profile) does not exist β€” fix the missing object rather than disabling validation.
  • ℹ️ Neither policy group has any effect on its own. Each only takes effect once selected by a leaf or spine switch profile's node-block selector.

πŸ”‘ Required APIC Roles & Privileges

  • Create / modify either policy group: the admin role, or a custom role with fabric access-policy (infra) write privilege β€” access policies are fabric-scoped, not tenant-scoped, so this typically requires a broader security-domain grant than a tenant-admin role.
  • Referenced node policies (BFD, Fibre Channel, CoPP, CDP/LLDP, monitoring, MST, NetFlow, PoE, forwarding-scale, inbound control profiles): read, scoped to the fabric access-policy security domain.

The module never sees a credential β€” authentication is a provider/caller concern supplied out of band (e.g. ACI_USERNAME / ACI_PASSWORD, or ACI_PRIVATE_KEY / ACI_CERT_NAME for signature-based auth).

Cisco ACI Prerequisites

  • A reachable Cisco APIC (ACI_URL) whose version is compatible with the ~> 2.20 provider, with the provider configured and authenticated by the caller.
  • In production, the provider should be configured with insecure = false and proper CA trust β€” the provider's own default (insecure = true, skip TLS verification) is not a safe steady state.
  • Any node policy referenced by a relation must exist (or be created in the same apply) so the provider's DN validation passes.
  • A leaf switch profile (to select the access switch policy group) and/or a spine switch profile (to select a spine switch policy group) β€” neither policy group takes effect until a node-block selector references it.

πŸ“ Module Structure

terraform-aci-access-switch-policy-group/
β”œβ”€β”€ providers.tf     # terraform{} + required_providers (aci ~> 2.20); no provider block
β”œβ”€β”€ variables.tf     # access_switch_policy_group object + spine_switch_policy_groups map β€” typed, secure defaults
β”œβ”€β”€ main.tf          # aci_access_switch_policy_group.this (keystone) + aci_spine_switch_policy_group.this[for_each]
β”œβ”€β”€ outputs.tf       # id (the DN) first, then name, then the spine DN map
β”œβ”€β”€ README.md        # this document
β”œβ”€β”€ SCOPE.md         # cross-module contract (scope, consumes/emits, roles, prerequisites)
β”œβ”€β”€ LICENSE          # MIT
└── .gitignore       # canonical library ignore set

βš™οΈ Quick Start

# The caller configures the provider (authentication is out of band).
provider "aci" {
  # username / password, or private_key + cert_name for signature auth;
  # url = "https://apic.example.com"; set insecure = false in production.
}

module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name = "leaf-standard"
  }
}

output "access_switch_policy_group_dn" {
  value = module.access_switch_pg.id # select this from a leaf switch profile node-block
}

πŸ”Œ Cross-Module Contract

Consumes

Input Type Typical source
access_switch_policy_group object({...}) caller (name + metadata tail + policy relation DNs)
spine_switch_policy_groups map(object({...})) caller (keyed by name; metadata tail + policy relation DNs)

Emits

Output Description Consumed by
id Access switch policy group DN (uni/infra/funcprof/accnodepgrp-{name}) β€” primary reference leaf switch profile node-block selectors
name Access switch policy group name composition / audit
spine_switch_policy_group_dns Map of spine switch policy group key (name) β†’ DN spine switch profile node-block selectors

πŸ“š Example Library

1 Β· Minimal β€” an access switch policy group with secure defaults
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name = "leaf-standard"
  }
}

πŸ’‘ The minimal call creates only the access switch policy group. No node policy is force-attached, and annotation is preserved as orchestrator:terraform, so the object is identifiable as Terraform-managed in APIC.

2 Β· Description and GUI alias
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name        = "leaf-standard"
    name_alias  = "Standard Leaf Group"
    description = "Standard leaf node policy bundle for production racks"
  }
}

ℹ️ name_alias is a display alias shown in the APIC GUI; name remains the immutable identity encoded in the DN.

3 Β· Binding CDP and LLDP interface policies
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name                          = "leaf-standard"
    relation_to_cdp_interface_policy  = "uni/infra/cdpIfP-cdp-enabled"
    relation_to_lldp_interface_policy = "uni/infra/lldpIfP-lldp-enabled"
  }
}

⚠️ Each relation takes the target policy's DN. With the provider's validate_relation_dn default of true, a dangling DN fails at apply β€” create the referenced policy first, or wire it from a sibling module's id.

4 Β· Binding a CoPP leaf profile and a monitoring policy
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name                       = "leaf-standard"
    relation_to_leaf_copp_profile = "uni/infra/coppprof-leaf-strict"
    relation_to_monitoring_policy = "uni/fabric/moninfra-default"
  }
}

ℹ️ relation_to_leaf_copp_profile protects the leaf CPU from control-plane traffic floods; relation_to_monitoring_policy attaches fabric-wide monitoring/faults/stats policy to this node group.

5 Β· Binding BFD instance policies (single-hop and multihop)
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name                              = "leaf-standard"
    relation_to_bfd_ipv4_policy          = "uni/tn-infra/bfdIpv4InstPol-default"
    relation_to_bfd_multihop_ipv4_policy = "uni/tn-infra/bfdMhIpv4InstPol-default"
  }
}

ℹ️ Single-hop and multihop BFD instance policies are independent relations β€” set only the ones relevant to the leaf's routing design.

6 Β· Binding Fibre Channel fabric and instance policies
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name                    = "fcoe-leaf-pg"
    relation_to_fc_fabric_policy   = "uni/infra/fcfabricpol-default"
    relation_to_fc_instance_policy = "uni/infra/fcinstpol-default"
  }
}

ℹ️ Relevant only to leaf switches carrying FCoE/native Fibre Channel uplinks.

7 Β· Binding the inbound control (ACL) leaf profile
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name                                    = "leaf-standard"
    relation_to_inbound_control_leaf_profile = "uni/tn-infra/laclprof-leaf-baseline"
  }
}

πŸ”’ The inbound (ingress) control ACL profile restricts control-plane-directed traffic to the leaf's CPU β€” a hardening measure worth pairing with relation_to_leaf_copp_profile.

8 Β· Binding NetFlow, PoE, and forwarding-scale policies
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name                                        = "leaf-standard"
    relation_to_netflow_node_policy                = "uni/infra/netflownodepol-default"
    relation_to_poe_instance_policy                = "uni/infra/poeInstP-default"
    relation_to_forwarding_scale_profile_policy    = "uni/fabric/topoctrlfwdscaleprofpol-default"
  }
}

ℹ️ relation_to_poe_instance_policy only applies to PoE-capable leaf hardware; relation_to_forwarding_scale_profile_policy controls hardware forwarding-table scale profiles.

9 Β· A single companion spine switch policy group
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name = "leaf-standard"
  }

  spine_switch_policy_groups = {
    "spine-standard" = {
      description = "Standard spine node policy bundle"
    }
  }
}

πŸ’‘ spine_switch_policy_groups is keyed by name; the map key becomes the spine switch policy group's name. Leave it empty (the default) to manage only the leaf-side keystone.

10 Β· Multiple spine switch policy groups with policy relations
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name = "leaf-standard"
  }

  spine_switch_policy_groups = {
    "spine-standard" = {
      relation_to_spine_copp_profile   = "uni/infra/coppprof-spine-strict"
      relation_to_cdp_interface_policy = "uni/infra/cdpIfP-cdp-enabled"
    }
    "spine-strict" = {
      relation_to_inbound_control_spine_profile = "uni/tn-infra/laclprof-spine-strict"
      relation_to_bfd_ipv4_policy                 = "uni/tn-infra/bfdIpv4InstPol-default"
    }
  }
}

ℹ️ Each map entry is an independent aci_spine_switch_policy_group, iterated with for_each β€” adding or removing an entry never churns the others.

11 Β· A fully-annotated access switch policy group (metadata + several relations)
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name                          = "leaf-standard"
    name_alias                    = "Standard Leaf Group"
    description                   = "Standard leaf node policy bundle for production racks"
    relation_to_cdp_interface_policy = "uni/infra/cdpIfP-cdp-enabled"
    relation_to_lldp_interface_policy = "uni/infra/lldpIfP-lldp-enabled"
    relation_to_leaf_copp_profile    = "uni/infra/coppprof-leaf-strict"
    relation_to_monitoring_policy    = "uni/fabric/moninfra-default"
  }
}
12 Β· Least-privilege operating model (documentation variant)
# Configure the provider with a login scoped to fabric access-policy
# management only β€” not a fabric-wide admin β€” for day-2 changes.
provider "aci" {
  # username    = "svc-fabric-access-admin"  # write-scoped to the fabric
  #                                          # access-policy security domain
  # private_key = var.apic_private_key        # signature auth avoids login-rate limits
  # cert_name   = "terraform-cert"
  # url         = "https://apic.example.com"
  # insecure    = false
}

module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"
  access_switch_policy_group = { name = "leaf-standard" }
}

πŸ”’ Fabric access-policy objects require a broader grant than a tenant-scoped role. Prefer signature-based (X.509) auth for automation to avoid APIC login-rate thresholds.

13 Β· Many policy groups from one definition (caller-side for_each)
locals {
  leaf_groups = {
    "leaf-standard" = { name = "leaf-standard" }
    "leaf-fcoe"     = { name = "leaf-fcoe", description = "FCoE-capable leaf racks" }
  }
}

module "access_switch_pgs" {
  source   = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"
  for_each = local.leaf_groups

  access_switch_policy_group = each.value
}

output "access_switch_policy_group_dns" {
  value = { for k, m in module.access_switch_pgs : k => m.id }
}

πŸ’‘ Instantiate the module with for_each to manage a fleet of access switch policy groups from a single, auditable map. Each instance can still carry its own spine_switch_policy_groups.

14 Β· Reading outputs for downstream wiring
module "access_switch_pg" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = { name = "leaf-standard" }
  spine_switch_policy_groups = {
    "spine-standard" = {}
  }
}

output "access_switch_policy_group_dn" { value = module.access_switch_pg.id }
output "access_switch_policy_group_name" { value = module.access_switch_pg.name }
output "spine_switch_policy_group_dns" { value = module.access_switch_pg.spine_switch_policy_group_dns }
15 Β· πŸ—οΈ End-to-end composition β€” policy modules β†’ switch policy groups β†’ switch profiles
provider "aci" {
  # configured + authenticated by the caller; insecure = false in production
}

# 1) Node-level policies referenced by the switch policy groups.
module "cdp_policy" {
  source          = "git::https://github.com/microsoftexpert/terraform-aci-cdp-interface-policy.git?ref=v1.0.0"
  cdp_interface_policy = { name = "cdp-enabled", admin_state = true }
}

module "lldp_policy" {
  source           = "git::https://github.com/microsoftexpert/terraform-aci-lldp-interface-policy.git?ref=v1.0.0"
  lldp_interface_policy = { name = "lldp-enabled" }
}

module "monitoring_policy" {
  source            = "git::https://github.com/microsoftexpert/terraform-aci-monitoring-policy.git?ref=v1.0.0"
  monitoring_policy = { name = "fabric-default" }
}

# 2) The access switch policy group + companion spine switch policy group,
#    wiring the sibling policies above by DN.
module "switch_policy_groups" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"

  access_switch_policy_group = {
    name                             = "leaf-standard"
    relation_to_cdp_interface_policy  = module.cdp_policy.id
    relation_to_lldp_interface_policy = module.lldp_policy.id
    relation_to_monitoring_policy     = module.monitoring_policy.id
  }

  spine_switch_policy_groups = {
    "spine-standard" = {
      relation_to_cdp_interface_policy  = module.cdp_policy.id
      relation_to_lldp_interface_policy = module.lldp_policy.id
    }
  }
}

# 3) Leaf/spine switch profiles select the policy groups by DN onto real switches.
module "leaf_switch_profile" {
  source                        = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"
  leaf_switch_profile           = { name = "leaf-profile-101-102" }
  access_switch_policy_group_dn = module.switch_policy_groups.id
}

module "spine_switch_profile" {
  source                       = "git::https://github.com/microsoftexpert/terraform-aci-spine-switch-profile.git?ref=v1.0.0"
  spine_switch_profile         = { name = "spine-profile-201-202" }
  spine_switch_policy_group_dn = module.switch_policy_groups.spine_switch_policy_group_dns["spine-standard"]
}

output "access_switch_policy_group_dn" { value = module.switch_policy_groups.id }

πŸ—οΈ Node-level policies in; leaf and spine switch profiles bind their respective node-block selectors to the policy groups this module manages β€” completing the fabric access-policy chain from individual policies to switches in the field.

πŸ“₯ Inputs

Name Type Required Default Description
access_switch_policy_group object({...}) βœ… β€” The access switch policy group: name (required, immutable) plus the metadata tail and the 18 classic policy relations.
spine_switch_policy_groups map(object({...})) ❌ {} Companion spine switch policy groups, keyed by name. Empty manages none.
Full input schema (from variables.tf)
variable "access_switch_policy_group" {
  type = object({
    name        = string                                     # REQUIRED, expected immutable (DN component), 1-64 chars
    annotation  = optional(string, "orchestrator:terraform") # ACI annotation marker (kept identifiable)
    name_alias  = optional(string, null)                     # GUI display alias
    description = optional(string, null)                     # free-form description

    relation_to_bfd_ipv4_policy                 = optional(string, null) # -> bfdIpv4InstPol DN
    relation_to_bfd_ipv6_policy                 = optional(string, null) # -> bfdIpv6InstPol DN
    relation_to_bfd_multihop_ipv4_policy        = optional(string, null) # -> bfdMhIpv4InstPol DN
    relation_to_bfd_multihop_ipv6_policy        = optional(string, null) # -> bfdMhIpv6InstPol DN
    relation_to_flash_config_policy             = optional(string, null) # -> equipmentFlashConfigPol DN
    relation_to_fc_fabric_policy                = optional(string, null) # -> fcFabricPol DN
    relation_to_fc_instance_policy              = optional(string, null) # -> fcInstPol DN
    relation_to_inbound_control_leaf_profile    = optional(string, null) # -> iaclLeafProfile DN
    relation_to_l2_node_auth_policy             = optional(string, null) # -> l2NodeAuthPol DN
    relation_to_leaf_copp_profile               = optional(string, null) # -> coppLeafProfile DN
    relation_to_cdp_interface_policy            = optional(string, null) # -> cdpIfPol DN
    relation_to_lldp_interface_policy           = optional(string, null) # -> lldpIfPol DN
    relation_to_monitoring_policy               = optional(string, null) # -> monInfraPol DN
    relation_to_stp_instance_policy             = optional(string, null) # -> stpInstPol DN
    relation_to_netflow_node_policy             = optional(string, null) # -> netflowNodePol DN
    relation_to_poe_instance_policy             = optional(string, null) # -> poeInstPol DN
    relation_to_fast_link_failover_policy       = optional(string, null) # -> topoctrlFastLinkFailoverInstPol DN
    relation_to_forwarding_scale_profile_policy = optional(string, null) # -> topoctrlFwdScaleProfilePol DN
  })
  # validation: name is 1-64 chars and matches ^[a-zA-Z0-9_.:-]+$ (ACI naming rules)
}

variable "spine_switch_policy_groups" {
  type = map(object({
    annotation  = optional(string, "orchestrator:terraform")
    name_alias  = optional(string, null)
    description = optional(string, null)

    relation_to_inbound_control_spine_profile = optional(string, null) # -> iaclSpineProfile DN
    relation_to_bfd_ipv4_policy                = optional(string, null) # -> bfdIpv4InstPol DN
    relation_to_bfd_ipv6_policy                = optional(string, null) # -> bfdIpv6InstPol DN
    relation_to_spine_copp_profile              = optional(string, null) # -> coppSpineProfile DN
    relation_to_cdp_interface_policy            = optional(string, null) # -> cdpIfPol DN
    relation_to_lldp_interface_policy           = optional(string, null) # -> lldpIfPol DN
  }))
  default = {}
  # validation: every map key (name) is 1-64 chars and matches ^[a-zA-Z0-9_.:-]+$
}

🧾 Outputs

Output Description Notes
id Access switch policy group Distinguished Name (uni/infra/funcprof/accnodepgrp-{name}) Primary cross-module reference.
name Access switch policy group name For composition / audit.
spine_switch_policy_group_dns Map of spine switch policy group key (name) β†’ DN Empty map if spine_switch_policy_groups is empty.

🧠 Architecture Notes

  • One keystone, one independent sibling collection. aci_access_switch_policy_group.this is the single keystone resource. aci_spine_switch_policy_group.this is iterated with for_each alongside it β€” deliberately not wired through the keystone's id, because the two objects share no DN nesting in the ACI object model. Modeling it as a fabricated parent-child relationship would misrepresent the schema.
  • Classic (SDKv2) flat relations. Neither resource has been migrated to the plugin-framework in this provider line; every policy relation is a flat relation_infra_rs_* string attribute (a target DN), surfaced here under a descriptive relation_to_* name rather than the raw provider attribute name.
  • for_each key stability. spine_switch_policy_groups is keyed by policy group name β€” the map key becomes name in main.tf (name = each.key), so the map has no redundant name field and inserting/removing an entry never churns the others.
  • Immutable identity. name on both resources is expected immutable (a DN component); the validation blocks reject names that violate the ACI length/character rules at plan time, not apply time.
  • Secure by omission. The minimal call attaches no policy relations on either resource β€” nothing is force-applied to leaf or spine ports by default, and the orchestrator:terraform annotation is preserved on every managed object.

🧱 Design Principles

Concern Secure default How to opt out (deliberately)
access_switch_policy_group.annotation / each spine entry's annotation orchestrator:terraform β€” Terraform-managed objects stay identifiable in APIC Extend the marker (e.g. add a team suffix); do not blank it.
Every relation_to_* (both resources) null β€” no policy relation forced Set the relevant relation to the target policy's DN.
spine_switch_policy_groups {} β€” no spine switch policy groups managed Add entries keyed by name.
Transport (provider) This suite instructs callers to set insecure = false with CA trust The provider default is insecure = true; do not keep it as a steady state.
Secrets None accepted or emitted n/a β€” neither resource carries secret material; credentials are provider config.

πŸš€ Runbook

# From the module directory (offline, no credentials, no backend):
terraform init -backend=false
terraform validate
terraform fmt -check
  • Pin the module by immutable tag: ?ref=v1.0.0 β€” never a branch.
  • This module is plan-only from the library's perspective. A human runs terraform plan / apply against a sub-production APIC from their own pipeline, with a login scoped to the permissions above. No cloud apply happens here.

πŸ§ͺ Testing

The offline proof gate for this module:

  • βœ… terraform validate β€” parses the module, resolves the access_switch_policy_group object and spine_switch_policy_groups map types, runs every name validation, and confirms every argument exists in the provider schema.
  • βœ… terraform fmt -check β€” canonical formatting.
  • β›” Not exercised offline (only a real plan / apply against an APIC covers these): DN validation of every relation_to_* (server-side validate_relation_dn), APIC-side name-collision checks, the computed DNs returned as id / spine_switch_policy_group_dns, and whether either policy group is actually selected by a switch profile node block.

πŸ’¬ Example Output

$ terraform output
id                             = "uni/infra/funcprof/accnodepgrp-leaf-standard"
name                           = "leaf-standard"
spine_switch_policy_group_dns  = {
  "spine-standard" = "uni/infra/funcprof/spaccnodepgrp-spine-standard"
}

πŸ” Troubleshooting

Symptom Cause Fix
access_switch_policy_group.name must be 1-64 characters Name is empty or too long Use a 1-64 character name.
access_switch_policy_group.name may contain only letters, digits, and the characters _ . : - Name has spaces or unsupported characters Remove spaces/special characters (ACI naming rules).
Every spine_switch_policy_groups map key ... must be 1-64 characters A spine_switch_policy_groups key violates the naming rule Use a 1-64 character key matching ^[a-zA-Z0-9_.:-]+$.
Changing name wants to destroy/recreate a policy group name is a DN component and expected immutable Treat a rename as a migration; expect the policy group (and its selection by a switch profile) to be replaced.
Apply fails validating a relation_to_* DN The referenced policy (BFD, CoPP, CDP/LLDP, monitoring, MST, NetFlow, PoE, forwarding-scale, inbound control profile) does not exist Create the referenced policy first (or in the same apply); do not disable validate_relation_dn.
Policy group exists but has no visible effect Neither policy group takes effect until selected Bind the policy group's id (or the relevant spine_switch_policy_group_dns entry) from a leaf/spine switch profile node-block selector.
Post ... 401 / authentication error Provider not configured or wrong credentials Configure the aci provider with valid credentials and url; prefer signature auth for automation.
TLS verification error against the APIC insecure = false (correct) but no CA trust Install the APIC's CA chain in the caller's trust store rather than reverting to insecure = true.

πŸ”— Related Docs

  • Cisco ACI provider β€” aci_access_switch_policy_group
  • Cisco ACI provider β€” aci_spine_switch_policy_group
  • Cisco ACI provider β€” provider configuration & authentication
  • Cisco APIC object model β€” classes infraAccNodePGrp (leaf switch policy group) and infraSpineAccNodePGrp (spine switch policy group).
  • Sibling modules: terraform-aci-leaf-switch-profile, terraform-aci-spine-switch-profile, terraform-aci-cdp-interface-policy, terraform-aci-lldp-interface-policy, terraform-aci-copp-interface-policy, terraform-aci-monitoring-policy, terraform-aci-leaf-access-port-policy-group.
  • This module's SCOPE.md β€” the cross-module contract.

πŸ’™ "Infrastructure as Code should be standardized, consistent, and secure."

Releases

Packages

Contributors

Languages