feat(ansible): run a playbook against a single Steward host (task 547)
From a host's edit page, an operator can run a playbook against just that host via an ephemeral one-host inventory — no need for the host to exist in a source inventory. - ansible/sources.py: pure host_inventory_content(host) -> '<name> ansible_host=<addr>' - executor.start_run: optional inventory_content written to the temp dir and used as -i (overrides inventory_path); cleaned up with the creds dir - hosts/routes.py: POST /hosts/<id>/run-playbook (operator) — validates source + playbook, builds the ephemeral inventory, starts a user-triggered run, redirects to the live run detail; edit page gets the ansible source list - hosts/form.html: 'Run Ansible playbook against this host' panel (shown when editing an existing host and sources exist) — source + playbook + extra-vars/ tags/dry-run (no --limit; single host) - tests: unit host_inventory_content; integration runs a hosts:all/connection:local playbook against the ephemeral inventory and asserts inventory_hostname Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -134,8 +134,13 @@ async def start_run(
|
||||
inventory_path: str,
|
||||
source_path: str,
|
||||
params: dict | None = None,
|
||||
inventory_content: str | None = None,
|
||||
) -> None:
|
||||
"""Execute ansible-playbook as a subprocess and update the DB run row."""
|
||||
"""Execute ansible-playbook as a subprocess and update the DB run row.
|
||||
|
||||
If inventory_content is given (host-targeted runs), it's written to a temp
|
||||
file and used as the inventory instead of inventory_path.
|
||||
"""
|
||||
_run_lines[run_id] = []
|
||||
|
||||
db_output = ""
|
||||
@@ -161,14 +166,21 @@ async def start_run(
|
||||
lines_since_flush = 0
|
||||
last_flush_at = time.monotonic()
|
||||
|
||||
cmd = build_ansible_command(playbook_path, inventory_path, params)
|
||||
cwd = source_path if Path(source_path).exists() else None
|
||||
creds = app.config.get("ANSIBLE", {})
|
||||
|
||||
# Materialize credentials into a private temp dir; removed in finally.
|
||||
# Temp dir holds the ephemeral inventory (if any) + credential files; removed in finally.
|
||||
tmpdir = tempfile.mkdtemp(prefix="steward-ansible-")
|
||||
os.chmod(tmpdir, 0o700)
|
||||
try:
|
||||
if inventory_content:
|
||||
inv_target = os.path.join(tmpdir, "inventory")
|
||||
with open(inv_target, "w", encoding="utf-8") as invf:
|
||||
invf.write(inventory_content)
|
||||
else:
|
||||
inv_target = inventory_path
|
||||
cmd = build_ansible_command(playbook_path, inv_target, params)
|
||||
|
||||
cred_args, cred_files = build_credentials(creds, tmpdir)
|
||||
for cred_path, content in cred_files:
|
||||
with open(cred_path, "w", encoding="utf-8") as cf:
|
||||
|
||||
Reference in New Issue
Block a user