2021-04-01 16:11:45 +08:00
package api
import (
2022-02-24 00:30:04 +08:00
"context"
2021-04-15 20:54:37 +08:00
"errors"
2021-09-03 00:38:42 +08:00
"fmt"
2021-04-01 16:11:45 +08:00
"net/http"
2022-06-16 04:01:14 +08:00
"strings"
2021-04-01 16:11:45 +08:00
"time"
2022-01-12 00:39:34 +08:00
"github.com/prometheus/common/model"
2021-08-25 21:11:22 +08:00
"github.com/grafana/grafana/pkg/api/apierrors"
2021-04-01 16:11:45 +08:00
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
2023-01-30 16:55:35 +08:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2023-01-27 15:50:36 +08:00
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
2023-01-30 16:55:35 +08:00
"github.com/grafana/grafana/pkg/services/dashboards"
2021-04-20 02:26:04 +08:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
2023-01-30 16:55:35 +08:00
"github.com/grafana/grafana/pkg/services/ngalert/eval"
2021-04-01 16:11:45 +08:00
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2023-01-30 16:55:35 +08:00
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
2022-01-12 00:39:34 +08:00
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
2023-01-30 16:55:35 +08:00
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/quota"
"github.com/grafana/grafana/pkg/setting"
2021-04-01 16:11:45 +08:00
"github.com/grafana/grafana/pkg/util"
)
2022-09-22 03:14:11 +08:00
type ConditionValidator interface {
// Validate validates that the condition is correct. Returns nil if the condition is correct. Otherwise, error that describes the failure
2022-10-20 03:19:43 +08:00
Validate ( ctx eval . EvaluationContext , condition ngmodels . Condition ) error
2022-09-22 03:14:11 +08:00
}
2021-04-01 16:11:45 +08:00
type RulerSrv struct {
2022-09-22 03:14:11 +08:00
xactManager provisioning . TransactionManager
provenanceStore provisioning . ProvisioningStore
2022-09-27 21:56:30 +08:00
store RuleStore
2022-09-22 03:14:11 +08:00
QuotaService quota . Service
scheduleService schedule . ScheduleService
log log . Logger
cfg * setting . UnifiedAlertingSettings
ac accesscontrol . AccessControl
conditionValidator ConditionValidator
2021-04-01 16:11:45 +08:00
}
2022-02-24 00:30:04 +08:00
var (
2022-06-16 04:01:14 +08:00
errProvisionedResource = errors . New ( "request affects resources created via provisioning API" )
2022-02-24 00:30:04 +08:00
)
2022-08-25 03:33:33 +08:00
// RouteDeleteAlertRules deletes all alert rules the user is authorized to access in the given namespace
// or, if non-empty, a specific group of rules in the namespace.
// Returns http.StatusUnauthorized if user does not have access to any of the rules that match the filter.
// Returns http.StatusBadRequest if all rules that match the filter and the user is authorized to delete are provisioned.
2023-01-27 15:50:36 +08:00
func ( srv RulerSrv ) RouteDeleteAlertRules ( c * contextmodel . ReqContext , namespaceTitle string , group string ) response . Response {
2022-08-11 19:28:55 +08:00
namespace , err := srv . store . GetNamespaceByTitle ( c . Req . Context ( ) , namespaceTitle , c . SignedInUser . OrgID , c . SignedInUser , true )
2021-04-01 16:11:45 +08:00
if err != nil {
2021-04-15 20:54:37 +08:00
return toNamespaceErrorResponse ( err )
2021-04-01 16:11:45 +08:00
}
2022-03-26 00:39:24 +08:00
var loggerCtx = [ ] interface { } {
"namespace" ,
namespace . Title ,
}
2022-04-25 18:42:42 +08:00
var ruleGroup string
2022-06-24 04:13:39 +08:00
if group != "" {
2022-04-25 18:42:42 +08:00
ruleGroup = group
2022-03-26 00:39:24 +08:00
loggerCtx = append ( loggerCtx , "group" , group )
2021-04-01 16:11:45 +08:00
}
2022-03-26 00:39:24 +08:00
logger := srv . log . New ( loggerCtx ... )
2021-05-04 02:01:33 +08:00
2022-03-26 00:39:24 +08:00
hasAccess := func ( evaluator accesscontrol . Evaluator ) bool {
return accesscontrol . HasAccess ( srv . ac , c ) ( accesscontrol . ReqOrgAdminOrEditor , evaluator )
2021-05-04 02:01:33 +08:00
}
2022-08-11 19:28:55 +08:00
provenances , err := srv . provenanceStore . GetProvenances ( c . Req . Context ( ) , c . SignedInUser . OrgID , ( & ngmodels . AlertRule { } ) . ResourceType ( ) )
2022-05-07 02:55:27 +08:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to fetch provenances of alert rules" )
}
2022-08-25 03:33:33 +08:00
deletedGroups := make ( map [ ngmodels . AlertRuleGroupKey ] [ ] ngmodels . AlertRuleKey )
2022-03-26 00:39:24 +08:00
err = srv . xactManager . InTransaction ( c . Req . Context ( ) , func ( ctx context . Context ) error {
2022-08-25 03:33:33 +08:00
unauthz , provisioned := false , false
2022-04-25 18:42:42 +08:00
q := ngmodels . ListAlertRulesQuery {
2022-08-11 19:28:55 +08:00
OrgID : c . SignedInUser . OrgID ,
2022-11-11 21:28:24 +08:00
NamespaceUIDs : [ ] string { namespace . UID } ,
2022-04-25 18:42:42 +08:00
RuleGroup : ruleGroup ,
2022-03-26 00:39:24 +08:00
}
2022-04-25 18:42:42 +08:00
if err = srv . store . ListAlertRules ( ctx , & q ) ; err != nil {
2022-03-26 00:39:24 +08:00
return err
}
2021-04-01 16:11:45 +08:00
2022-03-26 00:39:24 +08:00
if len ( q . Result ) == 0 {
logger . Debug ( "no alert rules to delete from namespace/group" )
return nil
}
2022-08-25 03:33:33 +08:00
var deletionCandidates = make ( map [ ngmodels . AlertRuleGroupKey ] [ ] * ngmodels . AlertRule )
for _ , rule := range q . Result {
key := rule . GetGroupKey ( )
deletionCandidates [ key ] = append ( deletionCandidates [ key ] , rule )
2022-03-26 00:39:24 +08:00
}
2022-08-25 03:33:33 +08:00
rulesToDelete := make ( [ ] string , 0 , len ( q . Result ) )
for groupKey , rules := range deletionCandidates {
if ! authorizeAccessToRuleGroup ( rules , hasAccess ) {
unauthz = true
continue
}
if containsProvisionedAlerts ( provenances , rules ) {
provisioned = true
continue
}
uid := make ( [ ] string , 0 , len ( rules ) )
keys := make ( [ ] ngmodels . AlertRuleKey , 0 , len ( rules ) )
for _ , rule := range rules {
uid = append ( uid , rule . UID )
keys = append ( keys , rule . GetKey ( ) )
}
rulesToDelete = append ( rulesToDelete , uid ... )
deletedGroups [ groupKey ] = keys
2022-03-26 00:39:24 +08:00
}
2022-08-25 03:33:33 +08:00
if len ( rulesToDelete ) > 0 {
return srv . store . DeleteAlertRulesByUID ( ctx , c . SignedInUser . OrgID , rulesToDelete ... )
2022-05-07 02:55:27 +08:00
}
2022-08-25 03:33:33 +08:00
// if none rules were deleted return an error.
// Check whether provisioned check failed first because if it is true, then all rules that the user can access (actually read via GET API) are provisioned.
if provisioned {
return errProvisionedResource
2022-05-07 02:55:27 +08:00
}
2022-08-25 03:33:33 +08:00
if unauthz {
if group == "" {
return fmt . Errorf ( "%w to delete any existing rules in the namespace" , ErrAuthorization )
}
return fmt . Errorf ( "%w to delete group of the rules" , ErrAuthorization )
2022-05-07 02:55:27 +08:00
}
2022-08-25 03:33:33 +08:00
return nil
2022-03-26 00:39:24 +08:00
} )
2021-05-04 02:01:33 +08:00
if err != nil {
2022-03-26 00:39:24 +08:00
if errors . Is ( err , ErrAuthorization ) {
2022-08-25 03:33:33 +08:00
return ErrResp ( http . StatusUnauthorized , err , "failed to delete rule group" )
}
if errors . Is ( err , errProvisionedResource ) {
return ErrResp ( http . StatusBadRequest , err , "failed to delete rule group" )
2021-04-16 20:00:07 +08:00
}
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusInternalServerError , err , "failed to delete rule group" )
2021-04-01 16:11:45 +08:00
}
2021-04-16 20:00:07 +08:00
2022-03-26 00:39:24 +08:00
logger . Debug ( "rules have been deleted from the store. updating scheduler" )
2022-08-25 03:33:33 +08:00
for _ , ruleKeys := range deletedGroups {
srv . scheduleService . DeleteAlertRule ( ruleKeys ... )
2021-05-04 02:01:33 +08:00
}
2022-03-26 00:39:24 +08:00
return response . JSON ( http . StatusAccepted , util . DynMap { "message" : "rules deleted" } )
2021-04-01 16:11:45 +08:00
}
2022-06-18 01:55:31 +08:00
// RouteGetNamespaceRulesConfig returns all rules in a specific folder that user has access to
2023-01-27 15:50:36 +08:00
func ( srv RulerSrv ) RouteGetNamespaceRulesConfig ( c * contextmodel . ReqContext , namespaceTitle string ) response . Response {
2022-08-11 19:28:55 +08:00
namespace , err := srv . store . GetNamespaceByTitle ( c . Req . Context ( ) , namespaceTitle , c . SignedInUser . OrgID , c . SignedInUser , false )
2021-04-01 16:11:45 +08:00
if err != nil {
2021-04-15 20:54:37 +08:00
return toNamespaceErrorResponse ( err )
2021-04-01 16:11:45 +08:00
}
2022-04-25 18:42:42 +08:00
q := ngmodels . ListAlertRulesQuery {
2022-08-11 19:28:55 +08:00
OrgID : c . SignedInUser . OrgID ,
2022-11-11 21:28:24 +08:00
NamespaceUIDs : [ ] string { namespace . UID } ,
2021-04-01 16:11:45 +08:00
}
2022-04-25 18:42:42 +08:00
if err := srv . store . ListAlertRules ( c . Req . Context ( ) , & q ) ; err != nil {
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusInternalServerError , err , "failed to update rule group" )
2021-04-01 16:11:45 +08:00
}
result := apimodels . NamespaceConfigResponse { }
2022-04-12 05:37:44 +08:00
hasAccess := func ( evaluator accesscontrol . Evaluator ) bool {
2022-05-19 21:22:26 +08:00
return accesscontrol . HasAccess ( srv . ac , c ) ( accesscontrol . ReqViewer , evaluator )
2022-04-12 05:37:44 +08:00
}
2022-08-11 19:28:55 +08:00
provenanceRecords , err := srv . provenanceStore . GetProvenances ( c . Req . Context ( ) , c . SignedInUser . OrgID , ( & ngmodels . AlertRule { } ) . ResourceType ( ) )
2022-04-29 03:27:34 +08:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to get provenance for rule group" )
}
2022-06-22 22:52:46 +08:00
ruleGroups := make ( map [ string ] ngmodels . RulesGroup )
2021-04-01 16:11:45 +08:00
for _ , r := range q . Result {
2022-06-18 01:55:31 +08:00
ruleGroups [ r . RuleGroup ] = append ( ruleGroups [ r . RuleGroup ] , r )
2021-04-01 16:11:45 +08:00
}
2022-06-18 01:55:31 +08:00
for groupName , rules := range ruleGroups {
if ! authorizeAccessToRuleGroup ( rules , hasAccess ) {
continue
}
2022-11-11 21:28:24 +08:00
result [ namespaceTitle ] = append ( result [ namespaceTitle ] , toGettableRuleGroupConfig ( groupName , rules , namespace . ID , provenanceRecords ) )
2021-04-01 16:11:45 +08:00
}
return response . JSON ( http . StatusAccepted , result )
}
2022-06-18 01:55:31 +08: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
2023-01-27 15:50:36 +08:00
func ( srv RulerSrv ) RouteGetRulesGroupConfig ( c * contextmodel . ReqContext , namespaceTitle string , ruleGroup string ) response . Response {
2022-08-11 19:28:55 +08:00
namespace , err := srv . store . GetNamespaceByTitle ( c . Req . Context ( ) , namespaceTitle , c . SignedInUser . OrgID , c . SignedInUser , false )
2021-04-01 16:11:45 +08:00
if err != nil {
2021-04-15 20:54:37 +08:00
return toNamespaceErrorResponse ( err )
2021-04-01 16:11:45 +08:00
}
2022-04-25 18:42:42 +08:00
q := ngmodels . ListAlertRulesQuery {
2022-08-11 19:28:55 +08:00
OrgID : c . SignedInUser . OrgID ,
2022-11-11 21:28:24 +08:00
NamespaceUIDs : [ ] string { namespace . UID } ,
2022-04-25 18:42:42 +08:00
RuleGroup : ruleGroup ,
2021-04-01 16:11:45 +08:00
}
2022-04-25 18:42:42 +08:00
if err := srv . store . ListAlertRules ( c . Req . Context ( ) , & q ) ; err != nil {
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusInternalServerError , err , "failed to get group alert rules" )
2021-04-01 16:11:45 +08:00
}
2022-04-12 05:37:44 +08:00
hasAccess := func ( evaluator accesscontrol . Evaluator ) bool {
2022-05-19 21:22:26 +08:00
return accesscontrol . HasAccess ( srv . ac , c ) ( accesscontrol . ReqViewer , evaluator )
2022-04-12 05:37:44 +08:00
}
2022-08-11 19:28:55 +08:00
provenanceRecords , err := srv . provenanceStore . GetProvenances ( c . Req . Context ( ) , c . SignedInUser . OrgID , ( & ngmodels . AlertRule { } ) . ResourceType ( ) )
2022-04-29 03:27:34 +08:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to get group alert rules" )
}
2022-06-18 01:55:31 +08: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 16:11:45 +08:00
}
result := apimodels . RuleGroupConfigResponse {
2022-11-11 21:28:24 +08:00
GettableRuleGroupConfig : toGettableRuleGroupConfig ( ruleGroup , q . Result , namespace . ID , provenanceRecords ) ,
2021-04-01 16:11:45 +08:00
}
return response . JSON ( http . StatusAccepted , result )
}
2022-06-18 01:55:31 +08:00
// RouteGetRulesConfig returns all alert rules that are available to the current user
2023-01-27 15:50:36 +08:00
func ( srv RulerSrv ) RouteGetRulesConfig ( c * contextmodel . ReqContext ) response . Response {
2022-08-11 19:28:55 +08:00
namespaceMap , err := srv . store . GetUserVisibleNamespaces ( c . Req . Context ( ) , c . OrgID , c . SignedInUser )
2021-07-22 14:53:14 +08:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to get namespaces visible to the user" )
}
2021-11-08 21:26:08 +08:00
result := apimodels . NamespaceConfigResponse { }
if len ( namespaceMap ) == 0 {
2022-06-08 01:54:23 +08:00
srv . log . Debug ( "user has no access to any namespaces" )
2021-11-08 21:26:08 +08:00
return response . JSON ( http . StatusOK , result )
}
2021-07-22 14:53:14 +08:00
namespaceUIDs := make ( [ ] string , len ( namespaceMap ) )
for k := range namespaceMap {
namespaceUIDs = append ( namespaceUIDs , k )
}
2021-10-04 23:33:55 +08: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 16:11:45 +08:00
q := ngmodels . ListAlertRulesQuery {
2022-08-11 19:28:55 +08:00
OrgID : c . SignedInUser . OrgID ,
2021-07-22 14:53:14 +08:00
NamespaceUIDs : namespaceUIDs ,
2021-10-04 23:33:55 +08:00
DashboardUID : dashboardUID ,
PanelID : panelID ,
2021-04-01 16:11:45 +08:00
}
2021-07-22 14:53:14 +08:00
2022-04-25 18:42:42 +08:00
if err := srv . store . ListAlertRules ( c . Req . Context ( ) , & q ) ; err != nil {
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusInternalServerError , err , "failed to get alert rules" )
2021-04-01 16:11:45 +08:00
}
2022-04-12 05:37:44 +08:00
hasAccess := func ( evaluator accesscontrol . Evaluator ) bool {
2022-05-19 21:22:26 +08:00
return accesscontrol . HasAccess ( srv . ac , c ) ( accesscontrol . ReqViewer , evaluator )
2022-04-12 05:37:44 +08:00
}
2022-08-11 19:28:55 +08:00
provenanceRecords , err := srv . provenanceStore . GetProvenances ( c . Req . Context ( ) , c . SignedInUser . OrgID , ( & ngmodels . AlertRule { } ) . ResourceType ( ) )
2022-04-29 03:27:34 +08:00
if err != nil {
return ErrResp ( http . StatusInternalServerError , err , "failed to get alert rules" )
}
2022-06-22 22:52:46 +08:00
configs := make ( map [ ngmodels . AlertRuleGroupKey ] ngmodels . RulesGroup )
2021-04-01 16:11:45 +08:00
for _ , r := range q . Result {
2022-05-17 03:45:45 +08:00
groupKey := r . GetGroupKey ( )
group := configs [ groupKey ]
2022-05-12 22:42:31 +08:00
group = append ( group , r )
2022-05-17 03:45:45 +08:00
configs [ groupKey ] = group
2021-04-01 16:11:45 +08:00
}
2022-05-17 03:45:45 +08:00
for groupKey , rules := range configs {
folder , ok := namespaceMap [ groupKey . NamespaceUID ]
2022-05-12 22:42:31 +08:00
if ! ok {
2022-08-11 19:28:55 +08:00
srv . log . Error ( "namespace not visible to the user" , "user" , c . SignedInUser . UserID , "namespace" , groupKey . NamespaceUID )
2022-05-12 22:42:31 +08:00
continue
}
2022-06-01 22:23:54 +08:00
if ! authorizeAccessToRuleGroup ( rules , hasAccess ) {
continue
}
2022-05-12 22:42:31 +08:00
namespace := folder . Title
2022-11-11 21:28:24 +08:00
result [ namespace ] = append ( result [ namespace ] , toGettableRuleGroupConfig ( groupKey . RuleGroup , rules , folder . ID , provenanceRecords ) )
2021-04-01 16:11:45 +08:00
}
2021-10-04 23:33:55 +08:00
return response . JSON ( http . StatusOK , result )
2021-04-01 16:11:45 +08:00
}
2023-01-27 15:50:36 +08:00
func ( srv RulerSrv ) RoutePostNameRulesConfig ( c * contextmodel . ReqContext , ruleGroupConfig apimodels . PostableRuleGroupConfig , namespaceTitle string ) response . Response {
2022-08-11 19:28:55 +08:00
namespace , err := srv . store . GetNamespaceByTitle ( c . Req . Context ( ) , namespaceTitle , c . SignedInUser . OrgID , c . SignedInUser , true )
2021-04-01 16:11:45 +08:00
if err != nil {
2021-04-15 20:54:37 +08:00
return toNamespaceErrorResponse ( err )
2021-04-01 16:11:45 +08:00
}
2022-09-22 03:14:11 +08:00
rules , err := validateRuleGroup ( & ruleGroupConfig , c . SignedInUser . OrgID , namespace , func ( condition ngmodels . Condition ) error {
2022-10-20 03:19:43 +08:00
return srv . conditionValidator . Validate ( eval . Context ( c . Req . Context ( ) , c . SignedInUser ) , condition )
2022-09-22 03:14:11 +08:00
} , srv . cfg )
2022-02-24 00:30:04 +08:00
if err != nil {
return ErrResp ( http . StatusBadRequest , err , "" )
2021-04-16 20:00:07 +08:00
}
2022-05-17 03:45:45 +08:00
groupKey := ngmodels . AlertRuleGroupKey {
2022-08-11 19:28:55 +08:00
OrgID : c . SignedInUser . OrgID ,
2022-11-11 21:28:24 +08:00
NamespaceUID : namespace . UID ,
2022-05-17 03:45:45 +08:00
RuleGroup : ruleGroupConfig . Name ,
}
return srv . updateAlertRulesInGroup ( c , groupKey , rules )
2022-02-24 00:30:04 +08:00
}
2022-03-25 04:53:00 +08: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
2023-02-01 20:15:03 +08:00
func ( srv RulerSrv ) updateAlertRulesInGroup ( c * contextmodel . ReqContext , groupKey ngmodels . AlertRuleGroupKey , rules [ ] * ngmodels . AlertRuleWithOptionals ) response . Response {
2022-08-02 12:41:23 +08:00
var finalChanges * store . GroupDelta
2022-03-22 07:20:35 +08:00
hasAccess := accesscontrol . HasAccess ( srv . ac , c )
2022-03-16 00:48:42 +08:00
err := srv . xactManager . InTransaction ( c . Req . Context ( ) , func ( tranCtx context . Context ) error {
2022-08-11 19:28:55 +08:00
logger := srv . log . New ( "namespace_uid" , groupKey . NamespaceUID , "group" , groupKey . RuleGroup , "org_id" , groupKey . OrgID , "user_id" , c . UserID )
2022-08-02 12:41:23 +08:00
groupChanges , err := store . CalculateChanges ( tranCtx , srv . store , groupKey , rules )
2022-02-24 00:30:04 +08:00
if err != nil {
return err
2021-04-28 16:31:51 +08:00
}
2022-02-24 00:30:04 +08:00
2022-08-02 12:41:23 +08:00
if groupChanges . IsEmpty ( ) {
2022-05-07 02:55:27 +08:00
finalChanges = groupChanges
2022-03-25 04:53:00 +08:00
logger . Info ( "no changes detected in the request. Do nothing" )
2022-03-05 05:16:33 +08:00
return nil
2021-09-03 00:38:42 +08:00
}
2022-02-24 00:30:04 +08:00
2022-06-01 22:23:54 +08:00
// if RBAC is disabled the permission are limited to folder access that is done upstream
2022-05-23 21:58:20 +08:00
if ! srv . ac . IsDisabled ( ) {
2022-06-01 22:23:54 +08:00
err = authorizeRuleChanges ( groupChanges , func ( evaluator accesscontrol . Evaluator ) bool {
2022-05-23 21:58:20 +08:00
return hasAccess ( accesscontrol . ReqOrgAdminOrEditor , evaluator )
} )
if err != nil {
return err
}
2022-03-25 04:53:00 +08:00
}
2022-08-11 19:28:55 +08:00
if err := verifyProvisionedRulesNotAffected ( c . Req . Context ( ) , srv . provenanceStore , c . OrgID , groupChanges ) ; err != nil {
2022-05-07 02:55:27 +08:00
return err
}
2022-08-02 12:41:23 +08:00
finalChanges = store . UpdateCalculatedRuleFields ( groupChanges )
2022-05-07 02:55:27 +08:00
logger . Debug ( "updating database with the authorized changes" , "add" , len ( finalChanges . New ) , "update" , len ( finalChanges . New ) , "delete" , len ( finalChanges . Delete ) )
2022-03-22 07:20:35 +08:00
2022-05-07 02:55:27 +08:00
if len ( finalChanges . Update ) > 0 || len ( finalChanges . New ) > 0 {
2022-09-30 04:47:56 +08:00
updates := make ( [ ] ngmodels . UpdateRule , 0 , len ( finalChanges . Update ) )
2022-05-07 02:55:27 +08:00
inserts := make ( [ ] ngmodels . AlertRule , 0 , len ( finalChanges . New ) )
for _ , update := range finalChanges . Update {
2022-03-25 04:53:00 +08:00
logger . Debug ( "updating rule" , "rule_uid" , update . New . UID , "diff" , update . Diff . String ( ) )
2022-09-30 04:47:56 +08:00
updates = append ( updates , ngmodels . UpdateRule {
2022-03-05 05:16:33 +08:00
Existing : update . Existing ,
New : * update . New ,
} )
}
2022-05-07 02:55:27 +08:00
for _ , rule := range finalChanges . New {
2022-04-14 20:21:36 +08:00
inserts = append ( inserts , * rule )
}
2022-06-02 20:48:53 +08:00
_ , err = srv . store . InsertAlertRules ( tranCtx , inserts )
2022-04-14 20:21:36 +08:00
if err != nil {
return fmt . Errorf ( "failed to add rules: %w" , err )
2022-03-05 05:16:33 +08:00
}
2022-04-14 20:21:36 +08:00
err = srv . store . UpdateAlertRules ( tranCtx , updates )
2022-03-05 05:16:33 +08:00
if err != nil {
2022-04-14 20:21:36 +08:00
return fmt . Errorf ( "failed to update rules: %w" , err )
2022-03-05 05:16:33 +08:00
}
}
2022-05-07 02:55:27 +08:00
if len ( finalChanges . Delete ) > 0 {
UIDs := make ( [ ] string , 0 , len ( finalChanges . Delete ) )
for _ , rule := range finalChanges . Delete {
2022-03-24 04:09:53 +08:00
UIDs = append ( UIDs , rule . UID )
}
2022-08-11 19:28:55 +08:00
if err = srv . store . DeleteAlertRulesByUID ( tranCtx , c . SignedInUser . OrgID , UIDs ... ) ; err != nil {
2022-03-24 04:09:53 +08:00
return fmt . Errorf ( "failed to delete rules: %w" , err )
2021-09-03 00:38:42 +08:00
}
}
2022-05-07 02:55:27 +08:00
if len ( finalChanges . New ) > 0 {
2022-11-15 03:08:10 +08:00
limitReached , err := srv . QuotaService . CheckQuotaReached ( tranCtx , ngmodels . QuotaTargetSrv , & quota . ScopeParameters {
2022-08-11 19:28:55 +08:00
OrgID : c . OrgID ,
UserID : c . UserID ,
2022-02-24 00:30:04 +08:00
} ) // alert rule is table name
if err != nil {
return fmt . Errorf ( "failed to get alert rules quota: %w" , err )
}
if limitReached {
2022-07-14 06:36:17 +08:00
return ngmodels . ErrQuotaReached
2022-02-24 00:30:04 +08:00
}
2021-04-28 16:31:51 +08:00
}
2022-02-24 00:30:04 +08:00
return nil
} )
2021-04-28 16:31:51 +08:00
2022-02-24 00:30:04 +08:00
if err != nil {
2021-04-15 20:54:37 +08:00
if errors . Is ( err , ngmodels . ErrAlertRuleNotFound ) {
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusNotFound , err , "failed to update rule group" )
2022-06-16 04:01:14 +08:00
} else if errors . Is ( err , ngmodels . ErrAlertRuleFailedValidation ) || errors . Is ( err , errProvisionedResource ) {
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusBadRequest , err , "failed to update rule group" )
2022-07-14 06:36:17 +08:00
} else if errors . Is ( err , ngmodels . ErrQuotaReached ) {
2022-02-24 00:30:04 +08:00
return ErrResp ( http . StatusForbidden , err , "" )
2022-03-22 07:20:35 +08:00
} else if errors . Is ( err , ErrAuthorization ) {
return ErrResp ( http . StatusUnauthorized , err , "" )
2022-06-14 00:15:28 +08:00
} else if errors . Is ( err , store . ErrOptimisticLock ) {
return ErrResp ( http . StatusConflict , err , "" )
2021-04-15 20:54:37 +08:00
}
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusInternalServerError , err , "failed to update rule group" )
2021-04-01 16:11:45 +08:00
}
2022-05-07 02:55:27 +08:00
for _ , rule := range finalChanges . Update {
2022-03-05 05:16:33 +08:00
srv . scheduleService . UpdateAlertRule ( ngmodels . AlertRuleKey {
2022-08-11 19:28:55 +08:00
OrgID : c . SignedInUser . OrgID ,
2022-03-05 05:16:33 +08:00
UID : rule . Existing . UID ,
2023-01-27 01:29:10 +08:00
} , rule . Existing . Version + 1 , rule . New . IsPaused )
2022-03-05 05:16:33 +08:00
}
2022-02-24 00:30:04 +08:00
2022-08-25 03:33:33 +08:00
if len ( finalChanges . Delete ) > 0 {
keys := make ( [ ] ngmodels . AlertRuleKey , 0 , len ( finalChanges . Delete ) )
for _ , rule := range finalChanges . Delete {
keys = append ( keys , rule . GetKey ( ) )
}
srv . scheduleService . DeleteAlertRule ( keys ... )
2021-05-07 00:39:34 +08:00
}
2022-08-02 12:41:23 +08:00
if finalChanges . IsEmpty ( ) {
2022-03-05 05:16:33 +08:00
return response . JSON ( http . StatusAccepted , util . DynMap { "message" : "no changes detected in the rule group" } )
}
2021-04-01 16:11:45 +08:00
return response . JSON ( http . StatusAccepted , util . DynMap { "message" : "rule group updated successfully" } )
}
2022-06-22 22:52:46 +08:00
func toGettableRuleGroupConfig ( groupName string , rules ngmodels . RulesGroup , namespaceID int64 , provenanceRecords map [ string ] ngmodels . Provenance ) apimodels . GettableRuleGroupConfig {
rules . SortByGroupIndex ( )
2022-05-12 22:42:31 +08: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-29 03:27:34 +08: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 20:54:37 +08:00
gettableExtendedRuleNode := apimodels . GettableExtendedRuleNode {
2021-04-01 16:11:45 +08: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 20:54:37 +08:00
NamespaceID : namespaceID ,
2021-04-01 16:11:45 +08:00
RuleGroup : r . RuleGroup ,
NoDataState : apimodels . NoDataState ( r . NoDataState ) ,
ExecErrState : apimodels . ExecutionErrorState ( r . ExecErrState ) ,
2022-04-29 03:27:34 +08:00
Provenance : provenance ,
2023-02-01 20:15:03 +08:00
IsPaused : r . IsPaused ,
2021-04-01 16:11:45 +08:00
} ,
}
2022-06-30 23:46:26 +08:00
forDuration := model . Duration ( r . For )
2021-04-15 20:54:37 +08:00
gettableExtendedRuleNode . ApiRuleNode = & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & forDuration ,
2021-04-15 20:54:37 +08:00
Annotations : r . Annotations ,
Labels : r . Labels ,
}
return gettableExtendedRuleNode
2021-04-01 16:11:45 +08:00
}
2021-04-07 20:28:06 +08:00
2021-04-15 20:54:37 +08:00
func toNamespaceErrorResponse ( err error ) response . Response {
if errors . Is ( err , ngmodels . ErrCannotEditNamespace ) {
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusForbidden , err , err . Error ( ) )
2021-04-15 20:54:37 +08:00
}
2022-06-30 21:31:54 +08:00
if errors . Is ( err , dashboards . ErrDashboardIdentifierNotSet ) {
2021-05-28 23:55:03 +08:00
return ErrResp ( http . StatusBadRequest , err , err . Error ( ) )
2021-04-15 20:54:37 +08:00
}
2021-08-25 21:11:22 +08:00
return apierrors . ToFolderErrorResponse ( err )
2021-04-07 20:28:06 +08:00
}
2022-02-24 00:30:04 +08:00
2022-06-16 04:01:14 +08: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-02 12:41:23 +08:00
func verifyProvisionedRulesNotAffected ( ctx context . Context , provenanceStore provisioning . ProvisioningStore , orgID int64 , ch * store . GroupDelta ) error {
2022-06-16 04:01:14 +08:00
provenances , err := provenanceStore . GetProvenances ( ctx , orgID , ( & ngmodels . AlertRule { } ) . ResourceType ( ) )
if err != nil {
return err
}
errorMsg := strings . Builder { }
for group , alertRules := range ch . AffectedGroups {
2022-08-25 03:33:33 +08:00
if ! containsProvisionedAlerts ( provenances , alertRules ) {
continue
}
if errorMsg . Len ( ) > 0 {
errorMsg . WriteRune ( ',' )
2022-06-16 04:01:14 +08:00
}
2022-08-25 03:33:33 +08:00
errorMsg . WriteString ( group . String ( ) )
2022-06-16 04:01:14 +08:00
}
if errorMsg . Len ( ) == 0 {
return nil
}
return fmt . Errorf ( "%w: alert rule group [%s]" , errProvisionedResource , errorMsg . String ( ) )
}