Plugins: Refactor forward of cookies, OAuth token and header modifications by introducing client middlewares (#58132)
Adding support for backend plugin client middlewares. This allows headers in outgoing backend plugin and HTTP requests to be modified using client middlewares. The following client middlewares added: Forward cookies: Will forward incoming HTTP request Cookies to outgoing plugins.Client and HTTP requests if the datasource has enabled forwarding of cookies (keepCookies). Forward OAuth token: Will set OAuth token headers on outgoing plugins.Client and HTTP requests if the datasource has enabled Forward OAuth Identity (oauthPassThru). Clear auth headers: Will clear any outgoing HTTP headers that was part of the incoming HTTP request and used when authenticating to Grafana. The current suggested way to register client middlewares is to have a separate package, pluginsintegration, responsible for bootstrap/instantiate the backend plugin client with middlewares and/or longer term bootstrap/instantiate plugin management. Fixes #54135 Related to #47734 Related to #57870 Related to #41623 Related to #57065
This commit is contained in:
@@ -79,3 +79,20 @@ type PluginLoaderAuthorizer interface {
|
||||
type RoleRegistry interface {
|
||||
DeclarePluginRoles(ctx context.Context, ID, name string, registrations []RoleRegistration) error
|
||||
}
|
||||
|
||||
// ClientMiddleware is an interface representing the ability to create a middleware
|
||||
// that implements the Client interface.
|
||||
type ClientMiddleware interface {
|
||||
// CreateClientMiddleware creates a new client middleware.
|
||||
CreateClientMiddleware(next Client) Client
|
||||
}
|
||||
|
||||
// The ClientMiddlewareFunc type is an adapter to allow the use of ordinary
|
||||
// functions as ClientMiddleware's. If f is a function with the appropriate
|
||||
// signature, ClientMiddlewareFunc(f) is a ClientMiddleware that calls f.
|
||||
type ClientMiddlewareFunc func(next Client) Client
|
||||
|
||||
// CreateClientMiddleware implements the ClientMiddleware interface.
|
||||
func (fn ClientMiddlewareFunc) CreateClientMiddleware(next Client) Client {
|
||||
return fn(next)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,10 @@ func ProvideService(pluginRegistry registry.Service, cfg *config.Cfg) *Service {
|
||||
}
|
||||
|
||||
func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
plugin, exists := s.plugin(ctx, req.PluginContext.PluginID)
|
||||
if !exists {
|
||||
return nil, plugins.ErrPluginNotRegistered.Errorf("%w", backendplugin.ErrPluginNotRegistered)
|
||||
@@ -65,6 +69,14 @@ func (s *Service) QueryData(ctx context.Context, req *backend.QueryDataRequest)
|
||||
}
|
||||
|
||||
func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
if req == nil {
|
||||
return fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
if sender == nil {
|
||||
return fmt.Errorf("sender cannot be nil")
|
||||
}
|
||||
|
||||
p, exists := s.plugin(ctx, req.PluginContext.PluginID)
|
||||
if !exists {
|
||||
return backendplugin.ErrPluginNotRegistered
|
||||
@@ -84,6 +96,10 @@ func (s *Service) CallResource(ctx context.Context, req *backend.CallResourceReq
|
||||
}
|
||||
|
||||
func (s *Service) CollectMetrics(ctx context.Context, req *backend.CollectMetricsRequest) (*backend.CollectMetricsResult, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
p, exists := s.plugin(ctx, req.PluginContext.PluginID)
|
||||
if !exists {
|
||||
return nil, backendplugin.ErrPluginNotRegistered
|
||||
@@ -102,6 +118,10 @@ func (s *Service) CollectMetrics(ctx context.Context, req *backend.CollectMetric
|
||||
}
|
||||
|
||||
func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
p, exists := s.plugin(ctx, req.PluginContext.PluginID)
|
||||
if !exists {
|
||||
return nil, backendplugin.ErrPluginNotRegistered
|
||||
@@ -129,6 +149,10 @@ func (s *Service) CheckHealth(ctx context.Context, req *backend.CheckHealthReque
|
||||
}
|
||||
|
||||
func (s *Service) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
plugin, exists := s.plugin(ctx, req.PluginContext.PluginID)
|
||||
if !exists {
|
||||
return nil, backendplugin.ErrPluginNotRegistered
|
||||
@@ -138,6 +162,10 @@ func (s *Service) SubscribeStream(ctx context.Context, req *backend.SubscribeStr
|
||||
}
|
||||
|
||||
func (s *Service) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
plugin, exists := s.plugin(ctx, req.PluginContext.PluginID)
|
||||
if !exists {
|
||||
return nil, backendplugin.ErrPluginNotRegistered
|
||||
@@ -147,6 +175,14 @@ func (s *Service) PublishStream(ctx context.Context, req *backend.PublishStreamR
|
||||
}
|
||||
|
||||
func (s *Service) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
if req == nil {
|
||||
return fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
if sender == nil {
|
||||
return fmt.Errorf("sender cannot be nil")
|
||||
}
|
||||
|
||||
plugin, exists := s.plugin(ctx, req.PluginContext.PluginID)
|
||||
if !exists {
|
||||
return backendplugin.ErrPluginNotRegistered
|
||||
|
||||
@@ -0,0 +1,205 @@
|
||||
package clienttest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/client"
|
||||
"github.com/grafana/grafana/pkg/services/contexthandler/ctxkey"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var _ plugins.Client = &TestClient{}
|
||||
|
||||
type ClientDecoratorTest struct {
|
||||
T *testing.T
|
||||
Context context.Context
|
||||
TestClient *TestClient
|
||||
Middlewares []plugins.ClientMiddleware
|
||||
Decorator *client.Decorator
|
||||
ReqContext *models.ReqContext
|
||||
QueryDataReq *backend.QueryDataRequest
|
||||
QueryDataCtx context.Context
|
||||
CallResourceReq *backend.CallResourceRequest
|
||||
CallResourceCtx context.Context
|
||||
CheckHealthReq *backend.CheckHealthRequest
|
||||
CheckHealthCtx context.Context
|
||||
}
|
||||
|
||||
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
|
||||
return nil
|
||||
},
|
||||
CheckHealthFunc: func(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
cdt.CheckHealthReq = req
|
||||
cdt.CheckHealthCtx = 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 = &models.ReqContext{
|
||||
Context: &web.Context{},
|
||||
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...)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
// NewDecorator creates a new plugins.client decorator.
|
||||
func NewDecorator(client plugins.Client, middlewares ...plugins.ClientMiddleware) (*Decorator, error) {
|
||||
if client == nil {
|
||||
return nil, fmt.Errorf("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, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
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 fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
if sender == nil {
|
||||
return fmt.Errorf("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, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
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, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
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, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
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, fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
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 fmt.Errorf("req cannot be nil")
|
||||
}
|
||||
|
||||
if sender == nil {
|
||||
return fmt.Errorf("sender cannot be nil")
|
||||
}
|
||||
|
||||
client := clientFromMiddlewares(d.middlewares, d.client)
|
||||
return client.RunStream(ctx, req, sender)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
var _ plugins.Client = &Decorator{}
|
||||
@@ -0,0 +1,229 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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 := 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
type callResourceResponseSenderFunc func(res *backend.CallResourceResponse) error
|
||||
|
||||
func (fn callResourceResponseSenderFunc) Send(res *backend.CallResourceResponse) error {
|
||||
return fn(res)
|
||||
}
|
||||
|
||||
var _ plugins.Client = &TestClient{}
|
||||
Reference in New Issue
Block a user