Automate the PuppetDB reality import (issue #1) by querying pdbmux — the PuppetDB multiplexer whose HTTPS gateway is reachable from CI/workstations, unlike raw PuppetDB — instead of PuppetDB directly, and shaping the result to the NetBox reality side the devices module reconciles. - Add tools/backfill (Go): query pdbmux /pdb/query/v4/facts for the 13 existing physicals and emit per-host reality YAML — serial/model/UUID, every recordable interface (real NICs plus overlay/loopback/kube-lb) with MAC and CIDR IPs, and CPU/RAM/disk inventory. Filter ephemeral Calico veths and Ceph RBD volumes; take interface names from Facter, never assume them. - Emit deterministic, idempotent, yamllint-clean output into config/au/syd1/reality/<host>.yaml, generated for prodnxsr0001-0013. - Extend modules/infra with a reality variable and reality.tf creating netbox_device_interface/netbox_mac_address/netbox_ip_address/ netbox_inventory_item and device serial; wire reality only for hosts that also have an intent device. - Load reality/*.yaml in the terragrunt env; add `make backfill`; add a go vet/test woodpecker job; drop the in-cluster-only Python script. Closes #1 Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
terraform-infra
YAML-driven Terraform/Terragrunt for infrastructure state that belongs in NetBox + Kea: subnets (NetBox prefixes + Kea DHCP scopes), logical networks, and physical devices. NetBox is authoritative for all IP/interface data; this repo declares intent and lets discovery/PuppetDB fill hardware reality.
Renamed from
terraform-ipam— scope now spans devices and provisioning, not just IPAM. The old repo name is archived by the terraform-git rename.
Layout
config/<region>/<dc>/subnets/<name>.yaml # one file per subnet (prefix + DHCP scope)
config/<region>/<dc>/networks/<name>.yaml # logical networks devices join
config/<region>/<dc>/devices/<host>.yaml # one file per device (intent only)
config/<region>/<dc>/reality/<host>.yaml # one file per device (generated reality)
config/<region>/<dc>/managed-ips.yaml # extra manually-managed IPs
environments/<region>/<dc>/terragrunt.hcl # one Terragrunt env per region/dc
modules/infra/ # the module
tools/backfill/ # pdbmux -> reality YAML generator (Go)
make plan / make apply reconcile the YAML against NetBox + Kea. State lives in
Consul under infra/terraform/infra/<region>/<dc>/state.
Subnet schema (subnets/<name>.yaml)
prefix: 198.18.15.0/24
description: syd1 production
site: syd1 # NetBox site slug (optional)
dhcp: # omit the block for a NetBox-only prefix
enabled: true
start: 200 # host-octet int
stop: 220
router: 254 # host-octet int -> gateway IP
dns: [198.18.200.7] # DHCP-scope DNS
next_server: 198.18.19.19
domain: main.unkin.net
Creates netbox_prefix (+ netbox_ip_range role dhcp + kea_subnet when dhcp
set, + gateway netbox_ip_address). A prefix backing a network is tagged net:<name>.
Network schema (networks/<name>.yaml)
Per-network provisioning constants — hoisted here, never repeated per device.
subnet: net-198-18-15 # which subnet's prefix backs this network
gateway: 198.18.15.254
dns: [198.18.19.16]
search: main.unkin.net
Device schema (devices/<host>.yaml) — intent only
Declare only human intent. Serial, real interface names (which vary by model —
OptiPlex 3060 = enp1s0, 3050/7080 = enp2s0), MACs and inventory are owned by the
first-boot discovery image / PuppetDB, never hardcoded here.
site: syd1
role: roles::base # puppet mgmt_class / ENC role -> NetBox device role
model_hint: optiplex-3070 # NetBox device type; discovery confirms via dmidecode
provision:
profile: almalinux9-dell_3070 # cobbler profile / kickstart template
platform: almalinux9
networks:
mgmt: 198.18.15.14/24 # requested IP; "" = next-available from the prefix
pxe: true
bootstrap_mac: a4:bb:6d:xx:xx:xx # TRANSITIONAL — see below
Mapping: netbox_device (+ netbox_manufacturer/netbox_device_type/
netbox_device_role), netbox_ip_address (static) or netbox_available_ip_address
(next-available, ignore_changes so a machine is never re-IPed). bootstrap_mac is
transitional: it seeds one placeholder netbox_device_interface + netbox_mac_address
so bootapi can key the PXE boot on MAC until the discovery image exists — remove it
once discovery/backfill populates the real NICs.
Reality backfill (reality/<host>.yaml)
tools/backfill (Go) queries pdbmux — the PuppetDB multiplexer at
https://pdbmux.k8s.syd1.au.unkin.net, which merges the old (Consul) and new
(k8s) PuppetDBs and needs no auth — for already-provisioned hosts and emits their
hardware reality as reviewable per-host YAML:
- identity — serial, model, UUID (from
dmi.product); - interfaces — every recordable NIC with MAC + CIDR IPs, including the
overlay/loopback/kube-lb addresses Cobbler never had. Real NICs are always
kept; virtual interfaces are kept only when they carry a routable address,
which drops ephemeral Calico veths and
lowhile keepingflannel.1,kube-lb0,loopback*. Interface names are taken from Facter, never assumed; - inventory — CPU model/count, total RAM bytes, and physical disks (Ceph RBD and other virtual block devices are skipped).
make backfill # all 13 existing physicals
make backfill BACKFILL_HOSTS="prodnxsr0001" # a subset
Output is deterministic and idempotent (stable ordering, sorted lists), so
re-running against unchanged facts leaves a clean diff. Unlike raw PuppetDB,
pdbmux's HTTPS gateway is reachable from CI/workstations. The generator only
writes committed config; netbox_device_interface/netbox_mac_address/
netbox_ip_address/netbox_inventory_item (plus device serial) reconcile it
into NetBox on the next apply. NetBox stays authoritative for IP data.
Reality is wired into NetBox only for hosts that also have an intent
devices/<host>.yaml; the 13 existing physicals get their intent seeded
separately, so until then their reality/*.yaml is committed but inert.
Providers
| Provider | Source | Notes |
|---|---|---|
| netbox | e-breuninger/netbox 4.3.0 |
OpenTofu registry |
| kea | artifactapi.k8s.syd1.au.unkin.net/terraform-unkin/kea 0.0.1 |
cluster_ref=kea |
| vault | hashicorp/vault 5.6.0 |
reads NetBox + Kea tokens |
Tokens: Vault KV v2 kv/service/terraform/infra fields netbox_token + kea_token.
Blockers
- NetBox not deployed yet (unmerged
benvin/netboxargocd branch); NetBox sites must pre-exist. KeaAPI not deployed yet (argocd-apps PR #333). Seedkv/service/terraform/infrabefore the pipeline can auth.