Manage a Cisco ACI NetFlow monitor policy together with its exporter and record policies, and the bridge domains that export flow data through it (class
netflowMonitorPol, DNuni/infra/monitorpol-{name}oruni/tn-{name}/monitorpol-{name}), as a typed, secure-by-default building block targetingCiscoDevNet/aci ~> 2.20.
This module manages an ACI NetFlow monitor policy together with its exporters, record, and bridge-domain bindings as one coherent, secure-by-default unit:
- π‘ The monitor policy (
aci_netflow_monitor_policy.this) β the keystone that ties a record (what to collect) to one or more exporters (where to send it), addressed by the Distinguished Nameuni/infra/monitorpol-{name}(fabric-wide, the default) oruni/tn-{name}/monitorpol-{name}(tenant-scoped). - π€ Exporter policies (
aci_netflow_exporter_policy.this,for_each) β the flow-collector destinations, keyed by a stable natural key, each automatically attached to the monitor policy. - π§Ύ A record policy (
aci_netflow_record_policy.this,for_eachover 0 or 1) β the match/collect field set, automatically wired into the monitor policy's single record relation. - π Bridge-domain bindings (
aci_relation_from_bridge_domain_to_netflow_monitor_policy.this,for_each) β the from-the-other-side attachments that turn flow export on for one or more bridge domains. - π·οΈ The ACI metadata tail β
annotation(preserved asorchestrator:terraform),name_alias, anddescriptionon every object this module manages, plusowner_key/owner_tagand theannotations/tagskey-value lists. - π Dual scope, never credentials β
parent_dndefaults to the fabric-wideuni/infraand can instead target a single tenant; authentication and the APIC URL are the caller's provider concern and are never module variables.
π‘ Why it matters: NetFlow visibility is only useful if the monitor policy, its record, its exporters, and the bridge domains that use it are all consistent β a record with no exporter attached silently collects nothing, and a monitor policy with no bridge-domain binding never sees traffic. Managing all four as one unit means a reviewer sees the whole flow-export path in a single diff.
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
bd["terraform-aci-bridge-domain"]:::sib
vrf["terraform-aci-vrf"]:::sib
epg["terraform-aci-application-epg"]:::sib
nf["terraform-aci-netflow (this module)"]:::this
mon["aci_netflow_monitor_policy - class netflowMonitorPol"]:::keystone
exp["aci_netflow_exporter_policy (for_each)"]:::keystone
rec["aci_netflow_record_policy (for_each 0..1)"]:::keystone
bind["aci_relation_from_bridge_domain_to_netflow_monitor_policy (for_each)"]:::keystone
tenant -->|"parent_dn (optional, tenant-scoped)"| nf
nf -->|"manages"| mon
nf -->|"for_each exporters"| exp
nf -->|"for_each record (0..1)"| rec
nf -->|"for_each bridge_domain_bindings"| bind
bd -->|"bridge_domain_dn"| bind
vrf -->|"relation_to_vrf (by DN)"| exp
epg -->|"relation_to_epg (by DN)"| exp
classDef this fill:#00BCEB,color:#fff,stroke:#00BCEB;
classDef keystone fill:#0D274D,color:#fff,stroke:#0D274D;
classDef sib fill:#f5f5f5,color:#333,stroke:#cccccc;
The monitor policy takes an optional parent scope (parent_dn) β fabric-wide by default, or a tenant when
supplied. Exporters and the record are created alongside it and wired in automatically; bridge domains are
attached from their own side through bridge_domain_bindings, and exporters may in turn reference an EPG or
VRF by DN for their source context.
graph TD
pdn["parent_dn (optional, default uni/infra)"]:::in
moni["netflow_monitor_policy object"]:::in
expi["exporters map (for_each)"]:::in
reci["record object (0..1)"]:::in
bindi["bridge_domain_bindings map (for_each)"]:::in
this["aci_netflow_monitor_policy.this (keystone, netflowMonitorPol)"]:::this
exp["aci_netflow_exporter_policy.this (for_each, netflowExporterPol)"]:::this
rec["aci_netflow_record_policy.this (for_each 0..1, netflowRecordPol)"]:::this
bind["aci_relation_from_bridge_domain_to_netflow_monitor_policy.this (for_each, fvRsBDToNetflowMonitorPol)"]:::this
oid["output: id (monitor policy DN)"]:::out
oex["output: exporter_dns (map)"]:::out
orc["output: record_dn"]:::out
obd["output: bridge_domain_binding_dns (map)"]:::out
pdn --> this
moni --> this
pdn --> exp
expi --> exp
pdn --> rec
reci --> rec
bindi --> bind
this --> bind
expi --> this
reci --> this
this --> oid
exp --> oex
rec --> orc
bind --> obd
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_netflow_monitor_policy |
this |
1 (keystone) | The monitor policy tying record + exporters together (netflowMonitorPol). |
aci_netflow_exporter_policy |
this |
0..N (for_each over exporters) |
Flow-collector destinations (netflowExporterPol). |
aci_netflow_record_policy |
this |
0..1 (for_each over record) |
The match/collect field set (netflowRecordPol). |
aci_relation_from_bridge_domain_to_netflow_monitor_policy |
this |
0..N (for_each over bridge_domain_bindings) |
Attaches a bridge domain to this monitor policy (fvRsBDToNetflowMonitorPol). |
| 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 | Dual β fabric-wide (uni/infra, the default) or a single tenant, via the optional parent_dn. |
Schema notes that bite (verified against the live provider schema):
- π
netflow_monitor_policy.name, everyexporters[*].name, andrecord.nameare immutable. Changing any of them forces replacement of that specific object. - βΉοΈ
parent_dnis genuinely optional here, not a deprecated attribute to avoid. The monitor policy, exporter, and record resources expose onlyparent_dn(notenant_dncounterpart to migrate away from) and the provider computes a default ofuni/infrawhen it is left unset β this module mirrors that default rather than forcing a required parent. β οΈ aci_relation_from_bridge_domain_to_netflow_monitor_policypoints the other way. Itsparent_dnis required and is the bridge domain's DN, not the monitor policy's β it is a from-the-other-side binding, not a child of this module's own keystone DN.- βΉοΈ All four resources are migrated (plugin-framework).
annotations/tagsare typed{key, value}lists and every relation (relation_to_netflow_exporters,relation_to_netflow_record,relation_to_epg,relation_to_vrf) is a typed nested attribute β assigned with=, not HCLblock {}stanzas. β οΈ exporters[*].destination_portandexporters[*].qos_dscp_valueare dual-form. Each accepts either a closed set of named values (dns/ftpData/β¦/unspecified;AF11β¦VA) or a raw numeric string (0-65535;0-63) β both forms are validated at plan time.- βΉοΈ
aci_relation_to_netflow_exporteris intentionally not modeled. It is a standalone alternative that manages the samenetflowRsMonitorToExporterrelation already covered by the keystone's typedrelation_to_netflow_exportersattribute β using both would fight over one relation object. β οΈ validate_relation_dn(provider defaulttrue) fails apply if abridge_domain_bindings[*].bridge_domain_dn, or an exporter'srelation_to_epg/relation_to_vrftarget, does not exist.
Scope the caller's APIC login to the least privilege this module needs:
- Fabric-wide monitor policy / exporter / record (
parent_dn = "uni/infra", the default): thefabric-adminrole (or a custom role with fabric-infra-policy write privilege), scoped to theallsecurity domain. - Tenant-scoped monitor policy / exporter / record: the
tenant-adminrole (or a custom role with tenant-networking write privilege), scoped to the tenant's own security domain. - Bridge-domain bindings: write on the target bridge domain's security domain β each binding is authored under the BD's own DN.
- Referenced EPG / VRF: read privilege on any object named in an exporter's
relation_to_epg/relation_to_vrf.
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. - If
parent_dntargets a tenant, that tenant already exists. - Every bridge domain named in
bridge_domain_bindingsalready exists (or is created in the same apply). - Any EPG / VRF referenced by an exporter's
relation_to_epg/relation_to_vrfalready exists. - APIC 2.2(1k) or later for the base resources; 3.2(1l) or later for
annotation/annotations/tagssupport on all four resources.
terraform-aci-netflow/
βββ providers.tf # terraform{} + required_providers (aci ~> 2.20); no provider block
βββ variables.tf # parent_dn + netflow_monitor_policy + exporters + record + bridge_domain_bindings β deeply typed, secure defaults, heredoc schema, validations
βββ main.tf # aci_netflow_monitor_policy.this (keystone) + aci_netflow_exporter_policy.this / aci_netflow_record_policy.this / aci_relation_from_bridge_domain_to_netflow_monitor_policy.this (for_each children)
βββ outputs.tf # id (the monitor policy DN) first, then name, exporter_dns, record_dn, bridge_domain_binding_dns
βββ 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 "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
}
output "netflow_monitor_policy_dn" {
value = module.netflow.id # fabric-wide by default (uni/infra/monitorpol-prod-netflow)
}Consumes
| Input | Type | Typical source |
|---|---|---|
parent_dn |
string (DN, default "uni/infra") |
terraform-aci-tenant (optional, when tenant-scoped) |
netflow_monitor_policy |
object({...}) |
caller (name + metadata tail) |
exporters |
map(object({...})) |
caller (optionally EPG/VRF DNs from terraform-aci-application-epg / terraform-aci-vrf) |
record |
object({...}) or null |
caller |
bridge_domain_bindings |
map(object({...})) |
terraform-aci-bridge-domain (BD DN) |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Monitor policy DN (uni/infra/monitorpol-{name} or uni/tn-{name}/monitorpol-{name}) β primary reference |
audits / any module referencing this policy directly |
name |
Monitor policy name | composition / audit |
exporter_dns |
Map of exporter key β exporter policy DN | audits / downstream reference |
record_dn |
DN of the managed record policy, or null |
audits / downstream reference |
bridge_domain_binding_dns |
Map of binding key β relation DN | audits / downstream reference |
1 Β· Minimal β a fabric-wide monitor policy with secure defaults
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
}π‘ The minimal call creates only the monitor policy under the fabric-wide default
uni/infra.annotationis preserved asorchestrator:terraform, and no exporter, record, or bridge-domain binding is created yet.
2 Β· Tenant-scoped monitor policy
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
parent_dn = module.tenant.id # uni/tn-core-prod
netflow_monitor_policy = { name = "tenant-netflow" }
}βΉοΈ Setting
parent_dnto a tenant DN scopes the monitor policy (and any exporter/record created alongside it) to that tenant instead of the fabric-wide default.
3 Β· A single UDP exporter
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
exporters = {
"collector-1" = {
name = "collector-1"
destination_ip_address = "10.20.30.40"
destination_port = "2055"
}
}
}π‘
exportersis a map keyed by a stable natural key β here the collector's logical name β so adding a second exporter later never disturbs this one's address in state. Every entry is automatically wired into the monitor policy's exporter relation.
4 Β· Multiple exporters (redundant collectors)
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
exporters = {
"collector-1" = { name = "collector-1", destination_ip_address = "10.20.30.40", destination_port = "2055" }
"collector-2" = { name = "collector-2", destination_ip_address = "10.20.30.41", destination_port = "2055" }
}
}βΉοΈ Each map entry becomes its own
aci_netflow_exporter_policyresource viafor_eachβ order-independent, and safe to add or remove entries without touching the others.
5 Β· An exporter with a named destination port and DSCP class
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
exporters = {
"collector-1" = {
name = "collector-1"
destination_ip_address = "10.20.30.40"
destination_port = "https"
qos_dscp_value = "AF31"
version = "v9"
}
}
}βΉοΈ
destination_portandqos_dscp_valueeach accept either a named value from a closed set or a raw numeric string β both are validated at plan time.
6 Β· An exporter sourced from a specific IP address
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
exporters = {
"collector-1" = {
name = "collector-1"
destination_ip_address = "10.20.30.40"
source_ip_address = "10.0.0.254"
source_ip_type = "custom-src-ip"
}
}
}βΉοΈ
source_ip_type = "custom-src-ip"(the default) usessource_ip_addressverbatim; set it toinband-mgmt-ip,oob-mgmt-ip, orptepto source flows from a management or tunnel-endpoint address instead.
7 Β· An exporter bound to a VRF by DN
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
exporters = {
"collector-1" = {
name = "collector-1"
destination_ip_address = "10.20.30.40"
relation_to_vrf = { target_dn = module.vrf.id }
}
}
}
β οΈ relation_to_vrf.target_dnmust reference a VRF that already exists β the provider'svalidate_relation_dncheck fails the apply otherwise.
8 Β· A record policy with explicit match and collect parameters
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
record = {
name = "prod-record"
match_parameters = ["src-ip", "dst-ip", "src-port", "dst-port", "proto"]
collect_parameters = ["count-bytes", "count-pkts", "tcp-flags"]
}
}π‘
recordis a single optional object, not a map β the provider supports only one record relation per monitor policy. Setting it automatically attaches the record viarelation_to_netflow_record.
9 Β· Monitor policy, exporter, and record together
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
exporters = {
"collector-1" = { name = "collector-1", destination_ip_address = "10.20.30.40" }
}
record = {
name = "prod-record"
match_parameters = ["src-ip", "dst-ip"]
}
}βΉοΈ This is the smallest complete flow-export path: a record defining what to key on, one exporter defining where flows go, and the monitor policy tying them together.
10 Β· Attaching the policy to a single bridge domain
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
bridge_domain_bindings = {
"app-bd" = { bridge_domain_dn = module.bridge_domain.id }
}
}π‘
bridge_domain_bindingsis keyed by a stable natural key β here the BD's own name β and each entry is authored under the bridge domain's DN, not this module's monitor-policy DN.
11 Β· Attaching the policy to multiple bridge domains with a specific filter type
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = { name = "prod-netflow" }
bridge_domain_bindings = {
"app-bd" = { bridge_domain_dn = module.bridge_domain_app.id, filter_type = "ipv4" }
"db-bd" = { bridge_domain_dn = module.bridge_domain_db.id, filter_type = "ipv4" }
}
}βΉοΈ
filter_typenarrows the binding to a specific traffic class (ce,ipv4,ipv6) instead of the defaultunspecified(all traffic).
12 Β· User metadata via the annotations and tags lists
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = {
name = "prod-netflow"
annotations = {
"cost-center" = "CC-4021"
"environment" = "production"
}
tags = {
"tier" = "gold"
}
}
}βΉοΈ
annotationsandtagsare given as ergonomic{ key = value }maps and rendered as the ACItagAnnotation/tagTag{key, value}lists.
13 Β· Ownership and correlation keys
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
netflow_monitor_policy = {
name = "prod-netflow"
owner_key = "cmdb-7710"
owner_tag = "provisioned-by-network-automation"
}
}βΉοΈ
owner_key/owner_taglet external systems correlate this policy with their own records β neither carries security posture.
14 Β· A fully-specified monitor policy (record + exporter + binding + metadata combined)
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
parent_dn = module.tenant.id
netflow_monitor_policy = {
name = "tenant-netflow"
name_alias = "Tenant-NetFlow"
description = "Tenant-scoped NetFlow monitor policy"
annotations = { "environment" = "production" }
}
exporters = {
"collector-1" = {
name = "collector-1"
destination_ip_address = "10.20.30.40"
destination_port = "2055"
relation_to_vrf = { target_dn = module.vrf.id }
}
}
record = {
name = "tenant-record"
match_parameters = ["src-ip", "dst-ip", "src-port", "dst-port"]
collect_parameters = ["count-bytes", "count-pkts"]
}
bridge_domain_bindings = {
"app-bd" = { bridge_domain_dn = module.bridge_domain.id, filter_type = "ipv4" }
}
}15 Β· ποΈ End-to-end composition β tenant β VRF β bridge domain β NetFlow monitor policy
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 L3 context the bridge domain routes within.
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 bridge domain bound to the VRF.
module "bridge_domain" {
source = "git::https://github.com/microsoftexpert/terraform-aci-bridge-domain.git?ref=v1.0.0"
tenant_dn = module.tenant.id
bridge_domain = {
name = "app-bd"
relation_to_vrf = { vrf_name = module.vrf.name }
}
subnets = {
"10.0.1.1/24" = { ip = "10.0.1.1/24", preferred = true }
}
}
# 4) This module: a tenant-scoped monitor policy with its exporter and record,
# attached to the bridge domain above.
module "netflow" {
source = "git::https://github.com/microsoftexpert/terraform-aci-netflow.git?ref=v1.0.0"
parent_dn = module.tenant.id
netflow_monitor_policy = {
name = "app-netflow"
description = "Flow export for the application tier"
}
exporters = {
"collector-1" = {
name = "collector-1"
destination_ip_address = "10.20.30.40"
destination_port = "2055"
relation_to_vrf = { target_dn = module.vrf.id }
}
}
record = {
name = "app-record"
match_parameters = ["src-ip", "dst-ip", "src-port", "dst-port"]
}
bridge_domain_bindings = {
"app-bd" = { bridge_domain_dn = module.bridge_domain.id, filter_type = "ipv4" }
}
}
output "netflow_monitor_policy_dn" { value = module.netflow.id }ποΈ One tenant, VRF, and bridge domain in; a fully-wired flow-export path out β the monitor policy, its exporter, its record, and the bridge-domain binding all come from a single module block, keyed to the same tenant and VRF the bridge domain already uses.
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
parent_dn |
string |
β | "uni/infra" |
Fabric-wide (default) or a tenant DN (uni/tn-{name}); wired to every resource's parent_dn. |
netflow_monitor_policy |
object({...}) |
β | β | The monitor policy: name plus the metadata tail. |
exporters |
map(object({...})) |
β | {} |
Exporter policies, keyed by a stable natural key. |
record |
object({...}) |
β | null |
The single record policy, or null to manage none. |
bridge_domain_bindings |
map(object({...})) |
β | {} |
Bridge domains bound to this monitor policy, keyed by a stable natural key. |
Full input schema (from variables.tf)
variable "parent_dn" {
type = string
default = "uni/infra"
# validation: must be "uni/infra" or a tenant DN of the form uni/tn-{name}
}
variable "netflow_monitor_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.
owner_key = optional(string, null) # Client correlation key.
owner_tag = optional(string, null) # Client correlation tag.
annotations = optional(map(string), {}) # tagAnnotation {key,value} pairs as a map.
tags = optional(map(string), {}) # tagTag {key,value} pairs as a map.
})
# validations: name length/charset
}
variable "exporters" {
type = map(object({
name = string # REQUIRED, immutable, 1-64 chars
destination_ip_address = string # REQUIRED. Flow-collector IP.
destination_port = optional(string, "unspecified") # Named service or numeric 0-65535.
source_ip_address = optional(string, null)
source_ip_type = optional(string, "custom-src-ip") # custom-src-ip | inband-mgmt-ip | oob-mgmt-ip | ptep.
version = optional(string, "v9") # cisco-v1 | v5 | v9.
qos_dscp_value = optional(string, "CS2") # Named DSCP class or numeric 0-63.
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
description = optional(string, null)
owner_key = optional(string, null)
owner_tag = optional(string, null)
annotations = optional(map(string), {})
tags = optional(map(string), {})
relation_to_epg = optional(object({ target_dn = string }), null) # by DN.
relation_to_vrf = optional(object({ target_dn = string }), null) # by DN.
}))
default = {}
# validations: name length/charset; destination_port; source_ip_type; version; qos_dscp_value
}
variable "record" {
type = object({
name = string # REQUIRED, immutable, 1-64 chars
collect_parameters = optional(list(string), []) # Subset of a closed enum.
match_parameters = optional(list(string), []) # Subset of a closed enum.
annotation = optional(string, "orchestrator:terraform")
name_alias = optional(string, null)
description = optional(string, null)
owner_key = optional(string, null)
owner_tag = optional(string, null)
annotations = optional(map(string), {})
tags = optional(map(string), {})
})
default = null
# validations (when not null): name length/charset; collect_parameters / match_parameters subsets
}
variable "bridge_domain_bindings" {
type = map(object({
bridge_domain_dn = string # REQUIRED. DN uni/tn-{name}/BD-{name}.
filter_type = optional(string, "unspecified") # ce | ipv4 | ipv6 | unspecified.
}))
default = {}
# validations: bridge_domain_dn format; filter_type enum
}| Output | Description | Notes |
|---|---|---|
id |
Monitor policy Distinguished Name (uni/infra/monitorpol-{name} or uni/tn-{name}/monitorpol-{name}) |
Primary cross-module reference. |
name |
Monitor policy name | For composition / audit. |
exporter_dns |
Map of exporter key β exporter policy DN | For downstream reference / audit. |
record_dn |
DN of the managed record policy, or null |
Conditional β null when record is not set. |
bridge_domain_binding_dns |
Map of binding key β relation DN | For downstream reference / audit. |
- One keystone, three families of children.
aci_netflow_monitor_policy.thisis the keystone.aci_netflow_exporter_policy.thisandaci_netflow_record_policy.thisare siblings created under the sameparent_dnand wired into the keystone's typed relations;aci_relation_from_bridge_domain_to_netflow_monitor_policy.thisis authored under a different parent (the bridge domain) but exists purely to attach this monitor policy, so it is modeled here rather than in a separate binding module. - Relations wired from input, not from resource attributes.
relation_to_netflow_exportersandrelation_to_netflow_recordare built fromvar.exporters[*].name/var.record.namedirectly, the same pattern the bridge-domain module uses forrelation_to_vrfβ this avoids an unnecessary implicit dependency ordering while still referencing the correct name. - The record is a 0..1
for_each, nevercount. Expressing the optional record asfor_eachover a map with at most one entry keeps addressing stable if the module is later extended, consistent with the suite's never-countconvention. - Dual-scope parent, matching the live schema.
parent_dndefaults to"uni/infra"(fabric-wide) and accepts a tenant DN instead β this mirrors the provider's own computed default rather than forcing every NetFlow object to belong to a tenant. - Null-when-empty for lists.
annotations/tagsare passed asnull(not an empty list) whenever empty, so the module never fights provider-computed state or produces a spurious diff. - Secure by omission. The minimal call creates only the monitor policy β no exporter, no record, and no bridge-domain binding β so no flow data leaves the fabric until explicitly configured.
| Concern | Secure default | How to opt out (deliberately) |
|---|---|---|
parent_dn |
"uni/infra" β fabric-wide, matching the provider's own computed default |
Set to a tenant DN to scope the policy to that tenant. |
exporters |
{} β no flow-collector destinations configured |
Add entries to send flow data somewhere. |
record |
null β no record policy managed |
Set an object to define match/collect fields. |
bridge_domain_bindings |
{} β no bridge domain exports flow data via this policy |
Add entries to turn on export for specific bridge domains. |
exporters[*].source_ip_type |
custom-src-ip β an explicit source address, not an inferred one |
Switch to inband-mgmt-ip / oob-mgmt-ip / ptep only where that source is intended. |
netflow_monitor_policy.annotation |
orchestrator:terraform β Terraform-managed objects stay identifiable in APIC |
Extend the marker; do not blank it. |
annotations / tags |
{} β no user metadata, no spurious diffs |
Populate the maps explicitly. |
| 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 β NetFlow policy objects 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 thenetflow_monitor_policy/exporters/record/bridge_domain_bindingsobject types, runs every name/enum/DN-format validation, 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 ofbridge_domain_bindings[*].bridge_domain_dnand exporters'relation_to_epg/relation_to_vrf(server-sidevalidate_relation_dn), APIC-side name-collision checks, and the computed DNs returned asid/exporter_dns/record_dn/bridge_domain_binding_dns.
$ terraform output
id = "uni/tn-core-prod/monitorpol-app-netflow"
name = "app-netflow"
exporter_dns = {
"collector-1" = "uni/tn-core-prod/exporterpol-collector-1"
}
record_dn = "uni/tn-core-prod/recordpol-app-record"
bridge_domain_binding_dns = {
"app-bd" = "uni/tn-core-prod/BD-app-bd/rsBDToNetflowMonitorPol-[app-netflow]-ipv4"
}
| Symptom | Cause | Fix |
|---|---|---|
netflow_monitor_policy.name must be 1-64 characters |
Name is empty or too long | Use a 1-64 character name. |
netflow_monitor_policy.name may contain only letters, digits, and the characters _ . : - |
Name has spaces or unsupported characters | Remove spaces/special characters (ACI naming rules). |
parent_dn must be either "uni/infra" ... or a tenant DN |
parent_dn set to something other than uni/infra or uni/tn-{name} |
Use the fabric-wide default or a valid tenant DN. |
... destination_port must be a named service ... or a numeric port 0-65535 |
An exporter's destination_port is outside both accepted forms |
Use a named service or a numeric string in range. |
... qos_dscp_value must be a named DSCP class ... or a numeric value 0-63 |
An exporter's qos_dscp_value is outside both accepted forms |
Use a named DSCP class or a numeric string in range. |
bridge_domain_bindings[*].bridge_domain_dn must be a bridge domain DN of the form uni/tn-{name}/BD-{name} |
A binding's bridge_domain_dn is malformed |
Pass the bridge domain module's id output directly. |
| Apply fails validating a bridge-domain binding or an exporter's EPG/VRF relation | The referenced object does not exist yet | Create the referenced object first (or in the same apply); do not disable validate_relation_dn. |
| A bridge domain never seems to export flow data | No bridge_domain_bindings entry references it, or no exporter/record is attached |
Add a binding for that BD and confirm both exporters and record are set on the monitor policy. |
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_netflow_monitor_policy - Cisco ACI provider β
aci_netflow_exporter_policy - Cisco ACI provider β
aci_netflow_record_policy - Cisco ACI provider β
aci_relation_from_bridge_domain_to_netflow_monitor_policy - Cisco ACI provider β provider configuration & authentication
- Cisco APIC object model β classes
netflowMonitorPol,netflowExporterPol,netflowRecordPol, andfvRsBDToNetflowMonitorPol. - Sibling modules:
terraform-aci-tenant,terraform-aci-vrf,terraform-aci-bridge-domain,terraform-aci-application-epg. - This module's
SCOPE.mdβ the cross-module contract.
π "Infrastructure as Code should be standardized, consistent, and secure."