boma/scripts/tf_to_inventory.py
sjat 9e0c264658 docs: reconcile lower-severity review findings (O9-O24)
- ADR-007: document ubongo on the legacy V4 net at 10.20.10.151 (transitional,
  outside the planned srv /24 until the LAN is re-cut) (O10); single authoritative
  boma.baobab.band -> boma.wingu.me transition note already added earlier
- terraform tfvars.example + variables.tf (both envs): pve01 -> pve0 and
  <host>.boma.baobab.band per ADR-007 naming (O11)
- ADR-012/013/015/016/017/018: convert "See also:" prose to `## Related` sections
  placed after Consequences, matching ADR-014/019-023 (O13)
- docs/README + inventories/README: list the missing subdirs / offsite_hosts +
  offsite.yml merge behaviour (O14, O29 note)
- ADR-009: drop the retired `nyumbani` example; use vaultwarden.wingu.me split-horizon (O19)
- ROADMAP M2: askari shipped as cx23/x86 (CAX11/ARM out of stock) (O20)
- ADR-020: 80/443/3478 opened in M4a (past tense); coordinator role is M4b (O21)
- netbird -> netbird_coordinator across ROADMAP M4b, the M4b plan, ADR-024 (O23)
- ADR-024: align the M1 DNS-01 wildcard scope wording with ROADMAP (O24)
- capacity-scan.py: read the inventory directory so offsite.yml (askari) is seen (O28)
- tf_to_inventory.py: generated header now warns it overwrites the manual control node (O9)
- tests/tags.yml: proxy concern comment Traefik -> Caddy (missed in the O3 sweep)

O9's existing stub hosts.yml header stays as-is (generator-owned, hook-protected);
the fix lives in the generator for the next regeneration. make lint + pytest (57) green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-14 19:31:40 +02:00

78 lines
2.4 KiB
Python

#!/usr/bin/env python3
"""
Read `terraform output -json` from stdin, emit an Ansible hosts.yml to stdout.
Usage:
terraform -chdir=terraform/environments/<env> output -json \\
| python3 scripts/tf_to_inventory.py > inventories/<env>/hosts.yml
Expected Terraform output shape:
{
"vms": {
"value": {
"hostname": { "ip": "192.168.1.10", "group": "docker_hosts" }
}
}
}
Valid groups: control, docker_hosts, proxmox_hosts, offsite_hosts
(control and offsite_hosts hold manually-provisioned hosts not in Terraform; they
are valid so their sections appear in the generated inventory — see ADR-009.)
"""
import json
import sys
VALID_GROUPS = {"control", "docker_hosts", "proxmox_hosts", "offsite_hosts"}
def main() -> None:
try:
data = json.load(sys.stdin)
except json.JSONDecodeError as exc:
print(f"error: could not parse Terraform output JSON: {exc}", file=sys.stderr)
sys.exit(1)
vms = data.get("vms", {}).get("value", {})
if not vms:
print("warning: no VMs in Terraform output — writing empty inventory", file=sys.stderr)
groups: dict[str, dict[str, str]] = {}
for hostname, info in vms.items():
group = info.get("group", "")
if group not in VALID_GROUPS:
print(
f"error: unknown group '{group}' for host '{hostname}' "
f"(valid: {', '.join(sorted(VALID_GROUPS))})",
file=sys.stderr,
)
sys.exit(1)
groups.setdefault(group, {})[hostname] = info["ip"]
lines = [
"---",
"# Generated by scripts/tf_to_inventory.py — do not edit manually.",
"# Regenerate with: make tf-inventory TF_ENV=<env>",
"# This OVERWRITES the file, including any manually-added control node (ubongo) —",
"# re-add it afterwards (the one hand-edit exception; docs/runbooks/new-host.md Part E).",
"",
"all:",
" children:",
]
for group in sorted(VALID_GROUPS):
lines.append(f" {group}:")
hosts = groups.get(group, {})
if hosts:
lines.append(" hosts:")
for hostname in sorted(hosts):
lines.append(f" {hostname}:")
lines.append(f" ansible_host: {hosts[hostname]}")
else:
lines.append(" hosts: {}")
print("\n".join(lines))
if __name__ == "__main__":
main()