Files
terraform-infra/README.md
unkinben 9a6f775300
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/plan Pipeline failed
Rename terraform-ipam -> terraform-infra; add devices + networks + puppetdb backfill
Scope now spans devices and provisioning, not just IPAM.

- rename module/consul-path/role ipam -> infra
- networks config (subnet binding + gateway/dns/search constants); prefixes tagged net:<name>
- intent-only devices module: netbox_device + device_type/role/manufacturer, static or
  next-available IPs (sticky via ignore_changes), transitional bootstrap_mac interface for
  bootapi PXE keying
- seed 6 pending hosts prodnxsr0014-0019 (mgmt IPs .14-.19, optiplex-3070)
- ci/puppetdb_backfill.py: emit NetBox reality (serial/model/uuid/interfaces) for existing hosts

Claude-Session: https://claude.ai/code/session_01JUoARVdmhxKQHyyyp1pxeT
2026-08-05 19:41:41 +10:00

104 lines
4.3 KiB
Markdown

# 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>/managed-ips.yaml # extra manually-managed IPs
environments/<region>/<dc>/terragrunt.hcl # one Terragrunt env per region/dc
modules/infra/ # the module
ci/puppetdb_backfill.py # reality backfill generator (local/in-cluster)
```
`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`)
```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.
```yaml
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.
```yaml
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.
## PuppetDB reality backfill
`ci/puppetdb_backfill.py` sweeps already-provisioned hosts and emits their hardware
reality (serial/model/UUID + every interface's MAC/IPs, incl. overlay/loopback
addresses Cobbler lacks) as reviewable YAML — the NetBox *reality* side, not the
intent above. PuppetDB needs no auth but is **not reachable from CI**; run it
in-cluster/from puppet infra. Wiring its output into NetBox is a follow-up (issue).
## 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/netbox` argocd branch); NetBox sites
must pre-exist. **KeaAPI not deployed** yet (argocd-apps PR #333). Seed
`kv/service/terraform/infra` before the pipeline can auth.