Plugins: Make manager more easily composable (#44467)

* make more easily composable

* fix build
This commit is contained in:
Will Browne 2022-01-27 18:06:38 +01:00 committed by GitHub
parent d3b8fc53aa
commit b5dd4842d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 84 additions and 86 deletions

View File

@ -524,19 +524,19 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo
ds, err := hs.DataSourceCache.GetDatasource(c.Req.Context(), datasourceID, c.SignedInUser, c.SkipCache) ds, err := hs.DataSourceCache.GetDatasource(c.Req.Context(), datasourceID, c.SignedInUser, c.SkipCache)
if err != nil { if err != nil {
if errors.Is(err, models.ErrDataSourceAccessDenied) { if errors.Is(err, models.ErrDataSourceAccessDenied) {
return response.Error(403, "Access denied to datasource", err) return response.Error(http.StatusForbidden, "Access denied to datasource", err)
} }
return response.Error(500, "Unable to load datasource metadata", err) return response.Error(http.StatusInternalServerError, "Unable to load datasource metadata", err)
} }
plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), ds.Type) plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), ds.Type)
if !exists { if !exists {
return response.Error(500, "Unable to find datasource plugin", err) return response.Error(http.StatusInternalServerError, "Unable to find datasource plugin", err)
} }
dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds, hs.decryptSecureJsonDataFn()) dsInstanceSettings, err := adapters.ModelToInstanceSettings(ds, hs.decryptSecureJsonDataFn())
if err != nil { if err != nil {
return response.Error(500, "Unable to get datasource model", err) return response.Error(http.StatusInternalServerError, "Unable to get datasource model", err)
} }
req := &backend.CheckHealthRequest{ req := &backend.CheckHealthRequest{
PluginContext: backend.PluginContext{ PluginContext: backend.PluginContext{
@ -547,6 +547,16 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo
}, },
} }
var dsURL string
if req.PluginContext.DataSourceInstanceSettings != nil {
dsURL = req.PluginContext.DataSourceInstanceSettings.URL
}
err = hs.PluginRequestValidator.Validate(dsURL, c.Req)
if err != nil {
return response.Error(http.StatusForbidden, "Access denied", err)
}
resp, err := hs.pluginClient.CheckHealth(c.Req.Context(), req) resp, err := hs.pluginClient.CheckHealth(c.Req.Context(), req)
if err != nil { if err != nil {
return translatePluginRequestErrorToAPIError(err) return translatePluginRequestErrorToAPIError(err)
@ -562,17 +572,17 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo
var jsonDetails map[string]interface{} var jsonDetails map[string]interface{}
err = json.Unmarshal(resp.JSONDetails, &jsonDetails) err = json.Unmarshal(resp.JSONDetails, &jsonDetails)
if err != nil { if err != nil {
return response.Error(500, "Failed to unmarshal detailed response from backend plugin", err) return response.Error(http.StatusInternalServerError, "Failed to unmarshal detailed response from backend plugin", err)
} }
payload["details"] = jsonDetails payload["details"] = jsonDetails
} }
if resp.Status != backend.HealthStatusOk { if resp.Status != backend.HealthStatusOk {
return response.JSON(400, payload) return response.JSON(http.StatusBadRequest, payload)
} }
return response.JSON(200, payload) return response.JSON(http.StatusOK, payload)
} }
func (hs *HTTPServer) decryptSecureJsonDataFn() func(map[string][]byte) map[string]string { func (hs *HTTPServer) decryptSecureJsonDataFn() func(map[string][]byte) map[string]string {

View File

@ -1,8 +1,10 @@
package coreplugin package coreplugin
import ( import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "context"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/tsdb/azuremonitor" "github.com/grafana/grafana/pkg/tsdb/azuremonitor"
"github.com/grafana/grafana/pkg/tsdb/cloudmonitoring" "github.com/grafana/grafana/pkg/tsdb/cloudmonitoring"
@ -76,6 +78,16 @@ func (cr *Registry) Get(pluginID string) backendplugin.PluginFactoryFunc {
return cr.store[pluginID] return cr.store[pluginID]
} }
func (cr *Registry) BackendFactoryProvider() func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
return func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
if !p.IsCorePlugin() {
return nil
}
return cr.Get(p.ID)
}
}
func asBackendPlugin(svc interface{}) backendplugin.PluginFactoryFunc { func asBackendPlugin(svc interface{}) backendplugin.PluginFactoryFunc {
opts := backend.ServeOpts{} opts := backend.ServeOpts{}
if queryHandler, ok := svc.(backend.QueryDataHandler); ok { if queryHandler, ok := svc.(backend.QueryDataHandler); ok {

View File

@ -12,18 +12,28 @@ import (
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2" "github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
) )
// PluginBackendProvider is a function type for initializing a Plugin backend.
type PluginBackendProvider func(_ context.Context, _ *plugins.Plugin) backendplugin.PluginFactoryFunc
type Service struct { type Service struct {
coreRegistry *coreplugin.Registry providerChain []PluginBackendProvider
} }
func ProvideService(coreRegistry *coreplugin.Registry) *Service { func New(providers ...PluginBackendProvider) *Service {
if len(providers) == 0 {
return New(RendererProvider, DefaultProvider)
}
return &Service{ return &Service{
coreRegistry: coreRegistry, providerChain: providers,
} }
} }
func ProvideService(coreRegistry *coreplugin.Registry) *Service {
return New(coreRegistry.BackendFactoryProvider(), RendererProvider, DefaultProvider)
}
func (s *Service) BackendFactory(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { func (s *Service) BackendFactory(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
for _, provider := range []PluginBackendProvider{CorePluginProvider(ctx, s.coreRegistry), RendererProvider, DefaultProvider} { for _, provider := range s.providerChain {
if factory := provider(ctx, p); factory != nil { if factory := provider(ctx, p); factory != nil {
return factory return factory
} }
@ -31,9 +41,6 @@ func (s *Service) BackendFactory(ctx context.Context, p *plugins.Plugin) backend
return nil return nil
} }
// PluginBackendProvider is a function type for initializing a Plugin backend.
type PluginBackendProvider func(_ context.Context, _ *plugins.Plugin) backendplugin.PluginFactoryFunc
var RendererProvider PluginBackendProvider = func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc { var RendererProvider PluginBackendProvider = func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
if !p.IsRenderer() { if !p.IsRenderer() {
return nil return nil
@ -52,13 +59,3 @@ var DefaultProvider PluginBackendProvider = func(_ context.Context, p *plugins.P
cmd := plugins.ComposePluginStartCommand(p.Executable) cmd := plugins.ComposePluginStartCommand(p.Executable)
return grpcplugin.NewBackendPlugin(p.ID, filepath.Join(p.PluginDir, cmd)) return grpcplugin.NewBackendPlugin(p.ID, filepath.Join(p.PluginDir, cmd))
} }
var CorePluginProvider = func(ctx context.Context, registry *coreplugin.Registry) PluginBackendProvider {
return func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
if !p.IsCorePlugin() {
return nil
}
return registry.Get(p.ID)
}
}

View File

@ -93,8 +93,8 @@ func pluginScenario(t *testing.T, desc string, fn func(*testing.T, *PluginManage
} }
pmCfg := plugins.FromGrafanaCfg(cfg) pmCfg := plugins.FromGrafanaCfg(cfg)
pm, err := ProvideService(cfg, nil, loader.New(pmCfg, nil, pm, err := ProvideService(cfg, loader.New(pmCfg, nil, signature.NewUnsignedAuthorizer(pmCfg),
&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, &provider.Service{}), &sqlstore.SQLStore{}) &provider.Service{}), &sqlstore.SQLStore{})
require.NoError(t, err) require.NoError(t, err)
t.Run(desc, func(t *testing.T) { t.Run(desc, func(t *testing.T) {

View File

@ -25,8 +25,8 @@ func TestGetPluginDashboards(t *testing.T) {
}, },
} }
pmCfg := plugins.FromGrafanaCfg(cfg) pmCfg := plugins.FromGrafanaCfg(cfg)
pm, err := ProvideService(cfg, nil, loader.New(pmCfg, nil, pm, err := ProvideService(cfg, loader.New(pmCfg, nil,
&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, &provider.Service{}), &sqlstore.SQLStore{}) signature.NewUnsignedAuthorizer(pmCfg), &provider.Service{}), &sqlstore.SQLStore{})
require.NoError(t, err) require.NoError(t, err)
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error {

View File

@ -1081,7 +1081,7 @@ func newLoader(cfg *plugins.Cfg) *Loader {
cfg: cfg, cfg: cfg,
pluginFinder: finder.New(), pluginFinder: finder.New(),
pluginInitializer: initializer.New(cfg, provider.ProvideService(coreplugin.NewRegistry(make(map[string]backendplugin.PluginFactoryFunc))), &fakeLicensingService{}), pluginInitializer: initializer.New(cfg, provider.ProvideService(coreplugin.NewRegistry(make(map[string]backendplugin.PluginFactoryFunc))), &fakeLicensingService{}),
signatureValidator: signature.NewValidator(&signature.UnsignedPluginAuthorizer{Cfg: cfg}), signatureValidator: signature.NewValidator(signature.NewUnsignedAuthorizer(cfg)),
errs: make(map[string]*plugins.SignatureError), errs: make(map[string]*plugins.SignatureError),
log: &fakeLogger{}, log: &fakeLogger{},
} }

View File

@ -4,7 +4,6 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"net/http"
"path/filepath" "path/filepath"
"sync" "sync"
"time" "time"
@ -12,7 +11,6 @@ import (
"github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin" "github.com/grafana/grafana/pkg/plugins/backendplugin"
"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation" "github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation"
@ -34,7 +32,6 @@ var _ plugins.RendererManager = (*PluginManager)(nil)
type PluginManager struct { type PluginManager struct {
cfg *plugins.Cfg cfg *plugins.Cfg
requestValidator models.PluginRequestValidator
sqlStore *sqlstore.SQLStore sqlStore *sqlstore.SQLStore
store map[string]*plugins.Plugin store map[string]*plugins.Plugin
pluginInstaller plugins.Installer pluginInstaller plugins.Installer
@ -44,9 +41,9 @@ type PluginManager struct {
log log.Logger log log.Logger
} }
func ProvideService(grafanaCfg *setting.Cfg, requestValidator models.PluginRequestValidator, pluginLoader plugins.Loader, func ProvideService(grafanaCfg *setting.Cfg, pluginLoader plugins.Loader,
sqlStore *sqlstore.SQLStore) (*PluginManager, error) { sqlStore *sqlstore.SQLStore) (*PluginManager, error) {
pm := New(plugins.FromGrafanaCfg(grafanaCfg), requestValidator, map[plugins.Class][]string{ pm := New(plugins.FromGrafanaCfg(grafanaCfg), map[plugins.Class][]string{
plugins.Core: corePluginPaths(grafanaCfg), plugins.Core: corePluginPaths(grafanaCfg),
plugins.Bundled: {grafanaCfg.BundledPluginsPath}, plugins.Bundled: {grafanaCfg.BundledPluginsPath},
plugins.External: append([]string{grafanaCfg.PluginsPath}, pluginSettingPaths(grafanaCfg)...), plugins.External: append([]string{grafanaCfg.PluginsPath}, pluginSettingPaths(grafanaCfg)...),
@ -57,11 +54,10 @@ func ProvideService(grafanaCfg *setting.Cfg, requestValidator models.PluginReque
return pm, nil return pm, nil
} }
func New(cfg *plugins.Cfg, requestValidator models.PluginRequestValidator, pluginPaths map[plugins.Class][]string, func New(cfg *plugins.Cfg, pluginPaths map[plugins.Class][]string, pluginLoader plugins.Loader,
pluginLoader plugins.Loader, sqlStore *sqlstore.SQLStore) *PluginManager { sqlStore *sqlstore.SQLStore) *PluginManager {
return &PluginManager{ return &PluginManager{
cfg: cfg, cfg: cfg,
requestValidator: requestValidator,
pluginLoader: pluginLoader, pluginLoader: pluginLoader,
pluginPaths: pluginPaths, pluginPaths: pluginPaths,
store: make(map[string]*plugins.Plugin), store: make(map[string]*plugins.Plugin),
@ -253,26 +249,13 @@ func (m *PluginManager) CollectMetrics(ctx context.Context, pluginID string) (*b
} }
func (m *PluginManager) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) { func (m *PluginManager) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
var dsURL string
if req.PluginContext.DataSourceInstanceSettings != nil {
dsURL = req.PluginContext.DataSourceInstanceSettings.URL
}
err := m.requestValidator.Validate(dsURL, nil)
if err != nil {
return &backend.CheckHealthResult{
Status: http.StatusForbidden,
Message: "Access denied",
}, nil
}
p, exists := m.plugin(req.PluginContext.PluginID) p, exists := m.plugin(req.PluginContext.PluginID)
if !exists { if !exists {
return nil, backendplugin.ErrPluginNotRegistered return nil, backendplugin.ErrPluginNotRegistered
} }
var resp *backend.CheckHealthResult var resp *backend.CheckHealthResult
err = instrumentation.InstrumentCheckHealthRequest(p.PluginID(), func() (innerErr error) { err := instrumentation.InstrumentCheckHealthRequest(p.PluginID(), func() (innerErr error) {
resp, innerErr = p.CheckHealth(ctx, &backend.CheckHealthRequest{PluginContext: req.PluginContext}) resp, innerErr = p.CheckHealth(ctx, &backend.CheckHealthRequest{PluginContext: req.PluginContext})
return return
}) })

View File

@ -91,8 +91,8 @@ func TestPluginManager_int_init(t *testing.T) {
coreRegistry := coreplugin.ProvideCoreRegistry(am, cw, cm, es, grap, idb, lk, otsdb, pr, tmpo, td, pg, my, ms, graf) coreRegistry := coreplugin.ProvideCoreRegistry(am, cw, cm, es, grap, idb, lk, otsdb, pr, tmpo, td, pg, my, ms, graf)
pmCfg := plugins.FromGrafanaCfg(cfg) pmCfg := plugins.FromGrafanaCfg(cfg)
pm, err := ProvideService(cfg, nil, loader.New(pmCfg, license, pm, err := ProvideService(cfg, loader.New(pmCfg, license, signature.NewUnsignedAuthorizer(pmCfg),
&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, provider.ProvideService(coreRegistry)), nil) provider.ProvideService(coreRegistry)), nil)
require.NoError(t, err) require.NoError(t, err)
verifyCorePluginCatalogue(t, pm) verifyCorePluginCatalogue(t, pm)

View File

@ -467,8 +467,7 @@ func TestPluginManager_lifecycle_unmanaged(t *testing.T) {
func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager { func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager {
t.Helper() t.Helper()
requestValidator := &testPluginRequestValidator{} pm := New(&plugins.Cfg{}, nil, &fakeLoader{}, &sqlstore.SQLStore{})
pm := New(&plugins.Cfg{}, requestValidator, nil, &fakeLoader{}, &sqlstore.SQLStore{})
for _, cb := range cbs { for _, cb := range cbs {
cb(pm) cb(pm)
@ -521,9 +520,8 @@ func newScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerS
cfg.Azure.Cloud = "AzureCloud" cfg.Azure.Cloud = "AzureCloud"
cfg.Azure.ManagedIdentityClientId = "client-id" cfg.Azure.ManagedIdentityClientId = "client-id"
requestValidator := &testPluginRequestValidator{}
loader := &fakeLoader{} loader := &fakeLoader{}
manager := New(cfg, requestValidator, nil, loader, nil) manager := New(cfg, nil, loader, nil)
manager.pluginLoader = loader manager.pluginLoader = loader
ctx := &managerScenarioCtx{ ctx := &managerScenarioCtx{
manager: manager, manager: manager,
@ -698,12 +696,6 @@ func (pc *fakePluginClient) RunStream(_ context.Context, _ *backend.RunStreamReq
return backendplugin.ErrMethodNotImplemented return backendplugin.ErrMethodNotImplemented
} }
type testPluginRequestValidator struct{}
func (t *testPluginRequestValidator) Validate(string, *http.Request) error {
return nil
}
type fakeLogger struct { type fakeLogger struct {
log.Logger log.Logger
} }

View File

@ -5,14 +5,18 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
) )
func ProvideService(cfg *setting.Cfg) (*UnsignedPluginAuthorizer, error) { func NewUnsignedAuthorizer(cfg *plugins.Cfg) *UnsignedPluginAuthorizer {
return &UnsignedPluginAuthorizer{ return &UnsignedPluginAuthorizer{
Cfg: plugins.FromGrafanaCfg(cfg), cfg: cfg,
}, nil }
}
func ProvideOSSAuthorizer(cfg *setting.Cfg) *UnsignedPluginAuthorizer {
return NewUnsignedAuthorizer(plugins.FromGrafanaCfg(cfg))
} }
type UnsignedPluginAuthorizer struct { type UnsignedPluginAuthorizer struct {
Cfg *plugins.Cfg cfg *plugins.Cfg
} }
func (u *UnsignedPluginAuthorizer) CanLoadPlugin(p *plugins.Plugin) bool { func (u *UnsignedPluginAuthorizer) CanLoadPlugin(p *plugins.Plugin) bool {
@ -20,11 +24,11 @@ func (u *UnsignedPluginAuthorizer) CanLoadPlugin(p *plugins.Plugin) bool {
return true return true
} }
if u.Cfg.DevMode { if u.cfg.DevMode {
return true return true
} }
for _, pID := range u.Cfg.PluginsAllowUnsigned { for _, pID := range u.cfg.PluginsAllowUnsigned {
if pID == p.ID { if pID == p.ID {
return true return true
} }

View File

@ -61,7 +61,7 @@ var wireExtsBasicSet = wire.NewSet(
wire.Bind(new(models.SearchUserFilter), new(*filters.OSSSearchUserFilter)), wire.Bind(new(models.SearchUserFilter), new(*filters.OSSSearchUserFilter)),
searchusers.ProvideUsersService, searchusers.ProvideUsersService,
wire.Bind(new(searchusers.Service), new(*searchusers.OSSService)), wire.Bind(new(searchusers.Service), new(*searchusers.OSSService)),
signature.ProvideService, signature.ProvideOSSAuthorizer,
wire.Bind(new(plugins.PluginLoaderAuthorizer), new(*signature.UnsignedPluginAuthorizer)), wire.Bind(new(plugins.PluginLoaderAuthorizer), new(*signature.UnsignedPluginAuthorizer)),
provider.ProvideService, provider.ProvideService,
wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)), wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)),