Alerting: introduce AlertRuleGroupKey and use it in API handlers (#48945)
* create AlertGroupKey structure * update PrometheusSrv. - extract creation of RuleGroup to a separate method. Use group key for grouping * update RuleSrv - update calculateChanges to use groupKey - authorize to use groupkey
This commit is contained in:
@@ -286,8 +286,6 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
|
||||
}
|
||||
|
||||
configs := make(map[string]map[string][]*ngmodels.AlertRule)
|
||||
|
||||
hasAccess := func(evaluator accesscontrol.Evaluator) bool {
|
||||
return accesscontrol.HasAccess(srv.ac, c)(accesscontrol.ReqSignedIn, evaluator)
|
||||
}
|
||||
@@ -297,30 +295,25 @@ func (srv RulerSrv) RouteGetRulesConfig(c *models.ReqContext) response.Response
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
|
||||
}
|
||||
|
||||
configs := make(map[ngmodels.AlertRuleGroupKey][]*ngmodels.AlertRule)
|
||||
for _, r := range q.Result {
|
||||
if !authorizeDatasourceAccessForRule(r, hasAccess) {
|
||||
continue
|
||||
}
|
||||
namespaceCfgs, ok := configs[r.NamespaceUID]
|
||||
if !ok {
|
||||
namespaceCfgs = make(map[string][]*ngmodels.AlertRule)
|
||||
configs[r.NamespaceUID] = namespaceCfgs
|
||||
}
|
||||
group := namespaceCfgs[r.RuleGroup]
|
||||
groupKey := r.GetGroupKey()
|
||||
group := configs[groupKey]
|
||||
group = append(group, r)
|
||||
namespaceCfgs[r.RuleGroup] = group
|
||||
configs[groupKey] = group
|
||||
}
|
||||
|
||||
for namespaceUID, m := range configs {
|
||||
folder, ok := namespaceMap[namespaceUID]
|
||||
for groupKey, rules := range configs {
|
||||
folder, ok := namespaceMap[groupKey.NamespaceUID]
|
||||
if !ok {
|
||||
srv.log.Error("namespace not visible to the user", "user", c.SignedInUser.UserId, "namespace", namespaceUID)
|
||||
srv.log.Error("namespace not visible to the user", "user", c.SignedInUser.UserId, "namespace", groupKey.NamespaceUID)
|
||||
continue
|
||||
}
|
||||
namespace := folder.Title
|
||||
for groupName, groupRules := range m {
|
||||
result[namespace] = append(result[namespace], toGettableRuleGroupConfig(groupName, groupRules, folder.Id, provenanceRecords))
|
||||
}
|
||||
result[namespace] = append(result[namespace], toGettableRuleGroupConfig(groupKey.RuleGroup, rules, folder.Id, provenanceRecords))
|
||||
}
|
||||
return response.JSON(http.StatusOK, result)
|
||||
}
|
||||
@@ -337,19 +330,24 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *models.ReqContext, ruleGroupConf
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
|
||||
return srv.updateAlertRulesInGroup(c, namespace, ruleGroupConfig.Name, rules)
|
||||
groupKey := ngmodels.AlertRuleGroupKey{
|
||||
OrgID: c.SignedInUser.OrgId,
|
||||
NamespaceUID: namespace.Uid,
|
||||
RuleGroup: ruleGroupConfig.Name,
|
||||
}
|
||||
|
||||
return srv.updateAlertRulesInGroup(c, groupKey, rules)
|
||||
}
|
||||
|
||||
// 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
|
||||
//nolint: gocyclo
|
||||
func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, namespace *models.Folder, groupName string, rules []*ngmodels.AlertRule) response.Response {
|
||||
// nolint: gocyclo
|
||||
func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, groupKey ngmodels.AlertRuleGroupKey, rules []*ngmodels.AlertRule) response.Response {
|
||||
var finalChanges *changes
|
||||
hasAccess := accesscontrol.HasAccess(srv.ac, c)
|
||||
err := srv.xactManager.InTransaction(c.Req.Context(), func(tranCtx context.Context) error {
|
||||
logger := srv.log.New("namespace_uid", namespace.Uid, "group", groupName, "org_id", c.OrgId, "user_id", c.UserId)
|
||||
|
||||
groupChanges, err := calculateChanges(tranCtx, srv.store, c.SignedInUser.OrgId, namespace, groupName, rules)
|
||||
logger := srv.log.New("namespace_uid", groupKey.NamespaceUID, "group", groupKey.RuleGroup, "org_id", groupKey.OrgID, "user_id", c.UserId)
|
||||
groupChanges, err := calculateChanges(tranCtx, srv.store, groupKey, rules)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -360,7 +358,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *models.ReqContext, namespace *mod
|
||||
return nil
|
||||
}
|
||||
|
||||
authorizedChanges, err := authorizeRuleChanges(namespace, groupChanges, func(evaluator accesscontrol.Evaluator) bool {
|
||||
authorizedChanges, err := authorizeRuleChanges(groupChanges, func(evaluator accesscontrol.Evaluator) bool {
|
||||
return hasAccess(accesscontrol.ReqOrgAdminOrEditor, evaluator)
|
||||
})
|
||||
if err != nil {
|
||||
@@ -565,9 +563,10 @@ type ruleUpdate struct {
|
||||
}
|
||||
|
||||
type changes struct {
|
||||
New []*ngmodels.AlertRule
|
||||
Update []ruleUpdate
|
||||
Delete []*ngmodels.AlertRule
|
||||
GroupKey ngmodels.AlertRuleGroupKey
|
||||
New []*ngmodels.AlertRule
|
||||
Update []ruleUpdate
|
||||
Delete []*ngmodels.AlertRule
|
||||
}
|
||||
|
||||
func (c *changes) isEmpty() bool {
|
||||
@@ -576,14 +575,14 @@ func (c *changes) isEmpty() bool {
|
||||
|
||||
// calculateChanges calculates the difference between rules in the group in the database and the submitted rules. If a submitted rule has UID it tries to find it in the database (in other groups).
|
||||
// returns a list of rules that need to be added, updated and deleted. Deleted considered rules in the database that belong to the group but do not exist in the list of submitted rules.
|
||||
func calculateChanges(ctx context.Context, ruleStore store.RuleStore, orgId int64, namespace *models.Folder, ruleGroupName string, submittedRules []*ngmodels.AlertRule) (*changes, error) {
|
||||
func calculateChanges(ctx context.Context, ruleStore store.RuleStore, groupKey ngmodels.AlertRuleGroupKey, submittedRules []*ngmodels.AlertRule) (*changes, error) {
|
||||
q := &ngmodels.ListAlertRulesQuery{
|
||||
OrgID: orgId,
|
||||
NamespaceUIDs: []string{namespace.Uid},
|
||||
RuleGroup: ruleGroupName,
|
||||
OrgID: groupKey.OrgID,
|
||||
NamespaceUIDs: []string{groupKey.NamespaceUID},
|
||||
RuleGroup: groupKey.RuleGroup,
|
||||
}
|
||||
if err := ruleStore.ListAlertRules(ctx, q); err != nil {
|
||||
return nil, fmt.Errorf("failed to query database for rules in the group %s: %w", ruleGroupName, err)
|
||||
return nil, fmt.Errorf("failed to query database for rules in the group %s: %w", groupKey, err)
|
||||
}
|
||||
existingGroupRules := q.Result
|
||||
|
||||
@@ -604,7 +603,7 @@ func calculateChanges(ctx context.Context, ruleStore store.RuleStore, orgId int6
|
||||
delete(existingGroupRulesUIDs, r.UID)
|
||||
} else {
|
||||
// Rule can be from other group or namespace
|
||||
q := &ngmodels.GetAlertRuleByUIDQuery{OrgID: orgId, UID: r.UID}
|
||||
q := &ngmodels.GetAlertRuleByUIDQuery{OrgID: groupKey.OrgID, UID: r.UID}
|
||||
if err := ruleStore.GetAlertRuleByUID(ctx, q); err != nil || q.Result == nil {
|
||||
// if rule has UID then it is considered an update. Therefore, fail if there is no rule to update
|
||||
if errors.Is(err, ngmodels.ErrAlertRuleNotFound) || q.Result == nil && err == nil {
|
||||
@@ -641,9 +640,10 @@ func calculateChanges(ctx context.Context, ruleStore store.RuleStore, orgId int6
|
||||
}
|
||||
|
||||
return &changes{
|
||||
New: toAdd,
|
||||
Delete: toDelete,
|
||||
Update: toUpdate,
|
||||
GroupKey: groupKey,
|
||||
New: toAdd,
|
||||
Delete: toDelete,
|
||||
Update: toUpdate,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user