ntfy-alertmanager/silence.go
Thorben Günther 6d64c21549
Use Logger.With to create child loggers for the handlers
With that it will be easier to check on which endpoint a failure
happened.
2023-08-13 15:16:26 +02:00

149 lines
3.2 KiB
Go

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()
logger := br.logger.With(slog.String("handler", "/silences"))
if r.Method != http.MethodPost {
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
logger.Debug(fmt.Sprintf("Illegal HTTP method: expected %q, got %q", "POST", r.Method))
return
}
b, err := io.ReadAll(r.Body)
if err != nil {
logger.Error("Failed to read body",
slog.String("error", err.Error()))
return
}
b, err = base64.StdEncoding.DecodeString(string(b))
if err != nil {
logger.Error("Failed to decode",
slog.String("error", err.Error()))
return
}
var sb silenceBody
err = json.Unmarshal(b, &sb)
if err != nil {
logger.Error("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 {
logger.Error("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 {
logger.Error("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 {
logger.Error("Failed to POST request",
slog.String("error", err.Error()))
return
}
defer resp.Body.Close()
b, err = io.ReadAll(resp.Body)
if err != nil {
logger.Error("Failed to read response body",
slog.String("error", err.Error()))
return
}
if resp.StatusCode != http.StatusOK {
logger.Error("Received non-200 status code",
slog.Int("status", resp.StatusCode))
return
}
var id silenceResponse
if err := json.Unmarshal(b, &id); err != nil {
logger.Error("Failed to unmarshal response",
slog.String("error", err.Error()))
return
}
logger.Info("Created new silence",
slog.String("ID", id.ID))
}