9b4ca1482f
- Migration 003: alerts table with fingerprint UNIQUE, JSON label/annotation
columns, nullable ends_at, and indexed status/name/received_at
- POST /api/alertmanager/webhook — upserts each alert by fingerprint;
zero endsAt ("0001-01-01") stored as NULL (still firing)
- GET /api/alerts — filtered list (?status, ?name, ?from, ?to, ?limit)
- GET /api/alerts/{id} — single alert lookup
73 lines
2.0 KiB
Go
73 lines
2.0 KiB
Go
package api
|
|
|
|
import (
|
|
"database/sql"
|
|
"encoding/json"
|
|
"log"
|
|
"net/http"
|
|
"time"
|
|
)
|
|
|
|
// amPayload mirrors the Alertmanager webhook v4 payload.
|
|
type amPayload struct {
|
|
Version string `json:"version"`
|
|
Status string `json:"status"`
|
|
Alerts []amAlert `json:"alerts"`
|
|
}
|
|
|
|
type amAlert struct {
|
|
Status string `json:"status"`
|
|
Labels map[string]string `json:"labels"`
|
|
Annotations map[string]string `json:"annotations"`
|
|
StartsAt time.Time `json:"startsAt"`
|
|
EndsAt time.Time `json:"endsAt"`
|
|
GeneratorURL string `json:"generatorURL"`
|
|
Fingerprint string `json:"fingerprint"`
|
|
}
|
|
|
|
func handleAlertmanagerWebhook(db *sql.DB) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
var payload amPayload
|
|
if err := decodeJSON(r, &payload); err != nil {
|
|
respond(w, http.StatusBadRequest, errResp("invalid payload"))
|
|
return
|
|
}
|
|
|
|
now := time.Now().Unix()
|
|
for _, a := range payload.Alerts {
|
|
name := a.Labels["alertname"]
|
|
labelsJSON, _ := json.Marshal(a.Labels)
|
|
annotationsJSON, _ := json.Marshal(a.Annotations)
|
|
|
|
// Alertmanager uses zero time ("0001-01-01T00:00:00Z") to mean "still firing".
|
|
var endsAtUnix *int64
|
|
if a.EndsAt.Year() > 1 {
|
|
t := a.EndsAt.Unix()
|
|
endsAtUnix = &t
|
|
}
|
|
|
|
_, err := db.ExecContext(r.Context(), `
|
|
INSERT INTO alerts
|
|
(fingerprint, name, status, labels, annotations, starts_at, ends_at, generator_url, received_at)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(fingerprint) DO UPDATE SET
|
|
status = excluded.status,
|
|
labels = excluded.labels,
|
|
annotations = excluded.annotations,
|
|
ends_at = excluded.ends_at,
|
|
generator_url = excluded.generator_url,
|
|
received_at = excluded.received_at`,
|
|
a.Fingerprint, name, a.Status,
|
|
string(labelsJSON), string(annotationsJSON),
|
|
a.StartsAt.Unix(), endsAtUnix,
|
|
a.GeneratorURL, now,
|
|
)
|
|
if err != nil {
|
|
log.Printf("upsert alert %s: %v", a.Fingerprint, err)
|
|
}
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
}
|
|
}
|