alerting: golint fixes for alert notifiers. (#17167)

This commit is contained in:
Carl Bergquist
2019-05-20 15:23:06 +02:00
committed by GitHub
parent a0f5923b95
commit bfa7c3d963
30 changed files with 356 additions and 292 deletions
+15 -3
View File
@@ -13,10 +13,11 @@ const (
triggMetrString = "Triggered metrics:\n\n" triggMetrString = "Triggered metrics:\n\n"
) )
// NotifierBase is the base implementation of a notifier.
type NotifierBase struct { type NotifierBase struct {
Name string Name string
Type string Type string
Uid string UID string
IsDeault bool IsDeault bool
UploadImage bool UploadImage bool
SendReminder bool SendReminder bool
@@ -26,6 +27,7 @@ type NotifierBase struct {
log log.Logger log log.Logger
} }
// NewNotifierBase returns a new `NotifierBase`.
func NewNotifierBase(model *models.AlertNotification) NotifierBase { func NewNotifierBase(model *models.AlertNotification) NotifierBase {
uploadImage := true uploadImage := true
value, exist := model.Settings.CheckGet("uploadImage") value, exist := model.Settings.CheckGet("uploadImage")
@@ -34,7 +36,7 @@ func NewNotifierBase(model *models.AlertNotification) NotifierBase {
} }
return NotifierBase{ return NotifierBase{
Uid: model.Uid, UID: model.Uid,
Name: model.Name, Name: model.Name,
IsDeault: model.IsDefault, IsDeault: model.IsDefault,
Type: model.Type, Type: model.Type,
@@ -108,30 +110,40 @@ func (n *NotifierBase) ShouldNotify(ctx context.Context, context *alerting.EvalC
return true return true
} }
// GetType returns the notifier type.
func (n *NotifierBase) GetType() string { func (n *NotifierBase) GetType() string {
return n.Type return n.Type
} }
// NeedsImage returns true if an image is expected in the notification.
func (n *NotifierBase) NeedsImage() bool { func (n *NotifierBase) NeedsImage() bool {
return n.UploadImage return n.UploadImage
} }
// GetNotifierUid returns the notifier `uid`.
func (n *NotifierBase) GetNotifierUid() string { func (n *NotifierBase) GetNotifierUid() string {
return n.Uid return n.UID
} }
// GetIsDefault returns true if the notifiers should
// be used for all alerts.
func (n *NotifierBase) GetIsDefault() bool { func (n *NotifierBase) GetIsDefault() bool {
return n.IsDeault return n.IsDeault
} }
// GetSendReminder returns true if reminders should be sent.
func (n *NotifierBase) GetSendReminder() bool { func (n *NotifierBase) GetSendReminder() bool {
return n.SendReminder return n.SendReminder
} }
// GetDisableResolveMessage returns true if ok alert notifications
// should be skipped.
func (n *NotifierBase) GetDisableResolveMessage() bool { func (n *NotifierBase) GetDisableResolveMessage() bool {
return n.DisableResolveMessage return n.DisableResolveMessage
} }
// GetFrequency returns the freqency for how often
// alerts should be evaluated.
func (n *NotifierBase) GetFrequency() time.Duration { func (n *NotifierBase) GetFrequency() time.Duration {
return n.Frequency return n.Frequency
} }
+4 -4
View File
@@ -178,24 +178,24 @@ func TestShouldSendAlertNotification(t *testing.T) {
func TestBaseNotifier(t *testing.T) { func TestBaseNotifier(t *testing.T) {
Convey("default constructor for notifiers", t, func() { Convey("default constructor for notifiers", t, func() {
bJson := simplejson.New() bJSON := simplejson.New()
model := &models.AlertNotification{ model := &models.AlertNotification{
Uid: "1", Uid: "1",
Name: "name", Name: "name",
Type: "email", Type: "email",
Settings: bJson, Settings: bJSON,
} }
Convey("can parse false value", func() { Convey("can parse false value", func() {
bJson.Set("uploadImage", false) bJSON.Set("uploadImage", false)
base := NewNotifierBase(model) base := NewNotifierBase(model)
So(base.UploadImage, ShouldBeFalse) So(base.UploadImage, ShouldBeFalse)
}) })
Convey("can parse true value", func() { Convey("can parse true value", func() {
bJson.Set("uploadImage", true) bJSON.Set("uploadImage", true)
base := NewNotifierBase(model) base := NewNotifierBase(model)
So(base.UploadImage, ShouldBeTrue) So(base.UploadImage, ShouldBeTrue)
+29 -27
View File
@@ -12,8 +12,8 @@ import (
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
) )
const DefaultDingdingMsgType = "link" const defaultDingdingMsgType = "link"
const DingdingOptionsTemplate = ` const dingdingOptionsTemplate = `
<h3 class="page-heading">DingDing settings</h3> <h3 class="page-heading">DingDing settings</h3>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-10">Url</span> <span class="gf-form-label width-10">Url</span>
@@ -21,7 +21,7 @@ const DingdingOptionsTemplate = `
</div> </div>
<div class="gf-form"> <div class="gf-form">
<span class="gf-form-label width-10">MessageType</span> <span class="gf-form-label width-10">MessageType</span>
<select class="gf-form-input max-width-14" ng-model="ctrl.model.settings.msgType" ng-options="s for s in ['link','actionCard']" ng-init="ctrl.model.settings.msgType=ctrl.model.settings.msgType || '` + DefaultDingdingMsgType + `'"></select> <select class="gf-form-input max-width-14" ng-model="ctrl.model.settings.msgType" ng-options="s for s in ['link','actionCard']" ng-init="ctrl.model.settings.msgType=ctrl.model.settings.msgType || '` + defaultDingdingMsgType + `'"></select>
</div> </div>
` `
@@ -30,57 +30,59 @@ func init() {
Type: "dingding", Type: "dingding",
Name: "DingDing", Name: "DingDing",
Description: "Sends HTTP POST request to DingDing", Description: "Sends HTTP POST request to DingDing",
Factory: NewDingDingNotifier, Factory: newDingDingNotifier,
OptionsTemplate: DingdingOptionsTemplate, OptionsTemplate: dingdingOptionsTemplate,
}) })
} }
func NewDingDingNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func newDingDingNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
} }
msgType := model.Settings.Get("msgType").MustString(DefaultDingdingMsgType) msgType := model.Settings.Get("msgType").MustString(defaultDingdingMsgType)
return &DingDingNotifier{ return &DingDingNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
MsgType: msgType, MsgType: msgType,
Url: url, URL: url,
log: log.New("alerting.notifier.dingding"), log: log.New("alerting.notifier.dingding"),
}, nil }, nil
} }
// DingDingNotifier is responsible for sending alert notifications to ding ding.
type DingDingNotifier struct { type DingDingNotifier struct {
NotifierBase NotifierBase
MsgType string MsgType string
Url string URL string
log log.Logger log log.Logger
} }
func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify sends the alert notification to dingding.
this.log.Info("Sending dingding") func (dd *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
dd.log.Info("Sending dingding")
messageUrl, err := evalContext.GetRuleUrl() messageURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed to get messageUrl", "error", err, "dingding", this.Name) dd.log.Error("Failed to get messageUrl", "error", err, "dingding", dd.Name)
messageUrl = "" messageURL = ""
} }
q := url.Values{ q := url.Values{
"pc_slide": {"false"}, "pc_slide": {"false"},
"url": {messageUrl}, "url": {messageURL},
} }
// Use special link to auto open the message url outside of Dingding // Use special link to auto open the message url outside of Dingding
// Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9 // Refer: https://open-doc.dingtalk.com/docs/doc.htm?treeId=385&articleId=104972&docType=1#s9
messageUrl = "dingtalk://dingtalkclient/page/link?" + q.Encode() messageURL = "dingtalk://dingtalkclient/page/link?" + q.Encode()
this.log.Info("messageUrl:" + messageUrl) dd.log.Info("messageUrl:" + messageURL)
message := evalContext.Rule.Message message := evalContext.Rule.Message
picUrl := evalContext.ImagePublicUrl picURL := evalContext.ImagePublicUrl
title := evalContext.GetNotificationTitle() title := evalContext.GetNotificationTitle()
if message == "" { if message == "" {
message = title message = title
@@ -91,10 +93,10 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
} }
var bodyStr string var bodyStr string
if this.MsgType == "actionCard" { if dd.MsgType == "actionCard" {
// Embed the pic into the markdown directly because actionCard doesn't have a picUrl field // Embed the pic into the markdown directly because actionCard doesn't have a picUrl field
if picUrl != "" { if picURL != "" {
message = "![](" + picUrl + ")\\n\\n" + message message = "![](" + picURL + ")\\n\\n" + message
} }
bodyStr = `{ bodyStr = `{
@@ -103,7 +105,7 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
"text": "` + strings.Replace(message, `"`, "'", -1) + `", "text": "` + strings.Replace(message, `"`, "'", -1) + `",
"title": "` + strings.Replace(title, `"`, "'", -1) + `", "title": "` + strings.Replace(title, `"`, "'", -1) + `",
"singleTitle": "More", "singleTitle": "More",
"singleURL": "` + messageUrl + `" "singleURL": "` + messageURL + `"
} }
}` }`
} else { } else {
@@ -112,8 +114,8 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
"link": { "link": {
"text": "` + message + `", "text": "` + message + `",
"title": "` + title + `", "title": "` + title + `",
"picUrl": "` + picUrl + `", "picUrl": "` + picURL + `",
"messageUrl": "` + messageUrl + `" "messageUrl": "` + messageURL + `"
} }
}` }`
} }
@@ -121,7 +123,7 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
bodyJSON, err := simplejson.NewJson([]byte(bodyStr)) bodyJSON, err := simplejson.NewJson([]byte(bodyStr))
if err != nil { if err != nil {
this.log.Error("Failed to create Json data", "error", err, "dingding", this.Name) dd.log.Error("Failed to create Json data", "error", err, "dingding", dd.Name)
} }
body, err := bodyJSON.MarshalJSON() body, err := bodyJSON.MarshalJSON()
@@ -130,12 +132,12 @@ func (this *DingDingNotifier) Notify(evalContext *alerting.EvalContext) error {
} }
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: this.Url, Url: dd.URL,
Body: string(body), Body: string(body),
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send DingDing", "error", err, "dingding", this.Name) dd.log.Error("Failed to send DingDing", "error", err, "dingding", dd.Name)
return err return err
} }
@@ -20,7 +20,7 @@ func TestDingDingNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewDingDingNotifier(model) _, err := newDingDingNotifier(model)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -34,14 +34,13 @@ func TestDingDingNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewDingDingNotifier(model) not, err := newDingDingNotifier(model)
notifier := not.(*DingDingNotifier) notifier := not.(*DingDingNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(notifier.Name, ShouldEqual, "dingding_testing") So(notifier.Name, ShouldEqual, "dingding_testing")
So(notifier.Type, ShouldEqual, "dingding") So(notifier.Type, ShouldEqual, "dingding")
So(notifier.Url, ShouldEqual, "https://www.google.com") So(notifier.URL, ShouldEqual, "https://www.google.com")
}) })
}) })
} }
+13 -8
View File
@@ -30,12 +30,16 @@ func init() {
}) })
} }
// EmailNotifier is responsible for sending
// alert notifications over email.
type EmailNotifier struct { type EmailNotifier struct {
NotifierBase NotifierBase
Addresses []string Addresses []string
log log.Logger log log.Logger
} }
// NewEmailNotifier is the constructor function
// for the EmailNotifier.
func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
addressesString := model.Settings.Get("addresses").MustString() addressesString := model.Settings.Get("addresses").MustString()
@@ -59,12 +63,13 @@ func NewEmailNotifier(model *models.AlertNotification) (alerting.Notifier, error
}, nil }, nil
} }
func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify sends the alert notification.
this.log.Info("Sending alert notification to", "addresses", this.Addresses) func (en *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
en.log.Info("Sending alert notification to", "addresses", en.Addresses)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) en.log.Error("Failed get rule link", "error", err)
return err return err
} }
@@ -83,13 +88,13 @@ func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
"StateModel": evalContext.GetStateModel(), "StateModel": evalContext.GetStateModel(),
"Message": evalContext.Rule.Message, "Message": evalContext.Rule.Message,
"Error": error, "Error": error,
"RuleUrl": ruleUrl, "RuleUrl": ruleURL,
"ImageLink": "", "ImageLink": "",
"EmbeddedImage": "", "EmbeddedImage": "",
"AlertPageUrl": setting.AppUrl + "alerting", "AlertPageUrl": setting.AppUrl + "alerting",
"EvalMatches": evalContext.EvalMatches, "EvalMatches": evalContext.EvalMatches,
}, },
To: this.Addresses, To: en.Addresses,
Template: "alert_notification.html", Template: "alert_notification.html",
EmbededFiles: []string{}, EmbededFiles: []string{},
}, },
@@ -108,9 +113,9 @@ func (this *EmailNotifier) Notify(evalContext *alerting.EvalContext) error {
err = bus.DispatchCtx(evalContext.Ctx, cmd) err = bus.DispatchCtx(evalContext.Ctx, cmd)
if err != nil { if err != nil {
this.log.Error("Failed to send alert notification email", "error", err) en.log.Error("Failed to send alert notification email", "error", err)
return err return err
} }
return nil
return nil
} }
+18 -15
View File
@@ -18,7 +18,7 @@ func init() {
Name: "Google Hangouts Chat", Name: "Google Hangouts Chat",
Description: "Sends notifications to Google Hangouts Chat via webhooks based on the official JSON message " + Description: "Sends notifications to Google Hangouts Chat via webhooks based on the official JSON message " +
"format (https://developers.google.com/hangouts/chat/reference/message-formats/).", "format (https://developers.google.com/hangouts/chat/reference/message-formats/).",
Factory: NewGoogleChatNotifier, Factory: newGoogleChatNotifier,
OptionsTemplate: ` OptionsTemplate: `
<h3 class="page-heading">Google Hangouts Chat settings</h3> <h3 class="page-heading">Google Hangouts Chat settings</h3>
<div class="gf-form max-width-30"> <div class="gf-form max-width-30">
@@ -29,7 +29,7 @@ func init() {
}) })
} }
func NewGoogleChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func newGoogleChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} return nil, alerting.ValidationError{Reason: "Could not find url property in settings"}
@@ -37,14 +37,16 @@ func NewGoogleChatNotifier(model *models.AlertNotification) (alerting.Notifier,
return &GoogleChatNotifier{ return &GoogleChatNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
Url: url, URL: url,
log: log.New("alerting.notifier.googlechat"), log: log.New("alerting.notifier.googlechat"),
}, nil }, nil
} }
// GoogleChatNotifier is responsible for sending
// alert notifications to Google chat.
type GoogleChatNotifier struct { type GoogleChatNotifier struct {
NotifierBase NotifierBase
Url string URL string
log log.Logger log log.Logger
} }
@@ -90,7 +92,7 @@ type imageWidget struct {
} }
type image struct { type image struct {
ImageUrl string `json:"imageUrl"` ImageURL string `json:"imageUrl"`
} }
type button struct { type button struct {
@@ -107,19 +109,20 @@ type onClick struct {
} }
type openLink struct { type openLink struct {
Url string `json:"url"` URL string `json:"url"`
} }
func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify send an alert notification to Google Chat.
this.log.Info("Executing Google Chat notification") func (gcn *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error {
gcn.log.Info("Executing Google Chat notification")
headers := map[string]string{ headers := map[string]string{
"Content-Type": "application/json; charset=UTF-8", "Content-Type": "application/json; charset=UTF-8",
} }
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("evalContext returned an invalid rule URL") gcn.log.Error("evalContext returned an invalid rule URL")
} }
// add a text paragraph widget for the message // add a text paragraph widget for the message
@@ -152,11 +155,11 @@ func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error
if evalContext.ImagePublicUrl != "" { if evalContext.ImagePublicUrl != "" {
widgets = append(widgets, imageWidget{ widgets = append(widgets, imageWidget{
Image: image{ Image: image{
ImageUrl: evalContext.ImagePublicUrl, ImageURL: evalContext.ImagePublicUrl,
}, },
}) })
} else { } else {
this.log.Info("Could not retrieve a public image URL.") gcn.log.Info("Could not retrieve a public image URL.")
} }
// add a button widget (link to Grafana) // add a button widget (link to Grafana)
@@ -167,7 +170,7 @@ func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error
Text: "OPEN IN GRAFANA", Text: "OPEN IN GRAFANA",
OnClick: onClick{ OnClick: onClick{
OpenLink: openLink{ OpenLink: openLink{
Url: ruleUrl, URL: ruleURL,
}, },
}, },
}, },
@@ -200,14 +203,14 @@ func (this *GoogleChatNotifier) Notify(evalContext *alerting.EvalContext) error
body, _ := json.Marshal(res1D) body, _ := json.Marshal(res1D)
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: this.Url, Url: gcn.URL,
HttpMethod: "POST", HttpMethod: "POST",
HttpHeader: headers, HttpHeader: headers,
Body: string(body), Body: string(body),
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", this.Name) gcn.log.Error("Failed to send Google Hangouts Chat alert", "error", err, "webhook", gcn.Name)
return err return err
} }
@@ -22,7 +22,7 @@ func TestGoogleChatNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
_, err := NewGoogleChatNotifier(model) _, err := newGoogleChatNotifier(model)
So(err, ShouldNotBeNil) So(err, ShouldNotBeNil)
}) })
@@ -39,15 +39,14 @@ func TestGoogleChatNotifier(t *testing.T) {
Settings: settingsJSON, Settings: settingsJSON,
} }
not, err := NewGoogleChatNotifier(model) not, err := newGoogleChatNotifier(model)
webhookNotifier := not.(*GoogleChatNotifier) webhookNotifier := not.(*GoogleChatNotifier)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(webhookNotifier.Name, ShouldEqual, "ops") So(webhookNotifier.Name, ShouldEqual, "ops")
So(webhookNotifier.Type, ShouldEqual, "googlechat") So(webhookNotifier.Type, ShouldEqual, "googlechat")
So(webhookNotifier.Url, ShouldEqual, "http://google.com") So(webhookNotifier.URL, ShouldEqual, "http://google.com")
}) })
}) })
}) })
} }
+21 -16
View File
@@ -46,6 +46,8 @@ const (
maxFieldCount int = 4 maxFieldCount int = 4
) )
// NewHipChatNotifier is the constructor functions
// for the HipChatNotifier
func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if strings.HasSuffix(url, "/") { if strings.HasSuffix(url, "/") {
@@ -56,31 +58,34 @@ func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, err
} }
apikey := model.Settings.Get("apikey").MustString() apikey := model.Settings.Get("apikey").MustString()
roomId := model.Settings.Get("roomid").MustString() roomID := model.Settings.Get("roomid").MustString()
return &HipChatNotifier{ return &HipChatNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
Url: url, URL: url,
ApiKey: apikey, APIKey: apikey,
RoomId: roomId, RoomID: roomID,
log: log.New("alerting.notifier.hipchat"), log: log.New("alerting.notifier.hipchat"),
}, nil }, nil
} }
// HipChatNotifier is responsible for sending
// alert notifications to Hipchat.
type HipChatNotifier struct { type HipChatNotifier struct {
NotifierBase NotifierBase
Url string URL string
ApiKey string APIKey string
RoomId string RoomID string
log log.Logger log log.Logger
} }
func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify sends an alert notification to HipChat
this.log.Info("Executing hipchat notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) func (hc *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
hc.log.Info("Executing hipchat notification", "ruleId", evalContext.Rule.Id, "notification", hc.Name)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) hc.log.Error("Failed get rule link", "error", err)
return err return err
} }
@@ -133,7 +138,7 @@ func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
// Add a card with link to the dashboard // Add a card with link to the dashboard
card := map[string]interface{}{ card := map[string]interface{}{
"style": "application", "style": "application",
"url": ruleUrl, "url": ruleURL,
"id": "1", "id": "1",
"title": evalContext.GetNotificationTitle(), "title": evalContext.GetNotificationTitle(),
"description": message, "description": message,
@@ -160,13 +165,13 @@ func (this *HipChatNotifier) Notify(evalContext *alerting.EvalContext) error {
"card": card, "card": card,
} }
hipUrl := fmt.Sprintf("%s/v2/room/%s/notification?auth_token=%s", this.Url, this.RoomId, this.ApiKey) hipURL := fmt.Sprintf("%s/v2/room/%s/notification?auth_token=%s", hc.URL, hc.RoomID, hc.APIKey)
data, _ := json.Marshal(&body) data, _ := json.Marshal(&body)
this.log.Info("Request payload", "json", string(data)) hc.log.Info("Request payload", "json", string(data))
cmd := &models.SendWebhookSync{Url: hipUrl, Body: string(data)} cmd := &models.SendWebhookSync{Url: hipURL, Body: string(data)}
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send hipchat notification", "error", err, "webhook", this.Name) hc.log.Error("Failed to send hipchat notification", "error", err, "webhook", hc.Name)
return err return err
} }
@@ -45,9 +45,9 @@ func TestHipChatNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(hipchatNotifier.Name, ShouldEqual, "ops") So(hipchatNotifier.Name, ShouldEqual, "ops")
So(hipchatNotifier.Type, ShouldEqual, "hipchat") So(hipchatNotifier.Type, ShouldEqual, "hipchat")
So(hipchatNotifier.Url, ShouldEqual, "http://google.com") So(hipchatNotifier.URL, ShouldEqual, "http://google.com")
So(hipchatNotifier.ApiKey, ShouldEqual, "") So(hipchatNotifier.APIKey, ShouldEqual, "")
So(hipchatNotifier.RoomId, ShouldEqual, "") So(hipchatNotifier.RoomID, ShouldEqual, "")
}) })
Convey("from settings with Recipient and Mention", func() { Convey("from settings with Recipient and Mention", func() {
@@ -71,11 +71,10 @@ func TestHipChatNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(hipchatNotifier.Name, ShouldEqual, "ops") So(hipchatNotifier.Name, ShouldEqual, "ops")
So(hipchatNotifier.Type, ShouldEqual, "hipchat") So(hipchatNotifier.Type, ShouldEqual, "hipchat")
So(hipchatNotifier.Url, ShouldEqual, "http://www.hipchat.com") So(hipchatNotifier.URL, ShouldEqual, "http://www.hipchat.com")
So(hipchatNotifier.ApiKey, ShouldEqual, "1234") So(hipchatNotifier.APIKey, ShouldEqual, "1234")
So(hipchatNotifier.RoomId, ShouldEqual, "1234") So(hipchatNotifier.RoomID, ShouldEqual, "1234")
}) })
}) })
}) })
} }
+12 -9
View File
@@ -32,6 +32,7 @@ func init() {
}) })
} }
// NewKafkaNotifier is the constructor function for the Kafka notifier.
func NewKafkaNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewKafkaNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
endpoint := model.Settings.Get("kafkaRestProxy").MustString() endpoint := model.Settings.Get("kafkaRestProxy").MustString()
if endpoint == "" { if endpoint == "" {
@@ -50,6 +51,8 @@ func NewKafkaNotifier(model *models.AlertNotification) (alerting.Notifier, error
}, nil }, nil
} }
// KafkaNotifier is responsible for sending
// alert notifications to Kafka.
type KafkaNotifier struct { type KafkaNotifier struct {
NotifierBase NotifierBase
Endpoint string Endpoint string
@@ -57,8 +60,8 @@ type KafkaNotifier struct {
log log.Logger log log.Logger
} }
func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify sends the alert notification.
func (kn *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
state := evalContext.Rule.State state := evalContext.Rule.State
customData := triggMetrString customData := triggMetrString
@@ -66,7 +69,7 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value) customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value)
} }
this.log.Info("Notifying Kafka", "alert_state", state) kn.log.Info("Notifying Kafka", "alert_state", state)
recordJSON := simplejson.New() recordJSON := simplejson.New()
records := make([]interface{}, 1) records := make([]interface{}, 1)
@@ -77,12 +80,12 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
bodyJSON.Set("details", customData) bodyJSON.Set("details", customData)
bodyJSON.Set("incident_key", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10)) bodyJSON.Set("incident_key", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10))
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) kn.log.Error("Failed get rule link", "error", err)
return err return err
} }
bodyJSON.Set("client_url", ruleUrl) bodyJSON.Set("client_url", ruleURL)
if evalContext.ImagePublicUrl != "" { if evalContext.ImagePublicUrl != "" {
contexts := make([]interface{}, 1) contexts := make([]interface{}, 1)
@@ -99,10 +102,10 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
recordJSON.Set("records", records) recordJSON.Set("records", records)
body, _ := recordJSON.MarshalJSON() body, _ := recordJSON.MarshalJSON()
topicUrl := this.Endpoint + "/topics/" + this.Topic topicURL := kn.Endpoint + "/topics/" + kn.Topic
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: topicUrl, Url: topicURL,
Body: string(body), Body: string(body),
HttpMethod: "POST", HttpMethod: "POST",
HttpHeader: map[string]string{ HttpHeader: map[string]string{
@@ -112,7 +115,7 @@ func (this *KafkaNotifier) Notify(evalContext *alerting.EvalContext) error {
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send notification to Kafka", "error", err, "body", string(body)) kn.log.Error("Failed to send notification to Kafka", "error", err, "body", string(body))
return err return err
} }
@@ -49,7 +49,6 @@ func TestKafkaNotifier(t *testing.T) {
So(kafkaNotifier.Endpoint, ShouldEqual, "http://localhost:8082") So(kafkaNotifier.Endpoint, ShouldEqual, "http://localhost:8082")
So(kafkaNotifier.Topic, ShouldEqual, "topic1") So(kafkaNotifier.Topic, ShouldEqual, "topic1")
}) })
}) })
}) })
} }
+16 -12
View File
@@ -29,9 +29,10 @@ func init() {
} }
const ( const (
lineNotifyUrl string = "https://notify-api.line.me/api/notify" lineNotifyURL string = "https://notify-api.line.me/api/notify"
) )
// NewLINENotifier is the constructor for the LINE notifier
func NewLINENotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewLINENotifier(model *models.AlertNotification) (alerting.Notifier, error) {
token := model.Settings.Get("token").MustString() token := model.Settings.Get("token").MustString()
if token == "" { if token == "" {
@@ -45,33 +46,36 @@ func NewLINENotifier(model *models.AlertNotification) (alerting.Notifier, error)
}, nil }, nil
} }
// LineNotifier is responsible for sending
// alert notifications to LINE.
type LineNotifier struct { type LineNotifier struct {
NotifierBase NotifierBase
Token string Token string
log log.Logger log log.Logger
} }
func (this *LineNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify send an alert notification to LINE
this.log.Info("Executing line notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) func (ln *LineNotifier) Notify(evalContext *alerting.EvalContext) error {
ln.log.Info("Executing line notification", "ruleId", evalContext.Rule.Id, "notification", ln.Name)
var err error var err error
switch evalContext.Rule.State { switch evalContext.Rule.State {
case models.AlertStateAlerting: case models.AlertStateAlerting:
err = this.createAlert(evalContext) err = ln.createAlert(evalContext)
} }
return err return err
} }
func (this *LineNotifier) createAlert(evalContext *alerting.EvalContext) error { func (ln *LineNotifier) createAlert(evalContext *alerting.EvalContext) error {
this.log.Info("Creating Line notify", "ruleId", evalContext.Rule.Id, "notification", this.Name) ln.log.Info("Creating Line notify", "ruleId", evalContext.Rule.Id, "notification", ln.Name)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) ln.log.Error("Failed get rule link", "error", err)
return err return err
} }
form := url.Values{} form := url.Values{}
body := fmt.Sprintf("%s - %s\n%s", evalContext.Rule.Name, ruleUrl, evalContext.Rule.Message) body := fmt.Sprintf("%s - %s\n%s", evalContext.Rule.Name, ruleURL, evalContext.Rule.Message)
form.Add("message", body) form.Add("message", body)
if evalContext.ImagePublicUrl != "" { if evalContext.ImagePublicUrl != "" {
@@ -80,17 +84,17 @@ func (this *LineNotifier) createAlert(evalContext *alerting.EvalContext) error {
} }
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: lineNotifyUrl, Url: lineNotifyURL,
HttpMethod: "POST", HttpMethod: "POST",
HttpHeader: map[string]string{ HttpHeader: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", this.Token), "Authorization": fmt.Sprintf("Bearer %s", ln.Token),
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
}, },
Body: form.Encode(), Body: form.Encode(),
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send notification to LINE", "error", err, "body", body) ln.log.Error("Failed to send notification to LINE", "error", err, "body", body)
return err return err
} }
@@ -44,6 +44,5 @@ func TestLineNotifier(t *testing.T) {
So(lineNotifier.Type, ShouldEqual, "line") So(lineNotifier.Type, ShouldEqual, "line")
So(lineNotifier.Token, ShouldEqual, "abcdefgh0123456789") So(lineNotifier.Token, ShouldEqual, "abcdefgh0123456789")
}) })
}) })
} }
+29 -26
View File
@@ -44,54 +44,57 @@ var (
opsgenieAlertURL = "https://api.opsgenie.com/v2/alerts" opsgenieAlertURL = "https://api.opsgenie.com/v2/alerts"
) )
// NewOpsGenieNotifier is the constructor for OpsGenie.
func NewOpsGenieNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewOpsGenieNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
autoClose := model.Settings.Get("autoClose").MustBool(true) autoClose := model.Settings.Get("autoClose").MustBool(true)
apiKey := model.Settings.Get("apiKey").MustString() apiKey := model.Settings.Get("apiKey").MustString()
apiUrl := model.Settings.Get("apiUrl").MustString() apiURL := model.Settings.Get("apiUrl").MustString()
if apiKey == "" { if apiKey == "" {
return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"} return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"}
} }
if apiUrl == "" { if apiURL == "" {
apiUrl = opsgenieAlertURL apiURL = opsgenieAlertURL
} }
return &OpsGenieNotifier{ return &OpsGenieNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
ApiKey: apiKey, APIKey: apiKey,
ApiUrl: apiUrl, APIUrl: apiURL,
AutoClose: autoClose, AutoClose: autoClose,
log: log.New("alerting.notifier.opsgenie"), log: log.New("alerting.notifier.opsgenie"),
}, nil }, nil
} }
// OpsGenieNotifier is responsible for sending
// alert notifications to OpsGenie
type OpsGenieNotifier struct { type OpsGenieNotifier struct {
NotifierBase NotifierBase
ApiKey string APIKey string
ApiUrl string APIUrl string
AutoClose bool AutoClose bool
log log.Logger log log.Logger
} }
func (this *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify sends an alert notification to OpsGenie.
func (on *OpsGenieNotifier) Notify(evalContext *alerting.EvalContext) error {
var err error var err error
switch evalContext.Rule.State { switch evalContext.Rule.State {
case models.AlertStateOK: case models.AlertStateOK:
if this.AutoClose { if on.AutoClose {
err = this.closeAlert(evalContext) err = on.closeAlert(evalContext)
} }
case models.AlertStateAlerting: case models.AlertStateAlerting:
err = this.createAlert(evalContext) err = on.createAlert(evalContext)
} }
return err return err
} }
func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error { func (on *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) error {
this.log.Info("Creating OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", this.Name) on.log.Info("Creating OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", on.Name)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) on.log.Error("Failed get rule link", "error", err)
return err return err
} }
@@ -104,10 +107,10 @@ func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) err
bodyJSON.Set("message", evalContext.Rule.Name) bodyJSON.Set("message", evalContext.Rule.Name)
bodyJSON.Set("source", "Grafana") bodyJSON.Set("source", "Grafana")
bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10)) bodyJSON.Set("alias", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10))
bodyJSON.Set("description", fmt.Sprintf("%s - %s\n%s\n%s", evalContext.Rule.Name, ruleUrl, evalContext.Rule.Message, customData)) bodyJSON.Set("description", fmt.Sprintf("%s - %s\n%s\n%s", evalContext.Rule.Name, ruleURL, evalContext.Rule.Message, customData))
details := simplejson.New() details := simplejson.New()
details.Set("url", ruleUrl) details.Set("url", ruleURL)
if evalContext.ImagePublicUrl != "" { if evalContext.ImagePublicUrl != "" {
details.Set("image", evalContext.ImagePublicUrl) details.Set("image", evalContext.ImagePublicUrl)
} }
@@ -116,41 +119,41 @@ func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) err
body, _ := bodyJSON.MarshalJSON() body, _ := bodyJSON.MarshalJSON()
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: this.ApiUrl, Url: on.APIUrl,
Body: string(body), Body: string(body),
HttpMethod: "POST", HttpMethod: "POST",
HttpHeader: map[string]string{ HttpHeader: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": fmt.Sprintf("GenieKey %s", this.ApiKey), "Authorization": fmt.Sprintf("GenieKey %s", on.APIKey),
}, },
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body)) on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
} }
return nil return nil
} }
func (this *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error { func (on *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) error {
this.log.Info("Closing OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", this.Name) on.log.Info("Closing OpsGenie alert", "ruleId", evalContext.Rule.Id, "notification", on.Name)
bodyJSON := simplejson.New() bodyJSON := simplejson.New()
bodyJSON.Set("source", "Grafana") bodyJSON.Set("source", "Grafana")
body, _ := bodyJSON.MarshalJSON() body, _ := bodyJSON.MarshalJSON()
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: fmt.Sprintf("%s/alertId-%d/close?identifierType=alias", this.ApiUrl, evalContext.Rule.Id), Url: fmt.Sprintf("%s/alertId-%d/close?identifierType=alias", on.APIUrl, evalContext.Rule.Id),
Body: string(body), Body: string(body),
HttpMethod: "POST", HttpMethod: "POST",
HttpHeader: map[string]string{ HttpHeader: map[string]string{
"Content-Type": "application/json", "Content-Type": "application/json",
"Authorization": fmt.Sprintf("GenieKey %s", this.ApiKey), "Authorization": fmt.Sprintf("GenieKey %s", on.APIKey),
}, },
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body)) on.log.Error("Failed to send notification to OpsGenie", "error", err, "body", string(body))
return err return err
} }
@@ -45,7 +45,7 @@ func TestOpsGenieNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(opsgenieNotifier.Name, ShouldEqual, "opsgenie_testing") So(opsgenieNotifier.Name, ShouldEqual, "opsgenie_testing")
So(opsgenieNotifier.Type, ShouldEqual, "opsgenie") So(opsgenieNotifier.Type, ShouldEqual, "opsgenie")
So(opsgenieNotifier.ApiKey, ShouldEqual, "abcdefgh0123456789") So(opsgenieNotifier.APIKey, ShouldEqual, "abcdefgh0123456789")
}) })
}) })
}) })
+16 -12
View File
@@ -40,9 +40,10 @@ func init() {
} }
var ( var (
pagerdutyEventApiUrl = "https://events.pagerduty.com/v2/enqueue" pagerdutyEventAPIURL = "https://events.pagerduty.com/v2/enqueue"
) )
// NewPagerdutyNotifier is the constructor for the PagerDuty notifier
func NewPagerdutyNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewPagerdutyNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
autoResolve := model.Settings.Get("autoResolve").MustBool(false) autoResolve := model.Settings.Get("autoResolve").MustBool(false)
key := model.Settings.Get("integrationKey").MustString() key := model.Settings.Get("integrationKey").MustString()
@@ -58,6 +59,8 @@ func NewPagerdutyNotifier(model *models.AlertNotification) (alerting.Notifier, e
}, nil }, nil
} }
// PagerdutyNotifier is responsible for sending
// alert notifications to pagerduty
type PagerdutyNotifier struct { type PagerdutyNotifier struct {
NotifierBase NotifierBase
Key string Key string
@@ -65,10 +68,11 @@ type PagerdutyNotifier struct {
log log.Logger log log.Logger
} }
func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify sends an alert notification to PagerDuty
func (pn *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error {
if evalContext.Rule.State == models.AlertStateOK && !this.AutoResolve { if evalContext.Rule.State == models.AlertStateOK && !pn.AutoResolve {
this.log.Info("Not sending a trigger to Pagerduty", "state", evalContext.Rule.State, "auto resolve", this.AutoResolve) pn.log.Info("Not sending a trigger to Pagerduty", "state", evalContext.Rule.State, "auto resolve", pn.AutoResolve)
return nil return nil
} }
@@ -81,7 +85,7 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error {
customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value) customData = customData + fmt.Sprintf("%s: %v\n", evt.Metric, evt.Value)
} }
this.log.Info("Notifying Pagerduty", "event_type", eventType) pn.log.Info("Notifying Pagerduty", "event_type", eventType)
payloadJSON := simplejson.New() payloadJSON := simplejson.New()
payloadJSON.Set("summary", evalContext.Rule.Name+" - "+evalContext.Rule.Message) payloadJSON.Set("summary", evalContext.Rule.Name+" - "+evalContext.Rule.Message)
@@ -94,20 +98,20 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error {
payloadJSON.Set("custom_details", customData) payloadJSON.Set("custom_details", customData)
bodyJSON := simplejson.New() bodyJSON := simplejson.New()
bodyJSON.Set("routing_key", this.Key) bodyJSON.Set("routing_key", pn.Key)
bodyJSON.Set("event_action", eventType) bodyJSON.Set("event_action", eventType)
bodyJSON.Set("dedup_key", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10)) bodyJSON.Set("dedup_key", "alertId-"+strconv.FormatInt(evalContext.Rule.Id, 10))
bodyJSON.Set("payload", payloadJSON) bodyJSON.Set("payload", payloadJSON)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) pn.log.Error("Failed get rule link", "error", err)
return err return err
} }
links := make([]interface{}, 1) links := make([]interface{}, 1)
linkJSON := simplejson.New() linkJSON := simplejson.New()
linkJSON.Set("href", ruleUrl) linkJSON.Set("href", ruleURL)
bodyJSON.Set("client_url", ruleUrl) bodyJSON.Set("client_url", ruleURL)
bodyJSON.Set("client", "Grafana") bodyJSON.Set("client", "Grafana")
links[0] = linkJSON links[0] = linkJSON
bodyJSON.Set("links", links) bodyJSON.Set("links", links)
@@ -123,7 +127,7 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error {
body, _ := bodyJSON.MarshalJSON() body, _ := bodyJSON.MarshalJSON()
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: pagerdutyEventApiUrl, Url: pagerdutyEventAPIURL,
Body: string(body), Body: string(body),
HttpMethod: "POST", HttpMethod: "POST",
HttpHeader: map[string]string{ HttpHeader: map[string]string{
@@ -132,7 +136,7 @@ func (this *PagerdutyNotifier) Notify(evalContext *alerting.EvalContext) error {
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send notification to Pagerduty", "error", err, "body", string(body)) pn.log.Error("Failed to send notification to Pagerduty", "error", err, "body", string(body))
return err return err
} }
+29 -25
View File
@@ -14,7 +14,7 @@ import (
"github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/alerting"
) )
const PUSHOVER_ENDPOINT = "https://api.pushover.net/1/messages.json" const pushoverEndpoint = "https://api.pushover.net/1/messages.json"
func init() { func init() {
sounds := ` sounds := `
@@ -95,9 +95,10 @@ func init() {
}) })
} }
// NewPushoverNotifier is the constructor for the Pushover Notifier
func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
userKey := model.Settings.Get("userKey").MustString() userKey := model.Settings.Get("userKey").MustString()
apiToken := model.Settings.Get("apiToken").MustString() APIToken := model.Settings.Get("apiToken").MustString()
device := model.Settings.Get("device").MustString() device := model.Settings.Get("device").MustString()
priority, _ := strconv.Atoi(model.Settings.Get("priority").MustString()) priority, _ := strconv.Atoi(model.Settings.Get("priority").MustString())
retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString()) retry, _ := strconv.Atoi(model.Settings.Get("retry").MustString())
@@ -109,13 +110,13 @@ func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, er
if userKey == "" { if userKey == "" {
return nil, alerting.ValidationError{Reason: "User key not given"} return nil, alerting.ValidationError{Reason: "User key not given"}
} }
if apiToken == "" { if APIToken == "" {
return nil, alerting.ValidationError{Reason: "API token not given"} return nil, alerting.ValidationError{Reason: "API token not given"}
} }
return &PushoverNotifier{ return &PushoverNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
UserKey: userKey, UserKey: userKey,
ApiToken: apiToken, APIToken: APIToken,
Priority: priority, Priority: priority,
Retry: retry, Retry: retry,
Expire: expire, Expire: expire,
@@ -127,10 +128,12 @@ func NewPushoverNotifier(model *models.AlertNotification) (alerting.Notifier, er
}, nil }, nil
} }
// PushoverNotifier is responsible for sending
// alert notifications to Pushover
type PushoverNotifier struct { type PushoverNotifier struct {
NotifierBase NotifierBase
UserKey string UserKey string
ApiToken string APIToken string
Priority int Priority int
Retry int Retry int
Expire int Expire int
@@ -141,10 +144,11 @@ type PushoverNotifier struct {
log log.Logger log log.Logger
} }
func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify sends a alert notification to Pushover
ruleUrl, err := evalContext.GetRuleUrl() func (pn *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) pn.log.Error("Failed get rule link", "error", err)
return err return err
} }
@@ -163,34 +167,34 @@ func (this *PushoverNotifier) Notify(evalContext *alerting.EvalContext) error {
message = "Notification message missing (Set a notification message to replace this text.)" message = "Notification message missing (Set a notification message to replace this text.)"
} }
headers, uploadBody, err := this.genPushoverBody(evalContext, message, ruleUrl) headers, uploadBody, err := pn.genPushoverBody(evalContext, message, ruleURL)
if err != nil { if err != nil {
this.log.Error("Failed to generate body for pushover", "error", err) pn.log.Error("Failed to generate body for pushover", "error", err)
return err return err
} }
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: PUSHOVER_ENDPOINT, Url: pushoverEndpoint,
HttpMethod: "POST", HttpMethod: "POST",
HttpHeader: headers, HttpHeader: headers,
Body: uploadBody.String(), Body: uploadBody.String(),
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send pushover notification", "error", err, "webhook", this.Name) pn.log.Error("Failed to send pushover notification", "error", err, "webhook", pn.Name)
return err return err
} }
return nil return nil
} }
func (this *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext, message string, ruleUrl string) (map[string]string, bytes.Buffer, error) { func (pn *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext, message string, ruleURL string) (map[string]string, bytes.Buffer, error) {
var b bytes.Buffer var b bytes.Buffer
var err error var err error
w := multipart.NewWriter(&b) w := multipart.NewWriter(&b)
// Add image only if requested and available // Add image only if requested and available
if this.Upload && evalContext.ImageOnDiskPath != "" { if pn.Upload && evalContext.ImageOnDiskPath != "" {
f, err := os.Open(evalContext.ImageOnDiskPath) f, err := os.Open(evalContext.ImageOnDiskPath)
if err != nil { if err != nil {
return nil, b, err return nil, b, err
@@ -209,47 +213,47 @@ func (this *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext,
} }
// Add the user token // Add the user token
err = w.WriteField("user", this.UserKey) err = w.WriteField("user", pn.UserKey)
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
// Add the api token // Add the api token
err = w.WriteField("token", this.ApiToken) err = w.WriteField("token", pn.APIToken)
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
// Add priority // Add priority
err = w.WriteField("priority", strconv.Itoa(this.Priority)) err = w.WriteField("priority", strconv.Itoa(pn.Priority))
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
if this.Priority == 2 { if pn.Priority == 2 {
err = w.WriteField("retry", strconv.Itoa(this.Retry)) err = w.WriteField("retry", strconv.Itoa(pn.Retry))
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
err = w.WriteField("expire", strconv.Itoa(this.Expire)) err = w.WriteField("expire", strconv.Itoa(pn.Expire))
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
} }
// Add device // Add device
if this.Device != "" { if pn.Device != "" {
err = w.WriteField("device", this.Device) err = w.WriteField("device", pn.Device)
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
} }
// Add sound // Add sound
sound := this.AlertingSound sound := pn.AlertingSound
if evalContext.Rule.State == models.AlertStateOK { if evalContext.Rule.State == models.AlertStateOK {
sound = this.OkSound sound = pn.OkSound
} }
if sound != "default" { if sound != "default" {
err = w.WriteField("sound", sound) err = w.WriteField("sound", sound)
@@ -265,7 +269,7 @@ func (this *PushoverNotifier) genPushoverBody(evalContext *alerting.EvalContext,
} }
// Add URL // Add URL
err = w.WriteField("url", ruleUrl) err = w.WriteField("url", ruleURL)
if err != nil { if err != nil {
return nil, b, err return nil, b, err
} }
@@ -53,7 +53,7 @@ func TestPushoverNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(pushoverNotifier.Name, ShouldEqual, "Pushover") So(pushoverNotifier.Name, ShouldEqual, "Pushover")
So(pushoverNotifier.Type, ShouldEqual, "pushover") So(pushoverNotifier.Type, ShouldEqual, "pushover")
So(pushoverNotifier.ApiToken, ShouldEqual, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve") So(pushoverNotifier.APIToken, ShouldEqual, "4SrUFQL4A5V5TQ1z5Pg9nxHXPXSTve")
So(pushoverNotifier.UserKey, ShouldEqual, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o") So(pushoverNotifier.UserKey, ShouldEqual, "tzNZYf36y0ohWwXo4XoUrB61rz1A4o")
So(pushoverNotifier.Priority, ShouldEqual, 1) So(pushoverNotifier.Priority, ShouldEqual, 1)
So(pushoverNotifier.AlertingSound, ShouldEqual, "pushover") So(pushoverNotifier.AlertingSound, ShouldEqual, "pushover")
+18 -14
View File
@@ -44,6 +44,7 @@ func init() {
} }
// NewSensuNotifier is the constructor for the Sensu Notifier.
func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
@@ -52,7 +53,7 @@ func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error
return &SensuNotifier{ return &SensuNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
Url: url, URL: url,
User: model.Settings.Get("username").MustString(), User: model.Settings.Get("username").MustString(),
Source: model.Settings.Get("source").MustString(), Source: model.Settings.Get("source").MustString(),
Password: model.Settings.Get("password").MustString(), Password: model.Settings.Get("password").MustString(),
@@ -61,9 +62,11 @@ func NewSensuNotifier(model *models.AlertNotification) (alerting.Notifier, error
}, nil }, nil
} }
// SensuNotifier is responsible for sending
// alert notifications to Sensu.
type SensuNotifier struct { type SensuNotifier struct {
NotifierBase NotifierBase
Url string URL string
Source string Source string
User string User string
Password string Password string
@@ -71,8 +74,9 @@ type SensuNotifier struct {
log log.Logger log log.Logger
} }
func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify send alert notification to Sensu
this.log.Info("Sending sensu result") func (sn *SensuNotifier) Notify(evalContext *alerting.EvalContext) error {
sn.log.Info("Sending sensu result")
bodyJSON := simplejson.New() bodyJSON := simplejson.New()
bodyJSON.Set("ruleId", evalContext.Rule.Id) bodyJSON.Set("ruleId", evalContext.Rule.Id)
@@ -80,8 +84,8 @@ func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error {
bodyJSON.Set("name", strings.Replace(evalContext.Rule.Name, " ", "_", -1)) bodyJSON.Set("name", strings.Replace(evalContext.Rule.Name, " ", "_", -1))
// Sensu alerts require a source. We set it to the user-specified value (optional), // Sensu alerts require a source. We set it to the user-specified value (optional),
// else we fallback and use the grafana ruleID. // else we fallback and use the grafana ruleID.
if this.Source != "" { if sn.Source != "" {
bodyJSON.Set("source", this.Source) bodyJSON.Set("source", sn.Source)
} else { } else {
bodyJSON.Set("source", "grafana_rule_"+strconv.FormatInt(evalContext.Rule.Id, 10)) bodyJSON.Set("source", "grafana_rule_"+strconv.FormatInt(evalContext.Rule.Id, 10))
} }
@@ -98,13 +102,13 @@ func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error {
bodyJSON.Set("status", 0) bodyJSON.Set("status", 0)
} }
if this.Handler != "" { if sn.Handler != "" {
bodyJSON.Set("handler", this.Handler) bodyJSON.Set("handler", sn.Handler)
} }
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err == nil { if err == nil {
bodyJSON.Set("ruleUrl", ruleUrl) bodyJSON.Set("ruleUrl", ruleURL)
} }
if evalContext.ImagePublicUrl != "" { if evalContext.ImagePublicUrl != "" {
@@ -118,15 +122,15 @@ func (this *SensuNotifier) Notify(evalContext *alerting.EvalContext) error {
body, _ := bodyJSON.MarshalJSON() body, _ := bodyJSON.MarshalJSON()
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: this.Url, Url: sn.URL,
User: this.User, User: sn.User,
Password: this.Password, Password: sn.Password,
Body: string(body), Body: string(body),
HttpMethod: "POST", HttpMethod: "POST",
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send sensu event", "error", err, "sensu", this.Name) sn.log.Error("Failed to send sensu event", "error", err, "sensu", sn.Name)
return err return err
} }
@@ -47,7 +47,7 @@ func TestSensuNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(sensuNotifier.Name, ShouldEqual, "sensu") So(sensuNotifier.Name, ShouldEqual, "sensu")
So(sensuNotifier.Type, ShouldEqual, "sensu") So(sensuNotifier.Type, ShouldEqual, "sensu")
So(sensuNotifier.Url, ShouldEqual, "http://sensu-api.example.com:4567/results") So(sensuNotifier.URL, ShouldEqual, "http://sensu-api.example.com:4567/results")
So(sensuNotifier.Source, ShouldEqual, "grafana_instance_01") So(sensuNotifier.Source, ShouldEqual, "grafana_instance_01")
So(sensuNotifier.Handler, ShouldEqual, "myhandler") So(sensuNotifier.Handler, ShouldEqual, "myhandler")
}) })
+34 -30
View File
@@ -99,6 +99,7 @@ func init() {
} }
// NewSlackNotifier is the constructor for the Slack notifier
func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
@@ -108,18 +109,18 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error
recipient := model.Settings.Get("recipient").MustString() recipient := model.Settings.Get("recipient").MustString()
username := model.Settings.Get("username").MustString() username := model.Settings.Get("username").MustString()
iconEmoji := model.Settings.Get("icon_emoji").MustString() iconEmoji := model.Settings.Get("icon_emoji").MustString()
iconUrl := model.Settings.Get("icon_url").MustString() iconURL := model.Settings.Get("icon_url").MustString()
mention := model.Settings.Get("mention").MustString() mention := model.Settings.Get("mention").MustString()
token := model.Settings.Get("token").MustString() token := model.Settings.Get("token").MustString()
uploadImage := model.Settings.Get("uploadImage").MustBool(true) uploadImage := model.Settings.Get("uploadImage").MustBool(true)
return &SlackNotifier{ return &SlackNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
Url: url, URL: url,
Recipient: recipient, Recipient: recipient,
Username: username, Username: username,
IconEmoji: iconEmoji, IconEmoji: iconEmoji,
IconUrl: iconUrl, IconURL: iconURL,
Mention: mention, Mention: mention,
Token: token, Token: token,
Upload: uploadImage, Upload: uploadImage,
@@ -127,25 +128,28 @@ func NewSlackNotifier(model *models.AlertNotification) (alerting.Notifier, error
}, nil }, nil
} }
// SlackNotifier is responsible for sending
// alert notification to Slack.
type SlackNotifier struct { type SlackNotifier struct {
NotifierBase NotifierBase
Url string URL string
Recipient string Recipient string
Username string Username string
IconEmoji string IconEmoji string
IconUrl string IconURL string
Mention string Mention string
Token string Token string
Upload bool Upload bool
log log.Logger log log.Logger
} }
func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify send alert notification to Slack.
this.log.Info("Executing slack notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) func (sn *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
sn.log.Info("Executing slack notification", "ruleId", evalContext.Rule.Id, "notification", sn.Name)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) sn.log.Error("Failed get rule link", "error", err)
return err return err
} }
@@ -170,14 +174,14 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
}) })
} }
message := this.Mention message := sn.Mention
if evalContext.Rule.State != models.AlertStateOK { //don't add message when going back to alert state ok. if evalContext.Rule.State != models.AlertStateOK { //don't add message when going back to alert state ok.
message += " " + evalContext.Rule.Message message += " " + evalContext.Rule.Message
} }
image_url := "" imageURL := ""
// default to file.upload API method if a token is provided // default to file.upload API method if a token is provided
if this.Token == "" { if sn.Token == "" {
image_url = evalContext.ImagePublicUrl imageURL = evalContext.ImagePublicUrl
} }
body := map[string]interface{}{ body := map[string]interface{}{
@@ -186,10 +190,10 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
"fallback": evalContext.GetNotificationTitle(), "fallback": evalContext.GetNotificationTitle(),
"color": evalContext.GetStateModel().Color, "color": evalContext.GetStateModel().Color,
"title": evalContext.GetNotificationTitle(), "title": evalContext.GetNotificationTitle(),
"title_link": ruleUrl, "title_link": ruleURL,
"text": message, "text": message,
"fields": fields, "fields": fields,
"image_url": image_url, "image_url": imageURL,
"footer": "Grafana v" + setting.BuildVersion, "footer": "Grafana v" + setting.BuildVersion,
"footer_icon": "https://grafana.com/assets/img/fav32.png", "footer_icon": "https://grafana.com/assets/img/fav32.png",
"ts": time.Now().Unix(), "ts": time.Now().Unix(),
@@ -199,26 +203,26 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
} }
//recipient override //recipient override
if this.Recipient != "" { if sn.Recipient != "" {
body["channel"] = this.Recipient body["channel"] = sn.Recipient
} }
if this.Username != "" { if sn.Username != "" {
body["username"] = this.Username body["username"] = sn.Username
} }
if this.IconEmoji != "" { if sn.IconEmoji != "" {
body["icon_emoji"] = this.IconEmoji body["icon_emoji"] = sn.IconEmoji
} }
if this.IconUrl != "" { if sn.IconURL != "" {
body["icon_url"] = this.IconUrl body["icon_url"] = sn.IconURL
} }
data, _ := json.Marshal(&body) data, _ := json.Marshal(&body)
cmd := &models.SendWebhookSync{Url: this.Url, Body: string(data)} cmd := &models.SendWebhookSync{Url: sn.URL, Body: string(data)}
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send slack notification", "error", err, "webhook", this.Name) sn.log.Error("Failed to send slack notification", "error", err, "webhook", sn.Name)
return err return err
} }
if this.Token != "" && this.UploadImage { if sn.Token != "" && sn.UploadImage {
err = SlackFileUpload(evalContext, this.log, "https://slack.com/api/files.upload", this.Recipient, this.Token) err = slackFileUpload(evalContext, sn.log, "https://slack.com/api/files.upload", sn.Recipient, sn.Token)
if err != nil { if err != nil {
return err return err
} }
@@ -226,12 +230,12 @@ func (this *SlackNotifier) Notify(evalContext *alerting.EvalContext) error {
return nil return nil
} }
func SlackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url string, recipient string, token string) error { func slackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url string, recipient string, token string) error {
if evalContext.ImageOnDiskPath == "" { if evalContext.ImageOnDiskPath == "" {
evalContext.ImageOnDiskPath = filepath.Join(setting.HomePath, "public/img/mixed_styles.png") evalContext.ImageOnDiskPath = filepath.Join(setting.HomePath, "public/img/mixed_styles.png")
} }
log.Info("Uploading to slack via file.upload API") log.Info("Uploading to slack via file.upload API")
headers, uploadBody, err := GenerateSlackBody(evalContext.ImageOnDiskPath, token, recipient) headers, uploadBody, err := generateSlackBody(evalContext.ImageOnDiskPath, token, recipient)
if err != nil { if err != nil {
return err return err
} }
@@ -246,7 +250,7 @@ func SlackFileUpload(evalContext *alerting.EvalContext, log log.Logger, url stri
return nil return nil
} }
func GenerateSlackBody(file string, token string, recipient string) (map[string]string, bytes.Buffer, error) { func generateSlackBody(file string, token string, recipient string) (map[string]string, bytes.Buffer, error) {
// Slack requires all POSTs to files.upload to present // Slack requires all POSTs to files.upload to present
// an "application/x-www-form-urlencoded" encoded querystring // an "application/x-www-form-urlencoded" encoded querystring
// See https://api.slack.com/methods/files.upload // See https://api.slack.com/methods/files.upload
@@ -45,11 +45,11 @@ func TestSlackNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(slackNotifier.Name, ShouldEqual, "ops") So(slackNotifier.Name, ShouldEqual, "ops")
So(slackNotifier.Type, ShouldEqual, "slack") So(slackNotifier.Type, ShouldEqual, "slack")
So(slackNotifier.Url, ShouldEqual, "http://google.com") So(slackNotifier.URL, ShouldEqual, "http://google.com")
So(slackNotifier.Recipient, ShouldEqual, "") So(slackNotifier.Recipient, ShouldEqual, "")
So(slackNotifier.Username, ShouldEqual, "") So(slackNotifier.Username, ShouldEqual, "")
So(slackNotifier.IconEmoji, ShouldEqual, "") So(slackNotifier.IconEmoji, ShouldEqual, "")
So(slackNotifier.IconUrl, ShouldEqual, "") So(slackNotifier.IconURL, ShouldEqual, "")
So(slackNotifier.Mention, ShouldEqual, "") So(slackNotifier.Mention, ShouldEqual, "")
So(slackNotifier.Token, ShouldEqual, "") So(slackNotifier.Token, ShouldEqual, "")
}) })
@@ -79,11 +79,11 @@ func TestSlackNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(slackNotifier.Name, ShouldEqual, "ops") So(slackNotifier.Name, ShouldEqual, "ops")
So(slackNotifier.Type, ShouldEqual, "slack") So(slackNotifier.Type, ShouldEqual, "slack")
So(slackNotifier.Url, ShouldEqual, "http://google.com") So(slackNotifier.URL, ShouldEqual, "http://google.com")
So(slackNotifier.Recipient, ShouldEqual, "#ds-opentsdb") So(slackNotifier.Recipient, ShouldEqual, "#ds-opentsdb")
So(slackNotifier.Username, ShouldEqual, "Grafana Alerts") So(slackNotifier.Username, ShouldEqual, "Grafana Alerts")
So(slackNotifier.IconEmoji, ShouldEqual, ":smile:") So(slackNotifier.IconEmoji, ShouldEqual, ":smile:")
So(slackNotifier.IconUrl, ShouldEqual, "https://grafana.com/img/fav32.png") So(slackNotifier.IconURL, ShouldEqual, "https://grafana.com/img/fav32.png")
So(slackNotifier.Mention, ShouldEqual, "@carl") So(slackNotifier.Mention, ShouldEqual, "@carl")
So(slackNotifier.Token, ShouldEqual, "xoxb-XXXXXXXX-XXXXXXXX-XXXXXXXXXX") So(slackNotifier.Token, ShouldEqual, "xoxb-XXXXXXXX-XXXXXXXX-XXXXXXXXXX")
}) })
+13 -9
View File
@@ -26,6 +26,7 @@ func init() {
} }
// NewTeamsNotifier is the constructor for Teams notifier.
func NewTeamsNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewTeamsNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
@@ -34,23 +35,26 @@ func NewTeamsNotifier(model *models.AlertNotification) (alerting.Notifier, error
return &TeamsNotifier{ return &TeamsNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
Url: url, URL: url,
log: log.New("alerting.notifier.teams"), log: log.New("alerting.notifier.teams"),
}, nil }, nil
} }
// TeamsNotifier is responsible for sending
// alert notifications to Microsoft teams.
type TeamsNotifier struct { type TeamsNotifier struct {
NotifierBase NotifierBase
Url string URL string
log log.Logger log log.Logger
} }
func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify send an alert notification to Microsoft teams.
this.log.Info("Executing teams notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) func (tn *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
tn.log.Info("Executing teams notification", "ruleId", evalContext.Rule.Id, "notification", tn.Name)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) tn.log.Error("Failed get rule link", "error", err)
return err return err
} }
@@ -108,7 +112,7 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
"name": "View Rule", "name": "View Rule",
"targets": []map[string]interface{}{ "targets": []map[string]interface{}{
{ {
"os": "default", "uri": ruleUrl, "os": "default", "uri": ruleURL,
}, },
}, },
}, },
@@ -126,10 +130,10 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
} }
data, _ := json.Marshal(&body) data, _ := json.Marshal(&body)
cmd := &models.SendWebhookSync{Url: this.Url, Body: string(data)} cmd := &models.SendWebhookSync{Url: tn.URL, Body: string(data)}
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send teams notification", "error", err, "webhook", this.Name) tn.log.Error("Failed to send teams notification", "error", err, "webhook", tn.Name)
return err return err
} }
@@ -45,7 +45,7 @@ func TestTeamsNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(teamsNotifier.Name, ShouldEqual, "ops") So(teamsNotifier.Name, ShouldEqual, "ops")
So(teamsNotifier.Type, ShouldEqual, "teams") So(teamsNotifier.Type, ShouldEqual, "teams")
So(teamsNotifier.Url, ShouldEqual, "http://google.com") So(teamsNotifier.URL, ShouldEqual, "http://google.com")
}) })
Convey("from settings with Recipient and Mention", func() { Convey("from settings with Recipient and Mention", func() {
@@ -67,9 +67,8 @@ func TestTeamsNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(teamsNotifier.Name, ShouldEqual, "ops") So(teamsNotifier.Name, ShouldEqual, "ops")
So(teamsNotifier.Type, ShouldEqual, "teams") So(teamsNotifier.Type, ShouldEqual, "teams")
So(teamsNotifier.Url, ShouldEqual, "http://google.com") So(teamsNotifier.URL, ShouldEqual, "http://google.com")
}) })
}) })
}) })
} }
+33 -29
View File
@@ -18,7 +18,7 @@ const (
) )
var ( var (
telegramApiUrl = "https://api.telegram.org/bot%s/%s" telegramAPIURL = "https://api.telegram.org/bot%s/%s"
) )
func init() { func init() {
@@ -52,6 +52,8 @@ func init() {
} }
// TelegramNotifier is responsible for sending
// alert notifications to Telegram.
type TelegramNotifier struct { type TelegramNotifier struct {
NotifierBase NotifierBase
BotToken string BotToken string
@@ -60,50 +62,51 @@ type TelegramNotifier struct {
log log.Logger log log.Logger
} }
// NewTelegramNotifier is the constructor for the Telegram notifier
func NewTelegramNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewTelegramNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
if model.Settings == nil { if model.Settings == nil {
return nil, alerting.ValidationError{Reason: "No Settings Supplied"} return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
} }
botToken := model.Settings.Get("bottoken").MustString() botToken := model.Settings.Get("bottoken").MustString()
chatId := model.Settings.Get("chatid").MustString() chatID := model.Settings.Get("chatid").MustString()
uploadImage := model.Settings.Get("uploadImage").MustBool() uploadImage := model.Settings.Get("uploadImage").MustBool()
if botToken == "" { if botToken == "" {
return nil, alerting.ValidationError{Reason: "Could not find Bot Token in settings"} return nil, alerting.ValidationError{Reason: "Could not find Bot Token in settings"}
} }
if chatId == "" { if chatID == "" {
return nil, alerting.ValidationError{Reason: "Could not find Chat Id in settings"} return nil, alerting.ValidationError{Reason: "Could not find Chat Id in settings"}
} }
return &TelegramNotifier{ return &TelegramNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
BotToken: botToken, BotToken: botToken,
ChatID: chatId, ChatID: chatID,
UploadImage: uploadImage, UploadImage: uploadImage,
log: log.New("alerting.notifier.telegram"), log: log.New("alerting.notifier.telegram"),
}, nil }, nil
} }
func (this *TelegramNotifier) buildMessage(evalContext *alerting.EvalContext, sendImageInline bool) *models.SendWebhookSync { func (tn *TelegramNotifier) buildMessage(evalContext *alerting.EvalContext, sendImageInline bool) *models.SendWebhookSync {
if sendImageInline { if sendImageInline {
cmd, err := this.buildMessageInlineImage(evalContext) cmd, err := tn.buildMessageInlineImage(evalContext)
if err == nil { if err == nil {
return cmd return cmd
} }
this.log.Error("Could not generate Telegram message with inline image.", "err", err) tn.log.Error("Could not generate Telegram message with inline image.", "err", err)
} }
return this.buildMessageLinkedImage(evalContext) return tn.buildMessageLinkedImage(evalContext)
} }
func (this *TelegramNotifier) buildMessageLinkedImage(evalContext *alerting.EvalContext) *models.SendWebhookSync { func (tn *TelegramNotifier) buildMessageLinkedImage(evalContext *alerting.EvalContext) *models.SendWebhookSync {
message := fmt.Sprintf("<b>%s</b>\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message) message := fmt.Sprintf("<b>%s</b>\nState: %s\nMessage: %s\n", evalContext.GetNotificationTitle(), evalContext.Rule.Name, evalContext.Rule.Message)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err == nil { if err == nil {
message = message + fmt.Sprintf("URL: %s\n", ruleUrl) message = message + fmt.Sprintf("URL: %s\n", ruleURL)
} }
if evalContext.ImagePublicUrl != "" { if evalContext.ImagePublicUrl != "" {
@@ -115,14 +118,14 @@ func (this *TelegramNotifier) buildMessageLinkedImage(evalContext *alerting.Eval
message = message + fmt.Sprintf("\n<i>Metrics:</i>%s", metrics) message = message + fmt.Sprintf("\n<i>Metrics:</i>%s", metrics)
} }
cmd := this.generateTelegramCmd(message, "text", "sendMessage", func(w *multipart.Writer) { cmd := tn.generateTelegramCmd(message, "text", "sendMessage", func(w *multipart.Writer) {
fw, _ := w.CreateFormField("parse_mode") fw, _ := w.CreateFormField("parse_mode")
fw.Write([]byte("html")) fw.Write([]byte("html"))
}) })
return cmd return cmd
} }
func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.EvalContext) (*models.SendWebhookSync, error) { func (tn *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.EvalContext) (*models.SendWebhookSync, error) {
var imageFile *os.File var imageFile *os.File
var err error var err error
@@ -130,7 +133,7 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval
defer func() { defer func() {
err := imageFile.Close() err := imageFile.Close()
if err != nil { if err != nil {
this.log.Error("Could not close Telegram inline image.", "err", err) tn.log.Error("Could not close Telegram inline image.", "err", err)
} }
}() }()
@@ -138,27 +141,27 @@ func (this *TelegramNotifier) buildMessageInlineImage(evalContext *alerting.Eval
return nil, err return nil, err
} }
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
return nil, err return nil, err
} }
metrics := generateMetricsMessage(evalContext) metrics := generateMetricsMessage(evalContext)
message := generateImageCaption(evalContext, ruleUrl, metrics) message := generateImageCaption(evalContext, ruleURL, metrics)
cmd := this.generateTelegramCmd(message, "caption", "sendPhoto", func(w *multipart.Writer) { cmd := tn.generateTelegramCmd(message, "caption", "sendPhoto", func(w *multipart.Writer) {
fw, _ := w.CreateFormFile("photo", evalContext.ImageOnDiskPath) fw, _ := w.CreateFormFile("photo", evalContext.ImageOnDiskPath)
io.Copy(fw, imageFile) io.Copy(fw, imageFile)
}) })
return cmd, nil return cmd, nil
} }
func (this *TelegramNotifier) generateTelegramCmd(message string, messageField string, apiAction string, extraConf func(writer *multipart.Writer)) *models.SendWebhookSync { func (tn *TelegramNotifier) generateTelegramCmd(message string, messageField string, apiAction string, extraConf func(writer *multipart.Writer)) *models.SendWebhookSync {
var body bytes.Buffer var body bytes.Buffer
w := multipart.NewWriter(&body) w := multipart.NewWriter(&body)
fw, _ := w.CreateFormField("chat_id") fw, _ := w.CreateFormField("chat_id")
fw.Write([]byte(this.ChatID)) fw.Write([]byte(tn.ChatID))
fw, _ = w.CreateFormField(messageField) fw, _ = w.CreateFormField(messageField)
fw.Write([]byte(message)) fw.Write([]byte(message))
@@ -167,8 +170,8 @@ func (this *TelegramNotifier) generateTelegramCmd(message string, messageField s
w.Close() w.Close()
this.log.Info("Sending telegram notification", "chat_id", this.ChatID, "bot_token", this.BotToken, "apiAction", apiAction) tn.log.Info("Sending telegram notification", "chat_id", tn.ChatID, "bot_token", tn.BotToken, "apiAction", apiAction)
url := fmt.Sprintf(telegramApiUrl, this.BotToken, apiAction) url := fmt.Sprintf(telegramAPIURL, tn.BotToken, apiAction)
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: url, Url: url,
@@ -193,7 +196,7 @@ func generateMetricsMessage(evalContext *alerting.EvalContext) string {
return metrics return metrics
} }
func generateImageCaption(evalContext *alerting.EvalContext, ruleUrl string, metrics string) string { func generateImageCaption(evalContext *alerting.EvalContext, ruleURL string, metrics string) string {
message := evalContext.GetNotificationTitle() message := evalContext.GetNotificationTitle()
if len(evalContext.Rule.Message) > 0 { if len(evalContext.Rule.Message) > 0 {
@@ -205,8 +208,8 @@ func generateImageCaption(evalContext *alerting.EvalContext, ruleUrl string, met
} }
if len(ruleUrl) > 0 { if len(ruleURL) > 0 {
urlLine := fmt.Sprintf("\nURL: %s", ruleUrl) urlLine := fmt.Sprintf("\nURL: %s", ruleURL)
message = appendIfPossible(message, urlLine, captionLengthLimit) message = appendIfPossible(message, urlLine, captionLengthLimit)
} }
@@ -226,16 +229,17 @@ func appendIfPossible(message string, extra string, sizeLimit int) string {
return message return message
} }
func (this *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify send an alert notification to Telegram.
func (tn *TelegramNotifier) Notify(evalContext *alerting.EvalContext) error {
var cmd *models.SendWebhookSync var cmd *models.SendWebhookSync
if evalContext.ImagePublicUrl == "" && this.UploadImage { if evalContext.ImagePublicUrl == "" && tn.UploadImage {
cmd = this.buildMessage(evalContext, true) cmd = tn.buildMessage(evalContext, true)
} else { } else {
cmd = this.buildMessage(evalContext, false) cmd = tn.buildMessage(evalContext, false)
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send webhook", "error", err, "webhook", this.Name) tn.log.Error("Failed to send webhook", "error", err, "webhook", tn.Name)
return err return err
} }
@@ -68,6 +68,8 @@ func init() {
} }
// ThreemaNotifier is responsible for sending
// alert notifications to Threema.
type ThreemaNotifier struct { type ThreemaNotifier struct {
NotifierBase NotifierBase
GatewayID string GatewayID string
@@ -76,6 +78,7 @@ type ThreemaNotifier struct {
log log.Logger log log.Logger
} }
// NewThreemaNotifier is the constructor for the Threema notifer
func NewThreemaNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewThreemaNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
if model.Settings == nil { if model.Settings == nil {
return nil, alerting.ValidationError{Reason: "No Settings Supplied"} return nil, alerting.ValidationError{Reason: "No Settings Supplied"}
@@ -114,6 +117,7 @@ func NewThreemaNotifier(model *models.AlertNotification) (alerting.Notifier, err
}, nil }, nil
} }
// Notify send an alert notification to Threema
func (notifier *ThreemaNotifier) Notify(evalContext *alerting.EvalContext) error { func (notifier *ThreemaNotifier) Notify(evalContext *alerting.EvalContext) error {
notifier.log.Info("Sending alert notification from", "threema_id", notifier.GatewayID) notifier.log.Info("Sending alert notification from", "threema_id", notifier.GatewayID)
notifier.log.Info("Sending alert notification to", "threema_id", notifier.RecipientID) notifier.log.Info("Sending alert notification to", "threema_id", notifier.RecipientID)
@@ -113,7 +113,6 @@ func TestThreemaNotifier(t *testing.T) {
So(not, ShouldBeNil) So(not, ShouldBeNil)
So(err.(alerting.ValidationError).Reason, ShouldEqual, "Invalid Threema Recipient ID: Must be 8 characters long") So(err.(alerting.ValidationError).Reason, ShouldEqual, "Invalid Threema Recipient ID: Must be 8 characters long")
}) })
}) })
}) })
} }
+11 -11
View File
@@ -14,7 +14,7 @@ import (
// AlertStateCritical - Victorops uses "CRITICAL" string to indicate "Alerting" state // AlertStateCritical - Victorops uses "CRITICAL" string to indicate "Alerting" state
const AlertStateCritical = "CRITICAL" const AlertStateCritical = "CRITICAL"
const AlertStateRecovery = "RECOVERY" const alertStateRecovery = "RECOVERY"
func init() { func init() {
alerting.RegisterNotifier(&alerting.NotifierPlugin{ alerting.RegisterNotifier(&alerting.NotifierPlugin{
@@ -69,17 +69,17 @@ type VictoropsNotifier struct {
} }
// Notify sends notification to Victorops via POST to URL endpoint // Notify sends notification to Victorops via POST to URL endpoint
func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error { func (vn *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
this.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", this.Name) vn.log.Info("Executing victorops notification", "ruleId", evalContext.Rule.Id, "notification", vn.Name)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err != nil { if err != nil {
this.log.Error("Failed get rule link", "error", err) vn.log.Error("Failed get rule link", "error", err)
return err return err
} }
if evalContext.Rule.State == models.AlertStateOK && !this.AutoResolve { if evalContext.Rule.State == models.AlertStateOK && !vn.AutoResolve {
this.log.Info("Not alerting VictorOps", "state", evalContext.Rule.State, "auto resolve", this.AutoResolve) vn.log.Info("Not alerting VictorOps", "state", evalContext.Rule.State, "auto resolve", vn.AutoResolve)
return nil return nil
} }
@@ -89,7 +89,7 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
} }
if evalContext.Rule.State == models.AlertStateOK { if evalContext.Rule.State == models.AlertStateOK {
messageType = AlertStateRecovery messageType = alertStateRecovery
} }
fields := make(map[string]interface{}, 0) fields := make(map[string]interface{}, 0)
@@ -109,7 +109,7 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
bodyJSON.Set("state_start_time", evalContext.StartTime.Unix()) bodyJSON.Set("state_start_time", evalContext.StartTime.Unix())
bodyJSON.Set("state_message", evalContext.Rule.Message) bodyJSON.Set("state_message", evalContext.Rule.Message)
bodyJSON.Set("monitoring_tool", "Grafana v"+setting.BuildVersion) bodyJSON.Set("monitoring_tool", "Grafana v"+setting.BuildVersion)
bodyJSON.Set("alert_url", ruleUrl) bodyJSON.Set("alert_url", ruleURL)
bodyJSON.Set("metrics", fields) bodyJSON.Set("metrics", fields)
if evalContext.Error != nil { if evalContext.Error != nil {
@@ -121,10 +121,10 @@ func (this *VictoropsNotifier) Notify(evalContext *alerting.EvalContext) error {
} }
data, _ := bodyJSON.MarshalJSON() data, _ := bodyJSON.MarshalJSON()
cmd := &models.SendWebhookSync{Url: this.URL, Body: string(data)} cmd := &models.SendWebhookSync{Url: vn.URL, Body: string(data)}
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send Victorops notification", "error", err, "webhook", this.Name) vn.log.Error("Failed to send Victorops notification", "error", err, "webhook", vn.Name)
return err return err
} }
+19 -13
View File
@@ -40,6 +40,8 @@ func init() {
} }
// NewWebHookNotifier is the constructor for
// the WebHook notifier.
func NewWebHookNotifier(model *models.AlertNotification) (alerting.Notifier, error) { func NewWebHookNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
url := model.Settings.Get("url").MustString() url := model.Settings.Get("url").MustString()
if url == "" { if url == "" {
@@ -48,25 +50,29 @@ func NewWebHookNotifier(model *models.AlertNotification) (alerting.Notifier, err
return &WebhookNotifier{ return &WebhookNotifier{
NotifierBase: NewNotifierBase(model), NotifierBase: NewNotifierBase(model),
Url: url, URL: url,
User: model.Settings.Get("username").MustString(), User: model.Settings.Get("username").MustString(),
Password: model.Settings.Get("password").MustString(), Password: model.Settings.Get("password").MustString(),
HttpMethod: model.Settings.Get("httpMethod").MustString("POST"), HTTPMethod: model.Settings.Get("httpMethod").MustString("POST"),
log: log.New("alerting.notifier.webhook"), log: log.New("alerting.notifier.webhook"),
}, nil }, nil
} }
// WebhookNotifier is responsible for sending
// alert notifications as webhooks.
type WebhookNotifier struct { type WebhookNotifier struct {
NotifierBase NotifierBase
Url string URL string
User string User string
Password string Password string
HttpMethod string HTTPMethod string
log log.Logger log log.Logger
} }
func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error { // Notify send alert notifications as
this.log.Info("Sending webhook") // webhook as http requests.
func (wn *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error {
wn.log.Info("Sending webhook")
bodyJSON := simplejson.New() bodyJSON := simplejson.New()
bodyJSON.Set("title", evalContext.GetNotificationTitle()) bodyJSON.Set("title", evalContext.GetNotificationTitle())
@@ -75,9 +81,9 @@ func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error {
bodyJSON.Set("state", evalContext.Rule.State) bodyJSON.Set("state", evalContext.Rule.State)
bodyJSON.Set("evalMatches", evalContext.EvalMatches) bodyJSON.Set("evalMatches", evalContext.EvalMatches)
ruleUrl, err := evalContext.GetRuleUrl() ruleURL, err := evalContext.GetRuleUrl()
if err == nil { if err == nil {
bodyJSON.Set("ruleUrl", ruleUrl) bodyJSON.Set("ruleUrl", ruleURL)
} }
if evalContext.ImagePublicUrl != "" { if evalContext.ImagePublicUrl != "" {
@@ -91,15 +97,15 @@ func (this *WebhookNotifier) Notify(evalContext *alerting.EvalContext) error {
body, _ := bodyJSON.MarshalJSON() body, _ := bodyJSON.MarshalJSON()
cmd := &models.SendWebhookSync{ cmd := &models.SendWebhookSync{
Url: this.Url, Url: wn.URL,
User: this.User, User: wn.User,
Password: this.Password, Password: wn.Password,
Body: string(body), Body: string(body),
HttpMethod: this.HttpMethod, HttpMethod: wn.HTTPMethod,
} }
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
this.log.Error("Failed to send webhook", "error", err, "webhook", this.Name) wn.log.Error("Failed to send webhook", "error", err, "webhook", wn.Name)
return err return err
} }
@@ -45,7 +45,7 @@ func TestWebhookNotifier(t *testing.T) {
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(webhookNotifier.Name, ShouldEqual, "ops") So(webhookNotifier.Name, ShouldEqual, "ops")
So(webhookNotifier.Type, ShouldEqual, "webhook") So(webhookNotifier.Type, ShouldEqual, "webhook")
So(webhookNotifier.Url, ShouldEqual, "http://google.com") So(webhookNotifier.URL, ShouldEqual, "http://google.com")
}) })
}) })
}) })