From 7e6e63521b939330d2c3a50e1164dd4d453719ad Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 17 Jun 2026 10:11:30 -0400 Subject: [PATCH] fix(host_agent): provision/deploy vars shadowed by play-var precedence MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The assertion failed ("Pass steward_url/token/pubkey") because those were injected as inventory HOST vars, but the playbooks declared them in the play `vars:` block — and play vars OUTRANK inventory host vars, so the empty defaults won and the injected values never reached the play. - Pass globals (steward_url, steward_pubkey, steward_user, agent_interval) as extra-vars via the JSON -e @file (highest precedence, space-safe). Keep only the per-host steward_token as an inventory host var. - provision.yml / install.yml: drop steward_token from `vars:` so the host var isn't shadowed; assertions use `| default('')` for the un-defaulted token. This was the next layer under the inventory-format fix — first the inventory wouldn't parse, now the injected vars actually apply. Co-Authored-By: Claude Opus 4.8 (1M context) --- plugins/host_agent/routes.py | 28 ++++++++++--------- .../ansible/bundled/host_agent/install.yml | 9 +++--- .../ansible/bundled/host_agent/provision.yml | 13 +++++---- 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/plugins/host_agent/routes.py b/plugins/host_agent/routes.py index e25c1da..a377f71 100644 --- a/plugins/host_agent/routes.py +++ b/plugins/host_agent/routes.py @@ -741,10 +741,9 @@ async def deploy_via_ansible(): host = await _ensure_host_for_target(db, t) tokens[t.name] = await _mint_registration_token(db, host) inv = generate_inventory(targets) # built while ORM objects are still live + # Per-host token as a host var; steward_url goes in as an extra-var below. for name, tok in tokens.items(): - hv = inv["_meta"]["hostvars"].setdefault(name, {}) - hv["steward_url"] = url - hv["steward_token"] = tok + inv["_meta"]["hostvars"].setdefault(name, {})["steward_token"] = tok inventory_content = inventory_to_yaml(inv) await db.commit() @@ -756,7 +755,8 @@ async def deploy_via_ansible(): playbook_path="host_agent/install.yml", inventory_content=inventory_content, inventory_scope=scope, - params={"extra_vars": [f"agent_interval={interval}"]}, + # extra-vars outrank the playbook's `vars:` defaults; token stays per-host. + params={"extra_vars_map": {"steward_url": url, "agent_interval": interval}}, triggered_by=session.get("user_id"), ) if err: @@ -817,15 +817,10 @@ async def provision_via_ansible(): host = await _ensure_host_for_target(db, t) tokens[t.name] = await _mint_registration_token(db, host) inv = generate_inventory(targets) # built while ORM objects are still live + # Only the per-host token is a host var; the globals go in as extra-vars + # below (extra-vars outrank play vars, host vars do not). for name, tok in tokens.items(): - hv = inv["_meta"]["hostvars"].setdefault(name, {}) - hv["steward_url"] = url - hv["steward_token"] = tok - # Pubkey carries a space (the comment) and steward_user is free text — - # injected as JSON hostvars (space-safe), NOT -e k=v strings which - # Ansible would shlex-split on whitespace. - hv["steward_pubkey"] = pubkey - hv["steward_user"] = steward_user + inv["_meta"]["hostvars"].setdefault(name, {})["steward_token"] = tok inventory_content = inventory_to_yaml(inv) await db.commit() @@ -837,7 +832,14 @@ async def provision_via_ansible(): playbook_path="host_agent/provision.yml", inventory_content=inventory_content, inventory_scope=scope, - params={"extra_vars": [f"agent_interval={interval}"]}, + # extra_vars_map → a JSON -e @file: highest precedence (beats play vars) + # and space-safe (steward_pubkey carries a comment with a space). + params={"extra_vars_map": { + "steward_url": url, + "steward_pubkey": pubkey, + "steward_user": steward_user, + "agent_interval": interval, + }}, triggered_by=session.get("user_id"), connection={"user": boot_user, "password": boot_password}, ) diff --git a/steward/ansible/bundled/host_agent/install.yml b/steward/ansible/bundled/host_agent/install.yml index bb39dd4..ce4acdd 100644 --- a/steward/ansible/bundled/host_agent/install.yml +++ b/steward/ansible/bundled/host_agent/install.yml @@ -7,17 +7,18 @@ hosts: all become: true gather_facts: false + # steward_token is a per-host inventory var — not declared here, or a play var + # would outrank and shadow it. steward_url arrives as an extra-var. vars: steward_url: "" - steward_token: "" agent_interval: 30 tasks: - name: Require steward_url and steward_token ansible.builtin.assert: that: - - steward_url | length > 0 - - steward_token | length > 0 - fail_msg: "Pass steward_url and steward_token as extra-vars." + - steward_url | default('') | length > 0 + - steward_token | default('') | length > 0 + fail_msg: "Pass steward_url and steward_token (Steward injects these)." - name: Create steward-agent system user ansible.builtin.user: diff --git a/steward/ansible/bundled/host_agent/provision.yml b/steward/ansible/bundled/host_agent/provision.yml index 73be606..49883eb 100644 --- a/steward/ansible/bundled/host_agent/provision.yml +++ b/steward/ansible/bundled/host_agent/provision.yml @@ -19,9 +19,12 @@ hosts: all become: true gather_facts: false + # steward_token is injected as a per-host inventory var (it differs per host), + # so it must NOT be declared here — a play var would outrank the inventory var + # and shadow it. The globals below arrive as extra-vars (highest precedence), + # which override these defaults. vars: steward_url: "" - steward_token: "" steward_pubkey: "" steward_user: steward agent_interval: 30 @@ -29,10 +32,10 @@ - name: Require provisioning vars ansible.builtin.assert: that: - - steward_url | length > 0 - - steward_token | length > 0 - - steward_pubkey | length > 0 - fail_msg: "Pass steward_url, steward_token and steward_pubkey as extra-vars." + - steward_url | default('') | length > 0 + - steward_token | default('') | length > 0 + - steward_pubkey | default('') | length > 0 + fail_msg: "Pass steward_url, steward_token and steward_pubkey (Steward injects these)." # ── Managed login account ──────────────────────────────────────────────── - name: Create the steward management account