Browse Source

Merge pull request #249 from matrix-org/jcgruenhage/alertmanager

alertmanager support
pull/265/head
Jan Christian Grünhage 6 years ago
committed by GitHub
parent
commit
793f79ec77
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      README.md
  2. 1
      src/github.com/matrix-org/go-neb/goneb.go
  3. 187
      src/github.com/matrix-org/go-neb/services/alertmanager/alertmanager.go

3
README.md

@ -79,6 +79,9 @@ Invite the bot user into a Matrix room and type `!echo hello world`. It will rep
### Travis CI
- Ability to receive incoming build notifications.
- Ability to adjust the message which is sent into the room.
### Alertmanager
- Ability to receive alerts and render them with go templates
# Installing

1
src/github.com/matrix-org/go-neb/goneb.go

@ -19,6 +19,7 @@ import (
"github.com/matrix-org/go-neb/polling"
_ "github.com/matrix-org/go-neb/realms/github"
_ "github.com/matrix-org/go-neb/realms/jira"
_ "github.com/matrix-org/go-neb/services/alertmanager"
_ "github.com/matrix-org/go-neb/services/echo"
_ "github.com/matrix-org/go-neb/services/giphy"
_ "github.com/matrix-org/go-neb/services/github"

187
src/github.com/matrix-org/go-neb/services/alertmanager/alertmanager.go

@ -0,0 +1,187 @@
// Package alertmanager implements a Service capable of processing webhooks from prometheus alertmanager.
package alertmanager
import (
"bytes"
"encoding/json"
"fmt"
log "github.com/Sirupsen/logrus"
"github.com/matrix-org/go-neb/database"
"github.com/matrix-org/go-neb/types"
"github.com/matrix-org/gomatrix"
html "html/template"
"net/http"
text "text/template"
)
// ServiceType of the Alertmanager service.
const ServiceType = "alertmanager"
// Service contains the Config fields for the Alertmanager service.
//
// This service will send notifications into a Matrix room when Alertmanager sends
// webhook events to it. It requires a public domain which Alertmanager can reach.
// Notices will be sent as the service user ID.
//
// For the template strings, take a look at https://golang.org/pkg/text/template/
// and the html variant https://golang.org/pkg/html/template/.
// The data they get is a webhookNotification
//
// You can set msg_type to either m.text or m.notice
//
// Example JSON request:
// {
// rooms: {
// "!ewfug483gsfe:localhost": {
// "text_template": "your plain text template goes here",
// "html_template": "your html template goes here",
// "msg_type": "m.text"
// },
// }
// }
type Service struct {
types.DefaultService
webhookEndpointURL string
// The URL which should be added to alertmanagers config - Populated by Go-NEB after Service registration.
WebhookURL string `json:"webhook_url"`
// A map of matrix rooms to templates
Rooms map[string]struct {
TextTemplate string `json:"text_template"`
HTMLTemplate string `json:"html_template"`
MsgType string `json:"msg_type"`
} `json:"rooms"`
}
// The payload from Alertmanager
type WebhookNotification struct {
Version string `json:"version"`
GroupKey string `json:"groupKey"`
Status string `json:"status"`
Receiver string `json:"receiver"`
GroupLabels map[string]string `json:"groupLabels"`
CommonLabels map[string]string `json:"commonLabels"`
CommonAnnotations map[string]string `json:"commonAnnotations"`
ExternalUrl string `json:"externalURL"`
Alerts []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"`
} `json:"alerts"`
}
// OnReceiveWebhook receives requests from Alertmanager and sends requests to Matrix as a result.
func (s *Service) OnReceiveWebhook(w http.ResponseWriter, req *http.Request, cli *gomatrix.Client) {
decoder := json.NewDecoder(req.Body)
var notif WebhookNotification
if err := decoder.Decode(&notif); err != nil {
log.WithError(err).Error("Alertmanager webhook received an invalid JSON payload")
w.WriteHeader(400)
return
}
for roomID, templates := range s.Rooms {
var msg interface{}
// we don't check whether the templates parse because we already did when storing them in the db
textTemplate, _ := text.New("textTemplate").Parse(templates.TextTemplate)
var bodyBuffer bytes.Buffer
textTemplate.Execute(&bodyBuffer, notif)
if templates.HTMLTemplate != "" {
// we don't check whether the templates parse because we already did when storing them in the db
htmlTemplate, _ := html.New("htmlTemplate").Parse(templates.HTMLTemplate)
var formattedBodyBuffer bytes.Buffer
htmlTemplate.Execute(&formattedBodyBuffer, notif)
msg = gomatrix.HTMLMessage{
Body: bodyBuffer.String(),
MsgType: templates.MsgType,
Format: "org.matrix.custom.html",
FormattedBody: formattedBodyBuffer.String(),
}
} else {
msg = gomatrix.TextMessage{
Body: bodyBuffer.String(),
MsgType: templates.MsgType,
}
}
log.WithFields(log.Fields{
"message": msg,
"room_id": roomID,
}).Print("Sending Alertmanager notification to room")
if _, e := cli.SendMessageEvent(roomID, "m.room.message", msg); e != nil {
log.WithError(e).WithField("room_id", roomID).Print(
"Failed to send Alertmanager notification to room.")
}
}
w.WriteHeader(200)
}
// Register makes sure the Config information supplied is valid.
func (s *Service) Register(oldService types.Service, client *gomatrix.Client) error {
s.WebhookURL = s.webhookEndpointURL
for _, templates := range s.Rooms {
// validate that we have at least a plain text template
if templates.TextTemplate == "" {
return fmt.Errorf("plain text template missing")
} else {
// validate the plain text template is valid
_, err := text.New("textTemplate").Parse(templates.TextTemplate)
if err != nil {
return fmt.Errorf("plain text template is invalid")
}
}
if templates.HTMLTemplate != "" {
// validate that the html template is valid
_, err := html.New("htmlTemplate").Parse(templates.HTMLTemplate)
if err != nil {
return fmt.Errorf("html template is invalid")
}
}
// validate that the msgtype is either m.notice or m.text
if templates.MsgType != "m.notice" && templates.MsgType != "m.text" {
return fmt.Errorf("msg_type is neither 'm.notice' nor 'm.text'")
}
}
s.joinRooms(client)
return nil
}
// PostRegister deletes this service if there are no registered repos.
func (s *Service) PostRegister(oldService types.Service) {
// At least one room still active
if len(s.Rooms) > 0 {
return
}
// Delete this service since no repos are configured
logger := log.WithFields(log.Fields{
"service_type": s.ServiceType(),
"service_id": s.ServiceID(),
})
logger.Info("Removing service as no repositories are registered.")
if err := database.GetServiceDB().DeleteService(s.ServiceID()); err != nil {
logger.WithError(err).Error("Failed to delete service")
}
}
func (s *Service) joinRooms(client *gomatrix.Client) {
for roomID := range s.Rooms {
if _, err := client.JoinRoom(roomID, "", nil); err != nil {
log.WithFields(log.Fields{
log.ErrorKey: err,
"room_id": roomID,
"user_id": client.UserID,
}).Error("Failed to join room")
}
}
}
func init() {
types.RegisterService(func(serviceID, serviceUserID, webhookEndpointURL string) types.Service {
return &Service{
DefaultService: types.NewDefaultService(serviceID, serviceUserID, ServiceType),
webhookEndpointURL: webhookEndpointURL,
}
})
}
Loading…
Cancel
Save