9043ccfb5e
It is possible to configure a priority for an arbitrary label. Furthermore a priority/order of those arbitrary labels should be defined. The Alertmanager payload is checked for these and the priority will be selected from the highest valued label that has one set in the configuration file.
181 lines
4.4 KiB
Go
181 lines
4.4 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.xenrox.net/~xenrox/go-log"
|
|
"golang.org/x/text/cases"
|
|
"golang.org/x/text/language"
|
|
)
|
|
|
|
type receiver struct {
|
|
cfg *config
|
|
logger *log.Logger
|
|
}
|
|
|
|
type payload struct {
|
|
Status string `json:"status"`
|
|
Alerts []alert `json:"alerts"`
|
|
GroupLabels map[string]interface{} `json:"groupLabels"`
|
|
CommonLabels map[string]interface{} `json:"commonLabels"`
|
|
CommonAnnotations map[string]interface{} `json:"commonAnnotations"`
|
|
}
|
|
|
|
type alert struct {
|
|
Status string `json:"status"`
|
|
Labels map[string]interface{} `json:"labels"`
|
|
Annotations map[string]interface{} `json:"annotations"`
|
|
}
|
|
|
|
func (rcv *receiver) handleWebhooks(w http.ResponseWriter, r *http.Request) {
|
|
defer r.Body.Close()
|
|
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "Only POST allowed", http.StatusMethodNotAllowed)
|
|
rcv.logger.Debugf("illegal HTTP method: expected %q, got %q", "POST", r.Method)
|
|
return
|
|
}
|
|
|
|
contentType := r.Header.Get("Content-Type")
|
|
if contentType != "application/json" {
|
|
http.Error(w, "Only application/json allowed", http.StatusUnsupportedMediaType)
|
|
rcv.logger.Debugf("illegal content type: %s", contentType)
|
|
return
|
|
}
|
|
|
|
var event payload
|
|
if err := json.NewDecoder(r.Body).Decode(&event); err != nil {
|
|
rcv.logger.Debug(err)
|
|
return
|
|
}
|
|
|
|
count := len(event.Alerts)
|
|
title := fmt.Sprintf("[%s", strings.ToUpper(event.Status))
|
|
if event.Status == "firing" {
|
|
title = fmt.Sprintf("%s:%d", title, count)
|
|
}
|
|
|
|
title += "]"
|
|
for _, value := range event.GroupLabels {
|
|
title = fmt.Sprintf("%s %s", title, value)
|
|
}
|
|
|
|
var body string
|
|
c := cases.Title(language.English)
|
|
|
|
for _, alert := range event.Alerts {
|
|
alertBody := fmt.Sprintf("%s\nLabels:\n", c.String(alert.Status))
|
|
for key, value := range alert.Labels {
|
|
alertBody = fmt.Sprintf("%s%s = %s\n", alertBody, key, value)
|
|
}
|
|
|
|
alertBody += "Annotations:\n"
|
|
for key, value := range alert.Annotations {
|
|
alertBody = fmt.Sprintf("%s%s = %s\n", alertBody, key, value)
|
|
}
|
|
|
|
alertBody += "\n"
|
|
|
|
body += alertBody
|
|
}
|
|
|
|
client := &http.Client{Timeout: time.Second * 3}
|
|
req, err := http.NewRequest(http.MethodPost, rcv.cfg.ntfy.Topic, strings.NewReader(body))
|
|
if err != nil {
|
|
rcv.logger.Error(err)
|
|
}
|
|
|
|
// Basic auth
|
|
if rcv.cfg.ntfy.Password != "" && rcv.cfg.ntfy.User != "" {
|
|
auth := base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", rcv.cfg.ntfy.User, rcv.cfg.ntfy.Password)))
|
|
req.Header.Set("Authorization", fmt.Sprintf("Basic %s", auth))
|
|
}
|
|
|
|
req.Header.Set("X-Title", title)
|
|
|
|
var priority string
|
|
for _, labelName := range rcv.cfg.labels.Order {
|
|
val, ok := event.CommonLabels[labelName]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
labelConfig, ok := rcv.cfg.labels.Label[fmt.Sprintf("%s:%s", labelName, val)]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
if priority == "" {
|
|
priority = labelConfig.Priority
|
|
}
|
|
}
|
|
|
|
if priority != "" {
|
|
req.Header.Set("X-Priority", priority)
|
|
}
|
|
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
rcv.logger.Error(err)
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
if resp.StatusCode != http.StatusOK {
|
|
rcv.logger.Errorf("ntfy: received status code %d", resp.StatusCode)
|
|
return
|
|
}
|
|
}
|
|
|
|
func (rcv *receiver) basicAuthMiddleware(handler http.HandlerFunc) http.HandlerFunc {
|
|
return func(w http.ResponseWriter, r *http.Request) {
|
|
user, pass, ok := r.BasicAuth()
|
|
if !ok {
|
|
rcv.logger.Debug("basic auth failure")
|
|
return
|
|
}
|
|
|
|
if user != rcv.cfg.User || pass != rcv.cfg.Password {
|
|
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
rcv.logger.Debug("basic auth: wrong user or password")
|
|
return
|
|
}
|
|
|
|
handler(w, r)
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
var configPath string
|
|
flag.StringVar(&configPath, "config", "/etc/ntfy-alertmanager/config", "config file path")
|
|
flag.Parse()
|
|
|
|
logger := log.NewDefaultLogger()
|
|
|
|
cfg, err := readConfig(configPath)
|
|
if err != nil {
|
|
logger.Fatalf("config: %v", err)
|
|
}
|
|
|
|
if err := logger.ParseLevel(cfg.LogLevel); err != nil {
|
|
logger.Errorf("config: %v", err)
|
|
}
|
|
|
|
receiver := &receiver{cfg: cfg, logger: logger}
|
|
|
|
logger.Infof("Listening on %s", cfg.HTTPAddress)
|
|
|
|
if cfg.User != "" && cfg.Password != "" {
|
|
logger.Info("Enabling HTTP Basic Authentication")
|
|
http.HandleFunc("/", receiver.basicAuthMiddleware(receiver.handleWebhooks))
|
|
} else {
|
|
http.HandleFunc("/", receiver.handleWebhooks)
|
|
}
|
|
|
|
logger.Fatal(http.ListenAndServe(cfg.HTTPAddress, nil))
|
|
}
|