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