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:
@@ -128,6 +128,57 @@
|
||||
</div>
|
||||
</details>
|
||||
|
||||
{# ── Ansible action (admin only) ─────────────────────────────────────── #}
|
||||
{% if session.get("user_role") == "admin" %}
|
||||
{% set act = rule.ansible_action if rule and rule.ansible_action else None %}
|
||||
<div style="margin:1.25rem 0;padding:1rem;border:1px solid var(--border-mid);border-radius:6px;background:var(--bg-elevated);">
|
||||
<label style="display:flex;align-items:center;gap:0.5rem;font-weight:600;">
|
||||
<input type="checkbox" name="ansible_enabled" {% if act %}checked{% endif %}>
|
||||
On firing → run an Ansible playbook
|
||||
</label>
|
||||
<p style="font-size:0.78rem;color:var(--text-muted);margin:0.4rem 0 0.75rem;">
|
||||
Admin only. Runs once when the alert transitions into FIRING.
|
||||
<code>steward_alert_*</code> context vars (resource, metric, value…) are injected automatically.
|
||||
</p>
|
||||
<div class="form-group">
|
||||
<label>Source</label>
|
||||
<select name="ansible_source">
|
||||
<option value="">— select source —</option>
|
||||
{% for s in ansible_sources %}
|
||||
<option value="{{ s }}" {% if act and act.source == s %}selected{% endif %}>{{ s }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Playbook <span style="color:var(--text-muted);font-weight:normal;">(path within source)</span></label>
|
||||
<input type="text" name="ansible_playbook" placeholder="playbooks/shutdown.yml"
|
||||
value="{{ act.playbook if act else '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Inventory <span style="color:var(--text-muted);font-weight:normal;">(path within source)</span></label>
|
||||
<input type="text" name="ansible_inventory" placeholder="inventory/hosts"
|
||||
value="{{ act.inventory if act else '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Extra vars <span style="color:var(--text-muted);font-weight:normal;">(optional, one key=value per line)</span></label>
|
||||
<textarea name="ansible_extra_vars" rows="2"
|
||||
style="width:100%;font-family:ui-monospace,monospace;font-size:0.85rem;">{{ act.extra_vars | join("\n") if act and act.extra_vars else "" }}</textarea>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Limit to host(s) <span style="color:var(--text-muted);font-weight:normal;">(optional)</span></label>
|
||||
<input type="text" name="ansible_limit" value="{{ act.limit if act and act.limit else '' }}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Tags <span style="color:var(--text-muted);font-weight:normal;">(optional)</span></label>
|
||||
<input type="text" name="ansible_tags" value="{{ act.tags if act and act.tags else '' }}">
|
||||
</div>
|
||||
<label style="display:flex;align-items:center;gap:0.5rem;font-weight:normal;">
|
||||
<input type="checkbox" name="ansible_check" {% if act and act.check %}checked{% endif %}>
|
||||
Dry-run (--check --diff)
|
||||
</label>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div style="display:flex;gap:1rem;margin-top:1.5rem;">
|
||||
<button type="submit" class="btn">{% if rule %}Save Changes{% else %}Create Rule{% endif %}</button>
|
||||
<a href="/alerts/" class="btn btn-ghost">Cancel</a>
|
||||
|
||||
Reference in New Issue
Block a user