Aggregates all nine NIOS DNS record types — A, AAAA, PTR, CNAME, MX, TXT, SRV, NS, and Alias — behind one module, each an independently-optional
for_eachcollection keyed by a stable caller string. Built for infoblox v2.x / WAPI v2.12.3.
- 🟦 Manages nine NIOS DNS record resources —
infoblox_a_record,infoblox_aaaa_record,infoblox_ptr_record,infoblox_cname_record,infoblox_mx_record,infoblox_txt_record,infoblox_srv_record,infoblox_ns_record,infoblox_alias_record. - 🧩 Aggregation module — there is no
thiskeystone. Every record type is a namedfor_eachcollection (a_records,aaaa_records, …) over its ownmap(object(...)), keyed by a caller-supplied stable string. - 🎚️ Each collection is independently optional (defaults to
{}). Populate only the record types you need; the rest create nothing. - 🌐 Module-level shared defaults — one
dns_viewand onedefault_ttlapply to every record, with per-record overrides inside each object. - 🏷️ Exposes the universal NIOS fields
commentandext_attrs(plainmap(string),jsonencoded internally) on every record type exceptns_records, whose WAPI object has none. - 🔗 Emits one
{ key => WAPI ref }map per record type (the aggregation analogue of a singleid), plus a mergedrecord_fqdnsmap for monitoring and DTC wiring.
💡 Why it matters: DNS records share an identical shape (
fqdn,dns_view,comment,ext_attrs,ttl) and are almost always provisioned together against the same zone. One aggregation module with nine optional collections is the right boundary — far cleaner than nine separate module calls.
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!
flowchart TD
NV["terraform-infoblox-network-view<br/>(network_view)"]
DV["terraform-infoblox-dns-view<br/>(dns_view)"]
ZA["terraform-infoblox-zone-auth<br/>(authoritative zone)"]
DREC["terraform-infoblox-dns-records<br/>(9 record types)"]
DTC["terraform-infoblox-dtc<br/>(LBDN / pool / server)"]
IPAM["IPAM modules<br/>(network / range / allocation)"]
NV -->|network_view| DV
NV -->|network_view| IPAM
DV -->|dns_view| ZA
DV -->|dns_view| DREC
ZA -->|zone must exist| DREC
IPAM -.->|next_available_ip via cidr| DREC
DREC -->|record_fqdns| DTC
ZA -->|zone| DTC
classDef this fill:#00B4D8,color:#fff,stroke:#0077A3,stroke-width:2px;
class DREC this;
This module sits downstream of terraform-infoblox-dns-view and terraform-infoblox-zone-auth — the DNS view and the authoritative zone for every record's FQDN must already exist. Dynamic (cidr) A/AAAA/PTR records also depend on an IPAM network being present so NIOS can hand out the next-available address. The emitted record_fqdns map feeds DTC and monitoring.
flowchart TD
subgraph MOD["terraform-infoblox-dns-records (aggregation — no this keystone)"]
direction TB
A["infoblox_a_record<br/>for_each a_records"]
AAAA["infoblox_aaaa_record<br/>for_each aaaa_records"]
PTR["infoblox_ptr_record<br/>for_each ptr_records"]
CN["infoblox_cname_record<br/>for_each cname_records"]
MX["infoblox_mx_record<br/>for_each mx_records"]
TXT["infoblox_txt_record<br/>for_each txt_records"]
SRV["infoblox_srv_record<br/>for_each srv_records"]
NS["infoblox_ns_record<br/>for_each ns_records<br/>(no ttl/comment/ext_attrs)"]
AL["infoblox_alias_record<br/>for_each alias_records"]
end
DV["dns_view (shared default)"] --> MOD
TTL["default_ttl (shared default)"] --> MOD
classDef rec fill:#00B4D8,color:#fff,stroke:#0077A3,stroke-width:1px;
class A,AAAA,PTR,CN,MX,TXT,SRV,NS,AL rec;
Every box is a total for_each renderer over its collection. An empty collection ({}, the default) creates zero resources for that type — there is no overhead for record types you do not use.
| Requirement | Version |
|---|---|
| Terraform | >= 1.12.0 |
infobloxopen/infoblox |
>= 2.5.0, < 3.0 (GA v2.13.0 / WAPI v2.12.3) |
The module declares only a required_providers block — no provider "infoblox" {} block. Grid connection details are configured at the root/pipeline level (see Quick Start).
| Role / License | Required for | Notes |
|---|---|---|
| DNS Admin role (or a scoped admin group with read-write on the target zones/views) | Create / update / delete all nine record types | Sufficient for the entire module — no superuser needed for record CRUD. |
| Cloud Network Automation license (or superuser ) | The four required Extensible Attributes | The license auto-provisions the EAs; without it they must be created manually. |
| superuser | First-time auto-creation of the Terraform Internal ID EA |
Needed only if the EA is not already present in the grid. |
⚠️ Zone & superuser callout. The authoritative zone for every record FQDN must exist before apply — the NIOS plugin cannot create DNS zones (useterraform-infoblox-zone-authfirst). Record creation itself needs no superuser, but theTerraform Internal IDEA must exist; if your Terraform identity is a scoped DNS admin (not superuser), have a grid administrator pre-create all four EAs (below) before the firstterraform apply, or apply fails with a WAPI/connection error.
These four Extensible Attributes must exist in the NIOS grid before terraform apply. They are added automatically by the Cloud Network Automation license, or created manually / via cURL:
| # | Extensible Attribute | Type | Notes |
|---|---|---|---|
| 1 | Tenant ID |
String | Cloud tenancy identifier |
| 2 | CMP Type |
String | Cloud Management Platform type (e.g. Terraform) |
| 3 | Cloud API Owned |
List (True, False) |
Marks objects managed via the Cloud API |
| 4 | Terraform Internal ID |
String, read-only (CR flag) | Drift detection when a NIOS _ref changes outside Terraform |
Create the Terraform Internal ID EA via WAPI (adjust grid host / WAPI version as needed):
curl -k -u "$INFOBLOX_USERNAME:$INFOBLOX_PASSWORD" \
-H "Content-Type: application/json" \
-X POST "https://${INFOBLOX_SERVER}/wapi/v2.12.3/extensibleattributedef" \
-d '{
"name": "Terraform Internal ID",
"type": "STRING",
"flags": "CR",
"comment": "Internal ID for Terraform drift detection"
}'- WAPI version:
v2.12.3(provider default; align withWAPI_VERSION). - The
CRflags mark the EA as Cloud-API created and Read-only so operators do not edit it by hand.
terraform-infoblox-dns-records/
├── providers.tf # required_providers only — NO provider {} block
├── variables.tf # dns_view, default_ttl + nine record collections
├── main.tf # nine for_each resources, no `this` keystone
├── outputs.tf # nine *_record_ids maps + merged record_fqdns
├── README.md # this file
└── SCOPE.md # in/out-of-scope resources, permissions, gotchas
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
dns_view = "default"
a_records = {
web01 = {
fqdn = "web01.corp.example.com"
ip_addr = "10.20.30.10"
comment = "Primary web node"
}
}
cname_records = {
www = {
alias = "www.corp.example.com"
canonical = "web01.corp.example.com"
}
}
}Wire the dns_view from the upstream DNS view module, and ensure the zone exists:
module "dns_view" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-view?ref=v1.0.0"
name = "corp-internal"
}
module "zone" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-auth?ref=v1.0.0"
fqdn = "corp.example.com"
dns_view = module.dns_view.dns_view
}
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
dns_view = module.dns_view.dns_view # ← wired here
a_records = {
web01 = { fqdn = "web01.corp.example.com", ip_addr = "10.20.30.10" }
}
depends_on = [module.zone] # zone must exist before records
}Grid credentials (
INFOBLOX_SERVER/INFOBLOX_USERNAME/INFOBLOX_PASSWORD, plusSSLMODE/WAPI_VERSION) are configured at the root / pipeline level — never inside this module.
| Input | Type | Source module |
|---|---|---|
dns_view |
string |
terraform-infoblox-dns-view (dns_view output) |
| zone existence | (implicit) | terraform-infoblox-zone-auth — the zone in every fqdn must exist first |
network_view + a network |
(implicit) | IPAM modules — only for dynamic (cidr) A/AAAA/PTR allocation |
All outputs are maps keyed by the caller's collection key — never null, empty {} when a collection is unused.
| Output | Description | Consumed by |
|---|---|---|
a_record_ids |
{ key => WAPI ref } for A records (e.g. record:a/ZG5z...) |
cross-module wiring, monitoring |
aaaa_record_ids |
{ key => WAPI ref } for AAAA records |
wiring |
ptr_record_ids |
{ key => WAPI ref } for PTR records |
wiring |
cname_record_ids |
{ key => WAPI ref } for CNAME records |
wiring |
mx_record_ids |
{ key => WAPI ref } for MX records |
wiring |
txt_record_ids |
{ key => WAPI ref } for TXT records |
wiring |
srv_record_ids |
{ key => WAPI ref } for SRV records |
wiring |
ns_record_ids |
{ key => WAPI ref } for NS records |
wiring |
alias_record_ids |
{ key => WAPI ref } for Alias records |
wiring |
record_fqdns |
merged { "type:key" => name } across all populated collections |
DTC, monitoring |
The primary identifier convention (
id) is realised here as the per-type*_record_idsmaps — the aggregation analogue of a singleidoutput. Each WAPI ref is the canonical identifier forterraform importand ID-based wiring.
1 · Minimal — a single static A record
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
a_records = {
web01 = {
fqdn = "web01.corp.example.com"
ip_addr = "10.20.30.10"
}
}
}2 · A record with dynamic (next-available) allocation
Set cidr instead of ip_addr to let NIOS allocate the next-available IPv4 from a network. The two are mutually exclusive (validated).
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
a_records = {
app01 = {
fqdn = "app01.corp.example.com"
cidr = "10.20.30.0/24" # next_available_ip drawn from here
network_view = "default"
comment = "Dynamically allocated app node"
}
}
}3 · A record with extensible attributes (jsonencode pattern)
Callers pass a plain map(string); the module jsonencodes it internally and omits it entirely when empty (no "{}" drift).
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
a_records = {
web01 = {
fqdn = "web01.corp.example.com"
ip_addr = "10.20.30.10"
ext_attrs = {
"Tenant ID" = "corp"
"CMP Type" = "Terraform"
"Cloud API Owned" = "True"
}
}
}
}4 · AAAA record (IPv6)
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
aaaa_records = {
web01_v6 = {
fqdn = "web01.corp.example.com"
ipv6_addr = "2001:db8:abcd:12::10"
ttl = 3600
}
}
}5 · PTR record — all three shapes
A PTR is either forward-mapping (record_name), reverse static (ip_addr), or reverse dynamic (cidr). Exactly one is set (validated).
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
ptr_records = {
rev_static = {
ptrdname = "web01.corp.example.com" # reverse zone, static
ip_addr = "10.20.30.10"
}
rev_dynamic = {
ptrdname = "app01.corp.example.com" # reverse zone, dynamic
cidr = "10.20.30.0/24"
network_view = "default"
}
fwd = {
ptrdname = "host.corp.example.com" # forward-mapping zone
record_name = "alias.corp.example.com"
}
}
}6 · CNAME record
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
cname_records = {
www = {
alias = "www.corp.example.com"
canonical = "web01.corp.example.com"
comment = "Public alias for the primary web node"
}
}
}7 · MX records (mail exchangers)
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
mx_records = {
primary = {
fqdn = "corp.example.com"
mail_exchanger = "mail1.corp.example.com"
preference = 10
}
secondary = {
fqdn = "corp.example.com"
mail_exchanger = "mail2.corp.example.com"
preference = 20
}
}
}8 · TXT record (SPF)
TXT data is arbitrary and not case-folded. Quote the value to preserve spaces.
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
txt_records = {
spf = {
fqdn = "corp.example.com"
text = "\"v=spf1 include:_spf.example.com -all\""
}
}
}9 · SRV record (service location)
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
srv_records = {
sip = {
name = "_sip._tcp.corp.example.com"
target = "sipserver.corp.example.com"
port = 5060
priority = 10
weight = 50
}
}
}10 · NS record (delegation glue) — no ttl/comment/ext_attrs
The NIOS NS-record object has no ttl, comment, or ext_attrs — those keys are absent from the schema. At least one glue address is required.
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
ns_records = {
sub = {
name = "sub.corp.example.com"
nameserver = "ns1.corp.example.com"
addresses = [
{ address = "10.20.30.5", auto_create_ptr = true },
{ address = "10.20.30.6" },
]
}
}
}11 · Alias record (record:alias)
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
alias_records = {
legacy = {
name = "legacy.corp.example.com"
target_name = "web01.corp.example.com"
target_type = "A" # one of: A, AAAA, MX, NAPTR, PTR, SPF, SRV, TXT
}
}
}12 · Module-level default_ttl with per-record override
default_ttl applies to every record that does not set its own ttl. A per-record ttl (including 0) always wins.
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
default_ttl = 3600 # applies to all records below…
a_records = {
web01 = { fqdn = "web01.corp.example.com", ip_addr = "10.20.30.10" } # inherits 3600
cache0 = { fqdn = "cache.corp.example.com", ip_addr = "10.20.30.11", ttl = 0 } # disables caching
edge0 = { fqdn = "edge.corp.example.com", ip_addr = "10.20.30.12", ttl = 86400 } # explicit override
}
}13 · Per-record dns_view override
The module-level dns_view is the default; any record can override it.
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
dns_view = "corp-internal"
a_records = {
internal = { fqdn = "app.corp.example.com", ip_addr = "10.0.0.5" } # corp-internal
dmz = { fqdn = "app.corp.example.com", ip_addr = "203.0.113.5", dns_view = "dmz" } # override
}
}14 · Consume the output maps
output "web01_ref" {
value = module.dns_records.a_record_ids["web01"] # e.g. record:a/ZG5z...:web01.corp.example.com/default
}
output "all_managed_names" {
value = module.dns_records.record_fqdns # { "a:web01" = "web01.corp.example.com", "cname:www" = "www...",... }
}15 · End-to-end composition (dns_view → zone → many record types)
module "dns_view" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-view?ref=v1.0.0"
name = "corp-internal"
}
module "zone" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-zone-auth?ref=v1.0.0"
fqdn = "corp.example.com"
dns_view = module.dns_view.dns_view
}
module "dns_records" {
source = "git::https://github.com/microsoftexpert/terraform-infoblox-dns-records?ref=v1.0.0"
dns_view = module.dns_view.dns_view
depends_on = [module.zone]
default_ttl = 3600
a_records = {
web01 = { fqdn = "web01.corp.example.com", ip_addr = "10.20.30.10" }
web02 = { fqdn = "web02.corp.example.com", ip_addr = "10.20.30.11" }
}
cname_records = {
www = { alias = "www.corp.example.com", canonical = "web01.corp.example.com" }
}
mx_records = {
primary = { fqdn = "corp.example.com", mail_exchanger = "mail1.corp.example.com", preference = 10 }
}
txt_records = {
spf = { fqdn = "corp.example.com", text = "\"v=spf1 include:_spf.example.com -all\"" }
}
ptr_records = {
web01 = { ptrdname = "web01.corp.example.com", ip_addr = "10.20.30.10" }
}
}| Name | Type | Default | Required | Description |
|---|---|---|---|---|
dns_view |
string |
"default" |
— | Module-level DNS view for every record; per-record dns_view overrides it. |
default_ttl |
number |
null |
— | Module-level TTL for records that don't set their own ttl. null = inherit from zone; 0 = no caching. Does not apply to ns_records. |
a_records |
map(object) |
{} |
— | A records. Per record: fqdn, exactly one of ip_addr/cidr, optional network_view, filter_params, dns_view, ttl, comment, ext_attrs. |
aaaa_records |
map(object) |
{} |
— | AAAA records. As A records but ipv6_addr instead of ip_addr. |
ptr_records |
map(object) |
{} |
— | PTR records. Per record: ptrdname + exactly one of record_name/ip_addr/cidr. |
cname_records |
map(object) |
{} |
— | CNAME records. Per record: alias, canonical. |
mx_records |
map(object) |
{} |
— | MX records. Per record: fqdn, mail_exchanger, preference (0–65535). |
txt_records |
map(object) |
{} |
— | TXT records. Per record: fqdn, text (arbitrary, not case-folded). |
srv_records |
map(object) |
{} |
— | SRV records. Per record: name, target, port, priority, weight (each 0–65535). |
ns_records |
map(object) |
{} |
— | NS records. Per record: name, nameserver, non-empty addresses list, optional dns_view. No ttl/comment/ext_attrs. |
alias_records |
map(object) |
{} |
— | Alias records. Per record: name, target_name, target_type (A/AAAA/MX/NAPTR/PTR/SPF/SRV/TXT), optional disable. |
Every name/FQDN-bearing field is validated lowercase (NIOS DNS is case-sensitive). See each variable's heredoc
descriptioninvariables.tffor the full object schema.
| Output | Description | Typically consumed by |
|---|---|---|
a_record_ids |
Primary (A). { key => WAPI ref }, e.g. record:a/.... {} when unused. |
wiring, terraform import, monitoring |
aaaa_record_ids |
{ key => WAPI ref } (record:aaaa/...). |
wiring |
ptr_record_ids |
{ key => WAPI ref } (record:ptr/...). |
wiring |
cname_record_ids |
{ key => WAPI ref } (record:cname/...). |
wiring |
mx_record_ids |
{ key => WAPI ref } (record:mx/...). |
wiring |
txt_record_ids |
{ key => WAPI ref } (record:txt/...). |
wiring |
srv_record_ids |
{ key => WAPI ref } (record:srv/...). |
wiring |
ns_record_ids |
{ key => WAPI ref } (record:ns/...). |
wiring |
alias_record_ids |
{ key => WAPI ref } (record:alias/...). |
wiring |
record_fqdns |
merged { "type:key" => name } across all collections. |
DTC, monitoring, reporting |
WAPI reference string (the id convention). Each record's id is the NIOS object reference, e.g. record:a/ZG5zLnpvbmUkLl9kZWZhdWx0...:web01.corp.example.com/default. It is opaque, grid-specific, and is the canonical identifier for terraform import and reference-by-ID wiring. As an aggregation module there is no single id; the contract is realised as one { key => _ref } map per record type (a_record_ids, …) plus the merged record_fqdns.
NIOS object hierarchy. network_view → dns_view → zone (zone_auth) → record. This module owns only the leaf records. The DNS view (dns_view, default "default") and the authoritative zone for every FQDN must already exist — the plugin cannot create zones. Provision terraform-infoblox-dns-view and terraform-infoblox-zone-auth upstream.
Immutable fields (force-new). Renaming a record's identity (fqdn / alias / name / ptrdname) is a destroy-and-recreate. So is changing the map key — keys are the Terraform identity for each record and are immutable-by-convention; changing a key destroys and recreates that record. Switching a record between static (ip_addr/ipv6_addr) and dynamic (cidr) allocation also recreates it.
ext_attrs encoding. Callers pass a plain map(string). main.tf encodes per record:
ext_attrs = length(try(each.value.ext_attrs, {})) > 0 ? jsonencode(each.value.ext_attrs) : nullThe length(...) > 0 guard is required because the provider expects a JSON string, not a map. Encoding an empty map would render "{}", which the provider then reports as perpetual drift on every plan; encoding to null omits the argument entirely so unused EAs stay quiet. ns_records has no ext_attrs — the NIOS NS-record object exposes none.
TTL behavior. Resolution per record is: per-record ttl wins, else module-level default_ttl. Then null (both unset) → inherit from the parent zone (NIOS stores this as a negative value in state, by design — not drift); 0 → disable caching for the record; positive integer → explicit TTL in seconds. A per-record ttl = 0 is honoured because 0 != null. ns_records has no ttl — the NS-record object has no TTL field.
Terraform Internal ID EA (drift detection). When present (auto-created under a superuser identity, or pre-created), the provider stamps this EA onto every managed record so it can re-locate the object if the NIOS _ref changes due to out-of-band manual edits — preventing spurious destroy/recreate churn. Pre-create it if your identity is not superuser.
Case sensitivity — lowercase FQDNs only. NIOS DNS lookups are case-sensitive; capital letters cause silent lookup failures. Every name/FQDN-bearing field across all nine collections is validated lowercase (fqdn, alias, canonical, ptrdname, record_name, mail_exchanger, SRV name/target, NS name/nameserver, alias name/target_name). TXT text is the one exception — TXT data is arbitrary and not case-folded.
Eventual consistency. NIOS is a distributed grid with read-after-write lag; a freshly created record may not be immediately queryable. Allow brief propagation before dependent reads in tightly chained pipelines.
- Aggregation, not nine modules — DNS records share an identical shape and are provisioned together; one module with nine optional collections is the right boundary.
- No
thiskeystone — every resource is named by role (a_records,cname_records, …) and driven byfor_each. Nocount. - Each collection independently optional —
map(object(...))default{}, so callers populate only what they need. - Shared defaults + per-record overrides — module-level
dns_view/default_ttlavoid repetition while preserving per-record control. - Map-of-maps outputs keyed by the caller's key — deterministic wiring; an unused collection yields
{}, never an error. ext_attrsis the NIOS equivalent of tags —map(string),jsonencoded, guarded against"{}"drift. No Azuretags/resource_group_name/object_id.- NS-record exception surfaced —
ns_recordsomitsttl/comment/ext_attrsbecause the WAPI object has none. - Host records excluded —
infoblox_ip_allocation(host record) is a distinct family owned byterraform-infoblox-ip-allocation.
terraform init -backend=false
terraform validate
terraform fmt -check
terraform plan/applyrequire a live NIOS grid endpoint and the auth env vars (INFOBLOX_SERVER/INFOBLOX_USERNAME/INFOBLOX_PASSWORD).init -backend=false+validate+fmt -checkrun fully offline.
- Offline:
terraform init -backend=false && terraform validate && terraform fmt -check— confirms schema, type, and validation-block correctness without a grid. - Validation coverage to exercise: lowercase enforcement on every FQDN field; A/AAAA
ip_addrvscidrmutual exclusion; PTR exactly-one-ofrecord_name/ip_addr/cidr; MXpreference0–65535; SRVport/priority/weightranges; aliastarget_typeenum; NS non-emptyaddresses. - Integration (against a lab grid): stand up
dns_view+zone_auth, then apply a small mix of record types and assert the*_record_idsmaps are populated andrecord_fqdnsis correctly namespaced. - Idempotence: a second
planafter apply must report no changes — in particular noext_attrs"{}"drift and no TTL-inherit (negative value) churn.
Apply complete! Resources: 4 added, 0 changed, 0 destroyed.
Outputs:
a_record_ids = {
"web01" = "record:a/ZG5zLmJpbmRfYSQuX2RlZmF1bHQ...:web01.corp.example.com/default"
}
cname_record_ids = {
"www" = "record:cname/ZG5zLmJpbmRfY25hbWUk...:www.corp.example.com/default"
}
mx_record_ids = {}
record_fqdns = {
"a:web01" = "web01.corp.example.com"
"cname:www" = "www.corp.example.com"
}
| Symptom | Likely cause | Fix |
|---|---|---|
| Apply fails immediately with a connection / WAPI error | One of the four required EAs (esp. Terraform Internal ID) is missing |
Pre-create the EAs (cURL above) or run with the Cloud Network Automation license / superuser. |
Unknown extensible attribute "..." on apply |
An EA key in a record's ext_attrs is not defined in the grid |
Define the EA in NIOS first (see Prerequisites), or remove the key. |
... zone not found / record fails to create |
The authoritative zone for the FQDN doesn't exist | Create it via terraform-infoblox-zone-auth first; add depends_on on the zone module. |
| Silent lookup failure / record not resolving | An FQDN had uppercase letters and NIOS case-folded inconsistently | Use lowercase only — the module validates this; fix the input. |
401 Unauthorized / auth failure |
INFOBLOX_USERNAME / PASSWORD / SERVER not set at root |
Export the env vars (or set them in the pipeline) before plan/apply. |
| Plan wants to destroy and recreate a record | A map key, fqdn/name/alias, or static↔dynamic mode was changed (force-new) |
Revert the change, or accept the recreate; treat map keys as immutable. |
Every plan shows ext_attrs drift ("{}") |
An empty map was pushed without the length > 0 guard |
This module already guards it; confirm you're on v1.0.0+. |
| Plan shows TTL "drift" to a negative number | ttl/default_ttl is null → NIOS stores the inherited TTL as a negative value |
Expected behavior, not drift. Set an explicit ttl if you want a fixed value. |
Terraform Internal ID import / re-create churn |
The _ref changed out-of-band and the EA is absent |
Ensure the Terraform Internal ID EA exists so the provider can re-locate objects. |
| Record created but not immediately queryable | NIOS eventual consistency (read-after-write lag) | Allow brief propagation; add depends_on for tightly chained reads. |
... must set exactly one of... validation error |
A/AAAA set both/neither ip_addr/cidr, or PTR set ≠1 of the three shapes |
Set exactly one allocation field per the variable description. |
- Provider:
infobloxopen/infoblox—infoblox_*_recordresources - Infoblox NIOS documentation
SCOPE.md(this module) — in/out-of-scope resources, permissions, prerequisites, gotchas, design decisions- Upstream:
terraform-infoblox-dns-view,terraform-infoblox-zone-auth - Downstream:
terraform-infoblox-dtc(consumesrecord_fqdns)
💙 "Infrastructure as Code should be standardized, consistent, and secure."

