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
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user