package notifiers import ( "bytes" "io" "mime/multipart" "os" "strconv" "strings" "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/log" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/setting" ) func init() { alerting.RegisterNotifier(&alerting.NotifierPlugin{ Type: "discord", Name: "Discord", Description: "Sends notifications to Discord", Factory: NewDiscordNotifier, OptionsTemplate: `

Discord settings

Webhook URL
`, }) } func NewDiscordNotifier(model *m.AlertNotification) (alerting.Notifier, error) { url := model.Settings.Get("url").MustString() if url == "" { return nil, alerting.ValidationError{Reason: "Could not find webhook url property in settings"} } return &DiscordNotifier{ NotifierBase: NewNotifierBase(model), WebhookURL: url, log: log.New("alerting.notifier.discord"), }, nil } type DiscordNotifier struct { NotifierBase WebhookURL string log log.Logger } func (this *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error { this.log.Info("Sending alert notification to", "webhook_url", this.WebhookURL) ruleUrl, err := evalContext.GetRuleUrl() if err != nil { this.log.Error("Failed get rule link", "error", err) return err } bodyJSON := simplejson.New() bodyJSON.Set("username", "Grafana") fields := make([]map[string]interface{}, 0) for _, evt := range evalContext.EvalMatches { fields = append(fields, map[string]interface{}{ "name": evt.Metric, "value": evt.Value.FullString(), "inline": true, }) } footer := map[string]interface{}{ "text": "Grafana v" + setting.BuildVersion, "icon_url": "https://grafana.com/assets/img/fav32.png", } color, _ := strconv.ParseInt(strings.TrimLeft(evalContext.GetStateModel().Color, "#"), 16, 0) embed := simplejson.New() embed.Set("title", evalContext.GetNotificationTitle()) //Discord takes integer for color embed.Set("color", color) embed.Set("url", ruleUrl) embed.Set("description", evalContext.Rule.Message) embed.Set("type", "rich") embed.Set("fields", fields) embed.Set("footer", footer) var image map[string]interface{} var embeddedImage = false if evalContext.ImagePublicUrl != "" { image = map[string]interface{}{ "url": evalContext.ImagePublicUrl, } embed.Set("image", image) } else { image = map[string]interface{}{ "url": "attachment://graph.png", } embed.Set("image", image) embeddedImage = true } bodyJSON.Set("embeds", []interface{}{embed}) json, _ := bodyJSON.MarshalJSON() content_type := "application/json" var body []byte if embeddedImage { var b bytes.Buffer w := multipart.NewWriter(&b) f, err := os.Open(evalContext.ImageOnDiskPath) if err != nil { this.log.Error("Can't open graph file", err) return err } defer f.Close() fw, err := w.CreateFormField("payload_json") if err != nil { return err } if _, err = fw.Write([]byte(string(json))); err != nil { return err } fw, err = w.CreateFormFile("file", "graph.png") if err != nil { return err } if _, err = io.Copy(fw, f); err != nil { return err } w.Close() body = b.Bytes() content_type = w.FormDataContentType() } else { body = json } cmd := &m.SendWebhookSync{ Url: this.WebhookURL, Body: string(body), HttpMethod: "POST", ContentType: content_type, } if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil { this.log.Error("Failed to send notification to Discord", "error", err) return err } return nil }