Alerting: modify DB table, accessors and migration to restrict org access (#37414)
* Alerting: modify table and accessors to limit org access appropriately * Update migration to create multiple Alertmanager configs * Apply suggestions from code review Co-authored-by: gotjosh <josue@grafana.com> * replace mg.ClearMigrationEntry() mg.ClearMigrationEntry() would create a new session. This commit introduces a new migration for clearing an entry from migration log for replacing mg.ClearMigrationEntry() so that all dashboard alert migration operations will run inside the same transaction. It adds also `SkipMigrationLog()` in Migrator interface for skipping adding an entry in the migration_log. Co-authored-by: gotjosh <josue@grafana.com>
This commit is contained in:
co-authored by
gotjosh
parent
4a9fdb8b76
commit
04d5dcb7c8
@@ -41,6 +41,7 @@ func AddMigrations(mg *Migrator) {
|
||||
ualert.AddTablesMigrations(mg)
|
||||
ualert.AddDashAlertMigration(mg)
|
||||
addLibraryElementsMigrations(mg)
|
||||
ualert.RerunDashAlertMigration(mg)
|
||||
}
|
||||
|
||||
func addMigrationLogMigrations(mg *Migrator) {
|
||||
|
||||
@@ -9,14 +9,14 @@ import (
|
||||
)
|
||||
|
||||
type alertRule struct {
|
||||
OrgId int64
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
Title string
|
||||
Condition string
|
||||
Data []alertQuery
|
||||
IntervalSeconds int64
|
||||
Version int64
|
||||
Uid string
|
||||
NamespaceUid string
|
||||
UID string `xorm:"uid"`
|
||||
NamespaceUID string `xorm:"namespace_uid"`
|
||||
RuleGroup string
|
||||
NoDataState string
|
||||
ExecErrState string
|
||||
@@ -51,9 +51,9 @@ type alertRuleVersion struct {
|
||||
|
||||
func (a *alertRule) makeVersion() *alertRuleVersion {
|
||||
return &alertRuleVersion{
|
||||
RuleOrgID: a.OrgId,
|
||||
RuleUID: a.Uid,
|
||||
RuleNamespaceUID: a.NamespaceUid,
|
||||
RuleOrgID: a.OrgID,
|
||||
RuleUID: a.UID,
|
||||
RuleNamespaceUID: a.NamespaceUID,
|
||||
RuleGroup: a.RuleGroup,
|
||||
ParentVersion: 0,
|
||||
RestoredFrom: 0,
|
||||
@@ -96,14 +96,14 @@ func (m *migration) makeAlertRule(cond condition, da dashAlert, folderUID string
|
||||
annotations["message"] = da.Message
|
||||
|
||||
ar := &alertRule{
|
||||
OrgId: da.OrgId,
|
||||
OrgID: da.OrgId,
|
||||
Title: da.Name, // TODO: Make sure all names are unique, make new name on constraint insert error.
|
||||
Uid: util.GenerateShortUID(),
|
||||
UID: util.GenerateShortUID(),
|
||||
Condition: cond.Condition,
|
||||
Data: cond.Data,
|
||||
IntervalSeconds: ruleAdjustInterval(da.Frequency),
|
||||
Version: 1,
|
||||
NamespaceUid: folderUID, // Folder already created, comes from env var.
|
||||
NamespaceUID: folderUID, // Folder already created, comes from env var.
|
||||
RuleGroup: da.Name,
|
||||
For: duration(da.For),
|
||||
Updated: time.Now().UTC(),
|
||||
@@ -123,7 +123,7 @@ func (m *migration) makeAlertRule(cond condition, da dashAlert, folderUID string
|
||||
}
|
||||
|
||||
// Label for routing and silences.
|
||||
n, v := getLabelForRouteMatching(ar.Uid)
|
||||
n, v := getLabelForRouteMatching(ar.UID)
|
||||
ar.Labels[n] = v
|
||||
|
||||
if err := m.addSilence(da, ar); err != nil {
|
||||
|
||||
@@ -17,7 +17,8 @@ import (
|
||||
)
|
||||
|
||||
type notificationChannel struct {
|
||||
ID int `xorm:"id"`
|
||||
ID int64 `xorm:"id"`
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
Uid string `xorm:"uid"`
|
||||
Name string `xorm:"name"`
|
||||
Type string `xorm:"type"`
|
||||
@@ -27,9 +28,16 @@ type notificationChannel struct {
|
||||
SecureSettings securejsondata.SecureJsonData `xorm:"secure_settings"`
|
||||
}
|
||||
|
||||
func (m *migration) getNotificationChannelMap() (map[interface{}]*notificationChannel, []*notificationChannel, error) {
|
||||
// channelsPerOrg maps notification channels per organisation
|
||||
type channelsPerOrg map[int64]map[interface{}]*notificationChannel
|
||||
|
||||
// channelMap maps notification channels per organisation
|
||||
type defaultChannelsPerOrg map[int64][]*notificationChannel
|
||||
|
||||
func (m *migration) getNotificationChannelMap() (channelsPerOrg, defaultChannelsPerOrg, error) {
|
||||
q := `
|
||||
SELECT id,
|
||||
org_id,
|
||||
uid,
|
||||
name,
|
||||
type,
|
||||
@@ -50,25 +58,27 @@ func (m *migration) getNotificationChannelMap() (map[interface{}]*notificationCh
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
allChannelsMap := make(map[interface{}]*notificationChannel)
|
||||
var defaultChannels []*notificationChannel
|
||||
allChannelsMap := make(channelsPerOrg)
|
||||
defaultChannelsMap := make(defaultChannelsPerOrg)
|
||||
for i, c := range allChannels {
|
||||
if _, ok := allChannelsMap[c.OrgID]; !ok { // new seen org
|
||||
allChannelsMap[c.OrgID] = make(map[interface{}]*notificationChannel)
|
||||
}
|
||||
if c.Uid != "" {
|
||||
allChannelsMap[c.Uid] = &allChannels[i]
|
||||
allChannelsMap[c.OrgID][c.Uid] = &allChannels[i]
|
||||
}
|
||||
if c.ID != 0 {
|
||||
allChannelsMap[c.ID] = &allChannels[i]
|
||||
allChannelsMap[c.OrgID][c.ID] = &allChannels[i]
|
||||
}
|
||||
if c.IsDefault {
|
||||
// TODO: verify that there will be only 1 default channel.
|
||||
defaultChannels = append(defaultChannels, &allChannels[i])
|
||||
defaultChannelsMap[c.OrgID] = append(defaultChannelsMap[c.OrgID], &allChannels[i])
|
||||
}
|
||||
}
|
||||
|
||||
return allChannelsMap, defaultChannels, nil
|
||||
return allChannelsMap, defaultChannelsMap, nil
|
||||
}
|
||||
|
||||
func (m *migration) updateReceiverAndRoute(allChannels map[interface{}]*notificationChannel, defaultChannels []*notificationChannel, da dashAlert, rule *alertRule, amConfig *PostableUserConfig) error {
|
||||
func (m *migration) updateReceiverAndRoute(allChannels channelsPerOrg, defaultChannels defaultChannelsPerOrg, da dashAlert, rule *alertRule, amConfig *PostableUserConfig) error {
|
||||
// Create receiver and route for this rule.
|
||||
if allChannels == nil {
|
||||
return nil
|
||||
@@ -82,7 +92,7 @@ func (m *migration) updateReceiverAndRoute(allChannels map[interface{}]*notifica
|
||||
return nil
|
||||
}
|
||||
|
||||
recv, route, err := m.makeReceiverAndRoute(rule.Uid, channelIDs, defaultChannels, allChannels)
|
||||
recv, route, err := m.makeReceiverAndRoute(rule.UID, rule.OrgID, channelIDs, defaultChannels[rule.OrgID], allChannels[rule.OrgID])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -97,7 +107,7 @@ func (m *migration) updateReceiverAndRoute(allChannels map[interface{}]*notifica
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *migration) makeReceiverAndRoute(ruleUid string, channelUids []interface{}, defaultChannels []*notificationChannel, allChannels map[interface{}]*notificationChannel) (*PostableApiReceiver, *Route, error) {
|
||||
func (m *migration) makeReceiverAndRoute(ruleUid string, orgID int64, channelUids []interface{}, defaultChannels []*notificationChannel, allChannels map[interface{}]*notificationChannel) (*PostableApiReceiver, *Route, error) {
|
||||
portedChannels := []*PostableGrafanaReceiver{}
|
||||
var receiver *PostableApiReceiver
|
||||
|
||||
@@ -112,7 +122,10 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, channelUids []interface
|
||||
return errors.New("failed to generate UID for notification channel")
|
||||
}
|
||||
|
||||
m.migratedChannels[c] = struct{}{}
|
||||
if _, ok := m.migratedChannelsPerOrg[orgID]; !ok {
|
||||
m.migratedChannelsPerOrg[orgID] = make(map[*notificationChannel]struct{})
|
||||
}
|
||||
m.migratedChannelsPerOrg[orgID][c] = struct{}{}
|
||||
settings, secureSettings := migrateSettingsToSecureSettings(c.Type, c.Settings, c.SecureSettings)
|
||||
portedChannels = append(portedChannels, &PostableGrafanaReceiver{
|
||||
UID: uid,
|
||||
@@ -129,9 +142,10 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, channelUids []interface
|
||||
// Remove obsolete notification channels.
|
||||
filteredChannelUids := make(map[interface{}]struct{})
|
||||
for _, uid := range channelUids {
|
||||
_, ok := allChannels[uid]
|
||||
c, ok := allChannels[uid]
|
||||
if ok {
|
||||
filteredChannelUids[uid] = struct{}{}
|
||||
// always store the channel UID to prevent duplicates
|
||||
filteredChannelUids[c.Uid] = struct{}{}
|
||||
} else {
|
||||
m.mg.Logger.Warn("ignoring obsolete notification channel", "uid", uid)
|
||||
}
|
||||
@@ -142,9 +156,10 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, channelUids []interface
|
||||
if c.Uid == "" {
|
||||
id = c.ID
|
||||
}
|
||||
_, ok := allChannels[id]
|
||||
c, ok := allChannels[id]
|
||||
if ok {
|
||||
filteredChannelUids[id] = struct{}{}
|
||||
// always store the channel UID to prevent duplicates
|
||||
filteredChannelUids[c.Uid] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -159,7 +174,11 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, channelUids []interface
|
||||
}
|
||||
|
||||
var receiverName string
|
||||
if rn, ok := m.portedChannelGroups[chanKey]; ok {
|
||||
|
||||
if _, ok := m.portedChannelGroupsPerOrg[orgID]; !ok {
|
||||
m.portedChannelGroupsPerOrg[orgID] = make(map[string]string)
|
||||
}
|
||||
if rn, ok := m.portedChannelGroupsPerOrg[orgID][chanKey]; ok {
|
||||
// We have ported these exact set of channels already. Re-use it.
|
||||
receiverName = rn
|
||||
if receiverName == "autogen-contact-point-default" {
|
||||
@@ -180,7 +199,7 @@ func (m *migration) makeReceiverAndRoute(ruleUid string, channelUids []interface
|
||||
receiverName = fmt.Sprintf("autogen-contact-point-%d", m.lastReceiverID)
|
||||
}
|
||||
|
||||
m.portedChannelGroups[chanKey] = receiverName
|
||||
m.portedChannelGroupsPerOrg[orgID][chanKey] = receiverName
|
||||
receiver = &PostableApiReceiver{
|
||||
Name: receiverName,
|
||||
GrafanaManagedReceivers: portedChannels,
|
||||
@@ -220,32 +239,47 @@ func makeKeyForChannelGroup(channelUids map[interface{}]struct{}) (string, error
|
||||
}
|
||||
|
||||
// addDefaultChannels should be called before adding any other routes.
|
||||
func (m *migration) addDefaultChannels(amConfig *PostableUserConfig, allChannels map[interface{}]*notificationChannel, defaultChannels []*notificationChannel) error {
|
||||
// Default route and receiver.
|
||||
recv, route, err := m.makeReceiverAndRoute("default_route", nil, defaultChannels, allChannels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
func (m *migration) addDefaultChannels(amConfigsPerOrg amConfigsPerOrg, allChannels channelsPerOrg, defaultChannels defaultChannelsPerOrg) error {
|
||||
for orgID := range allChannels {
|
||||
if _, ok := amConfigsPerOrg[orgID]; !ok {
|
||||
amConfigsPerOrg[orgID] = &PostableUserConfig{
|
||||
AlertmanagerConfig: PostableApiAlertingConfig{
|
||||
Receivers: make([]*PostableApiReceiver, 0),
|
||||
Route: &Route{
|
||||
Routes: make([]*Route, 0),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
// Default route and receiver.
|
||||
recv, route, err := m.makeReceiverAndRoute("default_route", orgID, nil, defaultChannels[orgID], allChannels[orgID])
|
||||
if err != nil {
|
||||
// if one fails it will fail the migration
|
||||
return err
|
||||
}
|
||||
|
||||
if recv != nil {
|
||||
amConfig.AlertmanagerConfig.Receivers = append(amConfig.AlertmanagerConfig.Receivers, recv)
|
||||
if recv != nil {
|
||||
amConfigsPerOrg[orgID].AlertmanagerConfig.Receivers = append(amConfigsPerOrg[orgID].AlertmanagerConfig.Receivers, recv)
|
||||
}
|
||||
if route != nil {
|
||||
route.Matchers = nil // Don't need matchers for root route.
|
||||
amConfigsPerOrg[orgID].AlertmanagerConfig.Route = route
|
||||
}
|
||||
}
|
||||
if route != nil {
|
||||
route.Matchers = nil // Don't need matchers for root route.
|
||||
amConfig.AlertmanagerConfig.Route = route
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *migration) addUnmigratedChannels(amConfig *PostableUserConfig, allChannels map[interface{}]*notificationChannel, defaultChannels []*notificationChannel) error {
|
||||
func (m *migration) addUnmigratedChannels(orgID int64, amConfigs *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 := m.migratedChannelsPerOrg[orgID]; !ok {
|
||||
m.migratedChannelsPerOrg[orgID] = make(map[*notificationChannel]struct{})
|
||||
}
|
||||
_, ok := m.migratedChannelsPerOrg[orgID][c]
|
||||
if ok {
|
||||
continue
|
||||
}
|
||||
@@ -259,7 +293,7 @@ func (m *migration) addUnmigratedChannels(amConfig *PostableUserConfig, allChann
|
||||
return errors.New("failed to generate UID for notification channel")
|
||||
}
|
||||
|
||||
m.migratedChannels[c] = struct{}{}
|
||||
m.migratedChannelsPerOrg[orgID][c] = struct{}{}
|
||||
settings, secureSettings := migrateSettingsToSecureSettings(c.Type, c.Settings, c.SecureSettings)
|
||||
portedChannels = append(portedChannels, &PostableGrafanaReceiver{
|
||||
UID: uid,
|
||||
@@ -272,7 +306,7 @@ func (m *migration) addUnmigratedChannels(amConfig *PostableUserConfig, allChann
|
||||
}
|
||||
receiver.GrafanaManagedReceivers = portedChannels
|
||||
if len(portedChannels) > 0 {
|
||||
amConfig.AlertmanagerConfig.Receivers = append(amConfig.AlertmanagerConfig.Receivers, receiver)
|
||||
amConfigs.AlertmanagerConfig.Receivers = append(amConfigs.AlertmanagerConfig.Receivers, receiver)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -361,6 +395,8 @@ type PostableUserConfig struct {
|
||||
AlertmanagerConfig PostableApiAlertingConfig `yaml:"alertmanager_config" json:"alertmanager_config"`
|
||||
}
|
||||
|
||||
type amConfigsPerOrg = map[int64]*PostableUserConfig
|
||||
|
||||
func (c *PostableUserConfig) EncryptSecureSettings() error {
|
||||
for _, r := range c.AlertmanagerConfig.Receivers {
|
||||
for _, gr := range r.GrafanaManagedReceivers {
|
||||
|
||||
@@ -61,6 +61,25 @@ func (m *migration) getOrCreateGeneralFolder(orgID int64) (*dashboard, error) {
|
||||
return &dashboard, nil
|
||||
}
|
||||
|
||||
// returns the folder of the given dashboard (if exists)
|
||||
func (m *migration) getFolder(dash dashboard, da dashAlert) (dashboard, error) {
|
||||
// get folder if exists
|
||||
folder := dashboard{}
|
||||
if dash.FolderId > 0 {
|
||||
exists, err := m.sess.Where("id=?", dash.FolderId).Get(&folder)
|
||||
if err != nil {
|
||||
return folder, fmt.Errorf("failed to get folder %d: %w", dash.FolderId, err)
|
||||
}
|
||||
if !exists {
|
||||
return folder, fmt.Errorf("folder with id %v not found", dash.FolderId)
|
||||
}
|
||||
if !folder.IsFolder {
|
||||
return folder, fmt.Errorf("id %v is a dashboard not a folder", dash.FolderId)
|
||||
}
|
||||
}
|
||||
return folder, nil
|
||||
}
|
||||
|
||||
// based on sqlstore.saveDashboard()
|
||||
// it should be called from inside a transaction
|
||||
func (m *migration) createFolder(orgID int64, title string) (*dashboard, error) {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gofrs/uuid"
|
||||
@@ -27,7 +28,7 @@ func (m *migration) addSilence(da dashAlert, rule *alertRule) error {
|
||||
return errors.New("failed to create uuid for silence")
|
||||
}
|
||||
|
||||
n, v := getLabelForRouteMatching(rule.Uid)
|
||||
n, v := getLabelForRouteMatching(rule.UID)
|
||||
s := &pb.MeshSilence{
|
||||
Silence: &pb.Silence{
|
||||
Id: uid.String(),
|
||||
@@ -50,7 +51,7 @@ func (m *migration) addSilence(da dashAlert, rule *alertRule) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *migration) writeSilencesFile() error {
|
||||
func (m *migration) writeSilencesFile(orgID int64) error {
|
||||
var buf bytes.Buffer
|
||||
for _, e := range m.silences {
|
||||
if _, err := pbutil.WriteDelimited(&buf, e); err != nil {
|
||||
@@ -58,7 +59,7 @@ func (m *migration) writeSilencesFile() error {
|
||||
}
|
||||
}
|
||||
|
||||
f, err := openReplace(silencesFileName(m.mg))
|
||||
f, err := openReplace(silencesFileNameForOrg(m.mg, orgID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -70,8 +71,12 @@ func (m *migration) writeSilencesFile() error {
|
||||
return f.Close()
|
||||
}
|
||||
|
||||
func silencesFileName(mg *migrator.Migrator) string {
|
||||
return filepath.Join(mg.Cfg.DataPath, "alerting", "silences")
|
||||
func getSilenceFileNamesForAllOrgs(mg *migrator.Migrator) ([]string, error) {
|
||||
return filepath.Glob(filepath.Join(mg.Cfg.DataPath, "alerting", "*", "silences"))
|
||||
}
|
||||
|
||||
func silencesFileNameForOrg(mg *migrator.Migrator, orgID int64) string {
|
||||
return filepath.Join(mg.Cfg.DataPath, "alerting", strconv.Itoa(int(orgID)), "silences")
|
||||
}
|
||||
|
||||
// replaceFile wraps a file that is moved to another filename on closing.
|
||||
@@ -94,6 +99,10 @@ func (f *replaceFile) Close() error {
|
||||
func openReplace(filename string) (*replaceFile, error) {
|
||||
tmpFilename := fmt.Sprintf("%s.%x", filename, uint64(rand.Int63()))
|
||||
|
||||
if err := os.MkdirAll(filepath.Dir(tmpFilename), os.ModePerm); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
f, err := os.Create(tmpFilename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -267,6 +267,15 @@ func AddAlertmanagerConfigMigrations(mg *migrator.Migrator) {
|
||||
|
||||
mg.AddMigration("alert alert_configuration alertmanager_configuration column from TEXT to MEDIUMTEXT if mysql", migrator.NewRawSQLMigration("").
|
||||
Mysql("ALTER TABLE alert_configuration MODIFY alertmanager_configuration MEDIUMTEXT;"))
|
||||
|
||||
mg.AddMigration("add column org_id in alert_configuration", migrator.NewAddColumnMigration(alertConfiguration, &migrator.Column{
|
||||
Name: "org_id", Type: migrator.DB_BigInt, Nullable: false, Default: "0",
|
||||
}))
|
||||
|
||||
// add index on org_id
|
||||
mg.AddMigration("add index in alert_configuration table on org_id column", migrator.NewAddIndexMigration(alertConfiguration, &migrator.Index{
|
||||
Cols: []string{"org_id"},
|
||||
}))
|
||||
}
|
||||
|
||||
func AddAlertAdminConfigMigrations(mg *migrator.Migrator) {
|
||||
|
||||
@@ -23,6 +23,8 @@ var migTitle = "move dashboard alerts to unified alerting"
|
||||
|
||||
var rmMigTitle = "remove unified alerting data"
|
||||
|
||||
const clearMigrationEntryTitle = "clear migration entry %q"
|
||||
|
||||
type MigrationError struct {
|
||||
AlertId int64
|
||||
Err error
|
||||
@@ -49,19 +51,23 @@ func AddDashAlertMigration(mg *migrator.Migrator) {
|
||||
case ngEnabled && !migrationRun:
|
||||
// Remove the migration entry that removes all unified alerting data. This is so when the feature
|
||||
// flag is removed in future the "remove unified alerting data" migration will be run again.
|
||||
err = mg.ClearMigrationEntry(rmMigTitle)
|
||||
mg.AddMigration(fmt.Sprintf(clearMigrationEntryTitle, rmMigTitle), &clearMigrationEntry{
|
||||
migrationID: rmMigTitle,
|
||||
})
|
||||
if err != nil {
|
||||
mg.Logger.Error("alert migration error: could not clear alert migration for removing data", "error", err)
|
||||
}
|
||||
mg.AddMigration(migTitle, &migration{
|
||||
seenChannelUIDs: make(map[string]struct{}),
|
||||
migratedChannels: make(map[*notificationChannel]struct{}),
|
||||
portedChannelGroups: make(map[string]string),
|
||||
seenChannelUIDs: make(map[string]struct{}),
|
||||
migratedChannelsPerOrg: make(map[int64]map[*notificationChannel]struct{}),
|
||||
portedChannelGroupsPerOrg: make(map[int64]map[string]string),
|
||||
})
|
||||
case !ngEnabled && migrationRun:
|
||||
// Remove the migration entry that creates unified alerting data. This is so when the feature
|
||||
// flag is enabled in the future the migration "move dashboard alerts to unified alerting" will be run again.
|
||||
err = mg.ClearMigrationEntry(migTitle)
|
||||
mg.AddMigration(fmt.Sprintf(clearMigrationEntryTitle, migTitle), &clearMigrationEntry{
|
||||
migrationID: migTitle,
|
||||
})
|
||||
if err != nil {
|
||||
mg.Logger.Error("alert migration error: could not clear dashboard alert migration", "error", err)
|
||||
}
|
||||
@@ -69,17 +75,84 @@ func AddDashAlertMigration(mg *migrator.Migrator) {
|
||||
}
|
||||
}
|
||||
|
||||
// RerunDashAlertMigration force the dashboard alert migration to run
|
||||
// to make sure that the Alertmanager configurations will be created for each organisation
|
||||
func RerunDashAlertMigration(mg *migrator.Migrator) {
|
||||
logs, err := mg.GetMigrationLog()
|
||||
if err != nil {
|
||||
mg.Logger.Crit("alert migration failure: could not get migration log", "error", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
cloneMigTitle := fmt.Sprintf("clone %s", migTitle)
|
||||
cloneRmMigTitle := fmt.Sprintf("clone %s", rmMigTitle)
|
||||
|
||||
_, migrationRun := logs[cloneMigTitle]
|
||||
|
||||
ngEnabled := mg.Cfg.IsNgAlertEnabled()
|
||||
|
||||
switch {
|
||||
case ngEnabled && !migrationRun:
|
||||
// Removes all unified alerting data. It is not recorded so when the feature
|
||||
// flag is removed in future the "clone remove unified alerting data" migration will be run again.
|
||||
mg.AddMigration(cloneRmMigTitle, &rmMigrationWithoutLogging{})
|
||||
|
||||
mg.AddMigration(cloneMigTitle, &migration{
|
||||
seenChannelUIDs: make(map[string]struct{}),
|
||||
migratedChannelsPerOrg: make(map[int64]map[*notificationChannel]struct{}),
|
||||
portedChannelGroupsPerOrg: make(map[int64]map[string]string),
|
||||
})
|
||||
|
||||
case !ngEnabled && migrationRun:
|
||||
// Remove the migration entry that creates unified alerting data. This is so when the feature
|
||||
// flag is enabled in the future the migration "move dashboard alerts to unified alerting" will be run again.
|
||||
mg.AddMigration(fmt.Sprintf(clearMigrationEntryTitle, cloneMigTitle), &clearMigrationEntry{
|
||||
migrationID: cloneMigTitle,
|
||||
})
|
||||
if err != nil {
|
||||
mg.Logger.Error("alert migration error: could not clear clone dashboard alert migration", "error", err)
|
||||
}
|
||||
// Removes all unified alerting data. It is not recorded so when the feature
|
||||
// flag is enabled in future the "clone remove unified alerting data" migration will be run again.
|
||||
mg.AddMigration(cloneRmMigTitle, &rmMigrationWithoutLogging{})
|
||||
}
|
||||
}
|
||||
|
||||
// clearMigrationEntry removes an entry fromt the migration_log table.
|
||||
// This migration is not recorded in the migration_log so that it can re-run several times.
|
||||
type clearMigrationEntry struct {
|
||||
migrator.MigrationBase
|
||||
|
||||
migrationID string
|
||||
}
|
||||
|
||||
func (m *clearMigrationEntry) SQL(dialect migrator.Dialect) string {
|
||||
return "clear migration entry code migration"
|
||||
}
|
||||
|
||||
func (m *clearMigrationEntry) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
_, err := sess.SQL(`DELETE from migration_log where migration_id = ?`, m.migrationID).Query()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clear migration entry %v: %w", m.migrationID, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *clearMigrationEntry) SkipMigrationLog() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type migration struct {
|
||||
migrator.MigrationBase
|
||||
// session and mg are attached for convenience.
|
||||
sess *xorm.Session
|
||||
mg *migrator.Migrator
|
||||
|
||||
seenChannelUIDs map[string]struct{}
|
||||
migratedChannels map[*notificationChannel]struct{}
|
||||
silences []*pb.MeshSilence
|
||||
portedChannelGroups map[string]string // Channel group key -> receiver name.
|
||||
lastReceiverID int // For the auto generated receivers.
|
||||
seenChannelUIDs map[string]struct{}
|
||||
migratedChannelsPerOrg map[int64]map[*notificationChannel]struct{}
|
||||
silences []*pb.MeshSilence
|
||||
portedChannelGroupsPerOrg map[int64]map[string]string // Org -> Channel group key -> receiver name.
|
||||
lastReceiverID int // For the auto generated receivers.
|
||||
}
|
||||
|
||||
func (m *migration) SQL(dialect migrator.Dialect) string {
|
||||
@@ -108,13 +181,13 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
}
|
||||
|
||||
// allChannels: channelUID -> channelConfig
|
||||
allChannels, defaultChannels, err := m.getNotificationChannelMap()
|
||||
allChannelsPerOrg, defaultChannelsPerOrg, err := m.getNotificationChannelMap()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
amConfig := PostableUserConfig{}
|
||||
err = m.addDefaultChannels(&amConfig, allChannels, defaultChannels)
|
||||
amConfigPerOrg := make(amConfigsPerOrg, len(allChannelsPerOrg))
|
||||
err = m.addDefaultChannels(amConfigPerOrg, allChannelsPerOrg, defaultChannelsPerOrg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -144,26 +217,11 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
}
|
||||
|
||||
// get folder if exists
|
||||
folder := dashboard{}
|
||||
if dash.FolderId > 0 {
|
||||
exists, err := m.sess.Where("id=?", dash.FolderId).Get(&folder)
|
||||
if err != nil {
|
||||
return MigrationError{
|
||||
Err: fmt.Errorf("failed to get folder %d: %w", dash.FolderId, err),
|
||||
AlertId: da.Id,
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
return MigrationError{
|
||||
Err: fmt.Errorf("folder with id %v not found", dash.FolderId),
|
||||
AlertId: da.Id,
|
||||
}
|
||||
}
|
||||
if !folder.IsFolder {
|
||||
return MigrationError{
|
||||
Err: fmt.Errorf("id %v is a dashboard not a folder", dash.FolderId),
|
||||
AlertId: da.Id,
|
||||
}
|
||||
folder, err := m.getFolder(dash, da)
|
||||
if err != nil {
|
||||
return MigrationError{
|
||||
Err: err,
|
||||
AlertId: da.Id,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -220,8 +278,12 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.updateReceiverAndRoute(allChannels, defaultChannels, da, rule, &amConfig); err != nil {
|
||||
return err
|
||||
if _, ok := amConfigPerOrg[rule.OrgID]; !ok {
|
||||
m.mg.Logger.Info("no configuration found", "org", rule.OrgID)
|
||||
} else {
|
||||
if err := m.updateReceiverAndRoute(allChannelsPerOrg, defaultChannelsPerOrg, da, rule, amConfigPerOrg[rule.OrgID]); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(mg.Dialect.DriverName(), migrator.Postgres) {
|
||||
@@ -234,8 +296,8 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
}
|
||||
if err != nil {
|
||||
// TODO better error handling, if constraint
|
||||
rule.Title += fmt.Sprintf(" %v", rule.Uid)
|
||||
rule.RuleGroup += fmt.Sprintf(" %v", rule.Uid)
|
||||
rule.Title += fmt.Sprintf(" %v", rule.UID)
|
||||
rule.RuleGroup += fmt.Sprintf(" %v", rule.UID)
|
||||
|
||||
_, err = m.sess.Insert(rule)
|
||||
if err != nil {
|
||||
@@ -250,24 +312,26 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a separate receiver for all the unmigrated channels.
|
||||
err = m.addUnmigratedChannels(&amConfig, allChannels, defaultChannels)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for orgID, amConfig := range amConfigPerOrg {
|
||||
// Create a separate receiver for all the unmigrated channels.
|
||||
err = m.addUnmigratedChannels(orgID, amConfig, allChannelsPerOrg[orgID], defaultChannelsPerOrg[orgID])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.writeAlertmanagerConfig(&amConfig, allChannels); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := m.writeAlertmanagerConfig(orgID, amConfig, allChannelsPerOrg[orgID]); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := m.writeSilencesFile(); err != nil {
|
||||
m.mg.Logger.Error("alert migration error: failed to write silence file", "err", err)
|
||||
if err := m.writeSilencesFile(orgID); err != nil {
|
||||
m.mg.Logger.Error("alert migration error: failed to write silence file", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *migration) writeAlertmanagerConfig(amConfig *PostableUserConfig, allChannels map[interface{}]*notificationChannel) error {
|
||||
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")
|
||||
@@ -288,6 +352,7 @@ func (m *migration) writeAlertmanagerConfig(amConfig *PostableUserConfig, allCha
|
||||
// Since we are migration for a snapshot of the code, it is always going to migrate to
|
||||
// the v1 config.
|
||||
ConfigurationVersion: "v1",
|
||||
OrgID: orgID,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -297,13 +362,15 @@ func (m *migration) writeAlertmanagerConfig(amConfig *PostableUserConfig, allCha
|
||||
}
|
||||
|
||||
type AlertConfiguration struct {
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
ID int64 `xorm:"pk autoincr 'id'"`
|
||||
OrgID int64 `xorm:"org_id"`
|
||||
|
||||
AlertmanagerConfiguration string
|
||||
ConfigurationVersion string
|
||||
CreatedAt int64 `xorm:"created"`
|
||||
}
|
||||
|
||||
// rmMigration removes Grafana 8 alert data
|
||||
type rmMigration struct {
|
||||
migrator.MigrationBase
|
||||
}
|
||||
@@ -343,9 +410,23 @@ func (m *rmMigration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.RemoveAll(silencesFileName(mg)); err != nil {
|
||||
mg.Logger.Error("alert migration error: failed to remove silence file", "err", err)
|
||||
files, err := getSilenceFileNamesForAllOrgs(mg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, f := range files {
|
||||
if err := os.Remove(f); err != nil {
|
||||
mg.Logger.Error("alert migration error: failed to remove silence file", "file", f, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// rmMigrationWithoutLogging is similar migration to rmMigration
|
||||
// but is not recorded in the migration_log table so that it can rerun in the future
|
||||
type rmMigrationWithoutLogging = rmMigration
|
||||
|
||||
func (m *rmMigrationWithoutLogging) SkipMigrationLog() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ func (m *MigrationBase) GetCondition() MigrationCondition {
|
||||
return m.Condition
|
||||
}
|
||||
|
||||
func (m *MigrationBase) SkipMigrationLog() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
type RawSQLMigration struct {
|
||||
MigrationBase
|
||||
|
||||
|
||||
@@ -108,13 +108,17 @@ func (mg *Migrator) Start() error {
|
||||
if err != nil {
|
||||
mg.Logger.Error("Exec failed", "error", err, "sql", sql)
|
||||
record.Error = err.Error()
|
||||
if _, err := sess.Insert(&record); err != nil {
|
||||
return err
|
||||
if !m.SkipMigrationLog() {
|
||||
if _, err := sess.Insert(&record); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
record.Success = true
|
||||
_, err = sess.Insert(&record)
|
||||
if !m.SkipMigrationLog() {
|
||||
_, err = sess.Insert(&record)
|
||||
}
|
||||
if err == nil {
|
||||
migrationsPerformed++
|
||||
}
|
||||
@@ -171,16 +175,6 @@ func (mg *Migrator) exec(m Migration, sess *xorm.Session) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mg *Migrator) ClearMigrationEntry(id string) error {
|
||||
sess := mg.x.NewSession()
|
||||
defer sess.Close()
|
||||
_, err := sess.SQL(`DELETE from migration_log where migration_id = ?`, id).Query()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to clear migration entry %v: %w", id, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type dbTransactionFunc func(sess *xorm.Session) error
|
||||
|
||||
func (mg *Migrator) InTransaction(callback dbTransactionFunc) error {
|
||||
|
||||
@@ -19,6 +19,10 @@ type Migration interface {
|
||||
Id() string
|
||||
SetId(string)
|
||||
GetCondition() MigrationCondition
|
||||
// SkipMigrationLog is used by dashboard alert migration to Grafana 8 Alerts
|
||||
// for skipping recording it in the migration_log so that it can run several times.
|
||||
// For all the other migrations it should be false.
|
||||
SkipMigrationLog() bool
|
||||
}
|
||||
|
||||
type CodeMigration interface {
|
||||
|
||||
Reference in New Issue
Block a user