Manage a Cisco ACI tenant's OSPF protocol-policy set — the OSPF interface policy (class
ospfIfPol), OSPF timers policy (classospfCtxPol), OSPF route-summarization policy (classospfRtSummPol), and the VRF-to-timers binding (classfvRsCtxToOspfCtxPol) — as a typed, secure-by-default building block targetingCiscoDevNet/aci ~> 2.20.
This module manages a tenant's OSPF protocol-policy templates and the relationship that binds one of them to a VRF, as one coherent, secure-by-default unit:
- 🛣️ The OSPF interface policy (
aci_ospf_interface_policy.this) — the keystone; a tenant-scoped template of per-interface OSPF timers and controls (hello/dead intervals, network type, cost, priority) referenced by L3Out interface profiles, addressed byuni/tn-{tenant}/ospfIfPol-{name}. - ⏱️ OSPF timers policies (
aci_ospf_timers.this,for_each) — sibling tenant-scoped templates tuning VRF-wide OSPF process behavior (SPF/LSA pacing, max-ECMP, administrative distance, graceful restart, max-LSA protection), keyed by a stable natural key. - 🧮 OSPF route-summarization policies (
aci_ospf_route_summarization.this,for_each) — sibling tenant-scoped templates referenced from an L3Out's OSPF area configuration to summarize inter-area or external routes. - 🔗 A VRF-to-timers binding (
aci_relation_from_vrf_to_address_family_ospf_timers.this,for_each) — binds a VRF's OSPF address family (ipv4-ucast/ipv6-ucast) to one of this module's own timers policies, consuming the VRF by DN. - 🏷️ The ACI metadata tail —
annotation(preserved asorchestrator:terraform),name_alias, anddescriptionon every policy. - 🔑 Scope, not credentials — every tenant-scoped policy takes the parent tenant DN (
tenant_dn) as a required input; the VRF binding takes the VRF's own DN by reference. Authentication and the APIC URL are the caller's provider concern and are never module variables.
💡 Why it matters: OSPF's interface behavior, VRF-wide process tuning, and route summarization are configured as three independent tenant-scoped templates in the ACI object model, not as nested children of one another — but they are almost always authored, reviewed, and rolled out together as a tenant's OSPF posture. Bundling them (plus the VRF binding that activates the timers policy) in one module keeps that whole surface auditable as a single change, while still emitting each policy's DN for L3Out modules to reference independently.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
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!
graph LR
tenant["terraform-aci-tenant"]:::sib
ospf["terraform-aci-ospf-policies (this module)"]:::this
ifpol["aci_ospf_interface_policy - class ospfIfPol - DN uni/tn-{t}/ospfIfPol-{n}"]:::keystone
timers["aci_ospf_timers - class ospfCtxPol (for_each)"]:::keystone
rtsumm["aci_ospf_route_summarization - class ospfRtSummPol (for_each)"]:::keystone
rel["aci_relation_from_vrf_to_address_family_ospf_timers - class fvRsCtxToOspfCtxPol (for_each)"]:::keystone
vrf["terraform-aci-vrf"]:::sib
l3out["terraform-aci-l3-outside"]:::sib
tenant -->|"tenant_dn"| ospf
ospf -->|"manages"| ifpol
ospf -->|"for_each ospf_timers"| timers
ospf -->|"for_each ospf_route_summarization"| rtsumm
ospf -->|"for_each vrf_ospf_timer_relations"| rel
vrf -->|"vrf_dn (relation parent_dn)"| rel
ospf -->|"id (interface policy DN, by name)"| l3out
rtsumm -->|"by name"| l3out
classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
classDef keystone fill:#0D274D,color:#fff,stroke:#0D274D;
classDef sib fill:#f5f5f5,color:#333,stroke:#cccccc;
This module sits beside the VRF and the L3Out in a tenant's routing stack: it takes the tenant (tenant_dn) as its parent, consumes a VRF's DN only to bind that VRF's OSPF address family to one of its own timers policies, and emits the interface policy DN and the route-summarization DNs for the L3Out module's OSPF interface profiles and area configuration to reference.
graph TD
tdn["tenant_dn (required)"]:::in
ifp["ospf_interface_policy object"]:::in
tim["ospf_timers map (for_each)"]:::in
rts["ospf_route_summarization map (for_each)"]:::in
vrl["vrf_ospf_timer_relations map (for_each)"]:::in
this["aci_ospf_interface_policy.this (keystone, ospfIfPol)"]:::this
t["aci_ospf_timers.this (for_each, ospfCtxPol)"]:::this
r["aci_ospf_route_summarization.this (for_each, ospfRtSummPol)"]:::this
rel["aci_relation_from_vrf_to_address_family_ospf_timers.this (for_each, fvRsCtxToOspfCtxPol)"]:::this
oid["output: id (interface policy DN)"]:::out
otd["output: ospf_timers_dns (map)"]:::out
ord["output: ospf_route_summarization_dns (map)"]:::out
orl["output: vrf_ospf_timer_relation_dns (map)"]:::out
tdn --> this
ifp --> this
tdn --> t
tim --> t
tdn --> r
rts --> r
vrl --> rel
this --> oid
t --> otd
r --> ord
rel --> orl
classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
classDef in fill:#f5f5f5,color:#333,stroke:#cccccc;
classDef out fill:#eeeeff,color:#333,stroke:#9999ff;
Resource inventory
| Resource | Name | Cardinality | Role |
|---|---|---|---|
aci_ospf_interface_policy |
this |
1 (keystone) | The OSPF interface policy — per-interface timers/controls (ospfIfPol). |
aci_ospf_timers |
this |
0..N (for_each over ospf_timers) |
OSPF timers (VRF context) policies (ospfCtxPol). |
aci_ospf_route_summarization |
this |
0..N (for_each over ospf_route_summarization) |
OSPF route-summarization policies (ospfRtSummPol). |
aci_relation_from_vrf_to_address_family_ospf_timers |
this |
0..N (for_each over vrf_ospf_timer_relations) |
Binds a VRF's OSPF address family to a timers policy (fvRsCtxToOspfCtxPol). |
| 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 | Tenant-scoped policies require a parent tenant_dn; the VRF binding requires the VRF's own DN (vrf_dn). |
Schema notes that bite (verified against the live provider schema):
⚠️ aci_ospf_interface_policy,aci_ospf_timers, andaci_ospf_route_summarizationare classic (SDKv2) resources. None of the three exposes aparent_dnattribute in this provider line — the parent tenant is wired throughtenant_dn, which is correctly the current attribute here, not a deprecated one. Do not confuse this with migrated resources (like the bridge domain) wheretenant_dnis deprecated in favor ofparent_dn.- ℹ️
aci_relation_from_vrf_to_address_family_ospf_timersis the one migrated (plugin-framework) resource here. Its parent is the VRF, wired throughparent_dn(nottenant_dn— a VRF DN, not a tenant DN). It also exposes typedannotations/tagsnested attributes, unused in this module beyond the singleannotationmarker. - 🔒
nameis immutable on all three tenant-scoped policies. Changing it forces replacement of that policy — and, for the OSPF timers policy, breaks anyvrf_ospf_timer_relationsentry that references it by name until updated. - ℹ️ These three policy objects are DN siblings, not a parent/child chain. The OSPF interface policy, OSPF timers policy, and route-summarization policy do not nest under each other in the MIT — each is created directly under the tenant. This module bundles them because they are typically authored together, not because one contains another.
⚠️ Numeric fields are strings with documented ranges. Every OSPF timer/control field (cost,dead_intvl,hello_intvl,prio,rexmit_intvl,xmit_delay,bw_ref,dist, thelsa_*/spf_*intervals,max_ecmp, themax_lsa_*fields, and route-summarizationcost) is a string in the provider schema even though it carries a numeric range; this module validates each at plan time against the range confirmed in the live provider schema and leaves itnull(provider computed default) when unset.⚠️ ctrlis a distinct closed enum on each resource.unspecified | passive | mtu-ignore | advert-subnet | bfdon the interface policy vs.name-lookup | pfx-suppresson the timers policy — confirmed live; do not reuse one list on the other.- ℹ️
inter_area_enabledon the route-summarization policy is ayes/nostring in the provider; this module surfaces it asbooland renders it. ⚠️ validate_relation_dn(provider defaulttrue) fails apply ifvrf_ospf_timer_relations[*].vrf_dndoes not exist, or ifospf_timers_namedoes not resolve to an OSPF timers policy in the same tenant — create the missing VRF/policy first rather than disabling validation.
Scope the caller's APIC login to the least privilege this module needs:
- Create / modify the OSPF interface, timers, and route-summarization policies: the
tenant-adminrole (or a custom role with tenant-networking / protocol-policy write privilege), scoped to the tenant's own security domain. - Create the VRF-to-timers binding: write privilege scoped to the target VRF's security domain (the relation is created under the VRF's own DN).
- Referenced VRF: read privilege on any VRF named in
vrf_ospf_timer_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).
- A reachable Cisco APIC (
ACI_URL) whose version is compatible with the~> 2.20provider, with the provider configured and authenticated by the caller. In production, setinsecure = falsewith proper CA trust — the provider's own default (insecure = true) is not a safe steady state. - The parent tenant (
tenant_dn) already exists. - If
vrf_ospf_timer_relationsis set, the referenced VRF exists (or is created in the same apply) so the provider's DN validation passes, and the referencedospf_timers_namepolicy exists in the same tenant — either in this module's ownospf_timersmap or from another instance of this module.
terraform-aci-ospf-policies/
├── providers.tf # terraform{} + required_providers (aci ~> 2.20); no provider block
├── variables.tf # tenant_dn + ospf_interface_policy object + ospf_timers / ospf_route_summarization / vrf_ospf_timer_relations maps
├── main.tf # aci_ospf_interface_policy.this (keystone) + three for_each collections
├── outputs.tf # id (interface policy DN) first, then name and three DN maps
├── README.md # this document
├── SCOPE.md # cross-module contract (scope, consumes/emits, roles, prerequisites)
├── LICENSE # MIT
└── .gitignore # canonical library ignore set
# 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 "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id # from terraform-aci-tenant
ospf_interface_policy = { name = "ospf-if-default" }
}
output "ospf_interface_policy_dn" {
value = module.ospf_policies.id # reference from an L3Out interface profile
}Consumes
| Input | Type | Typical source |
|---|---|---|
tenant_dn |
string (DN) | terraform-aci-tenant |
ospf_interface_policy |
object({...}) |
caller (name + per-interface timers/controls + metadata) |
ospf_timers |
map(object({...})) |
caller |
ospf_route_summarization |
map(object({...})) |
caller |
vrf_ospf_timer_relations |
map(object({ vrf_dn, address_family, ospf_timers_name, annotation })) |
caller; vrf_dn from terraform-aci-vrf |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
OSPF interface policy DN (uni/tn-{tenant}/ospfIfPol-{name}) — primary reference |
L3Out logical interface profiles (interface-policy binding) |
name |
OSPF interface policy name | composition / audit |
ospf_timers_dns |
Map of ospf_timers key → timers policy DN |
audits / downstream reference |
ospf_route_summarization_dns |
Map of ospf_route_summarization key → route-summarization policy DN |
L3Out OSPF area configuration (summarization binding) |
vrf_ospf_timer_relation_dns |
Map of vrf_ospf_timer_relations key → relation DN |
audits / downstream reference |
1 · Minimal — an OSPF interface policy with secure defaults
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
}💡 The minimal call leaves every timer and control at the provider's own computed default (hello/dead intervals, priority, cost, network type all inherit the ACI defaults). No timers policy, route-summarization policy, or VRF binding is created yet.
2 · Passive interfaces with BFD-style fast-failure detection
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = {
name = "ospf-if-passive-bfd"
ctrl = ["passive", "bfd"]
}
}ℹ️
passivesuppresses OSPF hellos on the interface (routes are still advertised);bfdties OSPF neighbor liveness to a BFD session for sub-second failure detection.
3 · Point-to-point network type for a routed sub-interface
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = {
name = "ospf-if-p2p"
nw_t = "p2p"
}
}ℹ️
nw_t = "p2p"avoids DR/BDR election on point-to-point transit links — the common choice for routed sub-interfaces and L3Out numbered links.
4 · Custom cost, priority, and timers
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = {
name = "ospf-if-tuned"
cost = "50"
prio = "10"
hello_intvl = "5"
dead_intvl = "20"
rexmit_intvl = "3"
}
}
⚠️ hello_intvlanddead_intvlmust match (or be compatible with) the neighbor's OSPF interface policy — a mismatch prevents adjacency from forming. Every numeric field here is validated at plan time against the documented range.
5 · A single OSPF timers (VRF context) policy
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
ospf_timers = {
"core-vrf-timers" = { name = "core-vrf-timers" }
}
}💡
ospf_timersis a map keyed by a stable natural key so adding a second policy later never disturbs this one in state. The minimal entry leaves every SPF/LSA/graceful-restart knob at the provider's own default.
6 · Multiple OSPF timers policies (for_each)
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
ospf_timers = {
"core-vrf-timers" = { name = "core-vrf-timers" }
"dmz-vrf-timers" = { name = "dmz-vrf-timers", max_ecmp = "4" }
}
}ℹ️ Each map entry becomes its own
aci_ospf_timersresource viafor_each— order-independent, and safe to add or remove entries without touching the others.
7 · Tuning SPF and LSA pacing on a timers policy
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
ospf_timers = {
"fast-converge" = {
name = "fast-converge"
spf_init_intvl = "50"
spf_hold_intvl = "200"
spf_max_intvl = "1000"
lsa_start_intvl = "0"
lsa_hold_intvl = "1000"
}
}
}
⚠️ Lowering SPF/LSA intervals speeds convergence but increases CPU load on affected nodes during instability — validate against the fabric's scale before applying broadly.
8 · Disabling graceful restart on a timers policy
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
ospf_timers = {
"no-gr" = { name = "no-gr", gr_ctrl = "" }
}
}ℹ️
gr_ctrl = ""explicitly disables OSPF graceful restart for this VRF context; leavegr_ctrlunset (null) to keep the provider's own default (helper).
9 · A route-summarization policy (inter-area enabled)
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
ospf_route_summarization = {
"branch-summary" = {
name = "branch-summary"
inter_area_enabled = true
cost = "100"
}
}
}💡
inter_area_enableddefaults tofalse; set it explicitly to opt in to inter-area summarization for this policy. Reference the resulting DN from an L3Out's OSPF area configuration.
10 · Multiple route-summarization policies
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
ospf_route_summarization = {
"branch-summary" = { name = "branch-summary", inter_area_enabled = true }
"dr-summary" = { name = "dr-summary", inter_area_enabled = true, tag = "100" }
}
}ℹ️ Like
ospf_timers,ospf_route_summarizationis afor_eachmap — each entry is an independent tenant-scoped policy, not a child of the other.
11 · Binding a VRF's OSPF address family to a timers policy
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
ospf_timers = {
"core-vrf-timers" = { name = "core-vrf-timers" }
}
vrf_ospf_timer_relations = {
"core-vrf-ipv4" = {
vrf_dn = module.vrf.id
address_family = "ipv4-ucast"
ospf_timers_name = "core-vrf-timers"
}
}
}
⚠️ ospf_timers_namemust reference a policy that exists in the same tenant — either in this module's ownospf_timersmap or from another instance of this module — or the provider'svalidate_relation_dncheck fails the apply.vrf_dnmust be a VRF DN (uni/tn-{name}/ctx-{name}), typicallymodule.vrf.id.
12 · Dual-stack VRF bindings (ipv4-ucast and ipv6-ucast)
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = { name = "ospf-if-default" }
ospf_timers = {
"core-vrf-timers" = { name = "core-vrf-timers" }
}
vrf_ospf_timer_relations = {
"core-vrf-ipv4" = { vrf_dn = module.vrf.id, address_family = "ipv4-ucast", ospf_timers_name = "core-vrf-timers" }
"core-vrf-ipv6" = { vrf_dn = module.vrf.id, address_family = "ipv6-ucast", ospf_timers_name = "core-vrf-timers" }
}
}ℹ️ A VRF can bind the same or different timers policies per address family —
address_familyis validated toipv4-ucastoripv6-ucastat plan time.
13 · User metadata via the annotation marker
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = {
name = "ospf-if-default"
annotation = "orchestrator:terraform:network-platform"
}
}🔒 Keep the
orchestrator:terraformprefix so Terraform-managed objects stay identifiable in APIC. This suite defaultsannotationtoorchestrator:terraformon every policy; override it only to extend, not to erase, that marker.
14 · A full tenant OSPF policy bundle (all four objects together)
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = {
name = "ospf-if-core"
nw_t = "p2p"
hello_intvl = "5"
dead_intvl = "20"
}
ospf_timers = {
"core-vrf-timers" = { name = "core-vrf-timers", max_ecmp = "16" }
}
ospf_route_summarization = {
"branch-summary" = { name = "branch-summary", inter_area_enabled = true }
}
vrf_ospf_timer_relations = {
"core-vrf-ipv4" = { vrf_dn = module.vrf.id, address_family = "ipv4-ucast", ospf_timers_name = "core-vrf-timers" }
}
}15 · 🏗️ End-to-end composition — tenant → VRF → OSPF policies → L3Out
provider "aci" {
# configured + authenticated by the caller; insecure = false in production
}
# 1) The tenant — the root everything nests under.
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 — the OSPF process context this module binds timers to.
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) This module: interface policy + timers policy + route-summarization + VRF binding.
module "ospf_policies" {
source = "git::https://github.com/microsoftexpert/terraform-aci-ospf-policies.git?ref=v1.0.0"
tenant_dn = module.tenant.id
ospf_interface_policy = {
name = "ospf-if-core"
nw_t = "p2p"
}
ospf_timers = {
"core-vrf-timers" = { name = "core-vrf-timers" }
}
ospf_route_summarization = {
"branch-summary" = { name = "branch-summary", inter_area_enabled = true }
}
vrf_ospf_timer_relations = {
"core-vrf-ipv4" = {
vrf_dn = module.vrf.id
address_family = "ipv4-ucast"
ospf_timers_name = "core-vrf-timers"
}
}
}
# 4) An L3Out consumes the interface policy and route-summarization DNs by reference.
module "l3out" {
source = "git::https://github.com/microsoftexpert/terraform-aci-l3-outside.git?ref=v1.0.0"
tenant_dn = module.tenant.id
vrf_dn = module.vrf.id
l3_outside = {
name = "internet-l3out"
ospf_interface_policy_name = module.ospf_policies.name
ospf_route_summarization_name = "branch-summary"
}
}
output "ospf_interface_policy_dn" { value = module.ospf_policies.id }🏗️ One tenant and one VRF in; a fully-tuned OSPF policy surface out. The L3Out module references the interface policy and route-summarization policy by name within the tenant, while the VRF's OSPF address family is bound directly to the timers policy through this module's own relation resource.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
tenant_dn |
string |
✅ | — | Parent tenant DN (uni/tn-{name}); wired to every tenant-scoped policy's tenant_dn. |
ospf_interface_policy |
object({...}) |
✅ | — | The OSPF interface policy: name, per-interface timers/controls, and metadata tail. |
ospf_timers |
map(object({...})) |
➖ | {} |
OSPF timers (VRF context) policies, keyed by a stable natural key. |
ospf_route_summarization |
map(object({...})) |
➖ | {} |
OSPF route-summarization policies, keyed by a stable natural key. |
vrf_ospf_timer_relations |
map(object({...})) |
➖ | {} |
VRF-to-timers bindings, keyed by a stable natural key. |
Full input schema (from variables.tf)
variable "tenant_dn" {
type = string
# validation: must match ^uni/tn-[^/]+$ (a tenant DN)
}
variable "ospf_interface_policy" {
type = object({
name = string # REQUIRED, immutable (force-new), 1-64 chars
annotation = optional(string, "orchestrator:terraform") # ACI annotation marker.
name_alias = optional(string, null) # GUI display alias.
description = optional(string, null) # Free-form description.
cost = optional(string, null) # "unspecified" or "0"-"65535".
ctrl = optional(list(string), []) # Subset of: unspecified | passive | mtu-ignore | advert-subnet | bfd.
dead_intvl = optional(string, null) # "1"-"65535" seconds.
hello_intvl = optional(string, null) # "1"-"65535" seconds.
nw_t = optional(string, null) # unspecified | p2p | bcast.
pfx_suppress = optional(string, null) # inherit | enable | disable.
prio = optional(string, null) # "0"-"255".
rexmit_intvl = optional(string, null) # "1"-"65535" seconds.
xmit_delay = optional(string, null) # "1"-"450" seconds.
})
# validations: name length/charset; ctrl subset; nw_t/pfx_suppress enums; every numeric range
}
variable "ospf_timers" {
type = map(object({
name = string # REQUIRED, immutable, 1-64 chars
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
description = optional(string, null)
bw_ref = optional(string, null) # "1"-"4000000"
ctrl = optional(list(string), []) # Subset of: name-lookup | pfx-suppress.
dist = optional(string, null) # "1"-"255"
gr_ctrl = optional(string, null) # "helper" | "" (disabled)
lsa_arrival_intvl = optional(string, null) # "10"-"600000"
lsa_gp_pacing_intvl = optional(string, null) # "1"-"1800"
lsa_hold_intvl = optional(string, null) # "50"-"30000"
lsa_max_intvl = optional(string, null) # "50"-"30000"
lsa_start_intvl = optional(string, null) # "0"-"5000"
max_ecmp = optional(string, null) # "1"-"64"
max_lsa_action = optional(string, null) # reject | log | restart
max_lsa_num = optional(string, null) # "1"-"4294967295"
max_lsa_reset_intvl = optional(string, null) # "1"-"1440"
max_lsa_sleep_cnt = optional(string, null) # "1"-"4294967295"
max_lsa_sleep_intvl = optional(string, null) # "1"-"1440"
max_lsa_thresh = optional(string, null) # "1"-"100"
spf_hold_intvl = optional(string, null) # "1"-"600000"
spf_init_intvl = optional(string, null) # "1"-"600000"
spf_max_intvl = optional(string, null) # "1"-"600000"
}))
default = {}
# validations: name length/charset; ctrl subset; gr_ctrl/max_lsa_action enums
}
variable "ospf_route_summarization" {
type = map(object({
name = string # REQUIRED, immutable, 1-64 chars
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
description = optional(string, null)
cost = optional(string, null) # "unspecified" or "0"-"16777215"
inter_area_enabled = optional(bool, false) # rendered to yes/no
tag = optional(string, null)
}))
default = {}
# validations: name length/charset; cost range
}
variable "vrf_ospf_timer_relations" {
type = map(object({
vrf_dn = string # REQUIRED. VRF DN, e.g. module.vrf.id.
address_family = string # REQUIRED. ipv4-ucast | ipv6-ucast.
ospf_timers_name = string # REQUIRED. Name of an ospf_timers policy in this tenant.
annotation = optional(string, "orchestrator:terraform")
}))
default = {}
# validations: vrf_dn DN format; address_family enum; ospf_timers_name length
}| Output | Description | Notes |
|---|---|---|
id |
OSPF interface policy Distinguished Name (uni/tn-{tenant}/ospfIfPol-{name}) |
Primary cross-module reference — reference from an L3Out interface profile. |
name |
OSPF interface policy name | For composition / audit. |
ospf_timers_dns |
Map of ospf_timers key → timers policy DN |
For downstream reference / audit. |
ospf_route_summarization_dns |
Map of ospf_route_summarization key → route-summarization policy DN |
For downstream reference / audit. |
vrf_ospf_timer_relation_dns |
Map of vrf_ospf_timer_relations key → relation DN |
For downstream reference / audit. |
- Four resources, one keystone.
aci_ospf_interface_policy.thisis the keystone;aci_ospf_timers.this,aci_ospf_route_summarization.this, andaci_relation_from_vrf_to_address_family_ospf_timers.thisare each iterated withfor_eachover their own typed map — nevercount— so inserting or removing one policy never churns the others' state addresses. - Sibling policies, not nested children. The OSPF interface, timers, and route-summarization policies are all created directly under the tenant DN — none is a DN child of another, unlike a bridge domain's subnets. The module bundles them for authoring/review convenience, not because the ACI object model nests them.
- Classic resources,
tenant_dnis correct here.aci_ospf_interface_policy,aci_ospf_timers, andaci_ospf_route_summarizationhave noparent_dnattribute in the live provider schema —tenant_dnis the only (and current) way to set their parent. Only the VRF-binding relation is migrated and usesparent_dn. - The VRF binding reaches across modules.
aci_relation_from_vrf_to_address_family_ospf_timersis created under the VRF's DN (owned byterraform-aci-vrf), not the tenant, and references this module's own timers policies by name — the one place this module writes into another module's object. - Numeric-as-string fields, validated at plan time. Every OSPF timer/control field is a string in the provider schema but carries a documented numeric range; this module validates each range (and the
costfields'"unspecified"sentinel) withcan(tonumber(...))checks so out-of-range values fail atterraform validate, not at apply. - Null-when-empty for
ctrl. Each resource'sctrllist is passed asnull(not an empty list) when the caller supplies none, so the module never fights provider-computed state. - Boolean ergonomics.
ospf_route_summarization[*].inter_area_enabledis surfaced asbooland rendered to the provider's"yes"/"no"string inmain.tf. - Secure by default. The minimal call for each policy leaves every timer/control at the provider's own computed default, and no VRF binding, timers policy, or summarization policy is created unless explicitly supplied.
| Concern | Secure default | How to opt out (deliberately) |
|---|---|---|
ospf_interface_policy.* numeric/enum fields |
null — inherit the provider's own computed default |
Set explicitly per the documented range/enum. |
ospf_timers[*].gr_ctrl |
null — inherit the provider default (helper, graceful restart on) |
Set "" to explicitly disable graceful restart. |
ospf_route_summarization[*].inter_area_enabled |
false — no inter-area summarization |
Set true to opt in per policy. |
ospf_timers / ospf_route_summarization / vrf_ospf_timer_relations |
{} — no sibling policies or bindings created |
Populate the maps explicitly. |
annotation (every policy) |
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 — these policies carry no secret material; credentials are provider config. |
# 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/applyagainst a sub-production APIC from their own pipeline, with a login scoped to the permissions above. No cloud apply happens here.
The offline proof gate for this module:
- ✅
terraform validate— parses the module, resolves theospf_interface_policy,ospf_timers,ospf_route_summarization, andvrf_ospf_timer_relationsobject types, runs the name/enum/numeric-range validations, and confirms every argument exists in the provider schema. - ✅
terraform fmt -check— canonical formatting. - ⛔ Not exercised offline (only a real
plan/applyagainst an APIC covers these): DN validation ofvrf_ospf_timer_relations[*].vrf_dnandospf_timers_name(server-sidevalidate_relation_dn), APIC-side name-collision checks, OSPF neighbor-adjacency behavior from the configured timers, and the computed DNs returned asid/ the three DN maps.
$ terraform output
id = "uni/tn-core-prod/ospfIfPol-ospf-if-core"
name = "ospf-if-core"
ospf_timers_dns = {
"core-vrf-timers" = "uni/tn-core-prod/ospfCtxPol-core-vrf-timers"
}
ospf_route_summarization_dns = {
"branch-summary" = "uni/tn-core-prod/ospfRtSummPol-branch-summary"
}
vrf_ospf_timer_relation_dns = {
"core-vrf-ipv4" = "uni/tn-core-prod/ctx-core-vrf/rsctxToOspfCtxPol-[core-vrf-timers]-ipv4-ucast"
}
| Symptom | Cause | Fix |
|---|---|---|
ospf_interface_policy.name must be 1-64 characters (or the timers/route-summarization equivalent) |
Name is empty or too long | Use a 1-64 character name. |
... may contain only letters, digits, and the characters _ . : - |
Name has spaces or unsupported characters | Remove spaces/special characters (ACI naming rules). |
... must be null or a number (range error) |
A numeric OSPF field is outside its documented range | Use a value within the documented range, or leave it null for the provider default. |
... must be one of: ... (enum error) |
ctrl, nw_t, pfx_suppress, gr_ctrl, max_lsa_action, or address_family is outside its closed enum |
Use one of the documented values for that field. |
Changing a policy's name wants to destroy/recreate it |
name is immutable (force-new) on all three tenant-scoped policies |
Treat a rename as a migration; update any vrf_ospf_timer_relations[*].ospf_timers_name that referenced the old name. |
Apply fails validating vrf_ospf_timer_relations[*].vrf_dn |
The referenced VRF does not exist yet | Create the VRF first (or in the same apply); do not disable validate_relation_dn. |
Apply fails validating ospf_timers_name |
The named aci_ospf_timers policy does not exist in the same tenant |
Add it to this module's ospf_timers map (or confirm the other module instance that manages it applies first). |
| OSPF neighbors never form an adjacency | hello_intvl / dead_intvl mismatch between neighbors, or ctrl = ["passive"] set unintentionally |
Align interface-policy timers across the link; remove passive if hellos should be sent. |
Post ... 401 / authentication error |
Provider not configured or wrong credentials | Configure the aci provider with valid credentials and url; prefer signature auth for automation. |
- Cisco ACI provider —
aci_ospf_interface_policy - Cisco ACI provider —
aci_ospf_timers - Cisco ACI provider —
aci_ospf_route_summarization - Cisco ACI provider —
aci_relation_from_vrf_to_address_family_ospf_timers - Cisco ACI provider — provider configuration & authentication
- Cisco APIC object model — classes
ospfIfPol,ospfCtxPol,ospfRtSummPol, andfvRsCtxToOspfCtxPol. - Sibling modules:
terraform-aci-tenant,terraform-aci-vrf,terraform-aci-l3-outside. - This module's
SCOPE.md— the cross-module contract.
💙 "Infrastructure as Code should be standardized, consistent, and secure."