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/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/components/simplejson"
)
const defaultDingdingMsgType = "link"
type dingDingSettings struct {
URL string
MessageType string
Title string
Message string
URL string `json:"url,omitempty" yaml:"url,omitempty"`
MessageType string `json:"msgType,omitempty" yaml:"msgType,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
}
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 {
return nil, err
return nil, fmt.Errorf("failed to unmarshal settings: %w", err)
}
URL := settings.Get("url").MustString()
if URL == "" {
if settings.URL == "" {
return nil, errors.New("could not find url property in settings")
}
return &dingDingSettings{
URL: URL,
MessageType: settings.Get("msgType").MustString(defaultDingdingMsgType),
Title: settings.Get("title").MustString(channels.DefaultMessageTitleEmbed),
Message: settings.Get("message").MustString(channels.DefaultMessageEmbed),
}, nil
if settings.MessageType == "" {
settings.MessageType = defaultDingdingMsgType
}
if settings.Title == "" {
settings.Title = channels.DefaultMessageTitleEmbed
}
if settings.Message == "" {
settings.Message = channels.DefaultMessageEmbed
}
return &settings, nil
}
func DingDingFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error) {

View File

@ -80,7 +80,7 @@ func TestDingdingNotifier(t *testing.T) {
expMsgError: nil,
}, {
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{
{
Alert: model.Alert{

View File

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

View File

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

View File

@ -2,7 +2,9 @@ package channels
import (
"context"
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/prometheus/alertmanager/notify"
@ -24,14 +26,36 @@ type KafkaNotifier struct {
images channels.ImageStore
ns channels.WebhookSender
tmpl *template.Template
settings kafkaSettings
settings *kafkaSettings
}
type kafkaSettings struct {
Endpoint string
Topic string
Description string
Details string
Endpoint string `json:"kafkaRestProxy,omitempty" yaml:"kafkaRestProxy,omitempty"`
Topic string `json:"kafkaTopic,omitempty" yaml:"kafkaTopic,omitempty"`
Description string `json:"description,omitempty" yaml:"description,omitempty"`
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) {
@ -47,20 +71,10 @@ func KafkaFactory(fc channels.FactoryConfig) (channels.NotificationChannel, erro
// newKafkaNotifier is the constructor function for the Kafka notifier.
func newKafkaNotifier(fc channels.FactoryConfig) (*KafkaNotifier, error) {
settings, err := simplejson.NewJson(fc.Config.Settings)
settings, err := buildKafkaSettings(fc)
if err != nil {
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{
Base: channels.NewBase(fc.Config),
@ -68,7 +82,7 @@ func newKafkaNotifier(fc channels.FactoryConfig) (*KafkaNotifier, error) {
images: fc.ImageStore,
ns: fc.NotificationService,
tmpl: fc.Template,
settings: kafkaSettings{Endpoint: endpoint, Topic: topic, Description: description, Details: details},
settings: settings,
}, nil
}

View File

@ -2,6 +2,7 @@ package channels
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/url"
@ -10,8 +11,6 @@ import (
"github.com/grafana/alerting/alerting/notifier/channels"
"github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types"
"github.com/grafana/grafana/pkg/components/simplejson"
)
var (
@ -25,13 +24,32 @@ type LineNotifier struct {
log channels.Logger
ns channels.WebhookSender
tmpl *template.Template
settings lineSettings
settings *lineSettings
}
type lineSettings struct {
token string
title string
description string
Token string `json:"token,omitempty" yaml:"token,omitempty"`
Title string `json:"title,omitempty" yaml:"title,omitempty"`
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) {
@ -47,23 +65,17 @@ func LineFactory(fc channels.FactoryConfig) (channels.NotificationChannel, error
// newLineNotifier is the constructor for the LINE notifier
func newLineNotifier(fc channels.FactoryConfig) (*LineNotifier, error) {
settings, err := simplejson.NewJson(fc.Config.Settings)
settings, err := buildLineSettings(fc)
if err != nil {
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{
Base: channels.NewBase(fc.Config),
log: fc.Logger,
ns: fc.NotificationService,
tmpl: fc.Template,
settings: lineSettings{token: token, title: title, description: description},
settings: settings,
}, nil
}
@ -80,7 +92,7 @@ func (ln *LineNotifier) Notify(ctx context.Context, as ...*types.Alert) (bool, e
URL: LineNotifyURL,
HTTPMethod: "POST",
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",
},
Body: form.Encode(),
@ -106,9 +118,9 @@ func (ln *LineNotifier) buildMessage(ctx context.Context, as ...*types.Alert) st
body := fmt.Sprintf(
"%s\n%s\n\n%s",
tmpl(ln.settings.title),
tmpl(ln.settings.Title),
ruleURL,
tmpl(ln.settings.description),
tmpl(ln.settings.Description),
)
if tmplErr != nil {
ln.log.Warn("failed to template Line message", "error", tmplErr.Error())