Plugins: Support Admission validation hooks (#87718)

This commit is contained in:
Ryan McKinley
2024-05-24 18:45:16 +03:00
committed by GitHub
parent b1eb4b7dad
commit ffc2702552
43 changed files with 1091 additions and 117 deletions
+42
View File
@@ -217,6 +217,48 @@ func (s *Service) RunStream(ctx context.Context, req *backend.RunStreamRequest,
return plugin.RunStream(ctx, req, sender)
}
// ConvertObject implements plugins.Client.
func (s *Service) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
if req == nil {
return nil, errNilRequest
}
plugin, exists := s.plugin(ctx, req.PluginContext.PluginID, req.PluginContext.PluginVersion)
if !exists {
return nil, plugins.ErrPluginNotRegistered
}
return plugin.ConvertObject(ctx, req)
}
// MutateAdmission implements plugins.Client.
func (s *Service) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
if req == nil {
return nil, errNilRequest
}
plugin, exists := s.plugin(ctx, req.PluginContext.PluginID, req.PluginContext.PluginVersion)
if !exists {
return nil, plugins.ErrPluginNotRegistered
}
return plugin.MutateAdmission(ctx, req)
}
// ValidateAdmission implements plugins.Client.
func (s *Service) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
if req == nil {
return nil, errNilRequest
}
plugin, exists := s.plugin(ctx, req.PluginContext.PluginID, req.PluginContext.PluginVersion)
if !exists {
return nil, plugins.ErrPluginNotRegistered
}
return plugin.ValidateAdmission(ctx, req)
}
// plugin finds a plugin with `pluginID` from the registry that is not decommissioned
func (s *Service) plugin(ctx context.Context, pluginID, pluginVersion string) (*plugins.Plugin, bool) {
p, exists := s.pluginRegistry.Plugin(ctx, pluginID, pluginVersion)
@@ -20,13 +20,16 @@ import (
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
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
ConvertObjectFunc backend.ConvertObjectFunc
}
func (c *TestClient) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
@@ -84,14 +87,39 @@ func (c *TestClient) RunStream(ctx context.Context, req *backend.RunStreamReques
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) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
if c.ConvertObjectFunc != nil {
return c.ConvertObjectFunc(ctx, req)
}
return nil, nil
}
type MiddlewareScenarioContext struct {
QueryDataCallChain []string
CallResourceCallChain []string
CollectMetricsCallChain []string
CheckHealthCallChain []string
SubscribeStreamCallChain []string
PublishStreamCallChain []string
RunStreamCallChain []string
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 {
@@ -131,6 +159,27 @@ func (m *TestMiddleware) CollectMetrics(ctx context.Context, req *backend.Collec
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) ConvertObject(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.ConvertObject(ctx, req)
m.sCtx.ConvertObjectCallChain = append(m.sCtx.ConvertObjectCallChain, 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)
+32 -2
View File
@@ -5,6 +5,7 @@ import (
"errors"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/plugins"
)
@@ -14,6 +15,10 @@ type Decorator struct {
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 {
@@ -98,6 +103,33 @@ func (d *Decorator) RunStream(ctx context.Context, req *backend.RunStreamRequest
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
}
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
}
client := clientFromMiddlewares(d.middlewares, d.client)
return client.MutateAdmission(ctx, req)
}
func (d *Decorator) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
if req == nil {
return nil, errNilRequest
}
client := clientFromMiddlewares(d.middlewares, d.client)
return client.ConvertObject(ctx, req)
}
func clientFromMiddlewares(middlewares []plugins.ClientMiddleware, finalClient plugins.Client) plugins.Client {
if len(middlewares) == 0 {
return finalClient
@@ -123,5 +155,3 @@ func reverseMiddlewares(middlewares []plugins.ClientMiddleware) []plugins.Client
return reversed
}
var _ plugins.Client = &Decorator{}
+34 -8
View File
@@ -6,8 +6,9 @@ import (
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/plugins"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/plugins"
)
func TestDecorator(t *testing.T) {
@@ -146,13 +147,17 @@ func (c *TestClient) CheckHealth(ctx context.Context, req *backend.CheckHealthRe
}
type MiddlewareScenarioContext struct {
QueryDataCallChain []string
CallResourceCallChain []string
CollectMetricsCallChain []string
CheckHealthCallChain []string
SubscribeStreamCallChain []string
PublishStreamCallChain []string
RunStreamCallChain []string
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 {
@@ -220,4 +225,25 @@ func (m *TestMiddleware) RunStream(ctx context.Context, req *backend.RunStreamRe
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) ConvertObject(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.ConvertObject(ctx, req)
m.sCtx.ConvertObjectCallChain = append(m.sCtx.ConvertObjectCallChain, fmt.Sprintf("after %s", m.Name))
return res, err
}
var _ plugins.Client = &TestClient{}
+27
View File
@@ -70,6 +70,9 @@ type FakePluginClient struct {
backend.CheckHealthHandlerFunc
backend.QueryDataHandlerFunc
backend.CallResourceHandlerFunc
backend.MutateAdmissionFunc
backend.ValidateAdmissionFunc
backend.ConvertObjectFunc
mutex sync.RWMutex
backendplugin.Plugin
@@ -166,6 +169,30 @@ func (pc *FakePluginClient) RunStream(_ context.Context, _ *backend.RunStreamReq
return plugins.ErrMethodNotImplemented
}
func (pc *FakePluginClient) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
if pc.ValidateAdmissionFunc != nil {
return pc.ValidateAdmissionFunc(ctx, req)
}
return nil, plugins.ErrMethodNotImplemented
}
func (pc *FakePluginClient) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
if pc.MutateAdmissionFunc != nil {
return pc.MutateAdmissionFunc(ctx, req)
}
return nil, plugins.ErrMethodNotImplemented
}
func (pc *FakePluginClient) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
if pc.ConvertObjectFunc != nil {
return pc.ConvertObjectFunc(ctx, req)
}
return nil, plugins.ErrMethodNotImplemented
}
type FakePluginRegistry struct {
Store map[string]*plugins.Plugin
}