From 614d424de9ee547fae9af2b3b380f302b2ac011e Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Mon, 13 Feb 2023 19:00:02 -0500 Subject: [PATCH 01/15] Update GrafanaReceiver.Setting to be raw JSON --- notify/receivers.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/notify/receivers.go b/notify/receivers.go index 77dd3dd0..cb9a94e1 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -47,12 +47,12 @@ type InvalidReceiverError struct { } type GrafanaReceiver struct { - UID string `json:"uid"` - Name string `json:"name"` - Type string `json:"type"` - DisableResolveMessage bool `json:"disableResolveMessage"` - Settings map[string]interface{} `json:"settings"` - SecureSettings map[string]string `json:"secureSettings"` + UID string `json:"uid"` + Name string `json:"name"` + Type string `json:"type"` + DisableResolveMessage bool `json:"disableResolveMessage"` + Settings json.RawMessage `json:"settings"` + SecureSettings map[string]string `json:"secureSettings"` } type ConfigReceiver = config.Receiver From 08f941fd9c580994956bd7ecf71b35313c5a9f4f Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 14 Feb 2023 12:48:42 -0500 Subject: [PATCH 02/15] introduce GrafanaReceiverTyped and NotifierConfig --- notify/receivers.go | 194 +++++++++++++++++++++++++++++++++++++++++++ receivers/factory.go | 8 ++ 2 files changed, 202 insertions(+) diff --git a/notify/receivers.go b/notify/receivers.go index cb9a94e1..5edc3aa8 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -2,10 +2,13 @@ package notify import ( "context" + "encoding/base64" + "encoding/json" "errors" "fmt" "net/url" "sort" + "strings" "time" "github.com/prometheus/alertmanager/config" @@ -13,6 +16,27 @@ import ( "github.com/prometheus/alertmanager/types" "github.com/prometheus/common/model" "golang.org/x/sync/errgroup" + + "github.com/grafana/alerting/receivers" + "github.com/grafana/alerting/receivers/alertmanager" + "github.com/grafana/alerting/receivers/dinding" + "github.com/grafana/alerting/receivers/discord" + "github.com/grafana/alerting/receivers/email" + "github.com/grafana/alerting/receivers/googlechat" + "github.com/grafana/alerting/receivers/kafka" + "github.com/grafana/alerting/receivers/line" + "github.com/grafana/alerting/receivers/opsgenie" + "github.com/grafana/alerting/receivers/pagerduty" + "github.com/grafana/alerting/receivers/pushover" + "github.com/grafana/alerting/receivers/sensugo" + "github.com/grafana/alerting/receivers/slack" + "github.com/grafana/alerting/receivers/teams" + "github.com/grafana/alerting/receivers/telegram" + "github.com/grafana/alerting/receivers/threema" + "github.com/grafana/alerting/receivers/victorops" + "github.com/grafana/alerting/receivers/webex" + "github.com/grafana/alerting/receivers/webhook" + "github.com/grafana/alerting/receivers/wecom" ) const ( @@ -292,3 +316,173 @@ func ProcessNotifierError(config *GrafanaReceiver, err error) error { return err } + +// GrafanaReceiverTyped represents a parsed and validated APIReceiver +type GrafanaReceiverTyped struct { + Name string + AlertmanagerConfigs []*NotifierConfig[alertmanager.Config] + DingdingConfigs []*NotifierConfig[dinding.Config] + DiscordConfigs []*NotifierConfig[discord.Config] + EmailConfigs []*NotifierConfig[email.Config] + GooglechatConfigs []*NotifierConfig[googlechat.Config] + KafkaConfigs []*NotifierConfig[kafka.Config] + LineConfigs []*NotifierConfig[line.Config] + OpsgenieConfigs []*NotifierConfig[opsgenie.Config] + PagerdutyConfigs []*NotifierConfig[pagerduty.Config] + PushoverConfigs []*NotifierConfig[pushover.Config] + SensugoConfigs []*NotifierConfig[sensugo.Config] + SlackConfigs []*NotifierConfig[slack.Config] + TeamsConfigs []*NotifierConfig[teams.Config] + TelegramConfigs []*NotifierConfig[telegram.Config] + ThreemaConfigs []*NotifierConfig[threema.Config] + VictoropsConfigs []*NotifierConfig[victorops.Config] + WebhookConfigs []*NotifierConfig[webhook.Config] + WecomConfigs []*NotifierConfig[wecom.Config] + WebexConfigs []*NotifierConfig[webex.Config] +} + +// NotifierConfig represents parsed GrafanaReceiver. +type NotifierConfig[T interface{}] struct { + receivers.NotifierInfo + Settings T +} + +// ValidateAPIReceiver parses, decrypts and validates the APIReceiver. GrafanaReceiverTyped that contains configurations of all notifiers configurations for this receiver +func ValidateAPIReceiver(ctx context.Context, api *APIReceiver, decrypt receivers.GetDecryptedValueFn) (GrafanaReceiverTyped, error) { + result := GrafanaReceiverTyped{ + Name: api.Name, + } + parseConfig := func(receiver *GrafanaReceiver) error { + // secure settings are already encrypted at this point + secureSettings := make(map[string][]byte, len(receiver.SecureSettings)) + + if receiver.SecureSettings != nil { + for k, v := range receiver.SecureSettings { + d, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return InvalidReceiverError{ + Receiver: receiver, + Err: errors.New("failed to decode secure setting"), + } + } + secureSettings[k] = d + } + } + + var decryptFn receivers.DecryptFunc = func(key string, fallback string) string { + return decrypt(ctx, secureSettings, key, fallback) + } + + switch strings.ToLower(receiver.Type) { + case "prometheus-alertmanager": + return createReceiver[alertmanager.Config](receiver)(alertmanager.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[alertmanager.Config]) { + result.AlertmanagerConfigs = append(result.AlertmanagerConfigs, f) + }) + case "dingding": + return createReceiver[dinding.Config](receiver)(dinding.NewConfig(receiver.Settings))(func(f *NotifierConfig[dinding.Config]) { + result.DingdingConfigs = append(result.DingdingConfigs, f) + }) + case "discord": + return createReceiver[discord.Config](receiver)(discord.NewConfig(receiver.Settings))(func(f *NotifierConfig[discord.Config]) { + result.DiscordConfigs = append(result.DiscordConfigs, f) + }) + case "email": + return createReceiver[email.Config](receiver)(email.NewConfig(receiver.Settings))(func(f *NotifierConfig[email.Config]) { + result.EmailConfigs = append(result.EmailConfigs, f) + }) + case "googlechat": + return createReceiver[googlechat.Config](receiver)(googlechat.NewConfig(receiver.Settings))(func(f *NotifierConfig[googlechat.Config]) { + result.GooglechatConfigs = append(result.GooglechatConfigs, f) + }) + case "kafka": + return createReceiver[kafka.Config](receiver)(kafka.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[kafka.Config]) { + result.KafkaConfigs = append(result.KafkaConfigs, f) + }) + case "line": + return createReceiver[line.Config](receiver)(line.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[line.Config]) { + result.LineConfigs = append(result.LineConfigs, f) + }) + case "opsgenie": + return createReceiver[opsgenie.Config](receiver)(opsgenie.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[opsgenie.Config]) { + result.OpsgenieConfigs = append(result.OpsgenieConfigs, f) + }) + case "pagerduty": + return createReceiver[pagerduty.Config](receiver)(pagerduty.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[pagerduty.Config]) { + result.PagerdutyConfigs = append(result.PagerdutyConfigs, f) + }) + case "pushover": + return createReceiver[pushover.Config](receiver)(pushover.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[pushover.Config]) { + result.PushoverConfigs = append(result.PushoverConfigs, f) + }) + case "sensugo": + return createReceiver[sensugo.Config](receiver)(sensugo.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[sensugo.Config]) { + result.SensugoConfigs = append(result.SensugoConfigs, f) + }) + case "slack": + return createReceiver[slack.Config](receiver)(slack.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[slack.Config]) { + result.SlackConfigs = append(result.SlackConfigs, f) + }) + case "teams": + return createReceiver[teams.Config](receiver)(teams.NewConfig(receiver.Settings))(func(f *NotifierConfig[teams.Config]) { + result.TeamsConfigs = append(result.TeamsConfigs, f) + }) + case "telegram": + return createReceiver[telegram.Config](receiver)(telegram.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[telegram.Config]) { + result.TelegramConfigs = append(result.TelegramConfigs, f) + }) + case "threema": + return createReceiver[threema.Config](receiver)(threema.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[threema.Config]) { + result.ThreemaConfigs = append(result.ThreemaConfigs, f) + }) + case "victorops": + return createReceiver[victorops.Config](receiver)(victorops.NewConfig(receiver.Settings))(func(f *NotifierConfig[victorops.Config]) { + result.VictoropsConfigs = append(result.VictoropsConfigs, f) + }) + case "webhook": + return createReceiver[webhook.Config](receiver)(webhook.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[webhook.Config]) { + result.WebhookConfigs = append(result.WebhookConfigs, f) + }) + case "wecom": + return createReceiver[wecom.Config](receiver)(wecom.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[wecom.Config]) { + result.WecomConfigs = append(result.WecomConfigs, f) + }) + case "webex": + return createReceiver[webex.Config](receiver)(webex.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[webex.Config]) { + result.WebexConfigs = append(result.WebexConfigs, f) + }) + default: + return fmt.Errorf("notifier %s is not supported", receiver.Type) + } + } + for _, receiver := range api.Receivers { + err := parseConfig(receiver) + if err != nil { + return GrafanaReceiverTyped{}, &InvalidReceiverError{ + Receiver: receiver, + Err: err, + } + } + } + return result, nil +} + +func createReceiver[T interface{}](receiver *GrafanaReceiver) func(cfg T, err error) func(f func(f *NotifierConfig[T])) error { + return func(settings T, err error) func(f func(f *NotifierConfig[T])) error { + return func(f func(f *NotifierConfig[T])) error { + if err != nil { + return err + } + r := &NotifierConfig[T]{ + NotifierInfo: receivers.NotifierInfo{ + UID: receiver.UID, + Name: receiver.Name, + Type: receiver.Type, + DisableResolveMessage: receiver.DisableResolveMessage, + }, + Settings: settings, + } + f(r) + return nil + } + } +} diff --git a/receivers/factory.go b/receivers/factory.go index 5db431c0..efcccfcb 100644 --- a/receivers/factory.go +++ b/receivers/factory.go @@ -32,6 +32,14 @@ type NotificationChannelConfig struct { SecureSettings map[string][]byte `json:"secureSettings"` } +// NotifierInfo contains metadata of the notifier. Name, UID, Type, etc +type NotifierInfo struct { + UID string + Name string + Type string + DisableResolveMessage bool +} + type FactoryConfig struct { Config *NotificationChannelConfig // Used by some receivers to include as part of the source From d5fde2623faf025ccff934c9d3c87eced745d048 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 14 Feb 2023 14:47:30 -0500 Subject: [PATCH 03/15] move init config --- notify/factory.go | 35 ++++++++++------------------------- notify/receivers.go | 22 +++++++++++++++++++--- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/notify/factory.go b/notify/factory.go index 86cfbdbd..b8860f97 100644 --- a/notify/factory.go +++ b/notify/factory.go @@ -1,7 +1,6 @@ package notify import ( - "fmt" "strings" "github.com/prometheus/alertmanager/notify" @@ -67,32 +66,18 @@ func wrap[T NotificationChannel](f func(fc receivers.FactoryConfig) (T, error)) ch, err := f(fc) if err != nil { return nil, ReceiverInitError{ - Reason: err.Error(), - Cfg: *fc.Config, + Err: err, + // TODO it will be removed in the next PR + Cfg: &GrafanaReceiver{ + UID: fc.Config.UID, + Name: fc.Config.Name, + Type: fc.Config.Type, + DisableResolveMessage: fc.Config.DisableResolveMessage, + Settings: fc.Config.Settings, + SecureSettings: nil, + }, } } return ch, nil } } - -type ReceiverInitError struct { - Reason string - Err error - Cfg receivers.NotificationChannelConfig -} - -func (e ReceiverInitError) Error() string { - name := "" - if e.Cfg.Name != "" { - name = fmt.Sprintf("%q ", e.Cfg.Name) - } - - s := fmt.Sprintf("failed to validate receiver %sof type %q: %s", name, e.Cfg.Type, e.Reason) - if e.Err != nil { - return fmt.Sprintf("%s: %s", s, e.Err.Error()) - } - - return s -} - -func (e ReceiverInitError) Unwrap() error { return e.Err } diff --git a/notify/receivers.go b/notify/receivers.go index 5edc3aa8..600ecbcb 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -457,9 +457,9 @@ func ValidateAPIReceiver(ctx context.Context, api *APIReceiver, decrypt receiver for _, receiver := range api.Receivers { err := parseConfig(receiver) if err != nil { - return GrafanaReceiverTyped{}, &InvalidReceiverError{ - Receiver: receiver, - Err: err, + return GrafanaReceiverTyped{}, &ReceiverInitError{ + Cfg: receiver, + Err: err, } } } @@ -486,3 +486,19 @@ func createReceiver[T interface{}](receiver *GrafanaReceiver) func(cfg T, err er } } } + +type ReceiverInitError struct { + Err error + Cfg *GrafanaReceiver +} + +func (e ReceiverInitError) Error() string { + name := "" + if e.Cfg.Name != "" { + name = fmt.Sprintf("%q ", e.Cfg.Name) + } + s := fmt.Sprintf("failed to validate receiver %sof type %q: %s", name, e.Cfg.Type, e.Err.Error()) + return s +} + +func (e ReceiverInitError) Unwrap() error { return e.Err } From cc80c056240f296e4077548ca6b6b13c9bc9713a Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 14 Feb 2023 14:50:42 -0500 Subject: [PATCH 04/15] rename ReceiverInitError to ReceiverValidationError --- notify/factory.go | 4 ++-- notify/receivers.go | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/notify/factory.go b/notify/factory.go index b8860f97..659e4132 100644 --- a/notify/factory.go +++ b/notify/factory.go @@ -60,12 +60,12 @@ func Factory(receiverType string) (func(receivers.FactoryConfig) (NotificationCh return factory, exists } -// wrap wraps the notifier's factory errors with receivers.ReceiverInitError +// wrap wraps the notifier's factory errors with receivers.ReceiverValidationError func wrap[T NotificationChannel](f func(fc receivers.FactoryConfig) (T, error)) func(receivers.FactoryConfig) (NotificationChannel, error) { return func(fc receivers.FactoryConfig) (NotificationChannel, error) { ch, err := f(fc) if err != nil { - return nil, ReceiverInitError{ + return nil, ReceiverValidationError{ Err: err, // TODO it will be removed in the next PR Cfg: &GrafanaReceiver{ diff --git a/notify/receivers.go b/notify/receivers.go index 600ecbcb..165b7769 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -457,7 +457,7 @@ func ValidateAPIReceiver(ctx context.Context, api *APIReceiver, decrypt receiver for _, receiver := range api.Receivers { err := parseConfig(receiver) if err != nil { - return GrafanaReceiverTyped{}, &ReceiverInitError{ + return GrafanaReceiverTyped{}, &ReceiverValidationError{ Cfg: receiver, Err: err, } @@ -487,12 +487,12 @@ func createReceiver[T interface{}](receiver *GrafanaReceiver) func(cfg T, err er } } -type ReceiverInitError struct { +type ReceiverValidationError struct { Err error Cfg *GrafanaReceiver } -func (e ReceiverInitError) Error() string { +func (e ReceiverValidationError) Error() string { name := "" if e.Cfg.Name != "" { name = fmt.Sprintf("%q ", e.Cfg.Name) @@ -501,4 +501,4 @@ func (e ReceiverInitError) Error() string { return s } -func (e ReceiverInitError) Unwrap() error { return e.Err } +func (e ReceiverValidationError) Unwrap() error { return e.Err } From ad0a80759e2c2ccc4e0a918da0901d87472ba5af Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Mon, 27 Feb 2023 14:19:40 -0500 Subject: [PATCH 05/15] rename NotifierInfo to Metadata --- notify/receivers.go | 2 +- receivers/factory.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/notify/receivers.go b/notify/receivers.go index 165b7769..1a46a2a5 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -343,7 +343,7 @@ type GrafanaReceiverTyped struct { // NotifierConfig represents parsed GrafanaReceiver. type NotifierConfig[T interface{}] struct { - receivers.NotifierInfo + receivers.Metadata Settings T } diff --git a/receivers/factory.go b/receivers/factory.go index efcccfcb..dc190dfd 100644 --- a/receivers/factory.go +++ b/receivers/factory.go @@ -32,8 +32,8 @@ type NotificationChannelConfig struct { SecureSettings map[string][]byte `json:"secureSettings"` } -// NotifierInfo contains metadata of the notifier. Name, UID, Type, etc -type NotifierInfo struct { +// Metadata contains the metadata of the notifier +type Metadata struct { UID string Name string Type string From 1029b572f9dffb24099c5980d987cb76c1a7d5cf Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Mon, 27 Feb 2023 14:21:59 -0500 Subject: [PATCH 06/15] refactor ValidateAPIReceiver to be more readable --- notify/receivers.go | 273 +++++++++++++++++++++++++------------------- 1 file changed, 153 insertions(+), 120 deletions(-) diff --git a/notify/receivers.go b/notify/receivers.go index 1a46a2a5..bd57d06b 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -352,138 +352,171 @@ func ValidateAPIReceiver(ctx context.Context, api *APIReceiver, decrypt receiver result := GrafanaReceiverTyped{ Name: api.Name, } - parseConfig := func(receiver *GrafanaReceiver) error { - // secure settings are already encrypted at this point - secureSettings := make(map[string][]byte, len(receiver.SecureSettings)) - - if receiver.SecureSettings != nil { - for k, v := range receiver.SecureSettings { - d, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return InvalidReceiverError{ - Receiver: receiver, - Err: errors.New("failed to decode secure setting"), - } - } - secureSettings[k] = d - } - } - - var decryptFn receivers.DecryptFunc = func(key string, fallback string) string { - return decrypt(ctx, secureSettings, key, fallback) - } - - switch strings.ToLower(receiver.Type) { - case "prometheus-alertmanager": - return createReceiver[alertmanager.Config](receiver)(alertmanager.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[alertmanager.Config]) { - result.AlertmanagerConfigs = append(result.AlertmanagerConfigs, f) - }) - case "dingding": - return createReceiver[dinding.Config](receiver)(dinding.NewConfig(receiver.Settings))(func(f *NotifierConfig[dinding.Config]) { - result.DingdingConfigs = append(result.DingdingConfigs, f) - }) - case "discord": - return createReceiver[discord.Config](receiver)(discord.NewConfig(receiver.Settings))(func(f *NotifierConfig[discord.Config]) { - result.DiscordConfigs = append(result.DiscordConfigs, f) - }) - case "email": - return createReceiver[email.Config](receiver)(email.NewConfig(receiver.Settings))(func(f *NotifierConfig[email.Config]) { - result.EmailConfigs = append(result.EmailConfigs, f) - }) - case "googlechat": - return createReceiver[googlechat.Config](receiver)(googlechat.NewConfig(receiver.Settings))(func(f *NotifierConfig[googlechat.Config]) { - result.GooglechatConfigs = append(result.GooglechatConfigs, f) - }) - case "kafka": - return createReceiver[kafka.Config](receiver)(kafka.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[kafka.Config]) { - result.KafkaConfigs = append(result.KafkaConfigs, f) - }) - case "line": - return createReceiver[line.Config](receiver)(line.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[line.Config]) { - result.LineConfigs = append(result.LineConfigs, f) - }) - case "opsgenie": - return createReceiver[opsgenie.Config](receiver)(opsgenie.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[opsgenie.Config]) { - result.OpsgenieConfigs = append(result.OpsgenieConfigs, f) - }) - case "pagerduty": - return createReceiver[pagerduty.Config](receiver)(pagerduty.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[pagerduty.Config]) { - result.PagerdutyConfigs = append(result.PagerdutyConfigs, f) - }) - case "pushover": - return createReceiver[pushover.Config](receiver)(pushover.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[pushover.Config]) { - result.PushoverConfigs = append(result.PushoverConfigs, f) - }) - case "sensugo": - return createReceiver[sensugo.Config](receiver)(sensugo.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[sensugo.Config]) { - result.SensugoConfigs = append(result.SensugoConfigs, f) - }) - case "slack": - return createReceiver[slack.Config](receiver)(slack.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[slack.Config]) { - result.SlackConfigs = append(result.SlackConfigs, f) - }) - case "teams": - return createReceiver[teams.Config](receiver)(teams.NewConfig(receiver.Settings))(func(f *NotifierConfig[teams.Config]) { - result.TeamsConfigs = append(result.TeamsConfigs, f) - }) - case "telegram": - return createReceiver[telegram.Config](receiver)(telegram.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[telegram.Config]) { - result.TelegramConfigs = append(result.TelegramConfigs, f) - }) - case "threema": - return createReceiver[threema.Config](receiver)(threema.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[threema.Config]) { - result.ThreemaConfigs = append(result.ThreemaConfigs, f) - }) - case "victorops": - return createReceiver[victorops.Config](receiver)(victorops.NewConfig(receiver.Settings))(func(f *NotifierConfig[victorops.Config]) { - result.VictoropsConfigs = append(result.VictoropsConfigs, f) - }) - case "webhook": - return createReceiver[webhook.Config](receiver)(webhook.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[webhook.Config]) { - result.WebhookConfigs = append(result.WebhookConfigs, f) - }) - case "wecom": - return createReceiver[wecom.Config](receiver)(wecom.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[wecom.Config]) { - result.WecomConfigs = append(result.WecomConfigs, f) - }) - case "webex": - return createReceiver[webex.Config](receiver)(webex.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[webex.Config]) { - result.WebexConfigs = append(result.WebexConfigs, f) + for _, receiver := range api.Receivers { + secureSettings, err := decodeSecretsFromBase64(receiver.SecureSettings) + if err == nil { + err = parseReceiver(&result, receiver, func(key string, fallback string) string { + return decrypt(ctx, secureSettings, key, fallback) }) - default: - return fmt.Errorf("notifier %s is not supported", receiver.Type) } - } - for _, receiver := range api.Receivers { - err := parseConfig(receiver) if err != nil { return GrafanaReceiverTyped{}, &ReceiverValidationError{ Cfg: receiver, - Err: err, + Err: fmt.Errorf("failed to parse notifier %s (UID: %s): %w", receiver.Name, receiver.UID, err), } } } return result, nil } -func createReceiver[T interface{}](receiver *GrafanaReceiver) func(cfg T, err error) func(f func(f *NotifierConfig[T])) error { - return func(settings T, err error) func(f func(f *NotifierConfig[T])) error { - return func(f func(f *NotifierConfig[T])) error { - if err != nil { - return err - } - r := &NotifierConfig[T]{ - NotifierInfo: receivers.NotifierInfo{ - UID: receiver.UID, - Name: receiver.Name, - Type: receiver.Type, - DisableResolveMessage: receiver.DisableResolveMessage, - }, - Settings: settings, - } - f(r) - return nil +// parseReceiver parses receivers and populates corresponding field in GrafanaReceiverTyped. Returns error if configuration cannot be parsed +func parseReceiver(result *GrafanaReceiverTyped, receiver *GrafanaReceiver, decryptFn receivers.DecryptFunc) error { + switch strings.ToLower(receiver.Type) { + case "prometheus-alertmanager": + cfg, err := alertmanager.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.AlertmanagerConfigs = append(result.AlertmanagerConfigs, newNotifierConfig(receiver, cfg)) + case "dingding": + cfg, err := dinding.NewConfig(receiver.Settings) + if err != nil { + return err + } + result.DingdingConfigs = append(result.DingdingConfigs, newNotifierConfig(receiver, cfg)) + case "discord": + cfg, err := discord.NewConfig(receiver.Settings) + if err != nil { + return err + } + result.DiscordConfigs = append(result.DiscordConfigs, newNotifierConfig(receiver, cfg)) + case "email": + cfg, err := email.NewConfig(receiver.Settings) + if err != nil { + return err + } + result.EmailConfigs = append(result.EmailConfigs, newNotifierConfig(receiver, cfg)) + case "googlechat": + cfg, err := googlechat.NewConfig(receiver.Settings) + if err != nil { + return err + } + result.GooglechatConfigs = append(result.GooglechatConfigs, newNotifierConfig(receiver, cfg)) + case "kafka": + cfg, err := kafka.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.KafkaConfigs = append(result.KafkaConfigs, newNotifierConfig(receiver, cfg)) + case "line": + cfg, err := line.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.LineConfigs = append(result.LineConfigs, newNotifierConfig(receiver, cfg)) + case "opsgenie": + cfg, err := opsgenie.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.OpsgenieConfigs = append(result.OpsgenieConfigs, newNotifierConfig(receiver, cfg)) + case "pagerduty": + cfg, err := pagerduty.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.PagerdutyConfigs = append(result.PagerdutyConfigs, newNotifierConfig(receiver, cfg)) + case "pushover": + cfg, err := pushover.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.PushoverConfigs = append(result.PushoverConfigs, newNotifierConfig(receiver, cfg)) + case "sensugo": + cfg, err := sensugo.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.SensugoConfigs = append(result.SensugoConfigs, newNotifierConfig(receiver, cfg)) + case "slack": + cfg, err := slack.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err } + result.SlackConfigs = append(result.SlackConfigs, newNotifierConfig(receiver, cfg)) + case "teams": + cfg, err := teams.NewConfig(receiver.Settings) + if err != nil { + return err + } + result.TeamsConfigs = append(result.TeamsConfigs, newNotifierConfig(receiver, cfg)) + case "telegram": + cfg, err := telegram.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.TelegramConfigs = append(result.TelegramConfigs, newNotifierConfig(receiver, cfg)) + case "threema": + cfg, err := threema.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.ThreemaConfigs = append(result.ThreemaConfigs, newNotifierConfig(receiver, cfg)) + case "victorops": + cfg, err := victorops.NewConfig(receiver.Settings) + if err != nil { + return err + } + result.VictoropsConfigs = append(result.VictoropsConfigs, newNotifierConfig(receiver, cfg)) + case "webhook": + cfg, err := webhook.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.WebhookConfigs = append(result.WebhookConfigs, newNotifierConfig(receiver, cfg)) + case "wecom": + cfg, err := wecom.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.WecomConfigs = append(result.WecomConfigs, newNotifierConfig(receiver, cfg)) + case "webex": + cfg, err := webex.NewConfig(receiver.Settings, decryptFn) + if err != nil { + return err + } + result.WebexConfigs = append(result.WebexConfigs, newNotifierConfig(receiver, cfg)) + default: + return fmt.Errorf("notifier %s is not supported", receiver.Type) + } + return nil +} + +func decodeSecretsFromBase64(secrets map[string]string) (map[string][]byte, error) { + // secure settings are already encrypted at this point + secureSettings := make(map[string][]byte, len(secrets)) + if secrets == nil { + return secureSettings, nil + } + for k, v := range secrets { + d, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return nil, fmt.Errorf("failed to decode secure settings key %s: %w", k, err) + } + secureSettings[k] = d + } + return secureSettings, nil +} + +func newNotifierConfig[T interface{}](receiver *GrafanaReceiver, settings T) *NotifierConfig[T] { + return &NotifierConfig[T]{ + Metadata: receivers.Metadata{ + UID: receiver.UID, + Name: receiver.Name, + Type: receiver.Type, + DisableResolveMessage: receiver.DisableResolveMessage, + }, + Settings: settings, } } From f9874e2efe22b8afb02600a28203a7ab9a55b792 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 28 Feb 2023 10:33:50 -0500 Subject: [PATCH 07/15] rename func --- notify/receivers.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/notify/receivers.go b/notify/receivers.go index bd57d06b..72eb56e2 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -347,8 +347,8 @@ type NotifierConfig[T interface{}] struct { Settings T } -// ValidateAPIReceiver parses, decrypts and validates the APIReceiver. GrafanaReceiverTyped that contains configurations of all notifiers configurations for this receiver -func ValidateAPIReceiver(ctx context.Context, api *APIReceiver, decrypt receivers.GetDecryptedValueFn) (GrafanaReceiverTyped, error) { +// BuildReceiverIntegrations parses, decrypts and validates the APIReceiver. GrafanaReceiverTyped that contains configurations of all notifiers configurations for this receiver +func BuildReceiverIntegrations(ctx context.Context, api *APIReceiver, decrypt receivers.GetDecryptedValueFn) (GrafanaReceiverTyped, error) { result := GrafanaReceiverTyped{ Name: api.Name, } From c8e292bedd609405249bf737c0fef0a12e99837e Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 28 Feb 2023 10:35:04 -0500 Subject: [PATCH 08/15] dot --- receivers/factory.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/receivers/factory.go b/receivers/factory.go index dc190dfd..43cf9e82 100644 --- a/receivers/factory.go +++ b/receivers/factory.go @@ -32,7 +32,7 @@ type NotificationChannelConfig struct { SecureSettings map[string][]byte `json:"secureSettings"` } -// Metadata contains the metadata of the notifier +// Metadata contains the metadata of the notifier. type Metadata struct { UID string Name string From 9eb5100d746933cd9a4f6bd8a95555c3b78405e3 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 28 Feb 2023 10:35:15 -0500 Subject: [PATCH 09/15] remove comment --- notify/receivers.go | 1 - 1 file changed, 1 deletion(-) diff --git a/notify/receivers.go b/notify/receivers.go index 72eb56e2..2b70ac61 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -493,7 +493,6 @@ func parseReceiver(result *GrafanaReceiverTyped, receiver *GrafanaReceiver, decr } func decodeSecretsFromBase64(secrets map[string]string) (map[string][]byte, error) { - // secure settings are already encrypted at this point secureSettings := make(map[string][]byte, len(secrets)) if secrets == nil { return secureSettings, nil From 6668e765876eef00e37f5422084f51fd8c9baa28 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 28 Feb 2023 10:41:37 -0500 Subject: [PATCH 10/15] move some code to parseReceiver --- notify/receivers.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/notify/receivers.go b/notify/receivers.go index 2b70ac61..d9b767d2 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -353,12 +353,7 @@ func BuildReceiverIntegrations(ctx context.Context, api *APIReceiver, decrypt re Name: api.Name, } for _, receiver := range api.Receivers { - secureSettings, err := decodeSecretsFromBase64(receiver.SecureSettings) - if err == nil { - err = parseReceiver(&result, receiver, func(key string, fallback string) string { - return decrypt(ctx, secureSettings, key, fallback) - }) - } + err := parseReceiver(ctx, &result, receiver, decrypt) if err != nil { return GrafanaReceiverTyped{}, &ReceiverValidationError{ Cfg: receiver, @@ -370,7 +365,16 @@ func BuildReceiverIntegrations(ctx context.Context, api *APIReceiver, decrypt re } // parseReceiver parses receivers and populates corresponding field in GrafanaReceiverTyped. Returns error if configuration cannot be parsed -func parseReceiver(result *GrafanaReceiverTyped, receiver *GrafanaReceiver, decryptFn receivers.DecryptFunc) error { +func parseReceiver(ctx context.Context, result *GrafanaReceiverTyped, receiver *GrafanaReceiver, decrypt receivers.GetDecryptedValueFn) error { + secureSettings, err := decodeSecretsFromBase64(receiver.SecureSettings) + if err != nil { + return err + } + + decryptFn := func(key string, fallback string) string { + return decrypt(ctx, secureSettings, key, fallback) + } + switch strings.ToLower(receiver.Type) { case "prometheus-alertmanager": cfg, err := alertmanager.NewConfig(receiver.Settings, decryptFn) From 4e1e97416bf296590368a57e2551d6680c94cb90 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 28 Feb 2023 10:58:15 -0500 Subject: [PATCH 11/15] fix terminology --- notify/receivers.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notify/receivers.go b/notify/receivers.go index d9b767d2..395ed838 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -353,7 +353,7 @@ func BuildReceiverIntegrations(ctx context.Context, api *APIReceiver, decrypt re Name: api.Name, } for _, receiver := range api.Receivers { - err := parseReceiver(ctx, &result, receiver, decrypt) + err := parseNotifier(ctx, &result, receiver, decrypt) if err != nil { return GrafanaReceiverTyped{}, &ReceiverValidationError{ Cfg: receiver, @@ -364,8 +364,8 @@ func BuildReceiverIntegrations(ctx context.Context, api *APIReceiver, decrypt re return result, nil } -// parseReceiver parses receivers and populates corresponding field in GrafanaReceiverTyped. Returns error if configuration cannot be parsed -func parseReceiver(ctx context.Context, result *GrafanaReceiverTyped, receiver *GrafanaReceiver, decrypt receivers.GetDecryptedValueFn) error { +// parseNotifier parses receivers and populates corresponding field in GrafanaReceiverTyped. Returns error if configuration cannot be parsed +func parseNotifier(ctx context.Context, result *GrafanaReceiverTyped, receiver *GrafanaReceiver, decrypt receivers.GetDecryptedValueFn) error { secureSettings, err := decodeSecretsFromBase64(receiver.SecureSettings) if err != nil { return err From cf6fc3b980ecadcb05893e477417f06f0c8c59c2 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 28 Feb 2023 10:59:12 -0500 Subject: [PATCH 12/15] more rename --- notify/receivers.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/notify/receivers.go b/notify/receivers.go index 395ed838..21ed1dbb 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -317,8 +317,8 @@ func ProcessNotifierError(config *GrafanaReceiver, err error) error { return err } -// GrafanaReceiverTyped represents a parsed and validated APIReceiver -type GrafanaReceiverTyped struct { +// GrafanaReceiverConfig represents a parsed and validated APIReceiver +type GrafanaReceiverConfig struct { Name string AlertmanagerConfigs []*NotifierConfig[alertmanager.Config] DingdingConfigs []*NotifierConfig[dinding.Config] @@ -347,15 +347,15 @@ type NotifierConfig[T interface{}] struct { Settings T } -// BuildReceiverIntegrations parses, decrypts and validates the APIReceiver. GrafanaReceiverTyped that contains configurations of all notifiers configurations for this receiver -func BuildReceiverIntegrations(ctx context.Context, api *APIReceiver, decrypt receivers.GetDecryptedValueFn) (GrafanaReceiverTyped, error) { - result := GrafanaReceiverTyped{ +// BuildReceiverConfiguration parses, decrypts and validates the APIReceiver. GrafanaReceiverConfig that contains configurations of all notifiers configurations for this receiver +func BuildReceiverConfiguration(ctx context.Context, api *APIReceiver, decrypt receivers.GetDecryptedValueFn) (GrafanaReceiverConfig, error) { + result := GrafanaReceiverConfig{ Name: api.Name, } for _, receiver := range api.Receivers { err := parseNotifier(ctx, &result, receiver, decrypt) if err != nil { - return GrafanaReceiverTyped{}, &ReceiverValidationError{ + return GrafanaReceiverConfig{}, &ReceiverValidationError{ Cfg: receiver, Err: fmt.Errorf("failed to parse notifier %s (UID: %s): %w", receiver.Name, receiver.UID, err), } @@ -364,8 +364,8 @@ func BuildReceiverIntegrations(ctx context.Context, api *APIReceiver, decrypt re return result, nil } -// parseNotifier parses receivers and populates corresponding field in GrafanaReceiverTyped. Returns error if configuration cannot be parsed -func parseNotifier(ctx context.Context, result *GrafanaReceiverTyped, receiver *GrafanaReceiver, decrypt receivers.GetDecryptedValueFn) error { +// parseNotifier parses receivers and populates corresponding field in GrafanaReceiverConfig. Returns error if configuration cannot be parsed +func parseNotifier(ctx context.Context, result *GrafanaReceiverConfig, receiver *GrafanaReceiver, decrypt receivers.GetDecryptedValueFn) error { secureSettings, err := decodeSecretsFromBase64(receiver.SecureSettings) if err != nil { return err From 931fd6a37a81f9194c6d50384a7baad5a556ff23 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 28 Feb 2023 14:13:44 -0500 Subject: [PATCH 13/15] add valid configs for all receivers so they can be used outside of the package --- receivers/alertmanager/config_test.go | 12 +++------ receivers/alertmanager/testing.go | 13 +++++++++ receivers/dinding/config_test.go | 9 ++----- receivers/dinding/testing.go | 9 +++++++ receivers/discord/config_test.go | 2 +- receivers/discord/testing.go | 10 +++++++ receivers/email/config_test.go | 2 +- receivers/email/testing.go | 9 +++++++ receivers/googlechat/config_test.go | 2 +- receivers/googlechat/testing.go | 10 +++++++ receivers/kafka/config_test.go | 35 +++++++----------------- receivers/kafka/testing.go | 18 +++++++++++++ receivers/line/config_test.go | 12 ++++++++- receivers/line/testing.go | 13 +++++++++ receivers/opsgenie/config_test.go | 29 +++++++++++--------- receivers/opsgenie/testing.go | 17 ++++++++++++ receivers/pagerduty/config_test.go | 34 +++++++++++++----------- receivers/pagerduty/testing.go | 19 ++++++++++++++ receivers/pushover/config_test.go | 38 +++++++++++++++------------ receivers/pushover/testing.go | 23 ++++++++++++++++ receivers/sensugo/config_test.go | 28 +++++++++++--------- receivers/sensugo/testing.go | 17 ++++++++++++ receivers/slack/config_test.go | 37 ++++++++++++++++++++++++++ receivers/slack/testing.go | 23 ++++++++++++++++ receivers/teams/config_test.go | 9 ++----- receivers/teams/testing.go | 9 +++++++ receivers/telegram/config_test.go | 22 ++++++++++------ receivers/telegram/testing.go | 15 +++++++++++ receivers/testing/testing.go | 15 +++++++++++ receivers/threema/config_test.go | 22 ++++++++++------ receivers/threema/testing.go | 15 +++++++++++ receivers/victorops/config_test.go | 9 ++----- receivers/victorops/testing.go | 9 +++++++ receivers/webex/config_test.go | 20 +++++++++----- receivers/webex/testing.go | 14 ++++++++++ receivers/webhook/config_test.go | 30 ++++++++++++--------- receivers/webhook/testing.go | 20 ++++++++++++++ receivers/wecom/config_test.go | 33 +++++++++++++---------- receivers/wecom/testing.go | 19 ++++++++++++++ 39 files changed, 518 insertions(+), 164 deletions(-) create mode 100644 receivers/alertmanager/testing.go create mode 100644 receivers/dinding/testing.go create mode 100644 receivers/discord/testing.go create mode 100644 receivers/email/testing.go create mode 100644 receivers/googlechat/testing.go create mode 100644 receivers/kafka/testing.go create mode 100644 receivers/line/testing.go create mode 100644 receivers/opsgenie/testing.go create mode 100644 receivers/pagerduty/testing.go create mode 100644 receivers/pushover/testing.go create mode 100644 receivers/sensugo/testing.go create mode 100644 receivers/slack/testing.go create mode 100644 receivers/teams/testing.go create mode 100644 receivers/telegram/testing.go create mode 100644 receivers/threema/testing.go create mode 100644 receivers/victorops/testing.go create mode 100644 receivers/webex/testing.go create mode 100644 receivers/webhook/testing.go create mode 100644 receivers/wecom/testing.go diff --git a/receivers/alertmanager/config_test.go b/receivers/alertmanager/config_test.go index acd29584..3d5e9259 100644 --- a/receivers/alertmanager/config_test.go +++ b/receivers/alertmanager/config_test.go @@ -93,15 +93,9 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "User and password from secrets", - settings: `{ - "url": "https://alertmanager-01.com", - "basicAuthUser": "grafana", - "basicAuthPassword": "admin" - }`, - secrets: map[string][]byte{ - "basicAuthPassword": []byte("grafana-admin"), - }, + name: "User and password from secrets", + settings: FullValidConfigForTesting, + secrets: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), expectedConfig: Config{ URLs: []*url.URL{ receiversTesting.ParseURLUnsafe("https://alertmanager-01.com/api/v1/alerts"), diff --git a/receivers/alertmanager/testing.go b/receivers/alertmanager/testing.go new file mode 100644 index 00000000..28d6c6cc --- /dev/null +++ b/receivers/alertmanager/testing.go @@ -0,0 +1,13 @@ +package alertmanager + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "url": "https://alertmanager-01.com", + "basicAuthUser": "grafana", + "basicAuthPassword": "admin" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "basicAuthPassword": "grafana-admin" +}` diff --git a/receivers/dinding/config_test.go b/receivers/dinding/config_test.go index 5e94a758..b02541c7 100644 --- a/receivers/dinding/config_test.go +++ b/receivers/dinding/config_test.go @@ -53,13 +53,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Custom config with multiple alerts", - settings: `{ - "url": "http://localhost", - "message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved", - "title": "Alerts firing: {{ len .Alerts.Firing }}", - "msgType": "actionCard" - }`, + name: "All supported fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ URL: "http://localhost", MessageType: "actionCard", diff --git a/receivers/dinding/testing.go b/receivers/dinding/testing.go new file mode 100644 index 00000000..d82ce7c3 --- /dev/null +++ b/receivers/dinding/testing.go @@ -0,0 +1,9 @@ +package dinding + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "url": "http://localhost", + "message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved", + "title": "Alerts firing: {{ len .Alerts.Firing }}", + "msgType": "actionCard" +}` diff --git a/receivers/discord/config_test.go b/receivers/discord/config_test.go index 6f01ca2e..751fabde 100644 --- a/receivers/discord/config_test.go +++ b/receivers/discord/config_test.go @@ -55,7 +55,7 @@ func TestNewConfig(t *testing.T) { }, { name: "Extracts all fields", - settings: `{"url": "http://localhost", "title": "test-title", "message": "test-message", "avatar_url" : "http://avatar", "use_discord_username": true}`, + settings: FullValidConfigForTesting, expectedConfig: Config{ Title: "test-title", Message: "test-message", diff --git a/receivers/discord/testing.go b/receivers/discord/testing.go new file mode 100644 index 00000000..bf32431f --- /dev/null +++ b/receivers/discord/testing.go @@ -0,0 +1,10 @@ +package discord + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "url": "http://localhost", + "title": "test-title", + "message": "test-message", + "avatar_url" : "http://avatar", + "use_discord_username": true +}` diff --git a/receivers/email/config_test.go b/receivers/email/config_test.go index a214caf3..b03e3589 100644 --- a/receivers/email/config_test.go +++ b/receivers/email/config_test.go @@ -72,7 +72,7 @@ func TestNewConfig(t *testing.T) { }, { name: "Extracts all fields", - settings: `{"addresses": "test@grafana.com", "subject": "test-subject", "message": "test-message", "singleEmail": true}`, + settings: FullValidConfigForTesting, expectedConfig: Config{ SingleEmail: true, Addresses: []string{ diff --git a/receivers/email/testing.go b/receivers/email/testing.go new file mode 100644 index 00000000..43530c95 --- /dev/null +++ b/receivers/email/testing.go @@ -0,0 +1,9 @@ +package email + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "addresses": "test@grafana.com", + "subject": "test-subject", + "message": "test-message", + "singleEmail": true +}` diff --git a/receivers/googlechat/config_test.go b/receivers/googlechat/config_test.go index a8af4963..be7dbed5 100644 --- a/receivers/googlechat/config_test.go +++ b/receivers/googlechat/config_test.go @@ -51,7 +51,7 @@ func TestNewConfig(t *testing.T) { }, { name: "Extracts all fields", - settings: `{"url": "http://localhost", "title": "test-title", "message": "test-message", "avatar_url" : "http://avatar", "use_discord_username": true}`, + settings: FullValidConfigForTesting, expectedConfig: Config{ Title: "test-title", Message: "test-message", diff --git a/receivers/googlechat/testing.go b/receivers/googlechat/testing.go new file mode 100644 index 00000000..792d7efb --- /dev/null +++ b/receivers/googlechat/testing.go @@ -0,0 +1,10 @@ +package googlechat + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "url": "http://localhost", + "title": "test-title", + "message": "test-message", + "avatar_url" : "http://avatar", + "use_discord_username": true +}` diff --git a/receivers/kafka/config_test.go b/receivers/kafka/config_test.go index a0630947..3fb8ceba 100644 --- a/receivers/kafka/config_test.go +++ b/receivers/kafka/config_test.go @@ -107,17 +107,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "kafkaRestProxy": "http://localhost/", - "kafkaTopic" : "test-topic", - "description" : "test-description", - "details": "test-details", - "username": "test-user", - "password": "password", - "apiVersion": "v2", - "kafkaClusterId": "12345" - }`, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ Endpoint: "http://localhost", Topic: "test-topic", @@ -130,24 +121,18 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Should override password from secrets", - settings: `{ - "kafkaRestProxy": "http://localhost/", - "kafkaTopic" : "test-topic", - "password": "password" - }`, - secureSettings: map[string][]byte{ - "password": []byte("test-password"), - }, + name: "Should override password from secrets", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), expectedConfig: Config{ Endpoint: "http://localhost", Topic: "test-topic", - Description: templates.DefaultMessageTitleEmbed, - Details: templates.DefaultMessageEmbed, - Username: "", + Description: "test-description", + Details: "test-details", + Username: "test-user", Password: "test-password", - APIVersion: apiVersionV2, - KafkaClusterID: "", + APIVersion: "v2", + KafkaClusterID: "12345", }, }, { diff --git a/receivers/kafka/testing.go b/receivers/kafka/testing.go new file mode 100644 index 00000000..6c6412db --- /dev/null +++ b/receivers/kafka/testing.go @@ -0,0 +1,18 @@ +package kafka + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "kafkaRestProxy": "http://localhost/", + "kafkaTopic" : "test-topic", + "description" : "test-description", + "details": "test-details", + "username": "test-user", + "password": "password", + "apiVersion": "v2", + "kafkaClusterId": "12345" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "password": "test-password" +}` diff --git a/receivers/line/config_test.go b/receivers/line/config_test.go index d9bdd162..c22650ef 100644 --- a/receivers/line/config_test.go +++ b/receivers/line/config_test.go @@ -80,7 +80,7 @@ func TestNewConfig(t *testing.T) { }, { name: "Extracts all fields", - settings: `{"token": "test", "title": "test-title", "description": "test-description" }`, + settings: FullValidConfigForTesting, secureSettings: map[string][]byte{}, expectedConfig: Config{ Title: "test-title", @@ -88,6 +88,16 @@ func TestNewConfig(t *testing.T) { Token: "test", }, }, + { + name: "Extracts all fields + override from secrets", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + Title: "test-title", + Description: "test-description", + Token: "test-secret-token", + }, + }, } for _, c := range cases { diff --git a/receivers/line/testing.go b/receivers/line/testing.go new file mode 100644 index 00000000..4efe6aed --- /dev/null +++ b/receivers/line/testing.go @@ -0,0 +1,13 @@ +package line + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "token": "test", + "title": "test-title", + "description": "test-description" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "token": "test-secret-token" +}` diff --git a/receivers/opsgenie/config_test.go b/receivers/opsgenie/config_test.go index bbc20033..544c6b54 100644 --- a/receivers/opsgenie/config_test.go +++ b/receivers/opsgenie/config_test.go @@ -170,18 +170,9 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - secureSettings: map[string][]byte{ - "apiKey": []byte("test-api-key"), - }, - settings: `{ - "apiUrl" : "http://localhost", - "message" : "test-message", - "description": "test-description", - "autoClose": false, - "overridePriority": false, - "sendTagsAs": "both" - }`, + name: "Extracts all fields", + secureSettings: map[string][]byte{}, + settings: FullValidConfigForTesting, expectedConfig: Config{ APIKey: "test-api-key", APIUrl: "http://localhost", @@ -192,6 +183,20 @@ func TestNewConfig(t *testing.T) { SendTagsAs: "both", }, }, + { + name: "Extracts all fields + override from secrets", + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + settings: FullValidConfigForTesting, + expectedConfig: Config{ + APIKey: "test-secret-api-key", + APIUrl: "http://localhost", + Message: "test-message", + Description: "test-description", + AutoClose: false, + OverridePriority: false, + SendTagsAs: "both", + }, + }, } for _, c := range cases { diff --git a/receivers/opsgenie/testing.go b/receivers/opsgenie/testing.go new file mode 100644 index 00000000..aebc7acf --- /dev/null +++ b/receivers/opsgenie/testing.go @@ -0,0 +1,17 @@ +package opsgenie + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "apiUrl" : "http://localhost", + "apiKey": "test-api-key", + "message" : "test-message", + "description": "test-description", + "autoClose": false, + "overridePriority": false, + "sendTagsAs": "both" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "apiKey": "test-secret-api-key" +}` diff --git a/receivers/pagerduty/config_test.go b/receivers/pagerduty/config_test.go index 91beac30..3d252fa7 100644 --- a/receivers/pagerduty/config_test.go +++ b/receivers/pagerduty/config_test.go @@ -124,21 +124,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "All empty fields = minimal valid configuration", - secureSettings: map[string][]byte{ - "integrationKey": []byte("test-api-key"), - }, - settings: `{ - "integrationKey": "", - "severity" : "test-severity", - "class" : "test-class", - "component": "test-component", - "group": "test-group", - "summary": "test-summary", - "source": "test-source", - "client" : "test-client", - "client_url": "test-client-url" - }`, + name: "Extract all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ Key: "test-api-key", Severity: "test-severity", @@ -152,6 +139,23 @@ func TestNewConfig(t *testing.T) { ClientURL: "test-client-url", }, }, + { + name: "Extract all fields + override from secrets", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + Key: "test-secret-api-key", + Severity: "test-severity", + CustomDetails: defaultCustomDetails(), + Class: "test-class", + Component: "test-component", + Group: "test-group", + Summary: "test-summary", + Source: "test-source", + Client: "test-client", + ClientURL: "test-client-url", + }, + }, { name: "Should ignore custom details", secureSettings: map[string][]byte{ diff --git a/receivers/pagerduty/testing.go b/receivers/pagerduty/testing.go new file mode 100644 index 00000000..a64765f1 --- /dev/null +++ b/receivers/pagerduty/testing.go @@ -0,0 +1,19 @@ +package pagerduty + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "integrationKey": "test-api-key", + "severity" : "test-severity", + "class" : "test-class", + "component": "test-component", + "group": "test-group", + "summary": "test-summary", + "source": "test-source", + "client" : "test-client", + "client_url": "test-client-url" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "integrationKey": "test-secret-api-key" +}` diff --git a/receivers/pushover/config_test.go b/receivers/pushover/config_test.go index 9b520e00..22d83cdb 100644 --- a/receivers/pushover/config_test.go +++ b/receivers/pushover/config_test.go @@ -136,23 +136,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "priority": 1, - "okPriority": 2, - "retry": 555, - "expire": 333, - "device": "test-device", - "sound": "test-sound", - "okSound": "test-ok-sound", - "uploadImage": false, - "title": "test-title", - "message": "test-message" - }`, - secureSettings: map[string][]byte{ - "userKey": []byte("test-user-key"), - "apiToken": []byte("test-api-token"), - }, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ UserKey: "test-user-key", APIToken: "test-api-token", @@ -168,6 +153,25 @@ func TestNewConfig(t *testing.T) { Message: "test-message", }, }, + { + name: "Extracts all fields + override from secrets", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + UserKey: "test-secret-user-key", + APIToken: "test-secret-api-token", + AlertingPriority: 1, + OkPriority: 2, + Retry: 555, + Expire: 333, + Device: "test-device", + AlertingSound: "test-sound", + OkSound: "test-ok-sound", + Upload: false, + Title: "test-title", + Message: "test-message", + }, + }, { name: "Should treat strings as numbers", settings: `{ diff --git a/receivers/pushover/testing.go b/receivers/pushover/testing.go new file mode 100644 index 00000000..dc2bb5fd --- /dev/null +++ b/receivers/pushover/testing.go @@ -0,0 +1,23 @@ +package pushover + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "priority": 1, + "okPriority": 2, + "retry": 555, + "expire": 333, + "device": "test-device", + "sound": "test-sound", + "okSound": "test-ok-sound", + "uploadImage": false, + "title": "test-title", + "message": "test-message", + "userKey": "test-user-key", + "apiToken": "test-api-token" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "userKey": "test-secret-user-key", + "apiToken": "test-secret-api-token" +}` diff --git a/receivers/sensugo/config_test.go b/receivers/sensugo/config_test.go index 8f215e97..0b401074 100644 --- a/receivers/sensugo/config_test.go +++ b/receivers/sensugo/config_test.go @@ -108,18 +108,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "url": "http://localhost", - "entity" : "test-entity", - "check" : "test-check", - "namespace" : "test-namespace", - "handler" : "test-handler", - "message" : "test-message" - }`, - secureSettings: map[string][]byte{ - "apikey": []byte("test-api-key"), - }, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ URL: "http://localhost", Entity: "test-entity", @@ -130,6 +120,20 @@ func TestNewConfig(t *testing.T) { Message: "test-message", }, }, + { + name: "Extracts all fields + override from encrypted", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + URL: "http://localhost", + Entity: "test-entity", + Check: "test-check", + Namespace: "test-namespace", + Handler: "test-handler", + APIKey: "test-secret-api-key", + Message: "test-message", + }, + }, } for _, c := range cases { diff --git a/receivers/sensugo/testing.go b/receivers/sensugo/testing.go new file mode 100644 index 00000000..8fd7217d --- /dev/null +++ b/receivers/sensugo/testing.go @@ -0,0 +1,17 @@ +package sensugo + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "url": "http://localhost", + "apikey": "test-api-key", + "entity" : "test-entity", + "check" : "test-check", + "namespace" : "test-namespace", + "handler" : "test-handler", + "message" : "test-message" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "apikey": "test-secret-api-key" +}` diff --git a/receivers/slack/config_test.go b/receivers/slack/config_test.go index c8997a57..61a88797 100644 --- a/receivers/slack/config_test.go +++ b/receivers/slack/config_test.go @@ -251,6 +251,43 @@ func TestNewConfig(t *testing.T) { }, }, }, + { + name: "Extract all fields", + settings: FullValidConfigForTesting, + expectedConfig: Config{ + EndpointURL: "http://localhost/endpoint_url", + URL: "http://localhost/url", + Token: "test-token", + Recipient: "test-recipient", + Text: "test-text", + Title: "test-title", + Username: "test-username", + IconEmoji: "test-icon", + IconURL: "http://localhost/icon_url", + MentionChannel: "channel", + MentionUsers: []string{"test-mentionUsers"}, + MentionGroups: []string{"test-mentionGroups"}, + }, + }, + { + name: "Extract all fields + override from secrets", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + EndpointURL: "http://localhost/endpoint_url", + URL: "http://localhost/url-secret", + Token: "test-secret-token", + Recipient: "test-recipient", + Text: "test-text", + Title: "test-title", + Username: "test-username", + IconEmoji: "test-icon", + IconURL: "http://localhost/icon_url", + MentionChannel: "channel", + MentionUsers: []string{"test-mentionUsers"}, + MentionGroups: []string{"test-mentionGroups"}, + }, + }, } for _, c := range cases { diff --git a/receivers/slack/testing.go b/receivers/slack/testing.go new file mode 100644 index 00000000..fa85e8da --- /dev/null +++ b/receivers/slack/testing.go @@ -0,0 +1,23 @@ +package slack + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "endpointUrl": "http://localhost/endpoint_url", + "url": "http://localhost/url", + "token": "test-token", + "recipient": "test-recipient", + "text": "test-text", + "title": "test-title", + "username": "test-username", + "icon_emoji": "test-icon", + "icon_url": "http://localhost/icon_url", + "mentionChannel": "channel", + "mentionUsers": "test-mentionUsers", + "mentionGroups": "test-mentionGroups" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "url": "http://localhost/url-secret", + "token": "test-secret-token" +}` diff --git a/receivers/teams/config_test.go b/receivers/teams/config_test.go index 4eb546f8..9b97a9d1 100644 --- a/receivers/teams/config_test.go +++ b/receivers/teams/config_test.go @@ -52,13 +52,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "url": "http://localhost", - "message" : "test-message", - "title" : "test-title", - "sectiontitle" : "test-second-title" - }`, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ URL: "http://localhost", Message: `test-message`, diff --git a/receivers/teams/testing.go b/receivers/teams/testing.go new file mode 100644 index 00000000..2eed1249 --- /dev/null +++ b/receivers/teams/testing.go @@ -0,0 +1,9 @@ +package teams + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "url": "http://localhost", + "message" : "test-message", + "title" : "test-title", + "sectiontitle" : "test-second-title" +}` diff --git a/receivers/telegram/config_test.go b/receivers/telegram/config_test.go index 1ce1d033..c3f64c4f 100644 --- a/receivers/telegram/config_test.go +++ b/receivers/telegram/config_test.go @@ -98,14 +98,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "bottoken" :"test-token", - "chatid" :"12345678", - "message" :"test-message", - "parse_mode" :"html", - "disable_notifications" :true - }`, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ BotToken: "test-token", ChatID: "12345678", @@ -114,6 +108,18 @@ func TestNewConfig(t *testing.T) { DisableNotifications: true, }, }, + { + name: "Extracts all fields + override from secrets", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + BotToken: "test-secret-token", + ChatID: "12345678", + Message: "test-message", + ParseMode: "HTML", + DisableNotifications: true, + }, + }, { name: "should fail if parse mode not supported", settings: `{"chatid": "12345678", "parse_mode": "test" }`, diff --git a/receivers/telegram/testing.go b/receivers/telegram/testing.go new file mode 100644 index 00000000..c1b6ab8e --- /dev/null +++ b/receivers/telegram/testing.go @@ -0,0 +1,15 @@ +package telegram + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "bottoken" :"test-token", + "chatid" :"12345678", + "message" :"test-message", + "parse_mode" :"html", + "disable_notifications" :true +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "bottoken": "test-secret-token" +}` diff --git a/receivers/testing/testing.go b/receivers/testing/testing.go index 4ff86ae0..50ea91a4 100644 --- a/receivers/testing/testing.go +++ b/receivers/testing/testing.go @@ -1,6 +1,7 @@ package testing import ( + "encoding/json" "net/url" "github.com/grafana/alerting/receivers" @@ -23,3 +24,17 @@ func DecryptForTesting(sjd map[string][]byte) receivers.DecryptFunc { return string(v) } } + +// ReadSecretsJSONForTesting reads a JSON object where all fields are strings and converts it to map[string][]byte that is accepted in tests. +func ReadSecretsJSONForTesting(raw string) map[string][]byte { + r := map[string]string{} + err := json.Unmarshal([]byte(raw), &r) + if err != nil { + panic(err) + } + result := make(map[string][]byte, len(r)) + for key, value := range r { + result[key] = []byte(value) + } + return result +} diff --git a/receivers/threema/config_test.go b/receivers/threema/config_test.go index 6249f5e6..241a6af1 100644 --- a/receivers/threema/config_test.go +++ b/receivers/threema/config_test.go @@ -147,14 +147,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "gateway_id": "*1234567", - "recipient_id": "*1234567", - "api_secret": "test-secret", - "title" : "test-title", - "description": "test-description" - }`, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ GatewayID: "*1234567", RecipientID: "*1234567", @@ -163,6 +157,18 @@ func TestNewConfig(t *testing.T) { Description: "test-description", }, }, + { + name: "Extracts all fields + override from secrets", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + GatewayID: "*1234567", + RecipientID: "*1234567", + APISecret: "test-secret-secret", + Title: "test-title", + Description: "test-description", + }, + }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { diff --git a/receivers/threema/testing.go b/receivers/threema/testing.go new file mode 100644 index 00000000..e974b54a --- /dev/null +++ b/receivers/threema/testing.go @@ -0,0 +1,15 @@ +package threema + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "gateway_id": "*1234567", + "recipient_id": "*1234567", + "api_secret": "test-secret", + "title" : "test-title", + "description": "test-description" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "api_secret": "test-secret-secret" +}` diff --git a/receivers/victorops/config_test.go b/receivers/victorops/config_test.go index 31a7b6f4..b58e8d88 100644 --- a/receivers/victorops/config_test.go +++ b/receivers/victorops/config_test.go @@ -52,13 +52,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "url" : "http://localhost", - "messageType" :"test-messagetype", - "title" :"test-title", - "description" :"test-description" - }`, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ URL: "http://localhost", MessageType: "test-messagetype", diff --git a/receivers/victorops/testing.go b/receivers/victorops/testing.go new file mode 100644 index 00000000..4c2d1799 --- /dev/null +++ b/receivers/victorops/testing.go @@ -0,0 +1,9 @@ +package victorops + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "url" : "http://localhost", + "messageType" :"test-messagetype", + "title" :"test-title", + "description" :"test-description" +}` diff --git a/receivers/webex/config_test.go b/receivers/webex/config_test.go index 6bfb9f4e..d108caa5 100644 --- a/receivers/webex/config_test.go +++ b/receivers/webex/config_test.go @@ -54,13 +54,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "message" :"test-message", - "room_id" :"test-room-id", - "api_url" :"http://localhost", - "bot_token" :"12345" - }`, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ Message: "test-message", RoomID: "test-room-id", @@ -94,6 +89,17 @@ func TestNewConfig(t *testing.T) { Token: "test-token", }, }, + { + name: "Extracts all fields + override from secrets", + settings: FullValidConfigForTesting, + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + Message: "test-message", + RoomID: "test-room-id", + APIURL: "http://localhost", + Token: "12345-secret", + }, + }, } for _, c := range cases { diff --git a/receivers/webex/testing.go b/receivers/webex/testing.go new file mode 100644 index 00000000..f29dcf92 --- /dev/null +++ b/receivers/webex/testing.go @@ -0,0 +1,14 @@ +package webex + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "message" :"test-message", + "room_id" :"test-room-id", + "api_url" :"http://localhost", + "bot_token" :"12345" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "bot_token" :"12345-secret" +}` diff --git a/receivers/webhook/config_test.go b/receivers/webhook/config_test.go index d1139fe1..370ddd9a 100644 --- a/receivers/webhook/config_test.go +++ b/receivers/webhook/config_test.go @@ -70,18 +70,8 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - settings: `{ - "url": "http://localhost", - "httpMethod": "test-httpMethod", - "maxAlerts": "2", - "authorization_scheme": "basic", - "authorization_credentials": "", - "username": "test-user", - "password": "test-pass", - "title": "test-title", - "message": "test-message" - }`, + name: "Extracts all fields", + settings: FullValidConfigForTesting, expectedConfig: Config{ URL: "http://localhost", HTTPMethod: "test-httpMethod", @@ -94,6 +84,22 @@ func TestNewConfig(t *testing.T) { Message: "test-message", }, }, + { + name: "Extracts all fields + override from secrets", + settings: FullValidConfigForTesting, + secretSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + expectedConfig: Config{ + URL: "http://localhost", + HTTPMethod: "test-httpMethod", + MaxAlerts: 2, + AuthorizationScheme: "basic", + AuthorizationCredentials: "", + User: "test-secret-user", + Password: "test-secret-pass", + Title: "test-title", + Message: "test-message", + }, + }, { name: "should parse maxAlerts as string", settings: `{"url": "http://localhost", "maxAlerts": "12" }`, diff --git a/receivers/webhook/testing.go b/receivers/webhook/testing.go new file mode 100644 index 00000000..e224a264 --- /dev/null +++ b/receivers/webhook/testing.go @@ -0,0 +1,20 @@ +package webhook + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "url": "http://localhost", + "httpMethod": "test-httpMethod", + "maxAlerts": "2", + "authorization_scheme": "basic", + "authorization_credentials": "", + "username": "test-user", + "password": "test-pass", + "title": "test-title", + "message": "test-message" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "username": "test-secret-user", + "password": "test-secret-pass" +}` diff --git a/receivers/wecom/config_test.go b/receivers/wecom/config_test.go index 93914845..61ad68a3 100644 --- a/receivers/wecom/config_test.go +++ b/receivers/wecom/config_test.go @@ -184,24 +184,29 @@ func TestNewConfig(t *testing.T) { }, }, { - name: "Extracts all fields", - secureSettings: map[string][]byte{ - "url": []byte("http://localhost"), + name: "Extracts all fields", + settings: FullValidConfigForTesting, + expectedConfig: Config{ + Channel: DefaultChannelType, + EndpointURL: "test-endpointUrl", + URL: "test-url", + AgentID: "test-agent_id", + CorpID: "test-corp_id", + Secret: "test-secret", + MsgType: "markdown", + Message: "test-message", + Title: "test-title", + ToUser: "test-touser", }, - settings: `{ - "endpointUrl" : "test-endpointUrl", - "agent_id" : "test-agent_id", - "corp_id" : "test-corp_id", - "secret" : "test-secret", - "msgtype" : "markdown", - "message" : "test-message", - "title" : "test-title", - "touser" : "test-touser" - }`, + }, + { + name: "Extracts all fields + override from secrets", + secureSettings: receiversTesting.ReadSecretsJSONForTesting(FullValidSecretsForTesting), + settings: FullValidConfigForTesting, expectedConfig: Config{ Channel: DefaultChannelType, EndpointURL: "test-endpointUrl", - URL: "http://localhost", + URL: "test-url-secret", AgentID: "test-agent_id", CorpID: "test-corp_id", Secret: "test-secret", diff --git a/receivers/wecom/testing.go b/receivers/wecom/testing.go new file mode 100644 index 00000000..47e97204 --- /dev/null +++ b/receivers/wecom/testing.go @@ -0,0 +1,19 @@ +package wecom + +// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +const FullValidConfigForTesting = `{ + "endpointUrl" : "test-endpointUrl", + "agent_id" : "test-agent_id", + "corp_id" : "test-corp_id", + "url" : "test-url", + "secret" : "test-secret", + "msgtype" : "markdown", + "message" : "test-message", + "title" : "test-title", + "touser" : "test-touser" +}` + +// FullValidSecretsForTesting is a string representation of JSON object that contains all fields that can be overridden from secrets +const FullValidSecretsForTesting = `{ + "url": "test-url-secret" +}` From 85dbe4219572cac4b7a4ff0e09f48a29a837f669 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Tue, 28 Feb 2023 16:21:23 -0500 Subject: [PATCH 14/15] add tests for BuildReceiverConfiguration --- notify/receivers_test.go | 326 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) diff --git a/notify/receivers_test.go b/notify/receivers_test.go index e65daeed..09349498 100644 --- a/notify/receivers_test.go +++ b/notify/receivers_test.go @@ -2,11 +2,37 @@ package notify import ( "context" + "encoding/base64" + "encoding/json" "errors" + "fmt" + "math/rand" "net/url" + "strings" "testing" "github.com/stretchr/testify/require" + + "github.com/grafana/alerting/receivers" + "github.com/grafana/alerting/receivers/alertmanager" + "github.com/grafana/alerting/receivers/dinding" + "github.com/grafana/alerting/receivers/discord" + "github.com/grafana/alerting/receivers/email" + "github.com/grafana/alerting/receivers/googlechat" + "github.com/grafana/alerting/receivers/kafka" + "github.com/grafana/alerting/receivers/line" + "github.com/grafana/alerting/receivers/opsgenie" + "github.com/grafana/alerting/receivers/pagerduty" + "github.com/grafana/alerting/receivers/pushover" + "github.com/grafana/alerting/receivers/sensugo" + "github.com/grafana/alerting/receivers/slack" + "github.com/grafana/alerting/receivers/teams" + "github.com/grafana/alerting/receivers/telegram" + "github.com/grafana/alerting/receivers/threema" + "github.com/grafana/alerting/receivers/victorops" + "github.com/grafana/alerting/receivers/webex" + "github.com/grafana/alerting/receivers/webhook" + "github.com/grafana/alerting/receivers/wecom" ) func TestInvalidReceiverError_Error(t *testing.T) { @@ -78,3 +104,303 @@ func TestProcessNotifierError(t *testing.T) { require.Equal(t, err, ProcessNotifierError(r, err)) }) } + +func TestBuildReceiverConfiguration(t *testing.T) { + var decrypt receivers.GetDecryptedValueFn = func(ctx context.Context, sjd map[string][]byte, key string, fallback string) string { + v, ok := sjd[key] + if !ok { + return fallback + } + return string(v) + } + + t.Run("should decode secrets from base64", func(t *testing.T) { + recCfg := &APIReceiver{ConfigReceiver: ConfigReceiver{Name: "test-receiver"}} + for notifierType, cfg := range allKnownConfigs { + recCfg.Receivers = append(recCfg.Receivers, cfg.getRawNotifierConfig(notifierType)) + } + counter := 0 + decryptCount := func(ctx context.Context, sjd map[string][]byte, key string, fallback string) string { + counter++ + return decrypt(ctx, sjd, key, fallback) + } + _, _ = BuildReceiverConfiguration(context.Background(), recCfg, decryptCount) + require.Greater(t, counter, 0) + }) + t.Run("should fail if at least one config is invalid", func(t *testing.T) { + recCfg := &APIReceiver{ConfigReceiver: ConfigReceiver{Name: "test-receiver"}} + for notifierType, cfg := range allKnownConfigs { + recCfg.Receivers = append(recCfg.Receivers, cfg.getRawNotifierConfig(notifierType)) + } + bad := &GrafanaReceiver{ + UID: "invalid-test", + Name: "invalid-test", + Type: "slack", + Settings: json.RawMessage(`{ "test" : "test" }`), + } + recCfg.Receivers = append(recCfg.Receivers, bad) + + parsed, err := BuildReceiverConfiguration(context.Background(), recCfg, decrypt) + require.NotNil(t, err) + require.Equal(t, GrafanaReceiverConfig{}, parsed) + require.IsType(t, &ReceiverValidationError{}, err) + typedError := err.(*ReceiverValidationError) + require.NotNil(t, typedError.Cfg) + require.Equal(t, bad, typedError.Cfg) + require.ErrorContains(t, err, fmt.Sprintf(`failed to validate receiver "%s" of type "%s"`, bad.Name, bad.Type)) + }) + t.Run("should accept empty config", func(t *testing.T) { + recCfg := &APIReceiver{ConfigReceiver: ConfigReceiver{Name: "test-receiver"}} + parsed, err := BuildReceiverConfiguration(context.Background(), recCfg, decrypt) + require.NoError(t, err) + require.Equal(t, recCfg.Name, parsed.Name) + }) + t.Run("should fail if secrets decoding fails", func(t *testing.T) { + recCfg := &APIReceiver{ConfigReceiver: ConfigReceiver{Name: "test-receiver"}} + for notifierType, cfg := range allKnownConfigs { + notifierRaw := cfg.getRawNotifierConfig(notifierType) + if len(notifierRaw.SecureSettings) == 0 { + continue + } + for key := range notifierRaw.SecureSettings { + notifierRaw.SecureSettings[key] = "bad base-64" + } + recCfg.Receivers = append(recCfg.Receivers, notifierRaw) + } + + parsed, err := BuildReceiverConfiguration(context.Background(), recCfg, decrypt) + require.NotNil(t, err) + require.Equal(t, GrafanaReceiverConfig{}, parsed) + require.IsType(t, &ReceiverValidationError{}, err) + typedError := err.(*ReceiverValidationError) + require.NotNil(t, typedError.Cfg) + require.ErrorContains(t, err, "failed to decode secure settings") + }) + t.Run("should fail if notifier type is unknown", func(t *testing.T) { + recCfg := &APIReceiver{ConfigReceiver: ConfigReceiver{Name: "test-receiver"}} + for notifierType, cfg := range allKnownConfigs { + recCfg.Receivers = append(recCfg.Receivers, cfg.getRawNotifierConfig(notifierType)) + } + bad := &GrafanaReceiver{ + UID: "test", + Name: "test", + Type: fmt.Sprintf("invalid-%d", rand.Uint32()), + Settings: json.RawMessage(`{ "test" : "test" }`), + } + recCfg.Receivers = append(recCfg.Receivers, bad) + + parsed, err := BuildReceiverConfiguration(context.Background(), recCfg, decrypt) + require.NotNil(t, err) + require.Equal(t, GrafanaReceiverConfig{}, parsed) + require.IsType(t, &ReceiverValidationError{}, err) + typedError := err.(*ReceiverValidationError) + require.NotNil(t, typedError.Cfg) + require.Equal(t, bad, typedError.Cfg) + require.ErrorContains(t, err, fmt.Sprintf("notifier %s is not supported", bad.Type)) + }) + t.Run("should recognize all known types", func(t *testing.T) { + recCfg := &APIReceiver{ConfigReceiver: ConfigReceiver{Name: "test-receiver"}} + for notifierType, cfg := range allKnownConfigs { + recCfg.Receivers = append(recCfg.Receivers, cfg.getRawNotifierConfig(notifierType)) + } + parsed, err := BuildReceiverConfiguration(context.Background(), recCfg, decrypt) + require.NoError(t, err) + require.Equal(t, recCfg.Name, parsed.Name) + require.Len(t, parsed.AlertmanagerConfigs, 1) + require.Len(t, parsed.DingdingConfigs, 1) + require.Len(t, parsed.DiscordConfigs, 1) + require.Len(t, parsed.EmailConfigs, 1) + require.Len(t, parsed.GooglechatConfigs, 1) + require.Len(t, parsed.KafkaConfigs, 1) + require.Len(t, parsed.LineConfigs, 1) + require.Len(t, parsed.OpsgenieConfigs, 1) + require.Len(t, parsed.PagerdutyConfigs, 1) + require.Len(t, parsed.PushoverConfigs, 1) + require.Len(t, parsed.SensugoConfigs, 1) + require.Len(t, parsed.SlackConfigs, 1) + require.Len(t, parsed.TeamsConfigs, 1) + require.Len(t, parsed.TelegramConfigs, 1) + require.Len(t, parsed.ThreemaConfigs, 1) + require.Len(t, parsed.VictoropsConfigs, 1) + require.Len(t, parsed.WebhookConfigs, 1) + require.Len(t, parsed.WecomConfigs, 1) + require.Len(t, parsed.WebexConfigs, 1) + + t.Run("should populate metadata", func(t *testing.T) { + var all []receivers.Metadata + all = append(all, getMetadata(parsed.AlertmanagerConfigs)...) + all = append(all, getMetadata(parsed.DingdingConfigs)...) + all = append(all, getMetadata(parsed.DiscordConfigs)...) + all = append(all, getMetadata(parsed.EmailConfigs)...) + all = append(all, getMetadata(parsed.GooglechatConfigs)...) + all = append(all, getMetadata(parsed.KafkaConfigs)...) + all = append(all, getMetadata(parsed.LineConfigs)...) + all = append(all, getMetadata(parsed.OpsgenieConfigs)...) + all = append(all, getMetadata(parsed.PagerdutyConfigs)...) + all = append(all, getMetadata(parsed.PushoverConfigs)...) + all = append(all, getMetadata(parsed.SensugoConfigs)...) + all = append(all, getMetadata(parsed.SlackConfigs)...) + all = append(all, getMetadata(parsed.TeamsConfigs)...) + all = append(all, getMetadata(parsed.TelegramConfigs)...) + all = append(all, getMetadata(parsed.ThreemaConfigs)...) + all = append(all, getMetadata(parsed.VictoropsConfigs)...) + all = append(all, getMetadata(parsed.WebhookConfigs)...) + all = append(all, getMetadata(parsed.WecomConfigs)...) + all = append(all, getMetadata(parsed.WebexConfigs)...) + + for idx, meta := range all { + require.NotEmptyf(t, meta.Type, "%s notifier (idx: %d) '%s' uid: '%s'.", meta.Type, idx, meta.Name, meta.UID) + require.NotEmptyf(t, meta.UID, "%s notifier (idx: %d) '%s' uid: '%s'.", meta.Type, idx, meta.Name, meta.UID) + require.NotEmptyf(t, meta.Name, "%s notifier (idx: %d) '%s' uid: '%s'.", meta.Type, idx, meta.Name, meta.UID) + var notifierRaw *GrafanaReceiver + for _, receiver := range recCfg.Receivers { + if receiver.Type == meta.Type && receiver.UID == meta.UID && receiver.Name == meta.Name { + notifierRaw = receiver + break + } + } + require.NotNilf(t, notifierRaw, "cannot find raw settings for %s notifier '%s' uid: '%s'.", meta.Type, meta.Name, meta.UID) + require.Equalf(t, notifierRaw.DisableResolveMessage, meta.DisableResolveMessage, "%s notifier '%s' uid: '%s'.", meta.Type, meta.Name, meta.UID) + } + }) + }) + t.Run("should recognize type in any case", func(t *testing.T) { + recCfg := &APIReceiver{ConfigReceiver: ConfigReceiver{Name: "test-receiver"}} + for notifierType, cfg := range allKnownConfigs { + notifierRaw := cfg.getRawNotifierConfig(notifierType) + notifierRaw.Type = strings.ToUpper(notifierRaw.Type) + recCfg.Receivers = append(recCfg.Receivers, cfg.getRawNotifierConfig(notifierType)) + } + parsed, err := BuildReceiverConfiguration(context.Background(), recCfg, decrypt) + require.NoError(t, err) + require.Len(t, parsed.AlertmanagerConfigs, 1) + require.Len(t, parsed.DingdingConfigs, 1) + require.Len(t, parsed.DiscordConfigs, 1) + require.Len(t, parsed.EmailConfigs, 1) + require.Len(t, parsed.GooglechatConfigs, 1) + require.Len(t, parsed.KafkaConfigs, 1) + require.Len(t, parsed.LineConfigs, 1) + require.Len(t, parsed.OpsgenieConfigs, 1) + require.Len(t, parsed.PagerdutyConfigs, 1) + require.Len(t, parsed.PushoverConfigs, 1) + require.Len(t, parsed.SensugoConfigs, 1) + require.Len(t, parsed.SlackConfigs, 1) + require.Len(t, parsed.TeamsConfigs, 1) + require.Len(t, parsed.TelegramConfigs, 1) + require.Len(t, parsed.ThreemaConfigs, 1) + require.Len(t, parsed.VictoropsConfigs, 1) + require.Len(t, parsed.WebhookConfigs, 1) + require.Len(t, parsed.WecomConfigs, 1) + require.Len(t, parsed.WebexConfigs, 1) + + }) +} + +func getMetadata[T any](notifiers []*NotifierConfig[T]) []receivers.Metadata { + result := make([]receivers.Metadata, 0, len(notifiers)) + for _, notifier := range notifiers { + result = append(result, notifier.Metadata) + } + return result +} + +var allKnownConfigs = map[string]notifierConfigTest{ + "prometheus-alertmanager": { + notifierType: "prometheus-alertmanager", + config: alertmanager.FullValidConfigForTesting, + secrets: alertmanager.FullValidSecretsForTesting, + }, + "dingding": {notifierType: "dingding", + config: dinding.FullValidConfigForTesting, + }, + "discord": {notifierType: "discord", + config: discord.FullValidConfigForTesting, + }, + "email": {notifierType: "email", + config: email.FullValidConfigForTesting, + }, + "googlechat": {notifierType: "googlechat", + config: googlechat.FullValidConfigForTesting, + }, + "kafka": {notifierType: "kafka", + config: kafka.FullValidConfigForTesting, + secrets: kafka.FullValidSecretsForTesting, + }, + "line": {notifierType: "line", + config: line.FullValidConfigForTesting, + secrets: line.FullValidSecretsForTesting, + }, + "opsgenie": {notifierType: "opsgenie", + config: opsgenie.FullValidConfigForTesting, + secrets: opsgenie.FullValidSecretsForTesting, + }, + "pagerduty": {notifierType: "pagerduty", + config: pagerduty.FullValidConfigForTesting, + secrets: pagerduty.FullValidSecretsForTesting, + }, + "pushover": {notifierType: "pushover", + config: pushover.FullValidConfigForTesting, + secrets: pushover.FullValidSecretsForTesting, + }, + "sensugo": {notifierType: "sensugo", + config: sensugo.FullValidConfigForTesting, + secrets: sensugo.FullValidSecretsForTesting, + }, + "slack": {notifierType: "slack", + config: slack.FullValidConfigForTesting, + secrets: slack.FullValidSecretsForTesting, + }, + "teams": {notifierType: "teams", + config: teams.FullValidConfigForTesting, + }, + "telegram": {notifierType: "telegram", + config: telegram.FullValidConfigForTesting, + secrets: telegram.FullValidSecretsForTesting, + }, + "threema": {notifierType: "threema", + config: threema.FullValidConfigForTesting, + secrets: threema.FullValidSecretsForTesting, + }, + "victorops": {notifierType: "victorops", + config: victorops.FullValidConfigForTesting, + }, + "webhook": {notifierType: "webhook", + config: webhook.FullValidConfigForTesting, + secrets: webhook.FullValidSecretsForTesting, + }, + "wecom": {notifierType: "wecom", + config: wecom.FullValidConfigForTesting, + secrets: wecom.FullValidSecretsForTesting, + }, + "webex": {notifierType: "webex", + config: webex.FullValidConfigForTesting, + secrets: webex.FullValidSecretsForTesting, + }, +} + +type notifierConfigTest struct { + notifierType string + config string + secrets string +} + +func (n notifierConfigTest) getRawNotifierConfig(name string) *GrafanaReceiver { + secrets := make(map[string]string) + if n.secrets != "" { + err := json.Unmarshal([]byte(n.secrets), &secrets) + if err != nil { + panic(err) + } + for key, value := range secrets { + secrets[key] = base64.StdEncoding.EncodeToString([]byte(value)) + } + } + return &GrafanaReceiver{ + UID: fmt.Sprintf("%s-%d", name, rand.Uint32()), + Name: name, + Type: n.notifierType, + DisableResolveMessage: rand.Int()%2 == 0, + Settings: json.RawMessage(n.config), + SecureSettings: secrets, + } +} From 542a0d1bc06e2ec71fb823c205f3a0f4d3fa6795 Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Fri, 3 Mar 2023 10:43:13 -0500 Subject: [PATCH 15/15] apply Josh's suggestions for comments --- notify/receivers.go | 4 ++-- receivers/alertmanager/testing.go | 2 +- receivers/dinding/testing.go | 2 +- receivers/discord/testing.go | 2 +- receivers/email/testing.go | 2 +- receivers/googlechat/testing.go | 2 +- receivers/kafka/testing.go | 2 +- receivers/line/testing.go | 2 +- receivers/opsgenie/testing.go | 2 +- receivers/pagerduty/testing.go | 2 +- receivers/pushover/testing.go | 2 +- receivers/sensugo/testing.go | 2 +- receivers/slack/testing.go | 2 +- receivers/teams/testing.go | 2 +- receivers/telegram/testing.go | 2 +- receivers/threema/testing.go | 2 +- receivers/victorops/testing.go | 2 +- receivers/webex/testing.go | 2 +- receivers/webhook/testing.go | 2 +- receivers/wecom/testing.go | 2 +- 20 files changed, 21 insertions(+), 21 deletions(-) diff --git a/notify/receivers.go b/notify/receivers.go index 21ed1dbb..e116b548 100644 --- a/notify/receivers.go +++ b/notify/receivers.go @@ -347,7 +347,7 @@ type NotifierConfig[T interface{}] struct { Settings T } -// BuildReceiverConfiguration parses, decrypts and validates the APIReceiver. GrafanaReceiverConfig that contains configurations of all notifiers configurations for this receiver +// BuildReceiverConfiguration parses, decrypts and validates the APIReceiver. func BuildReceiverConfiguration(ctx context.Context, api *APIReceiver, decrypt receivers.GetDecryptedValueFn) (GrafanaReceiverConfig, error) { result := GrafanaReceiverConfig{ Name: api.Name, @@ -364,7 +364,7 @@ func BuildReceiverConfiguration(ctx context.Context, api *APIReceiver, decrypt r return result, nil } -// parseNotifier parses receivers and populates corresponding field in GrafanaReceiverConfig. Returns error if configuration cannot be parsed +// parseNotifier parses receivers and populates the corresponding field in GrafanaReceiverConfig. Returns an error if the configuration cannot be parsed. func parseNotifier(ctx context.Context, result *GrafanaReceiverConfig, receiver *GrafanaReceiver, decrypt receivers.GetDecryptedValueFn) error { secureSettings, err := decodeSecretsFromBase64(receiver.SecureSettings) if err != nil { diff --git a/receivers/alertmanager/testing.go b/receivers/alertmanager/testing.go index 28d6c6cc..267842ac 100644 --- a/receivers/alertmanager/testing.go +++ b/receivers/alertmanager/testing.go @@ -1,6 +1,6 @@ package alertmanager -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "url": "https://alertmanager-01.com", "basicAuthUser": "grafana", diff --git a/receivers/dinding/testing.go b/receivers/dinding/testing.go index d82ce7c3..9238c39f 100644 --- a/receivers/dinding/testing.go +++ b/receivers/dinding/testing.go @@ -1,6 +1,6 @@ package dinding -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "url": "http://localhost", "message": "{{ len .Alerts.Firing }} alerts are firing, {{ len .Alerts.Resolved }} are resolved", diff --git a/receivers/discord/testing.go b/receivers/discord/testing.go index bf32431f..f0a483bc 100644 --- a/receivers/discord/testing.go +++ b/receivers/discord/testing.go @@ -1,6 +1,6 @@ package discord -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "url": "http://localhost", "title": "test-title", diff --git a/receivers/email/testing.go b/receivers/email/testing.go index 43530c95..ce91ade1 100644 --- a/receivers/email/testing.go +++ b/receivers/email/testing.go @@ -1,6 +1,6 @@ package email -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "addresses": "test@grafana.com", "subject": "test-subject", diff --git a/receivers/googlechat/testing.go b/receivers/googlechat/testing.go index 792d7efb..b82663f3 100644 --- a/receivers/googlechat/testing.go +++ b/receivers/googlechat/testing.go @@ -1,6 +1,6 @@ package googlechat -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "url": "http://localhost", "title": "test-title", diff --git a/receivers/kafka/testing.go b/receivers/kafka/testing.go index 6c6412db..1422c22a 100644 --- a/receivers/kafka/testing.go +++ b/receivers/kafka/testing.go @@ -1,6 +1,6 @@ package kafka -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "kafkaRestProxy": "http://localhost/", "kafkaTopic" : "test-topic", diff --git a/receivers/line/testing.go b/receivers/line/testing.go index 4efe6aed..d23eeb77 100644 --- a/receivers/line/testing.go +++ b/receivers/line/testing.go @@ -1,6 +1,6 @@ package line -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "token": "test", "title": "test-title", diff --git a/receivers/opsgenie/testing.go b/receivers/opsgenie/testing.go index aebc7acf..a46e7fc0 100644 --- a/receivers/opsgenie/testing.go +++ b/receivers/opsgenie/testing.go @@ -1,6 +1,6 @@ package opsgenie -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "apiUrl" : "http://localhost", "apiKey": "test-api-key", diff --git a/receivers/pagerduty/testing.go b/receivers/pagerduty/testing.go index a64765f1..6d05c796 100644 --- a/receivers/pagerduty/testing.go +++ b/receivers/pagerduty/testing.go @@ -1,6 +1,6 @@ package pagerduty -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "integrationKey": "test-api-key", "severity" : "test-severity", diff --git a/receivers/pushover/testing.go b/receivers/pushover/testing.go index dc2bb5fd..c323d7dc 100644 --- a/receivers/pushover/testing.go +++ b/receivers/pushover/testing.go @@ -1,6 +1,6 @@ package pushover -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "priority": 1, "okPriority": 2, diff --git a/receivers/sensugo/testing.go b/receivers/sensugo/testing.go index 8fd7217d..eb6215be 100644 --- a/receivers/sensugo/testing.go +++ b/receivers/sensugo/testing.go @@ -1,6 +1,6 @@ package sensugo -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "url": "http://localhost", "apikey": "test-api-key", diff --git a/receivers/slack/testing.go b/receivers/slack/testing.go index fa85e8da..0d7f1396 100644 --- a/receivers/slack/testing.go +++ b/receivers/slack/testing.go @@ -1,6 +1,6 @@ package slack -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "endpointUrl": "http://localhost/endpoint_url", "url": "http://localhost/url", diff --git a/receivers/teams/testing.go b/receivers/teams/testing.go index 2eed1249..9d504b58 100644 --- a/receivers/teams/testing.go +++ b/receivers/teams/testing.go @@ -1,6 +1,6 @@ package teams -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "url": "http://localhost", "message" : "test-message", diff --git a/receivers/telegram/testing.go b/receivers/telegram/testing.go index c1b6ab8e..460a4c55 100644 --- a/receivers/telegram/testing.go +++ b/receivers/telegram/testing.go @@ -1,6 +1,6 @@ package telegram -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "bottoken" :"test-token", "chatid" :"12345678", diff --git a/receivers/threema/testing.go b/receivers/threema/testing.go index e974b54a..88d6014f 100644 --- a/receivers/threema/testing.go +++ b/receivers/threema/testing.go @@ -1,6 +1,6 @@ package threema -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "gateway_id": "*1234567", "recipient_id": "*1234567", diff --git a/receivers/victorops/testing.go b/receivers/victorops/testing.go index 4c2d1799..e616a5b6 100644 --- a/receivers/victorops/testing.go +++ b/receivers/victorops/testing.go @@ -1,6 +1,6 @@ package victorops -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "url" : "http://localhost", "messageType" :"test-messagetype", diff --git a/receivers/webex/testing.go b/receivers/webex/testing.go index f29dcf92..d16df061 100644 --- a/receivers/webex/testing.go +++ b/receivers/webex/testing.go @@ -1,6 +1,6 @@ package webex -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "message" :"test-message", "room_id" :"test-room-id", diff --git a/receivers/webhook/testing.go b/receivers/webhook/testing.go index e224a264..e73877f4 100644 --- a/receivers/webhook/testing.go +++ b/receivers/webhook/testing.go @@ -1,6 +1,6 @@ package webhook -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "url": "http://localhost", "httpMethod": "test-httpMethod", diff --git a/receivers/wecom/testing.go b/receivers/wecom/testing.go index 47e97204..f6f23e7e 100644 --- a/receivers/wecom/testing.go +++ b/receivers/wecom/testing.go @@ -1,6 +1,6 @@ package wecom -// FullValidConfigForTesting a string representation of JSON object that contains all fields supported by the notifier Config. Can be used without secrets +// FullValidConfigForTesting is a string representation of a JSON object that contains all fields supported by the notifier Config. It can be used without secrets. const FullValidConfigForTesting = `{ "endpointUrl" : "test-endpointUrl", "agent_id" : "test-agent_id",