mirror of https://github.com/grafana/grafana.git
				
				
				
			Plugins: Make manager more easily composable (#44467)
* make more easily composable * fix build
This commit is contained in:
		
							parent
							
								
									d3b8fc53aa
								
							
						
					
					
						commit
						b5dd4842d0
					
				|  | @ -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) | ||||
| 	if err != nil { | ||||
| 		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) | ||||
| 	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()) | ||||
| 	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{ | ||||
| 		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) | ||||
| 	if err != nil { | ||||
| 		return translatePluginRequestErrorToAPIError(err) | ||||
|  | @ -562,17 +572,17 @@ func (hs *HTTPServer) CheckDatasourceHealth(c *models.ReqContext) response.Respo | |||
| 		var jsonDetails map[string]interface{} | ||||
| 		err = json.Unmarshal(resp.JSONDetails, &jsonDetails) | ||||
| 		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 | ||||
| 	} | ||||
| 
 | ||||
| 	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 { | ||||
|  |  | |||
|  | @ -1,8 +1,10 @@ | |||
| package coreplugin | ||||
| 
 | ||||
| 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/tsdb/azuremonitor" | ||||
| 	"github.com/grafana/grafana/pkg/tsdb/cloudmonitoring" | ||||
|  | @ -76,6 +78,16 @@ func (cr *Registry) Get(pluginID string) backendplugin.PluginFactoryFunc { | |||
| 	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 { | ||||
| 	opts := backend.ServeOpts{} | ||||
| 	if queryHandler, ok := svc.(backend.QueryDataHandler); ok { | ||||
|  |  | |||
|  | @ -12,18 +12,28 @@ import ( | |||
| 	"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 { | ||||
| 	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{ | ||||
| 		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 { | ||||
| 	for _, provider := range []PluginBackendProvider{CorePluginProvider(ctx, s.coreRegistry), RendererProvider, DefaultProvider} { | ||||
| 	for _, provider := range s.providerChain { | ||||
| 		if factory := provider(ctx, p); factory != nil { | ||||
| 			return factory | ||||
| 		} | ||||
|  | @ -31,9 +41,6 @@ func (s *Service) BackendFactory(ctx context.Context, p *plugins.Plugin) backend | |||
| 	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 { | ||||
| 	if !p.IsRenderer() { | ||||
| 		return nil | ||||
|  | @ -52,13 +59,3 @@ var DefaultProvider PluginBackendProvider = func(_ context.Context, p *plugins.P | |||
| 	cmd := plugins.ComposePluginStartCommand(p.Executable) | ||||
| 	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) | ||||
| 	} | ||||
| } | ||||
|  |  | |||
|  | @ -93,8 +93,8 @@ func pluginScenario(t *testing.T, desc string, fn func(*testing.T, *PluginManage | |||
| 		} | ||||
| 
 | ||||
| 		pmCfg := plugins.FromGrafanaCfg(cfg) | ||||
| 		pm, err := ProvideService(cfg, nil, loader.New(pmCfg, nil, | ||||
| 			&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, &provider.Service{}), &sqlstore.SQLStore{}) | ||||
| 		pm, err := ProvideService(cfg, loader.New(pmCfg, nil, signature.NewUnsignedAuthorizer(pmCfg), | ||||
| 			&provider.Service{}), &sqlstore.SQLStore{}) | ||||
| 		require.NoError(t, err) | ||||
| 
 | ||||
| 		t.Run(desc, func(t *testing.T) { | ||||
|  |  | |||
|  | @ -25,8 +25,8 @@ func TestGetPluginDashboards(t *testing.T) { | |||
| 		}, | ||||
| 	} | ||||
| 	pmCfg := plugins.FromGrafanaCfg(cfg) | ||||
| 	pm, err := ProvideService(cfg, nil, loader.New(pmCfg, nil, | ||||
| 		&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, &provider.Service{}), &sqlstore.SQLStore{}) | ||||
| 	pm, err := ProvideService(cfg, loader.New(pmCfg, nil, | ||||
| 		signature.NewUnsignedAuthorizer(pmCfg), &provider.Service{}), &sqlstore.SQLStore{}) | ||||
| 	require.NoError(t, err) | ||||
| 
 | ||||
| 	bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error { | ||||
|  |  | |||
|  | @ -1081,7 +1081,7 @@ func newLoader(cfg *plugins.Cfg) *Loader { | |||
| 		cfg:                cfg, | ||||
| 		pluginFinder:       finder.New(), | ||||
| 		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), | ||||
| 		log:                &fakeLogger{}, | ||||
| 	} | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ import ( | |||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"path/filepath" | ||||
| 	"sync" | ||||
| 	"time" | ||||
|  | @ -12,7 +11,6 @@ import ( | |||
| 	"github.com/grafana/grafana-plugin-sdk-go/backend" | ||||
| 
 | ||||
| 	"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/backendplugin" | ||||
| 	"github.com/grafana/grafana/pkg/plugins/backendplugin/instrumentation" | ||||
|  | @ -33,20 +31,19 @@ var _ plugins.StaticRouteResolver = (*PluginManager)(nil) | |||
| var _ plugins.RendererManager = (*PluginManager)(nil) | ||||
| 
 | ||||
| type PluginManager struct { | ||||
| 	cfg              *plugins.Cfg | ||||
| 	requestValidator models.PluginRequestValidator | ||||
| 	sqlStore         *sqlstore.SQLStore | ||||
| 	store            map[string]*plugins.Plugin | ||||
| 	pluginInstaller  plugins.Installer | ||||
| 	pluginLoader     plugins.Loader | ||||
| 	pluginsMu        sync.RWMutex | ||||
| 	pluginPaths      map[plugins.Class][]string | ||||
| 	log              log.Logger | ||||
| 	cfg             *plugins.Cfg | ||||
| 	sqlStore        *sqlstore.SQLStore | ||||
| 	store           map[string]*plugins.Plugin | ||||
| 	pluginInstaller plugins.Installer | ||||
| 	pluginLoader    plugins.Loader | ||||
| 	pluginsMu       sync.RWMutex | ||||
| 	pluginPaths     map[plugins.Class][]string | ||||
| 	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) { | ||||
| 	pm := New(plugins.FromGrafanaCfg(grafanaCfg), requestValidator, map[plugins.Class][]string{ | ||||
| 	pm := New(plugins.FromGrafanaCfg(grafanaCfg), map[plugins.Class][]string{ | ||||
| 		plugins.Core:     corePluginPaths(grafanaCfg), | ||||
| 		plugins.Bundled:  {grafanaCfg.BundledPluginsPath}, | ||||
| 		plugins.External: append([]string{grafanaCfg.PluginsPath}, pluginSettingPaths(grafanaCfg)...), | ||||
|  | @ -57,17 +54,16 @@ func ProvideService(grafanaCfg *setting.Cfg, requestValidator models.PluginReque | |||
| 	return pm, nil | ||||
| } | ||||
| 
 | ||||
| func New(cfg *plugins.Cfg, requestValidator models.PluginRequestValidator, pluginPaths map[plugins.Class][]string, | ||||
| 	pluginLoader plugins.Loader, sqlStore *sqlstore.SQLStore) *PluginManager { | ||||
| func New(cfg *plugins.Cfg, pluginPaths map[plugins.Class][]string, pluginLoader plugins.Loader, | ||||
| 	sqlStore *sqlstore.SQLStore) *PluginManager { | ||||
| 	return &PluginManager{ | ||||
| 		cfg:              cfg, | ||||
| 		requestValidator: requestValidator, | ||||
| 		pluginLoader:     pluginLoader, | ||||
| 		pluginPaths:      pluginPaths, | ||||
| 		store:            make(map[string]*plugins.Plugin), | ||||
| 		log:              log.New("plugin.manager"), | ||||
| 		pluginInstaller:  installer.New(false, cfg.BuildVersion, newInstallerLogger("plugin.installer", true)), | ||||
| 		sqlStore:         sqlStore, | ||||
| 		cfg:             cfg, | ||||
| 		pluginLoader:    pluginLoader, | ||||
| 		pluginPaths:     pluginPaths, | ||||
| 		store:           make(map[string]*plugins.Plugin), | ||||
| 		log:             log.New("plugin.manager"), | ||||
| 		pluginInstaller: installer.New(false, cfg.BuildVersion, newInstallerLogger("plugin.installer", true)), | ||||
| 		sqlStore:        sqlStore, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | @ -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) { | ||||
| 	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) | ||||
| 	if !exists { | ||||
| 		return nil, backendplugin.ErrPluginNotRegistered | ||||
| 	} | ||||
| 
 | ||||
| 	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}) | ||||
| 		return | ||||
| 	}) | ||||
|  |  | |||
|  | @ -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) | ||||
| 
 | ||||
| 	pmCfg := plugins.FromGrafanaCfg(cfg) | ||||
| 	pm, err := ProvideService(cfg, nil, loader.New(pmCfg, license, | ||||
| 		&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, provider.ProvideService(coreRegistry)), nil) | ||||
| 	pm, err := ProvideService(cfg, loader.New(pmCfg, license, signature.NewUnsignedAuthorizer(pmCfg), | ||||
| 		provider.ProvideService(coreRegistry)), nil) | ||||
| 	require.NoError(t, err) | ||||
| 
 | ||||
| 	verifyCorePluginCatalogue(t, pm) | ||||
|  |  | |||
|  | @ -467,8 +467,7 @@ func TestPluginManager_lifecycle_unmanaged(t *testing.T) { | |||
| func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager { | ||||
| 	t.Helper() | ||||
| 
 | ||||
| 	requestValidator := &testPluginRequestValidator{} | ||||
| 	pm := New(&plugins.Cfg{}, requestValidator, nil, &fakeLoader{}, &sqlstore.SQLStore{}) | ||||
| 	pm := New(&plugins.Cfg{}, nil, &fakeLoader{}, &sqlstore.SQLStore{}) | ||||
| 
 | ||||
| 	for _, cb := range cbs { | ||||
| 		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.ManagedIdentityClientId = "client-id" | ||||
| 
 | ||||
| 	requestValidator := &testPluginRequestValidator{} | ||||
| 	loader := &fakeLoader{} | ||||
| 	manager := New(cfg, requestValidator, nil, loader, nil) | ||||
| 	manager := New(cfg, nil, loader, nil) | ||||
| 	manager.pluginLoader = loader | ||||
| 	ctx := &managerScenarioCtx{ | ||||
| 		manager: manager, | ||||
|  | @ -698,12 +696,6 @@ func (pc *fakePluginClient) RunStream(_ context.Context, _ *backend.RunStreamReq | |||
| 	return backendplugin.ErrMethodNotImplemented | ||||
| } | ||||
| 
 | ||||
| type testPluginRequestValidator struct{} | ||||
| 
 | ||||
| func (t *testPluginRequestValidator) Validate(string, *http.Request) error { | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| type fakeLogger struct { | ||||
| 	log.Logger | ||||
| } | ||||
|  |  | |||
|  | @ -5,14 +5,18 @@ import ( | |||
| 	"github.com/grafana/grafana/pkg/setting" | ||||
| ) | ||||
| 
 | ||||
| func ProvideService(cfg *setting.Cfg) (*UnsignedPluginAuthorizer, error) { | ||||
| func NewUnsignedAuthorizer(cfg *plugins.Cfg) *UnsignedPluginAuthorizer { | ||||
| 	return &UnsignedPluginAuthorizer{ | ||||
| 		Cfg: plugins.FromGrafanaCfg(cfg), | ||||
| 	}, nil | ||||
| 		cfg: cfg, | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func ProvideOSSAuthorizer(cfg *setting.Cfg) *UnsignedPluginAuthorizer { | ||||
| 	return NewUnsignedAuthorizer(plugins.FromGrafanaCfg(cfg)) | ||||
| } | ||||
| 
 | ||||
| type UnsignedPluginAuthorizer struct { | ||||
| 	Cfg *plugins.Cfg | ||||
| 	cfg *plugins.Cfg | ||||
| } | ||||
| 
 | ||||
| func (u *UnsignedPluginAuthorizer) CanLoadPlugin(p *plugins.Plugin) bool { | ||||
|  | @ -20,11 +24,11 @@ func (u *UnsignedPluginAuthorizer) CanLoadPlugin(p *plugins.Plugin) bool { | |||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	if u.Cfg.DevMode { | ||||
| 	if u.cfg.DevMode { | ||||
| 		return true | ||||
| 	} | ||||
| 
 | ||||
| 	for _, pID := range u.Cfg.PluginsAllowUnsigned { | ||||
| 	for _, pID := range u.cfg.PluginsAllowUnsigned { | ||||
| 		if pID == p.ID { | ||||
| 			return true | ||||
| 		} | ||||
|  |  | |||
|  | @ -61,7 +61,7 @@ var wireExtsBasicSet = wire.NewSet( | |||
| 	wire.Bind(new(models.SearchUserFilter), new(*filters.OSSSearchUserFilter)), | ||||
| 	searchusers.ProvideUsersService, | ||||
| 	wire.Bind(new(searchusers.Service), new(*searchusers.OSSService)), | ||||
| 	signature.ProvideService, | ||||
| 	signature.ProvideOSSAuthorizer, | ||||
| 	wire.Bind(new(plugins.PluginLoaderAuthorizer), new(*signature.UnsignedPluginAuthorizer)), | ||||
| 	provider.ProvideService, | ||||
| 	wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)), | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue