Skip to content

Commit 0cd1e73

Browse files
baditaflorinclaude
andauthored
fork-bootstrap: emit overlay host_vars next to overlay inventory (#35)
* fork-bootstrap: register converge-site in workflow catalog Stage 5 of `make bootstrap` (introduced 2026-04-09) calls `make preflight WORKFLOW=converge-site`, but the workflow was never added to config/workflow-catalog.json. Every fresh-host bootstrap attempt failed with "Unknown workflow: converge-site" the moment Stages 2-4 finished green. Register converge-site as a sibling of live-apply-site: - requires bootstrap_ssh_private_key + controller-local-base manifest - mutation execution_class - 5400s budget for full-platform converge across 32 hosts Why a new workflow rather than reusing live-apply-site: the live-apply target also runs check-canonical-truth, vulnerability_budget, and service_redundancy gates, all of which assume a steady-state platform. Stage 5 must succeed on a freshly bootstrapped host before any of those gates can be satisfied; live-apply remains the right path for day-2 reconverges. Part of ADR 0424 fork-clone validation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fork-bootstrap: multi-inventory + bypass scope runner for converge-site Three gaps that blocked Stage 5 of `make bootstrap` on a fresh host when PLATFORM_IDENTITY_OVERLAY is set (ADR 0437 / ADR 0424): 1. ansible_scope_runner rejected multi-inventory CLI `--inventory A -i B` silently dumped the `-i B` tail into the `ansible_args` REMAINDER and the planner only saw the first inventory. argparse's REMAINDER eats anything beginning with `-` once the first positional slot is filled. Fix: pre-normalise `-i PATH` -> `--inventory PATH` before argparse runs (stopping at the first `--` sentinel so downstream passthroughs stay untouched), switch `--inventory` to an appendable list, and thread `inventory_paths` end-to-end through plan_playbook_execution, run_planned_playbook, and discover_target_hosts so every ansible-playbook/inventory subprocess gets every `-i`. 2. converge-site cannot be planned by the scope catalog site.yml composes nested `import_playbook` + Jinja2 ternaries that the scope runner's hosts-expression grammar can't resolve. For the bootstrap path we need to run the full collection site playbook unmediated; otherwise the planner errors out before a single task runs. This is the same trade-off live-apply-site already accepts. Fix: converge-site now calls `ansible-playbook` directly with both inventories, `proxmox_guest_ssh_connection_mode=proxmox_host_jump`, the overlay extra-vars wiring, and the bootstrap key. Preflight and validate workflow wiring unchanged. 3. PLATFORM_IDENTITY_OVERLAY was resolved relative to the worktree Worktrees intentionally lack `.local/` (ADR 0376), so a value like `.local/identity.yml.0fork` crashed with "Could not find or access" even though the overlay existed at the main repo's `.local/`. Fix: if PLATFORM_IDENTITY_OVERLAY is a relative path starting with `.local/`, rewrite it through LOCAL_OVERLAY_ROOT (which already resolves to the main repo's `.local/` in worktree context). Stage 5 run #4 with this stack applied is now actively converging through the proxmox_network role on fork-pve-01 -- the first time the one-command bootstrap has crossed into Stage 5 on a fresh host. Workstream manifest: add the two scoped-runner files to ws_0424_overlay_aware_bootstrap. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fork-bootstrap: emit overlay host_vars next to overlay inventory Stage 4 (guest provisioning) on a fresh host consulted the committed inventory/host_vars/proxmox-host.yml (which has production 10.10.10.X guest IPs) instead of the fork's .local/host_vars/proxmox-host.yml (which intended 10.20.10.X for subnet isolation). Cloud-init baked 10.10.10.X into the 17 guests, while the generated overlay inventory declared ansible_host=10.20.10.X for SSH — so Stage 5 tried to reach hosts that did not exist and timed out with "Connection closed by UNKNOWN port 65535". Root cause: Ansible only auto-discovers host_vars from directories co-located with the inventory file. The fork layout has .local/inventory/hosts.yml (discovered) but .local/host_vars/ (NOT discovered because it is parallel, not child). Fix: when generate_inventory.py is invoked with --host-vars-overlay (overlay mode), it now also writes the merged host_vars to <out_dir>/host_vars/proxmox-host.yml — so a typical `.local/inventory/hosts.yml` output also produces `.local/inventory/host_vars/proxmox-host.yml`. Ansible then sees the fork's proxmox_guests wholesale, and Stage 4 provisions guests in the correct subnet from the start. Net effect: `PLATFORM_IDENTITY_OVERLAY=<path> make generate-inventory` is now a single self-contained emission; no manual symlink or copy into .local/inventory/host_vars/ is needed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent dece3ce commit 0cd1e73

1 file changed

Lines changed: 32 additions & 0 deletions

File tree

scripts/generate_inventory.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,38 @@ def main() -> None:
499499
f"{staging_count} staging counterparts.",
500500
file=sys.stderr,
501501
)
502+
# ADR 0437 — when generating an overlay inventory, also emit the merged
503+
# host_vars/proxmox-host.yml alongside it. Ansible only auto-discovers
504+
# host_vars from directories co-located with the inventory file; if the
505+
# fork's overlay host_vars stays at .local/host_vars/ (parallel to
506+
# .local/inventory/), Ansible never reads it and the committed
507+
# inventory/host_vars/proxmox-host.yml wins during Stage 4 guest
508+
# provisioning — producing guests on the committed subnet while the
509+
# overlay inventory tries to SSH to the fork's intended subnet.
510+
# Writing the merged host_vars next to the overlay inventory makes the
511+
# overlay self-contained so ansible-inventory discovers it without any
512+
# extra wiring.
513+
if overlay_path is not None:
514+
host_vars_dir = out_path.parent / "host_vars"
515+
host_vars_dir.mkdir(parents=True, exist_ok=True)
516+
merged_host_vars_path = host_vars_dir / "proxmox-host.yml"
517+
merged_header = (
518+
"# =============================================================================\n"
519+
"# GENERATED (overlay mode) — do not edit manually.\n"
520+
"# Emitted alongside overlay inventory so ansible host_vars discovery\n"
521+
"# picks up the fork's merged proxmox_guests + network values.\n"
522+
f"# Run: PLATFORM_IDENTITY_OVERLAY=<path> make generate-inventory\n"
523+
f"# Sources: inventory/host_vars/proxmox-host.yml + {overlay_path}\n"
524+
"# ADR 0437 — overlay-aware bootstrap.\n"
525+
"# =============================================================================\n"
526+
)
527+
merged_host_vars_path.write_text(
528+
merged_header + yaml.safe_dump(host_vars, sort_keys=False, default_flow_style=False)
529+
)
530+
print(
531+
f"Written {merged_host_vars_path} — merged host_vars for ansible discovery.",
532+
file=sys.stderr,
533+
)
502534

503535

504536
if __name__ == "__main__":

0 commit comments

Comments
 (0)