Skip to content

Latest commit

Β 

History

1 Commit

Folders and files

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

Repository files navigation

πŸ’™ Cisco ACI Any (VRF Contract Scope) Terraform Module

Manage a Cisco ACI VRF's Any contract-scope object and its consumer/provider/contract-interface relations (class vzAny, DN uni/tn-{tenant}/ctx-{vrf}/any) as one typed, secure-by-default building block targeting CiscoDevNet/aci ~> 2.20.

Terraform Provider Module Version Type Resources

🧩 Overview

This module manages a VRF's "Any" contract-scope object and the relation objects that bind it to contracts, as one coherent, secure-by-default unit:

  • 🌐 The Any object (aci_any.this) β€” vzAny represents every EPG in the VRF for contract-matching purposes; it is a VRF singleton addressed by the Distinguished Name uni/tn-{tenant}/ctx-{vrf}/any.
  • 🀝 Consumer-contract relations β€” aci_relation_from_any_to_consumer_contract.this, for_each over a typed consumed_contracts map, makes every EPG in the VRF a consumer of the named contract.
  • πŸ“€ Provider-contract relations β€” aci_relation_from_any_to_provider_contract.this, for_each over a typed provided_contracts map, makes every EPG in the VRF a provider of the named contract.
  • πŸ”Œ Contract-interface relations β€” aci_relation_from_any_to_contract_interface.this, for_each over a typed contract_interfaces map, consumes a contract imported from another tenant.
  • πŸ”’ Secure posture by default β€” match_criteria defaults to AtleastOne and preferred_group_member defaults to disabled, so EPGs are not silently admitted into the VRF's unenforced preferred group.

πŸ’‘ Why it matters: relations bound at the VRF's Any object apply fabric-wide across every EPG in the VRF, not to a single EPG β€” a materially larger blast radius than an EPG-scoped contract relation. Managing this scope as its own typed, reviewable unit keeps VRF-wide contract exposure deliberate and auditable rather than an incidental side effect of an EPG change.

❀️ 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
  tenant["terraform-aci-tenant"]:::sib
  vrf["terraform-aci-vrf"]:::sib
  anymod["terraform-aci-any-vrf-contract (this module)"]:::this
  vzany["aci_any - class vzAny - DN uni/tn-{tenant}/ctx-{vrf}/any"]:::keystone
  contract["terraform-aci-contract"]:::sib
  epg["terraform-aci-application-epg"]:::sib

  apic -->|"provider configured by caller"| anymod
  tenant -->|"tenant_dn"| vrf
  vrf -->|"vrf_dn"| anymod
  anymod -->|"manages"| vzany
  contract -->|"contract name (consumed_contracts / provided_contracts)"| anymod
  epg -.->|"EPG-scoped contracts (separate binding, out of scope here)"| contract

  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 Any object nests directly under a VRF and takes the VRF's DN as its only parent input. It consumes contracts (and imported contract interfaces) by name β€” those objects are owned and versioned by their own modules. EPG-scoped contract relations are a separate, EPG-owned binding and are intentionally out of scope here.

🧬 What this module builds

graph TD
  vrfdn["vrf_dn (parent VRF DN)"]:::in
  anycfg["any: match_criteria / preferred_group_member / metadata"]:::in
  cc["consumed_contracts (map)"]:::in
  pc["provided_contracts (map)"]:::in
  ci["contract_interfaces (map)"]:::in

  this["aci_any.this (keystone, vzAny)"]:::this
  ccr["aci_relation_from_any_to_consumer_contract.this (for_each)"]:::this
  pcr["aci_relation_from_any_to_provider_contract.this (for_each)"]:::this
  cir["aci_relation_from_any_to_contract_interface.this (for_each)"]:::this

  oid["output: id"]:::out
  omc["output: match_criteria"]:::out
  occ["output: consumer_contract_dns"]:::out
  opc["output: provider_contract_dns"]:::out
  oci["output: contract_interface_dns"]:::out

  vrfdn --> this
  anycfg --> this
  this --> ccr
  cc --> ccr
  this --> pcr
  pc --> pcr
  this --> cir
  ci --> cir
  this --> oid
  this --> omc
  ccr --> occ
  pcr --> opc
  cir --> oci

  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_any this 1 (keystone) The VRF's Any contract-scope object (vzAny).
aci_relation_from_any_to_consumer_contract this 0..N (for_each) Consumer-contract relation (vzRsAnyToCons), keyed by contract name.
aci_relation_from_any_to_provider_contract this 0..N (for_each) Provider-contract relation (vzRsAnyToProv), keyed by contract name.
aci_relation_from_any_to_contract_interface this 0..N (for_each) Contract-interface relation (vzRsAnyToConsIf), keyed by imported contract 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 Parent VRF DN (vrf_dn) β€” required; the Any object is a VRF singleton.

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

  • πŸ”’ aci_any is a VRF singleton. Its DN's trailing path segment is the literal any, independent of any name value β€” the live schema exposes name as optional/computed, but this module does not surface it because it has no bearing on identity or placement.
  • ⚠️ match_criteria and match_t are mutually exclusive. Setting both on aci_any fails at plan/apply with Attribute "match_criteria" cannot be specified when "match_t" is specified. This module manages only the current match_criteria attribute; match_t is the deprecated alias and is not used.
  • ⚠️ preferred_group_member and pref_gr_memb are mutually exclusive, for the same reason. This module manages only preferred_group_member, surfaced as a bool and rendered to the provider's "enabled"/"disabled" string.
  • πŸ”’ vrf_dn (the resource attribute) is deprecated. The live schema emits an Attribute Deprecated warning directing callers to parent_dn; this module wires the parent exclusively through parent_dn.
  • ℹ️ All four resources are migrated (plugin-framework). annotations / tags are typed {key, value} lists, assigned with =, not HCL blocks. The provider also retains classic flat relation_vz_rs_any_to_cons / relation_vz_rs_any_to_cons_if / relation_vz_rs_any_to_prov set-of-string attributes; this module uses the dedicated, richer relation resources instead and leaves the classic flat attributes to the provider.
  • ℹ️ priority accepts two representations: a named QoS class (level1-level6, unspecified) or a single-digit numeric string (0-9), confirmed by the live validator's error text.
  • ⚠️ validate_relation_dn (provider default true) fails apply if a contract named in consumed_contracts / provided_contracts, or the imported contract behind contract_interfaces, does not exist.

πŸ”‘ Required APIC Roles & Privileges

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

  • Create / modify the Any object and its contract relations: the tenant-admin role (or a custom role with tenant-security write privilege) scoped to the tenant's security domain.
  • Referenced contracts / contract interface: read on every contract named in consumed_contracts / provided_contracts, and on the exporting tenant's contract interface referenced by contract_interfaces.

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) is not a safe steady state.
  • The parent VRF (vrf_dn) exists.
  • Every contract named in consumed_contracts / provided_contracts exists (or is created in the same apply); the imported contract behind any contract_interfaces entry exists in its exporting tenant.
  • The APIC version supports the chosen match_criteria value (vzAny matching has been available since ACI 1.0(1e)).

πŸ“ Module Structure

terraform-aci-any-vrf-contract/
β”œβ”€β”€ providers.tf     # terraform{} + required_providers (aci ~> 2.20); no provider block
β”œβ”€β”€ variables.tf     # vrf_dn, any, consumed_contracts, provided_contracts, contract_interfaces
β”œβ”€β”€ main.tf          # aci_any.this (keystone) + the three for_each relation children
β”œβ”€β”€ outputs.tf       # id (the DN) first, then match/posture and child relation DN maps
β”œβ”€β”€ 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 "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"

  vrf_dn = "uni/tn-core-prod/ctx-core-vrf" # e.g. module.vrf.id

  consumed_contracts = {
    "shared-dns" = {}
  }
}

output "any_dn" {
  value = module.any.id
}

πŸ”Œ Cross-Module Contract

Consumes

Input Type Typical source
vrf_dn string (DN) terraform-aci-vrf
any object({...}) caller (match criteria + preferred-group posture + metadata)
consumed_contracts map(object({...})) caller (contract names from terraform-aci-contract)
provided_contracts map(object({...})) caller (contract names from terraform-aci-contract)
contract_interfaces map(object({...})) caller (imported contract names from an exporting tenant)

Emits

Output Description Consumed by
id Any object DN (uni/tn-{tenant}/ctx-{vrf}/any) audits / downstream reference
match_criteria Configured provider-label match criteria audit
preferred_group_member Configured preferred-group posture (enabled/disabled) audit
consumer_contract_dns Map of contract name β†’ consumer-contract relation DN audits / policy review
provider_contract_dns Map of contract name β†’ provider-contract relation DN audits / policy review
contract_interface_dns Map of imported contract name β†’ contract-interface relation DN audits / policy review

πŸ“š Example Library

1 Β· Minimal β€” the Any object with secure defaults
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"
}

πŸ’‘ The minimal call creates only aci_any.this with match_criteria = "AtleastOne" and preferred_group_member = "disabled" β€” no contract relations, no permissive posture.

2 Β· Widening the match criteria
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  any = {
    match_criteria = "All"
  }
}

ℹ️ match_criteria controls how provider labels are matched against consumer labels: All requires every label to match, AtleastOne (the default) requires one, AtmostOne allows at most one, and None requires no match.

3 Β· Enabling the VRF preferred group (documented risk)
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  any = {
    preferred_group_member = true
  }
}

⚠️ Enabling preferred_group_member allows EPGs placed in the VRF's preferred group to communicate without any contract β€” a materially more permissive posture. This suite defaults it to false; opt in deliberately and document the change.

4 Β· Metadata tail β€” GUI alias and description
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  any = {
    name_alias  = "Core VRF Any"
    description = "VRF-wide contract scope for core-prod's core-vrf"
  }
}

ℹ️ name_alias is a display alias shown in the APIC GUI; it has no bearing on the object's DN.

5 Β· User metadata via annotations and tags
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  any = {
    annotations = { "cost-center" = "CC-4021", "environment" = "production" }
    tags        = { "tier" = "gold" }
  }
}

ℹ️ annotations / tags are given as ergonomic { key = value } maps and rendered to the provider's [{key, value}] list shape.

6 Β· A custom annotation marker
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  any = {
    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.

7 Β· A single consumer-contract relation
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  consumed_contracts = {
    "shared-dns" = {}
  }
}

⚠️ Every EPG in this VRF becomes a consumer of shared-dns β€” a VRF-wide effect, not an EPG-scoped one.

8 Β· A consumer-contract relation with an explicit QoS priority
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  consumed_contracts = {
    "shared-dns" = {
      priority = "level3"
    }
  }
}

ℹ️ priority accepts a named QoS class (level1-level6, unspecified) or a single-digit numeric string (0-9), per the live provider schema.

9 Β· A provider-contract relation with a narrower match criteria
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  provided_contracts = {
    "shared-ntp" = {
      match_criteria = "AtmostOne"
    }
  }
}

ℹ️ provided_contracts[*].match_criteria overrides the provider-label match algorithm for that specific relation, independent of any.match_criteria.

10 Β· A contract-interface relation (importing another tenant's contract)
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  contract_interfaces = {
    "shared-svcs-egress" = {}
  }
}

⚠️ The imported contract must exist in its exporting tenant so the provider's validate_relation_dn check passes at apply.

11 Β· Multiple consumed and provided contracts together
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  consumed_contracts = {
    "shared-dns" = {}
    "shared-ntp" = {}
  }
  provided_contracts = {
    "core-vrf-health-check" = { priority = "level6" }
  }
}

πŸ’‘ Every entry is a for_each map keyed by contract name β€” adding or removing one contract never churns the others.

12 Β· Least-privilege operating model (documentation variant)
# Configure the provider with a login scoped to tenant-security management only β€”
# not a fabric-wide admin β€” for day-2 contract-scope changes.
provider "aci" {
  # username    = "svc-tenant-security"  # a tenant-admin role, write-scoped to
  #                                       # this tenant's 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 "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"
}

πŸ”’ Prefer signature-based (X.509) auth for automation to avoid APIC login-rate thresholds.

13 Β· Wiring the VRF DN from terraform-aci-vrf
module "vrf" {
  source    = "git::https://github.com/microsoftexpert/terraform-aci-vrf.git?ref=v1.0.0"
  tenant_dn = "uni/tn-core-prod"
  vrf       = { name = "core-vrf" }
}

module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = module.vrf.id # <-- the VRF's DN becomes this module's parent

  consumed_contracts = {
    "shared-dns" = {}
  }
}
14 Β· Reading outputs for downstream audit
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = "uni/tn-core-prod/ctx-core-vrf"

  consumed_contracts = { "shared-dns" = {} }
  provided_contracts = { "shared-ntp" = {} }
}

output "any_dn"                 { value = module.any.id }
output "consumer_contract_dns"  { value = module.any.consumer_contract_dns }
output "provider_contract_dns"  { value = module.any.provider_contract_dns }
15 Β· πŸ—οΈ End-to-end composition β€” tenant β†’ VRF β†’ Any (VRF-wide contract scope) β†’ contract
provider "aci" {
  # configured + authenticated by the caller; insecure = false in production
}

# 1) The keystone tenant.
module "tenant" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-tenant.git?ref=v1.0.0"
  tenant = { name = "core-prod", description = "Core production tenant" }
}

# 2) A VRF in the tenant.
module "vrf" {
  source    = "git::https://github.com/microsoftexpert/terraform-aci-vrf.git?ref=v1.0.0"
  tenant_dn = module.tenant.id
  vrf       = { name = "core-vrf" }
}

# 3) A contract to expose fabric-wide within the VRF.
module "contract" {
  source    = "git::https://github.com/microsoftexpert/terraform-aci-contract.git?ref=v1.0.0"
  tenant_dn = module.tenant.id
  contract  = { name = "shared-dns" }
}

# 4) This module β€” bind the contract at the VRF's Any object.
module "any" {
  source = "git::https://github.com/microsoftexpert/terraform-aci-any-vrf-contract.git?ref=v1.0.0"
  vrf_dn = module.vrf.id

  provided_contracts = {
    (module.contract.name) = { priority = "unspecified" }
  }
}

output "any_dn" { value = module.any.id }

πŸ—οΈ One tenant, one VRF, and one contract in; a fabric-wide, VRF-scoped contract binding out. Every EPG in core-vrf becomes a provider of shared-dns without any EPG-level configuration β€” review this relation with the same rigor as a fabric-wide security change.

πŸ“₯ Inputs

Name Type Required Default Description
vrf_dn string βœ… β€” Parent VRF DN (uni/tn-{tenant}/ctx-{vrf}).
any object({...}) βž– {} Match criteria, preferred-group posture, and metadata tail.
consumed_contracts map(object({...})) βž– {} Consumer-contract relations, keyed by contract name.
provided_contracts map(object({...})) βž– {} Provider-contract relations, keyed by contract name.
contract_interfaces map(object({...})) βž– {} Contract-interface relations, keyed by imported contract name.
Full input schema (from variables.tf)
variable "vrf_dn" {
  type = string
  # validation: matches ^uni/tn-[^/]+/ctx-[^/]+$
}

variable "any" {
  type = object({
    match_criteria         = optional(string, "AtleastOne") # All | AtleastOne | AtmostOne | None
    preferred_group_member = optional(bool, false)          # renders enabled/disabled
    annotation              = optional(string, "orchestrator:terraform")
    name_alias              = optional(string, null)
    description             = optional(string, null)
    annotations             = optional(map(string), {})
    tags                    = optional(map(string), {})
  })
  default = {}
}

variable "consumed_contracts" {
  type = map(object({
    priority    = optional(string, "unspecified") # level1-level6 | unspecified | 0-9
    annotation  = optional(string, "orchestrator:terraform")
    annotations = optional(map(string), {})
    tags        = optional(map(string), {})
  }))
  default = {}
}

variable "provided_contracts" {
  type = map(object({
    priority       = optional(string, "unspecified")
    match_criteria = optional(string, "AtleastOne")
    annotation     = optional(string, "orchestrator:terraform")
    annotations    = optional(map(string), {})
    tags           = optional(map(string), {})
  }))
  default = {}
}

variable "contract_interfaces" {
  type = map(object({
    priority    = optional(string, "unspecified")
    annotation  = optional(string, "orchestrator:terraform")
    annotations = optional(map(string), {})
    tags        = optional(map(string), {})
  }))
  default = {}
}

🧾 Outputs

Output Description Notes
id Any object DN (uni/tn-{tenant}/ctx-{vrf}/any) Primary reference; usually for audit rather than as a downstream parent_dn.
match_criteria Configured provider-label match criteria β€”
preferred_group_member Configured preferred-group posture (enabled/disabled) β€”
consumer_contract_dns Map of contract name β†’ consumer-contract relation DN Empty map if consumed_contracts is empty.
provider_contract_dns Map of contract name β†’ provider-contract relation DN Empty map if provided_contracts is empty.
contract_interface_dns Map of imported contract name β†’ contract-interface relation DN Empty map if contract_interfaces is empty.

🧠 Architecture Notes

  • One keystone, three relation families. aci_any.this is the singleton keystone; each of the three relation types is its own for_each map keyed by a stable natural key (contract name, or imported contract name for interfaces) β€” never count β€” so adding one relation never churns the others.
  • Deprecated-attribute avoidance. The parent is wired through parent_dn, not the deprecated vrf_dn resource attribute; match_criteria / preferred_group_member are used instead of their deprecated match_t / pref_gr_memb aliases, which the live schema rejects if set alongside the current attribute.
  • No name surfaced. aci_any is a VRF singleton whose DN never varies with name; the module omits it rather than exposing a field with no effect.
  • Dedicated relation resources over inline nesting. The live schema also exposes relation_to_consumer_contracts / relation_to_provider_contracts / relation_to_contract_interfaces as inline nested attributes directly on aci_any. This module instead uses the dedicated standalone relation resources assigned to it, keeping each relation independently addressable and matching this suite's for_each-child convention.
  • Unmanaged when empty. annotations / tags pass null rather than an empty list when empty, on both the keystone and every child, so the module never fights provider-computed state.
  • Blast radius. Every relation created here applies VRF-wide, across every EPG in the VRF β€” treat changes to consumed_contracts / provided_contracts / contract_interfaces as VRF-scoped security changes, not EPG-scoped ones.

🧱 Design Principles

Concern Secure default How to opt out (deliberately)
any.match_criteria AtleastOne β€” require at least one matching provider label Set to All, AtmostOne, or None per the desired matching semantics.
any.preferred_group_member false (disabled) β€” EPGs do not bypass contract enforcement via the preferred group Set to true (enabled) deliberately, and document the widened blast radius.
consumed_contracts / provided_contracts / contract_interfaces {} β€” no relations managed, no VRF-wide exposure by default Populate the maps explicitly, one entry per contract.
any.annotation orchestrator:terraform β€” Terraform-managed objects stay identifiable in APIC Extend the marker; do not blank it.
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 β€” this module 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 any / consumed_contracts / provided_contracts / contract_interfaces object types, runs every enum and DN-format 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 the parent VRF and every referenced contract (server-side validate_relation_dn), APIC-side name-collision checks, and the computed DNs returned as outputs.

πŸ’¬ Example Output

$ terraform output
id                       = "uni/tn-core-prod/ctx-core-vrf/any"
match_criteria           = "AtleastOne"
preferred_group_member   = "disabled"
consumer_contract_dns    = {}
provider_contract_dns    = {
  "shared-dns" = "uni/tn-core-prod/ctx-core-vrf/any/rsanyToProv-shared-dns"
}
contract_interface_dns   = {}

πŸ” Troubleshooting

Symptom Cause Fix
vrf_dn must be a VRF DN of the form uni/tn-{tenant}/ctx-{vrf} vrf_dn is missing the tenant/VRF path segments Pass the VRF's actual DN (e.g. module.vrf.id), not a tenant DN or a free-form string.
any.match_criteria must be one of: All, AtleastOne, AtmostOne, None Unsupported value passed for any.match_criteria Use one of the four documented values.
Every consumed_contracts[*].priority must be one of level1-level6, unspecified, or a single digit 0-9 Unsupported priority value Use a named QoS class or a single digit 0-9.
Attribute "match_criteria" cannot be specified when "match_t" is specified Both a current and deprecated attribute set on the same resource This module manages only match_criteria; do not add match_t alongside it.
Apply fails validating a contract or contract-interface relation Referenced contract (or the imported contract behind contract_interfaces) does not exist Create the contract first (or in the same apply); do not disable validate_relation_dn.
Every EPG in the VRF unexpectedly gained contract exposure A relation was added at the Any (VRF-wide) scope instead of at the EPG Confirm whether the intended scope is VRF-wide (this module) or EPG-scoped (the application-EPG module) before adding an entry.
Post ... 401 / authentication error Provider not configured or wrong credentials Configure the aci provider with valid credentials and url; prefer signature auth for automation.

πŸ”— Related Docs


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

About

Terraform module: terraform-aci-any-vrf-contract

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages