"""The Playbooks tab and a playbook's own page (#5331): one list across sources whose row opens the playbook; the page carries the form that runs it (filled from an earlier run for "Run again"), the schedules that use it and its recent runs.""" from datetime import datetime, timedelta, timezone from types import SimpleNamespace from quart import render_template, session from steward.ansible.routes import run_prefill, run_view from steward.models.ansible import AnsibleRunStatus from tests.test_style_tokens import hex_colours def _run(rid, status, mins_ago, params=None): now = datetime.now(timezone.utc) return SimpleNamespace(id=rid, playbook_path="maintenance/prune.yml", source_name="steward-builtin", inventory_scope="steward:all", status=status, started_at=now - timedelta(minutes=mins_ago), finished_at=now - timedelta(minutes=mins_ago - 1), params=params, results={"hosts": {"a": {"ok": 2, "changed": 1}}}, output="") def test_playbook_urls_reach_their_own_routes(app): """The playbook page's path converter must not swallow the editor's URLs.""" urls = app.url_map.bind("localhost") assert urls.match("/ansible/playbooks/steward-builtin/maintenance/prune.yml")[0] == "ansible.playbook_page" assert urls.match("/ansible/playbooks/edit/steward-local/a/b.yml")[0] == "ansible.edit_playbook" assert urls.match("/ansible/playbooks/new")[0] == "ansible.new_playbook" assert urls.match("/ansible/playbooks")[0] == "ansible.playbooks" import pytest from werkzeug.exceptions import NotFound with pytest.raises(NotFound): urls.match("/ansible/browse") # moved, not redirected (rule 122) async def test_the_playbooks_list_is_one_table_across_sources(app): last = _run("r1", AnsibleRunStatus.success, 60) rows = [{"source": "steward-builtin", "path": "maintenance/prune.yml", "description": "Reclaim disk", "category": "maintenance", "confirm": True, "last": last}, {"source": "steward-builtin", "path": "host_agent/update.yml", "description": "Update the agent", "category": "", "confirm": False, "last": None}] empty = [{"name": "steward-local", "path": "/data/ansible/playbooks", "editable": True}] async with app.test_request_context("/ansible/playbooks"): session["user_role"] = "admin" page = await render_template("ansible/playbooks.html", rows=rows, empty_sources=empty, sources=[{"name": "steward-builtin"}, {"name": "steward-local"}], can_add=True) main = page[page.index("') == 2 assert 'class="row-target mono" href="/ansible/playbooks/steward-builtin/maintenance/prune.yml"' in main assert "2 playbooks from 1 source" in main and "1 flagged destructive by its author" in main assert ">destructive" in main and 'maintenance' in main assert 'Succeeded' in main and "never run" in main assert "steward-local" in main and "has no playbooks yet." in main and 'href="/ansible/playbooks/new"' in main assert 'Playbooks' in main assert "/run-form/" not in main # the form lives on the playbook's page assert not hex_colours(main), hex_colours(main) def _page_ctx(role_runs=True, prefill=None, schedules=None): now = datetime.now(timezone.utc) run = _run("r9", AnsibleRunStatus.failed, 30, params={"check": True}) return { "source": {"name": "steward-builtin"}, "source_name": "steward-builtin", "playbook_path": "maintenance/prune.yml", "contents": "- hosts: all\n", "description": "Reclaim disk", "category": "maintenance", "confirm": True, "variables": [{"name": "prune_volumes", "prompt": "", "default": "false", "required": False, "secret": False}], "editable": False, "runs": [{**run_view(run, {"steward:all": "all inventory targets"}, now), "origin": {"text": "alice", "url": None, "kind": "user"}}] if role_runs else [], "schedules": schedules or [], "targets": [SimpleNamespace(id="t1", name="web-1")], "groups": [SimpleNamespace(id="g1", name="Docker Swarm")], "inventories": [], "prefill": prefill or run_prefill(None, set()), } async def test_a_playbook_page_runs_it_and_shows_its_history(app): now = datetime.now(timezone.utc) sched = SimpleNamespace(id="s1", name="Nightly prune", enabled=True, interval_seconds=86400, last_run_at=now - timedelta(hours=22), inventory_scope="steward:group:g1") async with app.test_request_context("/ansible/playbooks/steward-builtin/maintenance/prune.yml"): session["user_role"] = "operator" page = await render_template("ansible/playbook.html", **_page_ctx(schedules=[ {"s": sched, "scope_label": "group Docker Swarm", "next": {"id": "s1", "name": "Nightly prune", "at": now + timedelta(hours=2)}}])) main = page[page.index("'):] verdict = verdict[:verdict.index("

")] assert "Reclaim disk" in verdict and "flagged destructive" in verdict and "last ran" in verdict assert 'id="run-form"' in main and 'hx-post="/ansible/runs"' in main assert 'name="confirmed"' in main # the destructive flag's gate assert 'name="var__prune_volumes"' in main and 'name="check"' in main assert "Nightly prune" in main and "on group Docker Swarm ยท daily" in main assert 'href="/ansible/schedules/s1"' in main assert 'href="/ansible/schedules/new?source=steward-builtin&playbook=maintenance/prune.yml"' in main assert 'href="/ansible/runs/r9"' in main and "End of the list." not in main assert 'href="/ansible/?q=maintenance/prune.yml"' in main assert "/ansible/playbooks/edit/" not in main # a built-in playbook is read-only assert not hex_colours(main), hex_colours(main) async def test_a_viewer_reads_the_playbook_but_gets_no_form(app): async with app.test_request_context("/ansible/playbooks/steward-builtin/maintenance/prune.yml"): session["user_role"] = "viewer" page = await render_template("ansible/playbook.html", **_page_ctx(role_runs=False)) assert 'id="run-form"' not in page and "never run" in page and "No schedule runs it." in page def test_run_again_fills_the_form_from_the_earlier_run(): run = SimpleNamespace(inventory_scope="steward:group:g1", params={ "extra_vars_map": {"prune_volumes": "true", "other": "x"}, "limit": "web*", "tags": "a", "check": True}) assert run_prefill(run, {"prune_volumes"}) == { "scope": "steward:group:g1", "values": {"prune_volumes": "true"}, "extra": "other=x", "limit": "web*", "tags": "a", "check": True} assert run_prefill(None, set())["scope"] == "" async def test_the_form_comes_back_as_the_earlier_run_left_it(app): prefill = run_prefill(SimpleNamespace(inventory_scope="steward:group:g1", params={ "extra_vars_map": {"prune_volumes": "true", "other": "x"}, "limit": "web*", "check": True}), {"prune_volumes"}) async with app.test_request_context("/ansible/playbooks/x"): session["user_role"] = "operator" page = await render_template("ansible/playbook.html", **_page_ctx(prefill=prefill)) assert '' in page assert 'name="var__prune_volumes"' in page and 'value="true"' in page assert ">other=x" in page and 'value="web*"' in page assert '
' in page and 'name="check" checked' in page