Alerting: Fix provisioning validation status codes and panics (#50464)

* Updates to all except alert rules

* Return 400 when rules fail to validate, add testinfra

* More sane package aliases

* More package alias renames

* One more bug in contact point validation

* remove unused function

Co-authored-by: Jean-Philippe Quémémer <jeanphilippe.quemener@grafana.com>
Co-authored-by: Jean-Philippe Quéméner <JohnnyQQQQ@users.noreply.github.com>
This commit is contained in:
Alexander Weaver 2022-06-09 03:38:46 -05:00 committed by GitHub
parent 52deb821d6
commit 7dd78fee2c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 533 additions and 197 deletions

View File

@ -8,7 +8,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/ngalert/store"
@ -33,27 +33,27 @@ type ProvisioningSrv struct {
}
type ContactPointService interface {
GetContactPoints(ctx context.Context, orgID int64) ([]apimodels.EmbeddedContactPoint, error)
CreateContactPoint(ctx context.Context, orgID int64, contactPoint apimodels.EmbeddedContactPoint, p alerting_models.Provenance) (apimodels.EmbeddedContactPoint, error)
UpdateContactPoint(ctx context.Context, orgID int64, contactPoint apimodels.EmbeddedContactPoint, p alerting_models.Provenance) error
GetContactPoints(ctx context.Context, orgID int64) ([]definitions.EmbeddedContactPoint, error)
CreateContactPoint(ctx context.Context, orgID int64, contactPoint definitions.EmbeddedContactPoint, p alerting_models.Provenance) (definitions.EmbeddedContactPoint, error)
UpdateContactPoint(ctx context.Context, orgID int64, contactPoint definitions.EmbeddedContactPoint, p alerting_models.Provenance) error
DeleteContactPoint(ctx context.Context, orgID int64, uid string) error
}
type TemplateService interface {
GetTemplates(ctx context.Context, orgID int64) (map[string]string, error)
SetTemplate(ctx context.Context, orgID int64, tmpl apimodels.MessageTemplate) (apimodels.MessageTemplate, error)
SetTemplate(ctx context.Context, orgID int64, tmpl definitions.MessageTemplate) (definitions.MessageTemplate, error)
DeleteTemplate(ctx context.Context, orgID int64, name string) error
}
type NotificationPolicyService interface {
GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error)
UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p alerting_models.Provenance) error
GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error)
UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p alerting_models.Provenance) error
}
type MuteTimingService interface {
GetMuteTimings(ctx context.Context, orgID int64) ([]apimodels.MuteTimeInterval, error)
CreateMuteTiming(ctx context.Context, mt apimodels.MuteTimeInterval, orgID int64) (*apimodels.MuteTimeInterval, error)
UpdateMuteTiming(ctx context.Context, mt apimodels.MuteTimeInterval, orgID int64) (*apimodels.MuteTimeInterval, error)
GetMuteTimings(ctx context.Context, orgID int64) ([]definitions.MuteTimeInterval, error)
CreateMuteTiming(ctx context.Context, mt definitions.MuteTimeInterval, orgID int64) (*definitions.MuteTimeInterval, error)
UpdateMuteTiming(ctx context.Context, mt definitions.MuteTimeInterval, orgID int64) (*definitions.MuteTimeInterval, error)
DeleteMuteTiming(ctx context.Context, name string, orgID int64) error
}
@ -77,7 +77,7 @@ func (srv *ProvisioningSrv) RouteGetPolicyTree(c *models.ReqContext) response.Re
return response.JSON(http.StatusOK, policies)
}
func (srv *ProvisioningSrv) RoutePutPolicyTree(c *models.ReqContext, tree apimodels.Route) response.Response {
func (srv *ProvisioningSrv) RoutePutPolicyTree(c *models.ReqContext, tree definitions.Route) response.Response {
err := srv.policies.UpdatePolicyTree(c.Req.Context(), c.OrgId, tree, alerting_models.ProvenanceAPI)
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
return ErrResp(http.StatusNotFound, err, "")
@ -100,18 +100,24 @@ func (srv *ProvisioningSrv) RouteGetContactPoints(c *models.ReqContext) response
return response.JSON(http.StatusOK, cps)
}
func (srv *ProvisioningSrv) RoutePostContactPoint(c *models.ReqContext, cp apimodels.EmbeddedContactPoint) response.Response {
func (srv *ProvisioningSrv) RoutePostContactPoint(c *models.ReqContext, cp definitions.EmbeddedContactPoint) response.Response {
// TODO: provenance is hardcoded for now, change it later to make it more flexible
contactPoint, err := srv.contactPointService.CreateContactPoint(c.Req.Context(), c.OrgId, cp, alerting_models.ProvenanceAPI)
if errors.Is(err, provisioning.ErrValidation) {
return ErrResp(http.StatusBadRequest, err, "")
}
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
return response.JSON(http.StatusAccepted, contactPoint)
}
func (srv *ProvisioningSrv) RoutePutContactPoint(c *models.ReqContext, cp apimodels.EmbeddedContactPoint) response.Response {
func (srv *ProvisioningSrv) RoutePutContactPoint(c *models.ReqContext, cp definitions.EmbeddedContactPoint) response.Response {
cp.UID = pathParam(c, uidPathParam)
err := srv.contactPointService.UpdateContactPoint(c.Req.Context(), c.OrgId, cp, alerting_models.ProvenanceAPI)
if errors.Is(err, provisioning.ErrValidation) {
return ErrResp(http.StatusBadRequest, err, "")
}
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
@ -132,9 +138,9 @@ func (srv *ProvisioningSrv) RouteGetTemplates(c *models.ReqContext) response.Res
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
result := make([]apimodels.MessageTemplate, 0, len(templates))
result := make([]definitions.MessageTemplate, 0, len(templates))
for k, v := range templates {
result = append(result, apimodels.MessageTemplate{Name: k, Template: v})
result = append(result, definitions.MessageTemplate{Name: k, Template: v})
}
return response.JSON(http.StatusOK, result)
}
@ -146,14 +152,14 @@ func (srv *ProvisioningSrv) RouteGetTemplate(c *models.ReqContext) response.Resp
return ErrResp(http.StatusInternalServerError, err, "")
}
if tmpl, ok := templates[name]; ok {
return response.JSON(http.StatusOK, apimodels.MessageTemplate{Name: name, Template: tmpl})
return response.JSON(http.StatusOK, definitions.MessageTemplate{Name: name, Template: tmpl})
}
return response.Empty(http.StatusNotFound)
}
func (srv *ProvisioningSrv) RoutePutTemplate(c *models.ReqContext, body apimodels.MessageTemplateContent) response.Response {
func (srv *ProvisioningSrv) RoutePutTemplate(c *models.ReqContext, body definitions.MessageTemplateContent) response.Response {
name := pathParam(c, namePathParam)
tmpl := apimodels.MessageTemplate{
tmpl := definitions.MessageTemplate{
Name: name,
Template: body.Template,
Provenance: alerting_models.ProvenanceAPI,
@ -199,7 +205,7 @@ func (srv *ProvisioningSrv) RouteGetMuteTimings(c *models.ReqContext) response.R
return response.JSON(http.StatusOK, timings)
}
func (srv *ProvisioningSrv) RoutePostMuteTiming(c *models.ReqContext, mt apimodels.MuteTimeInterval) response.Response {
func (srv *ProvisioningSrv) RoutePostMuteTiming(c *models.ReqContext, mt definitions.MuteTimeInterval) response.Response {
created, err := srv.muteTimings.CreateMuteTiming(c.Req.Context(), mt, c.OrgId)
if err != nil {
if errors.Is(err, provisioning.ErrValidation) {
@ -210,7 +216,7 @@ func (srv *ProvisioningSrv) RoutePostMuteTiming(c *models.ReqContext, mt apimode
return response.JSON(http.StatusCreated, created)
}
func (srv *ProvisioningSrv) RoutePutMuteTiming(c *models.ReqContext, mt apimodels.MuteTimeInterval) response.Response {
func (srv *ProvisioningSrv) RoutePutMuteTiming(c *models.ReqContext, mt definitions.MuteTimeInterval) response.Response {
name := pathParam(c, namePathParam)
mt.Name = name
updated, err := srv.muteTimings.UpdateMuteTiming(c.Req.Context(), mt, c.OrgId)
@ -241,11 +247,14 @@ func (srv *ProvisioningSrv) RouteRouteGetAlertRule(c *models.ReqContext) respons
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
return response.JSON(http.StatusOK, apimodels.NewAlertRule(rule, provenace))
return response.JSON(http.StatusOK, definitions.NewAlertRule(rule, provenace))
}
func (srv *ProvisioningSrv) RoutePostAlertRule(c *models.ReqContext, ar apimodels.AlertRule) response.Response {
func (srv *ProvisioningSrv) RoutePostAlertRule(c *models.ReqContext, ar definitions.AlertRule) response.Response {
createdAlertRule, err := srv.alertRules.CreateAlertRule(c.Req.Context(), ar.UpstreamModel(), alerting_models.ProvenanceAPI)
if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) {
return ErrResp(http.StatusBadRequest, err, "")
}
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
@ -255,8 +264,14 @@ func (srv *ProvisioningSrv) RoutePostAlertRule(c *models.ReqContext, ar apimodel
return response.JSON(http.StatusCreated, ar)
}
func (srv *ProvisioningSrv) RoutePutAlertRule(c *models.ReqContext, ar apimodels.AlertRule) response.Response {
func (srv *ProvisioningSrv) RoutePutAlertRule(c *models.ReqContext, ar definitions.AlertRule) response.Response {
updatedAlertRule, err := srv.alertRules.UpdateAlertRule(c.Req.Context(), ar.UpstreamModel(), alerting_models.ProvenanceAPI)
if errors.Is(err, alerting_models.ErrAlertRuleNotFound) {
return response.Empty(http.StatusNotFound)
}
if errors.Is(err, alerting_models.ErrAlertRuleFailedValidation) {
return ErrResp(http.StatusBadRequest, err, "")
}
if err != nil {
return ErrResp(http.StatusInternalServerError, err, "")
}
@ -273,7 +288,7 @@ func (srv *ProvisioningSrv) RouteDeleteAlertRule(c *models.ReqContext) response.
return response.JSON(http.StatusNoContent, "")
}
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag apimodels.AlertRuleGroup) response.Response {
func (srv *ProvisioningSrv) RoutePutAlertRuleGroup(c *models.ReqContext, ag definitions.AlertRuleGroup) response.Response {
rulegroup := pathParam(c, groupPathParam)
folderUID := pathParam(c, folderUIDPathParam)
err := srv.alertRules.UpdateRuleGroup(c.Req.Context(), c.OrgId, folderUID, rulegroup, ag.Interval)

View File

@ -2,147 +2,308 @@ package api
import (
"context"
"encoding/json"
"fmt"
"net/http"
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
domain "github.com/grafana/grafana/pkg/services/ngalert/models"
gfcore "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
"github.com/grafana/grafana/pkg/services/ngalert/store"
secrets "github.com/grafana/grafana/pkg/services/secrets/fakes"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/web"
prometheus "github.com/prometheus/alertmanager/config"
"github.com/prometheus/alertmanager/timeinterval"
"github.com/stretchr/testify/require"
)
func TestProvisioningApi(t *testing.T) {
t.Run("successful GET policies returns 200", func(t *testing.T) {
sut := createProvisioningSrvSut()
rc := createTestRequestCtx()
response := sut.RouteGetPolicyTree(&rc)
require.Equal(t, 200, response.Status())
})
t.Run("successful PUT policies returns 202", func(t *testing.T) {
sut := createProvisioningSrvSut()
rc := createTestRequestCtx()
tree := apimodels.Route{}
response := sut.RoutePutPolicyTree(&rc, tree)
require.Equal(t, 202, response.Status())
})
t.Run("when new policy tree is invalid", func(t *testing.T) {
t.Run("PUT policies returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut()
sut.policies = &fakeRejectingNotificationPolicyService{}
t.Run("policies", func(t *testing.T) {
t.Run("successful GET returns 200", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
tree := apimodels.Route{}
response := sut.RouteGetPolicyTree(&rc)
require.Equal(t, 200, response.Status())
})
t.Run("successful PUT returns 202", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
tree := definitions.Route{}
response := sut.RoutePutPolicyTree(&rc, tree)
require.Equal(t, 400, response.Status())
expBody := `{"error":"invalid object specification: invalid policy tree","message":"invalid object specification: invalid policy tree"}`
require.Equal(t, expBody, string(response.Body()))
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)
sut.policies = &fakeRejectingNotificationPolicyService{}
rc := createTestRequestCtx()
tree := definitions.Route{}
response := sut.RoutePutPolicyTree(&rc, tree)
require.Equal(t, 400, response.Status())
expBody := `{"error":"invalid object specification: invalid policy tree","message":"invalid object specification: invalid policy tree"}`
require.Equal(t, expBody, string(response.Body()))
})
})
t.Run("when org has no AM config", func(t *testing.T) {
t.Run("GET returns 404", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
rc.SignedInUser.OrgId = 2
response := sut.RouteGetPolicyTree(&rc)
require.Equal(t, 404, response.Status())
})
t.Run("POST returns 404", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
rc.SignedInUser.OrgId = 2
response := sut.RouteGetPolicyTree(&rc)
require.Equal(t, 404, response.Status())
})
})
t.Run("when an unspecified error occurrs", func(t *testing.T) {
t.Run("GET returns 500", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
sut.policies = &fakeFailingNotificationPolicyService{}
rc := createTestRequestCtx()
response := sut.RouteGetPolicyTree(&rc)
require.Equal(t, 500, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "something went wrong")
})
t.Run("PUT returns 500", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
sut.policies = &fakeFailingNotificationPolicyService{}
rc := createTestRequestCtx()
tree := definitions.Route{}
response := sut.RoutePutPolicyTree(&rc, tree)
require.Equal(t, 500, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "something went wrong")
})
})
})
t.Run("when org has no AM config", func(t *testing.T) {
t.Run("GET policies returns 404", func(t *testing.T) {
sut := createProvisioningSrvSut()
rc := createTestRequestCtx()
rc.SignedInUser.OrgId = 2
t.Run("contact points", func(t *testing.T) {
t.Run("are invalid", func(t *testing.T) {
t.Run("POST returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
cp := createInvalidContactPoint()
response := sut.RouteGetPolicyTree(&rc)
response := sut.RoutePostContactPoint(&rc, cp)
require.Equal(t, 404, response.Status())
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "recipient must be specified")
})
t.Run("PUT returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
cp := createInvalidContactPoint()
response := sut.RoutePutContactPoint(&rc, cp)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "recipient must be specified")
})
})
})
t.Run("templates", func(t *testing.T) {
t.Run("are invalid", func(t *testing.T) {
t.Run("PUT returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
withURLParams(rc, namePathParam, "test")
tmpl := definitions.MessageTemplateContent{Template: ""}
response := sut.RoutePutTemplate(&rc, tmpl)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "template must have content")
})
})
})
t.Run("mute timings", func(t *testing.T) {
t.Run("are invalid", func(t *testing.T) {
t.Run("POST returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
mti := createInvalidMuteTiming()
response := sut.RoutePostMuteTiming(&rc, mti)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid")
})
t.Run("PUT returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
withURLParams(rc, namePathParam, "interval")
mti := createInvalidMuteTiming()
response := sut.RoutePutMuteTiming(&rc, mti)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid")
})
})
t.Run("POST policies returns 404", func(t *testing.T) {
sut := createProvisioningSrvSut()
t.Run("are missing, PUT returns 404", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
rc.SignedInUser.OrgId = 2
withURLParams(rc, namePathParam, "does not exist")
mti := definitions.MuteTimeInterval{}
response := sut.RouteGetPolicyTree(&rc)
response := sut.RoutePutMuteTiming(&rc, mti)
require.Equal(t, 404, response.Status())
})
})
t.Run("when an unspecified error occurrs", func(t *testing.T) {
t.Run("GET policies returns 500", func(t *testing.T) {
sut := createProvisioningSrvSut()
sut.policies = &fakeFailingNotificationPolicyService{}
rc := createTestRequestCtx()
t.Run("alert rules", func(t *testing.T) {
t.Run("are invalid", func(t *testing.T) {
t.Run("POST returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
rule := createInvalidAlertRule()
response := sut.RouteGetPolicyTree(&rc)
response := sut.RoutePostAlertRule(&rc, rule)
require.Equal(t, 500, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "something went wrong")
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid alert rule")
})
t.Run("PUT returns 400", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
insertRule(t, sut, createTestAlertRule("rule", 1))
rule := createInvalidAlertRule()
response := sut.RoutePutAlertRule(&rc, rule)
require.Equal(t, 400, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "invalid alert rule")
})
})
t.Run("PUT policies returns 500", func(t *testing.T) {
sut := createProvisioningSrvSut()
sut.policies = &fakeFailingNotificationPolicyService{}
t.Run("are missing, PUT returns 404", func(t *testing.T) {
sut := createProvisioningSrvSut(t)
rc := createTestRequestCtx()
tree := apimodels.Route{}
rule := createTestAlertRule("rule", 1)
response := sut.RoutePutPolicyTree(&rc, tree)
response := sut.RoutePutAlertRule(&rc, rule)
require.Equal(t, 500, response.Status())
require.NotEmpty(t, response.Body())
require.Contains(t, string(response.Body()), "something went wrong")
require.Equal(t, 404, response.Status())
})
})
}
func createProvisioningSrvSut() ProvisioningSrv {
func createProvisioningSrvSut(t *testing.T) ProvisioningSrv {
t.Helper()
secrets := secrets.NewFakeSecretsService()
log := log.NewNopLogger()
configs := &provisioning.MockAMConfigStore{}
configs.EXPECT().
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: testConfig,
})
sqlStore := sqlstore.InitTestDB(t)
store := store.DBstore{
SQLStore: sqlStore,
BaseInterval: time.Second * 10,
}
xact := &provisioning.NopTransactionManager{}
prov := &provisioning.MockProvisioningStore{}
prov.EXPECT().SaveSucceeds()
prov.EXPECT().GetReturns(models.ProvenanceNone)
return ProvisioningSrv{
log: log.NewNopLogger(),
policies: newFakeNotificationPolicyService(),
log: log,
policies: newFakeNotificationPolicyService(),
contactPointService: provisioning.NewContactPointService(configs, secrets, prov, xact, log),
templates: provisioning.NewTemplateService(configs, prov, xact, log),
muteTimings: provisioning.NewMuteTimingService(configs, prov, xact, log),
alertRules: provisioning.NewAlertRuleService(store, prov, xact, 60, 10, log),
}
}
func createTestRequestCtx() models.ReqContext {
return models.ReqContext{
func createTestRequestCtx() gfcore.ReqContext {
return gfcore.ReqContext{
Context: &web.Context{
Req: &http.Request{},
},
SignedInUser: &models.SignedInUser{
SignedInUser: &gfcore.SignedInUser{
OrgId: 1,
},
}
}
func withURLParams(rc gfcore.ReqContext, key, value string) {
params := web.Params(rc.Req)
params[key] = value
rc.Req = web.SetURLParams(rc.Req, params)
}
type fakeNotificationPolicyService struct {
tree apimodels.Route
prov domain.Provenance
tree definitions.Route
prov models.Provenance
}
func newFakeNotificationPolicyService() *fakeNotificationPolicyService {
return &fakeNotificationPolicyService{
tree: apimodels.Route{
tree: definitions.Route{
Receiver: "some-receiver",
},
prov: domain.ProvenanceNone,
prov: models.ProvenanceNone,
}
}
func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) {
func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
if orgID != 1 {
return apimodels.Route{}, store.ErrNoAlertmanagerConfiguration
return definitions.Route{}, store.ErrNoAlertmanagerConfiguration
}
result := f.tree
result.Provenance = f.prov
return result, nil
}
func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p domain.Provenance) error {
func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
if orgID != 1 {
return store.ErrNoAlertmanagerConfiguration
}
@ -153,20 +314,112 @@ func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, or
type fakeFailingNotificationPolicyService struct{}
func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) {
return apimodels.Route{}, fmt.Errorf("something went wrong")
func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
return definitions.Route{}, fmt.Errorf("something went wrong")
}
func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p domain.Provenance) error {
func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
return fmt.Errorf("something went wrong")
}
type fakeRejectingNotificationPolicyService struct{}
func (f *fakeRejectingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (apimodels.Route, error) {
return apimodels.Route{}, nil
func (f *fakeRejectingNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
return definitions.Route{}, nil
}
func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree apimodels.Route, p domain.Provenance) error {
func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance) error {
return fmt.Errorf("%w: invalid policy tree", provisioning.ErrValidation)
}
func createInvalidContactPoint() definitions.EmbeddedContactPoint {
settings, _ := simplejson.NewJson([]byte(`{}`))
return definitions.EmbeddedContactPoint{
Name: "test-contact-point",
Type: "slack",
Settings: settings,
}
}
func createInvalidMuteTiming() definitions.MuteTimeInterval {
return definitions.MuteTimeInterval{
MuteTimeInterval: prometheus.MuteTimeInterval{
Name: "interval",
TimeIntervals: []timeinterval.TimeInterval{
{
Weekdays: []timeinterval.WeekdayRange{
{
InclusiveRange: timeinterval.InclusiveRange{
Begin: -1,
End: 7,
},
},
},
},
},
},
}
}
func createInvalidAlertRule() definitions.AlertRule {
return definitions.AlertRule{}
}
func createTestAlertRule(title string, orgID int64) definitions.AlertRule {
return definitions.AlertRule{
OrgID: orgID,
Title: title,
Condition: "A",
Data: []models.AlertQuery{
{
RefID: "A",
Model: json.RawMessage("{}"),
RelativeTimeRange: models.RelativeTimeRange{
From: models.Duration(60),
To: models.Duration(0),
},
},
},
RuleGroup: "my-cool-group",
For: time.Second * 60,
NoDataState: models.OK,
ExecErrState: models.OkErrState,
}
}
func insertRule(t *testing.T, srv ProvisioningSrv, rule definitions.AlertRule) {
t.Helper()
rc := createTestRequestCtx()
resp := srv.RoutePostAlertRule(&rc, rule)
require.Equal(t, 201, resp.Status())
}
var testConfig = `
{
"template_files": {
"a": "template"
},
"alertmanager_config": {
"route": {
"receiver": "grafana-default-email"
},
"receivers": [{
"name": "grafana-default-email",
"grafana_managed_receiver_configs": [{
"uid": "",
"name": "email receiver",
"type": "email",
"isDefault": true,
"settings": {
"addresses": "<example@email.com>"
}
}]
}],
"mute_time_intervals": [{
"name": "interval",
"time_intervals": []
}]
}
}
`

View File

@ -15,13 +15,13 @@ import (
type AlertRuleService struct {
defaultIntervalSeconds int64
baseIntervalSeconds int64
ruleStore store.RuleStore
ruleStore RuleStore
provenanceStore ProvisioningStore
xact TransactionManager
log log.Logger
}
func NewAlertRuleService(ruleStore store.RuleStore,
func NewAlertRuleService(ruleStore RuleStore,
provenanceStore ProvisioningStore,
xact TransactionManager,
defaultIntervalSeconds int64,

View File

@ -10,7 +10,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/secrets"
"github.com/grafana/grafana/pkg/util"
"github.com/prometheus/alertmanager/config"
@ -24,7 +23,7 @@ type ContactPointService struct {
log log.Logger
}
func NewContactPointService(store store.AlertingStore, encryptionService secrets.Service,
func NewContactPointService(store AMConfigStore, encryptionService secrets.Service,
provenanceStore ProvisioningStore, xact TransactionManager, log log.Logger) *ContactPointService {
return &ContactPointService{
amStore: store,
@ -111,7 +110,7 @@ func (ecp *ContactPointService) getContactPointDecrypted(ctx context.Context, or
func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID int64,
contactPoint apimodels.EmbeddedContactPoint, provenance models.Provenance) (apimodels.EmbeddedContactPoint, error) {
if err := contactPoint.Valid(ecp.encryptionService.GetDecryptedValue); err != nil {
return apimodels.EmbeddedContactPoint{}, fmt.Errorf("contact point is not valid: %w", err)
return apimodels.EmbeddedContactPoint{}, fmt.Errorf("%w: %s", ErrValidation, err.Error())
}
revision, err := getLastConfiguration(ctx, orgID, ecp.amStore)
@ -197,13 +196,16 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in
func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID int64, contactPoint apimodels.EmbeddedContactPoint, provenance models.Provenance) error {
// set all redacted values with the latest known value from the store
if contactPoint.Settings == nil {
return fmt.Errorf("%w: %s", ErrValidation, "settings should not be empty")
}
rawContactPoint, err := ecp.getContactPointDecrypted(ctx, orgID, contactPoint.UID)
if err != nil {
return err
}
secretKeys, err := contactPoint.SecretKeys()
if err != nil {
return err
return fmt.Errorf("%w: %s", ErrValidation, err.Error())
}
for _, secretKey := range secretKeys {
secretValue := contactPoint.Settings.Get(secretKey).MustString()
@ -211,10 +213,12 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in
contactPoint.Settings.Set(secretKey, rawContactPoint.Settings.Get(secretKey).MustString())
}
}
// validate merged values
if err := contactPoint.Valid(ecp.encryptionService.GetDecryptedValue); err != nil {
return err
return fmt.Errorf("%w: %s", ErrValidation, err.Error())
}
// check that provenance is not changed in a invalid way
storedProvenance, err := ecp.provenanceStore.GetProvenance(ctx, &contactPoint, orgID)
if err != nil {

View File

@ -57,6 +57,52 @@ func TestContactPointService(t *testing.T) {
require.Equal(t, customUID, cps[1].UID)
})
t.Run("create rejects contact points that fail validation", func(t *testing.T) {
sut := createContactPointServiceSut(secretsService)
newCp := createTestContactPoint()
newCp.Type = ""
_, err := sut.CreateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
require.ErrorIs(t, err, ErrValidation)
})
t.Run("update rejects contact points with no settings", func(t *testing.T) {
sut := createContactPointServiceSut(secretsService)
newCp := createTestContactPoint()
newCp, err := sut.CreateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
require.NoError(t, err)
newCp.Settings = nil
err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
require.ErrorIs(t, err, ErrValidation)
})
t.Run("update rejects contact points with no type", func(t *testing.T) {
sut := createContactPointServiceSut(secretsService)
newCp := createTestContactPoint()
newCp, err := sut.CreateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
require.NoError(t, err)
newCp.Type = ""
err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
require.ErrorIs(t, err, ErrValidation)
})
t.Run("update rejects contact points which fail validation after merging", func(t *testing.T) {
sut := createContactPointServiceSut(secretsService)
newCp := createTestContactPoint()
newCp, err := sut.CreateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
require.NoError(t, err)
newCp.Settings, _ = simplejson.NewJson([]byte(`{}`))
err = sut.UpdateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
require.ErrorIs(t, err, ErrValidation)
})
t.Run("default provenance of contact points is none", func(t *testing.T) {
sut := createContactPointServiceSut(secretsService)

View File

@ -17,7 +17,7 @@ func TestMuteTimingService(t *testing.T) {
t.Run("service returns timings from config file", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
@ -31,7 +31,7 @@ func TestMuteTimingService(t *testing.T) {
t.Run("service returns empty list when config file contains no mute timings", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
@ -56,7 +56,7 @@ func TestMuteTimingService(t *testing.T) {
t.Run("when config is invalid", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
@ -108,7 +108,7 @@ func TestMuteTimingService(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
@ -133,10 +133,10 @@ func TestMuteTimingService(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().
SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save provenance"))
@ -150,13 +150,13 @@ func TestMuteTimingService(t *testing.T) {
sut := createMuteTimingSvcSut()
timing := createMuteTiming()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save config"))
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.CreateMuteTiming(context.Background(), timing, 1)
@ -184,11 +184,11 @@ func TestMuteTimingService(t *testing.T) {
timing := createMuteTiming()
timing.Name = "does not exist"
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
updated, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
@ -215,7 +215,7 @@ func TestMuteTimingService(t *testing.T) {
timing := createMuteTiming()
timing.Name = "asdf"
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
@ -242,10 +242,10 @@ func TestMuteTimingService(t *testing.T) {
timing := createMuteTiming()
timing.Name = "asdf"
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().
SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save provenance"))
@ -260,13 +260,13 @@ func TestMuteTimingService(t *testing.T) {
timing := createMuteTiming()
timing.Name = "asdf"
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save config"))
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.UpdateMuteTiming(context.Background(), timing, 1)
@ -279,11 +279,11 @@ func TestMuteTimingService(t *testing.T) {
t.Run("returns nil if timing does not exist", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
err := sut.DeleteMuteTiming(context.Background(), "does not exist", 1)
@ -305,7 +305,7 @@ func TestMuteTimingService(t *testing.T) {
t.Run("when config is invalid", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
@ -328,10 +328,10 @@ func TestMuteTimingService(t *testing.T) {
t.Run("when provenance fails to save", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().
DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save provenance"))
@ -344,13 +344,13 @@ func TestMuteTimingService(t *testing.T) {
t.Run("when AM config fails to save", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimings,
})
sut.config.(*MockAMConfigStore).EXPECT().
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save config"))
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
err := sut.DeleteMuteTiming(context.Background(), "asdf", 1)
@ -360,7 +360,7 @@ func TestMuteTimingService(t *testing.T) {
t.Run("when mute timing is used in route", func(t *testing.T) {
sut := createMuteTimingSvcSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithMuteTimingsInRoute,
})

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/services/ngalert/store"
)
// AMStore is a store of Alertmanager configurations.
@ -26,3 +27,14 @@ type ProvisioningStore interface {
type TransactionManager interface {
InTransaction(ctx context.Context, work func(ctx context.Context) error) error
}
// RuleStore represents the ability to persist and query alert rules.
type RuleStore interface {
GetAlertRuleByUID(ctx context.Context, query *models.GetAlertRuleByUIDQuery) error
ListAlertRules(ctx context.Context, query *models.ListAlertRulesQuery) error
GetRuleGroupInterval(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string) (int64, error)
InsertAlertRules(ctx context.Context, rule []models.AlertRule) (map[string]int64, error)
UpdateRuleGroup(ctx context.Context, orgID int64, namespaceUID string, ruleGroup string, interval int64) error
UpdateAlertRules(ctx context.Context, rule []store.UpdateRule) error
DeleteAlertRulesByUID(ctx context.Context, orgID int64, ruleUID ...string) error
}

View File

@ -17,7 +17,7 @@ func TestTemplateService(t *testing.T) {
t.Run("service returns templates from config file", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithTemplates,
})
@ -30,7 +30,7 @@ func TestTemplateService(t *testing.T) {
t.Run("service returns empty map when config file contains no templates", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
@ -55,7 +55,7 @@ func TestTemplateService(t *testing.T) {
t.Run("when config is invalid", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
@ -106,7 +106,7 @@ func TestTemplateService(t *testing.T) {
sut := createTemplateServiceSut()
tmpl := createMessageTemplate()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
@ -131,10 +131,10 @@ func TestTemplateService(t *testing.T) {
sut := createTemplateServiceSut()
tmpl := createMessageTemplate()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithTemplates,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().
SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save provenance"))
@ -148,13 +148,13 @@ func TestTemplateService(t *testing.T) {
sut := createTemplateServiceSut()
tmpl := createMessageTemplate()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithTemplates,
})
sut.config.(*MockAMConfigStore).EXPECT().
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save config"))
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
@ -166,11 +166,11 @@ func TestTemplateService(t *testing.T) {
sut := createTemplateServiceSut()
tmpl := createMessageTemplate()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithTemplates,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
@ -181,11 +181,11 @@ func TestTemplateService(t *testing.T) {
sut := createTemplateServiceSut()
tmpl := createMessageTemplate()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
@ -199,11 +199,11 @@ func TestTemplateService(t *testing.T) {
Template: "content",
}
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
result, _ := sut.SetTemplate(context.Background(), 1, tmpl)
@ -218,11 +218,11 @@ func TestTemplateService(t *testing.T) {
Template: "{{define \"name\"}}content{{end}}",
}
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
result, _ := sut.SetTemplate(context.Background(), 1, tmpl)
@ -236,11 +236,11 @@ func TestTemplateService(t *testing.T) {
Template: "{{ .MyField }",
}
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
@ -254,11 +254,11 @@ func TestTemplateService(t *testing.T) {
Template: "{{ .NotAField }}",
}
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
@ -282,7 +282,7 @@ func TestTemplateService(t *testing.T) {
t.Run("when config is invalid", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: brokenConfig,
})
@ -305,10 +305,10 @@ func TestTemplateService(t *testing.T) {
t.Run("when provenance fails to save", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithTemplates,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().
DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save provenance"))
@ -321,13 +321,13 @@ func TestTemplateService(t *testing.T) {
t.Run("when AM config fails to save", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithTemplates,
})
sut.config.(*MockAMConfigStore).EXPECT().
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
Return(fmt.Errorf("failed to save config"))
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
err := sut.DeleteTemplate(context.Background(), 1, "template")
@ -338,11 +338,11 @@ func TestTemplateService(t *testing.T) {
t.Run("deletes template from config file on success", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithTemplates,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
err := sut.DeleteTemplate(context.Background(), 1, "a")
@ -352,11 +352,11 @@ func TestTemplateService(t *testing.T) {
t.Run("does not error when deleting templates that do not exist", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: configWithTemplates,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
err := sut.DeleteTemplate(context.Background(), 1, "does not exist")
@ -366,11 +366,11 @@ func TestTemplateService(t *testing.T) {
t.Run("succeeds when deleting from config file with no template section", func(t *testing.T) {
sut := createTemplateServiceSut()
sut.config.(*MockAMConfigStore).EXPECT().
getsConfig(models.AlertConfiguration{
GetsConfig(models.AlertConfiguration{
AlertmanagerConfiguration: defaultConfig,
})
sut.config.(*MockAMConfigStore).EXPECT().saveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().saveSucceeds()
sut.config.(*MockAMConfigStore).EXPECT().SaveSucceeds()
sut.prov.(*MockProvisioningStore).EXPECT().SaveSucceeds()
err := sut.DeleteTemplate(context.Background(), 1, "a")
@ -395,26 +395,6 @@ func createMessageTemplate() definitions.MessageTemplate {
}
}
func (m *MockAMConfigStore_Expecter) getsConfig(ac models.AlertConfiguration) *MockAMConfigStore_Expecter {
m.GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Run(func(ctx context.Context, q *models.GetLatestAlertmanagerConfigurationQuery) {
q.Result = &ac
}).
Return(nil)
return m
}
func (m *MockAMConfigStore_Expecter) saveSucceeds() *MockAMConfigStore_Expecter {
m.UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(nil)
return m
}
func (m *MockProvisioningStore_Expecter) saveSucceeds() *MockProvisioningStore_Expecter {
m.SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
m.DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).Return(nil)
return m
}
var defaultConfig = setting.GetAlertmanagerDefaultConfiguration()
var configWithTemplates = `

View File

@ -7,6 +7,7 @@ import (
"strings"
"github.com/grafana/grafana/pkg/services/ngalert/models"
mock "github.com/stretchr/testify/mock"
)
const defaultAlertmanagerConfigJSON = `
@ -135,12 +136,37 @@ func (f *fakeProvisioningStore) DeleteProvenance(ctx context.Context, o models.P
return nil
}
type nopTransactionManager struct{}
type NopTransactionManager struct{}
func newNopTransactionManager() *nopTransactionManager {
return &nopTransactionManager{}
func newNopTransactionManager() *NopTransactionManager {
return &NopTransactionManager{}
}
func (n *nopTransactionManager) InTransaction(ctx context.Context, work func(ctx context.Context) error) error {
func (n *NopTransactionManager) InTransaction(ctx context.Context, work func(ctx context.Context) error) error {
return work(ctx)
}
func (m *MockAMConfigStore_Expecter) GetsConfig(ac models.AlertConfiguration) *MockAMConfigStore_Expecter {
m.GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
Run(func(ctx context.Context, q *models.GetLatestAlertmanagerConfigurationQuery) {
q.Result = &ac
}).
Return(nil)
return m
}
func (m *MockAMConfigStore_Expecter) SaveSucceeds() *MockAMConfigStore_Expecter {
m.UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(nil)
return m
}
func (m *MockProvisioningStore_Expecter) GetReturns(p models.Provenance) *MockProvisioningStore_Expecter {
m.GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(p, nil)
return m
}
func (m *MockProvisioningStore_Expecter) SaveSucceeds() *MockProvisioningStore_Expecter {
m.SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(nil)
m.DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).Return(nil)
return m
}