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