DNS records

Canonical DNS declarations for Cloudflare and RouterOS

This reusable module translates an owner’s decoded dnsconfig.json through its provider-free normalizer, then manages individual global Cloudflare and dc1 RouterOS records with caller-provided providers. Owners with only global records use the global entrypoint, which needs only Cloudflare. The combined entrypoint delegates Cloudflare resources to that same module and consumes its normalization for RouterOS. Repository infrastructure roots may consume its Bazel source filegroups; the child normalizer also supports provider-free inspection.

module "dns" {
  source             = "../../../projects/tf_modules/dns_records"
  document           = jsondecode(file("${path.module}/../dnsconfig.json"))
  cloudflare_zone_id = var.dns_cloudflare_zone_id
  enabled            = var.dns_enabled
}

zone defaults to alwaldend.com. An explicit cloudflare_zone_id is optional; when it is null or empty, enabled global records resolve exactly one Cloudflare zone matching zone. The token must permit zone listing and reading. Disabled ownership and declarations without global records skip this lookup. enabled defaults to false so a prepared root owns no provider records. Keep it true after adoption: setting it false in a state that already owns records plans deletion and is not a rollback procedure. Disabling ownership does not suppress provider initialization. Operational Terraform commands still require the caller’s real provider configuration and the owning Vault/AppRole prerequisites. In particular, the pinned RouterOS provider probes its configured API even when no DNS records are enabled. Use the provider-free normalizer for offline inspection. The migration runbook owns cutover, import, and rollback ordering. This module supplies no provider credentials or state backend.

The input contains exactly one records object. Each stable logical key contains one or more scalar type members and a nonempty dsp list. Supported destinations are global, dc1, and all; repeated destinations are deduplicated.

Type Required member fields Default TTL
A name, address 300
AAAA name, address 600
CNAME name, target 600
NS name, address 300
MX name, target, priority 300
TXT name, content 300

Every member accepts an integer ttl from 60 through 86400 seconds. MX priorities range from 0 through 65535. Multiple scalar values at the same name and type use distinct logical keys; changing values never changes resource identity. Unsupported fields and malformed members are rejected.

Names accept @, relative names, or zone-qualified names. Absolute owner names ending with a dot must be inside the zone. CNAME, MX, and NS targets accept @, relative names, zone-qualified names, or absolute external names ending with a dot. Normalization lowercases domain names and omits final dots; address and TXT bytes are preserved. relative_name is @ at the apex.

normalized_records maps logical_key/type/view to key, name, relative_name, type, value, priority (null except MX), ttl, view, and proxied (always false). This output remains available while disabled. The same keys index module.global.cloudflare_dns_record.records and module.dc1[0].routeros_ip_dns_record.records in the combined entrypoint; the global-only entrypoint uses cloudflare_dns_record.records. import_addresses exposes these addresses relative to the selected module, including while staged. The pinned providers import Cloudflare records by zone_id/record_id and RouterOS records by static IDs such as *1A.

Terraform tests use mocked providers and pinned local provider packages. The global-only test root has no RouterOS provider dependency. Normalization tests require no providers. Both run without network access; they establish declaration and resource mapping behavior, not live adoption.


DNS normalization

Provider-free canonical DNS declaration transformation

DNS test providers

Pinned provider packages for offline Terraform tests

Global DNS records

Cloudflare-only entrypoint for canonical DNS declarations