Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

πŸ’™ Cisco ACI NetFlow Monitor Policy Terraform Module

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, DN uni/infra/monitorpol-{name} or uni/tn-{name}/monitorpol-{name}), as a typed, secure-by-default building block targeting CiscoDevNet/aci ~> 2.20.

Terraform Provider Module Version Type Resources

🧩 Overview

This module manages an ACI 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 Name uni/infra/monitorpol-{name} (fabric-wide, the default) or uni/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_each over 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 as orchestrator:terraform), name_alias, and description on every object this module manages, plus owner_key / owner_tag and the annotations / tags key-value lists.
  • πŸ”‘ Dual scope, never credentials β€” parent_dn defaults to the fabric-wide uni/infra and 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.

❀️ 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
  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;
Loading

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.

🧬 What this module builds

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;
Loading

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).

βœ… 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 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, every exporters[*].name, and record.name are immutable. Changing any of them forces replacement of that specific object.
  • ℹ️ parent_dn is genuinely optional here, not a deprecated attribute to avoid. The monitor policy, exporter, and record resources expose only parent_dn (no tenant_dn counterpart to migrate away from) and the provider computes a default of uni/infra when it is left unset β€” this module mirrors that default rather than forcing a required parent.
  • ⚠️ aci_relation_from_bridge_domain_to_netflow_monitor_policy points the other way. Its parent_dn is 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 / tags are 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 HCL block {} stanzas.
  • ⚠️ exporters[*].destination_port and exporters[*].qos_dscp_value are 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_exporter is intentionally not modeled. It is a standalone alternative that manages the same netflowRsMonitorToExporter relation already covered by the keystone's typed relation_to_netflow_exporters attribute β€” using both would fight over one relation object.
  • ⚠️ validate_relation_dn (provider default true) fails apply if a bridge_domain_bindings[*].bridge_domain_dn, or an exporter's relation_to_epg / relation_to_vrf target, does not exist.

πŸ”‘ Required APIC Roles & Privileges

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): the fabric-admin role (or a custom role with fabric-infra-policy write privilege), scoped to the all security domain.
  • Tenant-scoped monitor policy / exporter / record: the tenant-admin role (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).

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, set insecure = false with proper CA trust.
  • If parent_dn targets a tenant, that tenant already exists.
  • Every bridge domain named in bridge_domain_bindings already exists (or is created in the same apply).
  • Any EPG / VRF referenced by an exporter's relation_to_epg / relation_to_vrf already exists.
  • APIC 2.2(1k) or later for the base resources; 3.2(1l) or later for annotation / annotations / tags support on all four resources.

πŸ“ Module Structure

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

βš™οΈ 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 "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)
}

πŸ”Œ Cross-Module Contract

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

πŸ“š Example Library

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. annotation is preserved as orchestrator: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_dn to 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"
    }
  }
}

πŸ’‘ exporters is 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_policy resource via for_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_port and qos_dscp_value each 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) uses source_ip_address verbatim; set it to inband-mgmt-ip, oob-mgmt-ip, or ptep to 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_dn must reference a VRF that already exists β€” the provider's validate_relation_dn check 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"]
  }
}

πŸ’‘ record is a single optional object, not a map β€” the provider supports only one record relation per monitor policy. Setting it automatically attaches the record via relation_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_bindings is 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_type narrows the binding to a specific traffic class (ce, ipv4, ipv6) instead of the default unspecified (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"
    }
  }
}

ℹ️ annotations and tags are given as ergonomic { key = value } maps and rendered as the ACI tagAnnotation / 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_tag let 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.

πŸ“₯ Inputs

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
}

🧾 Outputs

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.

🧠 Architecture Notes

  • One keystone, three families of children. aci_netflow_monitor_policy.this is the keystone. aci_netflow_exporter_policy.this and aci_netflow_record_policy.this are siblings created under the same parent_dn and wired into the keystone's typed relations; aci_relation_from_bridge_domain_to_netflow_monitor_policy.this is 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_exporters and relation_to_netflow_record are built from var.exporters[*].name / var.record.name directly, the same pattern the bridge-domain module uses for relation_to_vrf β€” this avoids an unnecessary implicit dependency ordering while still referencing the correct name.
  • The record is a 0..1 for_each, never count. Expressing the optional record as for_each over a map with at most one entry keeps addressing stable if the module is later extended, consistent with the suite's never-count convention.
  • Dual-scope parent, matching the live schema. parent_dn defaults 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 / tags are passed as null (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.

🧱 Design Principles

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.

πŸš€ 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 netflow_monitor_policy / exporters / record / bridge_domain_bindings object 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 / apply against an APIC covers these): DN validation of bridge_domain_bindings[*].bridge_domain_dn and exporters' relation_to_epg / relation_to_vrf (server-side validate_relation_dn), APIC-side name-collision checks, and the computed DNs returned as id / exporter_dns / record_dn / bridge_domain_binding_dns.

πŸ’¬ Example Output

$ 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"
}

πŸ” Troubleshooting

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.

πŸ”— Related Docs


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

About

Terraform module: terraform-aci-netflow

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages