Alerting: Create folder for alerting when start from the scratch (#48866)

* create folderHelper struct
This commit is contained in:
Yuriy Tseretyan
2022-05-13 11:49:04 -04:00
committed by GitHub
parent 4cd1a113ef
commit 00ef1acb93
4 changed files with 102 additions and 18 deletions

View File

@@ -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

View File

@@ -94,6 +94,7 @@ func (*OSSMigrations) AddMigration(mg *Migrator) {
addEntityEventsTableMigration(mg)
addPublicDashboardMigration(mg)
ualert.CreateDefaultFoldersForAlertingMigration(mg)
}
func addMigrationLogMigrations(mg *Migrator) {

View File

@@ -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
}

View File

@@ -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"
}