2026-05-30 14:10:01 +02:00
|
|
|
#!/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" }
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-05 18:54:54 +02:00
|
|
|
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.)
|
2026-05-30 14:10:01 +02:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import sys
|
|
|
|
|
|
2026-06-05 18:54:54 +02:00
|
|
|
VALID_GROUPS = {"control", "docker_hosts", "proxmox_hosts", "offsite_hosts"}
|
2026-05-30 14:10:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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>",
|
2026-06-14 19:31:40 +02:00
|
|
|
"# 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).",
|
2026-05-30 14:10:01 +02:00
|
|
|
"",
|
|
|
|
|
"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()
|