A self-hosted orchestration system crafted for power users. composia.xyz
Something went wrong. Try again.
4.7 kB · 143 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144package controller
import ( "encoding/json" "net/http" "strings" "time"
appnotify "forgejo.alexma.top/alexma233/composia/internal/app/notify" "forgejo.alexma.top/alexma233/composia/internal/core/config" corenotify "forgejo.alexma.top/alexma233/composia/internal/core/notify")
type notificationDispatcher interface { Dispatch(appnotify.Event)}
type alertmanagerWebhookPayload struct { Receiver string `json:"receiver"` Status string `json:"status"` GroupLabels map[string]string `json:"groupLabels"` CommonLabels map[string]string `json:"commonLabels"` CommonAnnotations map[string]string `json:"commonAnnotations"` ExternalURL string `json:"externalURL"` Alerts []alertmanagerAlert `json:"alerts"`}
type alertmanagerAlert struct { Status string `json:"status"` Labels map[string]string `json:"labels"` Annotations map[string]string `json:"annotations"` StartsAt string `json:"startsAt"` EndsAt string `json:"endsAt"` GeneratorURL string `json:"generatorURL"` Fingerprint string `json:"fingerprint"`}
type alertmanagerHandler struct { notifier notificationDispatcher}
func registerAlertmanagerHandler(mux *http.ServeMux, cfg *config.ControllerNotificationsConfig, notifier notificationDispatcher) { if mux == nil || cfg == nil || cfg.Alertmanager == nil || !cfg.Alertmanager.IsEnabled() { return } mux.Handle(cfg.Alertmanager.EffectiveListenPath(), &alertmanagerHandler{notifier: notifier})}
func (handler *alertmanagerHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { if req.Method != http.MethodPost { w.Header().Set("Allow", http.MethodPost) http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } defer func() { _ = req.Body.Close() }() var payload alertmanagerWebhookPayload decoder := json.NewDecoder(http.MaxBytesReader(w, req.Body, 1<<20)) decoder.DisallowUnknownFields() if err := decoder.Decode(&payload); err != nil { http.Error(w, "invalid alertmanager payload", http.StatusBadRequest) return } if len(payload.Alerts) == 0 { w.WriteHeader(http.StatusAccepted) return } for _, alert := range payload.Alerts { handler.dispatchAlert(payload, alert) } w.WriteHeader(http.StatusAccepted)}
func (handler *alertmanagerHandler) dispatchAlert(payload alertmanagerWebhookPayload, alert alertmanagerAlert) { if handler == nil || handler.notifier == nil { return } labels := mergeStringMaps(payload.CommonLabels, alert.Labels) annotations := mergeStringMaps(payload.CommonAnnotations, alert.Annotations) startsAt := parseAlertmanagerTime(alert.StartsAt) endsAt := parseAlertmanagerTime(alert.EndsAt) handler.notifier.Dispatch(appnotify.Event{ Type: corenotify.EventAlertmanagerAlert, OccurredAt: derefTaskTime(startsAt), Alertmanager: &appnotify.AlertmanagerEvent{ Receiver: payload.Receiver, Status: strings.TrimSpace(alert.Status), GroupStatus: strings.TrimSpace(payload.Status), AlertName: strings.TrimSpace(labels["alertname"]), Severity: strings.TrimSpace(labels["severity"]), Instance: strings.TrimSpace(labels["instance"]), Summary: strings.TrimSpace(annotations["summary"]), Description: strings.TrimSpace(annotations["description"]), ExternalURL: strings.TrimSpace(payload.ExternalURL), GeneratorURL: strings.TrimSpace(alert.GeneratorURL), Fingerprint: strings.TrimSpace(alert.Fingerprint), StartsAt: startsAt, EndsAt: endsAt, Labels: labels, Annotations: annotations, GroupLabels: cloneStringMap(payload.GroupLabels), CommonLabels: cloneStringMap(payload.CommonLabels), CommonAnnotations: cloneStringMap(payload.CommonAnnotations), }, })}
func mergeStringMaps(left, right map[string]string) map[string]string { if len(left) == 0 && len(right) == 0 { return nil } merged := cloneStringMap(left) if merged == nil { merged = make(map[string]string, len(right)) } for key, value := range right { merged[key] = value } return merged}
func cloneStringMap(values map[string]string) map[string]string { if len(values) == 0 { return nil } cloned := make(map[string]string, len(values)) for key, value := range values { cloned[key] = value } return cloned}
func parseAlertmanagerTime(value string) *time.Time { value = strings.TrimSpace(value) if value == "" { return nil } parsed, err := time.Parse(time.RFC3339, value) if err != nil { return nil } parsed = parsed.UTC() return &parsed}