feat(ansible): scheduled recurring playbook runs
Adds cron-like recurring runs (the engine the maintenance-automation work needs). New AnsibleSchedule model + migration 0018; a core ScheduledTask (ansible_scheduled_runs, 60s) fires due schedules, each creating a system-triggered AnsibleRun (triggered_by=None). Centralises the resolve-inventory → create-run → launch flow in ansible/runner.trigger_run, shared by the manual route (refactored to use it) and the scheduler. Schedules UI under /ansible/schedules: create/edit/pause/delete/run-now, with interval presets, scope targeting (all / group / target), extra-vars / limit / tags / dry-run, and last-run status (resolved via last_run_id) + next-run. Unit test for the due-check. Task #549 (milestone #37). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ from .metrics import PluginMetric
|
||||
from .alerts import AlertRule, AlertState, AlertEvent, AlertOperator, AlertStateEnum
|
||||
from .ansible import AnsibleRun, AnsibleRunStatus
|
||||
from .ansible_inventory import AnsibleTarget, AnsibleGroup, ansible_target_groups
|
||||
from .ansible_schedule import AnsibleSchedule
|
||||
from .settings import AppSetting
|
||||
from .dashboard import Dashboard, DashboardWidget, DashboardShareToken
|
||||
|
||||
@@ -17,6 +18,7 @@ __all__ = [
|
||||
"AlertRule", "AlertState", "AlertEvent", "AlertOperator", "AlertStateEnum",
|
||||
"AnsibleRun", "AnsibleRunStatus",
|
||||
"AnsibleTarget", "AnsibleGroup", "ansible_target_groups",
|
||||
"AnsibleSchedule",
|
||||
"AppSetting",
|
||||
"Dashboard", "DashboardWidget", "DashboardShareToken",
|
||||
]
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
from __future__ import annotations
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy import Boolean, DateTime, Integer, JSON, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from .base import Base
|
||||
|
||||
|
||||
class AnsibleSchedule(Base):
|
||||
"""A recurring Ansible playbook run.
|
||||
|
||||
Fired by the core scheduler (ansible_scheduled_runs task) on an interval;
|
||||
each firing creates a system-triggered AnsibleRun (triggered_by=None). The
|
||||
actual run outcome is read back via last_run_id → AnsibleRun.status, so we
|
||||
only store provenance here, not a duplicated status.
|
||||
"""
|
||||
__tablename__ = "ansible_schedules"
|
||||
|
||||
id: Mapped[str] = mapped_column(String(36), primary_key=True, default=lambda: str(uuid.uuid4()))
|
||||
name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
source_name: Mapped[str] = mapped_column(String(128), nullable=False)
|
||||
playbook_path: Mapped[str] = mapped_column(String(512), nullable=False)
|
||||
inventory_scope: Mapped[str] = mapped_column(String(255), nullable=False, default="steward:all")
|
||||
# {extra_vars: [...], limit, tags, check} — same shape as AnsibleRun.params.
|
||||
params: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
||||
interval_seconds: Mapped[int] = mapped_column(Integer, nullable=False)
|
||||
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
last_run_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
last_run_id: Mapped[str | None] = mapped_column(String(36), nullable=True)
|
||||
# Set only when a firing failed to launch (e.g. source vanished); else NULL.
|
||||
last_error: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)
|
||||
)
|
||||
Reference in New Issue
Block a user