diff --git a/conf/defaults.ini b/conf/defaults.ini index 84819d83a75..ff30a4d640a 100644 --- a/conf/defaults.ini +++ b/conf/defaults.ini @@ -9,6 +9,9 @@ app_mode = production # instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty instance_name = ${HOSTNAME} +# force migration will run migrations that might cause dataloss +force_migration = false + #################################### Paths ############################### [paths] # Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) diff --git a/conf/sample.ini b/conf/sample.ini index 7807f03976b..2fea29cbefb 100644 --- a/conf/sample.ini +++ b/conf/sample.ini @@ -9,6 +9,9 @@ # instance name, defaults to HOSTNAME environment variable value or hostname if HOSTNAME var is empty ;instance_name = ${HOSTNAME} +# force migration will run migrations that might cause dataloss +;force_migration = false + #################################### Paths #################################### [paths] # Path to where grafana can store temp files, sessions, and the sqlite3 db (if that is used) diff --git a/docs/sources/administration/configuration.md b/docs/sources/administration/configuration.md index 6ac1f0f6015..c2466a03b4c 100644 --- a/docs/sources/administration/configuration.md +++ b/docs/sources/administration/configuration.md @@ -135,6 +135,10 @@ Options are `production` and `development`. Default is `production`. _Do not_ ch Set the name of the grafana-server instance. Used in logging, internal metrics, and clustering info. Defaults to: `${HOSTNAME}`, which will be replaced with environment variable `HOSTNAME`, if that is empty or does not exist Grafana will try to use system calls to get the machine name. +### force_migration + +Force migration will run migrations that might cause data loss. Default is `false`. +
## [paths] diff --git a/pkg/services/sqlstore/migrations/ualert/migration_test.go b/pkg/services/sqlstore/migrations/ualert/migration_test.go index 63cb4214ec4..4d9b1340316 100644 --- a/pkg/services/sqlstore/migrations/ualert/migration_test.go +++ b/pkg/services/sqlstore/migrations/ualert/migration_test.go @@ -29,6 +29,7 @@ func TestAddDashAlertMigration(t *testing.T) { name string config *setting.Cfg isMigrationRun bool + shouldPanic bool expected []string // set of migration titles }{ { @@ -47,6 +48,18 @@ func TestAddDashAlertMigration(t *testing.T) { UnifiedAlerting: setting.UnifiedAlertingSettings{ Enabled: boolPointer(false), }, + ForceMigration: true, + }, + isMigrationRun: true, + expected: []string{fmt.Sprintf(ualert.ClearMigrationEntryTitle, ualert.MigTitle), ualert.RmMigTitle}, + }, + { + name: "when unified alerting disabled, migration is already run and force migration is disabled, then the migration should panic", + config: &setting.Cfg{ + UnifiedAlerting: setting.UnifiedAlertingSettings{ + Enabled: boolPointer(false), + }, + ForceMigration: false, }, isMigrationRun: true, expected: []string{fmt.Sprintf(ualert.ClearMigrationEntryTitle, ualert.MigTitle), ualert.RmMigTitle}, @@ -75,6 +88,12 @@ func TestAddDashAlertMigration(t *testing.T) { for _, tt := range tc { t.Run(tt.name, func(t *testing.T) { + defer func() { + // if the code should panic, make sure it has + if r := recover(); r == nil && tt.shouldPanic { + t.Errorf("The code did not panic") + } + }() if tt.isMigrationRun { log := migrator.MigrationLog{ MigrationID: ualert.MigTitle, @@ -90,6 +109,7 @@ func TestAddDashAlertMigration(t *testing.T) { } mg := migrator.NewMigrator(x, tt.config) + ualert.AddDashAlertMigration(mg) require.Equal(t, tt.expected, mg.GetMigrationIDs(false)) }) diff --git a/pkg/services/sqlstore/migrations/ualert/ualert.go b/pkg/services/sqlstore/migrations/ualert/ualert.go index 7c91f53aaf2..3c66ddb33d1 100644 --- a/pkg/services/sqlstore/migrations/ualert/ualert.go +++ b/pkg/services/sqlstore/migrations/ualert/ualert.go @@ -73,6 +73,10 @@ func AddDashAlertMigration(mg *migrator.Migrator) { silences: make(map[int64][]*pb.MeshSilence), }) case !mg.Cfg.UnifiedAlerting.IsEnabled() && migrationRun: + // Safeguard to prevent data loss when migrating from UA to LA + if !mg.Cfg.ForceMigration { + panic("New alert rules created while using unified alerting will be deleted, set force_migration=true in your grafana.ini and try again if this is okay.") + } // 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, migTitle), &clearMigrationEntry{ diff --git a/pkg/setting/setting.go b/pkg/setting/setting.go index 43974b76986..632f822079b 100644 --- a/pkg/setting/setting.go +++ b/pkg/setting/setting.go @@ -386,6 +386,8 @@ type Cfg struct { Env string + ForceMigration bool + // Analytics CheckForGrafanaUpdates bool CheckForPluginUpdates bool @@ -886,6 +888,7 @@ func (cfg *Cfg) Load(args CommandLineArgs) error { Env = valueAsString(iniFile.Section(""), "app_mode", "development") cfg.Env = Env + cfg.ForceMigration = iniFile.Section("").Key("force_migration").MustBool(false) InstanceName = valueAsString(iniFile.Section(""), "instance_name", "unknown_instance_name") plugins := valueAsString(iniFile.Section("paths"), "plugins", "") cfg.PluginsPath = makeAbsolute(plugins, HomePath)