Files
grafana/pkg/services/sqlstore/migrations/ualert/channel.go
T
Grot (@grafanabot) 765655fefd Alerting: Migrate paused alerts to silences (#34898) (#34987)
* Alerting: Migrate paused alerts to silences

Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com>

* Fix lint

Signed-off-by: Ganesh Vernekar <ganeshvern@gmail.com>
(cherry picked from commit 7c25465b3a)

Co-authored-by: Ganesh Vernekar <15064823+codesome@users.noreply.github.com>
2021-05-31 14:32:41 +02:00

366 lines
11 KiB
Go

package ualert
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/prometheus/alertmanager/pkg/labels"
"github.com/grafana/grafana/pkg/components/securejsondata"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
type notificationChannel struct {
ID int `xorm:"id"`
Uid string `xorm:"uid"`
Name string `xorm:"name"`
Type string `xorm:"type"`
DisableResolveMessage bool `xorm:"disable_resolve_message"`
IsDefault bool `xorm:"is_default"`
Settings *simplejson.Json `xorm:"settings"`
SecureSettings securejsondata.SecureJsonData `xorm:"secure_settings"`
}
func (m *migration) getNotificationChannelMap() (map[interface{}]*notificationChannel, []*notificationChannel, error) {
q := `
SELECT id,
uid,
name,
type,
disable_resolve_message,
is_default,
settings,
secure_settings
FROM
alert_notification
`
allChannels := []notificationChannel{}
err := m.sess.SQL(q).Find(&allChannels)
if err != nil {
return nil, nil, err
}
if len(allChannels) == 0 {
return nil, nil, nil
}
allChannelsMap := make(map[interface{}]*notificationChannel)
var defaultChannels []*notificationChannel
for i, c := range allChannels {
if c.Uid != "" {
allChannelsMap[c.Uid] = &allChannels[i]
}
if c.ID != 0 {
allChannelsMap[c.ID] = &allChannels[i]
}
if c.IsDefault {
// TODO: verify that there will be only 1 default channel.
defaultChannels = append(defaultChannels, &allChannels[i])
}
}
return allChannelsMap, defaultChannels, nil
}
func (m *migration) updateReceiverAndRoute(allChannels map[interface{}]*notificationChannel, defaultChannels []*notificationChannel, da dashAlert, rule *alertRule, amConfig *PostableUserConfig) error {
// Create receiver and route for this rule.
if allChannels == nil {
return nil
}
channelIDs := extractChannelIDs(da)
if len(channelIDs) == 0 {
// If there are no channels associated, we skip adding any routes,
// receivers or labels to rules so that it goes through the default
// route.
return nil
}
recv, route, err := m.makeReceiverAndRoute(rule.Uid, channelIDs, defaultChannels, allChannels)
if err != nil {
return err
}
amConfig.AlertmanagerConfig.Receivers = append(amConfig.AlertmanagerConfig.Receivers, recv)
amConfig.AlertmanagerConfig.Route.Routes = append(amConfig.AlertmanagerConfig.Route.Routes, route)
return nil
}
func (m *migration) makeReceiverAndRoute(ruleUid string, channelUids []interface{}, defaultChannels []*notificationChannel, allChannels map[interface{}]*notificationChannel) (*PostableApiReceiver, *Route, error) {
receiverName := getMigratedReceiverNameFromRuleUID(ruleUid)
portedChannels := []*PostableGrafanaReceiver{}
receiver := &PostableApiReceiver{
Name: receiverName,
}
addedChannels := map[*notificationChannel]struct{}{}
addChannel := func(c *notificationChannel) error {
if c.Type == "hipchat" || c.Type == "sensu" {
m.mg.Logger.Error("alert migration error: discontinued notification channel found", "type", c.Type, "name", c.Name, "uid", c.Uid)
return nil
}
uid, ok := m.generateChannelUID()
if !ok {
return errors.New("failed to generate UID for notification channel")
}
m.migratedChannels[c] = struct{}{}
settings, secureSettings := migrateSettingsToSecureSettings(c.Type, c.Settings, c.SecureSettings)
portedChannels = append(portedChannels, &PostableGrafanaReceiver{
UID: uid,
Name: c.Name,
Type: c.Type,
DisableResolveMessage: c.DisableResolveMessage,
Settings: settings,
SecureSettings: secureSettings,
})
addedChannels[c] = struct{}{}
return nil
}
for _, n := range channelUids {
c, ok := allChannels[n]
if !ok {
continue
}
if err := addChannel(c); err != nil {
return nil, nil, err
}
}
for _, c := range defaultChannels {
if _, ok := addedChannels[c]; !ok {
if err := addChannel(c); err != nil {
return nil, nil, err
}
}
}
receiver.GrafanaManagedReceivers = portedChannels
n, v := getLabelForRouteMatching(ruleUid)
mat, err := labels.NewMatcher(labels.MatchEqual, n, v)
if err != nil {
return nil, nil, err
}
route := &Route{
Receiver: receiverName,
Matchers: Matchers{mat},
}
return receiver, route, nil
}
func (m *migration) updateDefaultAndUnmigratedChannels(amConfig *PostableUserConfig, allChannels map[interface{}]*notificationChannel, defaultChannels []*notificationChannel) error {
// Unmigrated channels.
portedChannels := []*PostableGrafanaReceiver{}
receiver := &PostableApiReceiver{
Name: "autogen-unlinked-channel-recv",
}
for _, c := range allChannels {
_, ok := m.migratedChannels[c]
if ok {
continue
}
if c.Type == "hipchat" || c.Type == "sensu" {
m.mg.Logger.Error("alert migration error: discontinued notification channel found", "type", c.Type, "name", c.Name, "uid", c.Uid)
continue
}
uid, ok := m.generateChannelUID()
if !ok {
return errors.New("failed to generate UID for notification channel")
}
m.migratedChannels[c] = struct{}{}
settings, secureSettings := migrateSettingsToSecureSettings(c.Type, c.Settings, c.SecureSettings)
portedChannels = append(portedChannels, &PostableGrafanaReceiver{
UID: uid,
Name: c.Name,
Type: c.Type,
DisableResolveMessage: c.DisableResolveMessage,
Settings: settings,
SecureSettings: secureSettings,
})
}
receiver.GrafanaManagedReceivers = portedChannels
if len(portedChannels) > 0 {
amConfig.AlertmanagerConfig.Receivers = append(amConfig.AlertmanagerConfig.Receivers, receiver)
}
// Default route and receiver.
var channelUids = []interface{}{}
for _, c := range defaultChannels {
if c.Uid == "" {
channelUids = append(channelUids, c.ID)
} else {
channelUids = append(channelUids, c.Uid)
}
}
recv, route, err := m.makeReceiverAndRoute("default_route", channelUids, defaultChannels, allChannels)
if err != nil {
return err
}
route.Matchers = nil // Don't need matchers for root route.
route.Routes = amConfig.AlertmanagerConfig.Route.Routes
amConfig.AlertmanagerConfig.Receivers = append(amConfig.AlertmanagerConfig.Receivers, recv)
amConfig.AlertmanagerConfig.Route = route
return nil
}
func (m *migration) generateChannelUID() (string, bool) {
for i := 0; i < 5; i++ {
gen := util.GenerateShortUID()
if _, ok := m.seenChannelUIDs[gen]; !ok {
m.seenChannelUIDs[gen] = struct{}{}
return gen, true
}
}
return "", false
}
// Some settings were migrated from settings to secure settings in between.
// See https://grafana.com/docs/grafana/latest/installation/upgrading/#ensure-encryption-of-existing-alert-notification-channel-secrets.
// migrateSettingsToSecureSettings takes care of that.
func migrateSettingsToSecureSettings(chanType string, settings *simplejson.Json, secureSettings securejsondata.SecureJsonData) (*simplejson.Json, map[string]string) {
keys := []string{}
switch chanType {
case "slack":
keys = []string{"url", "token"}
case "pagerduty":
keys = []string{"integrationKey"}
case "webhook":
keys = []string{"password"}
case "prometheus-alertmanager":
keys = []string{"basicAuthPassword"}
case "opsgenie":
keys = []string{"apiKey"}
case "telegram":
keys = []string{"bottoken"}
case "line":
keys = []string{"token"}
case "pushover":
keys = []string{"apiToken", "userKey"}
case "threema":
keys = []string{"api_secret"}
}
ss := secureSettings.Decrypt()
for _, k := range keys {
if v, ok := ss[k]; ok && v != "" {
continue
}
sv := settings.Get(k).MustString()
if sv != "" {
ss[k] = sv
settings.Del(k)
}
}
return settings, ss
}
func getMigratedReceiverNameFromRuleUID(ruleUID string) string {
return fmt.Sprintf("autogen-panel-recv-%s", ruleUID)
}
func getLabelForRouteMatching(ruleUID string) (string, string) {
return "rule_uid", ruleUID
}
func extractChannelIDs(d dashAlert) (channelUids []interface{}) {
// Extracting channel UID/ID.
for _, ui := range d.ParsedSettings.Notifications {
if ui.UID != "" {
channelUids = append(channelUids, ui.UID)
continue
}
// In certain circumstances, id is used instead of uid.
// We add this if there was no uid.
if ui.ID > 0 {
channelUids = append(channelUids, ui.ID)
}
}
return channelUids
}
// Below is a snapshot of all the config and supporting functions imported
// to avoid vendoring those packages.
type PostableUserConfig struct {
TemplateFiles map[string]string `yaml:"template_files" json:"template_files"`
AlertmanagerConfig PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
}
func (c *PostableUserConfig) EncryptSecureSettings() error {
for _, r := range c.AlertmanagerConfig.Receivers {
for _, gr := range r.GrafanaManagedReceivers {
for k, v := range gr.SecureSettings {
encryptedData, err := util.Encrypt([]byte(v), setting.SecretKey)
if err != nil {
return fmt.Errorf("failed to encrypt secure settings: %w", err)
}
gr.SecureSettings[k] = base64.StdEncoding.EncodeToString(encryptedData)
}
}
}
return nil
}
type PostableApiAlertingConfig struct {
Route *Route `yaml:"route,omitempty" json:"route,omitempty"`
Templates []string `yaml:"templates" json:"templates"`
Receivers []*PostableApiReceiver `yaml:"receivers,omitempty" json:"receivers,omitempty"`
}
type Route struct {
Receiver string `yaml:"receiver,omitempty" json:"receiver,omitempty"`
Matchers Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
Routes []*Route `yaml:"routes,omitempty" json:"routes,omitempty"`
}
type Matchers labels.Matchers
func (m Matchers) MarshalJSON() ([]byte, error) {
if len(m) == 0 {
return nil, nil
}
result := make([]string, len(m))
for i, matcher := range m {
result[i] = matcher.String()
}
return json.Marshal(result)
}
type PostableApiReceiver struct {
Name string `yaml:"name" json:"name"`
GrafanaManagedReceivers []*PostableGrafanaReceiver `yaml:"grafana_managed_receiver_configs,omitempty" json:"grafana_managed_receiver_configs,omitempty"`
}
type PostableGrafanaReceiver CreateAlertNotificationCommand
type CreateAlertNotificationCommand struct {
UID string `json:"uid"`
Name string `json:"name"`
Type string `json:"type"`
DisableResolveMessage bool `json:"disableResolveMessage"`
Settings *simplejson.Json `json:"settings"`
SecureSettings map[string]string `json:"secureSettings"`
}