Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

💙 Cisco ACI Leaf Switch Profile Terraform Module

Manage a Cisco ACI leaf (switch) profile — the fabric access-policy object (class infraNodeP, DN uni/infra/nprof-{name}) that groups the leaf selectors mapping physical switches to switch policy groups — together with its leaf selectors and node blocks, as a typed, secure-by-default building block targeting CiscoDevNet/aci ~> 2.20.

Terraform Provider Module Version Type Resources

🧩 Overview

This module manages an ACI leaf (switch) profile together with its leaf selectors and their nested node blocks as one coherent, secure-by-default unit:

  • 🗄️ The leaf profile (aci_leaf_profile.this) — the fabric access-policy object, addressed by the Distinguished Name uni/infra/nprof-{name}, that groups the leaf selectors covering a set of fabric leaf switches.
  • 🎯 Leaf selectors (switch associations) (aci_leaf_selector.this) — one per entry in a typed leaf_selectors map, each declaring how its member nodes are matched (ALL, range, or ALL_IN_POD) and, optionally, the switch policy group that configures them.
  • 🔢 Node blocks (node-ID ranges) (aci_node_block.this) — nested under each selector, flattened into a stable for_each map so a fabric's node membership (e.g. leaf IDs 101-104) is declared per selector without churning the rest of the set.
  • 🔗 Classic flat relationsrelation_infra_rs_acc_port_p / relation_infra_rs_acc_card_p on the keystone bind the profile to leaf interface / access-card profiles by DN; relation_infra_rs_acc_node_p_grp on each selector binds it to a switch policy group by DN.
  • 🏷️ The ACI metadata tailannotation (preserved as orchestrator:terraform), name_alias, and description on every object this module manages.
  • 🔑 Scope, not credentials — the leaf profile is a fabric access-policy object with no parent DN; authentication and the APIC URL are the caller's provider concern and are never module variables.

💡 Why it matters: the leaf profile is how ACI answers "which physical switches does this policy apply to?" Getting node-ID ranges and switch-policy-group bindings right — and keeping them declarative, typed, and individually addressable — is what lets interface and switch policy actually reach the correct hardware.

❤️ 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
  lsp["terraform-aci-leaf-switch-profile (this module)"]:::this
  infra["aci_leaf_profile - class infraNodeP - DN uni/infra/nprof-{name}"]:::keystone
  spg["terraform-aci-access-switch-policy-group"]:::sib
  lip["terraform-aci-leaf-interface-profile"]:::sib
  ipg["leaf interface policy group modules"]:::sib

  apic -->|"provider configured by caller"| lsp
  lsp -->|"manages"| infra
  spg -->|"switch policy group DN via relation_to_switch_policy_group_dn"| lsp
  lip -->|"leaf interface profile DN via relation_to_leaf_interface_profile_dns"| lsp
  ipg -->|"interface selectors resolved through the interface profile"| lip

  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

The leaf profile is the node-membership half of the access-policy model: it declares which leaf switches (by node-ID range or ALL/ALL_IN_POD) a switch policy group applies to. It takes no parent DN — it composes directly under uni/infra — and consumes a switch policy group DN and, optionally, leaf interface / access-card profile DNs from sibling modules by reference.

🧬 What this module builds

graph TD
  n["leaf_profile.name (required, immutable)"]:::in
  meta["annotation / name_alias / description"]:::in
  rel["relation_to_leaf_interface_profile_dns / relation_to_access_card_profile_dns"]:::in
  this["aci_leaf_profile.this (keystone, infraNodeP)"]:::this
  sel_in["leaf_selectors (typed map)"]:::in
  sel["aci_leaf_selector.this (for_each, infraLeafS)"]:::child
  blk_in["leaf_selectors[*].node_blocks (nested map)"]:::in
  blk["aci_node_block.this (for_each, infraNodeBlk)"]:::child
  oid["output: id (DN uni/infra/nprof-{name})"]:::out
  onm["output: name"]:::out
  osel["output: leaf_selector_dns"]:::out
  oblk["output: node_block_dns"]:::out

  n --> this
  meta --> this
  rel --> this
  this --> oid
  this --> onm
  sel_in --> sel
  this --> sel
  sel --> osel
  blk_in --> blk
  sel --> blk
  blk --> oblk

  classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
  classDef child 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_leaf_profile this 1 (keystone) The leaf profile (infraNodeP), with its classic flat interface/access-card-profile relations.
aci_leaf_selector this 0-N (for_each) Leaf selectors / switch associations (infraLeafS) under the profile.
aci_node_block this 0-N (for_each, flattened) Node-ID range blocks (infraNodeBlk) nested under each selector.

✅ 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 — the leaf profile is a fabric access-policy object under uni/infra (no parent DN).

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

  • 🔒 leaf_profile.name is immutable. Changing it forces replacement — a brand-new profile, and every selector and node block nested under its DN goes with it.
  • ℹ️ All three resources are classic (SDKv2) — none is migrated to the plugin-framework in this provider line. There is no typed relation_to_* alternative for any relation here: relation_infra_rs_acc_port_p / relation_infra_rs_acc_card_p on the keystone are flat set(string) DN lists, and relation_infra_rs_acc_node_p_grp on each selector is a flat string DN.
  • ⚠️ The keystone also exposes inline leaf_selector { node_block { ... } } blocks as an alternative shape for the identical child objects this module manages as dedicated aci_leaf_selector / aci_node_block resources. This module manages selectors and node blocks only through the dedicated child resources — do not add inline leaf_selector blocks to an aci_leaf_profile.this this module manages, or the two will fight over the same child objects.
  • ℹ️ switch_association_type is a closed enumALL, range, ALL_IN_POD — confirmed against the live provider's own validator. It also encodes into the selector's DN (.../leaves-{selector}-typ-{type}), so changing it changes the selector's identity.
  • ℹ️ node_block.from_ / to_ are provider strings, not numbers, holding a node-ID range (1-16000, default "1"); this module validates the numeric range at plan time.
  • ℹ️ Node blocks are nested under leaf_selectors[*].node_blocks, not a separate top-level variable — this avoids any dangling cross-map reference and mirrors the ACI hierarchy (a node block is meaningless without its owning selector). The module flattens the nesting internally into a stable "{selector_key}.{block_key}" for_each key.
  • ℹ️ id is the DN (uni/infra/nprof-{name}), computed by APIC at create.
  • ⚠️ validate_relation_dn (provider default true) means relation_to_switch_policy_group_dn or any keystone relation pointing at a DN that does not yet exist will fail at apply — create the target first, or wire it by reference.

🔑 Required APIC Roles & Privileges

Scope the caller's APIC login to the least privilege this module needs:

  • Create / modify a leaf profile, its selectors, and node blocks: the access-admin role (or a custom role with fabric access-policy write privilege), typically scoped to the all security domain — access policies are fabric-wide constructs.
  • Referenced switch policy group: read on the group targeted by relation_to_switch_policy_group_dn.
  • Referenced leaf interface / access-card profiles: read on any profile targeted by the keystone's flat relations.

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 switch policy group referenced by relation_to_switch_policy_group_dn must exist (or be created in the same configuration) so the provider's DN validation passes; likewise for any referenced leaf interface / access-card profiles.
  • No parent object is required — the leaf profile is created directly under uni/infra.
  • The physical leaf switches a range or ALL_IN_POD selector's node blocks reference are already discovered and registered in the fabric — this module associates already-known node IDs with policy; it does not perform fabric membership/registration.

📁 Module Structure

terraform-aci-leaf-switch-profile/
├── providers.tf     # terraform{} + required_providers (aci ~> 2.20); no provider block
├── variables.tf     # leaf_profile + leaf_selectors (with nested node_blocks) — typed, secure defaults
├── main.tf          # aci_leaf_profile.this (keystone) + aci_leaf_selector.this + aci_node_block.this
├── outputs.tf       # id (the DN) first, then name, leaf_selector_dns, node_block_dns
├── 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 "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name        = "leaf-101-104"
    description = "Leaf profile for compute-rack switches 101-104"
  }

  leaf_selectors = {
    "compute-rack" = {
      switch_association_type = "range"
      node_blocks = {
        "101-104" = { from_ = "101", to_ = "104" }
      }
    }
  }
}

output "leaf_profile_dn" {
  value = module.leaf_switch_profile.id
}

🔌 Cross-Module Contract

Consumes

Input Type Typical source
leaf_profile object({...}) caller (name + metadata tail + classic flat relations, by DN)
leaf_selectors map(object({...})) caller (selector name + type + switch-policy-group DN + nested node_blocks)

Emits

Output Description Consumed by
id Leaf profile DN (uni/infra/nprof-{name}) — primary reference downstream access-policy composition, audits
name Leaf profile name composition / audit
leaf_selector_dns Map of selector key (name) → selector DN audits / downstream reference
node_block_dns Map of node block key ("{selector_key}.{block_key}") → node block DN audits / downstream reference

📚 Example Library

1 · Minimal — a leaf profile with secure defaults
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-101-104"
  }
}

💡 The minimal call creates only the leaf profile. annotation is preserved as orchestrator:terraform, no selectors are created, and no relation is forced — nothing permissive by default.

2 · Description and GUI alias
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name        = "leaf-101-104"
    name_alias  = "Compute Rack Leafs"
    description = "Covers leaf switches 101-104 in the compute rack"
  }
}

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

3 · A custom annotation marker
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name       = "leaf-101-104"
    annotation = "orchestrator:terraform:platform-team"
  }
}

🔒 Keep the orchestrator:terraform prefix so Terraform-managed objects stay identifiable in APIC. This suite defaults annotation to orchestrator:terraform; override it only to extend, not to erase, that marker.

4 · A single leaf selector with a node-ID range
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-101-104"
  }

  leaf_selectors = {
    "compute-rack" = {
      switch_association_type = "range"
      node_blocks = {
        "101-104" = { from_ = "101", to_ = "104" }
      }
    }
  }
}

💡 leaf_selectors is keyed by selector name; each selector's node_blocks is a nested map keyed by block name, so the selector's name and each block's name are both derived from their map keys.

5 · Multiple disjoint node-ID ranges under one selector
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-storage"
  }

  leaf_selectors = {
    "storage-nodes" = {
      switch_association_type = "range"
      node_blocks = {
        "block-a" = { from_ = "201", to_ = "202" }
        "block-b" = { from_ = "210", to_ = "212" }
      }
    }
  }
}

ℹ️ A single selector can cover several non-contiguous node-ID ranges by adding more entries to its node_blocks map.

6 · Multiple leaf selectors in one profile
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-dc1"
  }

  leaf_selectors = {
    "compute-rack" = {
      switch_association_type = "range"
      node_blocks             = { "101-104" = { from_ = "101", to_ = "104" } }
    }
    "storage-rack" = {
      switch_association_type = "range"
      node_blocks             = { "201-202" = { from_ = "201", to_ = "202" } }
    }
  }
}

💡 Each map entry is an independently addressable aci_leaf_selector.this[...] resource — adding or removing a selector never churns the others.

7 · Selecting every leaf switch in a pod (ALL_IN_POD)
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-pod1-all"
  }

  leaf_selectors = {
    "pod1-all-leafs" = {
      switch_association_type = "ALL_IN_POD"
    }
  }
}

⚠️ ALL_IN_POD matches every leaf switch in the pod without an explicit node-ID range — node_blocks may be omitted. Prefer explicit range selectors for tightly-scoped policy; use ALL_IN_POD deliberately for fabric-wide policy.

8 · Selecting every leaf switch in the fabric (ALL)
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-fabric-wide"
  }

  leaf_selectors = {
    "all-leafs" = {
      switch_association_type = "ALL"
    }
  }
}

⚠️ ALL is the broadest possible match — every leaf switch in the fabric. Reserve it for genuinely fabric-wide policy (e.g. a baseline monitoring or NTP switch policy group); prefer range for anything workload-specific.

9 · Binding a selector to a switch policy group
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-101-104"
  }

  leaf_selectors = {
    "compute-rack" = {
      switch_association_type           = "range"
      relation_to_switch_policy_group_dn = "uni/infra/funcprof/accnodepgrp-compute-leaf-pg"
      node_blocks = {
        "101-104" = { from_ = "101", to_ = "104" }
      }
    }
  }
}

⚠️ The referenced switch policy group must exist (or be created in the same apply). With the provider's validate_relation_dn default of true, a dangling relation fails at apply — fix the missing group rather than disabling validation.

10 · Wiring a switch-policy-group module's DN into a selector
module "access_switch_policy_group" {
  source                     = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"
  access_switch_policy_group = { name = "compute-leaf-pg" }
}

module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-101-104"
  }

  leaf_selectors = {
    "compute-rack" = {
      switch_association_type           = "range"
      relation_to_switch_policy_group_dn = module.access_switch_policy_group.id
      node_blocks = {
        "101-104" = { from_ = "101", to_ = "104" }
      }
    }
  }
}

💡 Reference a sibling switch-policy-group module's id output directly rather than hardcoding the DN.

11 · Binding the profile to a leaf interface profile
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-101-104"
    relation_to_leaf_interface_profile_dns = [
      "uni/infra/accportprof-compute-leaf-ifprof"
    ]
  }

  leaf_selectors = {
    "compute-rack" = {
      switch_association_type = "range"
      node_blocks              = { "101-104" = { from_ = "101", to_ = "104" } }
    }
  }
}

ℹ️ relation_to_leaf_interface_profile_dns binds the profile to one or more leaf interface profiles (infraAccPortP, class of terraform-aci-leaf-interface-profile) — this is the classic flat relation; there is no typed alternative on this resource.

12 · Fully-annotated selector and node block (all metadata)
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name        = "leaf-101-104"
    name_alias  = "Compute Rack Leafs"
    description = "Covers leaf switches 101-104 in the compute rack"
  }

  leaf_selectors = {
    "compute-rack" = {
      switch_association_type           = "range"
      name_alias                         = "Compute Rack Selector"
      description                        = "Nodes 101-104"
      relation_to_switch_policy_group_dn = "uni/infra/funcprof/accnodepgrp-compute-leaf-pg"
      node_blocks = {
        "101-104" = {
          from_       = "101"
          to_         = "104"
          name_alias  = "Compute rack block"
          description = "Contiguous range 101-104"
        }
      }
    }
  }
}
13 · Least-privilege operating model (documentation variant)
# Configure the provider with a login scoped to access-policy management only —
# not a fabric-wide admin — for day-2 changes to an existing leaf profile.
provider "aci" {
  # username    = "svc-access-admin"    # an access-admin role
  # private_key = var.apic_private_key  # signature auth avoids login-rate limits
  # cert_name   = "terraform-cert"
  # url         = "https://apic.example.com"
  # insecure    = false
}

module "leaf_switch_profile" {
  source       = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"
  leaf_profile = { name = "leaf-101-104" }
}

🔒 Access policies are fabric-scoped; scope automation logins to the access-admin role rather than a fabric-wide admin account. Prefer signature-based (X.509) auth for automation to avoid APIC login-rate thresholds.

14 · Reading outputs for downstream wiring
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = { name = "leaf-101-104" }
  leaf_selectors = {
    "compute-rack" = {
      switch_association_type = "range"
      node_blocks              = { "101-104" = { from_ = "101", to_ = "104" } }
    }
  }
}

output "leaf_profile_dn"    { value = module.leaf_switch_profile.id }                # uni/infra/nprof-leaf-101-104
output "leaf_selector_dns"  { value = module.leaf_switch_profile.leaf_selector_dns }
output "node_block_dns"     { value = module.leaf_switch_profile.node_block_dns }
15 · 🏗️ End-to-end composition — switch policy group + interface profile → leaf profile
provider "aci" {
  # configured + authenticated by the caller; insecure = false in production
}

# 1) A switch policy group that carries the switch-level policy (NTP, monitoring, etc.).
module "access_switch_policy_group" {
  source                     = "git::https://github.com/microsoftexpert/terraform-aci-access-switch-policy-group.git?ref=v1.0.0"
  access_switch_policy_group = { name = "compute-leaf-pg" }
}

# 2) A leaf interface profile carrying the interface selectors for these switches.
module "leaf_interface_profile" {
  source                 = "git::https://github.com/microsoftexpert/terraform-aci-leaf-interface-profile.git?ref=v1.0.0"
  leaf_interface_profile = { name = "compute-leaf-ifprof" }
}

# 3) The keystone leaf profile, binding both by DN and declaring the node-ID range
#    it covers via a range selector.
module "leaf_switch_profile" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-leaf-switch-profile.git?ref=v1.0.0"

  leaf_profile = {
    name = "leaf-101-104"
    relation_to_leaf_interface_profile_dns = [
      module.leaf_interface_profile.id
    ]
  }

  leaf_selectors = {
    "compute-rack" = {
      switch_association_type           = "range"
      relation_to_switch_policy_group_dn = module.access_switch_policy_group.id
      node_blocks = {
        "101-104" = { from_ = "101", to_ = "104" }
      }
    }
  }
}

output "leaf_profile_dn" { value = module.leaf_switch_profile.id }

🏗️ The leaf profile is where node membership (this module's leaf_selectors / node_blocks) meets switch policy (access-switch-policy-group) and interface policy (leaf-interface-profile) — every access-policy association APIC needs to configure these physical switches and their ports converges on this module's keystone DN.

📥 Inputs

Name Type Required Default Description
leaf_profile object({...}) The leaf profile: name (required, immutable) plus the metadata tail and the classic flat interface/access-card-profile relations.
leaf_selectors map(object({...})) {} Leaf selectors keyed by name, each with a nested node_blocks map keyed by block name. Empty creates no selectors.
Full input schema (from variables.tf)
variable "leaf_profile" {
  type = object({
    name        = string                                     # REQUIRED, immutable (force-new), 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_leaf_interface_profile_dns = optional(list(string), []) # classic flat relation infraRsAccPortP, by DN
    relation_to_access_card_profile_dns    = optional(list(string), []) # classic flat relation infraRsAccCardP, by DN
  })
  # validation: name is 1-64 chars, matches ^[a-zA-Z0-9_.:-]+$, and every
  # relation DN entry is non-empty
}

variable "leaf_selectors" {
  type = map(object({
    switch_association_type            = string                                     # REQUIRED. ALL | range | ALL_IN_POD
    annotation                          = optional(string, "orchestrator:terraform")
    name_alias                          = optional(string, null)
    description                         = optional(string, null)
    relation_to_switch_policy_group_dn  = optional(string, null)                      # classic flat relation infraRsAccNodePGrp, by DN

    node_blocks = optional(map(object({
      from_       = optional(string, "1")  # node-ID range start, 1-16000
      to_         = optional(string, "1")  # node-ID range end, 1-16000
      annotation  = optional(string, "orchestrator:terraform")
      name_alias  = optional(string, null)
      description = optional(string, null)
    })), {})
  }))
  default = {}
  # validation: switch_association_type is one of ALL, range, ALL_IN_POD;
  # every node_blocks[*].from_ / to_ is within 1-16000
}

🧾 Outputs

Output Description Notes
id Leaf profile Distinguished Name (uni/infra/nprof-{name}) Primary cross-module reference.
name Leaf profile name For composition / audit.
leaf_selector_dns Map of selector key (name) → selector DN Empty map when leaf_selectors is {}.
node_block_dns Map of node block key ("{selector_key}.{block_key}") → node block DN Empty map when no selector declares node_blocks.

🧠 Architecture Notes

  • One keystone, two levels of for_each children. aci_leaf_profile.this owns identity and the classic flat interface/access-card-profile relations; aci_leaf_selector.this iterates the top-level leaf_selectors map; aci_node_block.this iterates a local.node_blocks map flattened from each selector's nested node_blocks — never count at any level, so inserting or removing a selector or a single node block never churns the rest of the set.
  • Node blocks nest inside their selector in the input, not a parallel top-level map. This mirrors the ACI hierarchy (a node block cannot exist without its owning selector) and avoids needing a cross-map "does this key exist" validation that plain Terraform validation {} blocks cannot express reliably at this Terraform floor.
  • Classic (SDKv2) shape throughout. All three resources predate the plugin-framework migration: relations are flat relation_infra_rs_* attributes (a set(string) on the keystone, a single string on the selector), not typed relation_to_* nested objects. This module surfaces them as ergonomic DN lists/strings and renders them directly.
  • Dedicated child resources, not inline blocks. aci_leaf_profile's live schema also supports inline leaf_selector { node_block { ... } } blocks on the keystone itself; this module never uses them, so there is exactly one place that manages any given selector or node block.
  • Immutable identity. leaf_profile.name is force-new: a rename destroys and recreates the profile (and, server-side, everything nested under its DN). The validation blocks reject names that violate the ACI length/character rules, invalid switch_association_type values, and out-of-range node IDs at plan time, not apply time.
  • Secure by omission. The minimal call preserves the orchestrator:terraform annotation, creates no selectors, and forces no relations — nothing permissive is created by default.

🧱 Design Principles

Concern Secure default How to opt out (deliberately)
leaf_profile.annotation orchestrator:terraform — Terraform-managed objects stay identifiable in APIC Extend the marker (e.g. add a team suffix); do not blank it.
leaf_selectors {} — no selector created by default Add entries keyed by selector name.
relation_to_leaf_interface_profile_dns / relation_to_access_card_profile_dns [] — no interface/access-card profile bound Add DNs explicitly.
leaf_selectors[*].relation_to_switch_policy_group_dn null — no switch policy group bound Set it to bind a switch policy group by DN.
switch_association_type scope Caller must choose explicitly (ALL / range / ALL_IN_POD) — no implicit default toward the broadest match Choose range for tightly-scoped policy; reserve ALL / ALL_IN_POD for deliberate fabric-/pod-wide policy.
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 — the leaf profile carries no 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 leaf_profile and leaf_selectors object types (including the nested node_blocks map), runs the name/enum/range validations, 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 relation_to_switch_policy_group_dn and the keystone's flat relations (server-side validate_relation_dn), APIC-side name-collision and node-ID-conflict checks, and the computed DNs returned as id / leaf_selector_dns / node_block_dns.

💬 Example Output

$ terraform output
id                = "uni/infra/nprof-leaf-101-104"
name              = "leaf-101-104"
leaf_selector_dns = {
  "compute-rack" = "uni/infra/nprof-leaf-101-104/leaves-compute-rack-typ-range"
}
node_block_dns = {
  "compute-rack.101-104" = "uni/infra/nprof-leaf-101-104/leaves-compute-rack-typ-range/nodeblk-101-104"
}

🔍 Troubleshooting

Symptom Cause Fix
leaf_profile.name must be 1-64 characters Name is empty or too long Use a 1-64 character name.
leaf_profile.name may contain only letters, digits, and the characters _ . : - Name has spaces or unsupported characters Remove spaces/special characters (ACI naming rules).
Every leaf_selectors[*].switch_association_type must be one of: ALL, range, ALL_IN_POD An unsupported value was passed Use one of the three allowed values (confirmed against the live provider validator).
Every leaf_selectors[*].node_blocks[*].from_ must be between 1 and 16000 (or to_) A node-ID string outside the valid range Use a numeric string between "1" and "16000".
Changing leaf_profile.name wants to destroy/recreate the profile leaf_profile.name is immutable (force-new) Treat a rename as a migration; expect the profile and its selectors/node blocks to be replaced.
Plan shows a conflicting change to the same selector/node-block object Inline leaf_selector { node_block { ... } } blocks were added to aci_leaf_profile.this alongside this module's leaf_selectors Manage selectors/node blocks through leaf_selectors only; never add inline blocks to a profile this module manages.
Apply fails validating relation_to_switch_policy_group_dn The referenced switch policy group does not exist Create the switch policy group first (or in the same apply); do not disable validate_relation_dn.
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_leaf_profile
  • Cisco ACI provider — aci_leaf_selector
  • Cisco ACI provider — aci_node_block
  • Cisco ACI provider — provider configuration & authentication
  • Cisco APIC object model — class infraNodeP (the leaf profile), infraLeafS (leaf selector / switch association), and infraNodeBlk (node block).
  • Sibling modules: terraform-aci-access-switch-policy-group, terraform-aci-leaf-interface-profile, terraform-aci-spine-access-port-policy-group.
  • This module's SCOPE.md — the cross-module contract.

💙 "Infrastructure as Code should be standardized, consistent, and secure."

About

Terraform module: terraform-aci-leaf-switch-profile

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages