Alerting: update dingding, discord, googlechat, kafka, line notifiers to use encoding/json to parse settings (#60542)

also, rename Content to Message to match JSON name for Discord and GoogleChat
This commit is contained in:
Yuri Tseretyan 2022-12-20 09:46:13 -05:00 committed by GitHub
parent 76e23a9fef
commit ec45c9c990
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 142 additions and 104 deletions

View File

@ -10,34 +10,36 @@ import (
"github.com/grafana/alerting/alerting/notifier/channels" "github.com/grafana/alerting/alerting/notifier/channels"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/components/simplejson"
) )
const defaultDingdingMsgType = "link" const defaultDingdingMsgType = "link"
type dingDingSettings struct { type dingDingSettings struct {
URL string URL string `json:"url,omitempty" yaml:"url,omitempty"`
MessageType string MessageType string `json:"msgType,omitempty" yaml:"msgType,omitempty"`
Title string Title string `json:"title,omitempty" yaml:"title,omitempty"`
Message string Message string `json:"message,omitempty" yaml:"message,omitempty"`
} }
func buildDingDingSettings(fc channels.FactoryConfig) (*dingDingSettings, error) { func buildDingDingSettings(fc channels.FactoryConfig) (*dingDingSettings, error) {
settings, err := simplejson.NewJson(fc.Config.Settings) var settings dingDingSettings
err := json.Unmarshal(fc.Config.Settings, &settings)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("failed to unmarshal settings: %w", err)
} }
URL := settings.Get("url").MustString() if settings.URL == "" {
if URL == "" {
return nil, errors.New("could not find url property in settings") return nil, errors.New("could not find url property in settings")
} }
return &dingDingSettings{ if settings.MessageType == "" {
URL: URL, settings.MessageType = defaultDingdingMsgType
MessageType: settings.Get("msgType").MustString(defaultDingdingMsgType), }
Title: settings.Get("title").MustString(channels.DefaultMessageTitleEmbed), if settings.Title == "" {
Message: settings.Get("message").MustString(channels.DefaultMessageEmbed), settings.Title = channels.DefaultMessageTitleEmbed
}, nil }
if settings.Message == "" {
settings.Message = channels.DefaultMessageEmbed
}
return &settings, nil
} }
func DingDingFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) { func DingDingFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) {

View File

@ -80,7 +80,7 @@ func TestDingdingNotifier(t *testing.T) {
expMsgError: nil, expMsgError: nil,
}, { }, {
name: "Default config with one alert and custom title and description", name: "Default config with one alert and custom title and description",
settings: `{"url": "http://localhost", "title": "Alerts firing: {{ len .Alerts.Firing }}", "message": "customMessage"}}`, settings: `{"url": "http://localhost", "title": "Alerts firing: {{ len .Alerts.Firing }}", "message": "customMessage"}`,
alerts: []*types.Alert{ alerts: []*types.Alert{
{ {
Alert: model.Alert{ Alert: model.Alert{

View File

@ -27,15 +27,33 @@ type DiscordNotifier struct {
ns channels.WebhookSender ns channels.WebhookSender
images channels.ImageStore images channels.ImageStore
tmpl *template.Template tmpl *template.Template
settings discordSettings settings *discordSettings
} }
type discordSettings struct { type discordSettings struct {
Title string Title string `json:"title,omitempty" yaml:"title,omitempty"`
Content string Message string `json:"message,omitempty" yaml:"message,omitempty"`
AvatarURL string AvatarURL string `json:"avatar_url,omitempty" yaml:"avatar_url,omitempty"`
WebhookURL string WebhookURL string `json:"url,omitempty" yaml:"url,omitempty"`
UseDiscordUsername bool UseDiscordUsername bool `json:"use_discord_username,omitempty" yaml:"use_discord_username,omitempty"`
}
func buildDiscordSettings(fc channels.FactoryConfig) (*discordSettings, error) {
var settings discordSettings
err := json.Unmarshal(fc.Config.Settings, &settings)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal settings: %w", err)
}
if settings.WebhookURL == "" {
return nil, errors.New("could not find webhook url property in settings")
}
if settings.Title == "" {
settings.Title = channels.DefaultMessageTitleEmbed
}
if settings.Message == "" {
settings.Message = channels.DefaultMessageEmbed
}
return &settings, nil
} }
type discordAttachment struct { type discordAttachment struct {
@ -60,28 +78,17 @@ func DiscordFactory(fc channels.FactoryConfig) (channels.NotificationChannel, er
} }
func newDiscordNotifier(fc channels.FactoryConfig) (*DiscordNotifier, error) { func newDiscordNotifier(fc channels.FactoryConfig) (*DiscordNotifier, error) {
settings, err := simplejson.NewJson(fc.Config.Settings) settings, err := buildDiscordSettings(fc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
dUrl := settings.Get("url").MustString()
if dUrl == "" {
return nil, errors.New("could not find webhook url property in settings")
}
return &DiscordNotifier{ return &DiscordNotifier{
Base: channels.NewBase(fc.Config), Base: channels.NewBase(fc.Config),
log: fc.Logger, log: fc.Logger,
ns: fc.NotificationService, ns: fc.NotificationService,
images: fc.ImageStore, images: fc.ImageStore,
tmpl: fc.Template, tmpl: fc.Template,
settings: discordSettings{ settings: settings,
Title: settings.Get("title").MustString(channels.DefaultMessageTitleEmbed),
Content: settings.Get("message").MustString(channels.DefaultMessageEmbed),
AvatarURL: settings.Get("avatar_url").MustString(),
WebhookURL: dUrl,
UseDiscordUsername: settings.Get("use_discord_username").MustBool(false),
},
}, nil }, nil
} }
@ -97,7 +104,7 @@ func (d DiscordNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool,
var tmplErr error var tmplErr error
tmpl, _ := channels.TmplText(ctx, d.tmpl, as, d.log, &tmplErr) tmpl, _ := channels.TmplText(ctx, d.tmpl, as, d.log, &tmplErr)
bodyJSON.Set("content", tmpl(d.settings.Content)) bodyJSON.Set("content", tmpl(d.settings.Message))
if tmplErr != nil { if tmplErr != nil {
d.log.Warn("failed to template Discord notification content", "error", tmplErr.Error()) d.log.Warn("failed to template Discord notification content", "error", tmplErr.Error())
// Reset tmplErr for templating other fields. // Reset tmplErr for templating other fields.

View File

@ -12,7 +12,6 @@ import (
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -24,13 +23,32 @@ type GoogleChatNotifier struct {
ns channels.WebhookSender ns channels.WebhookSender
images channels.ImageStore images channels.ImageStore
tmpl *template.Template tmpl *template.Template
settings googleChatSettings settings *googleChatSettings
} }
type googleChatSettings struct { type googleChatSettings struct {
URL string URL string `json:"url,omitempty" yaml:"url,omitempty"`
Title string Title string `json:"title,omitempty" yaml:"title,omitempty"`
Content string Message string `json:"message,omitempty" yaml:"message,omitempty"`
}
func buildGoogleChatSettings(fc channels.FactoryConfig) (*googleChatSettings, error) {
var settings googleChatSettings
err := json.Unmarshal(fc.Config.Settings, &settings)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal settings: %w", err)
}
if settings.URL == "" {
return nil, errors.New("could not find url property in settings")
}
if settings.Title == "" {
settings.Title = channels.DefaultMessageTitleEmbed
}
if settings.Message == "" {
settings.Message = channels.DefaultMessageEmbed
}
return &settings, nil
} }
func GoogleChatFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) { func GoogleChatFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) {
@ -45,32 +63,17 @@ func GoogleChatFactory(fc channels.FactoryConfig) (channels.NotificationChannel,
} }
func newGoogleChatNotifier(fc channels.FactoryConfig) (*GoogleChatNotifier, error) { func newGoogleChatNotifier(fc channels.FactoryConfig) (*GoogleChatNotifier, error) {
var settings googleChatSettings settings, err := buildGoogleChatSettings(fc)
err := json.Unmarshal(fc.Config.Settings, &settings)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal settings: %w", err)
}
rawsettings, err := simplejson.NewJson(fc.Config.Settings)
if err != nil { if err != nil {
return nil, err return nil, err
} }
URL := rawsettings.Get("url").MustString()
if URL == "" {
return nil, errors.New("could not find url property in settings")
}
return &GoogleChatNotifier{ return &GoogleChatNotifier{
Base: channels.NewBase(fc.Config), Base: channels.NewBase(fc.Config),
log: fc.Logger, log: fc.Logger,
ns: fc.NotificationService, ns: fc.NotificationService,
images: fc.ImageStore, images: fc.ImageStore,
tmpl: fc.Template, tmpl: fc.Template,
settings: googleChatSettings{ settings: settings,
URL: URL,
Title: rawsettings.Get("title").MustString(channels.DefaultMessageTitleEmbed),
Content: rawsettings.Get("message").MustString(channels.DefaultMessageEmbed),
},
}, nil }, nil
} }
@ -83,7 +86,7 @@ func (gcn *GoogleChatNotifier) Notify(ctx context.Context, as ...*types.Alert) (
var widgets []widget var widgets []widget
if msg := tmpl(gcn.settings.Content); msg != "" { if msg := tmpl(gcn.settings.Message); msg != "" {
// Add a text paragraph widget for the message if there is a message. // Add a text paragraph widget for the message if there is a message.
// Google Chat API doesn't accept an empty text property. // Google Chat API doesn't accept an empty text property.
widgets = append(widgets, textParagraphWidget{Text: text{Text: msg}}) widgets = append(widgets, textParagraphWidget{Text: text{Text: msg}})

View File

@ -2,7 +2,9 @@ package channels
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt"
"strings" "strings"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
@ -24,14 +26,36 @@ type KafkaNotifier struct {
images channels.ImageStore images channels.ImageStore
ns channels.WebhookSender ns channels.WebhookSender
tmpl *template.Template tmpl *template.Template
settings kafkaSettings settings *kafkaSettings
} }
type kafkaSettings struct { type kafkaSettings struct {
Endpoint string Endpoint string `json:"kafkaRestProxy,omitempty" yaml:"kafkaRestProxy,omitempty"`
Topic string Topic string `json:"kafkaTopic,omitempty" yaml:"kafkaTopic,omitempty"`
Description string Description string `json:"description,omitempty" yaml:"description,omitempty"`
Details string Details string `json:"details,omitempty" yaml:"details,omitempty"`
}
func buildKafkaSettings(fc channels.FactoryConfig) (*kafkaSettings, error) {
var settings kafkaSettings
err := json.Unmarshal(fc.Config.Settings, &settings)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal settings: %w", err)
}
if settings.Endpoint == "" {
return nil, errors.New("could not find kafka rest proxy endpoint property in settings")
}
if settings.Topic == "" {
return nil, errors.New("could not find kafka topic property in settings")
}
if settings.Description == "" {
settings.Description = channels.DefaultMessageTitleEmbed
}
if settings.Details == "" {
settings.Details = channels.DefaultMessageEmbed
}
return &settings, nil
} }
func KafkaFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) { func KafkaFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) {
@ -47,20 +71,10 @@ func KafkaFactory(fc channels.FactoryConfig) (channels.NotificationChannel, erro
// newKafkaNotifier is the constructor function for the Kafka notifier. // newKafkaNotifier is the constructor function for the Kafka notifier.
func newKafkaNotifier(fc channels.FactoryConfig) (*KafkaNotifier, error) { func newKafkaNotifier(fc channels.FactoryConfig) (*KafkaNotifier, error) {
settings, err := simplejson.NewJson(fc.Config.Settings) settings, err := buildKafkaSettings(fc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
endpoint := settings.Get("kafkaRestProxy").MustString()
if endpoint == "" {
return nil, errors.New("could not find kafka rest proxy endpoint property in settings")
}
topic := settings.Get("kafkaTopic").MustString()
if topic == "" {
return nil, errors.New("could not find kafka topic property in settings")
}
description := settings.Get("description").MustString(channels.DefaultMessageTitleEmbed)
details := settings.Get("details").MustString(channels.DefaultMessageEmbed)
return &KafkaNotifier{ return &KafkaNotifier{
Base: channels.NewBase(fc.Config), Base: channels.NewBase(fc.Config),
@ -68,7 +82,7 @@ func newKafkaNotifier(fc channels.FactoryConfig) (*KafkaNotifier, error) {
images: fc.ImageStore, images: fc.ImageStore,
ns: fc.NotificationService, ns: fc.NotificationService,
tmpl: fc.Template, tmpl: fc.Template,
settings: kafkaSettings{Endpoint: endpoint, Topic: topic, Description: description, Details: details}, settings: settings,
}, nil }, nil
} }

View File

@ -2,6 +2,7 @@ package channels
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/url" "net/url"
@ -10,8 +11,6 @@ import (
"github.com/grafana/alerting/alerting/notifier/channels" "github.com/grafana/alerting/alerting/notifier/channels"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/components/simplejson"
) )
var ( var (
@ -25,13 +24,32 @@ type LineNotifier struct {
log channels.Logger log channels.Logger
ns channels.WebhookSender ns channels.WebhookSender
tmpl *template.Template tmpl *template.Template
settings lineSettings settings *lineSettings
} }
type lineSettings struct { type lineSettings struct {
token string Token string `json:"token,omitempty" yaml:"token,omitempty"`
title string Title string `json:"title,omitempty" yaml:"title,omitempty"`
description string Description string `json:"description,omitempty" yaml:"description,omitempty"`
}
func buildLineSettings(fc channels.FactoryConfig) (*lineSettings, error) {
var settings lineSettings
err := json.Unmarshal(fc.Config.Settings, &settings)
if err != nil {
return nil, fmt.Errorf("failed to unmarshal settings: %w", err)
}
settings.Token = fc.DecryptFunc(context.Background(), fc.Config.SecureSettings, "token", settings.Token)
if settings.Token == "" {
return nil, errors.New("could not find token in settings")
}
if settings.Title == "" {
settings.Title = channels.DefaultMessageTitleEmbed
}
if settings.Description == "" {
settings.Description = channels.DefaultMessageEmbed
}
return &settings, nil
} }
func LineFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) { func LineFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) {
@ -47,23 +65,17 @@ func LineFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error
// newLineNotifier is the constructor for the LINE notifier // newLineNotifier is the constructor for the LINE notifier
func newLineNotifier(fc channels.FactoryConfig) (*LineNotifier, error) { func newLineNotifier(fc channels.FactoryConfig) (*LineNotifier, error) {
settings, err := simplejson.NewJson(fc.Config.Settings) settings, err := buildLineSettings(fc)
if err != nil { if err != nil {
return nil, err return nil, err
} }
token := fc.DecryptFunc(context.Background(), fc.Config.SecureSettings, "token", settings.Get("token").MustString())
if token == "" {
return nil, errors.New("could not find token in settings")
}
title := settings.Get("title").MustString(channels.DefaultMessageTitleEmbed)
description := settings.Get("description").MustString(channels.DefaultMessageEmbed)
return &LineNotifier{ return &LineNotifier{
Base: channels.NewBase(fc.Config), Base: channels.NewBase(fc.Config),
log: fc.Logger, log: fc.Logger,
ns: fc.NotificationService, ns: fc.NotificationService,
tmpl: fc.Template, tmpl: fc.Template,
settings: lineSettings{token: token, title: title, description: description}, settings: settings,
}, nil }, nil
} }
@ -80,7 +92,7 @@ func (ln *LineNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e
URL: LineNotifyURL, URL: LineNotifyURL,
HTTPMethod: "POST", HTTPMethod: "POST",
HTTPHeader: map[string]string{ HTTPHeader: map[string]string{
"Authorization": fmt.Sprintf("Bearer %s", ln.settings.token), "Authorization": fmt.Sprintf("Bearer %s", ln.settings.Token),
"Content-Type": "application/x-www-form-urlencoded;charset=UTF-8", "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
}, },
Body: form.Encode(), Body: form.Encode(),
@ -106,9 +118,9 @@ func (ln *LineNotifier) buildMessage(ctx context.Context, as ...*types.Alert) st
body := fmt.Sprintf( body := fmt.Sprintf(
"%s\n%s\n\n%s", "%s\n%s\n\n%s",
tmpl(ln.settings.title), tmpl(ln.settings.Title),
ruleURL, ruleURL,
tmpl(ln.settings.description), tmpl(ln.settings.Description),
) )
if tmplErr != nil { if tmplErr != nil {
ln.log.Warn("failed to template Line message", "error", tmplErr.Error()) ln.log.Warn("failed to template Line message", "error", tmplErr.Error())