Alerting: Allow administrators delete rules permanently via UI (#101974)

* add query parameter to existing APIs to control the permanent deletion of rules
* add GUID to gettable rule
* add new endpoint /ruler/grafana/api/v1/trash/rule/guid/{RuleGUID} to delete rules from trash permanently

---------

Signed-off-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
Yuri Tseretyan
2025-03-14 22:14:06 +02:00
committed by GitHub
parent e30034a42a
commit 309a2eb4e9
25 changed files with 585 additions and 115 deletions
+32 -4
View File
@@ -78,6 +78,14 @@ var ignoreFieldsForValidate = [...]string{"RuleGroupIndex"}
// Returns http.StatusForbidden 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.
func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceUID string, group string) response.Response {
var permanently bool
if c.QueryBool("deletePermanently") {
if !c.SignedInUser.HasRole(identity.RoleAdmin) {
return ErrResp(http.StatusForbidden, errors.New("only administrators can delete rules permanently"), "")
}
permanently = true
}
namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), namespaceUID, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return toNamespaceErrorResponse(err)
@@ -161,7 +169,7 @@ func (srv RulerSrv) RouteDeleteAlertRules(c *contextmodel.ReqContext, namespaceU
rulesToDelete = append(rulesToDelete, uid...)
}
if len(rulesToDelete) > 0 {
err := srv.store.DeleteAlertRulesByUID(ctx, c.SignedInUser.GetOrgID(), ngmodels.NewUserUID(c.SignedInUser), rulesToDelete...)
err := srv.store.DeleteAlertRulesByUID(ctx, c.SignedInUser.GetOrgID(), ngmodels.NewUserUID(c.SignedInUser), permanently, rulesToDelete...)
if err != nil {
return err
}
@@ -385,6 +393,14 @@ func (srv RulerSrv) RouteGetRuleVersionsByUID(c *contextmodel.ReqContext, ruleUI
}
func (srv RulerSrv) RoutePostNameRulesConfig(c *contextmodel.ReqContext, ruleGroupConfig apimodels.PostableRuleGroupConfig, namespaceUID string) response.Response {
var deletePermanently bool
if c.QueryBool("deletePermanently") {
if !c.SignedInUser.HasRole(identity.RoleAdmin) {
return ErrResp(http.StatusForbidden, errors.New("only administrators can delete rules permanently"), "")
}
deletePermanently = true
}
namespace, err := srv.store.GetNamespaceByUID(c.Req.Context(), namespaceUID, c.SignedInUser.GetOrgID(), c.SignedInUser)
if err != nil {
return toNamespaceErrorResponse(err)
@@ -405,7 +421,18 @@ func (srv RulerSrv) RoutePostNameRulesConfig(c *contextmodel.ReqContext, ruleGro
RuleGroup: ruleGroupConfig.Name,
}
return srv.updateAlertRulesInGroup(c, groupKey, rules)
return srv.updateAlertRulesInGroup(c, groupKey, rules, deletePermanently)
}
func (srv RulerSrv) RouteDeleteAlertRuleFromTrashByGUID(ctx *contextmodel.ReqContext, guid string) response.Response {
deleted, err := srv.store.DeleteRuleFromTrashByGUID(ctx.Req.Context(), ctx.SignedInUser.GetOrgID(), guid)
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "failed to delete rule from trash")
}
if deleted == 0 {
return response.Empty(http.StatusNotFound)
}
return response.Empty(http.StatusOK)
}
func (srv RulerSrv) checkGroupLimits(group apimodels.PostableRuleGroupConfig) error {
@@ -424,7 +451,7 @@ func (srv RulerSrv) checkGroupLimits(group apimodels.PostableRuleGroupConfig) er
// All operations are performed in a single transaction
//
//nolint:gocyclo
func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey ngmodels.AlertRuleGroupKey, rules []*ngmodels.AlertRuleWithOptionals) response.Response {
func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey ngmodels.AlertRuleGroupKey, rules []*ngmodels.AlertRuleWithOptionals, deletePermanently bool) response.Response {
var finalChanges *store.GroupDelta
var dbConfig *ngmodels.AlertConfiguration
err := srv.xactManager.InTransaction(c.Req.Context(), func(tranCtx context.Context) error {
@@ -485,7 +512,7 @@ func (srv RulerSrv) updateAlertRulesInGroup(c *contextmodel.ReqContext, groupKey
UIDs = append(UIDs, rule.UID)
}
if err = srv.store.DeleteAlertRulesByUID(tranCtx, c.SignedInUser.GetOrgID(), ngmodels.NewUserUID(c.SignedInUser), UIDs...); err != nil {
if err = srv.store.DeleteAlertRulesByUID(tranCtx, c.SignedInUser.GetOrgID(), ngmodels.NewUserUID(c.SignedInUser), deletePermanently, UIDs...); err != nil {
return fmt.Errorf("failed to delete rules: %w", err)
}
}
@@ -631,6 +658,7 @@ func toGettableExtendedRuleNode(r ngmodels.AlertRule, provenanceRecords map[stri
NotificationSettings: AlertRuleNotificationSettingsFromNotificationSettings(r.NotificationSettings),
Record: ApiRecordFromModelRecord(r.Record),
Metadata: AlertRuleMetadataFromModelMetadata(r.Metadata),
GUID: r.GUID,
},
}
forDuration := model.Duration(r.For)