From ec45c9c990b0f8b0376178e05e7371e90aecb07e Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 20 Dec 2022 09:46:13 -0500 Subject: [PATCH] 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 --- .../ngalert/notifier/channels/dingding.go | 34 ++++++----- .../notifier/channels/dingding_test.go | 2 +- .../ngalert/notifier/channels/discord.go | 57 ++++++++++-------- .../ngalert/notifier/channels/googlechat.go | 59 ++++++++++--------- .../ngalert/notifier/channels/kafka.go | 48 +++++++++------ .../ngalert/notifier/channels/line.go | 46 +++++++++------ 6 files changed, 142 insertions(+), 104 deletions(-) diff --git a/pkg/services/ngalert/notifier/channels/dingding.go b/pkg/services/ngalert/notifier/channels/dingding.go index fb92c63b950..b3bed47beec 100644 --- a/pkg/services/ngalert/notifier/channels/dingding.go +++ b/pkg/services/ngalert/notifier/channels/dingding.go @@ -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) { diff --git a/pkg/services/ngalert/notifier/channels/dingding_test.go b/pkg/services/ngalert/notifier/channels/dingding_test.go index 91f172efef7..20e579f3875 100644 --- a/pkg/services/ngalert/notifier/channels/dingding_test.go +++ b/pkg/services/ngalert/notifier/channels/dingding_test.go @@ -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{ diff --git a/pkg/services/ngalert/notifier/channels/discord.go b/pkg/services/ngalert/notifier/channels/discord.go index 34ee56a4229..bf37148d9c0 100644 --- a/pkg/services/ngalert/notifier/channels/discord.go +++ b/pkg/services/ngalert/notifier/channels/discord.go @@ -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. diff --git a/pkg/services/ngalert/notifier/channels/googlechat.go b/pkg/services/ngalert/notifier/channels/googlechat.go index 8679971796a..f75855b7286 100644 --- a/pkg/services/ngalert/notifier/channels/googlechat.go +++ b/pkg/services/ngalert/notifier/channels/googlechat.go @@ -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}}) diff --git a/pkg/services/ngalert/notifier/channels/kafka.go b/pkg/services/ngalert/notifier/channels/kafka.go index 0dcd2670fd7..959a58fc196 100644 --- a/pkg/services/ngalert/notifier/channels/kafka.go +++ b/pkg/services/ngalert/notifier/channels/kafka.go @@ -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 } diff --git a/pkg/services/ngalert/notifier/channels/line.go b/pkg/services/ngalert/notifier/channels/line.go index 11611cef9df..78fee70eb06 100644 --- a/pkg/services/ngalert/notifier/channels/line.go +++ b/pkg/services/ngalert/notifier/channels/line.go @@ -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())