-
Notifications
You must be signed in to change notification settings - Fork 50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parse notifier configs without creation of notifiers #62
Changes from 4 commits
614d424
08f941f
d5fde26
cc80c05
ad0a807
1029b57
f9874e2
c8e292b
9eb5100
6668e76
4e1e974
cf6fc3b
931fd6a
85dbe42
542a0d1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,17 +2,41 @@ package notify | |||||
|
||||||
import ( | ||||||
"context" | ||||||
"encoding/base64" | ||||||
"encoding/json" | ||||||
"errors" | ||||||
"fmt" | ||||||
"net/url" | ||||||
"sort" | ||||||
"strings" | ||||||
"time" | ||||||
|
||||||
"github.com/prometheus/alertmanager/config" | ||||||
"github.com/prometheus/alertmanager/notify" | ||||||
"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 ( | ||||||
|
@@ -47,12 +71,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"` | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this will actually help move forward with refactoring in Grafana because we have to do conversions back and forth for that field. |
||||||
SecureSettings map[string]string `json:"secureSettings"` | ||||||
} | ||||||
|
||||||
type ConfigReceiver = config.Receiver | ||||||
|
@@ -292,3 +316,189 @@ func ProcessNotifierError(config *GrafanaReceiver, err error) error { | |||||
|
||||||
return err | ||||||
} | ||||||
|
||||||
// GrafanaReceiverTyped represents a parsed and validated APIReceiver | ||||||
type GrafanaReceiverTyped struct { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should be called GrafanaReceiver.. but that name is already taken. Probably it makes sense to rename that to GrafanaNotifierSettings and this to GrafanaReceiverConfiguration There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remind me again how is this going to be used that will benefit from generics? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That is a way to have Metadata for all notifier configs |
||||||
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) { | ||||||
yuri-tceretian marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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) | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I'd like to move away from these nested functions, they make this part of the code really hard to read. Why not just make this a separate function, even better why not embrace the pointer to structs here all of this a function that's part of The code as written appears to take a functional approach - which even though is well intended I personally find it highly un-idiomatic. For example, in this function the main actor is: for _, receiver := range api.Receivers {
err := parseConfig(receiver)
if err != nil {
return GrafanaReceiverTyped{}, &ReceiverValidationError{
Cfg: receiver,
Err: err,
}
}
} Yet, I'm presented with A good example of the use of inline functions is when you care about concurrency and sending that execution to a goroutine - this is not any of that. |
||||||
|
||||||
switch strings.ToLower(receiver.Type) { | ||||||
case "prometheus-alertmanager": | ||||||
return createReceiver[alertmanager.Config](receiver)(alertmanager.NewConfig(receiver.Settings, decryptFn))(func(f *NotifierConfig[alertmanager.Config]) { | ||||||
yuri-tceretian marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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{}, &ReceiverValidationError{ | ||||||
Cfg: 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 | ||||||
} | ||||||
} | ||||||
} | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a utility func to propagate errors in the case of validation failure. |
||||||
|
||||||
type ReceiverValidationError struct { | ||||||
Err error | ||||||
Cfg *GrafanaReceiver | ||||||
} | ||||||
|
||||||
func (e ReceiverValidationError) 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()) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Other errors seem to use notifier instead? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that is expected because this error is returned by ValidateReceiver method... not a particular notifier. |
||||||
return s | ||||||
} | ||||||
|
||||||
func (e ReceiverValidationError) Unwrap() error { return e.Err } |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -32,6 +32,14 @@ type NotificationChannelConfig struct { | |
SecureSettings map[string][]byte `json:"secureSettings"` | ||
} | ||
|
||
// NotifierInfo contains metadata of the notifier. Name, UID, Type, etc | ||
yuri-tceretian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
type NotifierInfo struct { | ||
yuri-tceretian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
UID string | ||
Name string | ||
Type string | ||
DisableResolveMessage bool | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This struct will replace NotificationChannelConfig in #63 |
||
|
||
type FactoryConfig struct { | ||
Config *NotificationChannelConfig | ||
// Used by some receivers to include as part of the source | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
temporary, this will go away in #63