feat: add /alertmanager endpoint as Alertmanager -> Apprise bridge
🚀 Docker Build and Push / build-and-push (push) Successful in 3m55s

Reuses the same notification path as /crowdsec: formats the
Alertmanager webhook payload (firing/resolved, severity, instance,
summary/description) and forwards it to APPRISE_URL as {title, body}.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
maxime.killingerandClaude Sonnet 5 committed 2026-08-28 22:04:39 +02:00
1 parent bbcbb17302
commit 3d4ee65ec6
2 files changed
+83

No files matched your search

+15
View File
@@ -51,6 +51,21 @@ headers:
Content-Type: application/json Content-Type: application/json
``` ```
## Configuration Alertmanager
Sert aussi de pont entre Alertmanager et Apprise (le `webhook_configs` d'Alertmanager
n'a pas de templating de payload, il envoie son format natif tel quel).
Dans `alertmanager.yml` :
```yaml
receivers:
- name: 'apprise-notifications'
webhook_configs:
- url: 'http://crowdsec-notify:5000/alertmanager'
send_resolved: true
```
## Auteur ## Auteur
Maxime Killinger Maxime Killinger
+68
View File
@@ -111,6 +111,74 @@ def handle_crowdsec():
logger.exception(f"Error processing CrowdSec alert: {e}") logger.exception(f"Error processing CrowdSec alert: {e}")
return jsonify({"status": "error", "message": str(e)}), 500 return jsonify({"status": "error", "message": str(e)}), 500
@app.route('/alertmanager', methods=['POST'])
def handle_alertmanager():
try:
data = request.json or {}
alerts = data.get("alerts", [])
if not alerts:
logger.debug("Received empty Alertmanager payload")
return jsonify({"status": "no data"}), 200
def format_alert(alert):
labels = alert.get("labels", {})
annotations = alert.get("annotations", {})
name = labels.get("alertname", "alerte")
severity = labels.get("severity", "")
instance = labels.get("instance") or labels.get("container") or labels.get("job") or ""
head = name
if severity:
head += f" [{severity}]"
if instance:
head += f" — {instance}"
out = [head]
summary = annotations.get("summary")
description = annotations.get("description")
if summary:
out.append(f" {summary}")
if description and description != summary:
out.append(f" {description}")
return "\n".join(out)
firing = [a for a in alerts if a.get("status") == "firing"]
resolved = [a for a in alerts if a.get("status") == "resolved"]
lines = []
if firing:
lines.append(f"🔥 {len(firing)} alerte(s) active(s)")
lines.extend(format_alert(a) for a in firing)
if resolved:
if lines:
lines.append("")
lines.append(f"✅ {len(resolved)} résolue(s)")
lines.extend(format_alert(a) for a in resolved)
status = data.get("status", "firing")
group_name = data.get("groupLabels", {}).get("alertname", "")
title = ("🔥" if status == "firing" else "✅") + " Alertmanager"
if group_name:
title += f" — {group_name}"
body = "\n".join(lines).strip()
# Send to Apprise
response = requests.post(APPRISE_URL, json={"title": title, "body": body}, timeout=10)
if response.ok:
logger.info(f"Alertmanager notification sent: {len(firing)} firing, {len(resolved)} resolved")
else:
logger.error(f"Apprise error: {response.status_code} - {response.text}")
return jsonify({"status": "error", "code": response.status_code}), 502
return jsonify({"status": "sent", "firing": len(firing), "resolved": len(resolved)}), 200
except Exception as e:
logger.exception(f"Error processing Alertmanager alert: {e}")
return jsonify({"status": "error", "message": str(e)}), 500
@app.route('/health', methods=['GET']) @app.route('/health', methods=['GET'])
def health(): def health():
return jsonify({"status": "ok"}), 200 return jsonify({"status": "ok"}), 200