#5212 "Plain labels: no raw ids, scopes, metric keys or unexplained jargon", batch 2: - A run records where it came from: AnsibleRun.origin ("schedule", "alert" or "mcp") and origin_id, set by the scheduler, Run now, alert actions and the MCP tools. Migration 0031 backfills past runs from alert events, schedules' last runs, and MCP audit entries. - The runs list has a Started by column: "schedule nightly", "alert rule nas down", "alice via MCP" or "alice", linked where there is a page. The raw scope tooltip is gone. - Run detail: no run id in the subtitle or body, Started by in words, and the host summary lists failed and unreachable hosts first. The live output only follows new lines while the reader is at the bottom, and when the run finishes the page reloads once so the badge and summary are final. - list_ansible_runs and get_ansible_run report origin and origin_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
52 lines
2.5 KiB
Python
52 lines
2.5 KiB
Python
from __future__ import annotations
|
|
import enum
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
from sqlalchemy import JSON, DateTime, Enum, ForeignKey, String, Text
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
from .base import Base
|
|
|
|
|
|
class AnsibleRunStatus(str, enum.Enum):
|
|
queued = "queued" # waiting for a concurrency slot
|
|
running = "running"
|
|
success = "success"
|
|
failed = "failed"
|
|
interrupted = "interrupted" # process/host died mid-run (e.g. app restart)
|
|
cancelled = "cancelled" # stopped by an operator
|
|
|
|
|
|
class AnsibleRun(Base):
|
|
__tablename__ = "ansible_runs"
|
|
|
|
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
|
playbook_path: Mapped[str] = mapped_column(String(512), nullable=False)
|
|
inventory_path: Mapped[str | None] = mapped_column(String(512), nullable=True)
|
|
inventory_scope: Mapped[str] = mapped_column(
|
|
String(255), nullable=False, default="steward:all"
|
|
)
|
|
source_name: Mapped[str] = mapped_column(String(128), nullable=False)
|
|
# Nullable: automated runs (alert actions, schedules) have no human actor —
|
|
# NULL renders as "system". Manual runs still record the triggering user.
|
|
triggered_by: Mapped[str | None] = mapped_column(
|
|
String(36), ForeignKey("users.id", ondelete="RESTRICT"), nullable=True
|
|
)
|
|
# Where the run came from, beyond who started it: "schedule" or "alert"
|
|
# (origin_id is that schedule's or rule's id), or "mcp" for an agent on a
|
|
# user's token. NULL is a person on the web pages.
|
|
origin: Mapped[str | None] = mapped_column(String(16), nullable=True)
|
|
origin_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
|
status: Mapped[AnsibleRunStatus] = mapped_column(
|
|
Enum(AnsibleRunStatus), nullable=False, default=AnsibleRunStatus.running
|
|
)
|
|
started_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)
|
|
)
|
|
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
|
output: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
# Optional run parameters: {extra_vars: [..], limit: str, tags: str, check: bool}.
|
|
params: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
# Parsed per-host summary + captured failures:
|
|
# {hosts: {name: {ok,changed,unreachable,failed,skipped}}, failures: [str]}.
|
|
results: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|