diff --git a/bridge/alertmanager.go b/bridge/alertmanager.go new file mode 100644 index 0000000..c1fb9a9 --- /dev/null +++ b/bridge/alertmanager.go @@ -0,0 +1,70 @@ +package bridge + +import ( + "encoding/json" + "log/slog" + "net/http" + "strings" + "time" +) + +type AlertmanagerEvent struct { + Receiver string `json:"receiver"` + Status string `json:"status"` + Alerts []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"` + } `json:"alerts"` + GroupLabels map[string]string `json:"groupLabels"` + CommonLabels map[string]string `json:"commonLabels"` + CommonAnnotations map[string]string `json:"commonAnnotations"` + ExternalURL string `json:"externalURL"` + Version string `json:"version"` + GroupKey string `json:"groupKey"` + TruncatedAlerts int `json:"truncatedAlerts"` +} + +type AlertmanagerHandler struct{} + +func NewAlertmanagerHandler() AlertmanagerHandler { + return AlertmanagerHandler{} +} + +func (d AlertmanagerHandler) ProduceNotifications(r *http.Request) ([]Notification, error) { + l := slog.With(slog.String("handler", "alertmanager")) + + dec := json.NewDecoder(r.Body) + defer r.Body.Close() + + var event AlertmanagerEvent + if err := dec.Decode(&event); err != nil { + l.Error("invalid message format", "error", err) + return nil, err + } + + notifications := make([]Notification, 0, len(event.Alerts)) + for _, alert := range event.Alerts { + if alert.Annotations["summary"] == "" { + continue + } + + var not Notification + not.Title = "[" + strings.ToUpper(event.Status) + "] " + alert.Annotations["summary"] + not.Body = alert.Annotations["description"] + if runbook := alert.Annotations["runbook_url"]; runbook != "" { + not.Actions = append(not.Actions, NewViewAction("Runbook", runbook)) + } + if event.Status == "resolved" { + not.Tags = []string{"resolved"} + } + + notifications = append(notifications, not) + } + + return notifications, nil +} diff --git a/config/config.go b/config/config.go index a02188c..9e86006 100644 --- a/config/config.go +++ b/config/config.go @@ -12,6 +12,7 @@ type HandlerType int const ( HandlerFlux HandlerType = iota + 1 HandlerDiscordEmbed + HandlerAlertmanager ) func (h HandlerType) String() string { @@ -20,6 +21,8 @@ func (h HandlerType) String() string { return "flux" case HandlerDiscordEmbed: return "discord_embed" + case HandlerAlertmanager: + return "alertmanager" } panic("unreachable") } @@ -153,6 +156,8 @@ func readHandlerType(d *scfg.Directive) (HandlerType, error) { return HandlerFlux, nil case "discord_embed": return HandlerDiscordEmbed, nil + case "alertmanager": + return HandlerAlertmanager, nil default: return 0, fmt.Errorf("invalid handler type %q", ty) } diff --git a/k8s/bridge.yaml b/k8s/bridge.yaml index b9f00b5..915188d 100644 --- a/k8s/bridge.yaml +++ b/k8s/bridge.yaml @@ -22,6 +22,11 @@ data: type "discord_embed" topic "forgejo" } + + handler "/alerts" { + type "alertmanager" + topic "infra" + } --- apiVersion: apps/v1 kind: Deployment diff --git a/main.go b/main.go index 252371e..0846dd7 100644 --- a/main.go +++ b/main.go @@ -76,6 +76,8 @@ func main() { h = bridge.NewFluxHandler() case config.HandlerDiscordEmbed: h = bridge.NewDiscordEmbedHandler() + case config.HandlerAlertmanager: + h = bridge.NewAlertmanagerHandler() } slog.Debug("Registering bridge", "route", route, "handler", handler.Type)