mirror of
https://github.com/grafana/grafana.git
synced 2025-02-20 11:48:34 -06:00
* Not complete, put migration behind env flag for now: UALERT_MIG=iDidBackup * Important to backup, and not expect the same DB to keep working until the env trigger is removed. * Alerting: Migrate dashboard alert permissions * Do not use imported models * Change folder titles Co-authored-by: Sofia Papagiannaki <papagian@users.noreply.github.com>
181 lines
4.7 KiB
Go
181 lines
4.7 KiB
Go
package ualert
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
|
"xorm.io/xorm"
|
|
)
|
|
|
|
const GENERAL_FOLDER = "General Alerting"
|
|
const DASHBOARD_FOLDER = "Migrated %s"
|
|
|
|
type MigrationError struct {
|
|
AlertId int64
|
|
Err error
|
|
}
|
|
|
|
func (e MigrationError) Error() string {
|
|
return fmt.Sprintf("failed to migrate alert %d: %s", e.AlertId, e.Err.Error())
|
|
}
|
|
|
|
func (e *MigrationError) Unwrap() error { return e.Err }
|
|
|
|
func AddMigration(mg *migrator.Migrator) {
|
|
if os.Getenv("UALERT_MIG") == "iDidBackup" {
|
|
// TODO: unified alerting DB needs to be extacted into ../migrations.go
|
|
// so it runs and creates the tables before this migration runs.
|
|
mg.AddMigration("move dashboard alerts to unified alerting", &migration{})
|
|
}
|
|
}
|
|
|
|
type migration struct {
|
|
migrator.MigrationBase
|
|
// session and mg are attached for convenience.
|
|
sess *xorm.Session
|
|
mg *migrator.Migrator
|
|
}
|
|
|
|
func (m *migration) SQL(dialect migrator.Dialect) string {
|
|
return "code migration"
|
|
}
|
|
|
|
func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
|
m.sess = sess
|
|
m.mg = mg
|
|
|
|
dashAlerts, err := m.slurpDashAlerts()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// [orgID, dataSourceId] -> [UID, Name]
|
|
dsIDMap, err := m.slurpDSIDs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// [orgID, dashboardId] -> dashUID
|
|
dashIDMap, err := m.slurpDashUIDs()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, da := range dashAlerts {
|
|
newCond, err := transConditions(*da.ParsedSettings, da.OrgId, dsIDMap)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
da.DashboardUID = dashIDMap[[2]int64{da.OrgId, da.DashboardId}]
|
|
|
|
// get dashboard
|
|
dash := dashboard{}
|
|
exists, err := m.sess.Where("org_id=? AND uid=?", da.OrgId, da.DashboardUID).Get(&dash)
|
|
if err != nil {
|
|
return MigrationError{
|
|
Err: fmt.Errorf("failed to get dashboard %s under organisation %d: %w", da.DashboardUID, da.OrgId, err),
|
|
AlertId: da.Id,
|
|
}
|
|
}
|
|
if !exists {
|
|
return MigrationError{
|
|
Err: fmt.Errorf("dashboard with UID %v under organisation %d not found: %w", da.DashboardUID, da.OrgId, err),
|
|
AlertId: da.Id,
|
|
}
|
|
}
|
|
|
|
// 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,
|
|
}
|
|
}
|
|
}
|
|
|
|
switch {
|
|
case dash.HasAcl:
|
|
// create folder and assign the permissions of the dashboard (included default and inherited)
|
|
ptr, err := m.createFolder(dash.OrgId, fmt.Sprintf(DASHBOARD_FOLDER, getMigrationInfo(da)))
|
|
if err != nil {
|
|
return MigrationError{
|
|
Err: fmt.Errorf("failed to create folder: %w", err),
|
|
AlertId: da.Id,
|
|
}
|
|
}
|
|
folder = *ptr
|
|
permissions, err := m.getACL(dash.OrgId, dash.Id)
|
|
if err != nil {
|
|
return MigrationError{
|
|
Err: fmt.Errorf("failed to get dashboard %d under organisation %d permissions: %w", dash.Id, dash.OrgId, err),
|
|
AlertId: da.Id,
|
|
}
|
|
}
|
|
err = m.setACL(folder.OrgId, folder.Id, permissions)
|
|
if err != nil {
|
|
return MigrationError{
|
|
Err: fmt.Errorf("failed to set folder %d under organisation %d permissions: %w", folder.Id, folder.OrgId, err),
|
|
AlertId: da.Id,
|
|
}
|
|
}
|
|
case dash.FolderId > 0:
|
|
// link the new rule to the existing folder
|
|
default:
|
|
// get or create general folder
|
|
ptr, err := m.getOrCreateGeneralFolder(dash.OrgId)
|
|
if err != nil {
|
|
return MigrationError{
|
|
Err: fmt.Errorf("failed to get or create general folder under organisation %d: %w", dash.OrgId, err),
|
|
AlertId: da.Id,
|
|
}
|
|
}
|
|
// No need to assign default permissions to general folder
|
|
// because they are included to the query result if it's a folder with no permissions
|
|
// https://github.com/grafana/grafana/blob/076e2ce06a6ecf15804423fcc8dca1b620a321e5/pkg/services/sqlstore/dashboard_acl.go#L109
|
|
folder = *ptr
|
|
}
|
|
|
|
if folder.Uid == "" {
|
|
return MigrationError{
|
|
Err: fmt.Errorf("empty folder identifier"),
|
|
AlertId: da.Id,
|
|
}
|
|
}
|
|
rule, err := m.makeAlertRule(*newCond, da, folder.Uid)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = m.sess.Insert(rule)
|
|
if err != nil {
|
|
// TODO better error handling, if constraint
|
|
rule.Title += fmt.Sprintf(" %v", rule.Uid)
|
|
rule.RuleGroup += fmt.Sprintf(" %v", rule.Uid)
|
|
|
|
_, err = m.sess.Insert(rule)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|