From 2c991eabd2f626331501c4c99f9e8d5a7b03e43b Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Fri, 5 Jun 2026 18:49:02 -0400 Subject: [PATCH] feat(ansible): scope picker in run form + create_run() DB inventory generation --- steward/ansible/routes.py | 44 ++++++++++++++++++++--- steward/templates/ansible/browse.html | 44 +++++++++++++++-------- steward/templates/ansible/run_detail.html | 2 +- 3 files changed, 70 insertions(+), 20 deletions(-) diff --git a/steward/ansible/routes.py b/steward/ansible/routes.py index b304bd5..02918a6 100644 --- a/steward/ansible/routes.py +++ b/steward/ansible/routes.py @@ -37,6 +37,8 @@ async def index(): @ansible_bp.get("/browse") @require_role(UserRole.viewer) async def browse(): + from steward.models.ansible_inventory import AnsibleTarget, AnsibleGroup + all_sources = _get_sources() source_data = [] for source in all_sources: @@ -47,7 +49,21 @@ async def browse(): "playbooks": playbooks, "inventories": inventories, }) - return await render_template("ansible/browse.html", source_data=source_data) + + async with current_app.db_sessionmaker() as db: + targets = (await db.execute( + select(AnsibleTarget).order_by(AnsibleTarget.name) + )).scalars().all() + groups = (await db.execute( + select(AnsibleGroup).order_by(AnsibleGroup.name) + )).scalars().all() + + return await render_template( + "ansible/browse.html", + source_data=source_data, + targets=targets, + groups=groups, + ) @ansible_bp.get("/browse//") @@ -72,19 +88,35 @@ async def view_playbook(source_name: str, playbook_path: str): @ansible_bp.post("/runs") @require_role(UserRole.operator) async def create_run(): + import json as _json + from steward.ansible.inventory_gen import fetch_scope_targets, generate_inventory + form = await request.form playbook_path = form.get("playbook_path", "").strip() source_name = form.get("source_name", "").strip() - inventory_path = form.get("inventory_path", "").strip() + inventory_scope = form.get("inventory_scope", "steward:all").strip() - if not playbook_path or not inventory_path: - return "playbook_path and inventory_path are required", 400 + if not playbook_path: + return "playbook_path is required", 400 all_sources = _get_sources() source = next((s for s in all_sources if s["name"] == source_name), None) if source is None: return "Source not found", 404 + # Resolve inventory for this scope. + inventory_content: str | None = None + inventory_path: str | None = None + if inventory_scope.startswith("steward:"): + async with current_app.db_sessionmaker() as db: + targets = await fetch_scope_targets(db, inventory_scope) + inventory_content = _json.dumps(generate_inventory(targets)) + elif inventory_scope.startswith("repo:"): + parts = inventory_scope.split(":", 2) + inventory_path = parts[2] if len(parts) == 3 else "" + else: + return "Invalid inventory_scope", 400 + # Optional run parameters (all passed as argv by the executor — no shell). extra_vars: list[str] = [] for line in (form.get("extra_vars", "") or "").splitlines(): @@ -116,6 +148,7 @@ async def create_run(): id=run_id, playbook_path=playbook_path, inventory_path=inventory_path, + inventory_scope=inventory_scope, source_name=source_name, triggered_by=session["user_id"], status=AnsibleRunStatus.running, @@ -131,9 +164,10 @@ async def create_run(): current_app._get_current_object(), # type: ignore[attr-defined] run_id, playbook_path, - inventory_path, + inventory_path or "", source["path"], params_or_none, + inventory_content, ) ) task.add_done_callback( diff --git a/steward/templates/ansible/browse.html b/steward/templates/ansible/browse.html index e0b95b8..6591d79 100644 --- a/steward/templates/ansible/browse.html +++ b/steward/templates/ansible/browse.html @@ -71,18 +71,37 @@ readonly style="color:var(--text-muted);" value="">
- - + {% if targets %} + + + + {% if groups %} + + {% for grp in groups %} + + {% endfor %} + + {% endif %} + + {% for tgt in targets %} + + {% endfor %} + + {% endif %} + {% if sd.inventories %} + + {% for inv in sd.inventories %} + + {% endfor %} + + {% endif %} + {% if not targets and not sd.inventories %} + + {% endif %}
-
- - -