mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Plugins: Support Admission validation hooks (#87718)
This commit is contained in:
parent
b1eb4b7dad
commit
ffc2702552
2
go.mod
2
go.mod
@ -99,7 +99,7 @@ require (
|
||||
github.com/grafana/grafana-azure-sdk-go/v2 v2.0.3 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.231.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.232.0 // @grafana/plugins-platform-backend
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad
|
||||
github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad
|
||||
// This needs to be here for other projects that import grafana/grafana
|
||||
|
4
go.sum
4
go.sum
@ -2179,8 +2179,8 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs=
|
||||
github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.231.0 h1:Qt4PBDR8b4MTUxL48EaZw1fHI1rXUNNhvTU/Nf0Ex2g=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.231.0/go.mod h1:8fJk+5J1hMkpqY/7vrXHKgAsqELWNkQvLQ5A5xCVZHk=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.232.0 h1:RnaQwhAOxYdp9wwy0Yz5cJUGY5tpIXPxoFWmEKflfww=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.232.0/go.mod h1:bNgmNmub1I7Mc8dzIncgNqHC5jTgSZPPHlZ3aG8HKJQ=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 h1:hpyusz8c3yRFoJPlA0o34rWnsLbaOOBZleqRhFBi5Lg=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vrRQJuNprTWqwm6JPxHf3BoTJhvO15QMEjQ7Q/YUOnI=
|
||||
github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 h1:tIbI5zgos92vwJ8lV3zwHwuxkV03GR3FGLkFW9V5LxY=
|
||||
|
@ -661,6 +661,7 @@ github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8/
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.228.0/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.229.0/go.mod h1:6V6ikT4ryva8MrAp7Bdz5fTJx3/ztzKvpMJFfpzr4CI=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.230.0/go.mod h1:6V6ikT4ryva8MrAp7Bdz5fTJx3/ztzKvpMJFfpzr4CI=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.231.1-0.20240523124942-62dae9836284/go.mod h1:bNgmNmub1I7Mc8dzIncgNqHC5jTgSZPPHlZ3aG8HKJQ=
|
||||
github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo=
|
||||
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y=
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
"github.com/grafana/grafana/pkg/services/auth/identity"
|
||||
@ -827,7 +828,8 @@ func getDatasourceProxiedRequest(t *testing.T, ctx *contextmodel.ReqContext, cfg
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
features := featuremgmt.WithFeatures()
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features), &actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
||||
&actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(ds, routes, ctx, "", cfg, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, features)
|
||||
require.NoError(t, err)
|
||||
@ -947,7 +949,8 @@ func runDatasourceAuthTest(t *testing.T, secretsService secrets.Service, secrets
|
||||
var routes []*plugins.Route
|
||||
features := featuremgmt.WithFeatures()
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features), &actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
||||
&actest.FakePermissionsService{}, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
proxy, err := NewDataSourceProxy(test.datasource, routes, ctx, "", &setting.Cfg{}, httpclient.NewProvider(), &oauthtoken.Service{}, dsService, tracer, features)
|
||||
require.NoError(t, err)
|
||||
@ -1001,7 +1004,8 @@ func setupDSProxyTest(t *testing.T, ctx *contextmodel.ReqContext, ds *datasource
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(dbtest.NewFakeDB(), secretsService, log.NewNopLogger())
|
||||
features := featuremgmt.WithFeatures()
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features), &actest.FakePermissionsService{}, quotatest.New(false, nil), &pluginstore.FakePluginStore{})
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, features, acimpl.ProvideAccessControl(features),
|
||||
&actest.FakePermissionsService{}, quotatest.New(false, nil), &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
tracer := tracing.InitializeTracerForTest()
|
||||
|
@ -5,7 +5,7 @@ go 1.21.10
|
||||
require (
|
||||
github.com/bwmarrin/snowflake v0.3.0
|
||||
github.com/gorilla/mux v1.8.1
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.231.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.232.0
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
|
@ -124,7 +124,7 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
|
||||
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.231.0 h1:Qt4PBDR8b4MTUxL48EaZw1fHI1rXUNNhvTU/Nf0Ex2g=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.232.0 h1:RnaQwhAOxYdp9wwy0Yz5cJUGY5tpIXPxoFWmEKflfww=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f h1:+CK3tH3XrAAqx5urmVqpgSxMrL2MlpTOnLVSU4w4IjY=
|
||||
github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f/go.mod h1:ZxIaCOlDmFupiL55aLU+Qp7O1dgwkDMBAQBK7wnEVBg=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI=
|
||||
|
@ -25,13 +25,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -150,13 +154,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -316,13 +324,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -556,13 +568,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -744,13 +760,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -868,13 +888,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -35,13 +35,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -168,13 +172,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -342,13 +350,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -590,13 +602,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -786,13 +802,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
@ -918,13 +938,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^__expr__$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -18,6 +18,7 @@ type corePlugin struct {
|
||||
backend.CallResourceHandler
|
||||
backend.QueryDataHandler
|
||||
backend.StreamHandler
|
||||
backend.AdmissionHandler
|
||||
}
|
||||
|
||||
// New returns a new backendplugin.PluginFactoryFunc for creating a core (built-in) backendplugin.Plugin.
|
||||
@ -29,6 +30,7 @@ func New(opts backend.ServeOpts) backendplugin.PluginFactoryFunc {
|
||||
CheckHealthHandler: opts.CheckHealthHandler,
|
||||
CallResourceHandler: opts.CallResourceHandler,
|
||||
QueryDataHandler: opts.QueryDataHandler,
|
||||
AdmissionHandler: opts.AdmissionHandler,
|
||||
StreamHandler: opts.StreamHandler,
|
||||
}, nil
|
||||
}
|
||||
@ -124,3 +126,27 @@ func (cp *corePlugin) RunStream(ctx context.Context, req *backend.RunStreamReque
|
||||
}
|
||||
return plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
func (cp *corePlugin) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
if cp.AdmissionHandler != nil {
|
||||
ctx = backend.WithGrafanaConfig(ctx, req.PluginContext.GrafanaConfig)
|
||||
return cp.AdmissionHandler.MutateAdmission(ctx, req)
|
||||
}
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
func (cp *corePlugin) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
if cp.AdmissionHandler != nil {
|
||||
ctx = backend.WithGrafanaConfig(ctx, req.PluginContext.GrafanaConfig)
|
||||
return cp.AdmissionHandler.ValidateAdmission(ctx, req)
|
||||
}
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
func (cp *corePlugin) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
if cp.AdmissionHandler != nil {
|
||||
ctx = backend.WithGrafanaConfig(ctx, req.PluginContext.GrafanaConfig)
|
||||
return cp.AdmissionHandler.ConvertObject(ctx, req)
|
||||
}
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
sdktracing "github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
@ -145,6 +146,9 @@ func asBackendPlugin(svc any) backendplugin.PluginFactoryFunc {
|
||||
if healthHandler, ok := svc.(backend.CheckHealthHandler); ok {
|
||||
opts.CheckHealthHandler = healthHandler
|
||||
}
|
||||
if storageHandler, ok := svc.(backend.AdmissionHandler); ok {
|
||||
opts.AdmissionHandler = storageHandler
|
||||
}
|
||||
|
||||
if opts.QueryDataHandler != nil || opts.CallResourceHandler != nil ||
|
||||
opts.CheckHealthHandler != nil || opts.StreamHandler != nil {
|
||||
|
@ -33,6 +33,7 @@ var pluginSet = map[int]goplugin.PluginSet{
|
||||
"resource": &grpcplugin.ResourceGRPCPlugin{},
|
||||
"data": &grpcplugin.DataGRPCPlugin{},
|
||||
"stream": &grpcplugin.StreamGRPCPlugin{},
|
||||
"admission": &grpcplugin.AdmissionGRPCPlugin{},
|
||||
"renderer": &pluginextensionv2.RendererGRPCPlugin{},
|
||||
"secretsmanager": &secretsmanagerplugin.SecretsManagerGRPCPlugin{},
|
||||
},
|
||||
|
@ -24,6 +24,7 @@ type ClientV2 struct {
|
||||
grpcplugin.ResourceClient
|
||||
grpcplugin.DataClient
|
||||
grpcplugin.StreamClient
|
||||
grpcplugin.AdmissionClient
|
||||
pluginextensionv2.RendererPlugin
|
||||
secretsmanagerplugin.SecretsManagerPlugin
|
||||
}
|
||||
@ -44,6 +45,11 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawAdmission, err := rpcClient.Dispense("admission")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rawStream, err := rpcClient.Dispense("stream")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -78,6 +84,12 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
|
||||
}
|
||||
}
|
||||
|
||||
if rawAdmission != nil {
|
||||
if admissionClient, ok := rawAdmission.(grpcplugin.AdmissionClient); ok {
|
||||
c.AdmissionClient = admissionClient
|
||||
}
|
||||
}
|
||||
|
||||
if rawStream != nil {
|
||||
if streamClient, ok := rawStream.(grpcplugin.StreamClient); ok {
|
||||
c.StreamClient = streamClient
|
||||
@ -257,3 +269,60 @@ func (c *ClientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *ClientV2) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
if c.AdmissionClient == nil {
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
protoReq := backend.ToProto().AdmissionRequest(req)
|
||||
protoResp, err := c.AdmissionClient.ValidateAdmission(ctx, protoReq)
|
||||
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%v: %w", "Failed to ValidateAdmission", err)
|
||||
}
|
||||
|
||||
return backend.FromProto().ValidationResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *ClientV2) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
if c.AdmissionClient == nil {
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
protoReq := backend.ToProto().AdmissionRequest(req)
|
||||
protoResp, err := c.AdmissionClient.MutateAdmission(ctx, protoReq)
|
||||
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%v: %w", "Failed to MutateAdmission", err)
|
||||
}
|
||||
|
||||
return backend.FromProto().MutationResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *ClientV2) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
if c.AdmissionClient == nil {
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
protoReq := backend.ToProto().ConversionRequest(req)
|
||||
protoResp, err := c.AdmissionClient.ConvertObject(ctx, protoReq)
|
||||
|
||||
if err != nil {
|
||||
if status.Code(err) == codes.Unimplemented {
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("%v: %w", "Failed to ConvertObject", err)
|
||||
}
|
||||
|
||||
return backend.FromProto().ConversionResponse(protoResp), nil
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ type pluginClient interface {
|
||||
backend.CheckHealthHandler
|
||||
backend.QueryDataHandler
|
||||
backend.CallResourceHandler
|
||||
backend.AdmissionHandler
|
||||
backend.StreamHandler
|
||||
}
|
||||
|
||||
@ -199,3 +200,27 @@ func (p *grpcPlugin) RunStream(ctx context.Context, req *backend.RunStreamReques
|
||||
}
|
||||
return pluginClient.RunStream(ctx, req, sender)
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) ValidateAdmission(ctx context.Context, request *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
pluginClient, ok := p.getPluginClient()
|
||||
if !ok {
|
||||
return nil, plugins.ErrPluginUnavailable
|
||||
}
|
||||
return pluginClient.ValidateAdmission(ctx, request)
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) MutateAdmission(ctx context.Context, request *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
pluginClient, ok := p.getPluginClient()
|
||||
if !ok {
|
||||
return nil, plugins.ErrPluginUnavailable
|
||||
}
|
||||
return pluginClient.MutateAdmission(ctx, request)
|
||||
}
|
||||
|
||||
func (p *grpcPlugin) ConvertObject(ctx context.Context, request *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
pluginClient, ok := p.getPluginClient()
|
||||
if !ok {
|
||||
return nil, plugins.ErrPluginUnavailable
|
||||
}
|
||||
return pluginClient.ConvertObject(ctx, request)
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ type Plugin interface {
|
||||
backend.CheckHealthHandler
|
||||
backend.QueryDataHandler
|
||||
backend.CallResourceHandler
|
||||
backend.AdmissionHandler
|
||||
backend.StreamHandler
|
||||
}
|
||||
|
||||
|
@ -90,6 +90,7 @@ type Client interface {
|
||||
backend.QueryDataHandler
|
||||
backend.CheckHealthHandler
|
||||
backend.StreamHandler
|
||||
backend.AdmissionHandler
|
||||
backend.CallResourceHandler
|
||||
backend.CollectMetricsHandler
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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{}
|
||||
|
@ -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{}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -69,6 +69,15 @@ type Plugin struct {
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
var (
|
||||
_ = backend.CollectMetricsHandler(&Plugin{})
|
||||
_ = backend.CheckHealthHandler(&Plugin{})
|
||||
_ = backend.QueryDataHandler(&Plugin{})
|
||||
_ = backend.CallResourceHandler(&Plugin{})
|
||||
_ = backend.StreamHandler(&Plugin{})
|
||||
_ = backend.AdmissionHandler(&Plugin{})
|
||||
)
|
||||
|
||||
type AngularMeta struct {
|
||||
Detected bool `json:"detected"`
|
||||
HideDeprecation bool `json:"hideDeprecation"`
|
||||
@ -360,6 +369,33 @@ func (p *Plugin) RunStream(ctx context.Context, req *backend.RunStreamRequest, s
|
||||
return pluginClient.RunStream(ctx, req, sender)
|
||||
}
|
||||
|
||||
// ValidateAdmission implements backend.AdmissionHandler.
|
||||
func (p *Plugin) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
pluginClient, ok := p.Client()
|
||||
if !ok {
|
||||
return nil, ErrPluginUnavailable
|
||||
}
|
||||
return pluginClient.ValidateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
// MutateAdmission implements backend.AdmissionHandler.
|
||||
func (p *Plugin) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
pluginClient, ok := p.Client()
|
||||
if !ok {
|
||||
return nil, ErrPluginUnavailable
|
||||
}
|
||||
return pluginClient.MutateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
// ConvertObject implements backend.AdmissionHandler.
|
||||
func (p *Plugin) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
pluginClient, ok := p.Client()
|
||||
if !ok {
|
||||
return nil, ErrPluginUnavailable
|
||||
}
|
||||
return pluginClient.ConvertObject(ctx, req)
|
||||
}
|
||||
|
||||
func (p *Plugin) File(name string) (fs.File, error) {
|
||||
cleanPath, err := util.CleanRelativePath(name)
|
||||
if err != nil {
|
||||
@ -418,6 +454,7 @@ type PluginClient interface {
|
||||
backend.CollectMetricsHandler
|
||||
backend.CheckHealthHandler
|
||||
backend.CallResourceHandler
|
||||
backend.AdmissionHandler
|
||||
backend.StreamHandler
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/promlib
|
||||
go 1.21.10
|
||||
|
||||
require (
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.231.0
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.232.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/prometheus/client_golang v1.19.0
|
||||
|
@ -86,7 +86,7 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1
|
||||
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.231.0 h1:Qt4PBDR8b4MTUxL48EaZw1fHI1rXUNNhvTU/Nf0Ex2g=
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.232.0 h1:RnaQwhAOxYdp9wwy0Yz5cJUGY5tpIXPxoFWmEKflfww=
|
||||
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA=
|
||||
github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A=
|
||||
github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA=
|
||||
|
@ -46,13 +46,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^prometheus$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -56,13 +56,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^prometheus$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
sdkhttpclient "github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
sdkproxy "github.com/grafana/grafana-plugin-sdk-go/backend/proxy"
|
||||
|
||||
@ -18,6 +19,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@ -26,6 +28,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/secrets"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -44,6 +47,7 @@ type Service struct {
|
||||
logger log.Logger
|
||||
db db.DB
|
||||
pluginStore pluginstore.Store
|
||||
pluginClient plugins.Client // access to everything
|
||||
|
||||
ptc proxyTransportCache
|
||||
}
|
||||
@ -61,7 +65,7 @@ type cachedRoundTripper struct {
|
||||
func ProvideService(
|
||||
db db.DB, secretsService secrets.Service, secretsStore kvstore.SecretsKVStore, cfg *setting.Cfg,
|
||||
features featuremgmt.FeatureToggles, ac accesscontrol.AccessControl, datasourcePermissionsService accesscontrol.DatasourcePermissionsService,
|
||||
quotaService quota.Service, pluginStore pluginstore.Store,
|
||||
quotaService quota.Service, pluginStore pluginstore.Store, pluginClient plugins.Client,
|
||||
) (*Service, error) {
|
||||
dslogger := log.New("datasources")
|
||||
store := &SqlStore{db: db, logger: dslogger, features: features}
|
||||
@ -79,6 +83,7 @@ func ProvideService(
|
||||
logger: dslogger,
|
||||
db: db,
|
||||
pluginStore: pluginStore,
|
||||
pluginClient: pluginClient,
|
||||
}
|
||||
|
||||
ac.RegisterScopeAttributeResolver(NewNameScopeResolver(store))
|
||||
@ -205,10 +210,48 @@ func (s *Service) AddDataSource(ctx context.Context, cmd *datasources.AddDataSou
|
||||
cmd.Name = getAvailableName(cmd.Type, dataSources)
|
||||
}
|
||||
|
||||
if err := s.validateFields(ctx, cmd.Name, cmd.URL, cmd.Type, cmd.APIVersion); err != nil {
|
||||
// Validate the command
|
||||
jd, err := cmd.JsonData.ToDB()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid jsonData")
|
||||
}
|
||||
|
||||
settings, err := s.prepareInstanceSettings(ctx, backend.PluginContext{
|
||||
OrgID: cmd.OrgID,
|
||||
PluginID: cmd.Type,
|
||||
}, &backend.DataSourceInstanceSettings{
|
||||
UID: cmd.UID,
|
||||
Name: cmd.Name,
|
||||
URL: cmd.URL,
|
||||
Database: cmd.Database,
|
||||
JSONData: jd,
|
||||
DecryptedSecureJSONData: cmd.SecureJsonData,
|
||||
Type: cmd.Type,
|
||||
User: cmd.User,
|
||||
BasicAuthEnabled: cmd.BasicAuth,
|
||||
BasicAuthUser: cmd.BasicAuthUser,
|
||||
APIVersion: cmd.APIVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// The mutable properties
|
||||
cmd.URL = settings.URL
|
||||
cmd.User = settings.User
|
||||
cmd.BasicAuth = settings.BasicAuthEnabled
|
||||
cmd.BasicAuthUser = settings.BasicAuthUser
|
||||
cmd.Database = settings.Database
|
||||
cmd.SecureJsonData = settings.DecryptedSecureJSONData
|
||||
cmd.JsonData = nil
|
||||
if settings.JSONData != nil {
|
||||
cmd.JsonData = simplejson.New()
|
||||
err := cmd.JsonData.FromDB(settings.JSONData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var dataSource *datasources.DataSource
|
||||
return dataSource, s.db.InTransaction(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
@ -252,6 +295,122 @@ func (s *Service) AddDataSource(ctx context.Context, cmd *datasources.AddDataSou
|
||||
})
|
||||
}
|
||||
|
||||
// This will valid validate the instance settings return a version that is safe to be saved
|
||||
func (s *Service) prepareInstanceSettings(ctx context.Context, pluginContext backend.PluginContext, settings *backend.DataSourceInstanceSettings) (*backend.DataSourceInstanceSettings, error) {
|
||||
operation := backend.AdmissionRequestCreate
|
||||
|
||||
// First apply global validation rules -- these are required regardless which plugin we are talking to
|
||||
if len(settings.Name) > maxDatasourceNameLen {
|
||||
return nil, datasources.ErrDataSourceNameInvalid.Errorf("max length is %d", maxDatasourceNameLen)
|
||||
}
|
||||
|
||||
if len(settings.URL) > maxDatasourceUrlLen {
|
||||
return nil, datasources.ErrDataSourceURLInvalid.Errorf("max length is %d", maxDatasourceUrlLen)
|
||||
}
|
||||
|
||||
if settings.Type == "" {
|
||||
return settings, nil // NOOP used in tests
|
||||
}
|
||||
|
||||
// Make sure it is a known plugin type
|
||||
p, found := s.pluginStore.Plugin(ctx, settings.Type)
|
||||
if !found {
|
||||
return nil, errutil.BadRequest("datasource.unknownPlugin",
|
||||
errutil.WithPublicMessage(fmt.Sprintf("plugin '%s' not found", settings.Type)))
|
||||
}
|
||||
|
||||
// When the APIVersion is set, the client must also implement AdmissionHandler
|
||||
if p.APIVersion == "" {
|
||||
if settings.APIVersion != "" {
|
||||
return nil, fmt.Errorf("invalid request apiVersion (datasource does not have one configured)")
|
||||
}
|
||||
return settings, nil // NOOP
|
||||
}
|
||||
|
||||
pb, err := backend.DataSourceInstanceSettingsToProtoBytes(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &backend.AdmissionRequest{
|
||||
Operation: backend.AdmissionRequestCreate,
|
||||
PluginContext: pluginContext,
|
||||
Kind: settings.GVK(),
|
||||
ObjectBytes: pb,
|
||||
}
|
||||
|
||||
// Set the old bytes and change the operation
|
||||
if pluginContext.DataSourceInstanceSettings != nil {
|
||||
req.Operation = backend.AdmissionRequestUpdate
|
||||
req.OldObjectBytes, err = backend.DataSourceInstanceSettingsToProtoBytes(settings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
{ // As an example, this will first call validate (then mutate)
|
||||
// Implementations may vary, but typically validation is
|
||||
// more strict because it does not have the option to fix anything
|
||||
// that has reasonable fixes.
|
||||
rsp, err := s.pluginClient.ValidateAdmission(ctx, req)
|
||||
if err != nil {
|
||||
if errors.Is(err, plugins.ErrMethodNotImplemented) {
|
||||
return nil, errutil.Internal("plugin.unimplemented").
|
||||
Errorf("plugin (%s) with apiVersion=%s must implement ValidateAdmission", p.ID, p.APIVersion)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if rsp == nil {
|
||||
return nil, fmt.Errorf("expected response (%v)", operation)
|
||||
}
|
||||
if !rsp.Allowed {
|
||||
if rsp.Result != nil {
|
||||
return nil, toError(rsp.Result)
|
||||
}
|
||||
return nil, fmt.Errorf("not allowed")
|
||||
}
|
||||
// payload is OK, but now lets do the mutate version...
|
||||
}
|
||||
|
||||
// Next calling mutation -- this will try to get the input into an acceptable form
|
||||
rsp, err := s.pluginClient.MutateAdmission(ctx, req)
|
||||
if err != nil {
|
||||
if errors.Is(err, plugins.ErrMethodNotImplemented) {
|
||||
return nil, errutil.Internal("plugin.unimplemented").
|
||||
Errorf("plugin (%s) with apiVersion=%s must implement MutateAdmission", p.ID, p.APIVersion)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if rsp == nil {
|
||||
return nil, fmt.Errorf("expected response (%v)", operation)
|
||||
}
|
||||
if !rsp.Allowed {
|
||||
if rsp.Result != nil {
|
||||
return nil, toError(rsp.Result)
|
||||
}
|
||||
return nil, fmt.Errorf("not allowed")
|
||||
}
|
||||
if rsp.ObjectBytes == nil {
|
||||
return nil, fmt.Errorf("mutation response is missing value")
|
||||
}
|
||||
return backend.DataSourceInstanceSettingsFromProto(rsp.ObjectBytes, pluginContext.PluginID)
|
||||
}
|
||||
|
||||
func toError(status *backend.StatusResult) error {
|
||||
if status == nil {
|
||||
return fmt.Errorf("error converting status")
|
||||
}
|
||||
// hymm -- no way to pass the raw http status along!!
|
||||
// Looks like it must be based on the reason string
|
||||
return errutil.Error{
|
||||
Reason: errutil.CoreStatus(status.Reason),
|
||||
MessageID: "datasource.config.mutate",
|
||||
LogMessage: status.Message,
|
||||
PublicMessage: status.Message,
|
||||
Source: errutil.SourceDownstream,
|
||||
}
|
||||
}
|
||||
|
||||
// getAvailableName finds the first available name for a datasource of the given type.
|
||||
func getAvailableName(dsType string, dataSources []*datasources.DataSource) string {
|
||||
dsNames := make(map[string]bool)
|
||||
@ -284,13 +443,40 @@ func (s *Service) DeleteDataSource(ctx context.Context, cmd *datasources.DeleteD
|
||||
})
|
||||
}
|
||||
|
||||
func (s *Service) getPluginContext(ctx context.Context, orgID int64, pluginID string, ds *datasources.DataSource) (backend.PluginContext, error) {
|
||||
var err error
|
||||
if ds == nil {
|
||||
return backend.PluginContext{
|
||||
OrgID: orgID,
|
||||
PluginID: pluginID,
|
||||
}, err
|
||||
}
|
||||
pctx := backend.PluginContext{
|
||||
OrgID: orgID,
|
||||
PluginID: pluginID,
|
||||
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{
|
||||
UID: ds.UID,
|
||||
Type: pluginID,
|
||||
Name: ds.Name,
|
||||
URL: ds.URL,
|
||||
Database: ds.Database,
|
||||
BasicAuthEnabled: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
Updated: ds.Updated,
|
||||
APIVersion: ds.APIVersion,
|
||||
User: ds.User,
|
||||
},
|
||||
}
|
||||
pctx.DataSourceInstanceSettings.JSONData, err = ds.JsonData.ToDB()
|
||||
if err == nil && len(ds.SecureJsonData) > 0 {
|
||||
pctx.DataSourceInstanceSettings.DecryptedSecureJSONData, err = s.DecryptedValues(ctx, ds)
|
||||
}
|
||||
return pctx, err
|
||||
}
|
||||
|
||||
func (s *Service) UpdateDataSource(ctx context.Context, cmd *datasources.UpdateDataSourceCommand) (*datasources.DataSource, error) {
|
||||
var dataSource *datasources.DataSource
|
||||
|
||||
if err := s.validateFields(ctx, cmd.Name, cmd.URL, cmd.Type, cmd.APIVersion); err != nil {
|
||||
return dataSource, err
|
||||
}
|
||||
|
||||
return dataSource, s.db.InTransaction(ctx, func(ctx context.Context) error {
|
||||
var err error
|
||||
|
||||
@ -303,6 +489,54 @@ func (s *Service) UpdateDataSource(ctx context.Context, cmd *datasources.UpdateD
|
||||
return err
|
||||
}
|
||||
|
||||
// Validate the command
|
||||
jd, err := cmd.JsonData.ToDB()
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid jsonData")
|
||||
}
|
||||
pctx, err := s.getPluginContext(ctx, cmd.OrgID, cmd.Type, dataSource)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
settings, err := s.prepareInstanceSettings(ctx, pctx,
|
||||
&backend.DataSourceInstanceSettings{
|
||||
UID: cmd.UID,
|
||||
Name: cmd.Name,
|
||||
URL: cmd.URL,
|
||||
Database: cmd.Database,
|
||||
JSONData: jd,
|
||||
DecryptedSecureJSONData: cmd.SecureJsonData,
|
||||
Type: cmd.Type,
|
||||
User: cmd.User,
|
||||
BasicAuthEnabled: cmd.BasicAuth,
|
||||
BasicAuthUser: cmd.BasicAuthUser,
|
||||
APIVersion: cmd.APIVersion,
|
||||
Updated: time.Now(),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if settings == nil {
|
||||
return fmt.Errorf("settings or an error is required")
|
||||
}
|
||||
|
||||
// The mutable properties
|
||||
cmd.URL = settings.URL
|
||||
cmd.User = settings.User
|
||||
cmd.BasicAuth = settings.BasicAuthEnabled
|
||||
cmd.BasicAuthUser = settings.BasicAuthUser
|
||||
cmd.Database = settings.Database
|
||||
cmd.SecureJsonData = settings.DecryptedSecureJSONData
|
||||
cmd.JsonData = nil
|
||||
if settings.JSONData != nil {
|
||||
cmd.JsonData = simplejson.New()
|
||||
err := cmd.JsonData.FromDB(settings.JSONData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if cmd.Name != "" && cmd.Name != dataSource.Name {
|
||||
query := &datasources.GetDataSourceQuery{
|
||||
Name: cmd.Name,
|
||||
@ -716,32 +950,6 @@ func (s *Service) fillWithSecureJSONData(ctx context.Context, cmd *datasources.U
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) validateFields(ctx context.Context, name, url, pluginID, apiVersion string) error {
|
||||
if len(name) > maxDatasourceNameLen {
|
||||
return datasources.ErrDataSourceNameInvalid.Errorf("max length is %d", maxDatasourceNameLen)
|
||||
}
|
||||
|
||||
if len(url) > maxDatasourceUrlLen {
|
||||
return datasources.ErrDataSourceURLInvalid.Errorf("max length is %d", maxDatasourceUrlLen)
|
||||
}
|
||||
|
||||
if apiVersion == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
p, found := s.pluginStore.Plugin(context.Background(), pluginID)
|
||||
if !found {
|
||||
// Plugin not installed, ignore apiVersion check
|
||||
return nil
|
||||
}
|
||||
|
||||
if p.APIVersion != "" && p.APIVersion != apiVersion {
|
||||
return datasources.ErrDataSourceAPIVersionInvalid.Errorf("expected %s, got %s", p.APIVersion, apiVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func readQuotaConfig(cfg *setting.Cfg) (*quota.Map, error) {
|
||||
limits := "a.Map{}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@ -20,6 +21,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
@ -33,6 +35,8 @@ import (
|
||||
secretsmng "github.com/grafana/grafana/pkg/services/secrets/manager"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||
testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
@ -59,6 +63,7 @@ func TestService_AddDataSource(t *testing.T) {
|
||||
cfg := &setting.Cfg{}
|
||||
|
||||
t.Run("should return validation error if command validation failed", func(t *testing.T) {
|
||||
dsplugin := &testdatasource.Service{}
|
||||
sqlStore := db.InitTestDB(t)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
@ -67,10 +72,16 @@ func TestService_AddDataSource(t *testing.T) {
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test",
|
||||
Type: plugins.TypeDataSource,
|
||||
Name: "test",
|
||||
APIVersion: "v0alpha1",
|
||||
APIVersion: "v0alpha1", // When a value exists in plugin.json, the callback will be executed
|
||||
},
|
||||
}},
|
||||
}, &pluginfakes.FakePluginClient{
|
||||
ValidateAdmissionFunc: dsplugin.ValidateAdmission,
|
||||
MutateAdmissionFunc: dsplugin.MutateAdmission,
|
||||
ConvertObjectFunc: dsplugin.ConvertObject,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -92,12 +103,17 @@ func TestService_AddDataSource(t *testing.T) {
|
||||
|
||||
cmd = &datasources.AddDataSourceCommand{
|
||||
OrgID: 1,
|
||||
Type: "test", // required to validate apiserver
|
||||
Name: "test",
|
||||
APIVersion: "v0alpha2",
|
||||
APIVersion: "v123", // invalid apiVersion
|
||||
}
|
||||
|
||||
_, err = dsService.AddDataSource(context.Background(), cmd)
|
||||
require.EqualError(t, err, "[datasource.apiVersionInvalid] expected v0alpha1, got v0alpha2")
|
||||
|
||||
var gErr errutil.Error
|
||||
require.True(t, errors.As(err, &gErr))
|
||||
require.Equal(t, "expected apiVersion: v0alpha1, found: v123", gErr.PublicMessage)
|
||||
require.Equal(t, 400, gErr.Public().StatusCode)
|
||||
})
|
||||
}
|
||||
|
||||
@ -189,7 +205,7 @@ func TestService_UpdateDataSource(t *testing.T) {
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
mockPermission := acmock.NewMockedPermissionsService()
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd := &datasources.UpdateDataSourceCommand{
|
||||
@ -208,11 +224,31 @@ func TestService_UpdateDataSource(t *testing.T) {
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
mockPermission := acmock.NewMockedPermissionsService()
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{})
|
||||
mockPermission.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService,
|
||||
&pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
// First add the datasource
|
||||
ds, err := dsService.AddDataSource(context.Background(), &datasources.AddDataSourceCommand{
|
||||
OrgID: 1,
|
||||
Name: "test",
|
||||
Type: "test",
|
||||
UserID: 0,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
cmd := &datasources.UpdateDataSourceCommand{
|
||||
ID: 1,
|
||||
ID: ds.ID,
|
||||
UID: ds.UID,
|
||||
OrgID: 1,
|
||||
Name: string(make([]byte, 256)),
|
||||
}
|
||||
@ -221,7 +257,8 @@ func TestService_UpdateDataSource(t *testing.T) {
|
||||
require.EqualError(t, err, "[datasource.nameInvalid] max length is 190")
|
||||
|
||||
cmd = &datasources.UpdateDataSourceCommand{
|
||||
ID: 1,
|
||||
ID: ds.ID,
|
||||
UID: ds.UID,
|
||||
OrgID: 1,
|
||||
URL: string(make([]byte, 256)),
|
||||
}
|
||||
@ -236,7 +273,7 @@ func TestService_UpdateDataSource(t *testing.T) {
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
mockPermission := acmock.NewMockedPermissionsService()
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockPermission.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||
@ -263,7 +300,7 @@ func TestService_UpdateDataSource(t *testing.T) {
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
mockPermission := acmock.NewMockedPermissionsService()
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockPermission.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||
@ -296,7 +333,7 @@ func TestService_UpdateDataSource(t *testing.T) {
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
mockPermission := acmock.NewMockedPermissionsService()
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockPermission.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||
@ -340,7 +377,8 @@ func TestService_UpdateDataSource(t *testing.T) {
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
mockPermission := acmock.NewMockedPermissionsService()
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{}, mockPermission, quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), actest.FakeAccessControl{},
|
||||
mockPermission, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
mockPermission.On("SetPermissions", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return([]accesscontrol.ResourcePermission{}, nil)
|
||||
@ -602,7 +640,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
rt1, err := dsService.GetHTTPTransport(context.Background(), &ds, provider)
|
||||
@ -639,7 +677,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
@ -690,7 +728,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
@ -738,7 +776,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
@ -794,7 +832,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
@ -829,7 +867,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
@ -898,7 +936,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
@ -977,7 +1015,7 @@ func TestService_GetHttpTransport(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
ds := datasources.DataSource{
|
||||
@ -998,7 +1036,7 @@ func TestService_getProxySettings(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, &setting.Cfg{}, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("Should default to disabled", func(t *testing.T) {
|
||||
@ -1094,7 +1132,7 @@ func TestService_getTimeout(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tc := range testCases {
|
||||
@ -1117,7 +1155,7 @@ func TestService_GetDecryptedValues(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
jsonData := map[string]string{
|
||||
@ -1145,7 +1183,7 @@ func TestService_GetDecryptedValues(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
jsonData := map[string]string{
|
||||
@ -1169,7 +1207,7 @@ func TestDataSource_CustomHeaders(t *testing.T) {
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := ProvideService(sqlStore, secretsService, secretsStore, nil, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
dsService.cfg = setting.NewCfg()
|
||||
|
@ -43,3 +43,18 @@ func (m *baseMiddleware) PublishStream(ctx context.Context, req *backend.Publish
|
||||
func (m *baseMiddleware) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
return m.next.RunStream(ctx, req, sender)
|
||||
}
|
||||
|
||||
// ValidateAdmission implements backend.AdmissionHandler.
|
||||
func (m *baseMiddleware) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
return m.next.ValidateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
// MutateAdmission implements backend.AdmissionHandler.
|
||||
func (m *baseMiddleware) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
return m.next.MutateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
// ConvertObject implements backend.AdmissionHandler.
|
||||
func (m *baseMiddleware) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
return m.next.ConvertObject(ctx, req)
|
||||
}
|
||||
|
@ -70,3 +70,21 @@ func (m *ContextualLoggerMiddleware) RunStream(ctx context.Context, req *backend
|
||||
ctx = instrumentContext(ctx, endpointRunStream, req.PluginContext)
|
||||
return m.next.RunStream(ctx, req, sender)
|
||||
}
|
||||
|
||||
// ValidateAdmission implements backend.AdmissionHandler.
|
||||
func (m *ContextualLoggerMiddleware) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
ctx = instrumentContext(ctx, endpointValidateAdmission, req.PluginContext)
|
||||
return m.next.ValidateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
// MutateAdmission implements backend.AdmissionHandler.
|
||||
func (m *ContextualLoggerMiddleware) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
ctx = instrumentContext(ctx, endpointMutateAdmission, req.PluginContext)
|
||||
return m.next.MutateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
// ConvertObject implements backend.AdmissionHandler.
|
||||
func (m *ContextualLoggerMiddleware) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
ctx = instrumentContext(ctx, endpointConvertObject, req.PluginContext)
|
||||
return m.next.ConvertObject(ctx, req)
|
||||
}
|
||||
|
@ -185,3 +185,33 @@ func (m *MetricsMiddleware) CollectMetrics(ctx context.Context, req *backend.Col
|
||||
})
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (m *MetricsMiddleware) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
var result *backend.ValidationResponse
|
||||
err := m.instrumentPluginRequest(ctx, req.PluginContext, endpointMutateAdmission, func(ctx context.Context) (status requestStatus, innerErr error) {
|
||||
result, innerErr = m.next.ValidateAdmission(ctx, req)
|
||||
return requestStatusFromError(innerErr), innerErr
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (m *MetricsMiddleware) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
var result *backend.MutationResponse
|
||||
err := m.instrumentPluginRequest(ctx, req.PluginContext, endpointMutateAdmission, func(ctx context.Context) (status requestStatus, innerErr error) {
|
||||
result, innerErr = m.next.MutateAdmission(ctx, req)
|
||||
return requestStatusFromError(innerErr), innerErr
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (m *MetricsMiddleware) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
var result *backend.ConversionResponse
|
||||
err := m.instrumentPluginRequest(ctx, req.PluginContext, endpointMutateAdmission, func(ctx context.Context) (status requestStatus, innerErr error) {
|
||||
result, innerErr = m.next.ConvertObject(ctx, req)
|
||||
return requestStatusFromError(innerErr), innerErr
|
||||
})
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
@ -67,3 +67,21 @@ func (m *PluginRequestMetaMiddleware) RunStream(ctx context.Context, req *backen
|
||||
ctx = m.withDefaultPluginRequestMeta(ctx)
|
||||
return m.next.RunStream(ctx, req, sender)
|
||||
}
|
||||
|
||||
// ValidateAdmission implements backend.AdmissionHandler.
|
||||
func (m *PluginRequestMetaMiddleware) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
ctx = m.withDefaultPluginRequestMeta(ctx)
|
||||
return m.next.ValidateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
// MutateAdmission implements backend.AdmissionHandler.
|
||||
func (m *PluginRequestMetaMiddleware) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
ctx = m.withDefaultPluginRequestMeta(ctx)
|
||||
return m.next.MutateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
// ConvertObject implements backend.AdmissionHandler.
|
||||
func (m *PluginRequestMetaMiddleware) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
ctx = m.withDefaultPluginRequestMeta(ctx)
|
||||
return m.next.ConvertObject(ctx, req)
|
||||
}
|
||||
|
@ -135,3 +135,30 @@ func (m *TracingMiddleware) RunStream(ctx context.Context, req *backend.RunStrea
|
||||
err = m.next.RunStream(ctx, req, sender)
|
||||
return err
|
||||
}
|
||||
|
||||
// ValidateAdmission implements backend.AdmissionHandler.
|
||||
func (m *TracingMiddleware) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
var err error
|
||||
ctx, end := m.traceWrap(ctx, req.PluginContext, endpointValidateAdmission)
|
||||
defer func() { end(err) }()
|
||||
resp, err := m.next.ValidateAdmission(ctx, req)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// MutateAdmission implements backend.AdmissionHandler.
|
||||
func (m *TracingMiddleware) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
var err error
|
||||
ctx, end := m.traceWrap(ctx, req.PluginContext, endpointMutateAdmission)
|
||||
defer func() { end(err) }()
|
||||
resp, err := m.next.MutateAdmission(ctx, req)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// ConvertObject implements backend.AdmissionHandler.
|
||||
func (m *TracingMiddleware) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
var err error
|
||||
ctx, end := m.traceWrap(ctx, req.PluginContext, endpointConvertObject)
|
||||
defer func() { end(err) }()
|
||||
resp, err := m.next.ConvertObject(ctx, req)
|
||||
return resp, err
|
||||
}
|
||||
|
@ -387,6 +387,21 @@ func (m *alwaysErrorFuncMiddleware) RunStream(ctx context.Context, req *backend.
|
||||
return m.f()
|
||||
}
|
||||
|
||||
// ValidateAdmission implements backend.AdmissionHandler.
|
||||
func (m *alwaysErrorFuncMiddleware) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
return nil, m.f()
|
||||
}
|
||||
|
||||
// MutateAdmission implements backend.AdmissionHandler.
|
||||
func (m *alwaysErrorFuncMiddleware) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
return nil, m.f()
|
||||
}
|
||||
|
||||
// ConvertObject implements backend.AdmissionHandler.
|
||||
func (m *alwaysErrorFuncMiddleware) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
return nil, m.f()
|
||||
}
|
||||
|
||||
// newAlwaysErrorMiddleware returns a new middleware that always returns the specified error.
|
||||
func newAlwaysErrorMiddleware(err error) plugins.ClientMiddleware {
|
||||
return plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client {
|
||||
@ -401,7 +416,6 @@ func newAlwaysPanicMiddleware(message string) plugins.ClientMiddleware {
|
||||
return plugins.ClientMiddlewareFunc(func(next plugins.Client) plugins.Client {
|
||||
return &alwaysErrorFuncMiddleware{func() error {
|
||||
panic(message)
|
||||
return nil // nolint:govet
|
||||
}}
|
||||
})
|
||||
}
|
||||
|
@ -25,13 +25,16 @@ func (status requestStatus) String() string {
|
||||
}
|
||||
|
||||
const (
|
||||
endpointCallResource = "callResource"
|
||||
endpointCheckHealth = "checkHealth"
|
||||
endpointCollectMetrics = "collectMetrics"
|
||||
endpointQueryData = "queryData"
|
||||
endpointSubscribeStream = "subscribeStream"
|
||||
endpointPublishStream = "publishStream"
|
||||
endpointRunStream = "runStream"
|
||||
endpointCallResource = "callResource"
|
||||
endpointCheckHealth = "checkHealth"
|
||||
endpointCollectMetrics = "collectMetrics"
|
||||
endpointQueryData = "queryData"
|
||||
endpointSubscribeStream = "subscribeStream"
|
||||
endpointPublishStream = "publishStream"
|
||||
endpointRunStream = "runStream"
|
||||
endpointValidateAdmission = "validateAdmission"
|
||||
endpointMutateAdmission = "mutateAdmission"
|
||||
endpointConvertObject = "convertObject"
|
||||
)
|
||||
|
||||
type callResourceResponseSenderFunc func(res *backend.CallResourceResponse) error
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
|
||||
@ -484,7 +485,8 @@ func setupEnv(t *testing.T, sqlStore db.DB, cfg *setting.Cfg, b bus.Bus, quotaSe
|
||||
require.NoError(t, err)
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
|
||||
_, err = dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
_, err = dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), acmock.NewMockedPermissionsService(),
|
||||
quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
m := metrics.NewNGAlert(prometheus.NewRegistry())
|
||||
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/kvstore"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
dsservice "github.com/grafana/grafana/pkg/services/datasources/service"
|
||||
@ -36,7 +37,8 @@ func SetupTestDataSourceSecretMigrationService(t *testing.T, sqlStore db.DB, kvS
|
||||
}
|
||||
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsService, err := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(), acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := dsservice.ProvideService(sqlStore, secretsService, secretsStore, cfg, features, acmock.New(),
|
||||
acmock.NewMockedPermissionsService(), quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
migService := ProvideDataSourceMigrationService(dsService, kvStore, features)
|
||||
return migService
|
||||
|
@ -846,6 +846,7 @@ type testPlugin struct {
|
||||
backend.CallResourceHandler
|
||||
backend.QueryDataHandler
|
||||
backend.StreamHandler
|
||||
backend.AdmissionHandler
|
||||
}
|
||||
|
||||
func (tp *testPlugin) PluginID() string {
|
||||
@ -933,6 +934,33 @@ func (tp *testPlugin) RunStream(ctx context.Context, req *backend.RunStreamReque
|
||||
return plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
// ValidateAdmission implements backend.AdmissionHandler.
|
||||
func (tp *testPlugin) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
if tp.AdmissionHandler != nil {
|
||||
return tp.AdmissionHandler.ValidateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
// MutateAdmission implements backend.AdmissionHandler.
|
||||
func (tp *testPlugin) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
if tp.AdmissionHandler != nil {
|
||||
return tp.AdmissionHandler.MutateAdmission(ctx, req)
|
||||
}
|
||||
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
// ConvertObject implements backend.AdmissionHandler.
|
||||
func (tp *testPlugin) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
if tp.AdmissionHandler != nil {
|
||||
return tp.AdmissionHandler.ConvertObject(ctx, req)
|
||||
}
|
||||
|
||||
return nil, plugins.ErrMethodNotImplemented
|
||||
}
|
||||
|
||||
func metricRequestWithQueries(t *testing.T, rawQueries ...string) dtos.MetricRequest {
|
||||
t.Helper()
|
||||
queries := make([]*simplejson.Json, 0)
|
||||
|
87
pkg/tsdb/grafana-testdata-datasource/admission_handler.go
Normal file
87
pkg/tsdb/grafana-testdata-datasource/admission_handler.go
Normal file
@ -0,0 +1,87 @@
|
||||
package testdatasource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// ValidateAdmission implements backend.AdmissionHandler.
|
||||
func (s *Service) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
|
||||
rsp, err := s.MutateAdmission(ctx, req)
|
||||
if rsp != nil {
|
||||
return &backend.ValidationResponse{
|
||||
Allowed: rsp.Allowed,
|
||||
Result: rsp.Result,
|
||||
Warnings: rsp.Warnings,
|
||||
}, err
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// MutateAdmission implements backend.AdmissionHandler.
|
||||
func (s *Service) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
|
||||
expected := (&backend.DataSourceInstanceSettings{}).GVK()
|
||||
if req.Kind.Kind != expected.Kind && req.Kind.Group != expected.Group {
|
||||
return getBadRequest("expected DataSourceInstanceSettings protobuf payload"), nil
|
||||
}
|
||||
|
||||
// Convert the payload from protobuf to an SDK struct
|
||||
settings, err := backend.DataSourceInstanceSettingsFromProto(req.ObjectBytes, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if settings == nil {
|
||||
return getBadRequest("missing datasource settings"), nil
|
||||
}
|
||||
|
||||
switch settings.APIVersion {
|
||||
case "", "v0alpha1":
|
||||
// OK!
|
||||
default:
|
||||
return getBadRequest(fmt.Sprintf("expected apiVersion: v0alpha1, found: %s", settings.APIVersion)), nil
|
||||
}
|
||||
if settings.JSONData != nil {
|
||||
anything := map[string]any{}
|
||||
err := json.Unmarshal(settings.JSONData, &anything)
|
||||
if err != nil || len(anything) > 0 {
|
||||
return getBadRequest("Expected empty jsonData settings"), nil
|
||||
}
|
||||
}
|
||||
if len(settings.DecryptedSecureJSONData) > 0 {
|
||||
return getBadRequest("found unsupported secure json fields"), nil
|
||||
}
|
||||
if settings.URL != "" {
|
||||
return getBadRequest("unsupported URL value"), nil
|
||||
}
|
||||
if settings.User != "" {
|
||||
return getBadRequest("unsupported User value"), nil
|
||||
}
|
||||
|
||||
pb, err := backend.DataSourceInstanceSettingsToProtoBytes(settings)
|
||||
return &backend.MutationResponse{
|
||||
Allowed: true,
|
||||
ObjectBytes: pb,
|
||||
}, err
|
||||
}
|
||||
|
||||
// ConvertObject implements backend.AdmissionHandler.
|
||||
func (s *Service) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
|
||||
return nil, fmt.Errorf("not implemented")
|
||||
}
|
||||
|
||||
func getBadRequest(msg string) *backend.MutationResponse {
|
||||
return &backend.MutationResponse{
|
||||
Allowed: false,
|
||||
Result: &backend.StatusResult{
|
||||
Status: "Failure",
|
||||
Message: msg,
|
||||
Reason: string(metav1.StatusReasonBadRequest),
|
||||
Code: http.StatusBadRequest,
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package testdatasource
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestSettingsHandler(t *testing.T) {
|
||||
svc := &Service{}
|
||||
require.NotNil(t, svc)
|
||||
|
||||
// Check missing datasource
|
||||
s, err := svc.MutateAdmission(context.Background(), &backend.AdmissionRequest{
|
||||
PluginContext: backend.PluginContext{},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.False(t, s.Allowed)
|
||||
require.Equal(t, int32(400), s.Result.Code)
|
||||
|
||||
// Empty is OK
|
||||
s, _ = svc.MutateAdmission(context.Background(),
|
||||
asAdmissionRequest(&backend.DataSourceInstanceSettings{
|
||||
APIVersion: "v0alpha1",
|
||||
}))
|
||||
require.True(t, s.Allowed)
|
||||
|
||||
// Any values should be an error
|
||||
s, err = svc.MutateAdmission(context.Background(),
|
||||
asAdmissionRequest(&backend.DataSourceInstanceSettings{
|
||||
JSONData: json.RawMessage(`{"hello": "world"}`), // Settings must be empty
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
require.False(t, s.Allowed)
|
||||
require.Equal(t, int32(400), s.Result.Code)
|
||||
|
||||
// Any values should be an error
|
||||
s, err = svc.MutateAdmission(context.Background(),
|
||||
asAdmissionRequest(&backend.DataSourceInstanceSettings{
|
||||
DecryptedSecureJSONData: map[string]string{
|
||||
"A": "Value",
|
||||
},
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
require.False(t, s.Allowed)
|
||||
require.Equal(t, int32(400), s.Result.Code)
|
||||
|
||||
// Invalid API Version
|
||||
s, err = svc.MutateAdmission(context.Background(),
|
||||
asAdmissionRequest(&backend.DataSourceInstanceSettings{
|
||||
APIVersion: "v1234",
|
||||
}))
|
||||
require.NoError(t, err)
|
||||
require.False(t, s.Allowed)
|
||||
require.Equal(t, int32(400), s.Result.Code)
|
||||
}
|
||||
|
||||
func asAdmissionRequest(settings *backend.DataSourceInstanceSettings) *backend.AdmissionRequest {
|
||||
req := &backend.AdmissionRequest{}
|
||||
if settings != nil {
|
||||
req.Kind = settings.GVK()
|
||||
req.ObjectBytes, _ = backend.DataSourceInstanceSettingsToProtoBytes(settings)
|
||||
}
|
||||
return req
|
||||
}
|
@ -53,13 +53,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^grafana-testdata-datasource$|^testdata$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -63,13 +63,17 @@
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"apiVersion": {
|
||||
"description": "The apiserver version",
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"description": "The datasource plugin type",
|
||||
"type": "string",
|
||||
"pattern": "^grafana-testdata-datasource$|^testdata$"
|
||||
},
|
||||
"uid": {
|
||||
"description": "Datasource UID",
|
||||
"description": "Datasource UID (NOTE: name in k8s)",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
|
@ -47,6 +47,13 @@ func ProvideService() *Service {
|
||||
return s
|
||||
}
|
||||
|
||||
var (
|
||||
_ backend.QueryDataHandler = (*Service)(nil)
|
||||
_ backend.CallResourceHandler = (*Service)(nil)
|
||||
_ backend.AdmissionHandler = (*Service)(nil)
|
||||
_ backend.CollectMetricsHandler = (*Service)(nil)
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
logger log.Logger
|
||||
scenarios map[kinds.TestDataQueryType]*Scenario
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/datasources/guardian"
|
||||
@ -48,7 +49,8 @@ func TestHandleRequest(t *testing.T) {
|
||||
datasourcePermissions := acmock.NewMockedPermissionsService()
|
||||
quotaService := quotatest.New(false, nil)
|
||||
dsCache := datasourceservice.ProvideCacheService(localcache.ProvideService(), sqlStore, guardian.ProvideGuardian())
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions, quotaService, &pluginstore.FakePluginStore{})
|
||||
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(),
|
||||
acmock.New(), datasourcePermissions, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
|
||||
require.NoError(t, err)
|
||||
|
||||
pCtxProvider := plugincontext.ProvideService(cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{
|
||||
|
Loading…
Reference in New Issue
Block a user