terraform-netbox

YAML-driven Terraform/Terragrunt for the state that belongs in NetBox: prefixes and IP ranges, logical networks, physical devices, platforms, and the custom-field definitions bootapi reads. NetBox is authoritative for all IP/interface data; this repo declares intent and lets discovery/PuppetDB fill in hardware reality.

Supersedes terraform-infra, which also drove Kea DHCP. Kea is managed by the kea-operator CRs in argocd-apps (apps/base/dhcp-system/cr/) — both declaring the same subnets would fight, and two CRs for one prefix fails the kea config load. This repo is NetBox only.

Layout

config/<region>/<dc>/subnets/<name>.yaml    # one file per subnet (NetBox prefix + pool range)
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>/custom-fields.yaml     # custom-field DEFINITIONS
config/<region>/<dc>/managed-ips.yaml       # extra manually-managed IPs
environments/<region>/<dc>/terragrunt.hcl   # one Terragrunt env per region/dc
modules/netbox/                             # the module
ci/puppetdb_backfill.py                     # reality backfill generator

make plan / make apply reconcile the YAML against NetBox. State lives in Consul under infra/terraform/netbox/<region>/<dc>/state.

Sites, device roles, device types and manufacturers are all declared by being referenced: any slug a subnet or device names is created. A site that already exists in NetBox must be imported, not re-created, since site names are unique.

Subnet schema (subnets/<name>.yaml)

prefix: 198.18.15.0/24
description: syd1 production
site: syd1            # NetBox site slug (optional)
dhcp:                 # omit for a NetBox-only prefix
  enabled: true
  start: 200          # host-octet int
  stop: 220
router: 254           # host-octet int -> gateway IP

Creates netbox_prefix (+ an netbox_ip_range with role dhcp and the gateway netbox_ip_address). A prefix backing a network is tagged net:<name>.

The DHCP server config (pool, routers, DNS, next-server) is not here — it is the KeaSubnet CR in argocd-apps. dhcp only records the pool in NetBox so IPAM reflects what is handed out.

Device schema (devices/<host>.yaml) — intent only

Serial, real interface names (which vary by model) and inventory are owned by discovery/PuppetDB, never hardcoded here.

site: syd1
role: roles::base
model_hint: optiplex-3070         # NetBox device type AND bootapi catalog entry
provision:
  platform: almalinux9            # NetBox platform slug
networks:
  mgmt: 198.18.15.14/24           # requested IP; "" = next-available
pxe: true                         # device status staged vs active
bootstrap_mac: a4:bb:6d:xx:xx:xx  # TRANSITIONAL

model_hint is written to the device's provision_template custom field: the per-model OptiPlex catalog entries in bootapi-templates are named after the device-type slug, so one field drives bootapi's per-model storage layout. provision.platform becomes a netbox_platform and the device's platform_id, which is bootapi's primary catalog selector.

bootstrap_mac seeds one placeholder interface + MAC so bootapi can key the PXE boot on MAC until the discovery image exists; remove it once backfill populates the real NICs.

Custom fields (custom-fields.yaml)

Field definitions for what bootapi reads: pxe_enabled, provision_template, domain, nameservers, gateway. content_types are NetBox app_label.model strings.

pxe_enabled's value is deliberately not managed here — it is runtime state. bootapi's /provisioned callback clears it when an install finishes so the host boots local disk instead of re-imaging; setting it from terraform would re-enable installs on every apply.

PuppetDB reality backfill

ci/puppetdb_backfill.py sweeps provisioned hosts and emits their hardware reality (serial/model/UUID + every interface's MAC/IPs) as reviewable YAML. PuppetDB needs no auth but is not reachable from CI — run it in-cluster or from puppet infra. Wiring its output into NetBox is a follow-up.

Providers + auth

Provider Source
netbox e-breuninger/netbox 4.3.0
vault hashicorp/vault 5.6.0

The NetBox token is minted per run from the Vault netbox engine (netbox/creds/terraform-netbox) and lease-revoked when the run ends; Consul state creds come from consul_root/au/syd1/creds/terraform-netbox. Both are granted to the terraform_netbox AppRole and the woodpecker_terraform_netbox k8s role in terraform-vault.

S
Description
YAML-driven Terraform for NetBox: prefixes, IPs, devices, platforms and custom fields
Readme 102 KiB