From f069aae5763e3f0779c3af9148c78f8f79174d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Sun, 25 Dec 2016 20:57:08 +0000 Subject: [PATCH] Add Pushover alert notifications Pushover is a service for getting real-time notifications on your mobile devices and desktop computers: https://pushover.net --- pkg/metrics/metrics.go | 2 + pkg/services/alerting/notifiers/pushover.go | 177 ++++++++++++++++++ .../alerting/notifiers/pushover_test.go | 58 ++++++ .../app/features/alerting/alert_tab_ctrl.ts | 1 + 4 files changed, 238 insertions(+) create mode 100644 pkg/services/alerting/notifiers/pushover.go create mode 100644 pkg/services/alerting/notifiers/pushover_test.go diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go index 1020f28f874..e60a967f3fa 100644 --- a/pkg/metrics/metrics.go +++ b/pkg/metrics/metrics.go @@ -50,6 +50,7 @@ var ( M_Alerting_Notification_Sent_OpsGenie Counter M_Alerting_Notification_Sent_Telegram Counter M_Alerting_Notification_Sent_Sensu Counter + M_Alerting_Notification_Sent_Pushover Counter M_Aws_CloudWatch_GetMetricStatistics Counter M_Aws_CloudWatch_ListMetrics Counter @@ -120,6 +121,7 @@ func initMetricVars(settings *MetricSettings) { M_Alerting_Notification_Sent_Telegram = RegCounter("alerting.notifications_sent", "type", "telegram") M_Alerting_Notification_Sent_Sensu = RegCounter("alerting.notifications_sent", "type", "sensu") M_Alerting_Notification_Sent_LINE = RegCounter("alerting.notifications_sent", "type", "LINE") + M_Alerting_Notification_Sent_Pushover = RegCounter("alerting.notifications_sent", "type", "pushover") M_Aws_CloudWatch_GetMetricStatistics = RegCounter("aws.cloudwatch.get_metric_statistics") M_Aws_CloudWatch_ListMetrics = RegCounter("aws.cloudwatch.list_metrics") diff --git a/pkg/services/alerting/notifiers/pushover.go b/pkg/services/alerting/notifiers/pushover.go new file mode 100644 index 00000000000..c68ad421c62 --- /dev/null +++ b/pkg/services/alerting/notifiers/pushover.go @@ -0,0 +1,177 @@ +package notifiers + +import ( + "fmt" + "net/url" + "strconv" + + "github.com/grafana/grafana/pkg/bus" + "github.com/grafana/grafana/pkg/log" + "github.com/grafana/grafana/pkg/metrics" + 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() + + 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.Id, model.IsDefault, model.Name, model.Type, model.Settings), + UserKey: userKey, + ApiToken: apiToken, + Priority: priority, + Retry: retry, + Expire: expire, + Device: device, + Sound: sound, + 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 + log log.Logger +} + +func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error { + metrics.M_Alerting_Notification_Sent_Pushover.Inc(1) + 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()) + } + q := url.Values{} + q.Add("user", this.UserKey) + q.Add("token", this.ApiToken) + q.Add("priority", strconv.Itoa(this.Priority)) + if this.Priority == 2 { + q.Add("retry", strconv.Itoa(this.Retry)) + q.Add("expire", strconv.Itoa(this.Expire)) + } + if this.Device != "" { + q.Add("device", this.Device) + } + if this.Sound != "default" { + q.Add("sound", this.Sound) + } + q.Add("title", evalContext.GetNotificationTitle()) + q.Add("url", ruleUrl) + q.Add("url_title", "Show dashboard with alert") + q.Add("message", message) + q.Add("html", "1") + + cmd := &m.SendWebhookSync{ + Url: PUSHOVER_ENDPOINT, + HttpMethod: "POST", + HttpHeader: map[string]string{"Content-Type": "application/x-www-form-urlencoded"}, + Body: q.Encode(), + } + + 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 +} diff --git a/pkg/services/alerting/notifiers/pushover_test.go b/pkg/services/alerting/notifiers/pushover_test.go new file mode 100644 index 00000000000..a3bac73e065 --- /dev/null +++ b/pkg/services/alerting/notifiers/pushover_test.go @@ -0,0 +1,58 @@ +package notifiers + +import ( + "testing" + + "github.com/grafana/grafana/pkg/components/simplejson" + m "github.com/grafana/grafana/pkg/models" + . "github.com/smartystreets/goconvey/convey" +) + +func TestPushoverNotifier(t *testing.T) { + Convey("Pushover notifier tests", t, func() { + + Convey("Parsing alert notification from settings", func() { + Convey("empty settings should return error", func() { + json := `{ }` + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &m.AlertNotification{ + Name: "Pushover", + Type: "pushover", + Settings: settingsJSON, + } + + _, err := NewPushoverNotifier(model) + So(err, ShouldNotBeNil) + }) + + Convey("from settings", func() { + json := ` + { + "apiToken": "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve", + "userKey": "tzNZYf36y0ohWwXo4XoUrB61rz1A4o", + "priority": "1", + "sound": "pushover" + }` + + settingsJSON, _ := simplejson.NewJson([]byte(json)) + model := &m.AlertNotification{ + Name: "Pushover", + Type: "pushover", + Settings: settingsJSON, + } + + not, err := NewPushoverNotifier(model) + pushoverNotifier := not.(*PushoverNotifier) + + So(err, ShouldBeNil) + So(pushoverNotifier.Name, ShouldEqual, "Pushover") + So(pushoverNotifier.Type, ShouldEqual, "pushover") + So(pushoverNotifier.ApiToken, ShouldEqual, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve") + So(pushoverNotifier.UserKey, ShouldEqual, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o") + So(pushoverNotifier.Priority, ShouldEqual, 1) + So(pushoverNotifier.Sound, ShouldEqual, "pushover") + }) + }) + }) +} diff --git a/public/app/features/alerting/alert_tab_ctrl.ts b/public/app/features/alerting/alert_tab_ctrl.ts index 6be36380144..b21e5363ad8 100644 --- a/public/app/features/alerting/alert_tab_ctrl.ts +++ b/public/app/features/alerting/alert_tab_ctrl.ts @@ -105,6 +105,7 @@ export class AlertTabCtrl { case "pagerduty": return "fa fa-bullhorn"; case "opsgenie": return "fa fa-bell"; case "hipchat": return "fa fa-mail-forward"; + case "pushover": return "fa fa-mobile"; } }