2021-04-01 11:11:45 +03:00
package store
import (
"context"
2022-06-02 14:48:53 +02:00
"errors"
2021-04-01 11:11:45 +03:00
"fmt"
2021-07-22 09:53:14 +03:00
"strings"
2021-04-01 11:11:45 +03:00
2022-10-19 09:02:15 -04:00
"github.com/grafana/grafana/pkg/infra/db"
2021-04-01 11:11:45 +03:00
"github.com/grafana/grafana/pkg/models"
2022-11-11 14:28:24 +01:00
"github.com/grafana/grafana/pkg/services/folder"
2022-04-01 19:33:26 -04:00
"github.com/grafana/grafana/pkg/services/guardian"
2021-04-01 11:11:45 +03:00
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2022-04-01 19:33:26 -04:00
"github.com/grafana/grafana/pkg/services/sqlstore/searchstore"
2022-08-10 11:56:48 +02:00
"github.com/grafana/grafana/pkg/services/user"
2021-04-01 11:11:45 +03:00
"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
2022-06-02 14:48:53 +02:00
var (
ErrAlertRuleGroupNotFound = errors . New ( "rulegroup not found" )
2022-06-13 12:15:28 -04:00
ErrOptimisticLock = errors . New ( "version conflict while updating a record in the database with optimistic locking" )
2022-06-02 14:48:53 +02:00
)
2022-10-19 09:02:15 -04:00
func getAlertRuleByUID ( sess * db . Session , alertRuleUID string , orgID int64 ) ( * ngmodels . AlertRule , error ) {
2021-04-01 11:11:45 +03:00
// 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
}
2022-03-23 16:09:53 -04:00
// DeleteAlertRulesByUID is a handler for deleting an alert rule.
func ( st DBstore ) DeleteAlertRulesByUID ( ctx context . Context , orgID int64 , ruleUID ... string ) error {
logger := st . Logger . New ( "org_id" , orgID , "rule_uids" , ruleUID )
2022-10-19 09:02:15 -04:00
return st . SQLStore . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2022-03-23 16:09:53 -04:00
rows , err := sess . Table ( "alert_rule" ) . Where ( "org_id = ?" , orgID ) . In ( "uid" , ruleUID ) . Delete ( ngmodels . AlertRule { } )
2021-04-01 11:11:45 +03:00
if err != nil {
return err
}
2022-03-23 16:09:53 -04:00
logger . Debug ( "deleted alert rules" , "count" , rows )
2021-04-01 11:11:45 +03:00
2022-03-23 16:09:53 -04:00
rows , err = sess . Table ( "alert_rule_version" ) . Where ( "rule_org_id = ?" , orgID ) . In ( "rule_uid" , ruleUID ) . Delete ( ngmodels . AlertRule { } )
2021-04-01 11:11:45 +03:00
if err != nil {
return err
}
2022-03-23 16:09:53 -04:00
logger . Debug ( "deleted alert rule versions" , "count" , rows )
2021-04-01 11:11:45 +03:00
2022-03-23 16:09:53 -04:00
rows , err = sess . Table ( "alert_instance" ) . Where ( "rule_org_id = ?" , orgID ) . In ( "rule_uid" , ruleUID ) . Delete ( ngmodels . AlertRule { } )
2021-04-01 11:11:45 +03:00
if err != nil {
return err
}
2022-03-23 16:09:53 -04:00
logger . Debug ( "deleted alert instances" , "count" , rows )
2021-04-01 11:11:45 +03:00
return nil
} )
}
2022-08-01 19:28:38 -04:00
// IncreaseVersionForAllRulesInNamespace Increases version for all rules that have specified namespace. Returns all rules that belong to the namespace
func ( st DBstore ) IncreaseVersionForAllRulesInNamespace ( ctx context . Context , orgID int64 , namespaceUID string ) ( [ ] ngmodels . AlertRuleKeyWithVersion , error ) {
var keys [ ] ngmodels . AlertRuleKeyWithVersion
2022-10-19 09:02:15 -04:00
err := st . SQLStore . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2022-08-01 19:28:38 -04:00
now := TimeNow ( )
_ , err := sess . Exec ( "UPDATE alert_rule SET version = version + 1, updated = ? WHERE namespace_uid = ? AND org_id = ?" , now , namespaceUID , orgID )
if err != nil {
return err
}
return sess . Table ( ngmodels . AlertRule { } ) . Where ( "namespace_uid = ? AND org_id = ?" , namespaceUID , orgID ) . Find ( & keys )
} )
return keys , err
}
2021-04-01 11:11:45 +03:00
// 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.
2022-02-08 08:52:03 +00:00
func ( st DBstore ) GetAlertRuleByUID ( ctx context . Context , query * ngmodels . GetAlertRuleByUIDQuery ) error {
2022-10-19 09:02:15 -04:00
return st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2021-04-01 11:11:45 +03:00
alertRule , err := getAlertRuleByUID ( sess , query . UID , query . OrgID )
if err != nil {
return err
}
query . Result = alertRule
return nil
} )
}
2022-06-01 10:23:54 -04:00
// GetAlertRulesGroupByRuleUID is a handler for retrieving a group of alert rules from that database by UID and organisation ID of one of rules that belong to that group.
func ( st DBstore ) GetAlertRulesGroupByRuleUID ( ctx context . Context , query * ngmodels . GetAlertRulesGroupByRuleUIDQuery ) error {
2022-10-19 09:02:15 -04:00
return st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-06-01 10:23:54 -04:00
var result [ ] * ngmodels . AlertRule
2022-10-07 10:18:49 +01:00
err := sess . Table ( "alert_rule" ) . Alias ( "a" ) . Join (
2022-06-01 10:23:54 -04:00
"INNER" ,
2022-10-07 10:18:49 +01:00
"alert_rule AS b" , "a.org_id = b.org_id AND a.namespace_uid = b.namespace_uid AND a.rule_group = b.rule_group AND b.uid = ?" , query . UID ,
) . Where ( "a.org_id = ?" , query . OrgID ) . Select ( "a.*" ) . Find ( & result )
2022-06-01 10:23:54 -04:00
if err != nil {
return err
}
query . Result = result
return nil
} )
}
2022-04-14 14:21:36 +02:00
// InsertAlertRules is a handler for creating/updating alert rules.
2022-06-02 14:48:53 +02:00
func ( st DBstore ) InsertAlertRules ( ctx context . Context , rules [ ] ngmodels . AlertRule ) ( map [ string ] int64 , error ) {
ids := make ( map [ string ] int64 , len ( rules ) )
2022-10-19 09:02:15 -04:00
return ids , st . SQLStore . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2021-04-01 11:11:45 +03:00
newRules := make ( [ ] ngmodels . AlertRule , 0 , len ( rules ) )
ruleVersions := make ( [ ] ngmodels . AlertRuleVersion , 0 , len ( rules ) )
2022-04-14 14:21:36 +02:00
for i := range rules {
r := rules [ i ]
2022-06-02 14:48:53 +02:00
if r . UID == "" {
uid , err := GenerateNewAlertRuleUID ( sess , r . OrgID , r . Title )
if err != nil {
return fmt . Errorf ( "failed to generate UID for alert rule %q: %w" , r . Title , err )
}
r . UID = uid
2022-04-14 14:21:36 +02:00
}
r . Version = 1
if err := st . validateAlertRule ( r ) ; err != nil {
return err
}
if err := ( & r ) . PreSave ( TimeNow ) ; err != nil {
return err
}
newRules = append ( newRules , r )
ruleVersions = append ( ruleVersions , ngmodels . AlertRuleVersion {
RuleUID : r . UID ,
2022-06-02 14:48:53 +02:00
RuleOrgID : r . OrgID ,
2022-04-14 14:21:36 +02:00
RuleNamespaceUID : r . NamespaceUID ,
RuleGroup : r . RuleGroup ,
ParentVersion : 0 ,
Version : r . Version ,
Created : r . Updated ,
Condition : r . Condition ,
Title : r . Title ,
Data : r . Data ,
IntervalSeconds : r . IntervalSeconds ,
NoDataState : r . NoDataState ,
ExecErrState : r . ExecErrState ,
For : r . For ,
Annotations : r . Annotations ,
Labels : r . Labels ,
} )
}
if len ( newRules ) > 0 {
2022-06-02 14:48:53 +02:00
// we have to insert the rules one by one as otherwise we are
// not able to fetch the inserted id as it's not supported by xorm
for i := range newRules {
if _ , err := sess . Insert ( & newRules [ i ] ) ; err != nil {
2022-10-14 15:33:06 -04:00
if st . SQLStore . GetDialect ( ) . IsUniqueConstraintViolation ( err ) {
2022-06-02 14:48:53 +02:00
return ngmodels . ErrAlertRuleUniqueConstraintViolation
}
return fmt . Errorf ( "failed to create new rules: %w" , err )
2021-04-01 11:11:45 +03:00
}
2022-06-02 14:48:53 +02:00
ids [ newRules [ i ] . UID ] = newRules [ i ] . ID
2022-04-14 14:21:36 +02:00
}
}
2021-04-01 11:11:45 +03:00
2022-04-14 14:21:36 +02:00
if len ( ruleVersions ) > 0 {
if _ , err := sess . Insert ( & ruleVersions ) ; err != nil {
return fmt . Errorf ( "failed to create new rule versions: %w" , err )
}
}
return nil
} )
}
2021-04-01 11:11:45 +03:00
2022-06-02 14:48:53 +02:00
// UpdateAlertRules is a handler for updating alert rules.
2022-09-29 15:47:56 -05:00
func ( st DBstore ) UpdateAlertRules ( ctx context . Context , rules [ ] ngmodels . UpdateRule ) error {
2022-10-19 09:02:15 -04:00
return st . SQLStore . WithTransactionalDbSession ( ctx , func ( sess * db . Session ) error {
2022-04-14 14:21:36 +02:00
ruleVersions := make ( [ ] ngmodels . AlertRuleVersion , 0 , len ( rules ) )
for _ , r := range rules {
var parentVersion int64
r . New . ID = r . Existing . ID
2022-06-13 12:15:28 -04:00
r . New . Version = r . Existing . Version // xorm will take care of increasing it (see https://xorm.io/docs/chapter-06/1.lock/)
2022-04-14 14:21:36 +02:00
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
2022-06-13 12:15:28 -04:00
if updated , err := sess . ID ( r . Existing . ID ) . AllCols ( ) . Update ( r . New ) ; err != nil || updated == 0 {
if err != nil {
2022-10-14 15:33:06 -04:00
if st . SQLStore . GetDialect ( ) . IsUniqueConstraintViolation ( err ) {
2022-06-13 12:15:28 -04:00
return ngmodels . ErrAlertRuleUniqueConstraintViolation
}
return fmt . Errorf ( "failed to update rule [%s] %s: %w" , r . New . UID , r . New . Title , err )
2021-04-01 11:11:45 +03:00
}
2022-06-13 12:15:28 -04:00
return fmt . Errorf ( "%w: alert rule UID %s version %d" , ErrOptimisticLock , r . New . UID , r . New . Version )
2021-04-01 11:11:45 +03:00
}
2022-04-14 14:21:36 +02:00
parentVersion = r . Existing . Version
2021-04-01 11:11:45 +03:00
ruleVersions = append ( ruleVersions , ngmodels . AlertRuleVersion {
RuleOrgID : r . New . OrgID ,
RuleUID : r . New . UID ,
RuleNamespaceUID : r . New . NamespaceUID ,
RuleGroup : r . New . RuleGroup ,
2022-06-22 10:52:46 -04:00
RuleGroupIndex : r . New . RuleGroupIndex ,
2021-04-01 11:11:45 +03:00
ParentVersion : parentVersion ,
2022-06-13 12:15:28 -04:00
Version : r . New . Version + 1 ,
2021-04-01 11:11:45 +03:00
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 ,
2021-04-09 10:50:04 -04:00
For : r . New . For ,
Annotations : r . New . Annotations ,
2021-04-15 15:54:37 +03:00
Labels : r . New . Labels ,
2021-04-01 11:11:45 +03:00
} )
}
if len ( ruleVersions ) > 0 {
if _ , err := sess . Insert ( & ruleVersions ) ; err != nil {
return fmt . Errorf ( "failed to create new rule versions: %w" , err )
}
}
return nil
} )
}
2022-11-08 05:51:00 -05:00
// CountAlertRulesInFolder is a handler for retrieving the number of alert rules of
// specific organisation associated with a given namespace (parent folder).
func ( st DBstore ) CountAlertRulesInFolder ( ctx context . Context , query * ngmodels . CountAlertRulesQuery ) ( int64 , error ) {
var count int64
var err error
err = st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
q := sess . Table ( "alert_rule" ) . Where ( "org_id = ?" , query . OrgID ) . Where ( "namespace_uid = ?" , query . NamespaceUID )
count , err = q . Count ( )
return err
} )
return count , err
}
2022-06-13 12:15:28 -04:00
// ListAlertRules is a handler for retrieving alert rules of specific organisation.
2022-04-25 11:42:42 +01:00
func ( st DBstore ) ListAlertRules ( ctx context . Context , query * ngmodels . ListAlertRulesQuery ) error {
2022-10-19 09:02:15 -04:00
return st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-04-25 11:42:42 +01:00
q := sess . Table ( "alert_rule" )
2021-07-22 09:53:14 +03:00
2022-04-25 11:42:42 +01:00
if query . OrgID >= 0 {
q = q . Where ( "org_id = ?" , query . OrgID )
2021-07-22 09:53:14 +03:00
}
2021-10-04 16:33:55 +01:00
if query . DashboardUID != "" {
2022-04-25 11:42:42 +01:00
q = q . Where ( "dashboard_uid = ?" , query . DashboardUID )
2021-10-04 16:33:55 +01:00
if query . PanelID != 0 {
2022-04-25 11:42:42 +01:00
q = q . Where ( "panel_id = ?" , query . PanelID )
}
}
if len ( query . NamespaceUIDs ) > 0 {
args := make ( [ ] interface { } , 0 , len ( query . NamespaceUIDs ) )
in := make ( [ ] string , 0 , len ( query . NamespaceUIDs ) )
for _ , namespaceUID := range query . NamespaceUIDs {
args = append ( args , namespaceUID )
in = append ( in , "?" )
2021-10-04 16:33:55 +01:00
}
2022-04-25 11:42:42 +01:00
q = q . Where ( fmt . Sprintf ( "namespace_uid IN (%s)" , strings . Join ( in , "," ) ) , args ... )
}
if query . RuleGroup != "" {
q = q . Where ( "rule_group = ?" , query . RuleGroup )
2021-10-04 16:33:55 +01:00
}
2022-06-22 10:52:46 -04:00
q = q . Asc ( "namespace_uid" , "rule_group" , "rule_group_idx" , "id" )
2021-10-04 16:33:55 +01:00
2022-04-25 11:42:42 +01:00
alertRules := make ( [ ] * ngmodels . AlertRule , 0 )
if err := q . Find ( & alertRules ) ; err != nil {
2021-04-01 11:11:45 +03:00
return err
}
query . Result = alertRules
return nil
} )
}
2022-06-02 14:48:53 +02:00
func ( st DBstore ) GetRuleGroupInterval ( ctx context . Context , orgID int64 , namespaceUID string , ruleGroup string ) ( int64 , error ) {
var interval int64 = 0
2022-10-19 09:02:15 -04:00
return interval , st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-06-02 14:48:53 +02:00
ruleGroups := make ( [ ] ngmodels . AlertRule , 0 )
err := sess . Find (
& ruleGroups ,
ngmodels . AlertRule { OrgID : orgID , RuleGroup : ruleGroup , NamespaceUID : namespaceUID } ,
)
if len ( ruleGroups ) == 0 {
return ErrAlertRuleGroupNotFound
}
interval = ruleGroups [ 0 ] . IntervalSeconds
return err
} )
}
2022-06-13 12:15:28 -04:00
// GetUserVisibleNamespaces returns the folders that are visible to the user and have at least one alert in it
2022-11-11 14:28:24 +01:00
func ( st DBstore ) GetUserVisibleNamespaces ( ctx context . Context , orgID int64 , user * user . SignedInUser ) ( map [ string ] * folder . Folder , error ) {
namespaceMap := make ( map [ string ] * folder . Folder )
2022-04-01 19:33:26 -04:00
searchQuery := models . FindPersistedDashboardsQuery {
OrgId : orgID ,
SignedInUser : user ,
Type : searchstore . TypeAlertFolder ,
Limit : - 1 ,
Permission : models . PERMISSION_VIEW ,
Sort : models . SortOption { } ,
Filters : [ ] interface { } {
searchstore . FolderWithAlertsFilter { } ,
} ,
}
2021-07-22 09:53:14 +03:00
var page int64 = 1
for {
2022-04-01 19:33:26 -04:00
query := searchQuery
query . Page = page
2022-05-24 09:24:55 -04:00
proj , err := st . DashboardService . FindDashboards ( ctx , & query )
2021-07-22 09:53:14 +03:00
if err != nil {
return nil , err
}
2022-04-01 19:33:26 -04:00
if len ( proj ) == 0 {
2021-07-22 09:53:14 +03:00
break
}
2022-04-01 19:33:26 -04:00
for _ , hit := range proj {
if ! hit . IsFolder {
continue
}
2022-11-11 14:28:24 +01:00
namespaceMap [ hit . UID ] = & folder . Folder {
ID : hit . ID ,
UID : hit . UID ,
2022-04-01 19:33:26 -04:00
Title : hit . Title ,
}
2021-07-22 09:53:14 +03:00
}
page += 1
}
return namespaceMap , nil
}
2021-04-15 15:54:37 +03:00
// GetNamespaceByTitle is a handler for retrieving a namespace by its title. Alerting rules follow a Grafana folder-like structure which we call namespaces.
2022-11-11 14:28:24 +01:00
func ( st DBstore ) GetNamespaceByTitle ( ctx context . Context , namespace string , orgID int64 , user * user . SignedInUser , withCanSave bool ) ( * folder . Folder , error ) {
folder , err := st . FolderService . Get ( ctx , & folder . GetFolderQuery { OrgID : orgID , Title : & namespace } )
2021-04-01 11:11:45 +03:00
if err != nil {
2021-04-15 15:54:37 +03:00
return nil , err
2021-04-01 11:11:45 +03:00
}
2021-04-15 15:54:37 +03:00
2022-04-01 19:33:26 -04:00
// if access control is disabled, check that the user is allowed to save in the folder.
if withCanSave && st . AccessControl . IsDisabled ( ) {
2022-11-11 14:28:24 +01:00
g := guardian . New ( ctx , folder . ID , orgID , user )
2021-05-20 15:49:33 +03:00
if canSave , err := g . CanSave ( ) ; err != nil || ! canSave {
if err != nil {
2022-10-19 16:36:54 -05:00
st . Logger . Error ( "checking can save permission has failed" , "userId" , user . UserID , "username" , user . Login , "namespace" , namespace , "orgId" , orgID , "error" , err )
2021-05-20 15:49:33 +03:00
}
2021-04-15 15:54:37 +03:00
return nil , ngmodels . ErrCannotEditNamespace
}
}
return folder , nil
2021-04-01 11:11:45 +03:00
}
2022-06-17 13:10:49 -04:00
// GetNamespaceByUID is a handler for retrieving a namespace by its UID. Alerting rules follow a Grafana folder-like structure which we call namespaces.
2022-11-11 14:28:24 +01:00
func ( st DBstore ) GetNamespaceByUID ( ctx context . Context , uid string , orgID int64 , user * user . SignedInUser ) ( * folder . Folder , error ) {
folder , err := st . FolderService . Get ( ctx , & folder . GetFolderQuery { OrgID : orgID , Title : & uid } )
2022-06-17 13:10:49 -04:00
if err != nil {
return nil , err
}
return folder , nil
}
2022-08-31 11:08:19 -04:00
func ( st DBstore ) getFilterByOrgsString ( ) ( string , [ ] interface { } ) {
if len ( st . Cfg . DisabledOrgs ) == 0 {
return "" , nil
}
builder := strings . Builder { }
builder . WriteString ( "org_id NOT IN(" )
idx := len ( st . Cfg . DisabledOrgs )
args := make ( [ ] interface { } , 0 , len ( st . Cfg . DisabledOrgs ) )
for orgId := range st . Cfg . DisabledOrgs {
args = append ( args , orgId )
builder . WriteString ( "?" )
idx --
if idx == 0 {
builder . WriteString ( ")" )
break
}
builder . WriteString ( "," )
}
return builder . String ( ) , args
}
func ( st DBstore ) GetAlertRulesKeysForScheduling ( ctx context . Context ) ( [ ] ngmodels . AlertRuleKeyWithVersion , error ) {
var result [ ] ngmodels . AlertRuleKeyWithVersion
2022-10-19 09:02:15 -04:00
err := st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-08-31 11:08:19 -04:00
alertRulesSql := "SELECT org_id, uid, version FROM alert_rule"
filter , args := st . getFilterByOrgsString ( )
if filter != "" {
alertRulesSql += " WHERE " + filter
}
if err := sess . SQL ( alertRulesSql , args ... ) . Find ( & result ) ; err != nil {
return err
}
return nil
} )
return result , err
}
2022-05-12 09:55:05 -04:00
// GetAlertRulesForScheduling returns a short version of all alert rules except those that belong to an excluded list of organizations
func ( st DBstore ) GetAlertRulesForScheduling ( ctx context . Context , query * ngmodels . GetAlertRulesForSchedulingQuery ) error {
2022-08-31 11:08:19 -04:00
var folders [ ] struct {
Uid string
Title string
}
var rules [ ] * ngmodels . AlertRule
2022-10-19 09:02:15 -04:00
return st . SQLStore . WithDbSession ( ctx , func ( sess * db . Session ) error {
2022-09-06 08:28:42 +01:00
foldersSql := "SELECT D.uid, D.title FROM dashboard AS D WHERE is_folder IS TRUE AND EXISTS (SELECT 1 FROM alert_rule AS A WHERE D.uid = A.namespace_uid)"
2022-08-31 11:08:19 -04:00
alertRulesSql := "SELECT * FROM alert_rule"
filter , args := st . getFilterByOrgsString ( )
if filter != "" {
foldersSql += " AND " + filter
alertRulesSql += " WHERE " + filter
2021-09-29 17:16:40 +03:00
}
2022-08-31 11:08:19 -04:00
if err := sess . SQL ( alertRulesSql , args ... ) . Find ( & rules ) ; err != nil {
return fmt . Errorf ( "failed to fetch alert rules: %w" , err )
}
query . ResultRules = rules
if query . PopulateFolders {
if err := sess . SQL ( foldersSql , args ... ) . Find ( & folders ) ; err != nil {
return fmt . Errorf ( "failed to fetch a list of folders that contain alert rules: %w" , err )
}
query . ResultFoldersTitles = make ( map [ string ] string , len ( folders ) )
for _ , folder := range folders {
query . ResultFoldersTitles [ folder . Uid ] = folder . Title
}
2021-04-01 11:11:45 +03:00
}
return nil
} )
}
2021-05-13 22:58:19 +05:30
// 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.
2022-10-19 09:02:15 -04:00
var GenerateNewAlertRuleUID = func ( sess * db . Session , orgID int64 , ruleTitle string ) ( string , error ) {
2021-04-01 11:11:45 +03:00
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
}
2021-04-21 17:22:58 +03:00
// 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 )
2021-04-01 11:11:45 +03:00
}
if alertRule . Title == "" {
2021-04-21 17:22:58 +03:00
return fmt . Errorf ( "%w: title is empty" , ngmodels . ErrAlertRuleFailedValidation )
2021-04-01 11:11:45 +03:00
}
2022-07-15 14:13:30 -04:00
if err := ngmodels . ValidateRuleGroupInterval ( alertRule . IntervalSeconds , int64 ( st . Cfg . BaseInterval . Seconds ( ) ) ) ; err != nil {
2022-06-09 09:28:32 +02:00
return err
2021-04-01 11:11:45 +03:00
}
// enfore max name length in SQLite
if len ( alertRule . Title ) > AlertRuleMaxTitleLength {
2021-04-21 17:22:58 +03:00
return fmt . Errorf ( "%w: name length should not be greater than %d" , ngmodels . ErrAlertRuleFailedValidation , AlertRuleMaxTitleLength )
2021-04-01 11:11:45 +03:00
}
2021-04-21 17:22:58 +03:00
// enfore max rule group name length in SQLite
2021-04-01 11:11:45 +03:00
if len ( alertRule . RuleGroup ) > AlertRuleMaxRuleGroupNameLength {
2021-04-21 17:22:58 +03:00
return fmt . Errorf ( "%w: rule group name length should not be greater than %d" , ngmodels . ErrAlertRuleFailedValidation , AlertRuleMaxRuleGroupNameLength )
2021-04-01 11:11:45 +03:00
}
if alertRule . OrgID == 0 {
2021-04-21 17:22:58 +03:00
return fmt . Errorf ( "%w: no organisation is found" , ngmodels . ErrAlertRuleFailedValidation )
2021-04-01 11:11:45 +03:00
}
2021-10-06 11:34:11 +01:00
if alertRule . DashboardUID == nil && alertRule . PanelID != nil {
return fmt . Errorf ( "%w: cannot have Panel ID without a Dashboard UID" , ngmodels . ErrAlertRuleFailedValidation )
}
2022-06-02 14:48:53 +02:00
if _ , err := ngmodels . ErrStateFromString ( string ( alertRule . ExecErrState ) ) ; err != nil {
return err
}
if _ , err := ngmodels . NoDataStateFromString ( string ( alertRule . NoDataState ) ) ; err != nil {
return err
}
2022-06-30 11:46:26 -04:00
if alertRule . For < 0 {
return fmt . Errorf ( "%w: field `for` cannot be negative" , ngmodels . ErrAlertRuleFailedValidation )
}
2021-04-01 11:11:45 +03:00
return nil
}