package notifiers import ( "bytes" "fmt" "io" "mime/multipart" "os" "strconv" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" ) const PUSHOVER_ENDPOINT = "https://api.pushover.net/1/messages.json" func init() { alerting.RegisterNotifier(&alerting.NotifierPlugin{ Type: "pushover", Name: "Pushover", Description: "Sends HTTP POST request to the Pushover API", Factory: NewPushoverNotifier, OptionsTemplate: `

Pushover settings

API Token
User key(s)
Device(s) (optional)
Priority
Retry Expire
Sound
`, }) } func NewPushoverNotifier(model *m.AlertNotification) (alerting.Notifier, error) { userKey := model.Settings.Get("userKey").MustString() apiToken := model.Settings.Get("apiToken").MustString() device := model.Settings.Get("device").MustString() priority, _ := strconv.Atoi(model.Settings.Get("priority").MustString()) retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString()) expire, _ := strconv.Atoi(model.Settings.Get("expire").MustString()) sound := model.Settings.Get("sound").MustString() uploadImage := model.Settings.Get("uploadImage").MustBool(true) if userKey == "" { return nil, alerting.ValidationError{Reason: "User key not given"} } if apiToken == "" { return nil, alerting.ValidationError{Reason: "API token not given"} } return &PushoverNotifier{ NotifierBase: NewNotifierBase(model), UserKey: userKey, ApiToken: apiToken, Priority: priority, Retry: retry, Expire: expire, Device: device, Sound: sound, Upload: uploadImage, log: log.New("alerting.notifier.pushover"), }, nil } type PushoverNotifier struct { NotifierBase UserKey string ApiToken string Priority int Retry int Expire int Device string Sound string Upload bool log log.Logger } func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error { ruleUrl, err := evalContext.GetRuleUrl() if err != nil { this.log.Error("Failed get rule link", "error", err) return err } message := evalContext.Rule.Message for idx, evt := range evalContext.EvalMatches { message += fmt.Sprintf("\n%s: %v", evt.Metric, evt.Value) if idx > 4 { break } } if evalContext.Error != nil { message += fmt.Sprintf("\nError message: %s", evalContext.Error.Error()) } if message == "" { message = "Notification message missing (Set a notification message to replace this text.)" } headers, uploadBody, err := this.genPushoverBody(evalContext, message, ruleUrl) if err != nil { this.log.Error("Failed to generate body for pushover", "error", err) return err } cmd := &m.SendWebhookSync{ Url: PUSHOVER_ENDPOINT, HttpMethod: "POST", HttpHeader: headers, Body: uploadBody.String(), } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { this.log.Error("Failed to send pushover notification", "error", err, "webhook", this.Name) return err } return nil } func (this *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext, message string, ruleUrl string) (map[string]string, bytes.Buffer, error) { var b bytes.Buffer var err error w := multipart.NewWriter(&b) // Add image only if requested and available if this.Upload && evalContext.ImageOnDiskPath != "" { f, err := os.Open(evalContext.ImageOnDiskPath) if err != nil { return nil, b, err } defer f.Close() fw, err := w.CreateFormFile("attachment", evalContext.ImageOnDiskPath) if err != nil { return nil, b, err } _, err = io.Copy(fw, f) if err != nil { return nil, b, err } } // Add the user token err = w.WriteField("user", this.UserKey) if err != nil { return nil, b, err } // Add the api token err = w.WriteField("token", this.ApiToken) if err != nil { return nil, b, err } // Add priority err = w.WriteField("priority", strconv.Itoa(this.Priority)) if err != nil { return nil, b, err } if this.Priority == 2 { err = w.WriteField("retry", strconv.Itoa(this.Retry)) if err != nil { return nil, b, err } err = w.WriteField("expire", strconv.Itoa(this.Expire)) if err != nil { return nil, b, err } } // Add device if this.Device != "" { err = w.WriteField("device", this.Device) if err != nil { return nil, b, err } } // Add sound if this.Sound != "default" { err = w.WriteField("sound", this.Sound) if err != nil { return nil, b, err } } // Add title err = w.WriteField("title", evalContext.GetNotificationTitle()) if err != nil { return nil, b, err } // Add URL err = w.WriteField("url", ruleUrl) if err != nil { return nil, b, err } // Add URL title err = w.WriteField("url_title", "Show dashboard with alert") if err != nil { return nil, b, err } // Add message err = w.WriteField("message", message) if err != nil { return nil, b, err } // Mark as html message err = w.WriteField("html", "1") if err != nil { return nil, b, err } w.Close() headers := map[string]string{ "Content-Type": w.FormDataContentType(), } return headers, b, nil }