diff --git a/conf/defaults.ini b/conf/defaults.ini
index 904719d5620..311f879f026 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 fbe7b6b7f8d..62746ca6dc1 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 4a9106482d3..ada7828d842 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 0061d9f5871..84cd8a87e03 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 58d695f41fc..fb2b6251e60 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
@@ -884,6 +886,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)