Alerting: Validate contact point configuration during migration to Unified Alerting (#40717)

* Alerting: Validate contact point configuration during the migration

This minimises the chances of generating broken configuration as part of the migration. Originally, we wanted to generate it and not produce a hard stop in Grafana but this strategy has the chance to avoid delivering notifications for our users.

We now think it's better to hard stop the migration and let the user take care of resolving the configuration manually.
pull/40719/head^2
gotjosh 4 years ago committed by GitHub
parent 998ba06f39
commit 74fb491b6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      pkg/services/ngalert/notifier/channels/alertmanager.go
  2. 32
      pkg/services/ngalert/notifier/channels/base.go
  3. 5
      pkg/services/ngalert/notifier/channels/dingding.go
  4. 5
      pkg/services/ngalert/notifier/channels/discord.go
  5. 5
      pkg/services/ngalert/notifier/channels/email.go
  6. 5
      pkg/services/ngalert/notifier/channels/googlechat.go
  7. 5
      pkg/services/ngalert/notifier/channels/kafka.go
  8. 5
      pkg/services/ngalert/notifier/channels/line.go
  9. 5
      pkg/services/ngalert/notifier/channels/opsgenie.go
  10. 5
      pkg/services/ngalert/notifier/channels/pagerduty.go
  11. 5
      pkg/services/ngalert/notifier/channels/pushover.go
  12. 5
      pkg/services/ngalert/notifier/channels/sensugo.go
  13. 5
      pkg/services/ngalert/notifier/channels/slack.go
  14. 5
      pkg/services/ngalert/notifier/channels/teams.go
  15. 5
      pkg/services/ngalert/notifier/channels/telegram.go
  16. 5
      pkg/services/ngalert/notifier/channels/threema.go
  17. 5
      pkg/services/ngalert/notifier/channels/victorops.go
  18. 5
      pkg/services/ngalert/notifier/channels/webhook.go
  19. 16
      pkg/services/sqlstore/migrations/ualert/channel.go
  20. 119
      pkg/services/sqlstore/migrations/ualert/channel_test.go
  21. 23
      pkg/services/sqlstore/migrations/ualert/testing.go
  22. 129
      pkg/services/sqlstore/migrations/ualert/ualert.go
  23. 90
      pkg/services/sqlstore/migrations/ualert/ualert_test.go

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "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) basicAuthPassword := fn(context.Background(), model.SecureSettings, "basicAuthPassword", model.Settings.Get("basicAuthPassword").MustString(), setting.SecretKey)
return &AlertmanagerNotifier{ return &AlertmanagerNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
DisableResolveMessage: model.DisableResolveMessage, DisableResolveMessage: model.DisableResolveMessage,
@ -64,7 +63,7 @@ func NewAlertmanagerNotifier(model *NotificationChannelConfig, _ *template.Templ
// AlertmanagerNotifier sends alert notifications to the alert manager // AlertmanagerNotifier sends alert notifications to the alert manager
type AlertmanagerNotifier struct { type AlertmanagerNotifier struct {
old_notifiers.NotifierBase *Base
urls []*url.URL urls []*url.URL
basicAuthUser string basicAuthUser string

@ -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),
}
}

@ -12,7 +12,6 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
) )
const defaultDingdingMsgType = "link" const defaultDingdingMsgType = "link"
@ -31,7 +30,7 @@ func NewDingDingNotifier(model *NotificationChannelConfig, t *template.Template)
msgType := model.Settings.Get("msgType").MustString(defaultDingdingMsgType) msgType := model.Settings.Get("msgType").MustString(defaultDingdingMsgType)
return &DingDingNotifier{ return &DingDingNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,
@ -48,7 +47,7 @@ func NewDingDingNotifier(model *NotificationChannelConfig, t *template.Template)
// DingDingNotifier is responsible for sending alert notifications to ding ding. // DingDingNotifier is responsible for sending alert notifications to ding ding.
type DingDingNotifier struct { type DingDingNotifier struct {
old_notifiers.NotifierBase *Base
MsgType string MsgType string
URL string URL string
Message string Message string

@ -13,12 +13,11 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
type DiscordNotifier struct { type DiscordNotifier struct {
old_notifiers.NotifierBase *Base
log log.Logger log log.Logger
tmpl *template.Template tmpl *template.Template
Content string Content string
@ -41,7 +40,7 @@ func NewDiscordNotifier(model *NotificationChannelConfig, t *template.Template)
content := model.Settings.Get("message").MustString(`{{ template "default.message" . }}`) content := model.Settings.Get("message").MustString(`{{ template "default.message" . }}`)
return &DiscordNotifier{ return &DiscordNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -11,14 +11,13 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
// EmailNotifier is responsible for sending // EmailNotifier is responsible for sending
// alert notifications over email. // alert notifications over email.
type EmailNotifier struct { type EmailNotifier struct {
old_notifiers.NotifierBase *Base
Addresses []string Addresses []string
SingleEmail bool SingleEmail bool
Message string Message string
@ -44,7 +43,7 @@ func NewEmailNotifier(model *NotificationChannelConfig, t *template.Template) (*
addresses := util.SplitEmails(addressesString) addresses := util.SplitEmails(addressesString)
return &EmailNotifier{ return &EmailNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -12,14 +12,13 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
// GoogleChatNotifier is responsible for sending // GoogleChatNotifier is responsible for sending
// alert notifications to Google chat. // alert notifications to Google chat.
type GoogleChatNotifier struct { type GoogleChatNotifier struct {
old_notifiers.NotifierBase *Base
URL string URL string
log log.Logger log log.Logger
tmpl *template.Template tmpl *template.Template
@ -32,7 +31,7 @@ func NewGoogleChatNotifier(model *NotificationChannelConfig, t *template.Templat
} }
return &GoogleChatNotifier{ return &GoogleChatNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -13,13 +13,12 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
) )
// KafkaNotifier is responsible for sending // KafkaNotifier is responsible for sending
// alert notifications to Kafka. // alert notifications to Kafka.
type KafkaNotifier struct { type KafkaNotifier struct {
old_notifiers.NotifierBase *Base
Endpoint string Endpoint string
Topic string Topic string
log log.Logger log log.Logger
@ -38,7 +37,7 @@ func NewKafkaNotifier(model *NotificationChannelConfig, t *template.Template) (*
} }
return &KafkaNotifier{ return &KafkaNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
@ -27,7 +26,7 @@ func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn
} }
return &LineNotifier{ return &LineNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,
@ -43,7 +42,7 @@ func NewLineNotifier(model *NotificationChannelConfig, t *template.Template, fn
// LineNotifier is responsible for sending // LineNotifier is responsible for sending
// alert notifications to LINE. // alert notifications to LINE.
type LineNotifier struct { type LineNotifier struct {
old_notifiers.NotifierBase *Base
Token string Token string
log log.Logger log log.Logger
tmpl *template.Template tmpl *template.Template

@ -11,7 +11,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
@ -32,7 +31,7 @@ var (
// OpsgenieNotifier is responsible for sending alert notifications to Opsgenie. // OpsgenieNotifier is responsible for sending alert notifications to Opsgenie.
type OpsgenieNotifier struct { type OpsgenieNotifier struct {
old_notifiers.NotifierBase *Base
APIKey string APIKey string
APIUrl string APIUrl string
AutoClose bool AutoClose bool
@ -63,7 +62,7 @@ func NewOpsgenieNotifier(model *NotificationChannelConfig, t *template.Template,
} }
return &OpsgenieNotifier{ return &OpsgenieNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
@ -29,7 +28,7 @@ var (
// PagerdutyNotifier is responsible for sending // PagerdutyNotifier is responsible for sending
// alert notifications to pagerduty // alert notifications to pagerduty
type PagerdutyNotifier struct { type PagerdutyNotifier struct {
old_notifiers.NotifierBase *Base
Key string Key string
Severity string Severity string
CustomDetails map[string]string CustomDetails map[string]string
@ -53,7 +52,7 @@ func NewPagerdutyNotifier(model *NotificationChannelConfig, t *template.Template
} }
return &PagerdutyNotifier{ return &PagerdutyNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
@ -24,7 +23,7 @@ var (
// PushoverNotifier is responsible for sending // PushoverNotifier is responsible for sending
// alert notifications to Pushover // alert notifications to Pushover
type PushoverNotifier struct { type PushoverNotifier struct {
old_notifiers.NotifierBase *Base
UserKey string UserKey string
APIToken string APIToken string
AlertingPriority int AlertingPriority int
@ -70,7 +69,7 @@ func NewPushoverNotifier(model *NotificationChannelConfig, t *template.Template,
return nil, receiverInitError{Cfg: *model, Reason: "API token not found"} return nil, receiverInitError{Cfg: *model, Reason: "API token not found"}
} }
return &PushoverNotifier{ return &PushoverNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
@ -18,7 +17,7 @@ import (
) )
type SensuGoNotifier struct { type SensuGoNotifier struct {
old_notifiers.NotifierBase *Base
log log.Logger log log.Logger
tmpl *template.Template tmpl *template.Template
@ -48,7 +47,7 @@ func NewSensuGoNotifier(model *NotificationChannelConfig, t *template.Template,
} }
return &SensuGoNotifier{ return &SensuGoNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -16,7 +16,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/config" "github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
@ -26,7 +25,7 @@ import (
// SlackNotifier is responsible for sending // SlackNotifier is responsible for sending
// alert notification to Slack. // alert notification to Slack.
type SlackNotifier struct { type SlackNotifier struct {
old_notifiers.NotifierBase *Base
log log.Logger log log.Logger
tmpl *template.Template tmpl *template.Template
@ -106,7 +105,7 @@ func NewSlackNotifier(model *NotificationChannelConfig, t *template.Template, fn
} }
return &SlackNotifier{ return &SlackNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -11,13 +11,12 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
) )
// TeamsNotifier is responsible for sending // TeamsNotifier is responsible for sending
// alert notifications to Microsoft teams. // alert notifications to Microsoft teams.
type TeamsNotifier struct { type TeamsNotifier struct {
old_notifiers.NotifierBase *Base
URL string URL string
Message string Message string
tmpl *template.Template tmpl *template.Template
@ -36,7 +35,7 @@ func NewTeamsNotifier(model *NotificationChannelConfig, t *template.Template) (*
} }
return &TeamsNotifier{ return &TeamsNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -9,7 +9,6 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
@ -22,7 +21,7 @@ var (
// TelegramNotifier is responsible for sending // TelegramNotifier is responsible for sending
// alert notifications to Telegram. // alert notifications to Telegram.
type TelegramNotifier struct { type TelegramNotifier struct {
old_notifiers.NotifierBase *Base
BotToken string BotToken string
ChatID string ChatID string
Message string Message string
@ -49,7 +48,7 @@ func NewTelegramNotifier(model *NotificationChannelConfig, t *template.Template,
} }
return &TelegramNotifier{ return &TelegramNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
"github.com/prometheus/alertmanager/types" "github.com/prometheus/alertmanager/types"
@ -24,7 +23,7 @@ var (
// ThreemaNotifier is responsible for sending // ThreemaNotifier is responsible for sending
// alert notifications to Threema. // alert notifications to Threema.
type ThreemaNotifier struct { type ThreemaNotifier struct {
old_notifiers.NotifierBase *Base
GatewayID string GatewayID string
RecipientID string RecipientID string
APISecret string APISecret string
@ -63,7 +62,7 @@ func NewThreemaNotifier(model *NotificationChannelConfig, t *template.Template,
} }
return &ThreemaNotifier{ return &ThreemaNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -14,7 +14,6 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
@ -35,7 +34,7 @@ func NewVictoropsNotifier(model *NotificationChannelConfig, t *template.Template
} }
return &VictoropsNotifier{ return &VictoropsNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,
@ -53,7 +52,7 @@ func NewVictoropsNotifier(model *NotificationChannelConfig, t *template.Template
// and handles notification process by formatting POST body according to // and handles notification process by formatting POST body according to
// Victorops specifications (http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/) // Victorops specifications (http://victorops.force.com/knowledgebase/articles/Integration/Alert-Ingestion-API-Documentation/)
type VictoropsNotifier struct { type VictoropsNotifier struct {
old_notifiers.NotifierBase *Base
URL string URL string
MessageType string MessageType string
log log.Logger log log.Logger

@ -7,7 +7,6 @@ import (
"github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/models"
old_notifiers "github.com/grafana/grafana/pkg/services/alerting/notifiers"
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/alertmanager/notify" "github.com/prometheus/alertmanager/notify"
"github.com/prometheus/alertmanager/template" "github.com/prometheus/alertmanager/template"
@ -18,7 +17,7 @@ import (
// WebhookNotifier is responsible for sending // WebhookNotifier is responsible for sending
// alert notifications as webhooks. // alert notifications as webhooks.
type WebhookNotifier struct { type WebhookNotifier struct {
old_notifiers.NotifierBase *Base
URL string URL string
User string User string
Password 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 nil, receiverInitError{Cfg: *model, Reason: "could not find url property in settings"}
} }
return &WebhookNotifier{ return &WebhookNotifier{
NotifierBase: old_notifiers.NewNotifierBase(&models.AlertNotification{ Base: NewBase(&models.AlertNotification{
Uid: model.UID, Uid: model.UID,
Name: model.Name, Name: model.Name,
Type: model.Type, Type: model.Type,

@ -5,7 +5,6 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"net/url"
"sort" "sort"
"strings" "strings"
@ -129,21 +128,6 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, orgID int64, channelUid
return err 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{ portedChannels = append(portedChannels, &PostableGrafanaReceiver{
UID: uid, UID: uid,
Name: c.Name, Name: c.Name,

@ -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 &notificationChannel{
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 = "<EFBFBD>6<EFBFBD>M<EFBFBD><EFBFBD>)uk譹1(<EFBFBD>h`$<EFBFBD>o<EFBFBD>N>mĕ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>cS2<EFBFBD>dh![ę<EFBFBD> <EFBFBD><EFBFBD><EFBFBD>`csB<EFBFBD>!<EFBFBD><EFBFBD>OSxP<EFBFBD>{<EFBFBD>"

@ -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{}),
}
}

@ -1,6 +1,8 @@
package ualert package ualert
import ( import (
"context"
"encoding/base64"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
@ -9,11 +11,12 @@ import (
"strings" "strings"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models" 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" pb "github.com/prometheus/alertmanager/silence/silencepb"
"xorm.io/xorm" "xorm.io/xorm"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
) )
const GENERAL_FOLDER = "General Alerting" const GENERAL_FOLDER = "General Alerting"
@ -217,6 +220,7 @@ func (m *migration) SQL(dialect migrator.Dialect) string {
return "code migration" return "code migration"
} }
//nolint: gocyclo
func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error { func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
m.sess = sess m.sess = sess
m.mg = mg m.mg = mg
@ -377,34 +381,42 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
return err 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 return err
} }
if err := m.writeSilencesFile(orgID); err != nil { // Validate the alertmanager configuration produced, this gives a chance to catch bad configuration at migration time.
m.mg.Logger.Error("alert migration error: failed to write silence file", "err", err) // 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
} }
return nil if err := m.writeSilencesFile(orgID); err != nil {
m.mg.Logger.Error("alert migration error: failed to write silence file", "err", err)
}
} }
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 return nil
} }
if err := amConfig.EncryptSecureSettings(); err != nil { func (m *migration) writeAlertmanagerConfig(orgID int64, amConfig *PostableUserConfig) error {
return err
}
rawAmConfig, err := json.Marshal(amConfig) rawAmConfig, err := json.Marshal(amConfig)
if err != nil { if err != nil {
return err 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{ _, err = m.sess.Insert(AlertConfiguration{
AlertmanagerConfiguration: string(rawAmConfig), AlertmanagerConfiguration: string(rawAmConfig),
// Since we are migration for a snapshot of the code, it is always going to migrate to // 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 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 { type AlertConfiguration struct {
ID int64 `xorm:"pk autoincr 'id'"` ID int64 `xorm:"pk autoincr 'id'"`
OrgID int64 `xorm:"org_id"` OrgID int64 `xorm:"org_id"`

@ -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 = "<EFBFBD>6<EFBFBD>M<EFBFBD><EFBFBD>)uk譹1(<EFBFBD>h`$<EFBFBD>o<EFBFBD>N>mĕ<EFBFBD><EFBFBD><EFBFBD><EFBFBD>cS2<EFBFBD>dh![ę<EFBFBD> <EFBFBD><EFBFBD><EFBFBD>`csB<EFBFBD>!<EFBFBD><EFBFBD>OSxP<EFBFBD>{<EFBFBD>"
Loading…
Cancel
Save