package main import ( "bytes" "encoding/base64" "encoding/json" "fmt" "io" "log/slog" "net/http" "time" ) const dateLayout = "2006-01-02 15:04:05" type silence struct { Matchers []matcher `json:"matchers"` StartsAt string `json:"startsAt"` EndsAt string `json:"endsAt"` CreatedBy string `json:"createdBy"` Comment string `json:"comment"` } type matcher struct { Name string `json:"name"` Value string `json:"value"` IsRegex bool `json:"isRegex"` IsEqual bool `json:"isEqual"` } type silenceBody struct { AlertManagerURL string `json:"alertmanagerURL"` Labels map[string]string `json:"labels"` } type silenceResponse struct { ID string `json:"silenceID"` } func (br *bridge) handleSilences(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() if r.Method != http.MethodPost { http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed) br.logger.Debug(fmt.Sprintf("Silences: Illegal HTTP method: expected %q, got %q", "POST", r.Method)) return } b, err := io.ReadAll(r.Body) if err != nil { br.logger.Error("Silences: Failed to read body", slog.String("error", err.Error())) return } b, err = base64.StdEncoding.DecodeString(string(b)) if err != nil { br.logger.Error("Silences: Failed to decode", slog.String("error", err.Error())) return } var sb silenceBody err = json.Unmarshal(b, &sb) if err != nil { br.logger.Error("Silences: Failed to unmarshal", slog.String("error", err.Error())) return } var matchers []matcher for key, value := range sb.Labels { m := matcher{ Name: key, Value: value, IsRegex: false, IsEqual: true, } matchers = append(matchers, m) } silence := &silence{ StartsAt: time.Now().UTC().Format(dateLayout), EndsAt: time.Now().Add(br.cfg.Am.SilenceDuration).UTC().Format(dateLayout), CreatedBy: "ntfy-alertmanager", Comment: "", Matchers: matchers, } b, err = json.Marshal(silence) if err != nil { br.logger.Error("Silences: Failed to marshal", slog.String("error", err.Error())) return } url := sb.AlertManagerURL if br.cfg.Am.URL != "" { url = br.cfg.Am.URL } url += "/api/v2/silences" req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(b)) if err != nil { br.logger.Error("Silences: Failed to create request", slog.String("error", err.Error())) return } // Basic auth if br.cfg.Am.User != "" && br.cfg.Am.Password != "" { req.SetBasicAuth(br.cfg.Am.User, br.cfg.Am.Password) } req.Header.Add("Content-Type", "application/json") resp, err := br.client.Do(req) if err != nil { br.logger.Error("Silences: Failed to POST request", slog.String("error", err.Error())) return } defer resp.Body.Close() b, err = io.ReadAll(resp.Body) if err != nil { br.logger.Error("Silences: Failed to read response body", slog.String("error", err.Error())) return } if resp.StatusCode != http.StatusOK { br.logger.Error("Silences: Received non-200 status code", slog.Int("status", resp.StatusCode)) return } var id silenceResponse if err := json.Unmarshal(b, &id); err != nil { br.logger.Error("Silences: Failed to unmarshal response", slog.String("error", err.Error())) return } br.logger.Info("Silences: Created new silence", slog.String("ID", id.ID)) }