Files
terragrunt-enc/ci/extract_incus_nodes.py
T
Ben Vincent 054110d748
ci/woodpecker/pr/plan Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline was successful
Populate terragrunt-enc: encapi ENC data as code
terragrunt-enc is the single source of truth for encapi ENC data (Puppet
statuses, roles, and node classifications), managed with Terraform/Terragrunt.
It supersedes Cobbler classification for Puppet and the terraform-incus
dual-write prototype (PR #39).

- Add modules/encapi (encapi_status / encapi_role / encapi_node, nodes
  FK-ordered after their role and status).
- Add config/encapi leaf: 3 statuses, 51 roles, and 143 node assignments
  (130 container VMs env production + 13 prodnxsr physicals env develop),
  all YAML-driven.
- Add config/root.hcl (consul backend infra/terraform/enc/<leaf>/state) and
  the encapi provider from the artifactapi registry (v0.1.0).
- Add Makefile, Woodpecker pre-commit/plan/apply pipelines, pre-commit
  config, ci/extract_incus_nodes.py, and expand the README.
2026-07-24 23:12:03 +10:00

85 lines
2.9 KiB
Python
Executable File

#!/usr/bin/env python3
"""Regenerate the container-VM portion of config/encapi/nodes.yaml from a
checkout of terraform-incus.
encapi is the single source of truth for Puppet ENC data, but the 130 container
VMs are *defined* in terraform-incus (config/instances/<host>/config.yaml). This
script lifts that classification into plain config data here:
certname = <dir basename>.<cobbler_domain> (cobbler_domain = main.unkin.net)
role = cobbler_mgmt_classes[0] (default roles::base)
environment = production
It only rewrites the "container VMs" block; the prodnxsr physical nodes below
the marker are maintained by hand and left untouched.
Usage:
python3 ci/extract_incus_nodes.py --incus-repo /path/to/terraform-incus
"""
import argparse
import os
import sys
try:
import yaml
except ImportError:
sys.exit("PyYAML required: pip install pyyaml")
PHYS_MARKER = "# --- prodnxsr physical nodes (develop) ---"
VM_MARKER = "# --- container VMs (production) ---"
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--incus-repo", required=True,
help="Path to a terraform-incus checkout")
ap.add_argument("--domain", default="main.unkin.net")
ap.add_argument("--nodes-file",
default=os.path.join(os.path.dirname(__file__),
"..", "config", "encapi", "nodes.yaml"))
args = ap.parse_args()
inst = os.path.join(args.incus_repo, "config", "instances")
dirs = sorted(d for d in os.listdir(inst)
if os.path.isdir(os.path.join(inst, d))
and os.path.exists(os.path.join(inst, d, "config.yaml")))
vm_lines = []
for d in dirs:
with open(os.path.join(inst, d, "config.yaml")) as f:
cfg = yaml.safe_load(f) or {}
classes = cfg.get("cobbler_mgmt_classes") or []
role = classes[0] if classes else "roles::base"
vm_lines.append(f"{d}.{args.domain}:")
vm_lines.append(f" role: {role}")
vm_lines.append(" environment: production")
nodes_file = os.path.abspath(args.nodes_file)
with open(nodes_file) as f:
existing = f.read().splitlines()
# Preserve everything from the physical-nodes marker onward.
try:
idx = existing.index(PHYS_MARKER)
except ValueError:
sys.exit(f"marker not found in {nodes_file}: {PHYS_MARKER!r}")
tail = existing[idx:]
# Preserve only the top-of-file comment block, stopping at the VM marker.
header = []
for ln in existing[:idx]:
if ln == VM_MARKER:
break
header.append(ln)
while header and header[-1] == "":
header.pop()
out = header + ["", VM_MARKER] + vm_lines + ["", *tail]
with open(nodes_file, "w") as f:
f.write("\n".join(out).rstrip("\n") + "\n")
print(f"wrote {len(dirs)} container VM records to {nodes_file}")
if __name__ == "__main__":
main()