- /ansible/playbooks lists every playbook across sources: what it says it does, its category and destructive flag, how it last ran; the whole row opens the playbook. A source with none is one quiet line. - /ansible/playbooks/<source>/<path>: the verdict (what it does, flagged destructive, last ran), the run form in place (dry run beside the button, options folded), the schedules that use it, its recent runs, the playbook itself folded. ?from=<run> fills the form from that run: Run again on a run's page. - /ansible/browse, its view page and the /run-form fragment are gone, not redirected (rule 122); every link points at the new pages. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
97 lines
4.6 KiB
Python
97 lines
4.6 KiB
Python
"""Integration: the Ansible Runs tab (#5329) against real Postgres. Its
|
|
verdict counts the last 30 days; the filter bar narrows by playbook, status
|
|
and origin; an HTMX request from the bar gets the table alone, and the next
|
|
page as the reader scrolls keeps the filters. Requires STEWARD_DATABASE_URL."""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import os
|
|
import time
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
import pytest
|
|
|
|
pytestmark = pytest.mark.integration
|
|
|
|
|
|
@pytest.fixture
|
|
def app():
|
|
if not os.environ.get("STEWARD_DATABASE_URL"):
|
|
pytest.skip("needs Postgres")
|
|
from steward.app import create_app
|
|
return create_app(testing=False)
|
|
|
|
|
|
def test_the_runs_tab_counts_and_filters(app):
|
|
from steward.models.ansible import AnsibleRun, AnsibleRunStatus
|
|
from steward.models.users import User, UserRole
|
|
|
|
async def _go():
|
|
tag = uuid.uuid4().hex[:8]
|
|
uid = str(uuid.uuid4())
|
|
now = datetime.now(timezone.utc)
|
|
|
|
def run(name, status, mins, origin=None, by=None, results=None):
|
|
return AnsibleRun(playbook_path=f"it{tag}/{name}.yml", source_name="steward-builtin",
|
|
inventory_scope="steward:all", status=status, origin=origin,
|
|
triggered_by=by, started_at=now - timedelta(minutes=mins),
|
|
finished_at=now - timedelta(minutes=mins - 1), results=results)
|
|
async with app.db_sessionmaker() as s:
|
|
async with s.begin():
|
|
s.add(User(id=uid, username=f"runs-{tag}", email=f"runs-{tag}@test",
|
|
password_hash="x", role=UserRole.viewer, is_active=True))
|
|
async with s.begin():
|
|
s.add_all([
|
|
run("prune", AnsibleRunStatus.success, 10, origin="schedule",
|
|
results={"hosts": {"a": {"ok": 2, "changed": 1}, "b": {"ok": 2, "changed": 1}}}),
|
|
run("update", AnsibleRunStatus.failed, 20, by=uid,
|
|
results={"hosts": {f"db-{tag}": {"ok": 1, "failed": 1}}}),
|
|
])
|
|
|
|
client = app.test_client()
|
|
async with client.session_transaction() as sess:
|
|
sess.update(user_id=uid, user_role="viewer", username=f"runs-{tag}", login_at=time.time())
|
|
|
|
async def get(url, **kw):
|
|
resp = await client.get(url, **kw)
|
|
return resp.status_code, await resp.get_data(as_text=True)
|
|
|
|
out = {"page": await get(f"/ansible/?q=it{tag}"),
|
|
"failed": await get(f"/ansible/?q=it{tag}&status=failed", headers={"HX-Request": "true"}),
|
|
"sched": await get(f"/ansible/?q=it{tag}&origin=schedule", headers={"HX-Request": "true"}),
|
|
"person": await get(f"/ansible/?q=it{tag}&origin=manual", headers={"HX-Request": "true"}),
|
|
# The Playbooks tab and a built-in playbook's page (#5331): their
|
|
# newest-run and schedule queries against Postgres.
|
|
"playbooks": await get("/ansible/playbooks"),
|
|
"playbook": await get("/ansible/playbooks/steward-builtin/maintenance/docker_prune.yml"),
|
|
"missing": await get("/ansible/playbooks/steward-builtin/nope.yml")}
|
|
async with app.db_sessionmaker() as s:
|
|
async with s.begin():
|
|
from sqlalchemy import delete
|
|
await s.execute(delete(AnsibleRun).where(AnsibleRun.playbook_path.like(f"it{tag}/%")))
|
|
await s.delete(await s.get(User, uid))
|
|
return tag, out
|
|
|
|
tag, out = asyncio.run(_go())
|
|
status, page = out["page"]
|
|
assert status == 200
|
|
assert '<p class="page-verdict">' in page and "in the last 30 days" in page # the verdict queries ran
|
|
assert f"it{tag}/prune.yml" in page and f"it{tag}/update.yml" in page
|
|
assert "2 hosts · 2 changed" in page and f'<span class="tone-crit">failed on db-{tag}</span>' in page
|
|
assert '<nav class="section-tabs" aria-label="Ansible">' in page
|
|
|
|
failed = out["failed"][1]
|
|
assert "<html" not in failed and '<p class="page-verdict">' not in failed # the table alone
|
|
assert f"it{tag}/update.yml" in failed and f"it{tag}/prune.yml" not in failed
|
|
sched = out["sched"][1]
|
|
assert f"it{tag}/prune.yml" in sched and f"it{tag}/update.yml" not in sched
|
|
person = out["person"][1]
|
|
assert f"it{tag}/update.yml" in person and f"it{tag}/prune.yml" not in person
|
|
|
|
status, playbooks = out["playbooks"]
|
|
assert status == 200 and 'href="/ansible/playbooks/steward-builtin/maintenance/docker_prune.yml"' in playbooks
|
|
status, playbook = out["playbook"]
|
|
assert status == 200 and "The playbook" in playbook and 'id="run-form"' not in playbook # a viewer
|
|
assert out["missing"][0] == 404
|