Plugins: Use handler middleware from the SDK (#93445)
updates sdk to v0.251.0
This commit is contained in:
committed by
GitHub
parent
54faa541c3
commit
b7a7f2bd62
@@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/util/proxyutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -101,8 +102,11 @@ func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceReq
|
||||
removeConnectionHeaders(res.Headers)
|
||||
removeHopByHopHeaders(res.Headers)
|
||||
removeNonAllowedHeaders(res.Headers)
|
||||
} else {
|
||||
res.Headers = map[string][]string{}
|
||||
}
|
||||
|
||||
proxyutil.SetProxyResponseHeaders(res.Headers)
|
||||
ensureContentTypeHeader(res)
|
||||
}
|
||||
|
||||
|
||||
@@ -310,6 +310,48 @@ func TestCallResource(t *testing.T) {
|
||||
require.Equal(t, "should not be deleted", res.Headers["X-Custom"][0])
|
||||
})
|
||||
|
||||
t.Run("Should set proxy response headers", func(t *testing.T) {
|
||||
resHeaders := map[string][]string{
|
||||
"X-Custom": {"should not be deleted"},
|
||||
}
|
||||
|
||||
req := &backend.CallResourceRequest{
|
||||
PluginContext: backend.PluginContext{
|
||||
PluginID: "pid",
|
||||
},
|
||||
}
|
||||
|
||||
responses := []*backend.CallResourceResponse{}
|
||||
sender := backend.CallResourceResponseSenderFunc(func(res *backend.CallResourceResponse) error {
|
||||
responses = append(responses, res)
|
||||
return nil
|
||||
})
|
||||
|
||||
p.RegisterClient(&fakePluginBackend{
|
||||
crr: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
return sender.Send(&backend.CallResourceResponse{
|
||||
Headers: resHeaders,
|
||||
Status: http.StatusOK,
|
||||
Body: []byte(backendResponse),
|
||||
})
|
||||
},
|
||||
})
|
||||
err := registry.Add(context.Background(), p)
|
||||
require.NoError(t, err)
|
||||
|
||||
client := ProvideService(registry)
|
||||
|
||||
err = client.CallResource(context.Background(), req, sender)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Len(t, responses, 1)
|
||||
res := responses[0]
|
||||
require.Equal(t, http.StatusOK, res.Status)
|
||||
require.Equal(t, []byte(backendResponse), res.Body)
|
||||
require.Equal(t, "sandbox", res.Headers["Content-Security-Policy"][0])
|
||||
require.Equal(t, "should not be deleted", res.Headers["X-Custom"][0])
|
||||
})
|
||||
|
||||
t.Run("Should ensure content type header", func(t *testing.T) {
|
||||
tcs := []struct {
|
||||
contentType string
|
||||
|
||||
@@ -1,331 +0,0 @@
|
||||
package clienttest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/client"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
type TestClient struct {
|
||||
plugins.Client
|
||||
QueryDataFunc backend.QueryDataHandlerFunc
|
||||
CallResourceFunc backend.CallResourceHandlerFunc
|
||||
CheckHealthFunc backend.CheckHealthHandlerFunc
|
||||
CollectMetricsFunc backend.CollectMetricsHandlerFunc
|
||||
SubscribeStreamFunc func(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error)
|
||||
PublishStreamFunc func(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error)
|
||||
RunStreamFunc func(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error
|
||||
ValidateAdmissionFunc backend.ValidateAdmissionFunc
|
||||
MutateAdmissionFunc backend.MutateAdmissionFunc
|
||||
ConvertObjectsFunc backend.ConvertObjectsFunc
|
||||
}
|
||||
|
||||
func (c *TestClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
if c.QueryDataFunc != nil {
|
||||
return c.QueryDataFunc(ctx, req)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *TestClient) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
if c.CallResourceFunc != nil {
|
||||
return c.CallResourceFunc(ctx, req, sender)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TestClient) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
if c.CheckHealthFunc != nil {
|
||||
return c.CheckHealthFunc(ctx, req)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *TestClient) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) {
|
||||
if c.CollectMetricsFunc != nil {
|
||||
return c.CollectMetricsFunc(ctx, req)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *TestClient) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
|
||||
if c.PublishStreamFunc != nil {
|
||||
return c.PublishStreamFunc(ctx, req)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *TestClient) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
|
||||
if c.SubscribeStreamFunc != nil {
|
||||
return c.SubscribeStreamFunc(ctx, req)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *TestClient) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
if c.RunStreamFunc != nil {
|
||||
return c.RunStreamFunc(ctx, req, sender)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TestClient) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
if c.ValidateAdmissionFunc != nil {
|
||||
return c.ValidateAdmissionFunc(ctx, req)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *TestClient) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
if c.MutateAdmissionFunc != nil {
|
||||
return c.MutateAdmissionFunc(ctx, req)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *TestClient) ConvertObjects(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
if c.ConvertObjectsFunc != nil {
|
||||
return c.ConvertObjectsFunc(ctx, req)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type MiddlewareScenarioContext struct {
|
||||
QueryDataCallChain []string
|
||||
CallResourceCallChain []string
|
||||
CollectMetricsCallChain []string
|
||||
CheckHealthCallChain []string
|
||||
SubscribeStreamCallChain []string
|
||||
PublishStreamCallChain []string
|
||||
RunStreamCallChain []string
|
||||
InstanceSettingsCallChain []string
|
||||
ValidateAdmissionCallChain []string
|
||||
MutateAdmissionCallChain []string
|
||||
ConvertObjectsCallChain []string
|
||||
}
|
||||
|
||||
func (ctx *MiddlewareScenarioContext) NewMiddleware(name string) plugins.ClientMiddleware {
|
||||
return plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client {
|
||||
return &TestMiddleware{
|
||||
next: next,
|
||||
Name: name,
|
||||
sCtx: ctx,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type TestMiddleware struct {
|
||||
next plugins.Client
|
||||
sCtx *MiddlewareScenarioContext
|
||||
Name string
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
m.sCtx.QueryDataCallChain = append(m.sCtx.QueryDataCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.QueryData(ctx, req)
|
||||
m.sCtx.QueryDataCallChain = append(m.sCtx.QueryDataCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
m.sCtx.CallResourceCallChain = append(m.sCtx.CallResourceCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
err := m.next.CallResource(ctx, req, sender)
|
||||
m.sCtx.CallResourceCallChain = append(m.sCtx.CallResourceCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) {
|
||||
m.sCtx.CollectMetricsCallChain = append(m.sCtx.CollectMetricsCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.CollectMetrics(ctx, req)
|
||||
m.sCtx.CollectMetricsCallChain = append(m.sCtx.CollectMetricsCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
m.sCtx.ValidateAdmissionCallChain = append(m.sCtx.ValidateAdmissionCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.ValidateAdmission(ctx, req)
|
||||
m.sCtx.ValidateAdmissionCallChain = append(m.sCtx.ValidateAdmissionCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
m.sCtx.MutateAdmissionCallChain = append(m.sCtx.MutateAdmissionCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.MutateAdmission(ctx, req)
|
||||
m.sCtx.MutateAdmissionCallChain = append(m.sCtx.MutateAdmissionCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) ConvertObjects(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
m.sCtx.ConvertObjectsCallChain = append(m.sCtx.ConvertObjectsCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.ConvertObjects(ctx, req)
|
||||
m.sCtx.ConvertObjectsCallChain = append(m.sCtx.ConvertObjectsCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
m.sCtx.CheckHealthCallChain = append(m.sCtx.CheckHealthCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.CheckHealth(ctx, req)
|
||||
m.sCtx.CheckHealthCallChain = append(m.sCtx.CheckHealthCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
|
||||
m.sCtx.SubscribeStreamCallChain = append(m.sCtx.SubscribeStreamCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.SubscribeStream(ctx, req)
|
||||
m.sCtx.SubscribeStreamCallChain = append(m.sCtx.SubscribeStreamCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
|
||||
m.sCtx.PublishStreamCallChain = append(m.sCtx.PublishStreamCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.PublishStream(ctx, req)
|
||||
m.sCtx.PublishStreamCallChain = append(m.sCtx.PublishStreamCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
m.sCtx.RunStreamCallChain = append(m.sCtx.RunStreamCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
err := m.next.RunStream(ctx, req, sender)
|
||||
m.sCtx.RunStreamCallChain = append(m.sCtx.RunStreamCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return err
|
||||
}
|
||||
|
||||
var _ plugins.Client = &TestClient{}
|
||||
|
||||
type ClientDecoratorTest struct {
|
||||
T *testing.T
|
||||
Context context.Context
|
||||
TestClient *TestClient
|
||||
Middlewares []plugins.ClientMiddleware
|
||||
Decorator *client.Decorator
|
||||
ReqContext *contextmodel.ReqContext
|
||||
QueryDataReq *backend.QueryDataRequest
|
||||
QueryDataCtx context.Context
|
||||
CallResourceReq *backend.CallResourceRequest
|
||||
CallResourceCtx context.Context
|
||||
CheckHealthReq *backend.CheckHealthRequest
|
||||
CheckHealthCtx context.Context
|
||||
CollectMetricsReq *backend.CollectMetricsRequest
|
||||
CollectMetricsCtx context.Context
|
||||
SubscribeStreamReq *backend.SubscribeStreamRequest
|
||||
SubscribeStreamCtx context.Context
|
||||
PublishStreamReq *backend.PublishStreamRequest
|
||||
PublishStreamCtx context.Context
|
||||
|
||||
// When CallResource is called, the sender will be called with these values
|
||||
callResourceResponses []*backend.CallResourceResponse
|
||||
}
|
||||
|
||||
type ClientDecoratorTestOption func(*ClientDecoratorTest)
|
||||
|
||||
func NewClientDecoratorTest(t *testing.T, opts ...ClientDecoratorTestOption) *ClientDecoratorTest {
|
||||
cdt := &ClientDecoratorTest{
|
||||
T: t,
|
||||
Context: context.Background(),
|
||||
}
|
||||
cdt.TestClient = &TestClient{
|
||||
QueryDataFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
cdt.QueryDataReq = req
|
||||
cdt.QueryDataCtx = ctx
|
||||
return nil, nil
|
||||
},
|
||||
CallResourceFunc: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
cdt.CallResourceReq = req
|
||||
cdt.CallResourceCtx = ctx
|
||||
if cdt.callResourceResponses != nil {
|
||||
for _, r := range cdt.callResourceResponses {
|
||||
if err := sender.Send(r); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
CheckHealthFunc: func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
cdt.CheckHealthReq = req
|
||||
cdt.CheckHealthCtx = ctx
|
||||
return nil, nil
|
||||
},
|
||||
CollectMetricsFunc: func(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) {
|
||||
cdt.CollectMetricsReq = req
|
||||
cdt.CollectMetricsCtx = ctx
|
||||
return nil, nil
|
||||
},
|
||||
SubscribeStreamFunc: func(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
|
||||
cdt.SubscribeStreamReq = req
|
||||
cdt.SubscribeStreamCtx = ctx
|
||||
return nil, nil
|
||||
},
|
||||
PublishStreamFunc: func(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
|
||||
cdt.PublishStreamReq = req
|
||||
cdt.PublishStreamCtx = ctx
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
require.NotNil(t, cdt)
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cdt)
|
||||
}
|
||||
|
||||
d, err := client.NewDecorator(cdt.TestClient, cdt.Middlewares...)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, d)
|
||||
|
||||
cdt.Decorator = d
|
||||
|
||||
return cdt
|
||||
}
|
||||
|
||||
func WithReqContext(req *http.Request, user *user.SignedInUser) ClientDecoratorTestOption {
|
||||
return ClientDecoratorTestOption(func(cdt *ClientDecoratorTest) {
|
||||
if cdt.ReqContext == nil {
|
||||
cdt.ReqContext = &contextmodel.ReqContext{
|
||||
Context: &web.Context{
|
||||
Resp: web.NewResponseWriter(req.Method, httptest.NewRecorder()),
|
||||
},
|
||||
SignedInUser: user,
|
||||
}
|
||||
}
|
||||
|
||||
cdt.Context = ctxkey.Set(cdt.Context, cdt.ReqContext)
|
||||
|
||||
*req = *req.WithContext(cdt.Context)
|
||||
cdt.ReqContext.Req = req
|
||||
})
|
||||
}
|
||||
|
||||
func WithMiddlewares(middlewares ...plugins.ClientMiddleware) ClientDecoratorTestOption {
|
||||
return ClientDecoratorTestOption(func(cdt *ClientDecoratorTest) {
|
||||
if cdt.Middlewares == nil {
|
||||
cdt.Middlewares = []plugins.ClientMiddleware{}
|
||||
}
|
||||
|
||||
cdt.Middlewares = append(cdt.Middlewares, middlewares...)
|
||||
})
|
||||
}
|
||||
|
||||
// WithResourceResponses can be used to make the test client send simulated resource responses back over the sender stream
|
||||
func WithResourceResponses(responses []*backend.CallResourceResponse) ClientDecoratorTestOption {
|
||||
return ClientDecoratorTestOption(func(cdt *ClientDecoratorTest) {
|
||||
cdt.callResourceResponses = responses
|
||||
})
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
// Decorator allows a plugins.Client to be decorated with middlewares.
|
||||
type Decorator struct {
|
||||
client plugins.Client
|
||||
middlewares []plugins.ClientMiddleware
|
||||
}
|
||||
|
||||
var (
|
||||
_ = plugins.Client(&Decorator{})
|
||||
)
|
||||
|
||||
// NewDecorator creates a new plugins.client decorator.
|
||||
func NewDecorator(client plugins.Client, middlewares ...plugins.ClientMiddleware) (*Decorator, error) {
|
||||
if client == nil {
|
||||
return nil, errors.New("client cannot be nil")
|
||||
}
|
||||
|
||||
return &Decorator{
|
||||
client: client,
|
||||
middlewares: middlewares,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *Decorator) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
if req == nil {
|
||||
return nil, errNilRequest
|
||||
}
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointQueryData)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
|
||||
return client.QueryData(ctx, req)
|
||||
}
|
||||
|
||||
func (d *Decorator) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
if req == nil {
|
||||
return errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointCallResource)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
if sender == nil {
|
||||
return errors.New("sender cannot be nil")
|
||||
}
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.CallResource(ctx, req, sender)
|
||||
}
|
||||
|
||||
func (d *Decorator) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) {
|
||||
if req == nil {
|
||||
return nil, errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointCollectMetrics)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.CollectMetrics(ctx, req)
|
||||
}
|
||||
|
||||
func (d *Decorator) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
if req == nil {
|
||||
return nil, errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointCheckHealth)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.CheckHealth(ctx, req)
|
||||
}
|
||||
|
||||
func (d *Decorator) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
|
||||
if req == nil {
|
||||
return nil, errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointSubscribeStream)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.SubscribeStream(ctx, req)
|
||||
}
|
||||
|
||||
func (d *Decorator) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
|
||||
if req == nil {
|
||||
return nil, errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointPublishStream)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.PublishStream(ctx, req)
|
||||
}
|
||||
|
||||
func (d *Decorator) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
if req == nil {
|
||||
return errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointRunStream)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
if sender == nil {
|
||||
return errors.New("sender cannot be nil")
|
||||
}
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.RunStream(ctx, req, sender)
|
||||
}
|
||||
|
||||
func (d *Decorator) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
if req == nil {
|
||||
return nil, errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointValidateAdmission)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.ValidateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
func (d *Decorator) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
if req == nil {
|
||||
return nil, errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointMutateAdmission)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.MutateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
func (d *Decorator) ConvertObjects(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
if req == nil {
|
||||
return nil, errNilRequest
|
||||
}
|
||||
|
||||
ctx = backend.WithEndpoint(ctx, backend.EndpointConvertObject)
|
||||
ctx = backend.WithPluginContext(ctx, req.PluginContext)
|
||||
ctx = backend.WithUser(ctx, req.PluginContext.User)
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.ConvertObjects(ctx, req)
|
||||
}
|
||||
|
||||
func clientFromMiddlewares(middlewares []plugins.ClientMiddleware, finalClient plugins.Client) plugins.Client {
|
||||
if len(middlewares) == 0 {
|
||||
return finalClient
|
||||
}
|
||||
|
||||
reversed := reverseMiddlewares(middlewares)
|
||||
next := finalClient
|
||||
|
||||
for _, m := range reversed {
|
||||
next = m.CreateClientMiddleware(next)
|
||||
}
|
||||
|
||||
return next
|
||||
}
|
||||
|
||||
func reverseMiddlewares(middlewares []plugins.ClientMiddleware) []plugins.ClientMiddleware {
|
||||
reversed := make([]plugins.ClientMiddleware, len(middlewares))
|
||||
copy(reversed, middlewares)
|
||||
|
||||
for i, j := 0, len(reversed)-1; i < j; i, j = i+1, j-1 {
|
||||
reversed[i], reversed[j] = reversed[j], reversed[i]
|
||||
}
|
||||
|
||||
return reversed
|
||||
}
|
||||
@@ -1,249 +0,0 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
func TestDecorator(t *testing.T) {
|
||||
var queryDataCalled bool
|
||||
var callResourceCalled bool
|
||||
var checkHealthCalled bool
|
||||
c := &TestClient{
|
||||
QueryDataFunc: func(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
queryDataCalled = true
|
||||
return nil, nil
|
||||
},
|
||||
CallResourceFunc: func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
callResourceCalled = true
|
||||
return nil
|
||||
},
|
||||
CheckHealthFunc: func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
checkHealthCalled = true
|
||||
return nil, nil
|
||||
},
|
||||
}
|
||||
require.NotNil(t, c)
|
||||
|
||||
ctx := MiddlewareScenarioContext{}
|
||||
|
||||
mwOne := ctx.NewMiddleware("mw1")
|
||||
mwTwo := ctx.NewMiddleware("mw2")
|
||||
|
||||
d, err := NewDecorator(c, mwOne, mwTwo)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, d)
|
||||
|
||||
_, _ = d.QueryData(context.Background(), &backend.QueryDataRequest{})
|
||||
require.True(t, queryDataCalled)
|
||||
|
||||
sender := backend.CallResourceResponseSenderFunc(func(res *backend.CallResourceResponse) error {
|
||||
return nil
|
||||
})
|
||||
|
||||
_ = d.CallResource(context.Background(), &backend.CallResourceRequest{}, sender)
|
||||
require.True(t, callResourceCalled)
|
||||
|
||||
_, _ = d.CheckHealth(context.Background(), &backend.CheckHealthRequest{})
|
||||
require.True(t, checkHealthCalled)
|
||||
|
||||
require.Len(t, ctx.QueryDataCallChain, 4)
|
||||
require.EqualValues(t, []string{"before mw1", "before mw2", "after mw2", "after mw1"}, ctx.QueryDataCallChain)
|
||||
require.Len(t, ctx.CallResourceCallChain, 4)
|
||||
require.EqualValues(t, []string{"before mw1", "before mw2", "after mw2", "after mw1"}, ctx.CallResourceCallChain)
|
||||
require.Len(t, ctx.CheckHealthCallChain, 4)
|
||||
require.EqualValues(t, []string{"before mw1", "before mw2", "after mw2", "after mw1"}, ctx.CheckHealthCallChain)
|
||||
}
|
||||
|
||||
func TestReverseMiddlewares(t *testing.T) {
|
||||
t.Run("Should reverse 1 middleware", func(t *testing.T) {
|
||||
ctx := MiddlewareScenarioContext{}
|
||||
middlewares := []plugins.ClientMiddleware{
|
||||
ctx.NewMiddleware("mw1"),
|
||||
}
|
||||
reversed := reverseMiddlewares(middlewares)
|
||||
require.Len(t, reversed, 1)
|
||||
require.Equal(t, "mw1", reversed[0].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
})
|
||||
|
||||
t.Run("Should reverse 2 middlewares", func(t *testing.T) {
|
||||
ctx := MiddlewareScenarioContext{}
|
||||
middlewares := []plugins.ClientMiddleware{
|
||||
ctx.NewMiddleware("mw1"),
|
||||
ctx.NewMiddleware("mw2"),
|
||||
}
|
||||
reversed := reverseMiddlewares(middlewares)
|
||||
require.Len(t, reversed, 2)
|
||||
require.Equal(t, "mw2", reversed[0].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
require.Equal(t, "mw1", reversed[1].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
})
|
||||
|
||||
t.Run("Should reverse 3 middlewares", func(t *testing.T) {
|
||||
ctx := MiddlewareScenarioContext{}
|
||||
middlewares := []plugins.ClientMiddleware{
|
||||
ctx.NewMiddleware("mw1"),
|
||||
ctx.NewMiddleware("mw2"),
|
||||
ctx.NewMiddleware("mw3"),
|
||||
}
|
||||
reversed := reverseMiddlewares(middlewares)
|
||||
require.Len(t, reversed, 3)
|
||||
require.Equal(t, "mw3", reversed[0].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
require.Equal(t, "mw2", reversed[1].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
require.Equal(t, "mw1", reversed[2].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
})
|
||||
|
||||
t.Run("Should reverse 4 middlewares", func(t *testing.T) {
|
||||
ctx := MiddlewareScenarioContext{}
|
||||
middlewares := []plugins.ClientMiddleware{
|
||||
ctx.NewMiddleware("mw1"),
|
||||
ctx.NewMiddleware("mw2"),
|
||||
ctx.NewMiddleware("mw3"),
|
||||
ctx.NewMiddleware("mw4"),
|
||||
}
|
||||
reversed := reverseMiddlewares(middlewares)
|
||||
require.Len(t, reversed, 4)
|
||||
require.Equal(t, "mw4", reversed[0].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
require.Equal(t, "mw3", reversed[1].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
require.Equal(t, "mw2", reversed[2].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
require.Equal(t, "mw1", reversed[3].CreateClientMiddleware(nil).(*TestMiddleware).Name)
|
||||
})
|
||||
}
|
||||
|
||||
type TestClient struct {
|
||||
plugins.Client
|
||||
QueryDataFunc backend.QueryDataHandlerFunc
|
||||
CallResourceFunc backend.CallResourceHandlerFunc
|
||||
CheckHealthFunc backend.CheckHealthHandlerFunc
|
||||
}
|
||||
|
||||
func (c *TestClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
if c.QueryDataFunc != nil {
|
||||
return c.QueryDataFunc(ctx, req)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *TestClient) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
if c.CallResourceFunc != nil {
|
||||
return c.CallResourceFunc(ctx, req, sender)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *TestClient) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
if c.CheckHealthFunc != nil {
|
||||
return c.CheckHealthFunc(ctx, req)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
type MiddlewareScenarioContext struct {
|
||||
QueryDataCallChain []string
|
||||
CallResourceCallChain []string
|
||||
CollectMetricsCallChain []string
|
||||
CheckHealthCallChain []string
|
||||
SubscribeStreamCallChain []string
|
||||
PublishStreamCallChain []string
|
||||
RunStreamCallChain []string
|
||||
InstanceSettingsCallChain []string
|
||||
ValidateAdmissionCallChain []string
|
||||
MutateAdmissionCallChain []string
|
||||
ConvertObjectCallChain []string
|
||||
}
|
||||
|
||||
func (ctx *MiddlewareScenarioContext) NewMiddleware(name string) plugins.ClientMiddleware {
|
||||
return plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client {
|
||||
return &TestMiddleware{
|
||||
next: next,
|
||||
Name: name,
|
||||
sCtx: ctx,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type TestMiddleware struct {
|
||||
next plugins.Client
|
||||
sCtx *MiddlewareScenarioContext
|
||||
Name string
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
m.sCtx.QueryDataCallChain = append(m.sCtx.QueryDataCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.QueryData(ctx, req)
|
||||
m.sCtx.QueryDataCallChain = append(m.sCtx.QueryDataCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
m.sCtx.CallResourceCallChain = append(m.sCtx.CallResourceCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
err := m.next.CallResource(ctx, req, sender)
|
||||
m.sCtx.CallResourceCallChain = append(m.sCtx.CallResourceCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) {
|
||||
m.sCtx.CollectMetricsCallChain = append(m.sCtx.CollectMetricsCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.CollectMetrics(ctx, req)
|
||||
m.sCtx.CollectMetricsCallChain = append(m.sCtx.CollectMetricsCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
m.sCtx.CheckHealthCallChain = append(m.sCtx.CheckHealthCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.CheckHealth(ctx, req)
|
||||
m.sCtx.CheckHealthCallChain = append(m.sCtx.CheckHealthCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
|
||||
m.sCtx.SubscribeStreamCallChain = append(m.sCtx.SubscribeStreamCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.SubscribeStream(ctx, req)
|
||||
m.sCtx.SubscribeStreamCallChain = append(m.sCtx.SubscribeStreamCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
|
||||
m.sCtx.PublishStreamCallChain = append(m.sCtx.PublishStreamCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.PublishStream(ctx, req)
|
||||
m.sCtx.PublishStreamCallChain = append(m.sCtx.PublishStreamCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
m.sCtx.RunStreamCallChain = append(m.sCtx.RunStreamCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
err := m.next.RunStream(ctx, req, sender)
|
||||
m.sCtx.RunStreamCallChain = append(m.sCtx.RunStreamCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
m.sCtx.ValidateAdmissionCallChain = append(m.sCtx.ValidateAdmissionCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.ValidateAdmission(ctx, req)
|
||||
m.sCtx.ValidateAdmissionCallChain = append(m.sCtx.ValidateAdmissionCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
m.sCtx.MutateAdmissionCallChain = append(m.sCtx.MutateAdmissionCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.MutateAdmission(ctx, req)
|
||||
m.sCtx.MutateAdmissionCallChain = append(m.sCtx.MutateAdmissionCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
func (m *TestMiddleware) ConvertObjects(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
m.sCtx.ConvertObjectCallChain = append(m.sCtx.ConvertObjectCallChain, fmt.Sprintf("before %s", m.Name))
|
||||
res, err := m.next.ConvertObjects(ctx, req)
|
||||
m.sCtx.ConvertObjectCallChain = append(m.sCtx.ConvertObjectCallChain, fmt.Sprintf("after %s", m.Name))
|
||||
return res, err
|
||||
}
|
||||
|
||||
var _ plugins.Client = &TestClient{}
|
||||
Reference in New Issue
Block a user