fix(ansible): write DB inventory as static YAML, not dynamic --list JSON
generate_inventory() emits Ansible's dynamic --list shape (all.hosts is a
LIST, vars under _meta) — valid only as an executable inventory script's
stdout. We were writing it to a static file and passing -i, so Ansible's yaml
plugin rejected it ("Invalid 'hosts' entry for 'all' group, requires a
dictionary, found ...list...") and fell back to implicit localhost → "no hosts
matched". Affected every steward:* scope run; surfaced on the first real
provision.
- New inventory_to_yaml(inv): convert the --list dict → a valid static YAML
inventory (all.hosts dict keyed by host, groups under all.children, group
vars preserved, injected per-host vars like steward_token retained).
- Wire it into runner.trigger_run, host_agent deploy + provision.
- executor writes the file as inventory.yml so the yaml plugin's extension
check reliably claims it.
- generate_inventory unchanged (still the --list dict); conversion happens at
write time. Unit tests added.
Scribe issue #885.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -37,6 +37,33 @@ def generate_inventory(targets: list) -> dict:
|
||||
return inv
|
||||
|
||||
|
||||
def inventory_to_yaml(inv: dict) -> str:
|
||||
"""Convert the ``--list`` dict from generate_inventory into a STATIC YAML
|
||||
inventory string.
|
||||
|
||||
The ``--list`` shape (``all.hosts`` is a *list*, vars under ``_meta``) is only
|
||||
valid as the stdout of an executable dynamic-inventory script. When written to
|
||||
a plain file and passed via ``-i``, Ansible's yaml plugin requires
|
||||
``all.hosts`` to be a *dict* keyed by hostname — otherwise it fails to parse
|
||||
and silently falls back to implicit localhost. This emits that valid form.
|
||||
"""
|
||||
import yaml
|
||||
hostvars = (inv.get("_meta") or {}).get("hostvars") or {}
|
||||
all_hosts = (inv.get("all") or {}).get("hosts") or []
|
||||
root: dict = {"hosts": {h: (hostvars.get(h) or {}) for h in all_hosts}}
|
||||
children: dict = {}
|
||||
for key, val in inv.items():
|
||||
if key in ("all", "_meta") or not isinstance(val, dict):
|
||||
continue
|
||||
grp: dict = {"hosts": {h: {} for h in (val.get("hosts") or [])}}
|
||||
if val.get("vars"):
|
||||
grp["vars"] = val["vars"]
|
||||
children[key] = grp
|
||||
if children:
|
||||
root["children"] = children
|
||||
return yaml.safe_dump({"all": root}, default_flow_style=False, sort_keys=True)
|
||||
|
||||
|
||||
async def fetch_scope_targets(db, scope: str) -> list:
|
||||
"""Query AnsibleTarget objects for a given inventory scope string.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user