mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Move alertmanager api silence code to separate files (#86947)
* Move alertmanager api silence code to separate files unchanged * Replace with silence model instead interface --------- Co-authored-by: Matt Jacobson <matthew.jacobson@grafana.com>
This commit is contained in:
parent
0f98bd3b7b
commit
dff7cb9afb
@ -9,6 +9,7 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -78,10 +79,6 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
type Silence interface {
|
||||
GetRuleUID() *string
|
||||
}
|
||||
|
||||
type RuleUIDToNamespaceStore interface {
|
||||
GetNamespacesByRuleUID(ctx context.Context, orgID int64, uids ...string) (map[string]string, error)
|
||||
}
|
||||
@ -104,7 +101,7 @@ func NewSilenceService(ac ac.AccessControl, store RuleUIDToNamespaceStore) *Sile
|
||||
// Global silence (one that is not attached to a particular rule) is considered available to all users.
|
||||
// For silences that are not attached to a rule, are checked against authorization.
|
||||
// This method is more preferred when many silences need to be checked.
|
||||
func (s SilenceService) FilterByAccess(ctx context.Context, user identity.Requester, silences ...Silence) ([]Silence, error) {
|
||||
func (s SilenceService) FilterByAccess(ctx context.Context, user identity.Requester, silences ...*models.Silence) ([]*models.Silence, error) {
|
||||
canAll, err := s.HasAccess(ctx, user, readAllSilencesEvaluator)
|
||||
if err != nil || canAll { // return early if user can either read all silences or there is an error
|
||||
return silences, err
|
||||
@ -113,8 +110,8 @@ func (s SilenceService) FilterByAccess(ctx context.Context, user identity.Reques
|
||||
if err != nil || !canSome {
|
||||
return nil, err
|
||||
}
|
||||
result := make([]Silence, 0, len(silences))
|
||||
silencesByRuleUID := make(map[string][]Silence, len(silences))
|
||||
result := make([]*models.Silence, 0, len(silences))
|
||||
silencesByRuleUID := make(map[string][]*models.Silence, len(silences))
|
||||
for _, silence := range silences {
|
||||
ruleUID := silence.GetRuleUID()
|
||||
if ruleUID == nil { // if this is a general silence
|
||||
@ -154,7 +151,7 @@ func (s SilenceService) FilterByAccess(ctx context.Context, user identity.Reques
|
||||
}
|
||||
|
||||
// AuthorizeReadSilence checks if user has access to read a silence
|
||||
func (s SilenceService) AuthorizeReadSilence(ctx context.Context, user identity.Requester, silence Silence) error {
|
||||
func (s SilenceService) AuthorizeReadSilence(ctx context.Context, user identity.Requester, silence *models.Silence) error {
|
||||
canAll, err := s.HasAccess(ctx, user, readAllSilencesEvaluator)
|
||||
if canAll || err != nil { // return early if user can either read all silences or there is error
|
||||
return err
|
||||
@ -186,7 +183,7 @@ func (s SilenceService) AuthorizeReadSilence(ctx context.Context, user identity.
|
||||
}
|
||||
|
||||
// AuthorizeCreateSilence checks if user has access to create a silence. Returns ErrAuthorizationBase if user is not authorized
|
||||
func (s SilenceService) AuthorizeCreateSilence(ctx context.Context, user identity.Requester, silence Silence) error {
|
||||
func (s SilenceService) AuthorizeCreateSilence(ctx context.Context, user identity.Requester, silence *models.Silence) error {
|
||||
canAny, err := s.HasAccess(ctx, user, createAnySilenceEvaluator)
|
||||
if err != nil || canAny {
|
||||
// return early if user can either create any silence or there is an error
|
||||
@ -215,7 +212,7 @@ func (s SilenceService) AuthorizeCreateSilence(ctx context.Context, user identit
|
||||
}
|
||||
|
||||
// AuthorizeUpdateSilence checks if user has access to update\expire a silence. Returns ErrAuthorizationBase if user is not authorized
|
||||
func (s SilenceService) AuthorizeUpdateSilence(ctx context.Context, user identity.Requester, silence Silence) error {
|
||||
func (s SilenceService) AuthorizeUpdateSilence(ctx context.Context, user identity.Requester, silence *models.Silence) error {
|
||||
canAny, err := s.HasAccess(ctx, user, updateAnySilenceEvaluator)
|
||||
if err != nil || canAny {
|
||||
// return early if user can either update any silence or there is an error
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils"
|
||||
)
|
||||
@ -18,15 +19,15 @@ import (
|
||||
var orgID = rand.Int63()
|
||||
|
||||
func TestFilterByAccess(t *testing.T) {
|
||||
global := testSilence{ID: "global", RuleUID: nil}
|
||||
ruleSilence1 := testSilence{ID: "rule-1", RuleUID: utils.Pointer("rule-1-uid")}
|
||||
global := testSilence("global", nil)
|
||||
ruleSilence1 := testSilence("rule-1", utils.Pointer("rule-1-uid"))
|
||||
folder1 := "rule-1-folder-uid"
|
||||
folder1Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder1)
|
||||
ruleSilence2 := testSilence{ID: "rule-2", RuleUID: utils.Pointer("rule-2-uid")}
|
||||
ruleSilence2 := testSilence("rule-2", utils.Pointer("rule-2-uid"))
|
||||
folder2 := "rule-2-folder-uid"
|
||||
notFoundRule := testSilence{ID: "unknown-rule", RuleUID: utils.Pointer("unknown-rule-uid")}
|
||||
notFoundRule := testSilence("unknown-rule", utils.Pointer("unknown-rule-uid"))
|
||||
|
||||
silences := []Silence{
|
||||
silences := []*models.Silence{
|
||||
global,
|
||||
ruleSilence1,
|
||||
ruleSilence2,
|
||||
@ -36,19 +37,19 @@ func TestFilterByAccess(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
user identity.Requester
|
||||
expected []Silence
|
||||
expected []*models.Silence
|
||||
expectedDbAccess bool
|
||||
}{
|
||||
{
|
||||
name: "no silence access, empty list",
|
||||
user: newUser(),
|
||||
expected: []Silence{},
|
||||
expected: []*models.Silence{},
|
||||
expectedDbAccess: false,
|
||||
},
|
||||
{
|
||||
name: "instance reader should get all",
|
||||
user: newUser(ac.Permission{Action: instancesRead}),
|
||||
expected: []Silence{
|
||||
expected: []*models.Silence{
|
||||
global,
|
||||
ruleSilence1,
|
||||
ruleSilence2,
|
||||
@ -59,7 +60,7 @@ func TestFilterByAccess(t *testing.T) {
|
||||
{
|
||||
name: "silence reader should get global + folder",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}),
|
||||
expected: []Silence{
|
||||
expected: []*models.Silence{
|
||||
global,
|
||||
ruleSilence1,
|
||||
},
|
||||
@ -71,8 +72,8 @@ func TestFilterByAccess(t *testing.T) {
|
||||
ac := &recordingAccessControlFake{}
|
||||
store := &fakeRuleUIDToNamespaceStore{
|
||||
Response: map[string]string{
|
||||
*ruleSilence1.RuleUID: folder1,
|
||||
*ruleSilence2.RuleUID: folder2,
|
||||
*ruleSilence1.GetRuleUID(): folder1,
|
||||
*ruleSilence2.GetRuleUID(): folder2,
|
||||
},
|
||||
}
|
||||
svc := NewSilenceService(ac, store)
|
||||
@ -93,60 +94,60 @@ func TestFilterByAccess(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthorizeReadSilence(t *testing.T) {
|
||||
global := testSilence{ID: "global", RuleUID: nil}
|
||||
ruleSilence1 := testSilence{ID: "rule-1", RuleUID: utils.Pointer("rule-1-uid")}
|
||||
global := testSilence("global", nil)
|
||||
ruleSilence1 := testSilence("rule-1", utils.Pointer("rule-1-uid"))
|
||||
folder1 := "rule-1-folder-uid"
|
||||
folder1Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder1)
|
||||
ruleSilence2 := testSilence{ID: "rule-2", RuleUID: utils.Pointer("rule-2-uid")}
|
||||
ruleSilence2 := testSilence("rule-2", utils.Pointer("rule-2-uid"))
|
||||
folder2 := "rule-2-folder-uid"
|
||||
notFoundRule := testSilence{ID: "unknown-rule", RuleUID: utils.Pointer("unknown-rule-uid")}
|
||||
notFoundRule := testSilence("unknown-rule", utils.Pointer("unknown-rule-uid"))
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
user identity.Requester
|
||||
silence []testSilence
|
||||
silence []*models.Silence
|
||||
expectedErr error
|
||||
expectedDbAccess bool
|
||||
}{
|
||||
{
|
||||
name: "not authorized without permissions",
|
||||
user: newUser(),
|
||||
silence: []testSilence{global, ruleSilence1, notFoundRule},
|
||||
silence: []*models.Silence{global, ruleSilence1, notFoundRule},
|
||||
expectedErr: ErrAuthorizationBase,
|
||||
expectedDbAccess: false,
|
||||
},
|
||||
{
|
||||
name: "instance reader can read any silence",
|
||||
user: newUser(ac.Permission{Action: instancesRead}),
|
||||
silence: []testSilence{global, ruleSilence1, notFoundRule},
|
||||
silence: []*models.Silence{global, ruleSilence1, notFoundRule},
|
||||
expectedErr: nil,
|
||||
expectedDbAccess: false,
|
||||
},
|
||||
{
|
||||
name: "silence reader can read global",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}),
|
||||
silence: []testSilence{global},
|
||||
silence: []*models.Silence{global},
|
||||
expectedErr: nil,
|
||||
expectedDbAccess: false,
|
||||
},
|
||||
{
|
||||
name: "silence reader can read from allowed folder",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}),
|
||||
silence: []testSilence{ruleSilence1},
|
||||
silence: []*models.Silence{ruleSilence1},
|
||||
expectedErr: nil,
|
||||
expectedDbAccess: true,
|
||||
},
|
||||
{
|
||||
name: "silence reader cannot read from other folders",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}),
|
||||
silence: []testSilence{ruleSilence2},
|
||||
silence: []*models.Silence{ruleSilence2},
|
||||
expectedErr: ErrAuthorizationBase,
|
||||
expectedDbAccess: true,
|
||||
},
|
||||
{
|
||||
name: "silence reader cannot read unknown rule",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}),
|
||||
silence: []testSilence{notFoundRule},
|
||||
silence: []*models.Silence{notFoundRule},
|
||||
expectedErr: ErrAuthorizationBase,
|
||||
expectedDbAccess: true,
|
||||
},
|
||||
@ -155,12 +156,12 @@ func TestAuthorizeReadSilence(t *testing.T) {
|
||||
for _, testCase := range testCases {
|
||||
t.Run(testCase.name, func(t *testing.T) {
|
||||
for _, silence := range testCase.silence {
|
||||
t.Run(silence.ID, func(t *testing.T) {
|
||||
t.Run(*silence.ID, func(t *testing.T) {
|
||||
ac := &recordingAccessControlFake{}
|
||||
store := &fakeRuleUIDToNamespaceStore{
|
||||
Response: map[string]string{
|
||||
*ruleSilence1.RuleUID: folder1,
|
||||
*ruleSilence2.RuleUID: folder2,
|
||||
*ruleSilence1.GetRuleUID(): folder1,
|
||||
*ruleSilence2.GetRuleUID(): folder2,
|
||||
},
|
||||
}
|
||||
svc := NewSilenceService(ac, store)
|
||||
@ -183,16 +184,16 @@ func TestAuthorizeReadSilence(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthorizeCreateSilence(t *testing.T) {
|
||||
global := testSilence{ID: "global", RuleUID: nil}
|
||||
ruleSilence1 := testSilence{ID: "rule-1", RuleUID: utils.Pointer("rule-1-uid")}
|
||||
global := testSilence("global", nil)
|
||||
ruleSilence1 := testSilence("rule-1", utils.Pointer("rule-1-uid"))
|
||||
folder1 := "rule-1-folder-uid"
|
||||
folder1Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder1)
|
||||
ruleSilence2 := testSilence{ID: "rule-2", RuleUID: utils.Pointer("rule-2-uid")}
|
||||
ruleSilence2 := testSilence("rule-2", utils.Pointer("rule-2-uid"))
|
||||
folder2 := "rule-2-folder-uid"
|
||||
folder2Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2)
|
||||
notFoundRule := testSilence{ID: "unknown-rule", RuleUID: utils.Pointer("unknown-rule-uid")}
|
||||
notFoundRule := testSilence("unknown-rule", utils.Pointer("unknown-rule-uid"))
|
||||
|
||||
silences := []testSilence{
|
||||
silences := []*models.Silence{
|
||||
global,
|
||||
ruleSilence1,
|
||||
ruleSilence2,
|
||||
@ -208,7 +209,7 @@ func TestAuthorizeCreateSilence(t *testing.T) {
|
||||
user identity.Requester
|
||||
expectedErr error
|
||||
expectedDbAccess bool
|
||||
overrides map[testSilence]override
|
||||
overrides map[*models.Silence]override
|
||||
}{
|
||||
{
|
||||
name: "not authorized without permissions",
|
||||
@ -243,7 +244,7 @@ func TestAuthorizeCreateSilence(t *testing.T) {
|
||||
{
|
||||
name: "instance read + silence create",
|
||||
user: newUser(ac.Permission{Action: silenceCreate, Scope: folder1Scope}, ac.Permission{Action: instancesRead}),
|
||||
overrides: map[testSilence]override{
|
||||
overrides: map[*models.Silence]override{
|
||||
global: {
|
||||
expectedErr: ErrAuthorizationBase,
|
||||
expectedDbAccess: false,
|
||||
@ -259,7 +260,7 @@ func TestAuthorizeCreateSilence(t *testing.T) {
|
||||
{
|
||||
name: "silence read + instance create",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}, ac.Permission{Action: instancesCreate}),
|
||||
overrides: map[testSilence]override{
|
||||
overrides: map[*models.Silence]override{
|
||||
global: {
|
||||
expectedErr: nil,
|
||||
expectedDbAccess: false,
|
||||
@ -275,7 +276,7 @@ func TestAuthorizeCreateSilence(t *testing.T) {
|
||||
{
|
||||
name: "silence read + create",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}, ac.Permission{Action: silenceCreate, Scope: folder1Scope}),
|
||||
overrides: map[testSilence]override{
|
||||
overrides: map[*models.Silence]override{
|
||||
global: {
|
||||
expectedErr: ErrAuthorizationBase,
|
||||
expectedDbAccess: false,
|
||||
@ -299,12 +300,12 @@ func TestAuthorizeCreateSilence(t *testing.T) {
|
||||
expectedErr = s.expectedErr
|
||||
expectedDbAccess = s.expectedDbAccess
|
||||
}
|
||||
t.Run(silence.ID, func(t *testing.T) {
|
||||
t.Run(*silence.ID, func(t *testing.T) {
|
||||
ac := &recordingAccessControlFake{}
|
||||
store := &fakeRuleUIDToNamespaceStore{
|
||||
Response: map[string]string{
|
||||
*ruleSilence1.RuleUID: folder1,
|
||||
*ruleSilence2.RuleUID: folder2,
|
||||
*ruleSilence1.GetRuleUID(): folder1,
|
||||
*ruleSilence2.GetRuleUID(): folder2,
|
||||
},
|
||||
}
|
||||
svc := NewSilenceService(ac, store)
|
||||
@ -328,16 +329,16 @@ func TestAuthorizeCreateSilence(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAuthorizeUpdateSilence(t *testing.T) {
|
||||
global := testSilence{ID: "global", RuleUID: nil}
|
||||
ruleSilence1 := testSilence{ID: "rule-1", RuleUID: utils.Pointer("rule-1-uid")}
|
||||
global := testSilence("global", nil)
|
||||
ruleSilence1 := testSilence("rule-1", utils.Pointer("rule-1-uid"))
|
||||
folder1 := "rule-1-folder-uid"
|
||||
folder1Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder1)
|
||||
ruleSilence2 := testSilence{ID: "rule-2", RuleUID: utils.Pointer("rule-2-uid")}
|
||||
ruleSilence2 := testSilence("rule-2", utils.Pointer("rule-2-uid"))
|
||||
folder2 := "rule-2-folder-uid"
|
||||
folder2Scope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder2)
|
||||
notFoundRule := testSilence{ID: "unknown-rule", RuleUID: utils.Pointer("unknown-rule-uid")}
|
||||
notFoundRule := testSilence("unknown-rule", utils.Pointer("unknown-rule-uid"))
|
||||
|
||||
silences := []testSilence{
|
||||
silences := []*models.Silence{
|
||||
global,
|
||||
ruleSilence1,
|
||||
ruleSilence2,
|
||||
@ -353,7 +354,7 @@ func TestAuthorizeUpdateSilence(t *testing.T) {
|
||||
user identity.Requester
|
||||
expectedErr error
|
||||
expectedDbAccess bool
|
||||
overrides map[testSilence]override
|
||||
overrides map[*models.Silence]override
|
||||
}{
|
||||
{
|
||||
name: "not authorized without permissions",
|
||||
@ -388,7 +389,7 @@ func TestAuthorizeUpdateSilence(t *testing.T) {
|
||||
{
|
||||
name: "instance read + silence write",
|
||||
user: newUser(ac.Permission{Action: silenceWrite, Scope: folder1Scope}, ac.Permission{Action: instancesRead}),
|
||||
overrides: map[testSilence]override{
|
||||
overrides: map[*models.Silence]override{
|
||||
global: {
|
||||
expectedErr: ErrAuthorizationBase,
|
||||
expectedDbAccess: false,
|
||||
@ -404,7 +405,7 @@ func TestAuthorizeUpdateSilence(t *testing.T) {
|
||||
{
|
||||
name: "silence read + instance write",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}, ac.Permission{Action: instancesWrite}),
|
||||
overrides: map[testSilence]override{
|
||||
overrides: map[*models.Silence]override{
|
||||
global: {
|
||||
expectedErr: nil,
|
||||
expectedDbAccess: false,
|
||||
@ -420,7 +421,7 @@ func TestAuthorizeUpdateSilence(t *testing.T) {
|
||||
{
|
||||
name: "silence read + write",
|
||||
user: newUser(ac.Permission{Action: silenceRead, Scope: folder1Scope}, ac.Permission{Action: silenceWrite, Scope: folder1Scope}),
|
||||
overrides: map[testSilence]override{
|
||||
overrides: map[*models.Silence]override{
|
||||
global: {
|
||||
expectedErr: ErrAuthorizationBase,
|
||||
expectedDbAccess: false,
|
||||
@ -444,12 +445,12 @@ func TestAuthorizeUpdateSilence(t *testing.T) {
|
||||
expectedErr = s.expectedErr
|
||||
expectedDbAccess = s.expectedDbAccess
|
||||
}
|
||||
t.Run(silence.ID, func(t *testing.T) {
|
||||
t.Run(*silence.ID, func(t *testing.T) {
|
||||
ac := &recordingAccessControlFake{}
|
||||
store := &fakeRuleUIDToNamespaceStore{
|
||||
Response: map[string]string{
|
||||
*ruleSilence1.RuleUID: folder1,
|
||||
*ruleSilence2.RuleUID: folder2,
|
||||
*ruleSilence1.GetRuleUID(): folder1,
|
||||
*ruleSilence2.GetRuleUID(): folder2,
|
||||
},
|
||||
}
|
||||
svc := NewSilenceService(ac, store)
|
||||
@ -472,13 +473,8 @@ func TestAuthorizeUpdateSilence(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type testSilence struct {
|
||||
ID string
|
||||
RuleUID *string
|
||||
}
|
||||
|
||||
func (t testSilence) GetRuleUID() *string {
|
||||
return t.RuleUID
|
||||
func testSilence(id string, ruleUID *string) *models.Silence {
|
||||
return &models.Silence{ID: &id, RuleUID: ruleUID}
|
||||
}
|
||||
|
||||
type fakeRuleUIDToNamespaceStore struct {
|
||||
|
@ -9,15 +9,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
|
||||
alertingNotify "github.com/grafana/alerting/notify"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@ -59,50 +56,6 @@ func (srv AlertmanagerSrv) RouteGetAMStatus(c *contextmodel.ReqContext) response
|
||||
return response.JSON(http.StatusOK, status)
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postableSilence apimodels.PostableSilence) response.Response {
|
||||
err := postableSilence.Validate(strfmt.Default)
|
||||
if err != nil {
|
||||
srv.log.Error("Silence failed validation", "error", err)
|
||||
return ErrResp(http.StatusBadRequest, err, "silence failed validation")
|
||||
}
|
||||
|
||||
action := accesscontrol.ActionAlertingInstanceUpdate
|
||||
if postableSilence.ID == "" {
|
||||
action = accesscontrol.ActionAlertingInstanceCreate
|
||||
}
|
||||
evaluator := accesscontrol.EvalPermission(action)
|
||||
if !accesscontrol.HasAccess(srv.ac, c)(evaluator) {
|
||||
errAction := "update"
|
||||
if postableSilence.ID == "" {
|
||||
errAction = "create"
|
||||
}
|
||||
return response.Err(authz.NewAuthorizationErrorWithPermissions(fmt.Sprintf("%s silences", errAction), evaluator))
|
||||
}
|
||||
|
||||
silenceID, err := srv.mam.CreateSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), &postableSilence)
|
||||
if err != nil {
|
||||
if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
if errors.Is(err, notifier.ErrAlertmanagerNotReady) {
|
||||
return ErrResp(http.StatusConflict, err, "")
|
||||
}
|
||||
|
||||
if errors.Is(err, alertingNotify.ErrSilenceNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
|
||||
if errors.Is(err, alertingNotify.ErrCreateSilenceBadPayload) {
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create silence")
|
||||
}
|
||||
return response.JSON(http.StatusAccepted, apimodels.PostSilencesOKBody{
|
||||
SilenceID: silenceID,
|
||||
})
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteDeleteAlertingConfig(c *contextmodel.ReqContext) response.Response {
|
||||
am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID())
|
||||
if errResp != nil {
|
||||
@ -117,22 +70,6 @@ func (srv AlertmanagerSrv) RouteDeleteAlertingConfig(c *contextmodel.ReqContext)
|
||||
return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration deleted; the default is applied"})
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteDeleteSilence(c *contextmodel.ReqContext, silenceID string) response.Response {
|
||||
if err := srv.mam.DeleteSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), silenceID); err != nil {
|
||||
if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
if errors.Is(err, notifier.ErrAlertmanagerNotReady) {
|
||||
return ErrResp(http.StatusConflict, err, "")
|
||||
}
|
||||
if errors.Is(err, alertingNotify.ErrSilenceNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusOK, util.DynMap{"message": "silence deleted"})
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteGetAlertingConfig(c *contextmodel.ReqContext) response.Response {
|
||||
canSeeAutogen := c.SignedInUser.HasRole(org.RoleAdmin)
|
||||
config, err := srv.mam.GetAlertmanagerConfiguration(c.Req.Context(), c.SignedInUser.GetOrgID(), canSeeAutogen)
|
||||
@ -208,40 +145,6 @@ func (srv AlertmanagerSrv) RouteGetAMAlerts(c *contextmodel.ReqContext) response
|
||||
return response.JSON(http.StatusOK, alerts)
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteGetSilence(c *contextmodel.ReqContext, silenceID string) response.Response {
|
||||
am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID())
|
||||
if errResp != nil {
|
||||
return errResp
|
||||
}
|
||||
|
||||
gettableSilence, err := am.GetSilence(c.Req.Context(), silenceID)
|
||||
if err != nil {
|
||||
if errors.Is(err, alertingNotify.ErrSilenceNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
// any other error here should be an unexpected failure and thus an internal error
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusOK, gettableSilence)
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteGetSilences(c *contextmodel.ReqContext) response.Response {
|
||||
am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID())
|
||||
if errResp != nil {
|
||||
return errResp
|
||||
}
|
||||
|
||||
gettableSilences, err := am.ListSilences(c.Req.Context(), c.QueryStrings("filter"))
|
||||
if err != nil {
|
||||
if errors.Is(err, alertingNotify.ErrListSilencesBadPayload) {
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
// any other error here should be an unexpected failure and thus an internal error
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusOK, gettableSilences)
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RoutePostGrafanaAlertingConfigHistoryActivate(c *contextmodel.ReqContext, id string) response.Response {
|
||||
confId, err := strconv.ParseInt(id, 10, 64)
|
||||
if err != nil {
|
||||
|
112
pkg/services/ngalert/api/api_alertmanager_silences.go
Normal file
112
pkg/services/ngalert/api/api_alertmanager_silences.go
Normal file
@ -0,0 +1,112 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
|
||||
alertingNotify "github.com/grafana/alerting/notify"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
authz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func (srv AlertmanagerSrv) RouteGetSilence(c *contextmodel.ReqContext, silenceID string) response.Response {
|
||||
am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID())
|
||||
if errResp != nil {
|
||||
return errResp
|
||||
}
|
||||
|
||||
gettableSilence, err := am.GetSilence(c.Req.Context(), silenceID)
|
||||
if err != nil {
|
||||
if errors.Is(err, alertingNotify.ErrSilenceNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
// any other error here should be an unexpected failure and thus an internal error
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusOK, gettableSilence)
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteGetSilences(c *contextmodel.ReqContext) response.Response {
|
||||
am, errResp := srv.AlertmanagerFor(c.SignedInUser.GetOrgID())
|
||||
if errResp != nil {
|
||||
return errResp
|
||||
}
|
||||
|
||||
gettableSilences, err := am.ListSilences(c.Req.Context(), c.QueryStrings("filter"))
|
||||
if err != nil {
|
||||
if errors.Is(err, alertingNotify.ErrListSilencesBadPayload) {
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
// any other error here should be an unexpected failure and thus an internal error
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusOK, gettableSilences)
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteCreateSilence(c *contextmodel.ReqContext, postableSilence apimodels.PostableSilence) response.Response {
|
||||
err := postableSilence.Validate(strfmt.Default)
|
||||
if err != nil {
|
||||
srv.log.Error("Silence failed validation", "error", err)
|
||||
return ErrResp(http.StatusBadRequest, err, "silence failed validation")
|
||||
}
|
||||
|
||||
action := accesscontrol.ActionAlertingInstanceUpdate
|
||||
if postableSilence.ID == "" {
|
||||
action = accesscontrol.ActionAlertingInstanceCreate
|
||||
}
|
||||
evaluator := accesscontrol.EvalPermission(action)
|
||||
if !accesscontrol.HasAccess(srv.ac, c)(evaluator) {
|
||||
errAction := "update"
|
||||
if postableSilence.ID == "" {
|
||||
errAction = "create"
|
||||
}
|
||||
return response.Err(authz.NewAuthorizationErrorWithPermissions(fmt.Sprintf("%s silences", errAction), evaluator))
|
||||
}
|
||||
|
||||
silenceID, err := srv.mam.CreateSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), &postableSilence)
|
||||
if err != nil {
|
||||
if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
if errors.Is(err, notifier.ErrAlertmanagerNotReady) {
|
||||
return ErrResp(http.StatusConflict, err, "")
|
||||
}
|
||||
|
||||
if errors.Is(err, alertingNotify.ErrSilenceNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
|
||||
if errors.Is(err, alertingNotify.ErrCreateSilenceBadPayload) {
|
||||
return ErrResp(http.StatusBadRequest, err, "")
|
||||
}
|
||||
|
||||
return ErrResp(http.StatusInternalServerError, err, "failed to create silence")
|
||||
}
|
||||
return response.JSON(http.StatusAccepted, apimodels.PostSilencesOKBody{
|
||||
SilenceID: silenceID,
|
||||
})
|
||||
}
|
||||
|
||||
func (srv AlertmanagerSrv) RouteDeleteSilence(c *contextmodel.ReqContext, silenceID string) response.Response {
|
||||
if err := srv.mam.DeleteSilence(c.Req.Context(), c.SignedInUser.GetOrgID(), silenceID); err != nil {
|
||||
if errors.Is(err, notifier.ErrNoAlertmanagerForOrg) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
if errors.Is(err, notifier.ErrAlertmanagerNotReady) {
|
||||
return ErrResp(http.StatusConflict, err, "")
|
||||
}
|
||||
if errors.Is(err, alertingNotify.ErrSilenceNotFound) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
return ErrResp(http.StatusInternalServerError, err, "")
|
||||
}
|
||||
return response.JSON(http.StatusOK, util.DynMap{"message": "silence deleted"})
|
||||
}
|
194
pkg/services/ngalert/api/api_alertmanager_silences_test.go
Normal file
194
pkg/services/ngalert/api/api_alertmanager_silences_test.go
Normal file
@ -0,0 +1,194 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
func TestSilenceCreate(t *testing.T) {
|
||||
makeSilence := func(comment string, createdBy string,
|
||||
startsAt, endsAt strfmt.DateTime, matchers amv2.Matchers) amv2.Silence {
|
||||
return amv2.Silence{
|
||||
Comment: &comment,
|
||||
CreatedBy: &createdBy,
|
||||
StartsAt: &startsAt,
|
||||
EndsAt: &endsAt,
|
||||
Matchers: matchers,
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dt := func(t time.Time) strfmt.DateTime { return strfmt.DateTime(t) }
|
||||
tru := true
|
||||
testString := "testName"
|
||||
matchers := amv2.Matchers{&amv2.Matcher{Name: &testString, IsEqual: &tru, IsRegex: &tru, Value: &testString}}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
silence amv2.Silence
|
||||
status int
|
||||
}{
|
||||
{"Valid Silence",
|
||||
makeSilence("", "tests", dt(now), dt(now.Add(1*time.Second)), matchers),
|
||||
http.StatusAccepted,
|
||||
},
|
||||
{"No Comment Silence",
|
||||
func() amv2.Silence {
|
||||
s := makeSilence("", "tests", dt(now), dt(now.Add(1*time.Second)), matchers)
|
||||
s.Comment = nil
|
||||
return s
|
||||
}(),
|
||||
http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cas := range cases {
|
||||
t.Run(cas.name, func(t *testing.T) {
|
||||
rc := contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgRole: org.RoleEditor,
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
srv := createSut(t)
|
||||
|
||||
resp := srv.RouteCreateSilence(&rc, amv2.PostableSilence{
|
||||
ID: "",
|
||||
Silence: cas.silence,
|
||||
})
|
||||
require.Equal(t, cas.status, resp.Status())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouteCreateSilence(t *testing.T) {
|
||||
tesCases := []struct {
|
||||
name string
|
||||
silence func() apimodels.PostableSilence
|
||||
permissions map[int64]map[string][]string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "new silence, role-based access control is enabled, not authorized",
|
||||
silence: silenceGen(withEmptyID),
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {},
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "new silence, role-based access control is enabled, authorized",
|
||||
silence: silenceGen(withEmptyID),
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is enabled, not authorized",
|
||||
silence: silenceGen(),
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is enabled, authorized",
|
||||
silence: silenceGen(),
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceUpdate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tesCase := range tesCases {
|
||||
t.Run(tesCase.name, func(t *testing.T) {
|
||||
sut := createSut(t)
|
||||
|
||||
rc := contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
Permissions: tesCase.permissions,
|
||||
OrgID: 1,
|
||||
},
|
||||
}
|
||||
|
||||
silence := tesCase.silence()
|
||||
|
||||
if silence.ID != "" {
|
||||
alertmanagerFor, err := sut.mam.AlertmanagerFor(1)
|
||||
require.NoError(t, err)
|
||||
silence.ID = ""
|
||||
newID, err := alertmanagerFor.CreateSilence(context.Background(), &silence)
|
||||
require.NoError(t, err)
|
||||
silence.ID = newID
|
||||
}
|
||||
|
||||
response := sut.RouteCreateSilence(&rc, silence)
|
||||
require.Equal(t, tesCase.expectedStatus, response.Status())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func silenceGen(mutatorFuncs ...func(*apimodels.PostableSilence)) func() apimodels.PostableSilence {
|
||||
return func() apimodels.PostableSilence {
|
||||
testString := util.GenerateShortUID()
|
||||
isEqual := rand.Int()%2 == 0
|
||||
isRegex := rand.Int()%2 == 0
|
||||
value := util.GenerateShortUID()
|
||||
if isRegex {
|
||||
value = ".*" + util.GenerateShortUID()
|
||||
}
|
||||
|
||||
matchers := amv2.Matchers{&amv2.Matcher{Name: &testString, IsEqual: &isEqual, IsRegex: &isRegex, Value: &value}}
|
||||
comment := util.GenerateShortUID()
|
||||
starts := strfmt.DateTime(timeNow().Add(-time.Duration(rand.Int63n(9)+1) * time.Second))
|
||||
ends := strfmt.DateTime(timeNow().Add(time.Duration(rand.Int63n(9)+1) * time.Second))
|
||||
createdBy := "User-" + util.GenerateShortUID()
|
||||
s := apimodels.PostableSilence{
|
||||
ID: util.GenerateShortUID(),
|
||||
Silence: amv2.Silence{
|
||||
Comment: &comment,
|
||||
CreatedBy: &createdBy,
|
||||
EndsAt: &ends,
|
||||
Matchers: matchers,
|
||||
StartsAt: &starts,
|
||||
},
|
||||
}
|
||||
|
||||
for _, mutator := range mutatorFuncs {
|
||||
mutator(&s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func withEmptyID(silence *apimodels.PostableSilence) {
|
||||
silence.ID = ""
|
||||
}
|
@ -4,23 +4,20 @@ import (
|
||||
"context"
|
||||
"crypto/md5"
|
||||
"encoding/json"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
alertingNotify "github.com/grafana/alerting/notify"
|
||||
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
alertingNotify "github.com/grafana/alerting/notify"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@ -35,7 +32,6 @@ import (
|
||||
secretsManager "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
@ -624,141 +620,6 @@ func TestRoutePostTestTemplates(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestSilenceCreate(t *testing.T) {
|
||||
makeSilence := func(comment string, createdBy string,
|
||||
startsAt, endsAt strfmt.DateTime, matchers amv2.Matchers) amv2.Silence {
|
||||
return amv2.Silence{
|
||||
Comment: &comment,
|
||||
CreatedBy: &createdBy,
|
||||
StartsAt: &startsAt,
|
||||
EndsAt: &endsAt,
|
||||
Matchers: matchers,
|
||||
}
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
dt := func(t time.Time) strfmt.DateTime { return strfmt.DateTime(t) }
|
||||
tru := true
|
||||
testString := "testName"
|
||||
matchers := amv2.Matchers{&amv2.Matcher{Name: &testString, IsEqual: &tru, IsRegex: &tru, Value: &testString}}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
silence amv2.Silence
|
||||
status int
|
||||
}{
|
||||
{"Valid Silence",
|
||||
makeSilence("", "tests", dt(now), dt(now.Add(1*time.Second)), matchers),
|
||||
http.StatusAccepted,
|
||||
},
|
||||
{"No Comment Silence",
|
||||
func() amv2.Silence {
|
||||
s := makeSilence("", "tests", dt(now), dt(now.Add(1*time.Second)), matchers)
|
||||
s.Comment = nil
|
||||
return s
|
||||
}(),
|
||||
http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
|
||||
for _, cas := range cases {
|
||||
t.Run(cas.name, func(t *testing.T) {
|
||||
rc := contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
OrgRole: org.RoleEditor,
|
||||
OrgID: 1,
|
||||
Permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
srv := createSut(t)
|
||||
|
||||
resp := srv.RouteCreateSilence(&rc, amv2.PostableSilence{
|
||||
ID: "",
|
||||
Silence: cas.silence,
|
||||
})
|
||||
require.Equal(t, cas.status, resp.Status())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRouteCreateSilence(t *testing.T) {
|
||||
tesCases := []struct {
|
||||
name string
|
||||
silence func() apimodels.PostableSilence
|
||||
permissions map[int64]map[string][]string
|
||||
expectedStatus int
|
||||
}{
|
||||
{
|
||||
name: "new silence, role-based access control is enabled, not authorized",
|
||||
silence: silenceGen(withEmptyID),
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {},
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "new silence, role-based access control is enabled, authorized",
|
||||
silence: silenceGen(withEmptyID),
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is enabled, not authorized",
|
||||
silence: silenceGen(),
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceCreate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusForbidden,
|
||||
},
|
||||
{
|
||||
name: "update silence, role-based access control is enabled, authorized",
|
||||
silence: silenceGen(),
|
||||
permissions: map[int64]map[string][]string{
|
||||
1: {accesscontrol.ActionAlertingInstanceUpdate: {}},
|
||||
},
|
||||
expectedStatus: http.StatusAccepted,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tesCase := range tesCases {
|
||||
t.Run(tesCase.name, func(t *testing.T) {
|
||||
sut := createSut(t)
|
||||
|
||||
rc := contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Req: &http.Request{},
|
||||
},
|
||||
SignedInUser: &user.SignedInUser{
|
||||
Permissions: tesCase.permissions,
|
||||
OrgID: 1,
|
||||
},
|
||||
}
|
||||
|
||||
silence := tesCase.silence()
|
||||
|
||||
if silence.ID != "" {
|
||||
alertmanagerFor, err := sut.mam.AlertmanagerFor(1)
|
||||
require.NoError(t, err)
|
||||
silence.ID = ""
|
||||
newID, err := alertmanagerFor.CreateSilence(context.Background(), &silence)
|
||||
require.NoError(t, err)
|
||||
silence.ID = newID
|
||||
}
|
||||
|
||||
response := sut.RouteCreateSilence(&rc, silence)
|
||||
require.Equal(t, tesCase.expectedStatus, response.Status())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createSut(t *testing.T) AlertmanagerSrv {
|
||||
t.Helper()
|
||||
|
||||
@ -973,44 +834,6 @@ var brokenConfig = `
|
||||
}
|
||||
}`
|
||||
|
||||
func silenceGen(mutatorFuncs ...func(*apimodels.PostableSilence)) func() apimodels.PostableSilence {
|
||||
return func() apimodels.PostableSilence {
|
||||
testString := util.GenerateShortUID()
|
||||
isEqual := rand.Int()%2 == 0
|
||||
isRegex := rand.Int()%2 == 0
|
||||
value := util.GenerateShortUID()
|
||||
if isRegex {
|
||||
value = ".*" + util.GenerateShortUID()
|
||||
}
|
||||
|
||||
matchers := amv2.Matchers{&amv2.Matcher{Name: &testString, IsEqual: &isEqual, IsRegex: &isRegex, Value: &value}}
|
||||
comment := util.GenerateShortUID()
|
||||
starts := strfmt.DateTime(timeNow().Add(-time.Duration(rand.Int63n(9)+1) * time.Second))
|
||||
ends := strfmt.DateTime(timeNow().Add(time.Duration(rand.Int63n(9)+1) * time.Second))
|
||||
createdBy := "User-" + util.GenerateShortUID()
|
||||
s := apimodels.PostableSilence{
|
||||
ID: util.GenerateShortUID(),
|
||||
Silence: amv2.Silence{
|
||||
Comment: &comment,
|
||||
CreatedBy: &createdBy,
|
||||
EndsAt: &ends,
|
||||
Matchers: matchers,
|
||||
StartsAt: &starts,
|
||||
},
|
||||
}
|
||||
|
||||
for _, mutator := range mutatorFuncs {
|
||||
mutator(&s)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
func withEmptyID(silence *apimodels.PostableSilence) {
|
||||
silence.ID = ""
|
||||
}
|
||||
|
||||
func createRequestCtxInOrg(org int64) *contextmodel.ReqContext {
|
||||
return &contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
|
11
pkg/services/ngalert/models/silence.go
Normal file
11
pkg/services/ngalert/models/silence.go
Normal file
@ -0,0 +1,11 @@
|
||||
package models
|
||||
|
||||
// Silence is the model-layer representation of an alertmanager silence.
|
||||
type Silence struct { // TODO implement using matchers
|
||||
ID *string
|
||||
RuleUID *string
|
||||
}
|
||||
|
||||
func (s *Silence) GetRuleUID() *string {
|
||||
return s.RuleUID
|
||||
}
|
Loading…
Reference in New Issue
Block a user