mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 18:13:32 -06:00
653 lines
21 KiB
Go
653 lines
21 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/services/guardian"
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
|
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
|
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
// AlertRuleMaxTitleLength is the maximum length of the alert rule title
|
|
const AlertRuleMaxTitleLength = 190
|
|
|
|
// AlertRuleMaxRuleGroupNameLength is the maximum length of the alert rule group name
|
|
const AlertRuleMaxRuleGroupNameLength = 190
|
|
|
|
type UpdateRuleGroupCmd struct {
|
|
OrgID int64
|
|
NamespaceUID string
|
|
RuleGroupConfig apimodels.PostableRuleGroupConfig
|
|
}
|
|
|
|
type UpsertRule struct {
|
|
Existing *ngmodels.AlertRule
|
|
New ngmodels.AlertRule
|
|
}
|
|
|
|
// Store is the interface for persisting alert rules and instances
|
|
type RuleStore interface {
|
|
DeleteAlertRuleByUID(orgID int64, ruleUID string) error
|
|
DeleteNamespaceAlertRules(orgID int64, namespaceUID string) ([]string, error)
|
|
DeleteRuleGroupAlertRules(orgID int64, namespaceUID string, ruleGroup string) ([]string, error)
|
|
DeleteAlertInstancesByRuleUID(orgID int64, ruleUID string) error
|
|
GetAlertRuleByUID(*ngmodels.GetAlertRuleByUIDQuery) error
|
|
GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery) error
|
|
GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error
|
|
GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRulesQuery) error
|
|
GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error
|
|
GetNamespaces(context.Context, int64, *models.SignedInUser) (map[string]*models.Folder, error)
|
|
GetNamespaceByTitle(context.Context, string, int64, *models.SignedInUser, bool) (*models.Folder, error)
|
|
GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error
|
|
UpsertAlertRules([]UpsertRule) error
|
|
UpdateRuleGroup(UpdateRuleGroupCmd) error
|
|
}
|
|
|
|
func getAlertRuleByUID(sess *sqlstore.DBSession, alertRuleUID string, orgID int64) (*ngmodels.AlertRule, error) {
|
|
// we consider optionally enabling some caching
|
|
alertRule := ngmodels.AlertRule{OrgID: orgID, UID: alertRuleUID}
|
|
has, err := sess.Get(&alertRule)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !has {
|
|
return nil, ngmodels.ErrAlertRuleNotFound
|
|
}
|
|
return &alertRule, nil
|
|
}
|
|
|
|
// DeleteAlertRuleByUID is a handler for deleting an alert rule.
|
|
func (st DBstore) DeleteAlertRuleByUID(orgID int64, ruleUID string) error {
|
|
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
_, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? AND uid = ?", orgID, ruleUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = sess.Exec("DELETE FROM alert_rule_version WHERE rule_org_id = ? and rule_uid = ?", orgID, ruleUID)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = sess.Exec("DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid = ?", orgID, ruleUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// DeleteNamespaceAlertRules is a handler for deleting namespace alert rules. A list of deleted rule UIDs are returned.
|
|
func (st DBstore) DeleteNamespaceAlertRules(orgID int64, namespaceUID string) ([]string, error) {
|
|
ruleUIDs := []string{}
|
|
|
|
err := st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
if err := sess.SQL("SELECT uid FROM alert_rule WHERE org_id = ? and namespace_uid = ?", orgID, namespaceUID).Find(&ruleUIDs); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? and namespace_uid = ?", orgID, namespaceUID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? and namespace_uid = ?", orgID, namespaceUID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := sess.Exec("DELETE FROM alert_rule_version WHERE rule_org_id = ? and rule_namespace_uid = ?", orgID, namespaceUID); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := sess.Exec(`DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid NOT IN (
|
|
SELECT uid FROM alert_rule where org_id = ?
|
|
)`, orgID, orgID); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
return ruleUIDs, err
|
|
}
|
|
|
|
// DeleteRuleGroupAlertRules is a handler for deleting rule group alert rules. A list of deleted rule UIDs are returned.
|
|
func (st DBstore) DeleteRuleGroupAlertRules(orgID int64, namespaceUID string, ruleGroup string) ([]string, error) {
|
|
ruleUIDs := []string{}
|
|
|
|
err := st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
if err := sess.SQL("SELECT uid FROM alert_rule WHERE org_id = ? and namespace_uid = ? and rule_group = ?",
|
|
orgID, namespaceUID, ruleGroup).Find(&ruleUIDs); err != nil {
|
|
return err
|
|
}
|
|
exist, err := sess.Exist(&ngmodels.AlertRule{OrgID: orgID, NamespaceUID: namespaceUID, RuleGroup: ruleGroup})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if !exist {
|
|
return ngmodels.ErrRuleGroupNamespaceNotFound
|
|
}
|
|
|
|
if _, err := sess.Exec("DELETE FROM alert_rule WHERE org_id = ? and namespace_uid = ? and rule_group = ?", orgID, namespaceUID, ruleGroup); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := sess.Exec("DELETE FROM alert_rule_version WHERE rule_org_id = ? and rule_namespace_uid = ? and rule_group = ?", orgID, namespaceUID, ruleGroup); err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := sess.Exec(`DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid NOT IN (
|
|
SELECT uid FROM alert_rule where org_id = ?
|
|
)`, orgID, orgID); err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
})
|
|
|
|
return ruleUIDs, err
|
|
}
|
|
|
|
// DeleteAlertInstanceByRuleUID is a handler for deleting alert instances by alert rule UID when a rule has been updated
|
|
func (st DBstore) DeleteAlertInstancesByRuleUID(orgID int64, ruleUID string) error {
|
|
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
_, err := sess.Exec("DELETE FROM alert_instance WHERE rule_org_id = ? AND rule_uid = ?", orgID, ruleUID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// GetAlertRuleByUID is a handler for retrieving an alert rule from that database by its UID and organisation ID.
|
|
// It returns ngmodels.ErrAlertRuleNotFound if no alert rule is found for the provided ID.
|
|
func (st DBstore) GetAlertRuleByUID(query *ngmodels.GetAlertRuleByUIDQuery) error {
|
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
alertRule, err := getAlertRuleByUID(sess, query.UID, query.OrgID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
query.Result = alertRule
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// UpsertAlertRules is a handler for creating/updating alert rules.
|
|
func (st DBstore) UpsertAlertRules(rules []UpsertRule) error {
|
|
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
newRules := make([]ngmodels.AlertRule, 0, len(rules))
|
|
ruleVersions := make([]ngmodels.AlertRuleVersion, 0, len(rules))
|
|
for _, r := range rules {
|
|
if r.Existing == nil && r.New.UID != "" {
|
|
// check by UID
|
|
existingAlertRule, err := getAlertRuleByUID(sess, r.New.UID, r.New.OrgID)
|
|
if err != nil {
|
|
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) {
|
|
return fmt.Errorf("failed to get alert rule %s: %w", r.New.UID, err)
|
|
}
|
|
return err
|
|
}
|
|
r.Existing = existingAlertRule
|
|
}
|
|
|
|
var parentVersion int64
|
|
switch r.Existing {
|
|
case nil: // new rule
|
|
uid, err := GenerateNewAlertRuleUID(sess, r.New.OrgID, r.New.Title)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to generate UID for alert rule %q: %w", r.New.Title, err)
|
|
}
|
|
r.New.UID = uid
|
|
|
|
if r.New.IntervalSeconds == 0 {
|
|
r.New.IntervalSeconds = int64(st.DefaultInterval.Seconds())
|
|
}
|
|
|
|
r.New.Version = 1
|
|
|
|
if r.New.NoDataState == "" {
|
|
// set default no data state
|
|
r.New.NoDataState = ngmodels.NoData
|
|
}
|
|
|
|
if r.New.ExecErrState == "" {
|
|
// set default error state
|
|
r.New.ExecErrState = ngmodels.AlertingErrState
|
|
}
|
|
|
|
if err := st.validateAlertRule(r.New); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := (&r.New).PreSave(TimeNow); err != nil {
|
|
return err
|
|
}
|
|
|
|
newRules = append(newRules, r.New)
|
|
default:
|
|
// explicitly set the existing properties if missing
|
|
// do not rely on xorm
|
|
if r.New.Title == "" {
|
|
r.New.Title = r.Existing.Title
|
|
}
|
|
|
|
if r.New.Condition == "" {
|
|
r.New.Condition = r.Existing.Condition
|
|
}
|
|
|
|
if len(r.New.Data) == 0 {
|
|
r.New.Data = r.Existing.Data
|
|
}
|
|
|
|
r.New.ID = r.Existing.ID
|
|
r.New.OrgID = r.Existing.OrgID
|
|
r.New.NamespaceUID = r.Existing.NamespaceUID
|
|
r.New.RuleGroup = r.Existing.RuleGroup
|
|
r.New.Version = r.Existing.Version + 1
|
|
|
|
if r.New.ExecErrState == "" {
|
|
r.New.ExecErrState = r.Existing.ExecErrState
|
|
}
|
|
|
|
if r.New.NoDataState == "" {
|
|
r.New.NoDataState = r.Existing.NoDataState
|
|
}
|
|
|
|
if err := st.validateAlertRule(r.New); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := (&r.New).PreSave(TimeNow); err != nil {
|
|
return err
|
|
}
|
|
|
|
// no way to update multiple rules at once
|
|
if _, err := sess.ID(r.Existing.ID).AllCols().Update(r.New); err != nil {
|
|
return fmt.Errorf("failed to update rule %s: %w", r.New.Title, err)
|
|
}
|
|
|
|
parentVersion = r.Existing.Version
|
|
}
|
|
|
|
ruleVersions = append(ruleVersions, ngmodels.AlertRuleVersion{
|
|
RuleOrgID: r.New.OrgID,
|
|
RuleUID: r.New.UID,
|
|
RuleNamespaceUID: r.New.NamespaceUID,
|
|
RuleGroup: r.New.RuleGroup,
|
|
ParentVersion: parentVersion,
|
|
Version: r.New.Version,
|
|
Created: r.New.Updated,
|
|
Condition: r.New.Condition,
|
|
Title: r.New.Title,
|
|
Data: r.New.Data,
|
|
IntervalSeconds: r.New.IntervalSeconds,
|
|
NoDataState: r.New.NoDataState,
|
|
ExecErrState: r.New.ExecErrState,
|
|
For: r.New.For,
|
|
Annotations: r.New.Annotations,
|
|
Labels: r.New.Labels,
|
|
})
|
|
}
|
|
|
|
if len(newRules) > 0 {
|
|
if _, err := sess.Insert(&newRules); err != nil {
|
|
return fmt.Errorf("failed to create new rules: %w", err)
|
|
}
|
|
}
|
|
|
|
if len(ruleVersions) > 0 {
|
|
if _, err := sess.Insert(&ruleVersions); err != nil {
|
|
return fmt.Errorf("failed to create new rule versions: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// GetOrgAlertRules is a handler for retrieving alert rules of specific organisation.
|
|
func (st DBstore) GetOrgAlertRules(query *ngmodels.ListAlertRulesQuery) error {
|
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
alertRules := make([]*ngmodels.AlertRule, 0)
|
|
q := "SELECT * FROM alert_rule WHERE org_id = ?"
|
|
params := []interface{}{query.OrgID}
|
|
|
|
if len(query.NamespaceUIDs) > 0 {
|
|
placeholders := make([]string, 0, len(query.NamespaceUIDs))
|
|
for _, folderUID := range query.NamespaceUIDs {
|
|
params = append(params, folderUID)
|
|
placeholders = append(placeholders, "?")
|
|
}
|
|
q = fmt.Sprintf("%s AND namespace_uid IN (%s)", q, strings.Join(placeholders, ","))
|
|
}
|
|
|
|
if query.DashboardUID != "" {
|
|
params = append(params, query.DashboardUID)
|
|
q = fmt.Sprintf("%s AND dashboard_uid = ?", q)
|
|
if query.PanelID != 0 {
|
|
params = append(params, query.PanelID)
|
|
q = fmt.Sprintf("%s AND panel_id = ?", q)
|
|
}
|
|
}
|
|
|
|
q = fmt.Sprintf("%s ORDER BY id ASC", q)
|
|
|
|
if err := sess.SQL(q, params...).Find(&alertRules); err != nil {
|
|
return err
|
|
}
|
|
|
|
query.Result = alertRules
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// GetNamespaceAlertRules is a handler for retrieving namespace alert rules of specific organisation.
|
|
func (st DBstore) GetNamespaceAlertRules(query *ngmodels.ListNamespaceAlertRulesQuery) error {
|
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
alertRules := make([]*ngmodels.AlertRule, 0)
|
|
// TODO rewrite using group by namespace_uid, rule_group
|
|
q := "SELECT * FROM alert_rule WHERE org_id = ? and namespace_uid = ?"
|
|
if err := sess.SQL(q, query.OrgID, query.NamespaceUID).Find(&alertRules); err != nil {
|
|
return err
|
|
}
|
|
|
|
query.Result = alertRules
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// GetRuleGroupAlertRules is a handler for retrieving rule group alert rules of specific organisation.
|
|
func (st DBstore) GetRuleGroupAlertRules(query *ngmodels.ListRuleGroupAlertRulesQuery) error {
|
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
q := "SELECT * FROM alert_rule WHERE org_id = ? and namespace_uid = ? and rule_group = ?"
|
|
args := []interface{}{query.OrgID, query.NamespaceUID, query.RuleGroup}
|
|
|
|
if query.DashboardUID != "" {
|
|
q = fmt.Sprintf("%s and dashboard_uid = ?", q)
|
|
args = append(args, query.DashboardUID)
|
|
if query.PanelID != 0 {
|
|
q = fmt.Sprintf("%s and panel_id = ?", q)
|
|
args = append(args, query.PanelID)
|
|
}
|
|
}
|
|
|
|
alertRules := make([]*ngmodels.AlertRule, 0)
|
|
if err := sess.SQL(q, args...).Find(&alertRules); err != nil {
|
|
return err
|
|
}
|
|
|
|
query.Result = alertRules
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// GetNamespaces returns the folders that are visible to the user
|
|
func (st DBstore) GetNamespaces(ctx context.Context, orgID int64, user *models.SignedInUser) (map[string]*models.Folder, error) {
|
|
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
|
namespaceMap := make(map[string]*models.Folder)
|
|
var page int64 = 1
|
|
for {
|
|
// if limit is negative; it fetches at most 1000
|
|
folders, err := s.GetFolders(ctx, -1, page)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(folders) == 0 {
|
|
break
|
|
}
|
|
|
|
for _, f := range folders {
|
|
namespaceMap[f.Uid] = f
|
|
}
|
|
page += 1
|
|
}
|
|
return namespaceMap, nil
|
|
}
|
|
|
|
// GetNamespaceByTitle is a handler for retrieving a namespace by its title. Alerting rules follow a Grafana folder-like structure which we call namespaces.
|
|
func (st DBstore) GetNamespaceByTitle(ctx context.Context, namespace string, orgID int64, user *models.SignedInUser, withCanSave bool) (*models.Folder, error) {
|
|
s := dashboards.NewFolderService(orgID, user, st.SQLStore)
|
|
folder, err := s.GetFolderByTitle(ctx, namespace)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if withCanSave {
|
|
g := guardian.New(ctx, folder.Id, orgID, user)
|
|
if canSave, err := g.CanSave(); err != nil || !canSave {
|
|
if err != nil {
|
|
st.Logger.Error("checking can save permission has failed", "userId", user.UserId, "username", user.Login, "namespace", namespace, "orgId", orgID, "error", err)
|
|
}
|
|
return nil, ngmodels.ErrCannotEditNamespace
|
|
}
|
|
}
|
|
|
|
return folder, nil
|
|
}
|
|
|
|
// GetAlertRulesForScheduling returns alert rule info (identifier, interval, version state)
|
|
// that is useful for it's scheduling.
|
|
func (st DBstore) GetAlertRulesForScheduling(query *ngmodels.ListAlertRulesQuery) error {
|
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
alerts := make([]*ngmodels.AlertRule, 0)
|
|
q := "SELECT uid, org_id, interval_seconds, version FROM alert_rule"
|
|
if len(query.ExcludeOrgs) > 0 {
|
|
q = fmt.Sprintf("%s WHERE org_id NOT IN (%s)", q, strings.Join(strings.Split(strings.Trim(fmt.Sprint(query.ExcludeOrgs), "[]"), " "), ","))
|
|
}
|
|
if err := sess.SQL(q).Find(&alerts); err != nil {
|
|
return err
|
|
}
|
|
query.Result = alerts
|
|
return nil
|
|
})
|
|
}
|
|
|
|
// GenerateNewAlertRuleUID generates a unique UID for a rule.
|
|
// This is set as a variable so that the tests can override it.
|
|
// The ruleTitle is only used by the mocked functions.
|
|
var GenerateNewAlertRuleUID = func(sess *sqlstore.DBSession, orgID int64, ruleTitle string) (string, error) {
|
|
for i := 0; i < 3; i++ {
|
|
uid := util.GenerateShortUID()
|
|
|
|
exists, err := sess.Where("org_id=? AND uid=?", orgID, uid).Get(&ngmodels.AlertRule{})
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if !exists {
|
|
return uid, nil
|
|
}
|
|
}
|
|
|
|
return "", ngmodels.ErrAlertRuleFailedGenerateUniqueUID
|
|
}
|
|
|
|
// validateAlertRule validates the alert rule interval and organisation.
|
|
func (st DBstore) validateAlertRule(alertRule ngmodels.AlertRule) error {
|
|
if len(alertRule.Data) == 0 {
|
|
return fmt.Errorf("%w: no queries or expressions are found", ngmodels.ErrAlertRuleFailedValidation)
|
|
}
|
|
|
|
if alertRule.Title == "" {
|
|
return fmt.Errorf("%w: title is empty", ngmodels.ErrAlertRuleFailedValidation)
|
|
}
|
|
|
|
if alertRule.IntervalSeconds%int64(st.BaseInterval.Seconds()) != 0 || alertRule.IntervalSeconds <= 0 {
|
|
return fmt.Errorf("%w: interval (%v) should be non-zero and divided exactly by scheduler interval: %v", ngmodels.ErrAlertRuleFailedValidation, time.Duration(alertRule.IntervalSeconds)*time.Second, st.BaseInterval)
|
|
}
|
|
|
|
// enfore max name length in SQLite
|
|
if len(alertRule.Title) > AlertRuleMaxTitleLength {
|
|
return fmt.Errorf("%w: name length should not be greater than %d", ngmodels.ErrAlertRuleFailedValidation, AlertRuleMaxTitleLength)
|
|
}
|
|
|
|
// enfore max rule group name length in SQLite
|
|
if len(alertRule.RuleGroup) > AlertRuleMaxRuleGroupNameLength {
|
|
return fmt.Errorf("%w: rule group name length should not be greater than %d", ngmodels.ErrAlertRuleFailedValidation, AlertRuleMaxRuleGroupNameLength)
|
|
}
|
|
|
|
if alertRule.OrgID == 0 {
|
|
return fmt.Errorf("%w: no organisation is found", ngmodels.ErrAlertRuleFailedValidation)
|
|
}
|
|
|
|
if alertRule.DashboardUID == nil && alertRule.PanelID != nil {
|
|
return fmt.Errorf("%w: cannot have Panel ID without a Dashboard UID", ngmodels.ErrAlertRuleFailedValidation)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateRuleGroup creates new rules and updates and/or deletes existing rules
|
|
func (st DBstore) UpdateRuleGroup(cmd UpdateRuleGroupCmd) error {
|
|
return st.SQLStore.WithTransactionalDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
ruleGroup := cmd.RuleGroupConfig.Name
|
|
q := &ngmodels.ListRuleGroupAlertRulesQuery{
|
|
OrgID: cmd.OrgID,
|
|
NamespaceUID: cmd.NamespaceUID,
|
|
RuleGroup: ruleGroup,
|
|
}
|
|
if err := st.GetRuleGroupAlertRules(q); err != nil {
|
|
return err
|
|
}
|
|
existingGroupRules := q.Result
|
|
|
|
existingGroupRulesUIDs := make(map[string]ngmodels.AlertRule, len(existingGroupRules))
|
|
for _, r := range existingGroupRules {
|
|
existingGroupRulesUIDs[r.UID] = *r
|
|
}
|
|
|
|
upsertRules := make([]UpsertRule, 0)
|
|
for _, r := range cmd.RuleGroupConfig.Rules {
|
|
if r.GrafanaManagedAlert == nil {
|
|
continue
|
|
}
|
|
|
|
newAlertRule := ngmodels.AlertRule{
|
|
OrgID: cmd.OrgID,
|
|
Title: r.GrafanaManagedAlert.Title,
|
|
Condition: r.GrafanaManagedAlert.Condition,
|
|
Data: r.GrafanaManagedAlert.Data,
|
|
UID: r.GrafanaManagedAlert.UID,
|
|
IntervalSeconds: int64(time.Duration(cmd.RuleGroupConfig.Interval).Seconds()),
|
|
NamespaceUID: cmd.NamespaceUID,
|
|
RuleGroup: ruleGroup,
|
|
NoDataState: ngmodels.NoDataState(r.GrafanaManagedAlert.NoDataState),
|
|
ExecErrState: ngmodels.ExecutionErrorState(r.GrafanaManagedAlert.ExecErrState),
|
|
}
|
|
|
|
if r.ApiRuleNode != nil {
|
|
newAlertRule.For = time.Duration(r.ApiRuleNode.For)
|
|
newAlertRule.Annotations = r.ApiRuleNode.Annotations
|
|
newAlertRule.Labels = r.ApiRuleNode.Labels
|
|
}
|
|
|
|
if s := newAlertRule.Annotations[ngmodels.DashboardUIDAnnotation]; s != "" {
|
|
newAlertRule.DashboardUID = &s
|
|
}
|
|
|
|
if s := newAlertRule.Annotations[ngmodels.PanelIDAnnotation]; s != "" {
|
|
panelID, err := strconv.ParseInt(s, 10, 64)
|
|
if err != nil {
|
|
return fmt.Errorf("the %s annotation does not contain a valid Panel ID: %w", ngmodels.PanelIDAnnotation, err)
|
|
}
|
|
newAlertRule.PanelID = &panelID
|
|
}
|
|
|
|
upsertRule := UpsertRule{
|
|
New: newAlertRule,
|
|
}
|
|
|
|
if existingGroupRule, ok := existingGroupRulesUIDs[r.GrafanaManagedAlert.UID]; ok {
|
|
upsertRule.Existing = &existingGroupRule
|
|
// remove the rule from existingGroupRulesUIDs
|
|
delete(existingGroupRulesUIDs, r.GrafanaManagedAlert.UID)
|
|
}
|
|
upsertRules = append(upsertRules, upsertRule)
|
|
}
|
|
|
|
if err := st.UpsertAlertRules(upsertRules); err != nil {
|
|
if st.SQLStore.Dialect.IsUniqueConstraintViolation(err) {
|
|
return ngmodels.ErrAlertRuleUniqueConstraintViolation
|
|
}
|
|
return err
|
|
}
|
|
|
|
// delete instances for rules that will not be removed
|
|
for _, rule := range existingGroupRules {
|
|
if _, ok := existingGroupRulesUIDs[rule.UID]; !ok {
|
|
if err := st.DeleteAlertInstancesByRuleUID(cmd.OrgID, rule.UID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
|
|
// delete the remaining rules
|
|
for ruleUID := range existingGroupRulesUIDs {
|
|
if err := st.DeleteAlertRuleByUID(cmd.OrgID, ruleUID); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func (st DBstore) GetOrgRuleGroups(query *ngmodels.ListOrgRuleGroupsQuery) error {
|
|
return st.SQLStore.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error {
|
|
var ruleGroups [][]string
|
|
q := `
|
|
SELECT DISTINCT
|
|
rule_group,
|
|
namespace_uid,
|
|
(
|
|
SELECT title
|
|
FROM dashboard
|
|
WHERE
|
|
org_id = alert_rule.org_id AND
|
|
uid = alert_rule.namespace_uid
|
|
) AS namespace_title
|
|
FROM alert_rule
|
|
WHERE org_id = ?`
|
|
params := []interface{}{query.OrgID}
|
|
|
|
if len(query.NamespaceUIDs) > 0 {
|
|
placeholders := make([]string, 0, len(query.NamespaceUIDs))
|
|
for _, folderUID := range query.NamespaceUIDs {
|
|
params = append(params, folderUID)
|
|
placeholders = append(placeholders, "?")
|
|
}
|
|
q = fmt.Sprintf(" %s AND namespace_uid IN (%s)", q, strings.Join(placeholders, ","))
|
|
}
|
|
|
|
if query.DashboardUID != "" {
|
|
q = fmt.Sprintf("%s and dashboard_uid = ?", q)
|
|
params = append(params, query.DashboardUID)
|
|
if query.PanelID != 0 {
|
|
q = fmt.Sprintf("%s and panel_id = ?", q)
|
|
params = append(params, query.PanelID)
|
|
}
|
|
}
|
|
|
|
q = fmt.Sprintf(" %s ORDER BY namespace_title", q)
|
|
|
|
if err := sess.SQL(q, params...).Find(&ruleGroups); err != nil {
|
|
return err
|
|
}
|
|
|
|
query.Result = ruleGroups
|
|
return nil
|
|
})
|
|
}
|