feat: add /alertmanager endpoint as Alertmanager -> Apprise bridge
🚀 Docker Build and Push / build-and-push (push) Successful in 3m55s
🚀 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:
1 parent
bbcbb17302
commit
3d4ee65ec6
2 files changed
+83
No files matched your search
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in new issue
Block a user