Plugins: Use handler middleware from the SDK (#93445)

updates sdk to v0.251.0
This commit is contained in:
Marcus Efraimsson
2024-09-30 16:33:15 +02:00
committed by GitHub
parent 54faa541c3
commit b7a7f2bd62
47 changed files with 524 additions and 1448 deletions
+4
View File
@@ -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)
}
+42
View File
@@ -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
})
}
-196
View File
@@ -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{}