Alerting: Extend PUT rule-group route to write the entire rule group rather than top-level fields only (#53078)

* Wire up to full alert rule struct

* Extract group change detection logic to dedicated file

* GroupDiff -> GroupDelta for consistency

* Calculate deltas and handle backwards compatible requests

* Separate changes and insert/update/delete as needed

* Regenerate files

* Don't touch the DB if there are no changes

* Quota checking, delete unused file

* Mark modified records as provisioned

* Validation + a couple API layer tests

* Address linter errors

* Fix issue with UID assignment and rule creation

* Propagate top level group fields to all rules

* Tests for repeated updates and versioning

* Tests for quota and provenance checks

* Fix linter errors

* Regenerate

* Factor out some shared logic

* Drop unnecessary multiple nilchecks

* Use alternative strategy for rolling UIDs on inserted rules

* Fix tests, add back nilcheck, refresh UIDs during test

* Address feedback

* Add missing nil-check
This commit is contained in:
Alexander Weaver
2022-08-10 12:33:41 -05:00
committed by GitHub
parent dc23643bee
commit b198559225
12 changed files with 374 additions and 41 deletions

View File

@@ -56,7 +56,7 @@ type AlertRuleService interface {
UpdateAlertRule(ctx context.Context, 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
GetRuleGroup(ctx context.Context, orgID int64, folder, group string) (definitions.AlertRuleGroup, error)
UpdateRuleGroup(ctx context.Context, orgID int64, folderUID, rulegroup string, interval int64) error
ReplaceRuleGroup(ctx context.Context, orgID int64, group definitions.AlertRuleGroup, userID int64, provenance alerting_models.Provenance) error
}
func (srv *ProvisioningSrv) RouteGetPolicyTree(c *models.ReqContext) response.Response {
@@ -312,8 +312,13 @@ func (srv *ProvisioningSrv) RouteGetAlertRuleGroup(c *models.ReqContext, folder
return response.JSON(http.StatusOK, g)
}
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag definitions.AlertRuleGroupMetadata, folderUID string, group string) response.Response {
err := srv.alertRules.UpdateRuleGroup(c.Req.Context(), c.OrgId, folderUID, group, ag.Interval)
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag definitions.AlertRuleGroup, folderUID string, group string) response.Response {
ag.FolderUID = folderUID
ag.Title = group
err := srv.alertRules.ReplaceRuleGroup(c.Req.Context(), c.OrgId, ag, c.UserId, alerting_models.ProvenanceAPI)
if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) {
return ErrResp(http.StatusBadRequest, err, "")
}
if err != nil {
if errors.Is(err, store.ErrOptimisticLock) {
return ErrResp(http.StatusConflict, err, "")

View File

@@ -299,6 +299,37 @@ func TestProvisioningApi(t *testing.T) {
require.Equal(t, 404, response.Status())
})
t.Run("are invalid at group level", func(t *testing.T) {
t.Run("PUT returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
insertRule(t, sut, createTestAlertRule("rule", 1))
group := createInvalidAlertRuleGroup()
group.Interval = 0
response := sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid alert rule")
})
})
t.Run("are invalid at rule level", func(t *testing.T) {
t.Run("PUT returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
insertRule(t, sut, createTestAlertRule("rule", 1))
group := createInvalidAlertRuleGroup()
response := sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid alert rule")
})
})
})
}
@@ -477,6 +508,14 @@ func createInvalidAlertRule() definitions.ProvisionedAlertRule {
return definitions.ProvisionedAlertRule{}
}
func createInvalidAlertRuleGroup() definitions.AlertRuleGroup {
return definitions.AlertRuleGroup{
Title: "invalid",
Interval: 10,
Rules: []models.AlertRule{{}},
}
}
func createTestAlertRule(title string, orgID int64) definitions.ProvisionedAlertRule {
return definitions.ProvisionedAlertRule{
OrgID: orgID,

View File

@@ -135,7 +135,7 @@ func (f *ProvisioningApiHandler) RoutePutAlertRuleGroup(ctx *models.ReqContext)
folderUIDParam := web.Params(ctx.Req)[":FolderUID"]
groupParam := web.Params(ctx.Req)[":Group"]
// Parse Request Body
conf := apimodels.AlertRuleGroupMetadata{}
conf := apimodels.AlertRuleGroup{}
if err := web.Bind(ctx.Req, &conf); err != nil {
return response.Error(http.StatusBadRequest, "bad request data", err)
}

View File

@@ -100,6 +100,6 @@ func (f *ProvisioningApiHandler) handleRouteGetAlertRuleGroup(ctx *models.ReqCon
return f.svc.RouteGetAlertRuleGroup(ctx, folder, group)
}
func (f *ProvisioningApiHandler) handleRoutePutAlertRuleGroup(ctx *models.ReqContext, ag apimodels.AlertRuleGroupMetadata, folder, group string) response.Response {
func (f *ProvisioningApiHandler) handleRoutePutAlertRuleGroup(ctx *models.ReqContext, ag apimodels.AlertRuleGroup, folder, group string) response.Response {
return f.svc.RoutePutAlertRuleGroup(ctx, ag, folder, group)
}

View File

@@ -3513,7 +3513,6 @@
"$ref": "#/definitions/Duration"
},
"gettableAlert": {
"description": "GettableAlert gettable alert",
"properties": {
"annotations": {
"$ref": "#/definitions/labelSet"
@@ -3569,7 +3568,6 @@
"type": "object"
},
"gettableAlerts": {
"description": "GettableAlerts gettable alerts",
"items": {
"$ref": "#/definitions/gettableAlert"
},
@@ -3735,7 +3733,6 @@
"type": "array"
},
"postableSilence": {
"description": "PostableSilence postable silence",
"properties": {
"comment": {
"description": "comment",
@@ -4183,15 +4180,15 @@
"in": "body",
"name": "Body",
"schema": {
"$ref": "#/definitions/AlertRuleGroupMetadata"
"$ref": "#/definitions/AlertRuleGroup"
}
}
],
"responses": {
"200": {
"description": "AlertRuleGroupMetadata",
"description": "AlertRuleGroup",
"schema": {
"$ref": "#/definitions/AlertRuleGroupMetadata"
"$ref": "#/definitions/AlertRuleGroup"
}
},
"400": {

View File

@@ -151,7 +151,7 @@ func NewAlertRule(rule models.AlertRule, provenance models.Provenance) Provision
// - application/json
//
// Responses:
// 200: AlertRuleGroupMetadata
// 200: AlertRuleGroup
// 400: ValidationError
// swagger:parameters RouteGetAlertRuleGroup RoutePutAlertRuleGroup
@@ -169,7 +169,7 @@ type RuleGroupPathParam struct {
// swagger:parameters RoutePutAlertRuleGroup
type AlertRuleGroupPayload struct {
// in:body
Body AlertRuleGroupMetadata
Body AlertRuleGroup
}
// swagger:model

View File

@@ -3381,6 +3381,7 @@
"type": "object"
},
"alertGroup": {
"description": "AlertGroup alert group",
"properties": {
"alerts": {
"description": "alerts",
@@ -3568,7 +3569,6 @@
"type": "object"
},
"gettableAlerts": {
"description": "GettableAlerts gettable alerts",
"items": {
"$ref": "#/definitions/gettableAlert"
},
@@ -3624,7 +3624,6 @@
"type": "object"
},
"gettableSilences": {
"description": "GettableSilences gettable silences",
"items": {
"$ref": "#/definitions/gettableSilence"
},
@@ -3735,7 +3734,6 @@
"type": "array"
},
"postableSilence": {
"description": "PostableSilence postable silence",
"properties": {
"comment": {
"description": "comment",
@@ -5923,15 +5921,15 @@
"in": "body",
"name": "Body",
"schema": {
"$ref": "#/definitions/AlertRuleGroupMetadata"
"$ref": "#/definitions/AlertRuleGroup"
}
}
],
"responses": {
"200": {
"description": "AlertRuleGroupMetadata",
"description": "AlertRuleGroup",
"schema": {
"$ref": "#/definitions/AlertRuleGroupMetadata"
"$ref": "#/definitions/AlertRuleGroup"
}
},
"400": {

View File

@@ -2075,15 +2075,15 @@
"name": "Body",
"in": "body",
"schema": {
"$ref": "#/definitions/AlertRuleGroupMetadata"
"$ref": "#/definitions/AlertRuleGroup"
}
}
],
"responses": {
"200": {
"description": "AlertRuleGroupMetadata",
"description": "AlertRuleGroup",
"schema": {
"$ref": "#/definitions/AlertRuleGroupMetadata"
"$ref": "#/definitions/AlertRuleGroup"
}
},
"400": {
@@ -5886,6 +5886,7 @@
}
},
"alertGroup": {
"description": "AlertGroup alert group",
"type": "object",
"required": [
"alerts",
@@ -6076,7 +6077,6 @@
"$ref": "#/definitions/gettableAlert"
},
"gettableAlerts": {
"description": "GettableAlerts gettable alerts",
"type": "array",
"items": {
"$ref": "#/definitions/gettableAlert"
@@ -6134,7 +6134,6 @@
"$ref": "#/definitions/gettableSilence"
},
"gettableSilences": {
"description": "GettableSilences gettable silences",
"type": "array",
"items": {
"$ref": "#/definitions/gettableSilence"
@@ -6246,7 +6245,6 @@
}
},
"postableSilence": {
"description": "PostableSilence postable silence",
"type": "object",
"required": [
"comment",