From 8b465d081591c68a600f37909077b32c976310f2 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Fri, 8 Apr 2022 19:57:30 +0200 Subject: [PATCH 1/9] alertmanager interface --- cmd/pushbits/main.go | 2 +- config.example.yml | 7 ++ internal/api/alertmanager/handler.go | 137 ++++++++++++++++++++++++ internal/configuration/configuration.go | 11 +- internal/router/router.go | 10 +- 5 files changed, 163 insertions(+), 4 deletions(-) create mode 100644 internal/api/alertmanager/handler.go diff --git a/cmd/pushbits/main.go b/cmd/pushbits/main.go index 30d9c50..dae3431 100644 --- a/cmd/pushbits/main.go +++ b/cmd/pushbits/main.go @@ -75,7 +75,7 @@ func main() { log.Fatal(err) } - engine := router.Create(c.Debug, cm, db, dp) + engine := router.Create(c.Debug, cm, db, dp, &c.Alertmanager) err = runner.Run(engine, c.HTTP.ListenAddress, c.HTTP.Port) if err != nil { diff --git a/config.example.yml b/config.example.yml index 34457c5..3c5ccc2 100644 --- a/config.example.yml +++ b/config.example.yml @@ -60,3 +60,10 @@ crypto: formatting: # Whether to use colored titles based on the message priority (<0: grey, 0-3: default, 4-10: yellow, 10-20: orange, >20: red). coloredtitle: false + +# This settings are only relevant if you want to use PushBits with alertmanager +alertmanager: + # The name of the entry in the alerts annotations that should be used for the title + annotationtitle: title + # The name of the entry in the alerts annotations that should be used for the message + annotationmessage: message diff --git a/internal/api/alertmanager/handler.go b/internal/api/alertmanager/handler.go new file mode 100644 index 0000000..07436b8 --- /dev/null +++ b/internal/api/alertmanager/handler.go @@ -0,0 +1,137 @@ +package alertmanager + +import ( + "log" + "net/http" + "net/url" + "strings" + + "github.com/gin-gonic/gin" + "github.com/pushbits/server/internal/api" + "github.com/pushbits/server/internal/authentication" + "github.com/pushbits/server/internal/model" + "github.com/pushbits/server/internal/pberrors" +) + +type AlertmanagerHandler struct { + DP api.NotificationDispatcher + Settings AlertmanagerHandlerSettings +} + +type AlertmanagerHandlerSettings struct { + TitleAnnotation string + MessageAnnotation string +} + +type hookMessage struct { + Version string `json:"version"` + GroupKey string `json:"groupKey"` + Receiver string `json:"receiver"` + GroupLabels map[string]string `json:"groupLabels"` + CommonLabels map[string]string `json:"commonLabels"` + CommonAnnotations map[string]string `json:"commonAnnotiations"` + ExternalURL string `json:"externalURL"` + Alerts []alert `json:"alerts"` +} + +type alert struct { + Labels map[string]string `json:"labels"` + Annotiations map[string]string `json:"annotiations"` + StartsAt string `json:"startsAt"` + EndsAt string `json:"endsAt"` + Status string `json:"status"` +} + +// CreateAlert godoc +// @Summary Create an Alert +// @Description Creates an alert that is send to the channel as a notification. This endpoint is compatible with alertmanager webhooks. +// @ID post-alert +// @Tags Alertmanager +// @Accept json +// @Produce json +// @Param token query string true "Channels token, can also be provieded in the header" +// @Param data body hookMessage true "alertmanager webhook call" +// @Success 200 {object} []model.Notification +// @Failure 500,404,403 "" +// @Router /alert [post] +func (h *AlertmanagerHandler) CreateAlert(ctx *gin.Context) { + application := authentication.GetApplication(ctx) + log.Printf("Sending alert notification for application %s.", application.Name) + + var hook hookMessage + if err := ctx.Bind(&hook); err != nil { + return + } + + notifications := make([]model.Notification, len(hook.Alerts)) + for i, alert := range hook.Alerts { + notification := alert.ToNotification(h.Settings.TitleAnnotation, h.Settings.MessageAnnotation) + notification.Sanitize(application) + messageID, err := h.DP.SendNotification(application, ¬ification) + if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + return + } + + notification.ID = messageID + notification.UrlEncodedID = url.QueryEscape(messageID) + notifications[i] = notification + } + ctx.JSON(http.StatusOK, ¬ifications) +} + +func (alert *alert) ToNotification(titleAnnotation, messageAnnotation string) model.Notification { + title := strings.Builder{} + message := strings.Builder{} + + switch alert.Status { + case "firing": + title.WriteString("[FIR] ") + case "resolved": + title.WriteString("[RES] ") + } + message.WriteString("STATUS: ") + message.WriteString(alert.Status) + message.WriteString("\n\n") + + if titleString, ok := alert.Annotiations[titleAnnotation]; ok { + title.WriteString(titleString) + } else { + title.WriteString("Unknown Title") + } + title.WriteString(" - ") + title.WriteString(alert.StartsAt) + + if messageString, ok := alert.Annotiations[messageAnnotation]; ok { + message.WriteString(messageString) + } else { + message.WriteString("Unknown Message") + } + + message.WriteString("\n") + + for labelName, labelValue := range alert.Labels { + message.WriteString("\n") + message.WriteString(labelName) + message.WriteString(": ") + message.WriteString(labelValue) + } + + return model.Notification{ + Message: message.String(), + Title: title.String(), + } +} + +func successOrAbort(ctx *gin.Context, code int, err error) bool { + if err != nil { + // If we know the error force error code + switch err { + case pberrors.ErrorMessageNotFound: + ctx.AbortWithError(http.StatusNotFound, err) + default: + ctx.AbortWithError(code, err) + } + } + + return err == nil +} diff --git a/internal/configuration/configuration.go b/internal/configuration/configuration.go index 5da3653..83d0482 100644 --- a/internal/configuration/configuration.go +++ b/internal/configuration/configuration.go @@ -33,6 +33,12 @@ type Matrix struct { Password string `required:"true"` } +// Alertmanager holds information on how to parse alertmanager calls +type Alertmanager struct { + AnnotationTitle string `default:"title"` + AnnotationMessage string `default:"message"` +} + // Configuration holds values that can be configured by the user. type Configuration struct { Debug bool `default:"false"` @@ -53,8 +59,9 @@ type Configuration struct { Security struct { CheckHIBP bool `default:"false"` } - Crypto CryptoConfig - Formatting Formatting + Crypto CryptoConfig + Formatting Formatting + Alertmanager Alertmanager } func configFiles() []string { diff --git a/internal/router/router.go b/internal/router/router.go index 68f61ca..802f9b4 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -4,8 +4,10 @@ import ( "log" "github.com/pushbits/server/internal/api" + "github.com/pushbits/server/internal/api/alertmanager" "github.com/pushbits/server/internal/authentication" "github.com/pushbits/server/internal/authentication/credentials" + "github.com/pushbits/server/internal/configuration" "github.com/pushbits/server/internal/database" "github.com/pushbits/server/internal/dispatcher" @@ -14,7 +16,7 @@ import ( ) // Create a Gin engine and setup all routes. -func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *dispatcher.Dispatcher) *gin.Engine { +func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *dispatcher.Dispatcher, alertmanagerConfig *configuration.Alertmanager) *gin.Engine { log.Println("Setting up HTTP routes.") if !debug { @@ -27,6 +29,10 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp healthHandler := api.HealthHandler{DB: db} notificationHandler := api.NotificationHandler{DB: db, DP: dp} userHandler := api.UserHandler{AH: &applicationHandler, CM: cm, DB: db, DP: dp} + alertmanagerHandler := alertmanager.AlertmanagerHandler{DP: dp, Settings: alertmanager.AlertmanagerHandlerSettings{ + TitleAnnotation: alertmanagerConfig.AnnotationTitle, + MessageAnnotation: alertmanagerConfig.AnnotationMessage, + }} r := gin.Default() @@ -59,5 +65,7 @@ func Create(debug bool, cm *credentials.Manager, db *database.Database, dp *disp userGroup.PUT("/:id", api.RequireIDInURI(), userHandler.UpdateUser) } + r.POST("/alert", auth.RequireApplicationToken(), alertmanagerHandler.CreateAlert) + return r } From b7682e9f1efef75ddfb94fcc8fb26cf070174b18 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Mon, 18 Apr 2022 12:53:24 +0200 Subject: [PATCH 2/9] update go mod --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 54a8d97..c4dcc1b 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/leodido/go-urn v1.2.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect + github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 github.com/ugorji/go v1.2.4 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect From 64583e37108b66c84b32a363817ccf569bc7c2e7 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Thu, 21 Apr 2022 18:52:57 +0200 Subject: [PATCH 3/9] Handle lables and annotations --- config.example.yml | 4 ++-- internal/api/alertmanager/handler.go | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/config.example.yml b/config.example.yml index 3c5ccc2..526457e 100644 --- a/config.example.yml +++ b/config.example.yml @@ -63,7 +63,7 @@ formatting: # This settings are only relevant if you want to use PushBits with alertmanager alertmanager: - # The name of the entry in the alerts annotations that should be used for the title + # The name of the entry in the alerts annotations or lables that should be used for the title annotationtitle: title - # The name of the entry in the alerts annotations that should be used for the message + # The name of the entry in the alerts annotations or labels that should be used for the message annotationmessage: message diff --git a/internal/api/alertmanager/handler.go b/internal/api/alertmanager/handler.go index 07436b8..cb634a9 100644 --- a/internal/api/alertmanager/handler.go +++ b/internal/api/alertmanager/handler.go @@ -95,14 +95,16 @@ func (alert *alert) ToNotification(titleAnnotation, messageAnnotation string) mo if titleString, ok := alert.Annotiations[titleAnnotation]; ok { title.WriteString(titleString) + } else if titleString, ok := alert.Labels[titleAnnotation]; ok { + title.WriteString(titleString) } else { title.WriteString("Unknown Title") } - title.WriteString(" - ") - title.WriteString(alert.StartsAt) if messageString, ok := alert.Annotiations[messageAnnotation]; ok { message.WriteString(messageString) + } else if messageString, ok := alert.Labels[messageAnnotation]; ok { + message.WriteString(messageString) } else { message.WriteString("Unknown Message") } From 9a5079b393da9f731d102bc4f51edea0f32e4a20 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Thu, 21 Apr 2022 19:01:30 +0200 Subject: [PATCH 4/9] clean merge conflict stuff --- go.mod | 3 --- 1 file changed, 3 deletions(-) diff --git a/go.mod b/go.mod index e4a36e1..efe3fec 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,10 @@ require ( github.com/gin-gonic/gin v1.7.7 github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd github.com/jinzhu/configor v1.2.1 -<<<<<<< HEAD github.com/json-iterator/go v1.1.10 // indirect github.com/leodido/go-urn v1.2.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.1 // indirect -======= ->>>>>>> origin/main github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v2 v2.4.0 From aa33ef51f3d2384dcf88efb3fb28ff598b29afca Mon Sep 17 00:00:00 2001 From: cubicroot Date: Thu, 21 Apr 2022 19:06:11 +0200 Subject: [PATCH 5/9] Remove unwanted packages --- go.mod | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go.mod b/go.mod index efe3fec..3e735bc 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,6 @@ require ( github.com/gin-gonic/gin v1.7.7 github.com/gomarkdown/markdown v0.0.0-20210408062403-ad838ccf8cdd github.com/jinzhu/configor v1.2.1 - github.com/json-iterator/go v1.1.10 // indirect - github.com/leodido/go-urn v1.2.1 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.1 // indirect github.com/sirupsen/logrus v1.8.1 github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v2 v2.4.0 From 6a0c7dc7c933b9c007d93baa824a76533ab24d22 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Sat, 23 Apr 2022 20:14:14 +0200 Subject: [PATCH 6/9] rearange code --- internal/api/notification.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/api/notification.go b/internal/api/notification.go index 3095f56..83f8e59 100644 --- a/internal/api/notification.go +++ b/internal/api/notification.go @@ -54,7 +54,7 @@ func (h *NotificationHandler) CreateNotification(ctx *gin.Context) { notification.Sanitize(application) messageID, err := h.DP.SendNotification(application, ¬ification) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return } @@ -81,7 +81,7 @@ func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) { log.L.Printf("Deleting notification for application %s.", application.Name) id, err := getMessageID(ctx) - if success := successOrAbort(ctx, http.StatusUnprocessableEntity, err); !success { + if success := SuccessOrAbort(ctx, http.StatusUnprocessableEntity, err); !success { return } @@ -90,7 +90,7 @@ func (h *NotificationHandler) DeleteNotification(ctx *gin.Context) { Date: time.Now(), } - if success := successOrAbort(ctx, http.StatusInternalServerError, h.DP.DeleteNotification(application, &n)); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, h.DP.DeleteNotification(application, &n)); !success { return } From 101db63649c6f36fcb0a19f928f6a47e04653c2d Mon Sep 17 00:00:00 2001 From: cubicroot Date: Sat, 23 Apr 2022 20:14:23 +0200 Subject: [PATCH 7/9] rearange code --- internal/api/alertmanager/handler.go | 86 +--------------------------- internal/api/application.go | 18 +++--- internal/api/context.go | 4 +- internal/api/user.go | 16 +++--- internal/api/util.go | 2 +- internal/api/util_test.go | 2 +- internal/model/alertmanager.go | 67 ++++++++++++++++++++++ 7 files changed, 91 insertions(+), 104 deletions(-) create mode 100644 internal/model/alertmanager.go diff --git a/internal/api/alertmanager/handler.go b/internal/api/alertmanager/handler.go index cb634a9..69618cd 100644 --- a/internal/api/alertmanager/handler.go +++ b/internal/api/alertmanager/handler.go @@ -4,13 +4,11 @@ import ( "log" "net/http" "net/url" - "strings" "github.com/gin-gonic/gin" "github.com/pushbits/server/internal/api" "github.com/pushbits/server/internal/authentication" "github.com/pushbits/server/internal/model" - "github.com/pushbits/server/internal/pberrors" ) type AlertmanagerHandler struct { @@ -23,25 +21,6 @@ type AlertmanagerHandlerSettings struct { MessageAnnotation string } -type hookMessage struct { - Version string `json:"version"` - GroupKey string `json:"groupKey"` - Receiver string `json:"receiver"` - GroupLabels map[string]string `json:"groupLabels"` - CommonLabels map[string]string `json:"commonLabels"` - CommonAnnotations map[string]string `json:"commonAnnotiations"` - ExternalURL string `json:"externalURL"` - Alerts []alert `json:"alerts"` -} - -type alert struct { - Labels map[string]string `json:"labels"` - Annotiations map[string]string `json:"annotiations"` - StartsAt string `json:"startsAt"` - EndsAt string `json:"endsAt"` - Status string `json:"status"` -} - // CreateAlert godoc // @Summary Create an Alert // @Description Creates an alert that is send to the channel as a notification. This endpoint is compatible with alertmanager webhooks. @@ -50,7 +29,7 @@ type alert struct { // @Accept json // @Produce json // @Param token query string true "Channels token, can also be provieded in the header" -// @Param data body hookMessage true "alertmanager webhook call" +// @Param data body model.AlertmanagerWebhook true "alertmanager webhook call" // @Success 200 {object} []model.Notification // @Failure 500,404,403 "" // @Router /alert [post] @@ -58,7 +37,7 @@ func (h *AlertmanagerHandler) CreateAlert(ctx *gin.Context) { application := authentication.GetApplication(ctx) log.Printf("Sending alert notification for application %s.", application.Name) - var hook hookMessage + var hook model.AlertmanagerWebhook if err := ctx.Bind(&hook); err != nil { return } @@ -68,7 +47,7 @@ func (h *AlertmanagerHandler) CreateAlert(ctx *gin.Context) { notification := alert.ToNotification(h.Settings.TitleAnnotation, h.Settings.MessageAnnotation) notification.Sanitize(application) messageID, err := h.DP.SendNotification(application, ¬ification) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := api.SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return } @@ -78,62 +57,3 @@ func (h *AlertmanagerHandler) CreateAlert(ctx *gin.Context) { } ctx.JSON(http.StatusOK, ¬ifications) } - -func (alert *alert) ToNotification(titleAnnotation, messageAnnotation string) model.Notification { - title := strings.Builder{} - message := strings.Builder{} - - switch alert.Status { - case "firing": - title.WriteString("[FIR] ") - case "resolved": - title.WriteString("[RES] ") - } - message.WriteString("STATUS: ") - message.WriteString(alert.Status) - message.WriteString("\n\n") - - if titleString, ok := alert.Annotiations[titleAnnotation]; ok { - title.WriteString(titleString) - } else if titleString, ok := alert.Labels[titleAnnotation]; ok { - title.WriteString(titleString) - } else { - title.WriteString("Unknown Title") - } - - if messageString, ok := alert.Annotiations[messageAnnotation]; ok { - message.WriteString(messageString) - } else if messageString, ok := alert.Labels[messageAnnotation]; ok { - message.WriteString(messageString) - } else { - message.WriteString("Unknown Message") - } - - message.WriteString("\n") - - for labelName, labelValue := range alert.Labels { - message.WriteString("\n") - message.WriteString(labelName) - message.WriteString(": ") - message.WriteString(labelValue) - } - - return model.Notification{ - Message: message.String(), - Title: title.String(), - } -} - -func successOrAbort(ctx *gin.Context, code int, err error) bool { - if err != nil { - // If we know the error force error code - switch err { - case pberrors.ErrorMessageNotFound: - ctx.AbortWithError(http.StatusNotFound, err) - default: - ctx.AbortWithError(code, err) - } - } - - return err == nil -} diff --git a/internal/api/application.go b/internal/api/application.go index 6e2f053..f10db59 100644 --- a/internal/api/application.go +++ b/internal/api/application.go @@ -30,14 +30,14 @@ func (h *ApplicationHandler) registerApplication(ctx *gin.Context, a *model.Appl log.L.Printf("Registering application %s.", a.Name) channelID, err := h.DP.RegisterApplication(a.ID, a.Name, a.Token, u.MatrixID) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } a.MatrixID = channelID err = h.DB.UpdateApplication(a) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } @@ -53,13 +53,13 @@ func (h *ApplicationHandler) createApplication(ctx *gin.Context, u *model.User, application.UserID = u.ID err := h.DB.CreateApplication(&application) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return nil, err } if err := h.registerApplication(ctx, &application, u); err != nil { err := h.DB.DeleteApplication(&application) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { log.L.Printf("Cannot delete application with ID %d.", application.ID) } @@ -73,12 +73,12 @@ func (h *ApplicationHandler) deleteApplication(ctx *gin.Context, a *model.Applic log.L.Printf("Deleting application %s (ID %d).", a.Name, a.ID) err := h.DP.DeregisterApplication(a, u) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } err = h.DB.DeleteApplication(a) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } @@ -100,12 +100,12 @@ func (h *ApplicationHandler) updateApplication(ctx *gin.Context, a *model.Applic } err := h.DB.UpdateApplication(a) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } err = h.DP.UpdateApplication(a) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } @@ -164,7 +164,7 @@ func (h *ApplicationHandler) GetApplications(ctx *gin.Context) { } applications, err := h.DB.GetApplications(user) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return } diff --git a/internal/api/context.go b/internal/api/context.go index 90a5ec8..43a6271 100644 --- a/internal/api/context.go +++ b/internal/api/context.go @@ -38,7 +38,7 @@ func getApplication(ctx *gin.Context, db Database) (*model.Application, error) { } application, err := db.GetApplicationByID(id) - if success := successOrAbort(ctx, http.StatusNotFound, err); !success { + if success := SuccessOrAbort(ctx, http.StatusNotFound, err); !success { return nil, err } @@ -52,7 +52,7 @@ func getUser(ctx *gin.Context, db Database) (*model.User, error) { } user, err := db.GetUserByID(id) - if success := successOrAbort(ctx, http.StatusNotFound, err); !success { + if success := SuccessOrAbort(ctx, http.StatusNotFound, err); !success { return nil, err } diff --git a/internal/api/user.go b/internal/api/user.go index f6565cc..009a4f4 100644 --- a/internal/api/user.go +++ b/internal/api/user.go @@ -39,7 +39,7 @@ func (h *UserHandler) requireMultipleAdmins(ctx *gin.Context) error { func (h *UserHandler) deleteApplications(ctx *gin.Context, u *model.User) error { applications, err := h.DB.GetApplications(u) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } @@ -56,7 +56,7 @@ func (h *UserHandler) deleteApplications(ctx *gin.Context, u *model.User) error func (h *UserHandler) updateChannels(ctx *gin.Context, u *model.User, matrixID string) error { applications, err := h.DB.GetApplications(u) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } @@ -64,7 +64,7 @@ func (h *UserHandler) updateChannels(ctx *gin.Context, u *model.User, matrixID s application := application // See https://stackoverflow.com/a/68247837 err := h.DP.DeregisterApplication(&application, u) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } } @@ -97,7 +97,7 @@ func (h *UserHandler) updateUser(ctx *gin.Context, u *model.User, updateUser mod } if updateUser.Password != nil { hash, err := h.CM.CreatePasswordHash(*updateUser.Password) - if success := successOrAbort(ctx, http.StatusBadRequest, err); !success { + if success := SuccessOrAbort(ctx, http.StatusBadRequest, err); !success { return err } @@ -111,7 +111,7 @@ func (h *UserHandler) updateUser(ctx *gin.Context, u *model.User, updateUser mod } err := h.DB.UpdateUser(u) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return err } @@ -150,7 +150,7 @@ func (h *UserHandler) CreateUser(ctx *gin.Context) { user, err := h.DB.CreateUser(createUser) - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return } @@ -171,7 +171,7 @@ func (h *UserHandler) CreateUser(ctx *gin.Context) { // @Router /user [get] func (h *UserHandler) GetUsers(ctx *gin.Context) { users, err := h.DB.GetUsers() - if success := successOrAbort(ctx, http.StatusInternalServerError, err); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, err); !success { return } @@ -238,7 +238,7 @@ func (h *UserHandler) DeleteUser(ctx *gin.Context) { return } - if success := successOrAbort(ctx, http.StatusInternalServerError, h.DB.DeleteUser(user)); !success { + if success := SuccessOrAbort(ctx, http.StatusInternalServerError, h.DB.DeleteUser(user)); !success { return } diff --git a/internal/api/util.go b/internal/api/util.go index ff9e481..9c5ed86 100644 --- a/internal/api/util.go +++ b/internal/api/util.go @@ -10,7 +10,7 @@ import ( "github.com/gin-gonic/gin" ) -func successOrAbort(ctx *gin.Context, code int, err error) bool { +func SuccessOrAbort(ctx *gin.Context, code int, err error) bool { if err != nil { // If we know the error force error code switch err { diff --git a/internal/api/util_test.go b/internal/api/util_test.go index b89eb55..2870575 100644 --- a/internal/api/util_test.go +++ b/internal/api/util_test.go @@ -26,7 +26,7 @@ func TestApi_SuccessOrAbort(t *testing.T) { w, c, err := testCase.GetRequest() require.NoErrorf(err, "(Test case %s) Could not make request", testCase.Name) - aborted := successOrAbort(c, testCase.ShouldStatus, forcedErr) + aborted := SuccessOrAbort(c, testCase.ShouldStatus, forcedErr) if forcedErr != nil { assert.Equalf(testCase.ShouldStatus, w.Code, "(Test case %s) Expected status code %d but have %d", testCase.Name, testCase.ShouldStatus, w.Code) diff --git a/internal/model/alertmanager.go b/internal/model/alertmanager.go new file mode 100644 index 0000000..2d6a408 --- /dev/null +++ b/internal/model/alertmanager.go @@ -0,0 +1,67 @@ +package model + +import "strings" + +type AlertmanagerWebhook struct { + Version string `json:"version"` + GroupKey string `json:"groupKey"` + Receiver string `json:"receiver"` + GroupLabels map[string]string `json:"groupLabels"` + CommonLabels map[string]string `json:"commonLabels"` + CommonAnnotations map[string]string `json:"commonAnnotiations"` + ExternalURL string `json:"externalURL"` + Alerts []AlertmanagerAlert `json:"alerts"` +} + +type AlertmanagerAlert struct { + Labels map[string]string `json:"labels"` + Annotiations map[string]string `json:"annotiations"` + StartsAt string `json:"startsAt"` + EndsAt string `json:"endsAt"` + Status string `json:"status"` +} + +func (alert *AlertmanagerAlert) ToNotification(titleAnnotation, messageAnnotation string) Notification { + title := strings.Builder{} + message := strings.Builder{} + + switch alert.Status { + case "firing": + title.WriteString("[FIR] ") + case "resolved": + title.WriteString("[RES] ") + } + message.WriteString("STATUS: ") + message.WriteString(alert.Status) + message.WriteString("\n\n") + + if titleString, ok := alert.Annotiations[titleAnnotation]; ok { + title.WriteString(titleString) + } else if titleString, ok := alert.Labels[titleAnnotation]; ok { + title.WriteString(titleString) + } else { + title.WriteString("Unknown Title") + } + + if messageString, ok := alert.Annotiations[messageAnnotation]; ok { + message.WriteString(messageString) + } else if messageString, ok := alert.Labels[messageAnnotation]; ok { + message.WriteString(messageString) + } else { + message.WriteString("Unknown Message") + } + + message.WriteString("\n") + + for labelName, labelValue := range alert.Labels { + message.WriteString("\n") + message.WriteString(labelName) + message.WriteString(": ") + message.WriteString(labelValue) + } + + return Notification{ + Message: message.String(), + Title: title.String(), + } +} From 9fecabf1f726b28576c4c5b53900ca0dee83a96c Mon Sep 17 00:00:00 2001 From: cubicroot Date: Sun, 24 Apr 2022 16:58:40 +0200 Subject: [PATCH 8/9] fix typos --- internal/model/alertmanager.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/internal/model/alertmanager.go b/internal/model/alertmanager.go index 2d6a408..9013cea 100644 --- a/internal/model/alertmanager.go +++ b/internal/model/alertmanager.go @@ -8,17 +8,17 @@ type AlertmanagerWebhook struct { Receiver string `json:"receiver"` GroupLabels map[string]string `json:"groupLabels"` CommonLabels map[string]string `json:"commonLabels"` - CommonAnnotations map[string]string `json:"commonAnnotiations"` + CommonAnnotations map[string]string `json:"commonAnnotations"` ExternalURL string `json:"externalURL"` Alerts []AlertmanagerAlert `json:"alerts"` } type AlertmanagerAlert struct { - Labels map[string]string `json:"labels"` - Annotiations map[string]string `json:"annotiations"` - StartsAt string `json:"startsAt"` - EndsAt string `json:"endsAt"` - Status string `json:"status"` + Labels map[string]string `json:"labels"` + Annotations map[string]string `json:"annotations"` + StartsAt string `json:"startsAt"` + EndsAt string `json:"endsAt"` + Status string `json:"status"` } func (alert *AlertmanagerAlert) ToNotification(titleAnnotation, messageAnnotation string) Notification { @@ -35,7 +35,7 @@ func (alert *AlertmanagerAlert) ToNotification(titleAnnotation, messageAnnotatio message.WriteString(alert.Status) message.WriteString("\n\n") - if titleString, ok := alert.Annotiations[titleAnnotation]; ok { + if titleString, ok := alert.Annotations[titleAnnotation]; ok { title.WriteString(titleString) } else if titleString, ok := alert.Labels[titleAnnotation]; ok { title.WriteString(titleString) @@ -43,7 +43,7 @@ func (alert *AlertmanagerAlert) ToNotification(titleAnnotation, messageAnnotatio title.WriteString("Unknown Title") } - if messageString, ok := alert.Annotiations[messageAnnotation]; ok { + if messageString, ok := alert.Annotations[messageAnnotation]; ok { message.WriteString(messageString) } else if messageString, ok := alert.Labels[messageAnnotation]; ok { message.WriteString(messageString) From 5001bd5b5f2710284aeb10419d7e38f8b53c6101 Mon Sep 17 00:00:00 2001 From: cubicroot Date: Sun, 24 Apr 2022 17:30:33 +0200 Subject: [PATCH 9/9] use internal log package --- internal/api/alertmanager/handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/api/alertmanager/handler.go b/internal/api/alertmanager/handler.go index 69618cd..b5f73b1 100644 --- a/internal/api/alertmanager/handler.go +++ b/internal/api/alertmanager/handler.go @@ -1,13 +1,13 @@ package alertmanager import ( - "log" "net/http" "net/url" "github.com/gin-gonic/gin" "github.com/pushbits/server/internal/api" "github.com/pushbits/server/internal/authentication" + "github.com/pushbits/server/internal/log" "github.com/pushbits/server/internal/model" ) @@ -35,7 +35,7 @@ type AlertmanagerHandlerSettings struct { // @Router /alert [post] func (h *AlertmanagerHandler) CreateAlert(ctx *gin.Context) { application := authentication.GetApplication(ctx) - log.Printf("Sending alert notification for application %s.", application.Name) + log.L.Printf("Sending alert notification for application %s.", application.Name) var hook model.AlertmanagerWebhook if err := ctx.Bind(&hook); err != nil {