mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Receivers API (read only endpoints) (#81751)
* Add single receiver method * Add receiver permissions * Add single/multi GET endpoints for receivers * Remove stable tag from time intervals See end of PR description here: https://github.com/grafana/grafana/pull/81672
This commit is contained in:
parent
49b49e28af
commit
2ab7d3c725
@ -449,6 +449,11 @@ const (
|
|||||||
ActionAlertingNotificationsTimeIntervalsRead = "alert.notifications.time-intervals:read"
|
ActionAlertingNotificationsTimeIntervalsRead = "alert.notifications.time-intervals:read"
|
||||||
ActionAlertingNotificationsTimeIntervalsWrite = "alert.notifications.time-intervals:write"
|
ActionAlertingNotificationsTimeIntervalsWrite = "alert.notifications.time-intervals:write"
|
||||||
|
|
||||||
|
// Alerting receiver actions
|
||||||
|
ActionAlertingReceiversList = "alert.notifications.receivers:list"
|
||||||
|
ActionAlertingReceiversRead = "alert.notifications.receivers:read"
|
||||||
|
ActionAlertingReceiversReadSecrets = "alert.notifications.receivers.secrets:read"
|
||||||
|
|
||||||
// External alerting rule actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
|
// External alerting rule actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
|
||||||
ActionAlertingRuleExternalWrite = "alert.rules.external:write"
|
ActionAlertingRuleExternalWrite = "alert.rules.external:write"
|
||||||
ActionAlertingRuleExternalRead = "alert.rules.external:read"
|
ActionAlertingRuleExternalRead = "alert.rules.external:read"
|
||||||
|
@ -25,8 +25,12 @@ var (
|
|||||||
Action: accesscontrol.ActionAlertingRuleExternalRead,
|
Action: accesscontrol.ActionAlertingRuleExternalRead,
|
||||||
Scope: datasources.ScopeAll,
|
Scope: datasources.ScopeAll,
|
||||||
},
|
},
|
||||||
|
// Following are needed for simplified notification policies
|
||||||
{
|
{
|
||||||
Action: accesscontrol.ActionAlertingNotificationsTimeIntervalsRead, // This is needed for simplified notification policies
|
Action: accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Action: accesscontrol.ActionAlertingReceiversList,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -115,6 +119,9 @@ var (
|
|||||||
{
|
{
|
||||||
Action: accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
Action: accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Action: accesscontrol.ActionAlertingReceiversRead,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ type API struct {
|
|||||||
StateManager *state.Manager
|
StateManager *state.Manager
|
||||||
AccessControl ac.AccessControl
|
AccessControl ac.AccessControl
|
||||||
Policies *provisioning.NotificationPolicyService
|
Policies *provisioning.NotificationPolicyService
|
||||||
|
ReceiverService *notifier.ReceiverService
|
||||||
ContactPointService *provisioning.ContactPointService
|
ContactPointService *provisioning.ContactPointService
|
||||||
Templates *provisioning.TemplateService
|
Templates *provisioning.TemplateService
|
||||||
MuteTimings *provisioning.MuteTimingService
|
MuteTimings *provisioning.MuteTimingService
|
||||||
@ -152,7 +153,11 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
|||||||
hist: api.Historian,
|
hist: api.Historian,
|
||||||
}), m)
|
}), m)
|
||||||
|
|
||||||
api.RegisterNotificationsApiEndpoints(NewNotificationsApi(api.MuteTimings), m)
|
api.RegisterNotificationsApiEndpoints(NewNotificationsApi(&NotificationSrv{
|
||||||
|
logger: logger,
|
||||||
|
receiverService: api.ReceiverService,
|
||||||
|
muteTimingService: api.MuteTimings,
|
||||||
|
}), m)
|
||||||
|
|
||||||
// Inject upgrade endpoints if legacy alerting is enabled and the feature flag is enabled.
|
// Inject upgrade endpoints if legacy alerting is enabled and the feature flag is enabled.
|
||||||
if !api.Cfg.UnifiedAlerting.IsEnabled() && api.FeatureManager.IsEnabledGlobally(featuremgmt.FlagAlertingPreviewUpgrade) {
|
if !api.Cfg.UnifiedAlerting.IsEnabled() && api.FeatureManager.IsEnabledGlobally(featuremgmt.FlagAlertingPreviewUpgrade) {
|
||||||
|
83
pkg/services/ngalert/api/api_notifications.go
Normal file
83
pkg/services/ngalert/api/api_notifications.go
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
|
"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/notifier"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NotificationSrv struct {
|
||||||
|
logger log.Logger
|
||||||
|
receiverService ReceiverService
|
||||||
|
muteTimingService MuteTimingService // defined in api_provisioning.go
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReceiverService interface {
|
||||||
|
GetReceiver(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error)
|
||||||
|
GetReceivers(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *NotificationSrv) RouteGetTimeInterval(c *contextmodel.ReqContext, name string) response.Response {
|
||||||
|
muteTimeInterval, err := srv.muteTimingService.GetMuteTiming(c.Req.Context(), name, c.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return errorToResponse(err)
|
||||||
|
}
|
||||||
|
return response.JSON(http.StatusOK, muteTimeInterval) // TODO convert to timing interval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *NotificationSrv) RouteGetTimeIntervals(c *contextmodel.ReqContext) response.Response {
|
||||||
|
muteTimeIntervals, err := srv.muteTimingService.GetMuteTimings(c.Req.Context(), c.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return errorToResponse(err)
|
||||||
|
}
|
||||||
|
return response.JSON(http.StatusOK, muteTimeIntervals) // TODO convert to timing interval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *NotificationSrv) RouteGetReceiver(c *contextmodel.ReqContext, name string) response.Response {
|
||||||
|
q := models.GetReceiverQuery{
|
||||||
|
OrgID: c.SignedInUser.OrgID,
|
||||||
|
Name: name,
|
||||||
|
Decrypt: c.QueryBool("decrypt"),
|
||||||
|
}
|
||||||
|
|
||||||
|
receiver, err := srv.receiverService.GetReceiver(c.Req.Context(), q, c.SignedInUser)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, notifier.ErrNotFound) {
|
||||||
|
return ErrResp(http.StatusNotFound, err, "receiver not found")
|
||||||
|
}
|
||||||
|
if errors.Is(err, notifier.ErrPermissionDenied) {
|
||||||
|
return ErrResp(http.StatusForbidden, err, "permission denied")
|
||||||
|
}
|
||||||
|
return ErrResp(http.StatusInternalServerError, err, "failed to get receiver")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.JSON(http.StatusOK, receiver)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (srv *NotificationSrv) RouteGetReceivers(c *contextmodel.ReqContext) response.Response {
|
||||||
|
q := models.GetReceiversQuery{
|
||||||
|
OrgID: c.SignedInUser.OrgID,
|
||||||
|
Names: c.QueryStrings("names"),
|
||||||
|
Limit: c.QueryInt("limit"),
|
||||||
|
Offset: c.QueryInt("offset"),
|
||||||
|
Decrypt: c.QueryBool("decrypt"),
|
||||||
|
}
|
||||||
|
|
||||||
|
receivers, err := srv.receiverService.GetReceivers(c.Req.Context(), q, c.SignedInUser)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, notifier.ErrPermissionDenied) {
|
||||||
|
return ErrResp(http.StatusForbidden, err, "permission denied")
|
||||||
|
}
|
||||||
|
return ErrResp(http.StatusInternalServerError, err, "failed to get receiver groups")
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.JSON(http.StatusOK, receivers)
|
||||||
|
}
|
188
pkg/services/ngalert/api/api_notifications_test.go
Normal file
188
pkg/services/ngalert/api/api_notifications_test.go
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"net/url"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||||
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
|
"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/notifier"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||||
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/grafana/grafana/pkg/web"
|
||||||
|
|
||||||
|
am_config "github.com/prometheus/alertmanager/config"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRouteGetReceiver(t *testing.T) {
|
||||||
|
fakeReceiverSvc := fakes.NewFakeReceiverService()
|
||||||
|
|
||||||
|
t.Run("returns expected model", func(t *testing.T) {
|
||||||
|
expected := definitions.GettableApiReceiver{
|
||||||
|
Receiver: am_config.Receiver{
|
||||||
|
Name: "receiver1",
|
||||||
|
},
|
||||||
|
GettableGrafanaReceivers: definitions.GettableGrafanaReceivers{
|
||||||
|
GrafanaManagedReceivers: []*definitions.GettableGrafanaReceiver{
|
||||||
|
{
|
||||||
|
UID: "uid1",
|
||||||
|
Name: "receiver1",
|
||||||
|
Type: "slack",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fakeReceiverSvc.GetReceiverFn = func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
|
return expected, nil
|
||||||
|
}
|
||||||
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
|
rc := testReqCtx("GET")
|
||||||
|
resp := handler.handleRouteGetReceiver(&rc, "receiver1")
|
||||||
|
require.Equal(t, http.StatusOK, resp.Status())
|
||||||
|
json, err := json.Marshal(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, json, resp.Body())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builds query from request context and url param", func(t *testing.T) {
|
||||||
|
fakeReceiverSvc.GetReceiverFn = func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
|
return definitions.GettableApiReceiver{}, nil
|
||||||
|
}
|
||||||
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
|
rc := testReqCtx("GET")
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "true")
|
||||||
|
resp := handler.handleRouteGetReceiver(&rc, "receiver1")
|
||||||
|
require.Equal(t, http.StatusOK, resp.Status())
|
||||||
|
|
||||||
|
call := fakeReceiverSvc.PopMethodCall()
|
||||||
|
require.Equal(t, "GetReceiver", call.Method)
|
||||||
|
expectedQ := models.GetReceiverQuery{
|
||||||
|
Name: "receiver1",
|
||||||
|
Decrypt: true,
|
||||||
|
OrgID: 1,
|
||||||
|
}
|
||||||
|
require.Equal(t, expectedQ, call.Args[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should pass along not found response", func(t *testing.T) {
|
||||||
|
fakeReceiverSvc.GetReceiverFn = func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
|
return definitions.GettableApiReceiver{}, notifier.ErrNotFound
|
||||||
|
}
|
||||||
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
|
rc := testReqCtx("GET")
|
||||||
|
resp := handler.handleRouteGetReceiver(&rc, "receiver1")
|
||||||
|
require.Equal(t, http.StatusNotFound, resp.Status())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should pass along permission denied response", func(t *testing.T) {
|
||||||
|
fakeReceiverSvc.GetReceiverFn = func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
|
return definitions.GettableApiReceiver{}, notifier.ErrPermissionDenied
|
||||||
|
}
|
||||||
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
|
rc := testReqCtx("GET")
|
||||||
|
resp := handler.handleRouteGetReceiver(&rc, "receiver1")
|
||||||
|
require.Equal(t, http.StatusForbidden, resp.Status())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRouteGetReceivers(t *testing.T) {
|
||||||
|
fakeReceiverSvc := fakes.NewFakeReceiverService()
|
||||||
|
|
||||||
|
t.Run("returns expected model", func(t *testing.T) {
|
||||||
|
expected := []definitions.GettableApiReceiver{
|
||||||
|
{
|
||||||
|
Receiver: am_config.Receiver{
|
||||||
|
Name: "receiver1",
|
||||||
|
},
|
||||||
|
GettableGrafanaReceivers: definitions.GettableGrafanaReceivers{
|
||||||
|
GrafanaManagedReceivers: []*definitions.GettableGrafanaReceiver{
|
||||||
|
{
|
||||||
|
UID: "uid1",
|
||||||
|
Name: "receiver1",
|
||||||
|
Type: "slack",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fakeReceiverSvc.GetReceiversFn = func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
||||||
|
return expected, nil
|
||||||
|
}
|
||||||
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
|
rc := testReqCtx("GET")
|
||||||
|
rc.Context.Req.Form.Set("names", "receiver1")
|
||||||
|
resp := handler.handleRouteGetReceivers(&rc)
|
||||||
|
require.Equal(t, http.StatusOK, resp.Status())
|
||||||
|
json, err := json.Marshal(expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, json, resp.Body())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("builds query from request context", func(t *testing.T) {
|
||||||
|
fakeReceiverSvc.GetReceiversFn = func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
||||||
|
return []definitions.GettableApiReceiver{}, nil
|
||||||
|
}
|
||||||
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
|
rc := testReqCtx("GET")
|
||||||
|
rc.Context.Req.Form.Set("names", "receiver1")
|
||||||
|
rc.Context.Req.Form.Add("names", "receiver2")
|
||||||
|
rc.Context.Req.Form.Set("limit", "1")
|
||||||
|
rc.Context.Req.Form.Set("offset", "2")
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "true")
|
||||||
|
resp := handler.handleRouteGetReceivers(&rc)
|
||||||
|
require.Equal(t, http.StatusOK, resp.Status())
|
||||||
|
|
||||||
|
call := fakeReceiverSvc.PopMethodCall()
|
||||||
|
require.Equal(t, "GetReceivers", call.Method)
|
||||||
|
expectedQ := models.GetReceiversQuery{
|
||||||
|
Names: []string{"receiver1", "receiver2"},
|
||||||
|
Limit: 1,
|
||||||
|
Offset: 2,
|
||||||
|
Decrypt: true,
|
||||||
|
OrgID: 1,
|
||||||
|
}
|
||||||
|
require.Equal(t, expectedQ, call.Args[1])
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("should pass along permission denied response", func(t *testing.T) {
|
||||||
|
fakeReceiverSvc.GetReceiversFn = func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
||||||
|
return nil, notifier.ErrPermissionDenied
|
||||||
|
}
|
||||||
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
|
rc := testReqCtx("GET")
|
||||||
|
resp := handler.handleRouteGetReceivers(&rc)
|
||||||
|
require.Equal(t, http.StatusForbidden, resp.Status())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNotificationSrv(receiverService ReceiverService) *NotificationSrv {
|
||||||
|
return &NotificationSrv{
|
||||||
|
logger: log.NewNopLogger(),
|
||||||
|
receiverService: receiverService,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReqCtx(method string) contextmodel.ReqContext {
|
||||||
|
return contextmodel.ReqContext{
|
||||||
|
Context: &web.Context{
|
||||||
|
Req: &http.Request{
|
||||||
|
Header: make(http.Header),
|
||||||
|
Form: make(url.Values),
|
||||||
|
},
|
||||||
|
Resp: web.NewResponseWriter(method, httptest.NewRecorder()),
|
||||||
|
},
|
||||||
|
SignedInUser: &user.SignedInUser{
|
||||||
|
OrgID: 1,
|
||||||
|
},
|
||||||
|
Logger: &logtest.Fake{},
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import (
|
|||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -1363,9 +1364,13 @@ func TestProvisioningApiContactPointExport(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("decrypt true without alert.provisioning.secrets:read permissions returns 403", func(t *testing.T) {
|
t.Run("decrypt true without alert.provisioning.secrets:read permissions returns 403", func(t *testing.T) {
|
||||||
|
recPermCheck := false
|
||||||
env := createTestEnv(t, testConfig)
|
env := createTestEnv(t, testConfig)
|
||||||
env.ac = &recordingAccessControlFake{
|
env.ac = &recordingAccessControlFake{
|
||||||
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||||
|
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingProvisioningReadSecrets) {
|
||||||
|
recPermCheck = true
|
||||||
|
}
|
||||||
return false, nil
|
return false, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1377,16 +1382,18 @@ func TestProvisioningApiContactPointExport(t *testing.T) {
|
|||||||
|
|
||||||
response := sut.RouteGetContactPointsExport(&rc)
|
response := sut.RouteGetContactPointsExport(&rc)
|
||||||
|
|
||||||
|
require.True(t, recPermCheck)
|
||||||
require.Equal(t, 403, response.Status())
|
require.Equal(t, 403, response.Status())
|
||||||
require.Len(t, env.ac.EvaluateRecordings, 1)
|
|
||||||
require.Equal(t, accesscontrol.ActionAlertingProvisioningReadSecrets, env.ac.EvaluateRecordings[0].Evaluator.String())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("decrypt true with admin returns 200", func(t *testing.T) {
|
t.Run("decrypt true with admin returns 200", func(t *testing.T) {
|
||||||
|
recPermCheck := false
|
||||||
env := createTestEnv(t, testConfig)
|
env := createTestEnv(t, testConfig)
|
||||||
env.ac = &recordingAccessControlFake{
|
env.ac = &recordingAccessControlFake{
|
||||||
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||||
require.Equal(t, accesscontrol.ActionAlertingProvisioningReadSecrets, evaluator.String())
|
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingProvisioningReadSecrets) {
|
||||||
|
recPermCheck = true
|
||||||
|
}
|
||||||
return true, nil
|
return true, nil
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -1399,9 +1406,8 @@ func TestProvisioningApiContactPointExport(t *testing.T) {
|
|||||||
response := sut.RouteGetContactPointsExport(&rc)
|
response := sut.RouteGetContactPointsExport(&rc)
|
||||||
response.WriteTo(&rc)
|
response.WriteTo(&rc)
|
||||||
|
|
||||||
|
require.True(t, recPermCheck)
|
||||||
require.Equal(t, 200, response.Status())
|
require.Equal(t, 200, response.Status())
|
||||||
require.Len(t, env.ac.EvaluateRecordings, 1)
|
|
||||||
require.Equal(t, accesscontrol.ActionAlertingProvisioningReadSecrets, env.ac.EvaluateRecordings[0].Evaluator.String())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("json body content is as expected", func(t *testing.T) {
|
t.Run("json body content is as expected", func(t *testing.T) {
|
||||||
|
@ -43,10 +43,27 @@ func (api *API) authorize(method, path string) web.Handler {
|
|||||||
ac.EvalPermission(ac.ActionAlertingRuleCreate, scope),
|
ac.EvalPermission(ac.ActionAlertingRuleCreate, scope),
|
||||||
ac.EvalPermission(ac.ActionAlertingRuleDelete, scope),
|
ac.EvalPermission(ac.ActionAlertingRuleDelete, scope),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Grafana rule state history paths
|
// Grafana rule state history paths
|
||||||
case http.MethodGet + "/api/v1/rules/history":
|
case http.MethodGet + "/api/v1/rules/history":
|
||||||
eval = ac.EvalPermission(ac.ActionAlertingRuleRead)
|
eval = ac.EvalPermission(ac.ActionAlertingRuleRead)
|
||||||
|
|
||||||
|
// Grafana receivers paths
|
||||||
|
case http.MethodGet + "/api/v1/notifications/receivers":
|
||||||
|
// additional authorization is done at the service level
|
||||||
|
eval = ac.EvalAny(
|
||||||
|
ac.EvalPermission(ac.ActionAlertingNotificationsRead),
|
||||||
|
ac.EvalPermission(ac.ActionAlertingReceiversList),
|
||||||
|
ac.EvalPermission(ac.ActionAlertingReceiversRead),
|
||||||
|
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets),
|
||||||
|
)
|
||||||
|
case http.MethodGet + "/api/v1/notifications/receivers/{Name}":
|
||||||
|
// TODO: scope to :Name
|
||||||
|
eval = ac.EvalAny(
|
||||||
|
ac.EvalPermission(ac.ActionAlertingReceiversRead),
|
||||||
|
ac.EvalPermission(ac.ActionAlertingReceiversReadSecrets),
|
||||||
|
)
|
||||||
|
|
||||||
// Grafana unified alerting upgrade paths
|
// Grafana unified alerting upgrade paths
|
||||||
case http.MethodGet + "/api/v1/upgrade/org":
|
case http.MethodGet + "/api/v1/upgrade/org":
|
||||||
return middleware.ReqOrgAdmin
|
return middleware.ReqOrgAdmin
|
||||||
|
@ -40,7 +40,7 @@ func TestAuthorize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
paths[p] = methods
|
paths[p] = methods
|
||||||
}
|
}
|
||||||
require.Len(t, paths, 62)
|
require.Len(t, paths, 64)
|
||||||
|
|
||||||
ac := acmock.New()
|
ac := acmock.New()
|
||||||
api := &API{AccessControl: ac}
|
api := &API{AccessControl: ac}
|
||||||
|
@ -19,10 +19,20 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type NotificationsApi interface {
|
type NotificationsApi interface {
|
||||||
|
RouteGetReceiver(*contextmodel.ReqContext) response.Response
|
||||||
|
RouteGetReceivers(*contextmodel.ReqContext) response.Response
|
||||||
RouteNotificationsGetTimeInterval(*contextmodel.ReqContext) response.Response
|
RouteNotificationsGetTimeInterval(*contextmodel.ReqContext) response.Response
|
||||||
RouteNotificationsGetTimeIntervals(*contextmodel.ReqContext) response.Response
|
RouteNotificationsGetTimeIntervals(*contextmodel.ReqContext) response.Response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *NotificationsApiHandler) RouteGetReceiver(ctx *contextmodel.ReqContext) response.Response {
|
||||||
|
// Parse Path Parameters
|
||||||
|
nameParam := web.Params(ctx.Req)[":name"]
|
||||||
|
return f.handleRouteGetReceiver(ctx, nameParam)
|
||||||
|
}
|
||||||
|
func (f *NotificationsApiHandler) RouteGetReceivers(ctx *contextmodel.ReqContext) response.Response {
|
||||||
|
return f.handleRouteGetReceivers(ctx)
|
||||||
|
}
|
||||||
func (f *NotificationsApiHandler) RouteNotificationsGetTimeInterval(ctx *contextmodel.ReqContext) response.Response {
|
func (f *NotificationsApiHandler) RouteNotificationsGetTimeInterval(ctx *contextmodel.ReqContext) response.Response {
|
||||||
// Parse Path Parameters
|
// Parse Path Parameters
|
||||||
nameParam := web.Params(ctx.Req)[":name"]
|
nameParam := web.Params(ctx.Req)[":name"]
|
||||||
@ -34,6 +44,30 @@ func (f *NotificationsApiHandler) RouteNotificationsGetTimeIntervals(ctx *contex
|
|||||||
|
|
||||||
func (api *API) RegisterNotificationsApiEndpoints(srv NotificationsApi, m *metrics.API) {
|
func (api *API) RegisterNotificationsApiEndpoints(srv NotificationsApi, m *metrics.API) {
|
||||||
api.RouteRegister.Group("", func(group routing.RouteRegister) {
|
api.RouteRegister.Group("", func(group routing.RouteRegister) {
|
||||||
|
group.Get(
|
||||||
|
toMacaronPath("/api/v1/notifications/receivers/{Name}"),
|
||||||
|
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||||
|
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||||
|
api.authorize(http.MethodGet, "/api/v1/notifications/receivers/{Name}"),
|
||||||
|
metrics.Instrument(
|
||||||
|
http.MethodGet,
|
||||||
|
"/api/v1/notifications/receivers/{Name}",
|
||||||
|
api.Hooks.Wrap(srv.RouteGetReceiver),
|
||||||
|
m,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
group.Get(
|
||||||
|
toMacaronPath("/api/v1/notifications/receivers"),
|
||||||
|
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||||
|
requestmeta.SetSLOGroup(requestmeta.SLOGroupHighSlow),
|
||||||
|
api.authorize(http.MethodGet, "/api/v1/notifications/receivers"),
|
||||||
|
metrics.Instrument(
|
||||||
|
http.MethodGet,
|
||||||
|
"/api/v1/notifications/receivers",
|
||||||
|
api.Hooks.Wrap(srv.RouteGetReceivers),
|
||||||
|
m,
|
||||||
|
),
|
||||||
|
)
|
||||||
group.Get(
|
group.Get(
|
||||||
toMacaronPath("/api/v1/notifications/time-intervals/{name}"),
|
toMacaronPath("/api/v1/notifications/time-intervals/{name}"),
|
||||||
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
requestmeta.SetOwner(requestmeta.TeamAlerting),
|
||||||
|
@ -1,34 +1,32 @@
|
|||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotificationsApiHandler struct {
|
type NotificationsApiHandler struct {
|
||||||
muteTimingService MuteTimingService
|
notificationSrv *NotificationSrv
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNotificationsApi(muteTimingService MuteTimingService) NotificationsApi {
|
func NewNotificationsApi(notificationSrv *NotificationSrv) *NotificationsApiHandler {
|
||||||
return &NotificationsApiHandler{
|
return &NotificationsApiHandler{
|
||||||
muteTimingService: muteTimingService,
|
notificationSrv: notificationSrv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *NotificationsApiHandler) handleRouteNotificationsGetTimeInterval(ctx *contextmodel.ReqContext, name string) response.Response {
|
func (f *NotificationsApiHandler) handleRouteNotificationsGetTimeInterval(ctx *contextmodel.ReqContext, name string) response.Response {
|
||||||
model, err := f.muteTimingService.GetMuteTiming(ctx.Req.Context(), name, ctx.OrgID)
|
return f.notificationSrv.RouteGetTimeInterval(ctx, name)
|
||||||
if err != nil {
|
|
||||||
return errorToResponse(err)
|
|
||||||
}
|
|
||||||
return response.JSON(http.StatusOK, model) // TODO convert to timing interval
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *NotificationsApiHandler) handleRouteNotificationsGetTimeIntervals(ctx *contextmodel.ReqContext) response.Response {
|
func (f *NotificationsApiHandler) handleRouteNotificationsGetTimeIntervals(ctx *contextmodel.ReqContext) response.Response {
|
||||||
model, err := f.muteTimingService.GetMuteTimings(ctx.Req.Context(), ctx.OrgID)
|
return f.notificationSrv.RouteGetTimeIntervals(ctx)
|
||||||
if err != nil {
|
|
||||||
return errorToResponse(err)
|
|
||||||
}
|
}
|
||||||
return response.JSON(http.StatusOK, model) // TODO convert to timing interval
|
|
||||||
|
func (f *NotificationsApiHandler) handleRouteGetReceiver(ctx *contextmodel.ReqContext, name string) response.Response {
|
||||||
|
return f.notificationSrv.RouteGetReceiver(ctx, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *NotificationsApiHandler) handleRouteGetReceivers(ctx *contextmodel.ReqContext) response.Response {
|
||||||
|
return f.notificationSrv.RouteGetReceivers(ctx)
|
||||||
}
|
}
|
||||||
|
@ -4511,7 +4511,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroup": {
|
"alertGroup": {
|
||||||
"description": "AlertGroup alert group",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"description": "alerts",
|
"description": "alerts",
|
||||||
@ -4535,7 +4534,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
"description": "AlertGroups alert groups",
|
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup"
|
||||||
},
|
},
|
||||||
@ -4695,13 +4693,13 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"gettableAlerts": {
|
"gettableAlerts": {
|
||||||
|
"description": "GettableAlerts gettable alerts",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/gettableAlert"
|
"$ref": "#/definitions/gettableAlert"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
"description": "GettableSilence gettable silence",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
"description": "comment",
|
"description": "comment",
|
||||||
@ -4750,6 +4748,7 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"gettableSilences": {
|
"gettableSilences": {
|
||||||
|
"description": "GettableSilences gettable silences",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/gettableSilence"
|
"$ref": "#/definitions/gettableSilence"
|
||||||
},
|
},
|
||||||
@ -4900,7 +4899,6 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"postableSilence": {
|
"postableSilence": {
|
||||||
"description": "PostableSilence postable silence",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
"description": "comment",
|
"description": "comment",
|
||||||
@ -5057,61 +5055,6 @@
|
|||||||
"version": "1.1.0"
|
"version": "1.1.0"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/v1/notifications/time-intervals": {
|
|
||||||
"get": {
|
|
||||||
"description": "Get all the time intervals",
|
|
||||||
"operationId": "RouteNotificationsGetTimeIntervals",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"$ref": "#/responses/GetAllIntervalsResponse"
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "ForbiddenError",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/ForbiddenError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"notifications"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/notifications/time-intervals/{name}": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "RouteNotificationsGetTimeInterval",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "Time interval name",
|
|
||||||
"in": "path",
|
|
||||||
"name": "name",
|
|
||||||
"required": true,
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"$ref": "#/responses/GetIntervalsByNameResponse"
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "ForbiddenError",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/ForbiddenError"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "NotFound",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/NotFound"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"summary": "Get a time interval by name.",
|
|
||||||
"tags": [
|
|
||||||
"notifications"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/provisioning/alert-rules": {
|
"/v1/provisioning/alert-rules": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "RouteGetAlertRules",
|
"operationId": "RouteGetAlertRules",
|
||||||
@ -6168,6 +6111,21 @@
|
|||||||
"$ref": "#/definitions/GettableTimeIntervals"
|
"$ref": "#/definitions/GettableTimeIntervals"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"GetReceiverResponse": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/GettableApiReceiver"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetReceiversResponse": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/GettableApiReceiver"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
"GettableHistoricUserConfigs": {
|
"GettableHistoricUserConfigs": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
56
pkg/services/ngalert/api/tooling/definitions/receivers.go
Normal file
56
pkg/services/ngalert/api/tooling/definitions/receivers.go
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
package definitions
|
||||||
|
|
||||||
|
// swagger:route GET /v1/notifications/receivers/{Name} notifications RouteGetReceiver
|
||||||
|
//
|
||||||
|
// Get a receiver by name.
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: GetReceiverResponse
|
||||||
|
// 403: PermissionDenied
|
||||||
|
// 404: NotFound
|
||||||
|
|
||||||
|
// swagger:route GET /v1/notifications/receivers notifications RouteGetReceivers
|
||||||
|
//
|
||||||
|
// Get all receivers.
|
||||||
|
//
|
||||||
|
// Responses:
|
||||||
|
// 200: GetReceiversResponse
|
||||||
|
// 403: PermissionDenied
|
||||||
|
|
||||||
|
// swagger:parameters RouteGetReceiver
|
||||||
|
type GetReceiverParams struct {
|
||||||
|
// in:path
|
||||||
|
// required: true
|
||||||
|
Name string `json:"name"`
|
||||||
|
// in:query
|
||||||
|
// required: false
|
||||||
|
Decrypt bool `json:"decrypt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:parameters RouteGetReceivers
|
||||||
|
type GetReceiversParams struct {
|
||||||
|
// in:query
|
||||||
|
// required: false
|
||||||
|
Names []string `json:"names"`
|
||||||
|
// in:query
|
||||||
|
// required: false
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
// in:query
|
||||||
|
// required: false
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
// in:query
|
||||||
|
// required: false
|
||||||
|
Decrypt bool `json:"decrypt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:response GetReceiverResponse
|
||||||
|
type GetReceiverResponse struct {
|
||||||
|
// in:body
|
||||||
|
Body GettableApiReceiver
|
||||||
|
}
|
||||||
|
|
||||||
|
// swagger:response GetReceiversResponse
|
||||||
|
type GetReceiversResponse struct {
|
||||||
|
// in:body
|
||||||
|
Body []GettableApiReceiver
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
package definitions
|
package definitions
|
||||||
|
|
||||||
// swagger:route GET /v1/notifications/time-intervals notifications stable RouteNotificationsGetTimeIntervals
|
// swagger:route GET /v1/notifications/time-intervals notifications RouteNotificationsGetTimeIntervals
|
||||||
//
|
//
|
||||||
// Get all the time intervals
|
// Get all the time intervals
|
||||||
//
|
//
|
||||||
@ -8,7 +8,7 @@ package definitions
|
|||||||
// 200: GetAllIntervalsResponse
|
// 200: GetAllIntervalsResponse
|
||||||
// 403: ForbiddenError
|
// 403: ForbiddenError
|
||||||
|
|
||||||
// swagger:route GET /v1/notifications/time-intervals/{name} notifications stable RouteNotificationsGetTimeInterval
|
// swagger:route GET /v1/notifications/time-intervals/{name} notifications RouteNotificationsGetTimeInterval
|
||||||
//
|
//
|
||||||
// Get a time interval by name.
|
// Get a time interval by name.
|
||||||
//
|
//
|
||||||
@ -17,7 +17,7 @@ package definitions
|
|||||||
// 404: NotFound
|
// 404: NotFound
|
||||||
// 403: ForbiddenError
|
// 403: ForbiddenError
|
||||||
|
|
||||||
// swagger:parameters stable RouteNotificationsGetTimeInterval
|
// swagger:parameters RouteNotificationsGetTimeInterval
|
||||||
type RouteTimeIntervalNameParam struct {
|
type RouteTimeIntervalNameParam struct {
|
||||||
// Time interval name
|
// Time interval name
|
||||||
// in:path
|
// in:path
|
||||||
|
@ -4512,7 +4512,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroup": {
|
"alertGroup": {
|
||||||
"description": "AlertGroup alert group",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"description": "alerts",
|
"description": "alerts",
|
||||||
@ -4536,6 +4535,7 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
|
"description": "AlertGroups alert groups",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup"
|
||||||
},
|
},
|
||||||
@ -4640,6 +4640,7 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"gettableAlert": {
|
"gettableAlert": {
|
||||||
|
"description": "GettableAlert gettable alert",
|
||||||
"properties": {
|
"properties": {
|
||||||
"annotations": {
|
"annotations": {
|
||||||
"$ref": "#/definitions/labelSet"
|
"$ref": "#/definitions/labelSet"
|
||||||
@ -4701,7 +4702,6 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
"description": "GettableSilence gettable silence",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
"description": "comment",
|
"description": "comment",
|
||||||
@ -4756,6 +4756,7 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
|
"description": "Integration integration",
|
||||||
"properties": {
|
"properties": {
|
||||||
"lastNotifyAttempt": {
|
"lastNotifyAttempt": {
|
||||||
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
"description": "A timestamp indicating the last attempt to deliver a notification regardless of the outcome.\nFormat: date-time",
|
||||||
@ -4936,7 +4937,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"receiver": {
|
"receiver": {
|
||||||
"description": "Receiver receiver",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"active": {
|
"active": {
|
||||||
"description": "active",
|
"description": "active",
|
||||||
@ -7046,6 +7046,86 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/notifications/receivers": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "RouteGetReceivers",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": "names",
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "int64",
|
||||||
|
"in": "query",
|
||||||
|
"name": "limit",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"format": "int64",
|
||||||
|
"in": "query",
|
||||||
|
"name": "offset",
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "decrypt",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/GetReceiversResponse"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "PermissionDenied",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/PermissionDenied"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "Get all receivers.",
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/notifications/receivers/{Name}": {
|
||||||
|
"get": {
|
||||||
|
"operationId": "RouteGetReceiver",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"in": "path",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"in": "query",
|
||||||
|
"name": "decrypt",
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/GetReceiverResponse"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "NotFound",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/NotFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"summary": "Get a receiver by name.",
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/notifications/time-intervals": {
|
"/v1/notifications/time-intervals": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get all the time intervals",
|
"description": "Get all the time intervals",
|
||||||
@ -8507,6 +8587,21 @@
|
|||||||
"$ref": "#/definitions/GettableTimeIntervals"
|
"$ref": "#/definitions/GettableTimeIntervals"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"GetReceiverResponse": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/GettableApiReceiver"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetReceiversResponse": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/GettableApiReceiver"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
"GettableHistoricUserConfigs": {
|
"GettableHistoricUserConfigs": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -2007,12 +2007,91 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/v1/notifications/receivers": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "Get all receivers.",
|
||||||
|
"operationId": "RouteGetReceivers",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": "names",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"name": "limit",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "integer",
|
||||||
|
"format": "int64",
|
||||||
|
"name": "offset",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"name": "decrypt",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/GetReceiversResponse"
|
||||||
|
},
|
||||||
|
"403": {
|
||||||
|
"description": "PermissionDenied",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/PermissionDenied"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/v1/notifications/receivers/{Name}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"notifications"
|
||||||
|
],
|
||||||
|
"summary": "Get a receiver by name.",
|
||||||
|
"operationId": "RouteGetReceiver",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "boolean",
|
||||||
|
"name": "decrypt",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"$ref": "#/responses/GetReceiverResponse"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "NotFound",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/NotFound"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/v1/notifications/time-intervals": {
|
"/v1/notifications/time-intervals": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Get all the time intervals",
|
"description": "Get all the time intervals",
|
||||||
"tags": [
|
"tags": [
|
||||||
"notifications",
|
"notifications"
|
||||||
"stable"
|
|
||||||
],
|
],
|
||||||
"operationId": "RouteNotificationsGetTimeIntervals",
|
"operationId": "RouteNotificationsGetTimeIntervals",
|
||||||
"responses": {
|
"responses": {
|
||||||
@ -2031,8 +2110,7 @@
|
|||||||
"/v1/notifications/time-intervals/{name}": {
|
"/v1/notifications/time-intervals/{name}": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"notifications",
|
"notifications"
|
||||||
"stable"
|
|
||||||
],
|
],
|
||||||
"summary": "Get a time interval by name.",
|
"summary": "Get a time interval by name.",
|
||||||
"operationId": "RouteNotificationsGetTimeInterval",
|
"operationId": "RouteNotificationsGetTimeInterval",
|
||||||
@ -7994,7 +8072,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alertGroup": {
|
"alertGroup": {
|
||||||
"description": "AlertGroup alert group",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"alerts",
|
"alerts",
|
||||||
@ -8019,6 +8096,7 @@
|
|||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup"
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
|
"description": "AlertGroups alert groups",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup"
|
||||||
@ -8124,6 +8202,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gettableAlert": {
|
"gettableAlert": {
|
||||||
|
"description": "GettableAlert gettable alert",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"labels",
|
"labels",
|
||||||
@ -8187,7 +8266,6 @@
|
|||||||
"$ref": "#/definitions/gettableAlerts"
|
"$ref": "#/definitions/gettableAlerts"
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
"description": "GettableSilence gettable silence",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"comment",
|
"comment",
|
||||||
@ -8244,6 +8322,7 @@
|
|||||||
"$ref": "#/definitions/gettableSilences"
|
"$ref": "#/definitions/gettableSilences"
|
||||||
},
|
},
|
||||||
"integration": {
|
"integration": {
|
||||||
|
"description": "Integration integration",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"name",
|
"name",
|
||||||
@ -8426,7 +8505,6 @@
|
|||||||
"$ref": "#/definitions/postableSilence"
|
"$ref": "#/definitions/postableSilence"
|
||||||
},
|
},
|
||||||
"receiver": {
|
"receiver": {
|
||||||
"description": "Receiver receiver",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"active",
|
"active",
|
||||||
@ -8557,6 +8635,21 @@
|
|||||||
"$ref": "#/definitions/GettableTimeIntervals"
|
"$ref": "#/definitions/GettableTimeIntervals"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"GetReceiverResponse": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/GettableApiReceiver"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetReceiversResponse": {
|
||||||
|
"description": "",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/GettableApiReceiver"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"GettableHistoricUserConfigs": {
|
"GettableHistoricUserConfigs": {
|
||||||
"description": "",
|
"description": "",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -1,5 +1,12 @@
|
|||||||
package models
|
package models
|
||||||
|
|
||||||
|
// GetReceiverQuery represents a query for a single receiver.
|
||||||
|
type GetReceiverQuery struct {
|
||||||
|
OrgID int64
|
||||||
|
Name string
|
||||||
|
Decrypt bool
|
||||||
|
}
|
||||||
|
|
||||||
// GetReceiversQuery represents a query for receiver groups.
|
// GetReceiversQuery represents a query for receiver groups.
|
||||||
type GetReceiversQuery struct {
|
type GetReceiversQuery struct {
|
||||||
OrgID int64
|
OrgID int64
|
||||||
|
@ -46,19 +46,19 @@ func PostableApiAlertingConfigToApiReceivers(c apimodels.PostableApiAlertingConf
|
|||||||
|
|
||||||
type DecryptFn = func(value string) string
|
type DecryptFn = func(value string) string
|
||||||
|
|
||||||
func PostableToGettableGrafanaReceiver(r *apimodels.PostableGrafanaReceiver, provenance *models.Provenance, decryptFn DecryptFn) (apimodels.GettableGrafanaReceiver, error) {
|
func PostableToGettableGrafanaReceiver(r *apimodels.PostableGrafanaReceiver, provenance *models.Provenance, decryptFn DecryptFn, listOnly bool) (apimodels.GettableGrafanaReceiver, error) {
|
||||||
out := apimodels.GettableGrafanaReceiver{
|
out := apimodels.GettableGrafanaReceiver{
|
||||||
UID: r.UID,
|
UID: r.UID,
|
||||||
Name: r.Name,
|
Name: r.Name,
|
||||||
Type: r.Type,
|
Type: r.Type,
|
||||||
DisableResolveMessage: r.DisableResolveMessage,
|
|
||||||
SecureFields: make(map[string]bool, len(r.SecureSettings)),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if provenance != nil {
|
if provenance != nil {
|
||||||
out.Provenance = apimodels.Provenance(*provenance)
|
out.Provenance = apimodels.Provenance(*provenance)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if we aren't only listing, include the settings in the output
|
||||||
|
if !listOnly {
|
||||||
|
secureFields := make(map[string]bool, len(r.SecureSettings))
|
||||||
settings, err := simplejson.NewJson([]byte(r.Settings))
|
settings, err := simplejson.NewJson([]byte(r.Settings))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apimodels.GettableGrafanaReceiver{}, err
|
return apimodels.GettableGrafanaReceiver{}, err
|
||||||
@ -74,19 +74,23 @@ func PostableToGettableGrafanaReceiver(r *apimodels.PostableGrafanaReceiver, pro
|
|||||||
} else {
|
} else {
|
||||||
settings.Set(k, decryptedValue)
|
settings.Set(k, decryptedValue)
|
||||||
}
|
}
|
||||||
out.SecureFields[k] = true
|
secureFields[k] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
jsonBytes, err := settings.MarshalJSON()
|
jsonBytes, err := settings.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apimodels.GettableGrafanaReceiver{}, err
|
return apimodels.GettableGrafanaReceiver{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
out.Settings = jsonBytes
|
out.Settings = jsonBytes
|
||||||
|
out.SecureFields = secureFields
|
||||||
|
out.DisableResolveMessage = r.DisableResolveMessage
|
||||||
|
}
|
||||||
|
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func PostableToGettableApiReceiver(r *apimodels.PostableApiReceiver, provenances map[string]models.Provenance, decryptFn DecryptFn) (apimodels.GettableApiReceiver, error) {
|
func PostableToGettableApiReceiver(r *apimodels.PostableApiReceiver, provenances map[string]models.Provenance, decryptFn DecryptFn, listOnly bool) (apimodels.GettableApiReceiver, error) {
|
||||||
out := apimodels.GettableApiReceiver{
|
out := apimodels.GettableApiReceiver{
|
||||||
Receiver: config.Receiver{
|
Receiver: config.Receiver{
|
||||||
Name: r.Receiver.Name,
|
Name: r.Receiver.Name,
|
||||||
@ -99,7 +103,7 @@ func PostableToGettableApiReceiver(r *apimodels.PostableApiReceiver, provenances
|
|||||||
prov = &p
|
prov = &p
|
||||||
}
|
}
|
||||||
|
|
||||||
gettable, err := PostableToGettableGrafanaReceiver(gr, prov, decryptFn)
|
gettable, err := PostableToGettableGrafanaReceiver(gr, prov, decryptFn, listOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apimodels.GettableApiReceiver{}, err
|
return apimodels.GettableApiReceiver{}, err
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,8 @@ import (
|
|||||||
var (
|
var (
|
||||||
// ErrPermissionDenied is returned when the user does not have permission to perform the requested action.
|
// ErrPermissionDenied is returned when the user does not have permission to perform the requested action.
|
||||||
ErrPermissionDenied = errors.New("permission denied") // TODO: convert to errutil
|
ErrPermissionDenied = errors.New("permission denied") // TODO: convert to errutil
|
||||||
|
// ErrNotFound is returned when the requested resource does not exist.
|
||||||
|
ErrNotFound = errors.New("not found") // TODO: convert to errutil
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReceiverService is the service for managing alertmanager receivers.
|
// ReceiverService is the service for managing alertmanager receivers.
|
||||||
@ -61,19 +63,71 @@ func NewReceiverService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rs *ReceiverService) canDecrypt(ctx context.Context, user identity.Requester, name string) (bool, error) {
|
func (rs *ReceiverService) shouldDecrypt(ctx context.Context, user identity.Requester, name string, reqDecrypt bool) (bool, error) {
|
||||||
receiverAccess := false // TODO: stub, check for read secrets access
|
// TODO: migrate to new permission
|
||||||
eval := accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningReadSecrets)
|
eval := accesscontrol.EvalAny(
|
||||||
provisioningAccess, err := rs.ac.Evaluate(ctx, user, eval)
|
accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversReadSecrets),
|
||||||
|
accesscontrol.EvalPermission(accesscontrol.ActionAlertingProvisioningReadSecrets),
|
||||||
|
)
|
||||||
|
|
||||||
|
decryptAccess, err := rs.ac.Evaluate(ctx, user, eval)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
return receiverAccess || provisioningAccess, nil
|
|
||||||
|
if reqDecrypt && !decryptAccess {
|
||||||
|
return false, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
return decryptAccess && reqDecrypt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetReceiver returns a receiver by name.
|
||||||
|
// The receiver's secure settings are decrypted if requested and the user has access to do so.
|
||||||
|
func (rs *ReceiverService) GetReceiver(ctx context.Context, q models.GetReceiverQuery, user identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
|
if q.Decrypt && user == nil {
|
||||||
|
return definitions.GettableApiReceiver{}, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
|
baseCfg, err := rs.cfgStore.GetLatestAlertmanagerConfiguration(ctx, q.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return definitions.GettableApiReceiver{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := definitions.PostableUserConfig{}
|
||||||
|
err = json.Unmarshal([]byte(baseCfg.AlertmanagerConfiguration), &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return definitions.GettableApiReceiver{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
provenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, "contactPoint")
|
||||||
|
if err != nil {
|
||||||
|
return definitions.GettableApiReceiver{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
receivers := cfg.AlertmanagerConfig.Receivers
|
||||||
|
for _, r := range receivers {
|
||||||
|
if r.Name == q.Name {
|
||||||
|
decrypt, err := rs.shouldDecrypt(ctx, user, q.Name, q.Decrypt)
|
||||||
|
if err != nil {
|
||||||
|
return definitions.GettableApiReceiver{}, err
|
||||||
|
}
|
||||||
|
decryptFn := rs.decryptOrRedact(ctx, decrypt, q.Name, "")
|
||||||
|
|
||||||
|
return PostableToGettableApiReceiver(r, provenances, decryptFn, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return definitions.GettableApiReceiver{}, ErrNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReceivers returns a list of receivers a user has access to.
|
// GetReceivers returns a list of receivers a user has access to.
|
||||||
// Receivers can be filtered by name, and secure settings are decrypted if requested and the user has access to do so.
|
// Receivers can be filtered by name, and secure settings are decrypted if requested and the user has access to do so.
|
||||||
func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceiversQuery, user identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceiversQuery, user identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
||||||
|
if q.Decrypt && user == nil {
|
||||||
|
return nil, ErrPermissionDenied
|
||||||
|
}
|
||||||
|
|
||||||
baseCfg, err := rs.cfgStore.GetLatestAlertmanagerConfiguration(ctx, q.OrgID)
|
baseCfg, err := rs.cfgStore.GetLatestAlertmanagerConfiguration(ctx, q.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -90,7 +144,11 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check for list access
|
eval := accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversList)
|
||||||
|
listAccess, err := rs.ac.Evaluate(ctx, user, eval)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
var output []definitions.GettableApiReceiver
|
var output []definitions.GettableApiReceiver
|
||||||
for i := q.Offset; i < len(cfg.AlertmanagerConfig.Receivers); i++ {
|
for i := q.Offset; i < len(cfg.AlertmanagerConfig.Receivers); i++ {
|
||||||
@ -99,24 +157,18 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: check for scoped read access and continue if not allowed
|
decrypt, err := rs.shouldDecrypt(ctx, user, r.Name, q.Decrypt)
|
||||||
|
|
||||||
decryptAccess, err := rs.canDecrypt(ctx, user, r.Name)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if q.Decrypt && !decryptAccess {
|
|
||||||
return nil, ErrPermissionDenied
|
|
||||||
}
|
|
||||||
|
|
||||||
decryptFn := rs.decryptOrRedact(ctx, decryptAccess && q.Decrypt, r.Name, "")
|
|
||||||
|
|
||||||
res, err := PostableToGettableApiReceiver(r, provenances, decryptFn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: redact settings if the user only has list access
|
decryptFn := rs.decryptOrRedact(ctx, decrypt, r.Name, "")
|
||||||
|
listOnly := !decrypt && listAccess
|
||||||
|
|
||||||
|
res, err := PostableToGettableApiReceiver(r, provenances, decryptFn, listOnly)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
output = append(output, res)
|
output = append(output, res)
|
||||||
// stop if we have reached the limit or we have found all the requested receivers
|
// stop if we have reached the limit or we have found all the requested receivers
|
||||||
|
@ -3,6 +3,7 @@ package notifier
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
@ -22,24 +23,46 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestReceiverService_GetReceiver(t *testing.T) {
|
||||||
|
sqlStore := db.InitTestDB(t)
|
||||||
|
secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||||
|
|
||||||
|
t.Run("service gets receiver from AM config", func(t *testing.T) {
|
||||||
|
sut := createReceiverServiceSut(t, secretsService)
|
||||||
|
|
||||||
|
Receiver, err := sut.GetReceiver(context.Background(), singleQ(1, "slack receiver"), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "slack receiver", Receiver.Name)
|
||||||
|
require.Len(t, Receiver.GrafanaManagedReceivers, 1)
|
||||||
|
require.Equal(t, "UID2", Receiver.GrafanaManagedReceivers[0].UID)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("service returns error when receiver does not exist", func(t *testing.T) {
|
||||||
|
sut := createReceiverServiceSut(t, secretsService)
|
||||||
|
|
||||||
|
_, err := sut.GetReceiver(context.Background(), singleQ(1, "nonexistent"), nil)
|
||||||
|
require.ErrorIs(t, err, ErrNotFound)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestReceiverService_GetReceivers(t *testing.T) {
|
func TestReceiverService_GetReceivers(t *testing.T) {
|
||||||
sqlStore := db.InitTestDB(t)
|
sqlStore := db.InitTestDB(t)
|
||||||
secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||||
|
|
||||||
t.Run("service gets receiver groups from AM config", func(t *testing.T) {
|
t.Run("service gets receivers from AM config", func(t *testing.T) {
|
||||||
sut := createReceiverServiceSut(t, secretsService)
|
sut := createReceiverServiceSut(t, secretsService)
|
||||||
|
|
||||||
Receivers, err := sut.GetReceivers(context.Background(), rQuery(1), nil)
|
Receivers, err := sut.GetReceivers(context.Background(), multiQ(1), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, Receivers, 2)
|
require.Len(t, Receivers, 2)
|
||||||
require.Equal(t, "grafana-default-email", Receivers[0].Name)
|
require.Equal(t, "grafana-default-email", Receivers[0].Name)
|
||||||
require.Equal(t, "slack receiver", Receivers[1].Name)
|
require.Equal(t, "slack receiver", Receivers[1].Name)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("service filters receiver groups by name", func(t *testing.T) {
|
t.Run("service filters receivers by name", func(t *testing.T) {
|
||||||
sut := createReceiverServiceSut(t, secretsService)
|
sut := createReceiverServiceSut(t, secretsService)
|
||||||
|
|
||||||
Receivers, err := sut.GetReceivers(context.Background(), rQuery(1, "slack receiver"), nil)
|
Receivers, err := sut.GetReceivers(context.Background(), multiQ(1, "slack receiver"), nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, Receivers, 1)
|
require.Len(t, Receivers, 1)
|
||||||
require.Equal(t, "slack receiver", Receivers[0].Name)
|
require.Equal(t, "slack receiver", Receivers[0].Name)
|
||||||
@ -51,73 +74,96 @@ func TestReceiverService_DecryptRedact(t *testing.T) {
|
|||||||
secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
secretsService := manager.SetupTestService(t, database.ProvideSecretsStore(sqlStore))
|
||||||
ac := acimpl.ProvideAccessControl(setting.NewCfg())
|
ac := acimpl.ProvideAccessControl(setting.NewCfg())
|
||||||
|
|
||||||
t.Run("service redacts receiver groups by default", func(t *testing.T) {
|
getMethods := []string{"single", "multi"}
|
||||||
sut := createReceiverServiceSut(t, secretsService)
|
|
||||||
Receivers, err := sut.GetReceivers(context.Background(), rQuery(1, "slack receiver"), nil)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
require.Len(t, Receivers, 1)
|
readUser := &user.SignedInUser{
|
||||||
|
|
||||||
rGroup := Receivers[0]
|
|
||||||
require.Equal(t, "slack receiver", rGroup.Name)
|
|
||||||
require.Len(t, rGroup.GrafanaManagedReceivers, 1)
|
|
||||||
|
|
||||||
grafanaReceiver := rGroup.GrafanaManagedReceivers[0]
|
|
||||||
require.Equal(t, "UID2", grafanaReceiver.UID)
|
|
||||||
|
|
||||||
testedSettings, err := simplejson.NewJson([]byte(grafanaReceiver.Settings))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, definitions.RedactedValue, testedSettings.Get("url").MustString())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("service returns error when trying to decrypt with nil user", func(t *testing.T) {
|
|
||||||
sut := createReceiverServiceSut(t, secretsService)
|
|
||||||
sut.ac = ac
|
|
||||||
|
|
||||||
q := rQuery(1)
|
|
||||||
q.Decrypt = true
|
|
||||||
_, err := sut.GetReceivers(context.Background(), q, nil)
|
|
||||||
require.ErrorIs(t, err, ErrPermissionDenied)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("service returns error when trying to decrypt without permission", func(t *testing.T) {
|
|
||||||
sut := createReceiverServiceSut(t, secretsService)
|
|
||||||
sut.ac = ac
|
|
||||||
|
|
||||||
q := rQuery(1)
|
|
||||||
q.Decrypt = true
|
|
||||||
_, err := sut.GetReceivers(context.Background(), q, &user.SignedInUser{})
|
|
||||||
require.ErrorIs(t, err, ErrPermissionDenied)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("service decrypts receiver groups with permission", func(t *testing.T) {
|
|
||||||
sut := createReceiverServiceSut(t, secretsService)
|
|
||||||
sut.ac = ac
|
|
||||||
|
|
||||||
q := rQuery(1, "slack receiver")
|
|
||||||
q.Decrypt = true
|
|
||||||
Receivers, err := sut.GetReceivers(context.Background(), q, &user.SignedInUser{
|
|
||||||
OrgID: 1,
|
OrgID: 1,
|
||||||
Permissions: map[int64]map[string][]string{
|
Permissions: map[int64]map[string][]string{
|
||||||
1: {accesscontrol.ActionAlertingProvisioningReadSecrets: nil},
|
1: {accesscontrol.ActionAlertingProvisioningRead: nil},
|
||||||
},
|
},
|
||||||
})
|
}
|
||||||
|
|
||||||
|
secretUser := &user.SignedInUser{
|
||||||
|
OrgID: 1,
|
||||||
|
Permissions: map[int64]map[string][]string{
|
||||||
|
1: {
|
||||||
|
accesscontrol.ActionAlertingProvisioningRead: nil,
|
||||||
|
accesscontrol.ActionAlertingProvisioningReadSecrets: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
decrypt bool
|
||||||
|
user *user.SignedInUser
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "service redacts receivers by default",
|
||||||
|
decrypt: false,
|
||||||
|
user: readUser,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service returns error when trying to decrypt without permission",
|
||||||
|
decrypt: true,
|
||||||
|
user: readUser,
|
||||||
|
err: ErrPermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service returns error if user is nil and decrypt is true",
|
||||||
|
decrypt: true,
|
||||||
|
user: nil,
|
||||||
|
err: ErrPermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "service decrypts receivers with permission",
|
||||||
|
decrypt: true,
|
||||||
|
user: secretUser,
|
||||||
|
err: nil,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
for _, method := range getMethods {
|
||||||
|
t.Run(fmt.Sprintf("%s %s", tc.name, method), func(t *testing.T) {
|
||||||
|
sut := createReceiverServiceSut(t, secretsService)
|
||||||
|
sut.ac = ac
|
||||||
|
|
||||||
|
var res definitions.GettableApiReceiver
|
||||||
|
var err error
|
||||||
|
if method == "single" {
|
||||||
|
q := singleQ(1, "slack receiver")
|
||||||
|
q.Decrypt = tc.decrypt
|
||||||
|
res, err = sut.GetReceiver(context.Background(), q, tc.user)
|
||||||
|
} else {
|
||||||
|
q := multiQ(1, "slack receiver")
|
||||||
|
q.Decrypt = tc.decrypt
|
||||||
|
var multiRes []definitions.GettableApiReceiver
|
||||||
|
multiRes, err = sut.GetReceivers(context.Background(), q, tc.user)
|
||||||
|
if tc.err == nil {
|
||||||
|
require.Len(t, multiRes, 1)
|
||||||
|
res = multiRes[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.ErrorIs(t, err, tc.err)
|
||||||
|
|
||||||
|
if tc.err == nil {
|
||||||
|
require.Equal(t, "slack receiver", res.Name)
|
||||||
|
require.Len(t, res.GrafanaManagedReceivers, 1)
|
||||||
|
require.Equal(t, "UID2", res.GrafanaManagedReceivers[0].UID)
|
||||||
|
|
||||||
|
testedSettings, err := simplejson.NewJson([]byte(res.GrafanaManagedReceivers[0].Settings))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
if tc.decrypt {
|
||||||
require.Len(t, Receivers, 1)
|
require.Equal(t, "secure url", testedSettings.Get("url").MustString())
|
||||||
|
} else {
|
||||||
rGroup := Receivers[0]
|
require.Equal(t, definitions.RedactedValue, testedSettings.Get("url").MustString())
|
||||||
require.Equal(t, "slack receiver", rGroup.Name)
|
}
|
||||||
require.Len(t, rGroup.GrafanaManagedReceivers, 1)
|
}
|
||||||
|
|
||||||
grafanaReceiver := rGroup.GrafanaManagedReceivers[0]
|
|
||||||
require.Equal(t, "UID2", grafanaReceiver.UID)
|
|
||||||
|
|
||||||
settings, err := simplejson.NewJson([]byte(grafanaReceiver.Settings))
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, "secure url", settings.Get("url").MustString())
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func createReceiverServiceSut(t *testing.T, encryptSvc secrets.Service) *ReceiverService {
|
func createReceiverServiceSut(t *testing.T, encryptSvc secrets.Service) *ReceiverService {
|
||||||
cfg := createEncryptedConfig(t, encryptSvc)
|
cfg := createEncryptedConfig(t, encryptSvc)
|
||||||
@ -148,7 +194,14 @@ func createEncryptedConfig(t *testing.T, secretService secrets.Service) string {
|
|||||||
return string(bytes)
|
return string(bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func rQuery(orgID int64, names ...string) models.GetReceiversQuery {
|
func singleQ(orgID int64, name string) models.GetReceiverQuery {
|
||||||
|
return models.GetReceiverQuery{
|
||||||
|
OrgID: orgID,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func multiQ(orgID int64, names ...string) models.GetReceiversQuery {
|
||||||
return models.GetReceiversQuery{
|
return models.GetReceiversQuery{
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
Names: names,
|
Names: names,
|
||||||
|
@ -278,7 +278,7 @@ func TestContactPointServiceDecryptRedact(t *testing.T) {
|
|||||||
|
|
||||||
q := cpsQuery(1)
|
q := cpsQuery(1)
|
||||||
q.Decrypt = true
|
q.Decrypt = true
|
||||||
_, err := sut.GetContactPoints(context.Background(), q, &user.SignedInUser{})
|
_, err := sut.GetContactPoints(context.Background(), q, nil)
|
||||||
require.ErrorIs(t, err, ErrPermissionDenied)
|
require.ErrorIs(t, err, ErrPermissionDenied)
|
||||||
})
|
})
|
||||||
t.Run("GetContactPoints errors when Decrypt = true and user is nil", func(t *testing.T) {
|
t.Run("GetContactPoints errors when Decrypt = true and user is nil", func(t *testing.T) {
|
||||||
|
60
pkg/services/ngalert/tests/fakes/receivers.go
Normal file
60
pkg/services/ngalert/tests/fakes/receivers.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package fakes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ReceiverServiceMethodCall struct {
|
||||||
|
Method string
|
||||||
|
Args []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FakeReceiverService struct {
|
||||||
|
MethodCalls []ReceiverServiceMethodCall
|
||||||
|
GetReceiverFn func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error)
|
||||||
|
GetReceiversFn func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewFakeReceiverService() *FakeReceiverService {
|
||||||
|
return &FakeReceiverService{
|
||||||
|
GetReceiverFn: defaultReceiverFn,
|
||||||
|
GetReceiversFn: defaultReceiversFn,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeReceiverService) GetReceiver(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
|
f.MethodCalls = append(f.MethodCalls, ReceiverServiceMethodCall{Method: "GetReceiver", Args: []interface{}{ctx, q}})
|
||||||
|
return f.GetReceiverFn(ctx, q, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeReceiverService) GetReceivers(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
||||||
|
f.MethodCalls = append(f.MethodCalls, ReceiverServiceMethodCall{Method: "GetReceivers", Args: []interface{}{ctx, q}})
|
||||||
|
return f.GetReceiversFn(ctx, q, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeReceiverService) PopMethodCall() ReceiverServiceMethodCall {
|
||||||
|
if len(f.MethodCalls) == 0 {
|
||||||
|
return ReceiverServiceMethodCall{}
|
||||||
|
}
|
||||||
|
call := f.MethodCalls[len(f.MethodCalls)-1]
|
||||||
|
f.MethodCalls = f.MethodCalls[:len(f.MethodCalls)-1]
|
||||||
|
return call
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *FakeReceiverService) Reset() {
|
||||||
|
f.MethodCalls = nil
|
||||||
|
f.GetReceiverFn = defaultReceiverFn
|
||||||
|
f.GetReceiversFn = defaultReceiversFn
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultReceiverFn(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
|
return definitions.GettableApiReceiver{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultReceiversFn(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
@ -10355,61 +10355,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/notifications/time-intervals": {
|
|
||||||
"get": {
|
|
||||||
"description": "Get all the time intervals",
|
|
||||||
"tags": [
|
|
||||||
"notifications"
|
|
||||||
],
|
|
||||||
"operationId": "RouteNotificationsGetTimeIntervals",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"$ref": "#/responses/GetAllIntervalsResponse"
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "ForbiddenError",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/ForbiddenError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/notifications/time-intervals/{name}": {
|
|
||||||
"get": {
|
|
||||||
"tags": [
|
|
||||||
"notifications"
|
|
||||||
],
|
|
||||||
"summary": "Get a time interval by name.",
|
|
||||||
"operationId": "RouteNotificationsGetTimeInterval",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Time interval name",
|
|
||||||
"name": "name",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"$ref": "#/responses/GetIntervalsByNameResponse"
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"description": "ForbiddenError",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/ForbiddenError"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "NotFound",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/NotFound"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/provisioning/alert-rules": {
|
"/v1/provisioning/alert-rules": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@ -21704,7 +21649,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alertGroup": {
|
"alertGroup": {
|
||||||
"description": "AlertGroup alert group",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"alerts",
|
"alerts",
|
||||||
@ -21728,7 +21672,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
"description": "AlertGroups alert groups",
|
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/alertGroup"
|
"$ref": "#/definitions/alertGroup"
|
||||||
@ -21916,13 +21859,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gettableAlerts": {
|
"gettableAlerts": {
|
||||||
|
"description": "GettableAlerts gettable alerts",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/gettableAlert"
|
"$ref": "#/definitions/gettableAlert"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
"description": "GettableSilence gettable silence",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"comment",
|
"comment",
|
||||||
@ -21971,6 +21914,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"gettableSilences": {
|
"gettableSilences": {
|
||||||
|
"description": "GettableSilences gettable silences",
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/definitions/gettableSilence"
|
"$ref": "#/definitions/gettableSilence"
|
||||||
@ -22121,7 +22065,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postableSilence": {
|
"postableSilence": {
|
||||||
"description": "PostableSilence postable silence",
|
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"required": [
|
"required": [
|
||||||
"comment",
|
"comment",
|
||||||
@ -22388,6 +22331,21 @@
|
|||||||
"$ref": "#/definitions/GettableTimeIntervals"
|
"$ref": "#/definitions/GettableTimeIntervals"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"GetReceiverResponse": {
|
||||||
|
"description": "(empty)",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/GettableApiReceiver"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"GetReceiversResponse": {
|
||||||
|
"description": "(empty)",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/GettableApiReceiver"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"GettableHistoricUserConfigs": {
|
"GettableHistoricUserConfigs": {
|
||||||
"description": "(empty)",
|
"description": "(empty)",
|
||||||
"schema": {
|
"schema": {
|
||||||
|
@ -24,6 +24,29 @@
|
|||||||
},
|
},
|
||||||
"description": "(empty)"
|
"description": "(empty)"
|
||||||
},
|
},
|
||||||
|
"GetReceiverResponse": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/GettableApiReceiver"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "(empty)"
|
||||||
|
},
|
||||||
|
"GetReceiversResponse": {
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/GettableApiReceiver"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "(empty)"
|
||||||
|
},
|
||||||
"GettableHistoricUserConfigs": {
|
"GettableHistoricUserConfigs": {
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
@ -12194,7 +12217,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroup": {
|
"alertGroup": {
|
||||||
"description": "AlertGroup alert group",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"alerts": {
|
"alerts": {
|
||||||
"description": "alerts",
|
"description": "alerts",
|
||||||
@ -12218,7 +12240,6 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"alertGroups": {
|
"alertGroups": {
|
||||||
"description": "AlertGroups alert groups",
|
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/alertGroup"
|
"$ref": "#/components/schemas/alertGroup"
|
||||||
},
|
},
|
||||||
@ -12406,13 +12427,13 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"gettableAlerts": {
|
"gettableAlerts": {
|
||||||
|
"description": "GettableAlerts gettable alerts",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/gettableAlert"
|
"$ref": "#/components/schemas/gettableAlert"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"gettableSilence": {
|
"gettableSilence": {
|
||||||
"description": "GettableSilence gettable silence",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
"description": "comment",
|
"description": "comment",
|
||||||
@ -12461,6 +12482,7 @@
|
|||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"gettableSilences": {
|
"gettableSilences": {
|
||||||
|
"description": "GettableSilences gettable silences",
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/gettableSilence"
|
"$ref": "#/components/schemas/gettableSilence"
|
||||||
},
|
},
|
||||||
@ -12611,7 +12633,6 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"postableSilence": {
|
"postableSilence": {
|
||||||
"description": "PostableSilence postable silence",
|
|
||||||
"properties": {
|
"properties": {
|
||||||
"comment": {
|
"comment": {
|
||||||
"description": "comment",
|
"description": "comment",
|
||||||
@ -23973,75 +23994,6 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/v1/notifications/time-intervals": {
|
|
||||||
"get": {
|
|
||||||
"description": "Get all the time intervals",
|
|
||||||
"operationId": "RouteNotificationsGetTimeIntervals",
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"$ref": "#/components/responses/GetAllIntervalsResponse"
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ForbiddenError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": "ForbiddenError"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tags": [
|
|
||||||
"notifications"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/notifications/time-intervals/{name}": {
|
|
||||||
"get": {
|
|
||||||
"operationId": "RouteNotificationsGetTimeInterval",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"description": "Time interval name",
|
|
||||||
"in": "path",
|
|
||||||
"name": "name",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"$ref": "#/components/responses/GetIntervalsByNameResponse"
|
|
||||||
},
|
|
||||||
"403": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/ForbiddenError"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": "ForbiddenError"
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"content": {
|
|
||||||
"application/json": {
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/NotFound"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"description": "NotFound"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"summary": "Get a time interval by name.",
|
|
||||||
"tags": [
|
|
||||||
"notifications"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/v1/provisioning/alert-rules": {
|
"/v1/provisioning/alert-rules": {
|
||||||
"get": {
|
"get": {
|
||||||
"operationId": "RouteGetAlertRules",
|
"operationId": "RouteGetAlertRules",
|
||||||
|
Loading…
Reference in New Issue
Block a user