mirror of
https://github.com/grafana/grafana.git
synced 2025-02-11 16:15:42 -06:00
Alerting: Add method to reset notification policy tree back to the default (#51934)
* Define route and run codegen * Wire up HTTP layer * Update API layer and test fakes * Implement reset of policy tree * Implement service layer test and authorization bindings * API layer testing * Be more specific when injecting settings
This commit is contained in:
parent
05cdef5004
commit
fce283d73e
@ -40,6 +40,7 @@ type TemplateService interface {
|
||||
type NotificationPolicyService interface {
|
||||
GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error)
|
||||
UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p alerting_models.Provenance) error
|
||||
ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error)
|
||||
}
|
||||
|
||||
type MuteTimingService interface {
|
||||
@ -85,6 +86,14 @@ func (srv *ProvisioningSrv) RoutePutPolicyTree(c *models.ReqContext, tree defini
|
||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "policies updated"})
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RouteResetPolicyTree(c *models.ReqContext) response.Response {
|
||||
tree, err := srv.policies.ResetPolicyTree(c.Req.Context(), c.OrgId)
|
||||
if err != nil {
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusAccepted, tree)
|
||||
}
|
||||
|
||||
func (srv *ProvisioningSrv) RouteGetContactPoints(c *models.ReqContext) response.Response {
|
||||
cps, err := srv.contactPointService.GetContactPoints(c.Req.Context(), c.OrgId)
|
||||
if err != nil {
|
||||
|
@ -44,6 +44,15 @@ func TestProvisioningApi(t *testing.T) {
|
||||
require.Equal(t, 202, response.Status())
|
||||
})
|
||||
|
||||
t.Run("successful DELETE returns 202", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
response := sut.RouteResetPolicyTree(&rc)
|
||||
|
||||
require.Equal(t, 202, response.Status())
|
||||
})
|
||||
|
||||
t.Run("when new policy tree is invalid", func(t *testing.T) {
|
||||
t.Run("PUT returns 400", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
@ -106,6 +115,18 @@ func TestProvisioningApi(t *testing.T) {
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "something went wrong")
|
||||
})
|
||||
|
||||
t.Run("DELETE returns 500", func(t *testing.T) {
|
||||
sut := createProvisioningSrvSut(t)
|
||||
sut.policies = &fakeFailingNotificationPolicyService{}
|
||||
rc := createTestRequestCtx()
|
||||
|
||||
response := sut.RouteResetPolicyTree(&rc)
|
||||
|
||||
require.Equal(t, 500, response.Status())
|
||||
require.NotEmpty(t, response.Body())
|
||||
require.Contains(t, string(response.Body()), "something went wrong")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@ -335,6 +356,11 @@ func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, or
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
f.tree = definitions.Route{} // TODO
|
||||
return f.tree, nil
|
||||
}
|
||||
|
||||
type fakeFailingNotificationPolicyService struct{}
|
||||
|
||||
func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
@ -345,6 +371,10 @@ func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Cont
|
||||
return fmt.Errorf("something went wrong")
|
||||
}
|
||||
|
||||
func (f *fakeFailingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
return definitions.Route{}, fmt.Errorf("something went wrong")
|
||||
}
|
||||
|
||||
type fakeRejectingNotificationPolicyService struct{}
|
||||
|
||||
func (f *fakeRejectingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
@ -355,6 +385,10 @@ func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Co
|
||||
return fmt.Errorf("%w: invalid policy tree", provisioning.ErrValidation)
|
||||
}
|
||||
|
||||
func (f *fakeRejectingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
return definitions.Route{}, nil
|
||||
}
|
||||
|
||||
func createInvalidContactPoint() definitions.EmbeddedContactPoint {
|
||||
settings, _ := simplejson.NewJson([]byte(`{}`))
|
||||
return definitions.EmbeddedContactPoint{
|
||||
|
@ -191,6 +191,7 @@ func (api *API) authorize(method, path string) web.Handler {
|
||||
eval = ac.EvalPermission(ac.ActionAlertingProvisioningRead) // organization scope
|
||||
|
||||
case http.MethodPut + "/api/v1/provisioning/policies",
|
||||
http.MethodDelete + "/api/v1/provisioning/policies",
|
||||
http.MethodPost + "/api/v1/provisioning/contact-points",
|
||||
http.MethodPut + "/api/v1/provisioning/contact-points/{UID}",
|
||||
http.MethodDelete + "/api/v1/provisioning/contact-points/{UID}",
|
||||
|
@ -27,6 +27,10 @@ func (f *ForkedProvisioningApi) forkRoutePutPolicyTree(ctx *models.ReqContext, r
|
||||
return f.svc.RoutePutPolicyTree(ctx, route)
|
||||
}
|
||||
|
||||
func (f *ForkedProvisioningApi) forkRouteResetPolicyTree(ctx *models.ReqContext) response.Response {
|
||||
return f.svc.RouteResetPolicyTree(ctx)
|
||||
}
|
||||
|
||||
func (f *ForkedProvisioningApi) forkRouteGetContactpoints(ctx *models.ReqContext) response.Response {
|
||||
return f.svc.RouteGetContactPoints(ctx)
|
||||
}
|
||||
|
@ -40,6 +40,7 @@ type ProvisioningApiForkingService interface {
|
||||
RoutePutMuteTiming(*models.ReqContext) response.Response
|
||||
RoutePutPolicyTree(*models.ReqContext) response.Response
|
||||
RoutePutTemplate(*models.ReqContext) response.Response
|
||||
RouteResetPolicyTree(*models.ReqContext) response.Response
|
||||
}
|
||||
|
||||
func (f *ForkedProvisioningApi) RouteDeleteAlertRule(ctx *models.ReqContext) response.Response {
|
||||
@ -156,6 +157,9 @@ func (f *ForkedProvisioningApi) RoutePutTemplate(ctx *models.ReqContext) respons
|
||||
}
|
||||
return f.forkRoutePutTemplate(ctx, conf, nameParam)
|
||||
}
|
||||
func (f *ForkedProvisioningApi) RouteResetPolicyTree(ctx *models.ReqContext) response.Response {
|
||||
return f.forkRouteResetPolicyTree(ctx)
|
||||
}
|
||||
|
||||
func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApiForkingService, m *metrics.API) {
|
||||
api.RouteRegister.Group("", func(group routing.RouteRegister) {
|
||||
@ -369,5 +373,15 @@ func (api *API) RegisterProvisioningApiEndpoints(srv ProvisioningApiForkingServi
|
||||
m,
|
||||
),
|
||||
)
|
||||
group.Delete(
|
||||
toMacaronPath("/api/v1/provisioning/policies"),
|
||||
api.authorize(http.MethodDelete, "/api/v1/provisioning/policies"),
|
||||
metrics.Instrument(
|
||||
http.MethodDelete,
|
||||
"/api/v1/provisioning/policies",
|
||||
srv.RouteResetPolicyTree,
|
||||
m,
|
||||
),
|
||||
)
|
||||
}, middleware.ReqSignedIn)
|
||||
}
|
||||
|
@ -2785,6 +2785,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"alertGroups": {
|
||||
"description": "AlertGroups alert groups",
|
||||
"items": {
|
||||
"$ref": "#/definitions/alertGroup"
|
||||
},
|
||||
@ -3113,6 +3114,7 @@
|
||||
"type": "array"
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"properties": {
|
||||
"comment": {
|
||||
"description": "comment",
|
||||
@ -3150,7 +3152,6 @@
|
||||
"type": "object"
|
||||
},
|
||||
"receiver": {
|
||||
"description": "Receiver receiver",
|
||||
"properties": {
|
||||
"name": {
|
||||
"description": "name",
|
||||
@ -3718,6 +3719,24 @@
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/policies": {
|
||||
"delete": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"operationId": "RouteResetPolicyTree",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Ack",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Clears the notification policy tree.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "RouteGetPolicyTree",
|
||||
"responses": {
|
||||
|
@ -19,6 +19,16 @@ package definitions
|
||||
// 202: Ack
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route DELETE /api/v1/provisioning/policies provisioning stable RouteResetPolicyTree
|
||||
//
|
||||
// Clears the notification policy tree.
|
||||
//
|
||||
// Consumes:
|
||||
// - application/json
|
||||
//
|
||||
// Responses:
|
||||
// 202: Ack
|
||||
|
||||
// swagger:parameters RoutePutPolicyTree
|
||||
type Policytree struct {
|
||||
// The new notification routing tree to use
|
||||
|
@ -3003,6 +3003,7 @@
|
||||
"type": "object"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
@ -5344,6 +5345,24 @@
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/policies": {
|
||||
"delete": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"operationId": "RouteResetPolicyTree",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Ack",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
}
|
||||
},
|
||||
"summary": "Clears the notification policy tree.",
|
||||
"tags": [
|
||||
"provisioning"
|
||||
]
|
||||
},
|
||||
"get": {
|
||||
"operationId": "RouteGetPolicyTree",
|
||||
"responses": {
|
||||
|
@ -2169,6 +2169,25 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"provisioning",
|
||||
"stable"
|
||||
],
|
||||
"summary": "Clears the notification policy tree.",
|
||||
"operationId": "RouteResetPolicyTree",
|
||||
"responses": {
|
||||
"202": {
|
||||
"description": "Ack",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/Ack"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/provisioning/templates": {
|
||||
@ -5366,6 +5385,7 @@
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
},
|
||||
"gettableSilences": {
|
||||
"description": "GettableSilences gettable silences",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/gettableSilence"
|
||||
@ -5477,6 +5497,7 @@
|
||||
}
|
||||
},
|
||||
"postableSilence": {
|
||||
"description": "PostableSilence postable silence",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"comment",
|
||||
|
@ -158,7 +158,7 @@ func (ng *AlertNG) init() error {
|
||||
ng.schedule = scheduler
|
||||
|
||||
// Provisioning
|
||||
policyService := provisioning.NewNotificationPolicyService(store, store, store, ng.Log)
|
||||
policyService := provisioning.NewNotificationPolicyService(store, store, store, ng.Cfg.UnifiedAlerting, ng.Log)
|
||||
contactPointService := provisioning.NewContactPointService(store, ng.SecretsService, store, store, ng.Log)
|
||||
templateService := provisioning.NewTemplateService(store, store, store, ng.Log)
|
||||
muteTimingService := provisioning.NewMuteTimingService(store, store, store, ng.Log)
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type NotificationPolicyService struct {
|
||||
@ -14,15 +15,17 @@ type NotificationPolicyService struct {
|
||||
provenanceStore ProvisioningStore
|
||||
xact TransactionManager
|
||||
log log.Logger
|
||||
settings setting.UnifiedAlertingSettings
|
||||
}
|
||||
|
||||
func NewNotificationPolicyService(am AMConfigStore, prov ProvisioningStore,
|
||||
xact TransactionManager, log log.Logger) *NotificationPolicyService {
|
||||
xact TransactionManager, settings setting.UnifiedAlertingSettings, log log.Logger) *NotificationPolicyService {
|
||||
return &NotificationPolicyService{
|
||||
amStore: am,
|
||||
provenanceStore: prov,
|
||||
xact: xact,
|
||||
log: log,
|
||||
settings: settings,
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,6 +119,49 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||
defaultCfg, err := deserializeAlertmanagerConfig([]byte(nps.settings.DefaultConfiguration))
|
||||
if err != nil {
|
||||
nps.log.Error("failed to parse default alertmanager config: %w", err)
|
||||
return definitions.Route{}, fmt.Errorf("failed to parse default alertmanager config: %w", err)
|
||||
}
|
||||
route := defaultCfg.AlertmanagerConfig.Route
|
||||
|
||||
revision, err := getLastConfiguration(ctx, orgID, nps.amStore)
|
||||
if err != nil {
|
||||
return definitions.Route{}, err
|
||||
}
|
||||
revision.cfg.AlertmanagerConfig.Config.Route = route
|
||||
|
||||
serialized, err := serializeAlertmanagerConfig(*revision.cfg)
|
||||
if err != nil {
|
||||
return definitions.Route{}, err
|
||||
}
|
||||
cmd := models.SaveAlertmanagerConfigurationCmd{
|
||||
AlertmanagerConfiguration: string(serialized),
|
||||
ConfigurationVersion: revision.version,
|
||||
FetchedConfigurationHash: revision.concurrencyToken,
|
||||
Default: false,
|
||||
OrgID: orgID,
|
||||
}
|
||||
err = nps.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
err := nps.amStore.UpdateAlertmanagerConfiguration(ctx, &cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = nps.provenanceStore.DeleteProvenance(ctx, route, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return definitions.Route{}, nil
|
||||
}
|
||||
|
||||
return *route, nil
|
||||
}
|
||||
|
||||
func (nps *NotificationPolicyService) receiversToMap(records []*definitions.PostableApiReceiver) (map[string]struct{}, error) {
|
||||
receivers := map[string]struct{}{}
|
||||
for _, receiver := range records {
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/timeinterval"
|
||||
"github.com/prometheus/common/model"
|
||||
@ -213,6 +214,17 @@ func TestNotificationPolicyService(t *testing.T) {
|
||||
require.Error(t, err)
|
||||
require.ErrorIs(t, err, ErrValidation)
|
||||
})
|
||||
|
||||
t.Run("deleting route replaces with default", func(t *testing.T) {
|
||||
sut := createNotificationPolicyServiceSut()
|
||||
|
||||
tree, err := sut.ResetPolicyTree(context.Background(), 1)
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "grafana-default-email", tree.Receiver)
|
||||
require.Nil(t, tree.Routes)
|
||||
require.Nil(t, tree.GroupBy)
|
||||
})
|
||||
}
|
||||
|
||||
func createNotificationPolicyServiceSut() *NotificationPolicyService {
|
||||
@ -221,6 +233,9 @@ func createNotificationPolicyServiceSut() *NotificationPolicyService {
|
||||
provenanceStore: NewFakeProvisioningStore(),
|
||||
xact: newNopTransactionManager(),
|
||||
log: log.NewNopLogger(),
|
||||
settings: setting.UnifiedAlertingSettings{
|
||||
DefaultConfiguration: setting.GetAlertmanagerDefaultConfiguration(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user