Alerting: Update rule provisioning service to accept user (#84480)

This commit is contained in:
Yuri Tseretyan
2024-03-14 12:04:10 -04:00
committed by GitHub
parent 6d19894a7d
commit f7d836feed
4 changed files with 156 additions and 142 deletions
+20 -22
View File
@@ -58,17 +58,17 @@ type MuteTimingService interface {
} }
type AlertRuleService interface { type AlertRuleService interface {
GetAlertRules(ctx context.Context, orgID int64) ([]*alerting_models.AlertRule, map[string]alerting_models.Provenance, error) GetAlertRules(ctx context.Context, user identity.Requester) ([]*alerting_models.AlertRule, map[string]alerting_models.Provenance, error)
GetAlertRule(ctx context.Context, orgID int64, ruleUID string) (alerting_models.AlertRule, alerting_models.Provenance, error) GetAlertRule(ctx context.Context, user identity.Requester, ruleUID string) (alerting_models.AlertRule, alerting_models.Provenance, error)
CreateAlertRule(ctx context.Context, user identity.Requester, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error) CreateAlertRule(ctx context.Context, user identity.Requester, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error)
UpdateAlertRule(ctx context.Context, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error) UpdateAlertRule(ctx context.Context, user identity.Requester, rule alerting_models.AlertRule, provenance alerting_models.Provenance) (alerting_models.AlertRule, error)
DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance alerting_models.Provenance) error DeleteAlertRule(ctx context.Context, user identity.Requester, ruleUID string, provenance alerting_models.Provenance) error
GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (alerting_models.AlertRuleGroup, error) GetRuleGroup(ctx context.Context, user identity.Requester, folder, group string) (alerting_models.AlertRuleGroup, error)
ReplaceRuleGroup(ctx context.Context, orgID int64, group alerting_models.AlertRuleGroup, userID int64, provenance alerting_models.Provenance) error ReplaceRuleGroup(ctx context.Context, user identity.Requester, group alerting_models.AlertRuleGroup, provenance alerting_models.Provenance) error
DeleteRuleGroup(ctx context.Context, orgID int64, folder, group string, provenance alerting_models.Provenance) error DeleteRuleGroup(ctx context.Context, user identity.Requester, folder, group string, provenance alerting_models.Provenance) error
GetAlertRuleWithFolderTitle(ctx context.Context, orgID int64, ruleUID string) (provisioning.AlertRuleWithFolderTitle, error) GetAlertRuleWithFolderTitle(ctx context.Context, user identity.Requester, ruleUID string) (provisioning.AlertRuleWithFolderTitle, error)
GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, folder, group string) (alerting_models.AlertRuleGroupWithFolderTitle, error) GetAlertRuleGroupWithFolderTitle(ctx context.Context, user identity.Requester, folder, group string) (alerting_models.AlertRuleGroupWithFolderTitle, error)
GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64, folderUIDs []string) ([]alerting_models.AlertRuleGroupWithFolderTitle, error) GetAlertGroupsWithFolderTitle(ctx context.Context, user identity.Requester, folderUIDs []string) ([]alerting_models.AlertRuleGroupWithFolderTitle, error)
} }
func (srv *ProvisioningSrv) RouteGetPolicyTree(c *contextmodel.ReqContext) response.Response { func (srv *ProvisioningSrv) RouteGetPolicyTree(c *contextmodel.ReqContext) response.Response {
@@ -309,7 +309,7 @@ func (srv *ProvisioningSrv) RouteDeleteMuteTiming(c *contextmodel.ReqContext, na
} }
func (srv *ProvisioningSrv) RouteGetAlertRules(c *contextmodel.ReqContext) response.Response { func (srv *ProvisioningSrv) RouteGetAlertRules(c *contextmodel.ReqContext) response.Response {
rules, provenances, err := srv.alertRules.GetAlertRules(c.Req.Context(), c.SignedInUser.GetOrgID()) rules, provenances, err := srv.alertRules.GetAlertRules(c.Req.Context(), c.SignedInUser)
if err != nil { if err != nil {
return ErrResp(http.StatusInternalServerError, err, "") return ErrResp(http.StatusInternalServerError, err, "")
} }
@@ -317,7 +317,7 @@ func (srv *ProvisioningSrv) RouteGetAlertRules(c *contextmodel.ReqContext) respo
} }
func (srv *ProvisioningSrv) RouteRouteGetAlertRule(c *contextmodel.ReqContext, UID string) response.Response { func (srv *ProvisioningSrv) RouteRouteGetAlertRule(c *contextmodel.ReqContext, UID string) response.Response {
rule, provenace, err := srv.alertRules.GetAlertRule(c.Req.Context(), c.SignedInUser.GetOrgID(), UID) rule, provenace, err := srv.alertRules.GetAlertRule(c.Req.Context(), c.SignedInUser, UID)
if err != nil { if err != nil {
if errors.Is(err, alerting_models.ErrAlertRuleNotFound) { if errors.Is(err, alerting_models.ErrAlertRuleNotFound) {
return response.Empty(http.StatusNotFound) return response.Empty(http.StatusNotFound)
@@ -363,7 +363,7 @@ func (srv *ProvisioningSrv) RoutePutAlertRule(c *contextmodel.ReqContext, ar def
updated.OrgID = c.SignedInUser.GetOrgID() updated.OrgID = c.SignedInUser.GetOrgID()
updated.UID = UID updated.UID = UID
provenance := determineProvenance(c) provenance := determineProvenance(c)
updatedAlertRule, err := srv.alertRules.UpdateAlertRule(c.Req.Context(), updated, alerting_models.Provenance(provenance)) updatedAlertRule, err := srv.alertRules.UpdateAlertRule(c.Req.Context(), c.SignedInUser, updated, alerting_models.Provenance(provenance))
if errors.Is(err, alerting_models.ErrAlertRuleUniqueConstraintViolation) { if errors.Is(err, alerting_models.ErrAlertRuleUniqueConstraintViolation) {
return ErrResp(http.StatusBadRequest, err, "") return ErrResp(http.StatusBadRequest, err, "")
} }
@@ -386,7 +386,7 @@ func (srv *ProvisioningSrv) RoutePutAlertRule(c *contextmodel.ReqContext, ar def
func (srv *ProvisioningSrv) RouteDeleteAlertRule(c *contextmodel.ReqContext, UID string) response.Response { func (srv *ProvisioningSrv) RouteDeleteAlertRule(c *contextmodel.ReqContext, UID string) response.Response {
provenance := determineProvenance(c) provenance := determineProvenance(c)
err := srv.alertRules.DeleteAlertRule(c.Req.Context(), c.SignedInUser.GetOrgID(), UID, alerting_models.Provenance(provenance)) err := srv.alertRules.DeleteAlertRule(c.Req.Context(), c.SignedInUser, UID, alerting_models.Provenance(provenance))
if err != nil { if err != nil {
return ErrResp(http.StatusInternalServerError, err, "") return ErrResp(http.StatusInternalServerError, err, "")
} }
@@ -394,7 +394,7 @@ func (srv *ProvisioningSrv) RouteDeleteAlertRule(c *contextmodel.ReqContext, UID
} }
func (srv *ProvisioningSrv) RouteGetAlertRuleGroup(c *contextmodel.ReqContext, folder string, group string) response.Response { func (srv *ProvisioningSrv) RouteGetAlertRuleGroup(c *contextmodel.ReqContext, folder string, group string) response.Response {
g, err := srv.alertRules.GetRuleGroup(c.Req.Context(), c.SignedInUser.GetOrgID(), folder, group) g, err := srv.alertRules.GetRuleGroup(c.Req.Context(), c.SignedInUser, folder, group)
if err != nil { if err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "", err) return response.ErrOrFallback(http.StatusInternalServerError, "", err)
} }
@@ -422,7 +422,7 @@ func (srv *ProvisioningSrv) RouteGetAlertRulesExport(c *contextmodel.ReqContext)
return srv.RouteGetAlertRuleGroupExport(c, folderUIDs[0], group) return srv.RouteGetAlertRuleGroupExport(c, folderUIDs[0], group)
} }
groupsWithTitle, err := srv.alertRules.GetAlertGroupsWithFolderTitle(c.Req.Context(), c.SignedInUser.GetOrgID(), folderUIDs) groupsWithTitle, err := srv.alertRules.GetAlertGroupsWithFolderTitle(c.Req.Context(), c.SignedInUser, folderUIDs)
if err != nil { if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules") return ErrResp(http.StatusInternalServerError, err, "failed to get alert rules")
} }
@@ -440,7 +440,7 @@ func (srv *ProvisioningSrv) RouteGetAlertRulesExport(c *contextmodel.ReqContext)
// RouteGetAlertRuleGroupExport retrieves the given alert rule group in a format compatible with file provisioning. // RouteGetAlertRuleGroupExport retrieves the given alert rule group in a format compatible with file provisioning.
func (srv *ProvisioningSrv) RouteGetAlertRuleGroupExport(c *contextmodel.ReqContext, folder string, group string) response.Response { func (srv *ProvisioningSrv) RouteGetAlertRuleGroupExport(c *contextmodel.ReqContext, folder string, group string) response.Response {
g, err := srv.alertRules.GetAlertRuleGroupWithFolderTitle(c.Req.Context(), c.SignedInUser.GetOrgID(), folder, group) g, err := srv.alertRules.GetAlertRuleGroupWithFolderTitle(c.Req.Context(), c.SignedInUser, folder, group)
if err != nil { if err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get alert rule group", err) return response.ErrOrFallback(http.StatusInternalServerError, "failed to get alert rule group", err)
} }
@@ -455,7 +455,7 @@ func (srv *ProvisioningSrv) RouteGetAlertRuleGroupExport(c *contextmodel.ReqCont
// RouteGetAlertRuleExport retrieves the given alert rule in a format compatible with file provisioning. // RouteGetAlertRuleExport retrieves the given alert rule in a format compatible with file provisioning.
func (srv *ProvisioningSrv) RouteGetAlertRuleExport(c *contextmodel.ReqContext, UID string) response.Response { func (srv *ProvisioningSrv) RouteGetAlertRuleExport(c *contextmodel.ReqContext, UID string) response.Response {
rule, err := srv.alertRules.GetAlertRuleWithFolderTitle(c.Req.Context(), c.SignedInUser.GetOrgID(), UID) rule, err := srv.alertRules.GetAlertRuleWithFolderTitle(c.Req.Context(), c.SignedInUser, UID)
if err != nil { if err != nil {
if errors.Is(err, alerting_models.ErrAlertRuleNotFound) { if errors.Is(err, alerting_models.ErrAlertRuleNotFound) {
return ErrResp(http.StatusNotFound, err, "") return ErrResp(http.StatusNotFound, err, "")
@@ -481,9 +481,7 @@ func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *contextmodel.ReqContext, a
ErrResp(http.StatusBadRequest, err, "") ErrResp(http.StatusBadRequest, err, "")
} }
provenance := determineProvenance(c) provenance := determineProvenance(c)
err = srv.alertRules.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser, groupModel, alerting_models.Provenance(provenance))
userID, _ := identity.UserIdentifier(c.SignedInUser.GetNamespacedID())
err = srv.alertRules.ReplaceRuleGroup(c.Req.Context(), c.SignedInUser.GetOrgID(), groupModel, userID, alerting_models.Provenance(provenance))
if errors.Is(err, alerting_models.ErrAlertRuleUniqueConstraintViolation) { if errors.Is(err, alerting_models.ErrAlertRuleUniqueConstraintViolation) {
return ErrResp(http.StatusBadRequest, err, "") return ErrResp(http.StatusBadRequest, err, "")
} }
@@ -501,7 +499,7 @@ func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *contextmodel.ReqContext, a
func (srv *ProvisioningSrv) RouteDeleteAlertRuleGroup(c *contextmodel.ReqContext, folderUID string, group string) response.Response { func (srv *ProvisioningSrv) RouteDeleteAlertRuleGroup(c *contextmodel.ReqContext, folderUID string, group string) response.Response {
provenance := determineProvenance(c) provenance := determineProvenance(c)
err := srv.alertRules.DeleteRuleGroup(c.Req.Context(), c.SignedInUser.GetOrgID(), folderUID, group, alerting_models.Provenance(provenance)) err := srv.alertRules.DeleteRuleGroup(c.Req.Context(), c.SignedInUser, folderUID, group, alerting_models.Provenance(provenance))
if err != nil { if err != nil {
return response.ErrOrFallback(http.StatusInternalServerError, "", err) return response.ErrOrFallback(http.StatusInternalServerError, "", err)
} }
@@ -62,9 +62,9 @@ func NewAlertRuleService(ruleStore RuleStore,
} }
} }
func (service *AlertRuleService) GetAlertRules(ctx context.Context, orgID int64) ([]*models.AlertRule, map[string]models.Provenance, error) { func (service *AlertRuleService) GetAlertRules(ctx context.Context, user identity.Requester) ([]*models.AlertRule, map[string]models.Provenance, error) {
q := models.ListAlertRulesQuery{ q := models.ListAlertRulesQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
} }
rules, err := service.ruleStore.ListAlertRules(ctx, &q) rules, err := service.ruleStore.ListAlertRules(ctx, &q)
if err != nil { if err != nil {
@@ -73,7 +73,7 @@ func (service *AlertRuleService) GetAlertRules(ctx context.Context, orgID int64)
provenances := make(map[string]models.Provenance) provenances := make(map[string]models.Provenance)
if len(rules) > 0 { if len(rules) > 0 {
resourceType := rules[0].ResourceType() resourceType := rules[0].ResourceType()
provenances, err = service.provenanceStore.GetProvenances(ctx, orgID, resourceType) provenances, err = service.provenanceStore.GetProvenances(ctx, user.GetOrgID(), resourceType)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -81,16 +81,16 @@ func (service *AlertRuleService) GetAlertRules(ctx context.Context, orgID int64)
return rules, provenances, nil return rules, provenances, nil
} }
func (service *AlertRuleService) GetAlertRule(ctx context.Context, orgID int64, ruleUID string) (models.AlertRule, models.Provenance, error) { func (service *AlertRuleService) GetAlertRule(ctx context.Context, user identity.Requester, ruleUID string) (models.AlertRule, models.Provenance, error) {
query := &models.GetAlertRuleByUIDQuery{ query := &models.GetAlertRuleByUIDQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
UID: ruleUID, UID: ruleUID,
} }
rule, err := service.ruleStore.GetAlertRuleByUID(ctx, query) rule, err := service.ruleStore.GetAlertRuleByUID(ctx, query)
if err != nil { if err != nil {
return models.AlertRule{}, models.ProvenanceNone, err return models.AlertRule{}, models.ProvenanceNone, err
} }
provenance, err := service.provenanceStore.GetProvenance(ctx, rule, orgID) provenance, err := service.provenanceStore.GetProvenance(ctx, rule, user.GetOrgID())
if err != nil { if err != nil {
return models.AlertRule{}, models.ProvenanceNone, err return models.AlertRule{}, models.ProvenanceNone, err
} }
@@ -103,9 +103,9 @@ type AlertRuleWithFolderTitle struct {
} }
// GetAlertRuleWithFolderTitle returns a single alert rule with its folder title. // GetAlertRuleWithFolderTitle returns a single alert rule with its folder title.
func (service *AlertRuleService) GetAlertRuleWithFolderTitle(ctx context.Context, orgID int64, ruleUID string) (AlertRuleWithFolderTitle, error) { func (service *AlertRuleService) GetAlertRuleWithFolderTitle(ctx context.Context, user identity.Requester, ruleUID string) (AlertRuleWithFolderTitle, error) {
query := &models.GetAlertRuleByUIDQuery{ query := &models.GetAlertRuleByUIDQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
UID: ruleUID, UID: ruleUID,
} }
rule, err := service.ruleStore.GetAlertRuleByUID(ctx, query) rule, err := service.ruleStore.GetAlertRuleByUID(ctx, query)
@@ -114,7 +114,7 @@ func (service *AlertRuleService) GetAlertRuleWithFolderTitle(ctx context.Context
} }
dq := dashboards.GetDashboardQuery{ dq := dashboards.GetDashboardQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
UID: rule.NamespaceUID, UID: rule.NamespaceUID,
} }
@@ -184,12 +184,7 @@ func (service *AlertRuleService) CreateAlertRule(ctx context.Context, user ident
return errors.New("couldn't find newly created id") return errors.New("couldn't find newly created id")
} }
// default to 0 if there is no user if err = service.checkLimitsTransactionCtx(ctx, user); err != nil {
userID := int64(0)
if user != nil {
userID, _ = identity.UserIdentifier(user.GetNamespacedID())
}
if err = service.checkLimitsTransactionCtx(ctx, rule.OrgID, userID); err != nil {
return err return err
} }
@@ -201,9 +196,9 @@ func (service *AlertRuleService) CreateAlertRule(ctx context.Context, user ident
return rule, nil return rule, nil
} }
func (service *AlertRuleService) GetRuleGroup(ctx context.Context, orgID int64, namespaceUID, group string) (models.AlertRuleGroup, error) { func (service *AlertRuleService) GetRuleGroup(ctx context.Context, user identity.Requester, namespaceUID, group string) (models.AlertRuleGroup, error) {
q := models.ListAlertRulesQuery{ q := models.ListAlertRulesQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
NamespaceUIDs: []string{namespaceUID}, NamespaceUIDs: []string{namespaceUID},
RuleGroup: group, RuleGroup: group,
} }
@@ -229,13 +224,13 @@ func (service *AlertRuleService) GetRuleGroup(ctx context.Context, orgID int64,
} }
// UpdateRuleGroup will update the interval for all rules in the group. // UpdateRuleGroup will update the interval for all rules in the group.
func (service *AlertRuleService) UpdateRuleGroup(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string, intervalSeconds int64) error { func (service *AlertRuleService) UpdateRuleGroup(ctx context.Context, user identity.Requester, namespaceUID string, ruleGroup string, intervalSeconds int64) error {
if err := models.ValidateRuleGroupInterval(intervalSeconds, service.baseIntervalSeconds); err != nil { if err := models.ValidateRuleGroupInterval(intervalSeconds, service.baseIntervalSeconds); err != nil {
return err return err
} }
return service.xact.InTransaction(ctx, func(ctx context.Context) error { return service.xact.InTransaction(ctx, func(ctx context.Context) error {
query := &models.ListAlertRulesQuery{ query := &models.ListAlertRulesQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
NamespaceUIDs: []string{namespaceUID}, NamespaceUIDs: []string{namespaceUID},
RuleGroup: ruleGroup, RuleGroup: ruleGroup,
} }
@@ -259,12 +254,12 @@ func (service *AlertRuleService) UpdateRuleGroup(ctx context.Context, orgID int6
}) })
} }
func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, orgID int64, group models.AlertRuleGroup, userID int64, provenance models.Provenance) error { func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, user identity.Requester, group models.AlertRuleGroup, provenance models.Provenance) error {
if err := models.ValidateRuleGroupInterval(group.Interval, service.baseIntervalSeconds); err != nil { if err := models.ValidateRuleGroupInterval(group.Interval, service.baseIntervalSeconds); err != nil {
return err return err
} }
delta, err := service.calcDelta(ctx, orgID, group) delta, err := service.calcDelta(ctx, user, group)
if err != nil { if err != nil {
return err return err
} }
@@ -286,13 +281,13 @@ func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, orgID int
} }
} }
return service.persistDelta(ctx, orgID, delta, userID, provenance) return service.persistDelta(ctx, user, delta, provenance)
} }
func (service *AlertRuleService) DeleteRuleGroup(ctx context.Context, orgID int64, namespaceUID, group string, provenance models.Provenance) error { func (service *AlertRuleService) DeleteRuleGroup(ctx context.Context, user identity.Requester, namespaceUID, group string, provenance models.Provenance) error {
// List all rules in the group. // List all rules in the group.
q := models.ListAlertRulesQuery{ q := models.ListAlertRulesQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
NamespaceUIDs: []string{namespaceUID}, NamespaceUIDs: []string{namespaceUID},
RuleGroup: group, RuleGroup: group,
} }
@@ -317,16 +312,16 @@ func (service *AlertRuleService) DeleteRuleGroup(ctx context.Context, orgID int6
// Delete all rules. // Delete all rules.
return service.xact.InTransaction(ctx, func(ctx context.Context) error { return service.xact.InTransaction(ctx, func(ctx context.Context) error {
return service.deleteRules(ctx, orgID, ruleList...) return service.deleteRules(ctx, user.GetOrgID(), ruleList...)
}) })
} }
func (service *AlertRuleService) calcDelta(ctx context.Context, orgID int64, group models.AlertRuleGroup) (*store.GroupDelta, error) { func (service *AlertRuleService) calcDelta(ctx context.Context, user identity.Requester, group models.AlertRuleGroup) (*store.GroupDelta, error) {
// If the provided request did not provide the rules list at all, treat it as though it does not wish to change rules. // If the provided request did not provide the rules list at all, treat it as though it does not wish to change rules.
// This is done for backwards compatibility. Requests which specify only the interval must update only the interval. // This is done for backwards compatibility. Requests which specify only the interval must update only the interval.
if group.Rules == nil { if group.Rules == nil {
listRulesQuery := models.ListAlertRulesQuery{ listRulesQuery := models.ListAlertRulesQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
NamespaceUIDs: []string{group.FolderUID}, NamespaceUIDs: []string{group.FolderUID},
RuleGroup: group.Title, RuleGroup: group.Title,
} }
@@ -347,12 +342,12 @@ func (service *AlertRuleService) calcDelta(ctx context.Context, orgID int64, gro
} }
key := models.AlertRuleGroupKey{ key := models.AlertRuleGroupKey{
OrgID: orgID, OrgID: user.GetOrgID(),
NamespaceUID: group.FolderUID, NamespaceUID: group.FolderUID,
RuleGroup: group.Title, RuleGroup: group.Title,
} }
rules := make([]*models.AlertRuleWithOptionals, len(group.Rules)) rules := make([]*models.AlertRuleWithOptionals, len(group.Rules))
group = *syncGroupRuleFields(&group, orgID) group = *syncGroupRuleFields(&group, user.GetOrgID())
for i := range group.Rules { for i := range group.Rules {
if err := group.Rules[i].SetDashboardAndPanelFromAnnotations(); err != nil { if err := group.Rules[i].SetDashboardAndPanelFromAnnotations(); err != nil {
return nil, err return nil, err
@@ -368,13 +363,13 @@ func (service *AlertRuleService) calcDelta(ctx context.Context, orgID int64, gro
return store.UpdateCalculatedRuleFields(delta), nil return store.UpdateCalculatedRuleFields(delta), nil
} }
func (service *AlertRuleService) persistDelta(ctx context.Context, orgID int64, delta *store.GroupDelta, userID int64, provenance models.Provenance) error { func (service *AlertRuleService) persistDelta(ctx context.Context, user identity.Requester, delta *store.GroupDelta, provenance models.Provenance) error {
return service.xact.InTransaction(ctx, func(ctx context.Context) error { return service.xact.InTransaction(ctx, func(ctx context.Context) error {
// Delete first as this could prevent future unique constraint violations. // Delete first as this could prevent future unique constraint violations.
if len(delta.Delete) > 0 { if len(delta.Delete) > 0 {
for _, del := range delta.Delete { for _, del := range delta.Delete {
// check that provenance is not changed in an invalid way // check that provenance is not changed in an invalid way
storedProvenance, err := service.provenanceStore.GetProvenance(ctx, del, orgID) storedProvenance, err := service.provenanceStore.GetProvenance(ctx, del, user.GetOrgID())
if err != nil { if err != nil {
return err return err
} }
@@ -382,7 +377,7 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, orgID int64,
return fmt.Errorf("cannot update with provided provenance '%s', needs '%s'", provenance, storedProvenance) return fmt.Errorf("cannot update with provided provenance '%s', needs '%s'", provenance, storedProvenance)
} }
} }
if err := service.deleteRules(ctx, orgID, delta.Delete...); err != nil { if err := service.deleteRules(ctx, user.GetOrgID(), delta.Delete...); err != nil {
return err return err
} }
} }
@@ -391,7 +386,7 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, orgID int64,
updates := make([]models.UpdateRule, 0, len(delta.Update)) updates := make([]models.UpdateRule, 0, len(delta.Update))
for _, update := range delta.Update { for _, update := range delta.Update {
// check that provenance is not changed in an invalid way // check that provenance is not changed in an invalid way
storedProvenance, err := service.provenanceStore.GetProvenance(ctx, update.New, orgID) storedProvenance, err := service.provenanceStore.GetProvenance(ctx, update.New, user.GetOrgID())
if err != nil { if err != nil {
return err return err
} }
@@ -407,7 +402,7 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, orgID int64,
return fmt.Errorf("failed to update alert rules: %w", err) return fmt.Errorf("failed to update alert rules: %w", err)
} }
for _, update := range delta.Update { for _, update := range delta.Update {
if err := service.provenanceStore.SetProvenance(ctx, update.New, orgID, provenance); err != nil { if err := service.provenanceStore.SetProvenance(ctx, update.New, user.GetOrgID(), provenance); err != nil {
return err return err
} }
} }
@@ -419,13 +414,13 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, orgID int64,
return fmt.Errorf("failed to insert alert rules: %w", err) return fmt.Errorf("failed to insert alert rules: %w", err)
} }
for _, key := range uids { for _, key := range uids {
if err := service.provenanceStore.SetProvenance(ctx, &models.AlertRule{UID: key.UID}, orgID, provenance); err != nil { if err := service.provenanceStore.SetProvenance(ctx, &models.AlertRule{UID: key.UID}, user.GetOrgID(), provenance); err != nil {
return err return err
} }
} }
} }
if err := service.checkLimitsTransactionCtx(ctx, orgID, userID); err != nil { if err := service.checkLimitsTransactionCtx(ctx, user); err != nil {
return err return err
} }
@@ -434,8 +429,8 @@ func (service *AlertRuleService) persistDelta(ctx context.Context, orgID int64,
} }
// UpdateAlertRule updates an alert rule. // UpdateAlertRule updates an alert rule.
func (service *AlertRuleService) UpdateAlertRule(ctx context.Context, rule models.AlertRule, provenance models.Provenance) (models.AlertRule, error) { func (service *AlertRuleService) UpdateAlertRule(ctx context.Context, user identity.Requester, rule models.AlertRule, provenance models.Provenance) (models.AlertRule, error) {
storedRule, storedProvenance, err := service.GetAlertRule(ctx, rule.OrgID, rule.UID) storedRule, storedProvenance, err := service.GetAlertRule(ctx, user, rule.UID)
if err != nil { if err != nil {
return models.AlertRule{}, err return models.AlertRule{}, err
} }
@@ -478,9 +473,9 @@ func (service *AlertRuleService) UpdateAlertRule(ctx context.Context, rule model
return rule, err return rule, err
} }
func (service *AlertRuleService) DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance models.Provenance) error { func (service *AlertRuleService) DeleteAlertRule(ctx context.Context, user identity.Requester, ruleUID string, provenance models.Provenance) error {
rule := &models.AlertRule{ rule := &models.AlertRule{
OrgID: orgID, OrgID: user.GetOrgID(),
UID: ruleUID, UID: ruleUID,
} }
// check that provenance is not changed in an invalid way // check that provenance is not changed in an invalid way
@@ -492,14 +487,22 @@ func (service *AlertRuleService) DeleteAlertRule(ctx context.Context, orgID int6
return fmt.Errorf("cannot delete with provided provenance '%s', needs '%s'", provenance, storedProvenance) return fmt.Errorf("cannot delete with provided provenance '%s', needs '%s'", provenance, storedProvenance)
} }
return service.xact.InTransaction(ctx, func(ctx context.Context) error { return service.xact.InTransaction(ctx, func(ctx context.Context) error {
return service.deleteRules(ctx, orgID, rule) return service.deleteRules(ctx, user.GetOrgID(), rule)
}) })
} }
// checkLimitsTransactionCtx checks whether the current transaction (as identified by the ctx) breaches configured alert rule limits. // checkLimitsTransactionCtx checks whether the current transaction (as identified by the ctx) breaches configured alert rule limits.
func (service *AlertRuleService) checkLimitsTransactionCtx(ctx context.Context, orgID, userID int64) error { func (service *AlertRuleService) checkLimitsTransactionCtx(ctx context.Context, user identity.Requester) error {
// default to 0 if there is no user
userID := int64(0)
u, err := identity.UserIdentifier(user.GetNamespacedID())
if err != nil {
return fmt.Errorf("failed to check alert rule quota: %w", err)
}
userID = u
limitReached, err := service.quotas.CheckQuotaReached(ctx, models.QuotaTargetSrv, &quota.ScopeParameters{ limitReached, err := service.quotas.CheckQuotaReached(ctx, models.QuotaTargetSrv, &quota.ScopeParameters{
OrgID: orgID, OrgID: user.GetOrgID(),
UserID: userID, UserID: userID,
}) })
if err != nil { if err != nil {
@@ -532,9 +535,9 @@ func (service *AlertRuleService) deleteRules(ctx context.Context, orgID int64, t
} }
// GetAlertRuleGroupWithFolderTitle returns the alert rule group with folder title. // GetAlertRuleGroupWithFolderTitle returns the alert rule group with folder title.
func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, namespaceUID, group string) (models.AlertRuleGroupWithFolderTitle, error) { func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Context, user identity.Requester, namespaceUID, group string) (models.AlertRuleGroupWithFolderTitle, error) {
q := models.ListAlertRulesQuery{ q := models.ListAlertRulesQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
NamespaceUIDs: []string{namespaceUID}, NamespaceUIDs: []string{namespaceUID},
RuleGroup: group, RuleGroup: group,
} }
@@ -547,7 +550,7 @@ func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Co
} }
dq := dashboards.GetDashboardQuery{ dq := dashboards.GetDashboardQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
UID: namespaceUID, UID: namespaceUID,
} }
dash, err := service.dashboardService.GetDashboard(ctx, &dq) dash, err := service.dashboardService.GetDashboard(ctx, &dq)
@@ -560,9 +563,9 @@ func (service *AlertRuleService) GetAlertRuleGroupWithFolderTitle(ctx context.Co
} }
// GetAlertGroupsWithFolderTitle returns all groups with folder title in the folders identified by folderUID that have at least one alert. If argument folderUIDs is nil or empty - returns groups in all folders. // GetAlertGroupsWithFolderTitle returns all groups with folder title in the folders identified by folderUID that have at least one alert. If argument folderUIDs is nil or empty - returns groups in all folders.
func (service *AlertRuleService) GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64, folderUIDs []string) ([]models.AlertRuleGroupWithFolderTitle, error) { func (service *AlertRuleService) GetAlertGroupsWithFolderTitle(ctx context.Context, user identity.Requester, folderUIDs []string) ([]models.AlertRuleGroupWithFolderTitle, error) {
q := models.ListAlertRulesQuery{ q := models.ListAlertRulesQuery{
OrgID: orgID, OrgID: user.GetOrgID(),
} }
if len(folderUIDs) > 0 { if len(folderUIDs) > 0 {
@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/expr" "github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/infra/db" "github.com/grafana/grafana/pkg/infra/db"
@@ -25,17 +26,21 @@ import (
func TestAlertRuleService(t *testing.T) { func TestAlertRuleService(t *testing.T) {
ruleService := createAlertRuleService(t) ruleService := createAlertRuleService(t)
var orgID int64 = 1 var orgID int64 = 1
u := &user.SignedInUser{
UserID: 1,
OrgID: orgID,
}
t.Run("group creation should set the right provenance", func(t *testing.T) { t.Run("group creation should set the right provenance", func(t *testing.T) {
group := createDummyGroup("group-test-1", orgID) group := createDummyGroup("group-test-1", orgID)
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "group-test-1") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-1")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
for _, rule := range readGroup.Rules { for _, rule := range readGroup.Rules {
_, provenance, err := ruleService.GetAlertRule(context.Background(), orgID, rule.UID) _, provenance, err := ruleService.GetAlertRule(context.Background(), u, rule.UID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, models.ProvenanceAPI, provenance) require.Equal(t, models.ProvenanceAPI, provenance)
} }
@@ -44,15 +49,15 @@ func TestAlertRuleService(t *testing.T) {
t.Run("alert rule group should be updated correctly", func(t *testing.T) { t.Run("alert rule group should be updated correctly", func(t *testing.T) {
rule := dummyRule("test#3", orgID) rule := dummyRule("test#3", orgID)
rule.RuleGroup = "a" rule.RuleGroup = "a"
rule, err := ruleService.CreateAlertRule(context.Background(), nil, rule, models.ProvenanceNone) rule, err := ruleService.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(60), rule.IntervalSeconds) require.Equal(t, int64(60), rule.IntervalSeconds)
var interval int64 = 120 var interval int64 = 120
err = ruleService.UpdateRuleGroup(context.Background(), orgID, rule.NamespaceUID, rule.RuleGroup, 120) err = ruleService.UpdateRuleGroup(context.Background(), u, rule.NamespaceUID, rule.RuleGroup, 120)
require.NoError(t, err) require.NoError(t, err)
rule, _, err = ruleService.GetAlertRule(context.Background(), orgID, rule.UID) rule, _, err = ruleService.GetAlertRule(context.Background(), u, rule.UID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, interval, rule.IntervalSeconds) require.Equal(t, interval, rule.IntervalSeconds)
}) })
@@ -61,24 +66,24 @@ func TestAlertRuleService(t *testing.T) {
var orgID int64 = 2 var orgID int64 = 2
rule := dummyRule("test#1", orgID) rule := dummyRule("test#1", orgID)
rule.NamespaceUID = "123abc" rule.NamespaceUID = "123abc"
rule, err := ruleService.CreateAlertRule(context.Background(), nil, rule, models.ProvenanceNone) u := &user.SignedInUser{OrgID: orgID}
rule, err := ruleService.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.NoError(t, err) require.NoError(t, err)
rule.NamespaceUID = "abc123" rule.NamespaceUID = "abc123"
_, err = ruleService.UpdateAlertRule(context.Background(), _, err = ruleService.UpdateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
rule, models.ProvenanceNone)
require.NoError(t, err) require.NoError(t, err)
}) })
t.Run("group update should propagate folderUID from group to rules", func(t *testing.T) { t.Run("group update should propagate folderUID from group to rules", func(t *testing.T) {
ruleService := createAlertRuleService(t) ruleService := createAlertRuleService(t)
group := createDummyGroup("namespace-test", 1) group := createDummyGroup("namespace-test", orgID)
group.Rules[0].NamespaceUID = "" group.Rules[0].NamespaceUID = ""
err := ruleService.ReplaceRuleGroup(context.Background(), 1, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "namespace-test") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "namespace-test")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
require.Equal(t, "my-namespace", readGroup.Rules[0].NamespaceUID) require.Equal(t, "my-namespace", readGroup.Rules[0].NamespaceUID)
@@ -88,10 +93,10 @@ func TestAlertRuleService(t *testing.T) {
group := createDummyGroup("group-test-3", orgID) group := createDummyGroup("group-test-3", orgID)
group.Rules[0].RuleGroup = "something different" group.Rules[0].RuleGroup = "something different"
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "group-test-3") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-3")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
for _, rule := range readGroup.Rules { for _, rule := range readGroup.Rules {
@@ -102,16 +107,16 @@ func TestAlertRuleService(t *testing.T) {
t.Run("alert rule should get interval from existing rule group", func(t *testing.T) { t.Run("alert rule should get interval from existing rule group", func(t *testing.T) {
rule := dummyRule("test#4", orgID) rule := dummyRule("test#4", orgID)
rule.RuleGroup = "b" rule.RuleGroup = "b"
rule, err := ruleService.CreateAlertRule(context.Background(), nil, rule, models.ProvenanceNone) rule, err := ruleService.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.NoError(t, err) require.NoError(t, err)
var interval int64 = 120 var interval int64 = 120
err = ruleService.UpdateRuleGroup(context.Background(), orgID, rule.NamespaceUID, rule.RuleGroup, 120) err = ruleService.UpdateRuleGroup(context.Background(), u, rule.NamespaceUID, rule.RuleGroup, 120)
require.NoError(t, err) require.NoError(t, err)
rule = dummyRule("test#4-1", orgID) rule = dummyRule("test#4-1", orgID)
rule.RuleGroup = "b" rule.RuleGroup = "b"
rule, err = ruleService.CreateAlertRule(context.Background(), nil, rule, models.ProvenanceNone) rule, err = ruleService.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, interval, rule.IntervalSeconds) require.Equal(t, interval, rule.IntervalSeconds)
}) })
@@ -124,22 +129,23 @@ func TestAlertRuleService(t *testing.T) {
ruleGroup = "abc" ruleGroup = "abc"
newInterval int64 = 120 newInterval int64 = 120
) )
u := &user.SignedInUser{OrgID: orgID}
rule := dummyRule("my_rule", orgID) rule := dummyRule("my_rule", orgID)
rule.UID = ruleUID rule.UID = ruleUID
rule.RuleGroup = ruleGroup rule.RuleGroup = ruleGroup
rule.NamespaceUID = namespaceUID rule.NamespaceUID = namespaceUID
_, err := ruleService.CreateAlertRule(context.Background(), nil, rule, models.ProvenanceNone) _, err := ruleService.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.NoError(t, err) require.NoError(t, err)
rule, _, err = ruleService.GetAlertRule(context.Background(), orgID, ruleUID) rule, _, err = ruleService.GetAlertRule(context.Background(), u, ruleUID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(1), rule.Version) require.Equal(t, int64(1), rule.Version)
require.Equal(t, int64(60), rule.IntervalSeconds) require.Equal(t, int64(60), rule.IntervalSeconds)
err = ruleService.UpdateRuleGroup(context.Background(), orgID, namespaceUID, ruleGroup, newInterval) err = ruleService.UpdateRuleGroup(context.Background(), u, namespaceUID, ruleGroup, newInterval)
require.NoError(t, err) require.NoError(t, err)
rule, _, err = ruleService.GetAlertRule(context.Background(), orgID, ruleUID) rule, _, err = ruleService.GetAlertRule(context.Background(), u, ruleUID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, int64(2), rule.Version) require.Equal(t, int64(2), rule.Version)
require.Equal(t, newInterval, rule.IntervalSeconds) require.Equal(t, newInterval, rule.IntervalSeconds)
@@ -147,16 +153,16 @@ func TestAlertRuleService(t *testing.T) {
t.Run("updating a group by updating a rule should bump that rule's data and version number", func(t *testing.T) { t.Run("updating a group by updating a rule should bump that rule's data and version number", func(t *testing.T) {
group := createDummyGroup("group-test-5", orgID) group := createDummyGroup("group-test-5", orgID)
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
updatedGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "group-test-5") updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-5")
require.NoError(t, err) require.NoError(t, err)
updatedGroup.Rules[0].Title = "some-other-title-asdf" updatedGroup.Rules[0].Title = "some-other-title-asdf"
err = ruleService.ReplaceRuleGroup(context.Background(), orgID, updatedGroup, 0, models.ProvenanceAPI) err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "group-test-5") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-5")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
require.Len(t, readGroup.Rules, 1) require.Len(t, readGroup.Rules, 1)
@@ -175,17 +181,17 @@ func TestAlertRuleService(t *testing.T) {
dummyRule("overlap-test-rule-2", orgID), dummyRule("overlap-test-rule-2", orgID),
}, },
} }
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
updatedGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "overlap-test") updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "overlap-test")
require.NoError(t, err) require.NoError(t, err)
updatedGroup.Rules[0].Title = "overlap-test-rule-2" updatedGroup.Rules[0].Title = "overlap-test-rule-2"
updatedGroup.Rules[1].Title = "overlap-test-rule-3" updatedGroup.Rules[1].Title = "overlap-test-rule-3"
err = ruleService.ReplaceRuleGroup(context.Background(), orgID, updatedGroup, 0, models.ProvenanceAPI) err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "overlap-test") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "overlap-test")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
require.Len(t, readGroup.Rules, 2) require.Len(t, readGroup.Rules, 2)
@@ -206,17 +212,17 @@ func TestAlertRuleService(t *testing.T) {
dummyRule("swap-test-rule-2", orgID), dummyRule("swap-test-rule-2", orgID),
}, },
} }
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
updatedGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "swap-test") updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "swap-test")
require.NoError(t, err) require.NoError(t, err)
updatedGroup.Rules[0].Title = "swap-test-rule-2" updatedGroup.Rules[0].Title = "swap-test-rule-2"
updatedGroup.Rules[1].Title = "swap-test-rule-1" updatedGroup.Rules[1].Title = "swap-test-rule-1"
err = ruleService.ReplaceRuleGroup(context.Background(), orgID, updatedGroup, 0, models.ProvenanceAPI) err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "swap-test") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "swap-test")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
require.Len(t, readGroup.Rules, 2) require.Len(t, readGroup.Rules, 2)
@@ -238,18 +244,18 @@ func TestAlertRuleService(t *testing.T) {
dummyRule("cycle-test-rule-3", orgID), dummyRule("cycle-test-rule-3", orgID),
}, },
} }
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
updatedGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "cycle-test") updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "cycle-test")
require.NoError(t, err) require.NoError(t, err)
updatedGroup.Rules[0].Title = "cycle-test-rule-2" updatedGroup.Rules[0].Title = "cycle-test-rule-2"
updatedGroup.Rules[1].Title = "cycle-test-rule-3" updatedGroup.Rules[1].Title = "cycle-test-rule-3"
updatedGroup.Rules[2].Title = "cycle-test-rule-1" updatedGroup.Rules[2].Title = "cycle-test-rule-1"
err = ruleService.ReplaceRuleGroup(context.Background(), orgID, updatedGroup, 0, models.ProvenanceAPI) err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "cycle-test") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "cycle-test")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
require.Len(t, readGroup.Rules, 3) require.Len(t, readGroup.Rules, 3)
@@ -276,9 +282,9 @@ func TestAlertRuleService(t *testing.T) {
dummyRule("multi-cycle-test-rule-5", orgID), dummyRule("multi-cycle-test-rule-5", orgID),
}, },
} }
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
updatedGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "multi-cycle-test") updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "multi-cycle-test")
require.NoError(t, err) require.NoError(t, err)
updatedGroup.Rules[0].Title = "multi-cycle-test-rule-2" updatedGroup.Rules[0].Title = "multi-cycle-test-rule-2"
@@ -288,10 +294,10 @@ func TestAlertRuleService(t *testing.T) {
updatedGroup.Rules[3].Title = "multi-cycle-test-rule-5" updatedGroup.Rules[3].Title = "multi-cycle-test-rule-5"
updatedGroup.Rules[4].Title = "multi-cycle-test-rule-3" updatedGroup.Rules[4].Title = "multi-cycle-test-rule-3"
err = ruleService.ReplaceRuleGroup(context.Background(), orgID, updatedGroup, 0, models.ProvenanceAPI) err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "multi-cycle-test") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "multi-cycle-test")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
require.Len(t, readGroup.Rules, 5) require.Len(t, readGroup.Rules, 5)
@@ -317,7 +323,7 @@ func TestAlertRuleService(t *testing.T) {
dummyRule("recreate-test-rule-1", orgID), dummyRule("recreate-test-rule-1", orgID),
}, },
} }
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
updatedGroup := models.AlertRuleGroup{ updatedGroup := models.AlertRuleGroup{
Title: "recreate-test", Title: "recreate-test",
@@ -327,10 +333,10 @@ func TestAlertRuleService(t *testing.T) {
dummyRule("recreate-test-rule-1", orgID), dummyRule("recreate-test-rule-1", orgID),
}, },
} }
err = ruleService.ReplaceRuleGroup(context.Background(), orgID, updatedGroup, 0, models.ProvenanceAPI) err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "recreate-test") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "recreate-test")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
require.Len(t, readGroup.Rules, 1) require.Len(t, readGroup.Rules, 1)
@@ -348,17 +354,17 @@ func TestAlertRuleService(t *testing.T) {
dummyRule("create-overlap-test-rule-1", orgID), dummyRule("create-overlap-test-rule-1", orgID),
}, },
} }
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
updatedGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "create-overlap-test") updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "create-overlap-test")
require.NoError(t, err) require.NoError(t, err)
updatedGroup.Rules[0].Title = "create-overlap-test-rule-2" updatedGroup.Rules[0].Title = "create-overlap-test-rule-2"
updatedGroup.Rules = append(updatedGroup.Rules, dummyRule("create-overlap-test-rule-1", orgID)) updatedGroup.Rules = append(updatedGroup.Rules, dummyRule("create-overlap-test-rule-1", orgID))
err = ruleService.ReplaceRuleGroup(context.Background(), orgID, updatedGroup, 0, models.ProvenanceAPI) err = ruleService.ReplaceRuleGroup(context.Background(), u, updatedGroup, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
readGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "create-overlap-test") readGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "create-overlap-test")
require.NoError(t, err) require.NoError(t, err)
require.NotEmpty(t, readGroup.Rules) require.NotEmpty(t, readGroup.Rules)
require.Len(t, readGroup.Rules, 2) require.Len(t, readGroup.Rules, 2)
@@ -377,9 +383,9 @@ func TestAlertRuleService(t *testing.T) {
models.PanelIDAnnotation: strconv.FormatInt(panelId, 10), models.PanelIDAnnotation: strconv.FormatInt(panelId, 10),
} }
err := ruleService.ReplaceRuleGroup(context.Background(), orgID, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
updatedGroup, err := ruleService.GetRuleGroup(context.Background(), orgID, "my-namespace", "group-test-5") updatedGroup, err := ruleService.GetRuleGroup(context.Background(), u, "my-namespace", "group-test-5")
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, updatedGroup.Rules[0].DashboardUID) require.NotNil(t, updatedGroup.Rules[0].DashboardUID)
@@ -434,12 +440,11 @@ func TestAlertRuleService(t *testing.T) {
} }
for _, test := range tests { for _, test := range tests {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
var orgID int64 = 1
rule := dummyRule(t.Name(), orgID) rule := dummyRule(t.Name(), orgID)
rule, err := ruleService.CreateAlertRule(context.Background(), nil, rule, test.from) rule, err := ruleService.CreateAlertRule(context.Background(), u, rule, test.from)
require.NoError(t, err) require.NoError(t, err)
_, err = ruleService.UpdateAlertRule(context.Background(), rule, test.to) _, err = ruleService.UpdateAlertRule(context.Background(), u, rule, test.to)
if test.errNil { if test.errNil {
require.NoError(t, err) require.NoError(t, err)
} else { } else {
@@ -497,11 +502,11 @@ func TestAlertRuleService(t *testing.T) {
t.Run(test.name, func(t *testing.T) { t.Run(test.name, func(t *testing.T) {
var orgID int64 = 1 var orgID int64 = 1
group := createDummyGroup(t.Name(), orgID) group := createDummyGroup(t.Name(), orgID)
err := ruleService.ReplaceRuleGroup(context.Background(), 1, group, 0, test.from) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, test.from)
require.NoError(t, err) require.NoError(t, err)
group.Rules[0].Title = t.Name() group.Rules[0].Title = t.Name()
err = ruleService.ReplaceRuleGroup(context.Background(), 1, group, 0, test.to) err = ruleService.ReplaceRuleGroup(context.Background(), u, group, test.to)
if test.errNil { if test.errNil {
require.NoError(t, err) require.NoError(t, err)
} else { } else {
@@ -517,7 +522,7 @@ func TestAlertRuleService(t *testing.T) {
checker.EXPECT().LimitExceeded() checker.EXPECT().LimitExceeded()
ruleService.quotas = checker ruleService.quotas = checker
_, err := ruleService.CreateAlertRule(context.Background(), nil, dummyRule("test#1", orgID), models.ProvenanceNone) _, err := ruleService.CreateAlertRule(context.Background(), u, dummyRule("test#1", orgID), models.ProvenanceNone)
require.ErrorIs(t, err, models.ErrQuotaReached) require.ErrorIs(t, err, models.ErrQuotaReached)
}) })
@@ -528,8 +533,8 @@ func TestAlertRuleService(t *testing.T) {
checker.EXPECT().LimitExceeded() checker.EXPECT().LimitExceeded()
ruleService.quotas = checker ruleService.quotas = checker
group := createDummyGroup("quota-reached", 1) group := createDummyGroup("quota-reached", orgID)
err := ruleService.ReplaceRuleGroup(context.Background(), 1, group, 0, models.ProvenanceAPI) err := ruleService.ReplaceRuleGroup(context.Background(), u, group, models.ProvenanceAPI)
require.ErrorIs(t, err, models.ErrQuotaReached) require.ErrorIs(t, err, models.ErrQuotaReached)
}) })
@@ -538,18 +543,19 @@ func TestAlertRuleService(t *testing.T) {
func TestCreateAlertRule(t *testing.T) { func TestCreateAlertRule(t *testing.T) {
ruleService := createAlertRuleService(t) ruleService := createAlertRuleService(t)
var orgID int64 = 1 var orgID int64 = 1
u := &user.SignedInUser{OrgID: orgID}
t.Run("should return the created id", func(t *testing.T) { t.Run("should return the created id", func(t *testing.T) {
rule, err := ruleService.CreateAlertRule(context.Background(), nil, dummyRule("test#1", orgID), models.ProvenanceNone) rule, err := ruleService.CreateAlertRule(context.Background(), u, dummyRule("test#1", orgID), models.ProvenanceNone)
require.NoError(t, err) require.NoError(t, err)
require.NotEqual(t, 0, rule.ID, "expected to get the created id and not the zero value") require.NotEqual(t, 0, rule.ID, "expected to get the created id and not the zero value")
}) })
t.Run("should set the right provenance", func(t *testing.T) { t.Run("should set the right provenance", func(t *testing.T) {
rule, err := ruleService.CreateAlertRule(context.Background(), nil, dummyRule("test#2", orgID), models.ProvenanceAPI) rule, err := ruleService.CreateAlertRule(context.Background(), u, dummyRule("test#2", orgID), models.ProvenanceAPI)
require.NoError(t, err) require.NoError(t, err)
_, provenance, err := ruleService.GetAlertRule(context.Background(), orgID, rule.UID) _, provenance, err := ruleService.GetAlertRule(context.Background(), u, rule.UID)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, models.ProvenanceAPI, provenance) require.Equal(t, models.ProvenanceAPI, provenance)
}) })
@@ -558,17 +564,17 @@ func TestCreateAlertRule(t *testing.T) {
t.Run("return error if it is not valid UID", func(t *testing.T) { t.Run("return error if it is not valid UID", func(t *testing.T) {
rule := dummyRule("test#3", orgID) rule := dummyRule("test#3", orgID)
rule.UID = strings.Repeat("1", util.MaxUIDLength+1) rule.UID = strings.Repeat("1", util.MaxUIDLength+1)
rule, err := ruleService.CreateAlertRule(context.Background(), nil, rule, models.ProvenanceNone) rule, err := ruleService.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation) require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation)
}) })
t.Run("should create a new rule with this UID", func(t *testing.T) { t.Run("should create a new rule with this UID", func(t *testing.T) {
rule := dummyRule("test#3", orgID) rule := dummyRule("test#3", orgID)
uid := util.GenerateShortUID() uid := util.GenerateShortUID()
rule.UID = uid rule.UID = uid
created, err := ruleService.CreateAlertRule(context.Background(), nil, rule, models.ProvenanceNone) created, err := ruleService.CreateAlertRule(context.Background(), u, rule, models.ProvenanceNone)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, uid, created.UID) require.Equal(t, uid, created.UID)
_, _, err = ruleService.GetAlertRule(context.Background(), orgID, uid) _, _, err = ruleService.GetAlertRule(context.Background(), u, uid)
require.NoError(t, err) require.NoError(t, err)
}) })
}) })
@@ -7,10 +7,12 @@ import (
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder" "github.com/grafana/grafana/pkg/services/folder"
alert_models "github.com/grafana/grafana/pkg/services/ngalert/models" alert_models "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning" "github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
) )
@@ -42,6 +44,7 @@ func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context,
files []*AlertingFile) error { files []*AlertingFile) error {
for _, file := range files { for _, file := range files {
for _, group := range file.Groups { for _, group := range file.Groups {
u := provisionerUser(group.OrgID)
folderUID, err := prov.getOrCreateFolderUID(ctx, group.FolderTitle, group.OrgID) folderUID, err := prov.getOrCreateFolderUID(ctx, group.FolderTitle, group.OrgID)
if err != nil { if err != nil {
return err return err
@@ -54,19 +57,18 @@ func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context,
for _, rule := range group.Rules { for _, rule := range group.Rules {
rule.NamespaceUID = folderUID rule.NamespaceUID = folderUID
rule.RuleGroup = group.Title rule.RuleGroup = group.Title
err = prov.provisionRule(ctx, group.OrgID, rule) err = prov.provisionRule(ctx, u, rule)
if err != nil { if err != nil {
return err return err
} }
} }
err = prov.ruleService.UpdateRuleGroup(ctx, group.OrgID, folderUID, group.Title, group.Interval) err = prov.ruleService.UpdateRuleGroup(ctx, u, folderUID, group.Title, group.Interval)
if err != nil { if err != nil {
return err return err
} }
} }
for _, deleteRule := range file.DeleteRules { for _, deleteRule := range file.DeleteRules {
err := prov.ruleService.DeleteAlertRule(ctx, deleteRule.OrgID, err := prov.ruleService.DeleteAlertRule(ctx, provisionerUser(deleteRule.OrgID), deleteRule.UID, alert_models.ProvenanceFile)
deleteRule.UID, alert_models.ProvenanceFile)
if err != nil { if err != nil {
return err return err
} }
@@ -77,20 +79,20 @@ func (prov *defaultAlertRuleProvisioner) Provision(ctx context.Context,
func (prov *defaultAlertRuleProvisioner) provisionRule( func (prov *defaultAlertRuleProvisioner) provisionRule(
ctx context.Context, ctx context.Context,
orgID int64, user identity.Requester,
rule alert_models.AlertRule) error { rule alert_models.AlertRule) error {
prov.logger.Debug("provisioning alert rule", "uid", rule.UID, "org", rule.OrgID) prov.logger.Debug("provisioning alert rule", "uid", rule.UID, "org", rule.OrgID)
_, _, err := prov.ruleService.GetAlertRule(ctx, orgID, rule.UID) _, _, err := prov.ruleService.GetAlertRule(ctx, user, rule.UID)
if err != nil && !errors.Is(err, alert_models.ErrAlertRuleNotFound) { if err != nil && !errors.Is(err, alert_models.ErrAlertRuleNotFound) {
return err return err
} else if err != nil { } else if err != nil {
prov.logger.Debug("creating rule", "uid", rule.UID, "org", rule.OrgID) prov.logger.Debug("creating rule", "uid", rule.UID, "org", rule.OrgID)
// a nil user is passed in as then the quota logic will only check for // a nil user is passed in as then the quota logic will only check for
// the organization quota since we don't have any user scope here. // the organization quota since we don't have any user scope here.
_, err = prov.ruleService.CreateAlertRule(ctx, nil, rule, alert_models.ProvenanceFile) _, err = prov.ruleService.CreateAlertRule(ctx, user, rule, alert_models.ProvenanceFile)
} else { } else {
prov.logger.Debug("updating rule", "uid", rule.UID, "org", rule.OrgID) prov.logger.Debug("updating rule", "uid", rule.UID, "org", rule.OrgID)
_, err = prov.ruleService.UpdateAlertRule(ctx, rule, alert_models.ProvenanceFile) _, err = prov.ruleService.UpdateAlertRule(ctx, user, rule, alert_models.ProvenanceFile)
} }
return err return err
} }
@@ -129,3 +131,8 @@ func (prov *defaultAlertRuleProvisioner) getOrCreateFolderUID(
return cmdResult.UID, nil return cmdResult.UID, nil
} }
// UserID is 0 to use org quota
var provisionerUser = func(orgID int64) identity.Requester {
return &user.SignedInUser{UserID: 0, Login: "alert_provisioner", OrgID: orgID}
}