4a0a3ee46e
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>
35 lines
1.8 KiB
Python
35 lines
1.8 KiB
Python
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)
|
|
)
|