2021-04-01 11:11:45 +03:00
package api
import (
2022-02-23 11:30:04 -05:00
"context"
2021-04-15 15:54:37 +03:00
"errors"
2021-09-02 19:38:42 +03:00
"fmt"
2021-04-01 11:11:45 +03:00
"net/http"
2022-06-15 16:01:14 -04:00
"strings"
2021-04-01 11:11:45 +03:00
"time"
2022-03-21 19:20:35 -04:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2022-06-30 09:31:54 -04:00
"github.com/grafana/grafana/pkg/services/dashboards"
2022-01-11 11:39:34 -05:00
"github.com/grafana/grafana/pkg/services/datasources"
2022-04-05 16:48:51 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
2022-01-11 11:39:34 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/quota"
2022-02-23 11:30:04 -05:00
"github.com/grafana/grafana/pkg/setting"
2022-01-11 11:39:34 -05:00
"github.com/prometheus/common/model"
2021-08-25 15:11:22 +02:00
"github.com/grafana/grafana/pkg/api/apierrors"
2021-04-01 11:11:45 +03:00
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
2021-04-19 14:26:04 -04:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
2021-04-01 11:11:45 +03:00
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2022-01-11 11:39:34 -05:00
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
2021-04-01 11:11:45 +03:00
"github.com/grafana/grafana/pkg/util"
)
type RulerSrv struct {
2022-04-05 16:48:51 -05:00
xactManager provisioning . TransactionManager
2022-04-28 21:27:34 +02:00
provenanceStore provisioning . ProvisioningStore
2021-04-28 11:31:51 +03:00
store store . RuleStore
DatasourceCache datasources . CacheService
2022-07-15 18:06:44 +02:00
QuotaService quota . Service
2022-01-11 11:39:34 -05:00
scheduleService schedule . ScheduleService
2021-04-28 11:31:51 +03:00
log log . Logger
2022-02-23 11:30:04 -05:00
cfg * setting . UnifiedAlertingSettings
2022-03-21 19:20:35 -04:00
ac accesscontrol . AccessControl
2021-04-01 11:11:45 +03:00
}
2022-02-23 11:30:04 -05:00
var (
2022-06-15 16:01:14 -04:00
errProvisionedResource = errors . New ( "request affects resources created via provisioning API" )
2022-02-23 11:30:04 -05:00
)
2022-06-23 15:13:39 -05:00
// RouteDeleteAlertRules deletes all alert rules user is authorized to access in the given namespace
// or, if non-empty, a specific group of rules in the namespace
func ( srv RulerSrv ) RouteDeleteAlertRules ( c * models . ReqContext , namespaceTitle string , group string ) response . Response {
2022-08-11 13:28:55 +02:00
namespace , err := srv . store . GetNamespaceByTitle ( c . Req . Context (), namespaceTitle , c . SignedInUser . OrgID , c . SignedInUser , true )
2021-04-01 11:11:45 +03:00
if err != nil {
2021-04-15 15:54:37 +03:00
return toNamespaceErrorResponse ( err )
2021-04-01 11:11:45 +03:00
}
2022-03-25 12:39:24 -04:00
var loggerCtx = [] interface {}{
"namespace" ,
namespace . Title ,
}
2022-04-25 11:42:42 +01:00
var ruleGroup string
2022-06-23 15:13:39 -05:00
if group != "" {
2022-04-25 11:42:42 +01:00
ruleGroup = group
2022-03-25 12:39:24 -04:00
loggerCtx = append ( loggerCtx , "group" , group )
2021-04-01 11:11:45 +03:00
}
2022-03-25 12:39:24 -04:00
logger := srv . log . New ( loggerCtx ... )
2021-05-03 14:01:33 -04:00
2022-03-25 12:39:24 -04:00
hasAccess := func ( evaluator accesscontrol . Evaluator ) bool {
return accesscontrol . HasAccess ( srv . ac , c )( accesscontrol . ReqOrgAdminOrEditor , evaluator )
2021-05-03 14:01:33 -04:00
}
2022-08-11 13:28:55 +02:00
provenances , err := srv . provenanceStore . GetProvenances ( c . Req . Context (), c . SignedInUser . OrgID , ( & ngmodels . AlertRule {}). ResourceType ())
2022-05-06 20:55:27 +02:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to fetch provenances of alert rules" )
}
var deletableRules [] string
2022-03-25 12:39:24 -04:00
err = srv . xactManager . InTransaction ( c . Req . Context (), func ( ctx context . Context ) error {
2022-04-25 11:42:42 +01:00
q := ngmodels . ListAlertRulesQuery {
2022-08-11 13:28:55 +02:00
OrgID : c . SignedInUser . OrgID ,
2022-04-25 11:42:42 +01:00
NamespaceUIDs : [] string { namespace . Uid },
RuleGroup : ruleGroup ,
2022-03-25 12:39:24 -04:00
}
2022-04-25 11:42:42 +01:00
if err = srv . store . ListAlertRules ( ctx , & q ); err != nil {
2022-03-25 12:39:24 -04:00
return err
}
2021-04-01 11:11:45 +03:00
2022-03-25 12:39:24 -04:00
if len ( q . Result ) == 0 {
logger . Debug ( "no alert rules to delete from namespace/group" )
return nil
}
2022-05-06 20:55:27 +02:00
var canDelete [] * ngmodels . AlertRule
var cannotDelete [] string
// partition will partation the given rules in two, one partition
// being the rules that fulfill the predicate the other partation being
// the ruleIDs not fulfilling it.
partition := func ( alerts [] * ngmodels . AlertRule , predicate func ( rule * ngmodels . AlertRule ) bool ) ([] * ngmodels . AlertRule , [] string ) {
positive , negative := make ([] * ngmodels . AlertRule , 0 , len ( alerts )), make ([] string , 0 , len ( alerts ))
for _ , rule := range alerts {
if predicate ( rule ) {
positive = append ( positive , rule )
continue
}
negative = append ( negative , rule . UID )
2022-03-25 12:39:24 -04:00
}
2022-05-06 20:55:27 +02:00
return positive , negative
2022-03-25 12:39:24 -04:00
}
2022-05-06 20:55:27 +02:00
canDelete , cannotDelete = partition ( q . Result , func ( rule * ngmodels . AlertRule ) bool {
return authorizeDatasourceAccessForRule ( rule , hasAccess )
})
2022-03-25 12:39:24 -04:00
if len ( canDelete ) == 0 {
return fmt . Errorf ( "%w to delete rules because user is not authorized to access data sources used by the rules" , ErrAuthorization )
}
if len ( cannotDelete ) > 0 {
logger . Info ( "user cannot delete one or many alert rules because it does not have access to data sources. Those rules will be skipped" , "expected" , len ( q . Result ), "authorized" , len ( canDelete ), "unauthorized" , cannotDelete )
}
2022-05-06 20:55:27 +02:00
canDelete , cannotDelete = partition ( canDelete , func ( rule * ngmodels . AlertRule ) bool {
provenance , exists := provenances [ rule . UID ]
return ( exists && provenance == ngmodels . ProvenanceNone ) || ! exists
})
if len ( canDelete ) == 0 {
return fmt . Errorf ( "all rules have been provisioned and cannot be deleted through this api" )
}
if len ( cannotDelete ) > 0 {
logger . Info ( "user cannot delete one or many alert rules because it does have a provenance set. Those rules will be skipped" , "expected" , len ( q . Result ), "provenance_none" , len ( canDelete ), "provenance_set" , cannotDelete )
}
for _ , rule := range canDelete {
deletableRules = append ( deletableRules , rule . UID )
}
2022-08-11 13:28:55 +02:00
return srv . store . DeleteAlertRulesByUID ( ctx , c . SignedInUser . OrgID , deletableRules ... )
2022-03-25 12:39:24 -04:00
})
2021-05-03 14:01:33 -04:00
if err != nil {
2022-03-25 12:39:24 -04:00
if errors . Is ( err , ErrAuthorization ) {
return ErrResp ( http . StatusUnauthorized , err , "" )
2021-04-16 13:00:07 +01:00
}
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusInternalServerError , err , "failed to delete rule group" )
2021-04-01 11:11:45 +03:00
}
2021-04-16 13:00:07 +01:00
2022-03-25 12:39:24 -04:00
logger . Debug ( "rules have been deleted from the store. updating scheduler" )
2022-05-06 20:55:27 +02:00
for _ , uid := range deletableRules {
2022-01-11 11:39:34 -05:00
srv . scheduleService . DeleteAlertRule ( ngmodels . AlertRuleKey {
2022-08-11 13:28:55 +02:00
OrgID : c . SignedInUser . OrgID ,
2022-01-11 11:39:34 -05:00
UID : uid ,
})
2021-05-03 14:01:33 -04:00
}
2022-03-25 12:39:24 -04:00
return response . JSON ( http . StatusAccepted , util . DynMap { "message" : "rules deleted" })
2021-04-01 11:11:45 +03:00
}
2022-06-17 13:55:31 -04:00
// RouteGetNamespaceRulesConfig returns all rules in a specific folder that user has access to
2022-06-23 15:13:39 -05:00
func ( srv RulerSrv ) RouteGetNamespaceRulesConfig ( c * models . ReqContext , namespaceTitle string ) response . Response {
2022-08-11 13:28:55 +02:00
namespace , err := srv . store . GetNamespaceByTitle ( c . Req . Context (), namespaceTitle , c . SignedInUser . OrgID , c . SignedInUser , false )
2021-04-01 11:11:45 +03:00
if err != nil {
2021-04-15 15:54:37 +03:00
return toNamespaceErrorResponse ( err )
2021-04-01 11:11:45 +03:00
}
2022-04-25 11:42:42 +01:00
q := ngmodels . ListAlertRulesQuery {
2022-08-11 13:28:55 +02:00
OrgID : c . SignedInUser . OrgID ,
2022-04-25 11:42:42 +01:00
NamespaceUIDs : [] string { namespace . Uid },
2021-04-01 11:11:45 +03:00
}
2022-04-25 11:42:42 +01:00
if err := srv . store . ListAlertRules ( c . Req . Context (), & q ); err != nil {
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusInternalServerError , err , "failed to update rule group" )
2021-04-01 11:11:45 +03:00
}
result := apimodels . NamespaceConfigResponse {}
2022-04-11 17:37:44 -04:00
hasAccess := func ( evaluator accesscontrol . Evaluator ) bool {
2022-05-19 09:22:26 -04:00
return accesscontrol . HasAccess ( srv . ac , c )( accesscontrol . ReqViewer , evaluator )
2022-04-11 17:37:44 -04:00
}
2022-08-11 13:28:55 +02:00
provenanceRecords , err := srv . provenanceStore . GetProvenances ( c . Req . Context (), c . SignedInUser . OrgID , ( & ngmodels . AlertRule {}). ResourceType ())
2022-04-28 21:27:34 +02:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to get provenance for rule group" )
}
2022-06-22 10:52:46 -04:00
ruleGroups := make ( map [ string ] ngmodels . RulesGroup )
2021-04-01 11:11:45 +03:00
for _ , r := range q . Result {
2022-06-17 13:55:31 -04:00
ruleGroups [ r . RuleGroup ] = append ( ruleGroups [ r . RuleGroup ], r )
2021-04-01 11:11:45 +03:00
}
2022-06-17 13:55:31 -04:00
for groupName , rules := range ruleGroups {
if ! authorizeAccessToRuleGroup ( rules , hasAccess ) {
continue
}
result [ namespaceTitle ] = append ( result [ namespaceTitle ], toGettableRuleGroupConfig ( groupName , rules , namespace . Id , provenanceRecords ))
2021-04-01 11:11:45 +03:00
}
return response . JSON ( http . StatusAccepted , result )
}
2022-06-17 13:55:31 -04:00
// RouteGetRulesGroupConfig returns rules that belong to a specific group in a specific namespace (folder).
// If user does not have access to at least one of the rule in the group, returns status 401 Unauthorized
2022-06-23 15:13:39 -05:00
func ( srv RulerSrv ) RouteGetRulesGroupConfig ( c * models . ReqContext , namespaceTitle string , ruleGroup string ) response . Response {
2022-08-11 13:28:55 +02:00
namespace , err := srv . store . GetNamespaceByTitle ( c . Req . Context (), namespaceTitle , c . SignedInUser . OrgID , c . SignedInUser , false )
2021-04-01 11:11:45 +03:00
if err != nil {
2021-04-15 15:54:37 +03:00
return toNamespaceErrorResponse ( err )
2021-04-01 11:11:45 +03:00
}
2022-04-25 11:42:42 +01:00
q := ngmodels . ListAlertRulesQuery {
2022-08-11 13:28:55 +02:00
OrgID : c . SignedInUser . OrgID ,
2022-04-25 11:42:42 +01:00
NamespaceUIDs : [] string { namespace . Uid },
RuleGroup : ruleGroup ,
2021-04-01 11:11:45 +03:00
}
2022-04-25 11:42:42 +01:00
if err := srv . store . ListAlertRules ( c . Req . Context (), & q ); err != nil {
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusInternalServerError , err , "failed to get group alert rules" )
2021-04-01 11:11:45 +03:00
}
2022-04-11 17:37:44 -04:00
hasAccess := func ( evaluator accesscontrol . Evaluator ) bool {
2022-05-19 09:22:26 -04:00
return accesscontrol . HasAccess ( srv . ac , c )( accesscontrol . ReqViewer , evaluator )
2022-04-11 17:37:44 -04:00
}
2022-08-11 13:28:55 +02:00
provenanceRecords , err := srv . provenanceStore . GetProvenances ( c . Req . Context (), c . SignedInUser . OrgID , ( & ngmodels . AlertRule {}). ResourceType ())
2022-04-28 21:27:34 +02:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to get group alert rules" )
}
2022-06-17 13:55:31 -04:00
if ! authorizeAccessToRuleGroup ( q . Result , hasAccess ) {
return ErrResp ( http . StatusUnauthorized , fmt . Errorf ( "%w to access the group because it does not have access to one or many data sources one or many rules in the group use" , ErrAuthorization ), "" )
2021-04-01 11:11:45 +03:00
}
result := apimodels . RuleGroupConfigResponse {
2022-06-17 13:55:31 -04:00
GettableRuleGroupConfig : toGettableRuleGroupConfig ( ruleGroup , q . Result , namespace . Id , provenanceRecords ),
2021-04-01 11:11:45 +03:00
}
return response . JSON ( http . StatusAccepted , result )
}
2022-06-17 13:55:31 -04:00
// RouteGetRulesConfig returns all alert rules that are available to the current user
2021-04-01 11:11:45 +03:00
func ( srv RulerSrv ) RouteGetRulesConfig ( c * models . ReqContext ) response . Response {
2022-08-11 13:28:55 +02:00
namespaceMap , err := srv . store . GetUserVisibleNamespaces ( c . Req . Context (), c . OrgID , c . SignedInUser )
2021-07-22 09:53:14 +03:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to get namespaces visible to the user" )
}
2021-11-08 13:26:08 +00:00
result := apimodels . NamespaceConfigResponse {}
if len ( namespaceMap ) == 0 {
2022-06-07 18:54:23 +01:00
srv . log . Debug ( "user has no access to any namespaces" )
2021-11-08 13:26:08 +00:00
return response . JSON ( http . StatusOK , result )
}
2021-07-22 09:53:14 +03:00
namespaceUIDs := make ([] string , len ( namespaceMap ))
for k := range namespaceMap {
namespaceUIDs = append ( namespaceUIDs , k )
}
2021-10-04 16:33:55 +01:00
dashboardUID := c . Query ( "dashboard_uid" )
panelID , err := getPanelIDFromRequest ( c . Req )
if err != nil {
return ErrResp ( http . StatusBadRequest , err , "invalid panel_id" )
}
if dashboardUID == "" && panelID != 0 {
return ErrResp ( http . StatusBadRequest , errors . New ( "panel_id must be set with dashboard_uid" ), "" )
}
2021-04-01 11:11:45 +03:00
q := ngmodels . ListAlertRulesQuery {
2022-08-11 13:28:55 +02:00
OrgID : c . SignedInUser . OrgID ,
2021-07-22 09:53:14 +03:00
NamespaceUIDs : namespaceUIDs ,
2021-10-04 16:33:55 +01:00
DashboardUID : dashboardUID ,
PanelID : panelID ,
2021-04-01 11:11:45 +03:00
}
2021-07-22 09:53:14 +03:00
2022-04-25 11:42:42 +01:00
if err := srv . store . ListAlertRules ( c . Req . Context (), & q ); err != nil {
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusInternalServerError , err , "failed to get alert rules" )
2021-04-01 11:11:45 +03:00
}
2022-04-11 17:37:44 -04:00
hasAccess := func ( evaluator accesscontrol . Evaluator ) bool {
2022-05-19 09:22:26 -04:00
return accesscontrol . HasAccess ( srv . ac , c )( accesscontrol . ReqViewer , evaluator )
2022-04-11 17:37:44 -04:00
}
2022-08-11 13:28:55 +02:00
provenanceRecords , err := srv . provenanceStore . GetProvenances ( c . Req . Context (), c . SignedInUser . OrgID , ( & ngmodels . AlertRule {}). ResourceType ())
2022-04-28 21:27:34 +02:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to get alert rules" )
}
2022-06-22 10:52:46 -04:00
configs := make ( map [ ngmodels . AlertRuleGroupKey ] ngmodels . RulesGroup )
2021-04-01 11:11:45 +03:00
for _ , r := range q . Result {
2022-05-16 15:45:45 -04:00
groupKey := r . GetGroupKey ()
group := configs [ groupKey ]
2022-05-12 10:42:31 -04:00
group = append ( group , r )
2022-05-16 15:45:45 -04:00
configs [ groupKey ] = group
2021-04-01 11:11:45 +03:00
}
2022-05-16 15:45:45 -04:00
for groupKey , rules := range configs {
folder , ok := namespaceMap [ groupKey . NamespaceUID ]
2022-05-12 10:42:31 -04:00
if ! ok {
2022-08-11 13:28:55 +02:00
srv . log . Error ( "namespace not visible to the user" , "user" , c . SignedInUser . UserID , "namespace" , groupKey . NamespaceUID )
2022-05-12 10:42:31 -04:00
continue
}
2022-06-01 10:23:54 -04:00
if ! authorizeAccessToRuleGroup ( rules , hasAccess ) {
continue
}
2022-05-12 10:42:31 -04:00
namespace := folder . Title
2022-05-16 15:45:45 -04:00
result [ namespace ] = append ( result [ namespace ], toGettableRuleGroupConfig ( groupKey . RuleGroup , rules , folder . Id , provenanceRecords ))
2021-04-01 11:11:45 +03:00
}
2021-10-04 16:33:55 +01:00
return response . JSON ( http . StatusOK , result )
2021-04-01 11:11:45 +03:00
}
2022-06-23 15:13:39 -05:00
func ( srv RulerSrv ) RoutePostNameRulesConfig ( c * models . ReqContext , ruleGroupConfig apimodels . PostableRuleGroupConfig , namespaceTitle string ) response . Response {
2022-08-11 13:28:55 +02:00
namespace , err := srv . store . GetNamespaceByTitle ( c . Req . Context (), namespaceTitle , c . SignedInUser . OrgID , c . SignedInUser , true )
2021-04-01 11:11:45 +03:00
if err != nil {
2021-04-15 15:54:37 +03:00
return toNamespaceErrorResponse ( err )
2021-04-01 11:11:45 +03:00
}
2022-08-11 13:28:55 +02:00
rules , err := validateRuleGroup ( & ruleGroupConfig , c . SignedInUser . OrgID , namespace , conditionValidator ( c , srv . DatasourceCache ), srv . cfg )
2022-02-23 11:30:04 -05:00
if err != nil {
return ErrResp ( http . StatusBadRequest , err , "" )
2021-04-16 13:00:07 +01:00
}
2022-05-16 15:45:45 -04:00
groupKey := ngmodels . AlertRuleGroupKey {
2022-08-11 13:28:55 +02:00
OrgID : c . SignedInUser . OrgID ,
2022-05-16 15:45:45 -04:00
NamespaceUID : namespace . Uid ,
RuleGroup : ruleGroupConfig . Name ,
}
return srv . updateAlertRulesInGroup ( c , groupKey , rules )
2022-02-23 11:30:04 -05:00
}
2022-03-24 16:53:00 -04:00
// updateAlertRulesInGroup calculates changes (rules to add,update,delete), verifies that the user is authorized to do the calculated changes and updates database.
// All operations are performed in a single transaction
2022-05-16 15:45:45 -04:00
func ( srv RulerSrv ) updateAlertRulesInGroup ( c * models . ReqContext , groupKey ngmodels . AlertRuleGroupKey , rules [] * ngmodels . AlertRule ) response . Response {
2022-08-01 23:41:23 -05:00
var finalChanges * store . GroupDelta
2022-03-21 19:20:35 -04:00
hasAccess := accesscontrol . HasAccess ( srv . ac , c )
2022-03-15 11:48:42 -05:00
err := srv . xactManager . InTransaction ( c . Req . Context (), func ( tranCtx context . Context ) error {
2022-08-11 13:28:55 +02:00
logger := srv . log . New ( "namespace_uid" , groupKey . NamespaceUID , "group" , groupKey . RuleGroup , "org_id" , groupKey . OrgID , "user_id" , c . UserID )
2022-08-01 23:41:23 -05:00
groupChanges , err := store . CalculateChanges ( tranCtx , srv . store , groupKey , rules )
2022-02-23 11:30:04 -05:00
if err != nil {
return err
2021-04-28 11:31:51 +03:00
}
2022-02-23 11:30:04 -05:00
2022-08-01 23:41:23 -05:00
if groupChanges . IsEmpty () {
2022-05-06 20:55:27 +02:00
finalChanges = groupChanges
2022-03-24 16:53:00 -04:00
logger . Info ( "no changes detected in the request. Do nothing" )
2022-03-04 16:16:33 -05:00
return nil
2021-09-02 19:38:42 +03:00
}
2022-02-23 11:30:04 -05:00
2022-06-01 10:23:54 -04:00
// if RBAC is disabled the permission are limited to folder access that is done upstream
2022-05-23 09:58:20 -04:00
if ! srv . ac . IsDisabled () {
2022-06-01 10:23:54 -04:00
err = authorizeRuleChanges ( groupChanges , func ( evaluator accesscontrol . Evaluator ) bool {
2022-05-23 09:58:20 -04:00
return hasAccess ( accesscontrol . ReqOrgAdminOrEditor , evaluator )
})
if err != nil {
return err
}
2022-03-24 16:53:00 -04:00
}
2022-08-11 13:28:55 +02:00
if err := verifyProvisionedRulesNotAffected ( c . Req . Context (), srv . provenanceStore , c . OrgID , groupChanges ); err != nil {
2022-05-06 20:55:27 +02:00
return err
}
2022-08-01 23:41:23 -05:00
finalChanges = store . UpdateCalculatedRuleFields ( groupChanges )
2022-05-06 20:55:27 +02:00
logger . Debug ( "updating database with the authorized changes" , "add" , len ( finalChanges . New ), "update" , len ( finalChanges . New ), "delete" , len ( finalChanges . Delete ))
2022-03-21 19:20:35 -04:00
2022-05-06 20:55:27 +02:00
if len ( finalChanges . Update ) > 0 || len ( finalChanges . New ) > 0 {
updates := make ([] store . UpdateRule , 0 , len ( finalChanges . Update ))
inserts := make ([] ngmodels . AlertRule , 0 , len ( finalChanges . New ))
for _ , update := range finalChanges . Update {
2022-03-24 16:53:00 -04:00
logger . Debug ( "updating rule" , "rule_uid" , update . New . UID , "diff" , update . Diff . String ())
2022-04-14 14:21:36 +02:00
updates = append ( updates , store . UpdateRule {
2022-03-04 16:16:33 -05:00
Existing : update . Existing ,
New : * update . New ,
})
}
2022-05-06 20:55:27 +02:00
for _ , rule := range finalChanges . New {
2022-04-14 14:21:36 +02:00
inserts = append ( inserts , * rule )
}
2022-06-02 14:48:53 +02:00
_ , err = srv . store . InsertAlertRules ( tranCtx , inserts )
2022-04-14 14:21:36 +02:00
if err != nil {
return fmt . Errorf ( "failed to add rules: %w" , err )
2022-03-04 16:16:33 -05:00
}
2022-04-14 14:21:36 +02:00
err = srv . store . UpdateAlertRules ( tranCtx , updates )
2022-03-04 16:16:33 -05:00
if err != nil {
2022-04-14 14:21:36 +02:00
return fmt . Errorf ( "failed to update rules: %w" , err )
2022-03-04 16:16:33 -05:00
}
}
2022-05-06 20:55:27 +02:00
if len ( finalChanges . Delete ) > 0 {
UIDs := make ([] string , 0 , len ( finalChanges . Delete ))
for _ , rule := range finalChanges . Delete {
2022-03-23 16:09:53 -04:00
UIDs = append ( UIDs , rule . UID )
}
2022-08-11 13:28:55 +02:00
if err = srv . store . DeleteAlertRulesByUID ( tranCtx , c . SignedInUser . OrgID , UIDs ... ); err != nil {
2022-03-23 16:09:53 -04:00
return fmt . Errorf ( "failed to delete rules: %w" , err )
2021-09-02 19:38:42 +03:00
}
}
2022-05-06 20:55:27 +02:00
if len ( finalChanges . New ) > 0 {
2022-02-23 11:30:04 -05:00
limitReached , err := srv . QuotaService . CheckQuotaReached ( tranCtx , "alert_rule" , & quota . ScopeParameters {
2022-08-11 13:28:55 +02:00
OrgID : c . OrgID ,
UserID : c . UserID ,
2022-02-23 11:30:04 -05:00
}) // alert rule is table name
if err != nil {
return fmt . Errorf ( "failed to get alert rules quota: %w" , err )
}
if limitReached {
2022-07-13 17:36:17 -05:00
return ngmodels . ErrQuotaReached
2022-02-23 11:30:04 -05:00
}
2021-04-28 11:31:51 +03:00
}
2022-02-23 11:30:04 -05:00
return nil
})
2021-04-28 11:31:51 +03:00
2022-02-23 11:30:04 -05:00
if err != nil {
2021-04-15 15:54:37 +03:00
if errors . Is ( err , ngmodels . ErrAlertRuleNotFound ) {
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusNotFound , err , "failed to update rule group" )
2022-06-15 16:01:14 -04:00
} else if errors . Is ( err , ngmodels . ErrAlertRuleFailedValidation ) || errors . Is ( err , errProvisionedResource ) {
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusBadRequest , err , "failed to update rule group" )
2022-07-13 17:36:17 -05:00
} else if errors . Is ( err , ngmodels . ErrQuotaReached ) {
2022-02-23 11:30:04 -05:00
return ErrResp ( http . StatusForbidden , err , "" )
2022-03-21 19:20:35 -04:00
} else if errors . Is ( err , ErrAuthorization ) {
return ErrResp ( http . StatusUnauthorized , err , "" )
2022-06-13 12:15:28 -04:00
} else if errors . Is ( err , store . ErrOptimisticLock ) {
return ErrResp ( http . StatusConflict , err , "" )
2021-04-15 15:54:37 +03:00
}
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusInternalServerError , err , "failed to update rule group" )
2021-04-01 11:11:45 +03:00
}
2022-05-06 20:55:27 +02:00
for _ , rule := range finalChanges . Update {
2022-03-04 16:16:33 -05:00
srv . scheduleService . UpdateAlertRule ( ngmodels . AlertRuleKey {
2022-08-11 13:28:55 +02:00
OrgID : c . SignedInUser . OrgID ,
2022-03-04 16:16:33 -05:00
UID : rule . Existing . UID ,
2022-07-15 12:32:52 -04:00
}, rule . Existing . Version + 1 )
2022-03-04 16:16:33 -05:00
}
2022-02-23 11:30:04 -05:00
2022-05-06 20:55:27 +02:00
for _ , rule := range finalChanges . Delete {
2022-02-23 11:30:04 -05:00
srv . scheduleService . DeleteAlertRule ( ngmodels . AlertRuleKey {
2022-08-11 13:28:55 +02:00
OrgID : c . SignedInUser . OrgID ,
2022-02-23 11:30:04 -05:00
UID : rule . UID ,
2022-01-11 11:39:34 -05:00
})
2021-05-06 09:39:34 -07:00
}
2022-08-01 23:41:23 -05:00
if finalChanges . IsEmpty () {
2022-03-04 16:16:33 -05:00
return response . JSON ( http . StatusAccepted , util . DynMap { "message" : "no changes detected in the rule group" })
}
2021-04-01 11:11:45 +03:00
return response . JSON ( http . StatusAccepted , util . DynMap { "message" : "rule group updated successfully" })
}
2022-06-22 10:52:46 -04:00
func toGettableRuleGroupConfig ( groupName string , rules ngmodels . RulesGroup , namespaceID int64 , provenanceRecords map [ string ] ngmodels . Provenance ) apimodels . GettableRuleGroupConfig {
rules . SortByGroupIndex ()
2022-05-12 10:42:31 -04:00
ruleNodes := make ([] apimodels . GettableExtendedRuleNode , 0 , len ( rules ))
var interval time . Duration
if len ( rules ) > 0 {
interval = time . Duration ( rules [ 0 ]. IntervalSeconds ) * time . Second
}
for _ , r := range rules {
ruleNodes = append ( ruleNodes , toGettableExtendedRuleNode ( * r , namespaceID , provenanceRecords ))
}
return apimodels . GettableRuleGroupConfig {
Name : groupName ,
Interval : model . Duration ( interval ),
Rules : ruleNodes ,
}
}
2022-04-28 21:27:34 +02:00
func toGettableExtendedRuleNode ( r ngmodels . AlertRule , namespaceID int64 , provenanceRecords map [ string ] ngmodels . Provenance ) apimodels . GettableExtendedRuleNode {
provenance := ngmodels . ProvenanceNone
if prov , exists := provenanceRecords [ r . ResourceID ()]; exists {
provenance = prov
}
2021-04-15 15:54:37 +03:00
gettableExtendedRuleNode := apimodels . GettableExtendedRuleNode {
2021-04-01 11:11:45 +03:00
GrafanaManagedAlert : & apimodels . GettableGrafanaRule {
ID : r . ID ,
OrgID : r . OrgID ,
Title : r . Title ,
Condition : r . Condition ,
Data : r . Data ,
Updated : r . Updated ,
IntervalSeconds : r . IntervalSeconds ,
Version : r . Version ,
UID : r . UID ,
NamespaceUID : r . NamespaceUID ,
2021-04-15 15:54:37 +03:00
NamespaceID : namespaceID ,
2021-04-01 11:11:45 +03:00
RuleGroup : r . RuleGroup ,
NoDataState : apimodels . NoDataState ( r . NoDataState ),
ExecErrState : apimodels . ExecutionErrorState ( r . ExecErrState ),
2022-04-28 21:27:34 +02:00
Provenance : provenance ,
2021-04-01 11:11:45 +03:00
},
}
2022-06-30 11:46:26 -04:00
forDuration := model . Duration ( r . For )
2021-04-15 15:54:37 +03:00
gettableExtendedRuleNode . ApiRuleNode = & apimodels . ApiRuleNode {
2022-06-30 11:46:26 -04:00
For : & forDuration ,
2021-04-15 15:54:37 +03:00
Annotations : r . Annotations ,
Labels : r . Labels ,
}
return gettableExtendedRuleNode
2021-04-01 11:11:45 +03:00
}
2021-04-07 08:28:06 -04:00
2021-04-15 15:54:37 +03:00
func toNamespaceErrorResponse ( err error ) response . Response {
if errors . Is ( err , ngmodels . ErrCannotEditNamespace ) {
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusForbidden , err , err . Error ())
2021-04-15 15:54:37 +03:00
}
2022-06-30 09:31:54 -04:00
if errors . Is ( err , dashboards . ErrDashboardIdentifierNotSet ) {
2021-05-28 11:55:03 -04:00
return ErrResp ( http . StatusBadRequest , err , err . Error ())
2021-04-15 15:54:37 +03:00
}
2021-08-25 15:11:22 +02:00
return apierrors . ToFolderErrorResponse ( err )
2021-04-07 08:28:06 -04:00
}
2022-02-23 11:30:04 -05:00
2022-06-15 16:01:14 -04:00
// verifyProvisionedRulesNotAffected check that neither of provisioned alerts are affected by changes.
// Returns errProvisionedResource if there is at least one rule in groups affected by changes that was provisioned.
2022-08-01 23:41:23 -05:00
func verifyProvisionedRulesNotAffected ( ctx context . Context , provenanceStore provisioning . ProvisioningStore , orgID int64 , ch * store . GroupDelta ) error {
2022-06-15 16:01:14 -04:00
provenances , err := provenanceStore . GetProvenances ( ctx , orgID , ( & ngmodels . AlertRule {}). ResourceType ())
if err != nil {
return err
}
errorMsg := strings . Builder {}
for group , alertRules := range ch . AffectedGroups {
for _ , rule := range alertRules {
if provenance , exists := provenances [ rule . UID ]; ( exists && provenance == ngmodels . ProvenanceNone ) || ! exists {
continue
}
if errorMsg . Len () > 0 {
errorMsg . WriteRune ( ',' )
}
errorMsg . WriteString ( group . String ())
break
}
}
if errorMsg . Len () == 0 {
return nil
}
return fmt . Errorf ( "%w: alert rule group [%s]" , errProvisionedResource , errorMsg . String ())
}