feat(alerts): run an Ansible playbook on alert firing (task 250)
Rebuilds the deleted NUT/UPS automation as a general alert action: any metric can drive a playbook run on transition-to-firing. - models/alerts.py + migration 0014: AlertRule.ansible_action (JSON, admin-only, reuses the #546 param shape); AlertEvent.ansible_run_id links a firing event to the run it triggered - core/alerts.py: pure alert_extra_vars() injects steward_alert_* context; on ('firing', event) with an action set, schedule _run_ansible_action (deferred after commit, same pattern as notifications) — fires once per transition, consecutive_failures_required is the debounce; system-triggered AnsibleRun - alerts/routes.py: admin-only parse/validate of the action (source must be configured, playbook must exist); operators keep editing rules, action preserved - rules_form.html: admin-only 'On firing -> run a playbook' section - tests: unit for alert_extra_vars; integration drives record_metric to firing and asserts a system AnsibleRun ran the playbook with the injected var and the event linked to it Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@ from __future__ import annotations
|
||||
import enum
|
||||
import uuid
|
||||
from datetime import datetime, timezone
|
||||
from sqlalchemy import Boolean, DateTime, Enum, Float, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy import JSON, Boolean, DateTime, Enum, Float, ForeignKey, Integer, String, Text
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
from .base import Base
|
||||
|
||||
@@ -36,6 +36,9 @@ class AlertRule(Base):
|
||||
threshold: Mapped[float] = mapped_column(Float, nullable=False)
|
||||
consecutive_failures_required: Mapped[int] = mapped_column(Integer, nullable=False, default=1)
|
||||
enabled: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
# Optional admin-only action: run an Ansible playbook on transition-to-firing.
|
||||
# Shape: {source, playbook, inventory, extra_vars, limit, tags, check}. NULL = none.
|
||||
ansible_action: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
||||
created_by: Mapped[str] = mapped_column(String(36), ForeignKey("users.id", ondelete="RESTRICT"), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)
|
||||
@@ -97,3 +100,7 @@ class AlertEvent(Base):
|
||||
email_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
webhook_sent: Mapped[bool | None] = mapped_column(Boolean, nullable=True)
|
||||
webhook_error: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
# Set when this firing event triggered an Ansible action run (audit link).
|
||||
ansible_run_id: Mapped[str | None] = mapped_column(
|
||||
String(36), ForeignKey("ansible_runs.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user