1package notifiers 2 3import ( 4 "context" 5 "strconv" 6 "strings" 7 8 "github.com/grafana/grafana/pkg/bus" 9 "github.com/grafana/grafana/pkg/components/simplejson" 10 "github.com/grafana/grafana/pkg/infra/log" 11 "github.com/grafana/grafana/pkg/models" 12 "github.com/grafana/grafana/pkg/services/alerting" 13 "github.com/grafana/grafana/pkg/setting" 14) 15 16func init() { 17 alerting.RegisterNotifier(&alerting.NotifierPlugin{ 18 Type: "sensu", 19 Name: "Sensu", 20 Description: "Sends HTTP POST request to a Sensu API", 21 Heading: "Sensu settings", 22 Factory: NewSensuNotifier, 23 Options: []alerting.NotifierOption{ 24 { 25 Label: "Url", 26 Element: alerting.ElementTypeInput, 27 InputType: alerting.InputTypeText, 28 Placeholder: "http://sensu-api.local:4567/results", 29 PropertyName: "url", 30 Required: true, 31 }, 32 { 33 Label: "Source", 34 Element: alerting.ElementTypeInput, 35 InputType: alerting.InputTypeText, 36 Description: "If empty rule id will be used", 37 PropertyName: "source", 38 }, 39 { 40 Label: "Handler", 41 Element: alerting.ElementTypeInput, 42 InputType: alerting.InputTypeText, 43 Placeholder: "default", 44 PropertyName: "handler", 45 }, 46 { 47 Label: "Username", 48 Element: alerting.ElementTypeInput, 49 InputType: alerting.InputTypeText, 50 PropertyName: "username", 51 }, 52 { 53 Label: "Password", 54 Element: alerting.ElementTypeInput, 55 InputType: alerting.InputTypePassword, 56 PropertyName: "passsword ", 57 Secure: true, 58 }, 59 }, 60 }) 61} 62 63// NewSensuNotifier is the constructor for the Sensu Notifier. 64func NewSensuNotifier(model *models.AlertNotification, fn alerting.GetDecryptedValueFn) (alerting.Notifier, error) { 65 url := model.Settings.Get("url").MustString() 66 if url == "" { 67 return nil, alerting.ValidationError{Reason: "Could not find url property in settings"} 68 } 69 70 return &SensuNotifier{ 71 NotifierBase: NewNotifierBase(model), 72 URL: url, 73 User: model.Settings.Get("username").MustString(), 74 Source: model.Settings.Get("source").MustString(), 75 Password: fn(context.Background(), model.SecureSettings, "password", model.Settings.Get("password").MustString(), setting.SecretKey), 76 Handler: model.Settings.Get("handler").MustString(), 77 log: log.New("alerting.notifier.sensu"), 78 }, nil 79} 80 81// SensuNotifier is responsible for sending 82// alert notifications to Sensu. 83type SensuNotifier struct { 84 NotifierBase 85 URL string 86 Source string 87 User string 88 Password string 89 Handler string 90 log log.Logger 91} 92 93// Notify send alert notification to Sensu 94func (sn *SensuNotifier) Notify(evalContext *alerting.EvalContext) error { 95 sn.log.Info("Sending sensu result") 96 97 bodyJSON := simplejson.New() 98 bodyJSON.Set("ruleId", evalContext.Rule.ID) 99 // Sensu alerts cannot have spaces in them 100 bodyJSON.Set("name", strings.ReplaceAll(evalContext.Rule.Name, " ", "_")) 101 // Sensu alerts require a source. We set it to the user-specified value (optional), 102 // else we fallback and use the grafana ruleID. 103 if sn.Source != "" { 104 bodyJSON.Set("source", sn.Source) 105 } else { 106 bodyJSON.Set("source", "grafana_rule_"+strconv.FormatInt(evalContext.Rule.ID, 10)) 107 } 108 // Finally, sensu expects an output 109 // We set it to a default output 110 bodyJSON.Set("output", "Grafana Metric Condition Met") 111 bodyJSON.Set("evalMatches", evalContext.EvalMatches) 112 113 switch evalContext.Rule.State { 114 case "alerting": 115 bodyJSON.Set("status", 2) 116 case "no_data": 117 bodyJSON.Set("status", 1) 118 default: 119 bodyJSON.Set("status", 0) 120 } 121 122 if sn.Handler != "" { 123 bodyJSON.Set("handler", sn.Handler) 124 } 125 126 ruleURL, err := evalContext.GetRuleURL() 127 if err == nil { 128 bodyJSON.Set("ruleUrl", ruleURL) 129 } 130 131 if sn.NeedsImage() && evalContext.ImagePublicURL != "" { 132 bodyJSON.Set("imageUrl", evalContext.ImagePublicURL) 133 } 134 135 if evalContext.Rule.Message != "" { 136 bodyJSON.Set("output", evalContext.Rule.Message) 137 } 138 139 body, _ := bodyJSON.MarshalJSON() 140 141 cmd := &models.SendWebhookSync{ 142 Url: sn.URL, 143 User: sn.User, 144 Password: sn.Password, 145 Body: string(body), 146 HttpMethod: "POST", 147 } 148 149 if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { 150 sn.log.Error("Failed to send sensu event", "error", err, "sensu", sn.Name) 151 return err 152 } 153 154 return nil 155} 156