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