diff --git a/pkg/services/ngalert/notifier/channels/alertmanager.go b/pkg/services/ngalert/notifier/channels/alertmanager.go index 14c131fa878..309eae51fe4 100644 --- a/pkg/services/ngalert/notifier/channels/alertmanager.go +++ b/pkg/services/ngalert/notifier/channels/alertmanager.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" @@ -49,7 +48,7 @@ func NewAlertmanagerNotifier(model *NotificationChannelConfig, _ *template.Templ basicAuthPassword := fn(context.Background(), model.SecureSettings, "basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString(), setting.SecretKey) return &AlertmanagerNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, DisableResolveMessage: model.DisableResolveMessage, @@ -64,7 +63,7 @@ func NewAlertmanagerNotifier(model *NotificationChannelConfig, _ *template.Templ // AlertmanagerNotifier sends alert notifications to the alert manager type AlertmanagerNotifier struct { - old_notifiers.NotifierBase + *Base urls []*url.URL basicAuthUser string diff --git a/pkg/services/ngalert/notifier/channels/base.go b/pkg/services/ngalert/notifier/channels/base.go new file mode 100644 index 00000000000..6f261bc7ef0 --- /dev/null +++ b/pkg/services/ngalert/notifier/channels/base.go @@ -0,0 +1,32 @@ +package channels + +import ( + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/models" +) + +// Base is the base implementation of a notifier. It contains the common fields across all notifier types. +type Base struct { + Name string + Type string + UID string + IsDefault bool + DisableResolveMessage bool + + log log.Logger +} + +func (n *Base) GetDisableResolveMessage() bool { + return n.DisableResolveMessage +} + +func NewBase(model *models.AlertNotification) *Base { + return &Base{ + UID: model.Uid, + Name: model.Name, + IsDefault: model.IsDefault, + Type: model.Type, + DisableResolveMessage: model.DisableResolveMessage, + log: log.New("alerting.notifier." + model.Name), + } +} diff --git a/pkg/services/ngalert/notifier/channels/dingding.go b/pkg/services/ngalert/notifier/channels/dingding.go index 9f06f0ec56c..e6ac8d5f895 100644 --- a/pkg/services/ngalert/notifier/channels/dingding.go +++ b/pkg/services/ngalert/notifier/channels/dingding.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" ) const defaultDingdingMsgType = "link" @@ -31,7 +30,7 @@ func NewDingDingNotifier(model *NotificationChannelConfig, t *template.Template) msgType := model.Settings.Get("msgType").MustString(defaultDingdingMsgType) return &DingDingNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, @@ -48,7 +47,7 @@ func NewDingDingNotifier(model *NotificationChannelConfig, t *template.Template) // DingDingNotifier is responsible for sending alert notifications to ding ding. type DingDingNotifier struct { - old_notifiers.NotifierBase + *Base MsgType string URL string Message string diff --git a/pkg/services/ngalert/notifier/channels/discord.go b/pkg/services/ngalert/notifier/channels/discord.go index 0c086a96f71..faa1b53093a 100644 --- a/pkg/services/ngalert/notifier/channels/discord.go +++ b/pkg/services/ngalert/notifier/channels/discord.go @@ -13,12 +13,11 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" ) type DiscordNotifier struct { - old_notifiers.NotifierBase + *Base log log.Logger tmpl *template.Template Content string @@ -41,7 +40,7 @@ func NewDiscordNotifier(model *NotificationChannelConfig, t *template.Template) content := model.Settings.Get("message").MustString(`{{ template "default.message" . }}`) return &DiscordNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/email.go b/pkg/services/ngalert/notifier/channels/email.go index 5deb62ea829..574f3d2ff91 100644 --- a/pkg/services/ngalert/notifier/channels/email.go +++ b/pkg/services/ngalert/notifier/channels/email.go @@ -11,14 +11,13 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/util" ) // EmailNotifier is responsible for sending // alert notifications over email. type EmailNotifier struct { - old_notifiers.NotifierBase + *Base Addresses []string SingleEmail bool Message string @@ -44,7 +43,7 @@ func NewEmailNotifier(model *NotificationChannelConfig, t *template.Template) (* addresses := util.SplitEmails(addressesString) return &EmailNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/googlechat.go b/pkg/services/ngalert/notifier/channels/googlechat.go index 2f0448ac723..307026fb8cf 100644 --- a/pkg/services/ngalert/notifier/channels/googlechat.go +++ b/pkg/services/ngalert/notifier/channels/googlechat.go @@ -12,14 +12,13 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" ) // GoogleChatNotifier is responsible for sending // alert notifications to Google chat. type GoogleChatNotifier struct { - old_notifiers.NotifierBase + *Base URL string log log.Logger tmpl *template.Template @@ -32,7 +31,7 @@ func NewGoogleChatNotifier(model *NotificationChannelConfig, t *template.Templat } return &GoogleChatNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/kafka.go b/pkg/services/ngalert/notifier/channels/kafka.go index d9bad9e777d..bcfd9d052c0 100644 --- a/pkg/services/ngalert/notifier/channels/kafka.go +++ b/pkg/services/ngalert/notifier/channels/kafka.go @@ -13,13 +13,12 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" ) // KafkaNotifier is responsible for sending // alert notifications to Kafka. type KafkaNotifier struct { - old_notifiers.NotifierBase + *Base Endpoint string Topic string log log.Logger @@ -38,7 +37,7 @@ func NewKafkaNotifier(model *NotificationChannelConfig, t *template.Template) (* } return &KafkaNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/line.go b/pkg/services/ngalert/notifier/channels/line.go index a9268747561..4c7c4a7182d 100644 --- a/pkg/services/ngalert/notifier/channels/line.go +++ b/pkg/services/ngalert/notifier/channels/line.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" @@ -27,7 +26,7 @@ func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn } return &LineNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, @@ -43,7 +42,7 @@ func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn // LineNotifier is responsible for sending // alert notifications to LINE. type LineNotifier struct { - old_notifiers.NotifierBase + *Base Token string log log.Logger tmpl *template.Template diff --git a/pkg/services/ngalert/notifier/channels/opsgenie.go b/pkg/services/ngalert/notifier/channels/opsgenie.go index 64ee51be067..1fbbbcf52b5 100644 --- a/pkg/services/ngalert/notifier/channels/opsgenie.go +++ b/pkg/services/ngalert/notifier/channels/opsgenie.go @@ -11,7 +11,6 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/template" @@ -32,7 +31,7 @@ var ( // OpsgenieNotifier is responsible for sending alert notifications to Opsgenie. type OpsgenieNotifier struct { - old_notifiers.NotifierBase + *Base APIKey string APIUrl string AutoClose bool @@ -63,7 +62,7 @@ func NewOpsgenieNotifier(model *NotificationChannelConfig, t *template.Template, } return &OpsgenieNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/pagerduty.go b/pkg/services/ngalert/notifier/channels/pagerduty.go index c970cccb1c5..997139b7159 100644 --- a/pkg/services/ngalert/notifier/channels/pagerduty.go +++ b/pkg/services/ngalert/notifier/channels/pagerduty.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/template" @@ -29,7 +28,7 @@ var ( // PagerdutyNotifier is responsible for sending // alert notifications to pagerduty type PagerdutyNotifier struct { - old_notifiers.NotifierBase + *Base Key string Severity string CustomDetails map[string]string @@ -53,7 +52,7 @@ func NewPagerdutyNotifier(model *NotificationChannelConfig, t *template.Template } return &PagerdutyNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/pushover.go b/pkg/services/ngalert/notifier/channels/pushover.go index 9f7155ebc3f..7e810cb1f1d 100644 --- a/pkg/services/ngalert/notifier/channels/pushover.go +++ b/pkg/services/ngalert/notifier/channels/pushover.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" @@ -24,7 +23,7 @@ var ( // PushoverNotifier is responsible for sending // alert notifications to Pushover type PushoverNotifier struct { - old_notifiers.NotifierBase + *Base UserKey string APIToken string AlertingPriority int @@ -70,7 +69,7 @@ func NewPushoverNotifier(model *NotificationChannelConfig, t *template.Template, return nil, receiverInitError{Cfg: *model, Reason: "API token not found"} } return &PushoverNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/sensugo.go b/pkg/services/ngalert/notifier/channels/sensugo.go index 8eb7a620811..1912374c1eb 100644 --- a/pkg/services/ngalert/notifier/channels/sensugo.go +++ b/pkg/services/ngalert/notifier/channels/sensugo.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" @@ -18,7 +17,7 @@ import ( ) type SensuGoNotifier struct { - old_notifiers.NotifierBase + *Base log log.Logger tmpl *template.Template @@ -48,7 +47,7 @@ func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template, } return &SensuGoNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/slack.go b/pkg/services/ngalert/notifier/channels/slack.go index 9831e093f08..85150eee979 100644 --- a/pkg/services/ngalert/notifier/channels/slack.go +++ b/pkg/services/ngalert/notifier/channels/slack.go @@ -16,7 +16,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/template" @@ -26,7 +25,7 @@ import ( // SlackNotifier is responsible for sending // alert notification to Slack. type SlackNotifier struct { - old_notifiers.NotifierBase + *Base log log.Logger tmpl *template.Template @@ -106,7 +105,7 @@ func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template, fn } return &SlackNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/teams.go b/pkg/services/ngalert/notifier/channels/teams.go index 09599576746..2dfa4927851 100644 --- a/pkg/services/ngalert/notifier/channels/teams.go +++ b/pkg/services/ngalert/notifier/channels/teams.go @@ -11,13 +11,12 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" ) // TeamsNotifier is responsible for sending // alert notifications to Microsoft teams. type TeamsNotifier struct { - old_notifiers.NotifierBase + *Base URL string Message string tmpl *template.Template @@ -36,7 +35,7 @@ func NewTeamsNotifier(model *NotificationChannelConfig, t *template.Template) (* } return &TeamsNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/telegram.go b/pkg/services/ngalert/notifier/channels/telegram.go index e7e772dfbb5..d741626c4f6 100644 --- a/pkg/services/ngalert/notifier/channels/telegram.go +++ b/pkg/services/ngalert/notifier/channels/telegram.go @@ -9,7 +9,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" @@ -22,7 +21,7 @@ var ( // TelegramNotifier is responsible for sending // alert notifications to Telegram. type TelegramNotifier struct { - old_notifiers.NotifierBase + *Base BotToken string ChatID string Message string @@ -49,7 +48,7 @@ func NewTelegramNotifier(model *NotificationChannelConfig, t *template.Template, } return &TelegramNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/threema.go b/pkg/services/ngalert/notifier/channels/threema.go index 53c8568ad0d..09f75a0be72 100644 --- a/pkg/services/ngalert/notifier/channels/threema.go +++ b/pkg/services/ngalert/notifier/channels/threema.go @@ -10,7 +10,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/types" @@ -24,7 +23,7 @@ var ( // ThreemaNotifier is responsible for sending // alert notifications to Threema. type ThreemaNotifier struct { - old_notifiers.NotifierBase + *Base GatewayID string RecipientID string APISecret string @@ -63,7 +62,7 @@ func NewThreemaNotifier(model *NotificationChannelConfig, t *template.Template, } return &ThreemaNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/ngalert/notifier/channels/victorops.go b/pkg/services/ngalert/notifier/channels/victorops.go index 4a1c74342fb..39d9dfaf9ad 100644 --- a/pkg/services/ngalert/notifier/channels/victorops.go +++ b/pkg/services/ngalert/notifier/channels/victorops.go @@ -14,7 +14,6 @@ import ( "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" ) @@ -35,7 +34,7 @@ func NewVictoropsNotifier(model *NotificationChannelConfig, t *template.Template } return &VictoropsNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, @@ -53,7 +52,7 @@ func NewVictoropsNotifier(model *NotificationChannelConfig, t *template.Template // and handles notification process by formatting POST body according to // Victorops specifications (http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/) type VictoropsNotifier struct { - old_notifiers.NotifierBase + *Base URL string MessageType string log log.Logger diff --git a/pkg/services/ngalert/notifier/channels/webhook.go b/pkg/services/ngalert/notifier/channels/webhook.go index 7c456260274..712c1d640ed 100644 --- a/pkg/services/ngalert/notifier/channels/webhook.go +++ b/pkg/services/ngalert/notifier/channels/webhook.go @@ -7,7 +7,6 @@ import ( "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/models" - old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers" "github.com/grafana/grafana/pkg/setting" "github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/template" @@ -18,7 +17,7 @@ import ( // WebhookNotifier is responsible for sending // alert notifications as webhooks. type WebhookNotifier struct { - old_notifiers.NotifierBase + *Base URL string User string Password string @@ -40,7 +39,7 @@ func NewWebHookNotifier(model *NotificationChannelConfig, t *template.Template, return nil, receiverInitError{Cfg: *model, Reason: "could not find url property in settings"} } return &WebhookNotifier{ - NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ + Base: NewBase(&models.AlertNotification{ Uid: model.UID, Name: model.Name, Type: model.Type, diff --git a/pkg/services/sqlstore/migrations/ualert/channel.go b/pkg/services/sqlstore/migrations/ualert/channel.go index d55d4e63e2c..81c078c550e 100644 --- a/pkg/services/sqlstore/migrations/ualert/channel.go +++ b/pkg/services/sqlstore/migrations/ualert/channel.go @@ -5,7 +5,6 @@ import ( "encoding/json" "errors" "fmt" - "net/url" "sort" "strings" @@ -129,21 +128,6 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, orgID int64, channelUid return err } - // Grafana accepts any type of string as a URL for the Slack notification channel. - // However, the Alertmanager will fail if provided with an invalid URL we have two options at this point: - // Either we fail the migration or remove the URL, we've chosen the latter and assume that the notification - // channel was broken to begin with. - if c.Type == "slack" { - u, ok := decryptedSecureSettings["url"] - if ok { - _, err := url.Parse(u) - if err != nil { - m.mg.Logger.Warn("slack notification channel had invalid URL, removing", "name", c.Name, "uid", c.Uid, "org", c.OrgID) - delete(decryptedSecureSettings, "url") - } - } - } - portedChannels = append(portedChannels, &PostableGrafanaReceiver{ UID: uid, Name: c.Name, diff --git a/pkg/services/sqlstore/migrations/ualert/channel_test.go b/pkg/services/sqlstore/migrations/ualert/channel_test.go deleted file mode 100644 index 26ea14e6a95..00000000000 --- a/pkg/services/sqlstore/migrations/ualert/channel_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package ualert - -import ( - "fmt" - "math/rand" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/components/simplejson" - "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" - "github.com/grafana/grafana/pkg/util" -) - -func Test_makeReceiverAndRoute(t *testing.T) { - emptyMigration := func() *migration { - return &migration{ - mg: &migrator.Migrator{ - Logger: log.New("test"), - }, - migratedChannelsPerOrg: make(map[int64]map[*notificationChannel]struct{}), - portedChannelGroupsPerOrg: make(map[int64]map[string]string), - seenChannelUIDs: make(map[string]struct{}), - } - } - - generateChannel := func(channelType string, settings map[string]interface{}, secureSettings map[string]string) *notificationChannel { - uid := util.GenerateShortUID() - return ¬ificationChannel{ - ID: rand.Int63(), - OrgID: rand.Int63(), - Uid: uid, - Name: fmt.Sprintf("Test-%s", uid), - Type: channelType, - DisableResolveMessage: rand.Int63()%2 == 0, - IsDefault: rand.Int63()%2 == 0, - Settings: simplejson.NewFromAny(settings), - SecureSettings: GetEncryptedJsonData(secureSettings), - } - } - - t.Run("Slack channel is migrated", func(t *testing.T) { - t.Run("url is removed if it is invalid (secure settings)", func(t *testing.T) { - secureSettings := map[string]string{ - "url": invalidUri, - "token": util.GenerateShortUID(), - } - settings := map[string]interface{}{ - "test": "data", - "some_map": map[string]interface{}{ - "test": rand.Int63(), - }, - } - - channel := generateChannel("slack", settings, secureSettings) - channelsUid := []interface{}{ - channel.Uid, - } - defaultChannels := make([]*notificationChannel, 0) - allChannels := map[interface{}]*notificationChannel{ - channel.Uid: channel, - } - - apiReceiver, _, err := emptyMigration().makeReceiverAndRoute(util.GenerateShortUID(), channel.OrgID, channelsUid, defaultChannels, allChannels) - require.NoError(t, err) - - require.Len(t, apiReceiver.GrafanaManagedReceivers, 1) - - receiver := apiReceiver.GrafanaManagedReceivers[0] - - require.NotContains(t, receiver.SecureSettings, "url") - require.Contains(t, receiver.SecureSettings, "token") - require.Equal(t, secureSettings["token"], receiver.SecureSettings["token"]) - actualSettings, err := receiver.Settings.Map() - require.NoError(t, err) - require.Equal(t, settings, actualSettings) - }) - - t.Run("url is removed if it is invalid (settings)", func(t *testing.T) { - secureSettings := map[string]string{ - "token": util.GenerateShortUID(), - } - settings := map[string]interface{}{ - "url": invalidUri, - "test": "data", - "some_map": map[string]interface{}{ - "test": rand.Int63(), - }, - } - - channel := generateChannel("slack", settings, secureSettings) - channelsUid := []interface{}{ - channel.Uid, - } - defaultChannels := make([]*notificationChannel, 0) - allChannels := map[interface{}]*notificationChannel{ - channel.Uid: channel, - } - - apiReceiver, _, err := emptyMigration().makeReceiverAndRoute(util.GenerateShortUID(), channel.OrgID, channelsUid, defaultChannels, allChannels) - require.NoError(t, err) - - require.Len(t, apiReceiver.GrafanaManagedReceivers, 1) - - receiver := apiReceiver.GrafanaManagedReceivers[0] - - require.NotContains(t, receiver.SecureSettings, "url") - require.Contains(t, receiver.SecureSettings, "token") - require.Equal(t, secureSettings["token"], receiver.SecureSettings["token"]) - actualSettings, err := receiver.Settings.Map() - require.NoError(t, err) - delete(settings, "url") - require.Equal(t, settings, actualSettings) - }) - }) -} - -const invalidUri = "�6�M��)uk譹1(�h`$�o�N>mĕ����cS2�dh![ę� ���`csB�!��OSxP�{�" diff --git a/pkg/services/sqlstore/migrations/ualert/testing.go b/pkg/services/sqlstore/migrations/ualert/testing.go new file mode 100644 index 00000000000..af99d06e665 --- /dev/null +++ b/pkg/services/sqlstore/migrations/ualert/testing.go @@ -0,0 +1,23 @@ +package ualert + +import ( + "testing" + + "github.com/grafana/grafana/pkg/infra/log" + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" +) + +// newTestMigration generates an empty migration to use in tests. +func newTestMigration(t *testing.T) *migration { + t.Helper() + + return &migration{ + mg: &migrator.Migrator{ + + Logger: log.New("test"), + }, + migratedChannelsPerOrg: make(map[int64]map[*notificationChannel]struct{}), + portedChannelGroupsPerOrg: make(map[int64]map[string]string), + seenChannelUIDs: make(map[string]struct{}), + } +} diff --git a/pkg/services/sqlstore/migrations/ualert/ualert.go b/pkg/services/sqlstore/migrations/ualert/ualert.go index 6ca453d356a..9ead2184493 100644 --- a/pkg/services/sqlstore/migrations/ualert/ualert.go +++ b/pkg/services/sqlstore/migrations/ualert/ualert.go @@ -1,6 +1,8 @@ package ualert import ( + "context" + "encoding/base64" "encoding/json" "fmt" "os" @@ -9,11 +11,12 @@ import ( "strings" ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" + "github.com/grafana/grafana/pkg/services/ngalert/notifier/channels" + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" + "github.com/grafana/grafana/pkg/util" pb "github.com/prometheus/alertmanager/silence/silencepb" "xorm.io/xorm" - - "github.com/grafana/grafana/pkg/services/sqlstore/migrator" ) const GENERAL_FOLDER = "General Alerting" @@ -217,6 +220,7 @@ func (m *migration) SQL(dialect migrator.Dialect) string { return "code migration" } +//nolint: gocyclo func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error { m.sess = sess m.mg = mg @@ -377,7 +381,24 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error { return err } - if err := m.writeAlertmanagerConfig(orgID, amConfig, allChannelsPerOrg[orgID]); err != nil { + // No channels, hence don't require Alertmanager config - skip it. + if len(allChannelsPerOrg[orgID]) == 0 { + m.mg.Logger.Info("alert migration: no notification channel found, skipping Alertmanager config") + continue + } + + // Encrypt the secure settings before we continue. + if err := amConfig.EncryptSecureSettings(); err != nil { + return err + } + + // Validate the alertmanager configuration produced, this gives a chance to catch bad configuration at migration time. + // Validation between legacy and unified alerting can be different (e.g. due to bug fixes) so this would fail the migration in that case. + if err := m.validateAlertmanagerConfig(orgID, amConfig); err != nil { + return err + } + + if err := m.writeAlertmanagerConfig(orgID, amConfig); err != nil { return err } @@ -389,22 +410,13 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error { return nil } -func (m *migration) writeAlertmanagerConfig(orgID int64, amConfig *PostableUserConfig, allChannels map[interface{}]*notificationChannel) error { - if len(allChannels) == 0 { - // No channels, hence don't require Alertmanager config. - m.mg.Logger.Info("alert migration: no notification channel found, skipping Alertmanager config") - return nil - } - - if err := amConfig.EncryptSecureSettings(); err != nil { - return err - } +func (m *migration) writeAlertmanagerConfig(orgID int64, amConfig *PostableUserConfig) error { rawAmConfig, err := json.Marshal(amConfig) if err != nil { return err } - // TODO: should we apply the config here? Because Alertmanager can take upto 1 min to pick it up. + // We don't need to apply the configuration, given the multi org alertmanager will do an initial sync before the server is ready. _, err = m.sess.Insert(AlertConfiguration{ AlertmanagerConfiguration: string(rawAmConfig), // Since we are migration for a snapshot of the code, it is always going to migrate to @@ -419,6 +431,95 @@ func (m *migration) writeAlertmanagerConfig(orgID int64, amConfig *PostableUserC return nil } +// validateAlertmanagerConfig validates the alertmanager configuration produced by the migration against the receivers. +func (m *migration) validateAlertmanagerConfig(orgID int64, config *PostableUserConfig) error { + for _, r := range config.AlertmanagerConfig.Receivers { + for _, gr := range r.GrafanaManagedReceivers { + // First, let's decode the secure settings - given they're stored as base64. + secureSettings := make(map[string][]byte, len(gr.SecureSettings)) + for k, v := range gr.SecureSettings { + d, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return err + } + secureSettings[k] = d + } + + var ( + cfg = &channels.NotificationChannelConfig{ + UID: gr.UID, + OrgID: orgID, + Name: gr.Name, + Type: gr.Type, + DisableResolveMessage: gr.DisableResolveMessage, + Settings: gr.Settings, + SecureSettings: secureSettings, + } + err error + ) + + // decryptFunc represents the legacy way of decrypting data. Before the migration, we don't need any new way, + // given that the previous alerting will never support it. + decryptFunc := func(_ context.Context, sjd map[string][]byte, key string, fallback string, secret string) string { + if value, ok := sjd[key]; ok { + decryptedData, err := util.Decrypt(value, secret) + if err != nil { + m.mg.Logger.Warn("unable to decrypt key '%s' for %s receiver with uid %s, returning fallback.", key, gr.Type, gr.UID) + return fallback + } + return string(decryptedData) + } + return fallback + } + + switch gr.Type { + case "email": + _, err = channels.NewEmailNotifier(cfg, nil) // Email notifier already has a default template. + case "pagerduty": + _, err = channels.NewPagerdutyNotifier(cfg, nil, decryptFunc) + case "pushover": + _, err = channels.NewPushoverNotifier(cfg, nil, decryptFunc) + case "slack": + _, err = channels.NewSlackNotifier(cfg, nil, decryptFunc) + case "telegram": + _, err = channels.NewTelegramNotifier(cfg, nil, decryptFunc) + case "victorops": + _, err = channels.NewVictoropsNotifier(cfg, nil) + case "teams": + _, err = channels.NewTeamsNotifier(cfg, nil) + case "dingding": + _, err = channels.NewDingDingNotifier(cfg, nil) + case "kafka": + _, err = channels.NewKafkaNotifier(cfg, nil) + case "webhook": + _, err = channels.NewWebHookNotifier(cfg, nil, decryptFunc) + case "sensugo": + _, err = channels.NewSensuGoNotifier(cfg, nil, decryptFunc) + case "discord": + _, err = channels.NewDiscordNotifier(cfg, nil) + case "googlechat": + _, err = channels.NewGoogleChatNotifier(cfg, nil) + case "LINE": + _, err = channels.NewLineNotifier(cfg, nil, decryptFunc) + case "threema": + _, err = channels.NewThreemaNotifier(cfg, nil, decryptFunc) + case "opsgenie": + _, err = channels.NewOpsgenieNotifier(cfg, nil, decryptFunc) + case "prometheus-alertmanager": + _, err = channels.NewAlertmanagerNotifier(cfg, nil, decryptFunc) + default: + return fmt.Errorf("notifier %s is not supported", gr.Type) + } + + if err != nil { + return err + } + } + } + + return nil +} + type AlertConfiguration struct { ID int64 `xorm:"pk autoincr 'id'"` OrgID int64 `xorm:"org_id"` diff --git a/pkg/services/sqlstore/migrations/ualert/ualert_test.go b/pkg/services/sqlstore/migrations/ualert/ualert_test.go new file mode 100644 index 00000000000..3b9dd0ea165 --- /dev/null +++ b/pkg/services/sqlstore/migrations/ualert/ualert_test.go @@ -0,0 +1,90 @@ +package ualert + +import ( + "errors" + "fmt" + "testing" + + "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/util" + + "github.com/stretchr/testify/require" +) + +func Test_validateAlertmanagerConfig(t *testing.T) { + tc := []struct { + name string + receivers []*PostableGrafanaReceiver + err error + }{ + { + name: "when a slack receiver does not have a valid URL - it should error", + receivers: []*PostableGrafanaReceiver{ + { + UID: util.GenerateShortUID(), + Name: "SlackWithBadURL", + Type: "slack", + Settings: simplejson.NewFromAny(map[string]interface{}{}), + SecureSettings: map[string]string{"url": invalidUri}, + }, + }, + err: fmt.Errorf("failed to validate receiver \"SlackWithBadURL\" of type \"slack\": invalid URL %q: parse %q: net/url: invalid control character in URL", invalidUri, invalidUri), + }, + { + name: "when a slack receiver has an invalid recipient - it should error", + receivers: []*PostableGrafanaReceiver{ + { + UID: util.GenerateShortUID(), + Name: "SlackWithBadRecipient", + Type: "slack", + Settings: simplejson.NewFromAny(map[string]interface{}{"recipient": "this-doesnt-pass"}), + SecureSettings: map[string]string{"url": "http://webhook.slack.com/myuser"}, + }, + }, + err: errors.New("failed to validate receiver \"SlackWithBadRecipient\" of type \"slack\": recipient on invalid format: \"this-doesnt-pass\""), + }, + { + name: "when the configuration is valid - it should not error", + receivers: []*PostableGrafanaReceiver{ + { + UID: util.GenerateShortUID(), + Name: "SlackWithBadURL", + Type: "slack", + Settings: simplejson.NewFromAny(map[string]interface{}{"recipient": "#a-good-channel"}), + SecureSettings: map[string]string{"url": "http://webhook.slack.com/myuser"}, + }, + }, + }, + } + + for _, tt := range tc { + t.Run(tt.name, func(t *testing.T) { + mg := newTestMigration(t) + orgID := int64(1) + + config := configFromReceivers(t, tt.receivers) + require.NoError(t, config.EncryptSecureSettings()) // make sure we encrypt the settings + err := mg.validateAlertmanagerConfig(orgID, config) + if tt.err != nil { + require.Error(t, err) + require.EqualError(t, err, tt.err.Error()) + } else { + require.NoError(t, err) + } + }) + } +} + +func configFromReceivers(t *testing.T, receivers []*PostableGrafanaReceiver) *PostableUserConfig { + t.Helper() + + return &PostableUserConfig{ + AlertmanagerConfig: PostableApiAlertingConfig{ + Receivers: []*PostableApiReceiver{ + {GrafanaManagedReceivers: receivers}, + }, + }, + } +} + +const invalidUri = "�6�M��)uk譹1(�h`$�o�N>mĕ����cS2�dh![ę� ���`csB�!��OSxP�{�"