1package channels
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7	"strings"
8
9	"github.com/grafana/grafana/pkg/bus"
10	"github.com/grafana/grafana/pkg/infra/log"
11	"github.com/grafana/grafana/pkg/models"
12	"github.com/prometheus/alertmanager/template"
13	"github.com/prometheus/alertmanager/types"
14	"github.com/prometheus/common/model"
15)
16
17type SensuGoNotifier struct {
18	*Base
19	log  log.Logger
20	tmpl *template.Template
21
22	URL       string
23	Entity    string
24	Check     string
25	Namespace string
26	Handler   string
27	APIKey    string
28	Message   string
29}
30
31// NewSensuGoNotifier is the constructor for the SensuGo notifier
32func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template, fn GetDecryptedValueFn) (*SensuGoNotifier, error) {
33	if model.Settings == nil {
34		return nil, receiverInitError{Cfg: *model, Reason: "no settings supplied"}
35	}
36	if model.SecureSettings == nil {
37		return nil, receiverInitError{Cfg: *model, Reason: "no secure settings supplied"}
38	}
39	url := model.Settings.Get("url").MustString()
40	if url == "" {
41		return nil, receiverInitError{Cfg: *model, Reason: "could not find URL property in settings"}
42	}
43
44	apikey := fn(context.Background(), model.SecureSettings, "apikey", model.Settings.Get("apikey").MustString())
45	if apikey == "" {
46		return nil, receiverInitError{Cfg: *model, Reason: "could not find the API key property in settings"}
47	}
48
49	return &SensuGoNotifier{
50		Base: NewBase(&models.AlertNotification{
51			Uid:                   model.UID,
52			Name:                  model.Name,
53			Type:                  model.Type,
54			DisableResolveMessage: model.DisableResolveMessage,
55			Settings:              model.Settings,
56			SecureSettings:        model.SecureSettings,
57		}),
58		URL:       url,
59		Entity:    model.Settings.Get("entity").MustString(),
60		Check:     model.Settings.Get("check").MustString(),
61		Namespace: model.Settings.Get("namespace").MustString(),
62		Handler:   model.Settings.Get("handler").MustString(),
63		APIKey:    apikey,
64		Message:   model.Settings.Get("message").MustString(`{{ template "default.message" .}}`),
65		log:       log.New("alerting.notifier.sensugo"),
66		tmpl:      t,
67	}, nil
68}
69
70// Notify sends an alert notification to Sensu Go
71func (sn *SensuGoNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, error) {
72	sn.log.Debug("Sending Sensu Go result")
73
74	var tmplErr error
75	tmpl, _ := TmplText(ctx, sn.tmpl, as, sn.log, &tmplErr)
76
77	// Sensu Go alerts require an entity and a check. We set it to the user-specified
78	// value (optional), else we fallback and use the grafana rule anme  and ruleID.
79	entity := tmpl(sn.Entity)
80	if entity == "" {
81		entity = "default"
82	}
83
84	check := tmpl(sn.Check)
85	if check == "" {
86		check = "default"
87	}
88
89	alerts := types.Alerts(as...)
90	status := 0
91	if alerts.Status() == model.AlertFiring {
92		// TODO figure out about NoData old state (we used to send status 1 in that case)
93		status = 2
94	}
95
96	namespace := tmpl(sn.Namespace)
97	if namespace == "" {
98		namespace = "default"
99	}
100
101	var handlers []string
102	if sn.Handler != "" {
103		handlers = []string{tmpl(sn.Handler)}
104	}
105
106	ruleURL := joinUrlPath(sn.tmpl.ExternalURL.String(), "/alerting/list", sn.log)
107	bodyMsgType := map[string]interface{}{
108		"entity": map[string]interface{}{
109			"metadata": map[string]interface{}{
110				"name":      entity,
111				"namespace": namespace,
112			},
113		},
114		"check": map[string]interface{}{
115			"metadata": map[string]interface{}{
116				"name": check,
117				"labels": map[string]string{
118					"ruleURL": ruleURL,
119				},
120			},
121			"output":   tmpl(sn.Message),
122			"issued":   timeNow().Unix(),
123			"interval": 86400,
124			"status":   status,
125			"handlers": handlers,
126		},
127		"ruleUrl": ruleURL,
128	}
129
130	if tmplErr != nil {
131		sn.log.Warn("failed to template sensugo message", "err", tmplErr.Error())
132	}
133
134	body, err := json.Marshal(bodyMsgType)
135	if err != nil {
136		return false, err
137	}
138
139	cmd := &models.SendWebhookSync{
140		Url:        fmt.Sprintf("%s/api/core/v2/namespaces/%s/events", strings.TrimSuffix(sn.URL, "/"), namespace),
141		Body:       string(body),
142		HttpMethod: "POST",
143		HttpHeader: map[string]string{
144			"Content-Type":  "application/json",
145			"Authorization": fmt.Sprintf("Key %s", sn.APIKey),
146		},
147	}
148	if err := bus.DispatchCtx(ctx, cmd); err != nil {
149		sn.log.Error("Failed to send Sensu Go event", "error", err, "sensugo", sn.Name)
150		return false, err
151	}
152
153	return true, nil
154}
155
156func (sn *SensuGoNotifier) SendResolved() bool {
157	return !sn.GetDisableResolveMessage()
158}
159