mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Create folder for alerting when start from the scratch (#48866)
* create folderHelper struct
This commit is contained in:
@@ -49,6 +49,7 @@ Scopes must have an order to ensure consistency and ease of search, this helps u
|
||||
- `grafana_alerting_ticker_last_consumed_tick_timestamp_seconds`
|
||||
- `grafana_alerting_ticker_next_tick_timestamp_seconds`
|
||||
- `grafana_alerting_ticker_interval_seconds`
|
||||
- [ENHANCEMENT] Create folder 'General Alerting' when Grafana starts from the scratch #48866
|
||||
- [FEATURE] Indicate whether routes are provisioned when GETting Alertmanager configuration #47857
|
||||
- [FEATURE] Indicate whether contact point is provisioned when GETting Alertmanager configuration #48323
|
||||
- [FEATURE] Indicate whether alert rule is provisioned when GETting the rule #48458
|
||||
|
@@ -94,6 +94,7 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
|
||||
addEntityEventsTableMigration(mg)
|
||||
|
||||
addPublicDashboardMigration(mg)
|
||||
ualert.CreateDefaultFoldersForAlertingMigration(mg)
|
||||
}
|
||||
|
||||
func addMigrationLogMigrations(mg *Migrator) {
|
||||
|
@@ -4,7 +4,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"xorm.io/xorm"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
@@ -40,9 +43,14 @@ type dashboardAcl struct {
|
||||
Updated time.Time
|
||||
}
|
||||
|
||||
type folderHelper struct {
|
||||
sess *xorm.Session
|
||||
mg *migrator.Migrator
|
||||
}
|
||||
|
||||
// getOrCreateGeneralFolder returns the general folder under the specific organisation
|
||||
// If the general folder does not exist it creates it.
|
||||
func (m *migration) getOrCreateGeneralFolder(orgID int64) (*dashboard, error) {
|
||||
func (m *folderHelper) getOrCreateGeneralFolder(orgID int64) (*dashboard, error) {
|
||||
// there is a unique constraint on org_id, folder_id, title
|
||||
// there are no nested folders so the parent folder id is always 0
|
||||
dashboard := dashboard{OrgId: orgID, FolderId: 0, Title: GENERAL_FOLDER}
|
||||
@@ -51,18 +59,17 @@ func (m *migration) getOrCreateGeneralFolder(orgID int64) (*dashboard, error) {
|
||||
return nil, err
|
||||
} else if !has {
|
||||
// create folder
|
||||
result, err := m.createFolder(orgID, GENERAL_FOLDER)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return m.createGeneralFolder(orgID)
|
||||
}
|
||||
return &dashboard, nil
|
||||
}
|
||||
|
||||
func (m *folderHelper) createGeneralFolder(orgID int64) (*dashboard, error) {
|
||||
return m.createFolder(orgID, GENERAL_FOLDER)
|
||||
}
|
||||
|
||||
// returns the folder of the given dashboard (if exists)
|
||||
func (m *migration) getFolder(dash dashboard, da dashAlert) (dashboard, error) {
|
||||
func (m *folderHelper) getFolder(dash dashboard, da dashAlert) (dashboard, error) {
|
||||
// get folder if exists
|
||||
folder := dashboard{}
|
||||
if dash.FolderId > 0 {
|
||||
@@ -82,7 +89,7 @@ func (m *migration) getFolder(dash dashboard, da dashAlert) (dashboard, error) {
|
||||
|
||||
// based on sqlstore.saveDashboard()
|
||||
// it should be called from inside a transaction
|
||||
func (m *migration) createFolder(orgID int64, title string) (*dashboard, error) {
|
||||
func (m *folderHelper) createFolder(orgID int64, title string) (*dashboard, error) {
|
||||
cmd := saveFolderCommand{
|
||||
OrgId: orgID,
|
||||
FolderId: 0,
|
||||
@@ -129,7 +136,7 @@ func (m *migration) createFolder(orgID int64, title string) (*dashboard, error)
|
||||
return dash, nil
|
||||
}
|
||||
|
||||
func (m *migration) generateNewDashboardUid(orgId int64) (string, error) {
|
||||
func (m *folderHelper) generateNewDashboardUid(orgId int64) (string, error) {
|
||||
for i := 0; i < 3; i++ {
|
||||
uid := util.GenerateShortUID()
|
||||
|
||||
@@ -148,7 +155,7 @@ func (m *migration) generateNewDashboardUid(orgId int64) (string, error) {
|
||||
|
||||
// based on SQLStore.UpdateDashboardACL()
|
||||
// it should be called from inside a transaction
|
||||
func (m *migration) setACL(orgID int64, dashboardID int64, items []*dashboardAcl) error {
|
||||
func (m *folderHelper) setACL(orgID int64, dashboardID int64, items []*dashboardAcl) error {
|
||||
if dashboardID <= 0 {
|
||||
return fmt.Errorf("folder id must be greater than zero for a folder permission")
|
||||
}
|
||||
@@ -247,7 +254,7 @@ func (m *migration) setACL(orgID int64, dashboardID int64, items []*dashboardAcl
|
||||
}
|
||||
|
||||
// based on SQLStore.GetDashboardAclInfoList()
|
||||
func (m *migration) getACL(orgID, dashboardID int64) ([]*dashboardAcl, error) {
|
||||
func (m *folderHelper) getACL(orgID, dashboardID int64) ([]*dashboardAcl, error) {
|
||||
var err error
|
||||
|
||||
falseStr := m.mg.Dialect.BooleanStr(false)
|
||||
@@ -279,3 +286,18 @@ func (m *migration) getACL(orgID, dashboardID int64) ([]*dashboardAcl, error) {
|
||||
err = m.sess.SQL(rawSQL, orgID, dashboardID).Find(&result)
|
||||
return result, err
|
||||
}
|
||||
|
||||
// getOrgsThatHaveFolders returns a unique list of organization ID that have at least one folder
|
||||
func (m *folderHelper) getOrgsIDThatHaveFolders() (map[int64]struct{}, error) {
|
||||
// get folder if exists
|
||||
var rows []int64
|
||||
err := m.sess.Table(&dashboard{}).Where("is_folder=?", true).Distinct("org_id").Find(&rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := make(map[int64]struct{}, len(rows))
|
||||
for _, s := range rows {
|
||||
result[s] = struct{}{}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
@@ -223,7 +223,7 @@ func (m *migration) SQL(dialect migrator.Dialect) string {
|
||||
return "code migration"
|
||||
}
|
||||
|
||||
//nolint: gocyclo
|
||||
// nolint: gocyclo
|
||||
func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
m.sess = sess
|
||||
m.mg = mg
|
||||
@@ -276,6 +276,11 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
}
|
||||
}
|
||||
|
||||
folderHelper := folderHelper{
|
||||
sess: sess,
|
||||
mg: mg,
|
||||
}
|
||||
|
||||
var folder *dashboard
|
||||
switch {
|
||||
case dash.HasAcl:
|
||||
@@ -284,21 +289,21 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
if !ok {
|
||||
mg.Logger.Info("create a new folder for alerts that belongs to dashboard because it has custom permissions", "org", dash.OrgId, "dashboard_uid", dash.Uid, "folder", folderName)
|
||||
// create folder and assign the permissions of the dashboard (included default and inherited)
|
||||
f, err = m.createFolder(dash.OrgId, folderName)
|
||||
f, err = folderHelper.createFolder(dash.OrgId, folderName)
|
||||
if err != nil {
|
||||
return MigrationError{
|
||||
Err: fmt.Errorf("failed to create folder: %w", err),
|
||||
AlertId: da.Id,
|
||||
}
|
||||
}
|
||||
permissions, err := m.getACL(dash.OrgId, dash.Id)
|
||||
permissions, err := folderHelper.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(f.OrgId, f.Id, permissions)
|
||||
err = folderHelper.setACL(f.OrgId, f.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),
|
||||
@@ -310,7 +315,7 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
folder = f
|
||||
case dash.FolderId > 0:
|
||||
// get folder if exists
|
||||
f, err := m.getFolder(dash, da)
|
||||
f, err := folderHelper.getFolder(dash, da)
|
||||
if err != nil {
|
||||
return MigrationError{
|
||||
Err: err,
|
||||
@@ -322,7 +327,7 @@ func (m *migration) Exec(sess *xorm.Session, mg *migrator.Migrator) error {
|
||||
f, ok := folderCache[GENERAL_FOLDER]
|
||||
if !ok {
|
||||
// get or create general folder
|
||||
f, err = m.getOrCreateGeneralFolder(dash.OrgId)
|
||||
f, err = folderHelper.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),
|
||||
@@ -753,3 +758,58 @@ func getAlertFolderNameFromDashboard(dash *dashboard) string {
|
||||
}
|
||||
return fmt.Sprintf(DASHBOARD_FOLDER, title, dash.Uid) // include UID to the name to avoid collision
|
||||
}
|
||||
|
||||
// CreateDefaultFoldersForAlertingMigration creates a folder dedicated for alerting if no folders exist
|
||||
func CreateDefaultFoldersForAlertingMigration(mg *migrator.Migrator) {
|
||||
if !mg.Cfg.UnifiedAlerting.IsEnabled() {
|
||||
return
|
||||
}
|
||||
mg.AddMigration("create default alerting folders", &createDefaultFoldersForAlertingMigration{})
|
||||
}
|
||||
|
||||
type createDefaultFoldersForAlertingMigration struct {
|
||||
migrator.MigrationBase
|
||||
}
|
||||
|
||||
func (c createDefaultFoldersForAlertingMigration) Exec(sess *xorm.Session, migrator *migrator.Migrator) error {
|
||||
helper := folderHelper{
|
||||
sess: sess,
|
||||
mg: migrator,
|
||||
}
|
||||
|
||||
var rows []struct {
|
||||
Id int64
|
||||
Name string
|
||||
}
|
||||
|
||||
if err := sess.Table("org").Cols("id", "name").Find(&rows); err != nil {
|
||||
return fmt.Errorf("failed to read the list of organizations: %w", err)
|
||||
}
|
||||
|
||||
orgsWithFolders, err := helper.getOrgsIDThatHaveFolders()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list organizations that have at least one folder: %w", err)
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
// if there's at least one folder in the org or if alerting is disabled for that org, skip adding the default folder
|
||||
if _, ok := orgsWithFolders[row.Id]; ok {
|
||||
migrator.Logger.Debug("Skip adding default alerting folder because organization already has at least one folder", "org_id", row.Id)
|
||||
continue
|
||||
}
|
||||
if _, ok := migrator.Cfg.UnifiedAlerting.DisabledOrgs[row.Id]; ok {
|
||||
migrator.Logger.Debug("Skip adding default alerting folder because alerting is disabled for the organization ", "org_id", row.Id)
|
||||
continue
|
||||
}
|
||||
folder, err := helper.createGeneralFolder(row.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create the default alerting folder for organization %s (ID: %d): %w", row.Name, row.Id, err)
|
||||
}
|
||||
migrator.Logger.Info("created the default folder for alerting", "org_id", row.Id, "folder_name", folder.Title, "folder_uid", folder.Uid)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c createDefaultFoldersForAlertingMigration) SQL(migrator.Dialect) string {
|
||||
return "code migration"
|
||||
}
|
||||
|
Reference in New Issue
Block a user