mirror of
https://github.com/grafana/grafana.git
synced 2025-02-09 23:16:16 -06:00
Alerting: Support deleting rule groups in the provisioning API (#83514)
* Alerting: feat: support deleting rule groups in the provisioning API Adds support for DELETE to the provisioning API's alert rule groups route, which allows deleting the rule group with a single API call. Previously, groups were deleted by deleting rules one-by-one. Fixes #81860 This change doesn't add any new paths to the API, only new methods. --------- Co-authored-by: Yuri Tseretyan <yuriy.tseretyan@grafana.com>
This commit is contained in:
parent
467302480f
commit
b905777ba9
@ -65,6 +65,7 @@ type AlertRuleService interface {
|
||||
DeleteAlertRule(ctx context.Context, orgID int64, ruleUID string, provenance alerting_models.Provenance) error
|
||||
GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (alerting_models.AlertRuleGroup, error)
|
||||
ReplaceRuleGroup(ctx context.Context, orgID int64, group alerting_models.AlertRuleGroup, userID int64, provenance alerting_models.Provenance) error
|
||||
DeleteRuleGroup(ctx context.Context, orgID int64, folder, group string, provenance alerting_models.Provenance) error
|
||||
GetAlertRuleWithFolderTitle(ctx context.Context, orgID int64, ruleUID string) (provisioning.AlertRuleWithFolderTitle, error)
|
||||
GetAlertRuleGroupWithFolderTitle(ctx context.Context, orgID int64, folder, group string) (alerting_models.AlertRuleGroupWithFolderTitle, error)
|
||||
GetAlertGroupsWithFolderTitle(ctx context.Context, orgID int64, folderUIDs []string) ([]alerting_models.AlertRuleGroupWithFolderTitle, error)
|
||||
@ -505,6 +506,18 @@ func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *contextmodel.ReqContext, a
|
||||
return response.JSON(http.StatusOK, ag)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RouteDeleteAlertRuleGroup(c *contextmodel.ReqContext, folderUID string, group string) response.Response {
|
||||
provenance := determineProvenance(c)
|
||||
err := srv.alertRules.DeleteRuleGroup(c.Req.Context(), c.SignedInUser.GetOrgID(), folderUID, group, alerting_models.Provenance(provenance))
|
||||
if err != nil {
|
||||
if errors.Is(err, store.ErrAlertRuleGroupNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusNoContent, "")
|
||||
}
|
||||
|
||||
func determineProvenance(ctx *contextmodel.ReqContext) definitions.Provenance {
|
||||
if _, disabled := ctx.Req.Header[disableProvenanceHeaderName]; disabled {
|
||||
return definitions.Provenance(alerting_models.ProvenanceNone)
|
||||
|
@ -343,24 +343,40 @@ func TestProvisioningApi(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("alert rule groups", func(t *testing.T) {
|
||||
t.Run("are present, GET returns 200", func(t *testing.T) {
|
||||
t.Run("are present", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "my-cool-group")
|
||||
t.Run("GET returns 200", func(t *testing.T) {
|
||||
response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "my-cool-group")
|
||||
|
||||
require.Equal(t, 200, response.Status())
|
||||
require.Equal(t, 200, response.Status())
|
||||
})
|
||||
|
||||
t.Run("DELETE returns 204", func(t *testing.T) {
|
||||
response := sut.RouteDeleteAlertRuleGroup(&rc, "folder-uid", "my-cool-group")
|
||||
|
||||
require.Equal(t, 204, response.Status())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("are missing, GET returns 404", func(t *testing.T) {
|
||||
t.Run("are missing", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
insertRule(t, sut, createTestAlertRule("rule", 1))
|
||||
|
||||
response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "does not exist")
|
||||
t.Run("GET returns 404", func(t *testing.T) {
|
||||
response := sut.RouteGetAlertRuleGroup(&rc, "folder-uid", "does not exist")
|
||||
|
||||
require.Equal(t, 404, response.Status())
|
||||
require.Equal(t, 404, response.Status())
|
||||
})
|
||||
|
||||
t.Run("DELETE returns 404", func(t *testing.T) {
|
||||
response := sut.RouteDeleteAlertRuleGroup(&rc, "folder-uid", "does not exist")
|
||||
|
||||
require.Equal(t, 404, response.Status())
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("are invalid at group level", func(t *testing.T) {
|
||||
@ -1587,6 +1603,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
||||
})
|
||||
sqlStore := db.InitTestDB(t)
|
||||
store := store.DBstore{
|
||||
Logger: log,
|
||||
SQLStore: sqlStore,
|
||||
Cfg: setting.UnifiedAlertingSettings{
|
||||
BaseInterval: time.Second * 10,
|
||||
|
@ -253,7 +253,8 @@ func (api *API) authorize(method, path string) web.Handler {
|
||||
http.MethodPost + "/api/v1/provisioning/alert-rules",
|
||||
http.MethodPut + "/api/v1/provisioning/alert-rules/{UID}",
|
||||
http.MethodDelete + "/api/v1/provisioning/alert-rules/{UID}",
|
||||
http.MethodPut + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}":
|
||||
http.MethodPut + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}",
|
||||
http.MethodDelete + "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}":
|
||||
eval = ac.EvalPermission(ac.ActionAlertingProvisioningWrite) // organization scope
|
||||
case http.MethodGet + "/api/v1/notifications/time-intervals/{name}",
|
||||
http.MethodGet + "/api/v1/notifications/time-intervals":
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
|
||||
type ProvisioningApi interface {
|
||||
RouteDeleteAlertRule(*contextmodel.ReqContext) response.Response
|
||||
RouteDeleteAlertRuleGroup(*contextmodel.ReqContext) response.Response
|
||||
RouteDeleteContactpoints(*contextmodel.ReqContext) response.Response
|
||||
RouteDeleteMuteTiming(*contextmodel.ReqContext) response.Response
|
||||
RouteDeleteTemplate(*contextmodel.ReqContext) response.Response
|
||||
@ -57,6 +58,12 @@ func (f *ProvisioningApiHandler) RouteDeleteAlertRule(ctx *contextmodel.ReqConte
|
||||
uIDParam := web.Params(ctx.Req)[":UID"]
|
||||
return f.handleRouteDeleteAlertRule(ctx, uIDParam)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteDeleteAlertRuleGroup(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
folderUIDParam := web.Params(ctx.Req)[":FolderUID"]
|
||||
groupParam := web.Params(ctx.Req)[":Group"]
|
||||
return f.handleRouteDeleteAlertRuleGroup(ctx, folderUIDParam, groupParam)
|
||||
}
|
||||
func (f *ProvisioningApiHandler) RouteDeleteContactpoints(ctx *contextmodel.ReqContext) response.Response {
|
||||
// Parse Path Parameters
|
||||
uIDParam := web.Params(ctx.Req)[":UID"]
|
||||
@ -237,6 +244,18 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApi, m *metrics
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Delete(
|
||||
toMacaronPath("/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||
api.authorize(http.MethodDelete, "/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}"),
|
||||
metrics.Instrument(
|
||||
http.MethodDelete,
|
||||
"/api/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}",
|
||||
api.Hooks.Wrap(srv.RouteDeleteAlertRuleGroup),
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Delete(
|
||||
toMacaronPath("/api/v1/provisioning/contact-points/{UID}"),
|
||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||
|
@ -135,3 +135,7 @@ func (f *ProvisioningApiHandler) handleRouteExportMuteTiming(ctx *contextmodel.R
|
||||
func (f *ProvisioningApiHandler) handleRouteExportMuteTimings(ctx *contextmodel.ReqContext) response.Response {
|
||||
return f.svc.RouteGetMuteTimingsExport(ctx)
|
||||
}
|
||||
|
||||
func (f *ProvisioningApiHandler) handleRouteDeleteAlertRuleGroup(ctx *contextmodel.ReqContext, folderUID, group string) response.Response {
|
||||
return f.svc.RouteDeleteAlertRuleGroup(ctx, folderUID, group)
|
||||
}
|
||||
|
@ -5962,6 +5962,44 @@
|
||||
}
|
||||
},
|
||||
"/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": {
|
||||
"delete": {
|
||||
"description": "Delete rule group",
|
||||
"operationId": "RouteDeleteAlertRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "FolderUID",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "Group",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": " The alert rule group was deleted successfully."
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRuleGroup",
|
||||
"parameters": [
|
||||
|
@ -168,6 +168,15 @@ type ProvisionedAlertRule struct {
|
||||
// 200: AlertRuleGroup
|
||||
// 404: description: Not found.
|
||||
|
||||
// swagger:route DELETE /v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RouteDeleteAlertRuleGroup
|
||||
//
|
||||
// Delete rule group
|
||||
//
|
||||
// Responses:
|
||||
// 204: description: The alert rule group was deleted successfully.
|
||||
// 403: ForbiddenError
|
||||
// 404: NotFound
|
||||
|
||||
// swagger:route GET /v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export provisioning stable RouteGetAlertRuleGroupExport
|
||||
//
|
||||
// Export an alert rule group in provisioning file format.
|
||||
@ -192,13 +201,13 @@ type ProvisionedAlertRule struct {
|
||||
// 200: AlertRuleGroup
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup RouteGetAlertRuleGroupExport
|
||||
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup RouteGetAlertRuleGroupExport RouteDeleteAlertRuleGroup
|
||||
type FolderUIDPathParam struct {
|
||||
// in:path
|
||||
FolderUID string `json:"FolderUID"`
|
||||
}
|
||||
|
||||
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup RouteGetAlertRuleGroupExport
|
||||
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup RouteGetAlertRuleGroupExport RouteDeleteAlertRuleGroup
|
||||
type RuleGroupPathParam struct {
|
||||
// in:path
|
||||
Group string `json:"Group"`
|
||||
|
@ -7721,6 +7721,44 @@
|
||||
}
|
||||
},
|
||||
"/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": {
|
||||
"delete": {
|
||||
"description": "Delete rule group",
|
||||
"operationId": "RouteDeleteAlertRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "FolderUID",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "Group",
|
||||
"required": true,
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": " The alert rule group was deleted successfully."
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRuleGroup",
|
||||
"parameters": [
|
||||
|
@ -2690,6 +2690,45 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Delete rule group",
|
||||
"tags": [
|
||||
"provisioning",
|
||||
"stable"
|
||||
],
|
||||
"operationId": "RouteDeleteAlertRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "FolderUID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Group",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": " The alert rule group was deleted successfully."
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export": {
|
||||
|
@ -276,6 +276,38 @@ func (service *AlertRuleService) ReplaceRuleGroup(ctx context.Context, orgID int
|
||||
return service.persistDelta(ctx, orgID, delta, userID, provenance)
|
||||
}
|
||||
|
||||
func (service *AlertRuleService) DeleteRuleGroup(ctx context.Context, orgID int64, namespaceUID, group string, provenance models.Provenance) error {
|
||||
// List all rules in the group.
|
||||
q := models.ListAlertRulesQuery{
|
||||
OrgID: orgID,
|
||||
NamespaceUIDs: []string{namespaceUID},
|
||||
RuleGroup: group,
|
||||
}
|
||||
ruleList, err := service.ruleStore.ListAlertRules(ctx, &q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(ruleList) == 0 {
|
||||
return store.ErrAlertRuleGroupNotFound
|
||||
}
|
||||
|
||||
// Check provenance for all rules in the group. Fail to delete if any deletions aren't allowed.
|
||||
for _, rule := range ruleList {
|
||||
storedProvenance, err := service.provenanceStore.GetProvenance(ctx, rule, rule.OrgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if storedProvenance != provenance && storedProvenance != models.ProvenanceNone {
|
||||
return fmt.Errorf("cannot delete with provided provenance '%s', needs '%s'", provenance, storedProvenance)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete all rules.
|
||||
return service.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
return service.deleteRules(ctx, orgID, ruleList...)
|
||||
})
|
||||
}
|
||||
|
||||
func (service *AlertRuleService) calcDelta(ctx context.Context, orgID int64, 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.
|
||||
// This is done for backwards compatibility. Requests which specify only the interval must update only the interval.
|
||||
|
@ -10904,6 +10904,44 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Delete rule group",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
],
|
||||
"operationId": "RouteDeleteAlertRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "FolderUID",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "Group",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": " The alert rule group was deleted successfully."
|
||||
},
|
||||
"403": {
|
||||
"description": "ForbiddenError",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/ForbiddenError"
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "NotFound",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/NotFound"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}/export": {
|
||||
|
@ -24656,6 +24656,56 @@
|
||||
}
|
||||
},
|
||||
"/v1/provisioning/folder/{FolderUID}/rule-groups/{Group}": {
|
||||
"delete": {
|
||||
"description": "Delete rule group",
|
||||
"operationId": "RouteDeleteAlertRuleGroup",
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "FolderUID",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "path",
|
||||
"name": "Group",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": " The alert rule group was deleted successfully."
|
||||
},
|
||||
"403": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ForbiddenError"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "ForbiddenError"
|
||||
},
|
||||
"404": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/NotFound"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": "NotFound"
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "RouteGetAlertRuleGroup",
|
||||
"parameters": [
|
||||
|
Loading…
Reference in New Issue
Block a user