mirror of
https://github.com/grafana/grafana.git
synced 2024-11-27 11:20:27 -06:00
caa1314f44
* Build: use golangci-lint as a make command * Since gometalinter was deprecated in favor of golangci-lint so it was replaced by it. Responsibilities held by the gometalinter was moved to golangci-lint * There was some changes in implementation (that was also mentioned in the code comment) between the tools, which uncovered couple errors in the code. Those issues were either solved or disabled by the inline comments * Introduce the golangci-lint config, to make their configuration more manageable * Build: replace backend-lint.sh script with make
201 lines
4.9 KiB
Go
201 lines
4.9 KiB
Go
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/infra/log"
|
|
"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: `
|
|
<h3 class="page-heading">Discord settings</h3>
|
|
<div class="gf-form max-width-30">
|
|
<span class="gf-form-label width-10">Message Content</span>
|
|
<input type="text"
|
|
class="gf-form-input max-width-30"
|
|
ng-model="ctrl.model.settings.content"
|
|
data-placement="right">
|
|
</input>
|
|
<info-popover mode="right-absolute">
|
|
Mention a group using @ or a user using <@ID> when notifying in a channel
|
|
</info-popover>
|
|
</div>
|
|
<div class="gf-form max-width-30">
|
|
<span class="gf-form-label width-10">Webhook URL</span>
|
|
<input type="text" required class="gf-form-input max-width-30" ng-model="ctrl.model.settings.url" placeholder="Discord webhook URL"></input>
|
|
</div>
|
|
`,
|
|
})
|
|
}
|
|
|
|
func newDiscordNotifier(model *models.AlertNotification) (alerting.Notifier, error) {
|
|
content := model.Settings.Get("content").MustString()
|
|
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),
|
|
Content: content,
|
|
WebhookURL: url,
|
|
log: log.New("alerting.notifier.discord"),
|
|
}, nil
|
|
}
|
|
|
|
// DiscordNotifier is responsible for sending alert
|
|
// notifications to discord.
|
|
type DiscordNotifier struct {
|
|
NotifierBase
|
|
Content string
|
|
WebhookURL string
|
|
log log.Logger
|
|
}
|
|
|
|
// Notify send an alert notification to Discord.
|
|
func (dn *DiscordNotifier) Notify(evalContext *alerting.EvalContext) error {
|
|
dn.log.Info("Sending alert notification to", "webhook_url", dn.WebhookURL)
|
|
|
|
ruleURL, err := evalContext.GetRuleURL()
|
|
if err != nil {
|
|
dn.log.Error("Failed get rule link", "error", err)
|
|
return err
|
|
}
|
|
|
|
bodyJSON := simplejson.New()
|
|
bodyJSON.Set("username", "Grafana")
|
|
|
|
if dn.Content != "" {
|
|
bodyJSON.Set("content", dn.Content)
|
|
}
|
|
|
|
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()
|
|
|
|
cmd := &models.SendWebhookSync{
|
|
Url: dn.WebhookURL,
|
|
HttpMethod: "POST",
|
|
ContentType: "application/json",
|
|
}
|
|
|
|
if !embeddedImage {
|
|
cmd.Body = string(json)
|
|
} else {
|
|
err := dn.embedImage(cmd, evalContext.ImageOnDiskPath, json)
|
|
if err != nil {
|
|
dn.log.Error("failed to embed image", "error", err)
|
|
return err
|
|
}
|
|
}
|
|
|
|
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
|
|
dn.log.Error("Failed to send notification to Discord", "error", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (dn *DiscordNotifier) embedImage(cmd *models.SendWebhookSync, imagePath string, existingJSONBody []byte) error {
|
|
f, err := os.Open(imagePath)
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
cmd.Body = string(existingJSONBody)
|
|
return nil
|
|
}
|
|
if !os.IsNotExist(err) {
|
|
return err
|
|
}
|
|
}
|
|
|
|
defer f.Close()
|
|
|
|
var b bytes.Buffer
|
|
w := multipart.NewWriter(&b)
|
|
|
|
fw, err := w.CreateFormField("payload_json")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err = fw.Write([]byte(string(existingJSONBody))); 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()
|
|
|
|
cmd.Body = b.String()
|
|
cmd.ContentType = w.FormDataContentType()
|
|
|
|
return nil
|
|
}
|