Files
FabledSteward/steward/migrations/versions/0018_ansible_schedules.py
T
bvandeusen 4a0a3ee46e
CI / lint (push) Successful in 3s
CI / unit (push) Successful in 7s
CI / integration (push) Successful in 2m20s
CI / publish (push) Successful in 49s
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>
2026-06-16 11:04:02 -04:00

37 lines
1.4 KiB
Python

"""Ansible schedules table — recurring playbook runs
Revision ID: 0018_ansible_schedules
Revises: 0017_ansible_run_scope
Create Date: 2026-06-16
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
revision: str = "0018_ansible_schedules"
down_revision: Union[str, None] = "0017_ansible_run_scope"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
op.create_table(
"ansible_schedules",
sa.Column("id", sa.String(36), primary_key=True),
sa.Column("name", sa.String(128), nullable=False),
sa.Column("source_name", sa.String(128), nullable=False),
sa.Column("playbook_path", sa.String(512), nullable=False),
sa.Column("inventory_scope", sa.String(255), nullable=False, server_default="steward:all"),
sa.Column("params", sa.JSON(), nullable=True),
sa.Column("interval_seconds", sa.Integer(), nullable=False),
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.true()),
sa.Column("last_run_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("last_run_id", sa.String(36), nullable=True),
sa.Column("last_error", sa.String(255), nullable=True),
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False),
)
def downgrade() -> None:
op.drop_table("ansible_schedules")