mirror of
https://github.com/grafana/grafana.git
synced 2025-01-09 23:53:25 -06:00
[WIP] Plugins: Refactoring backend initialization flow (#42247)
* refactoring store interface and init flow * fix import * fix linter * refactor resource calling * load with class * re-order args * fix tests * fix linter * remove old creator * add custom config struct * fix some tests * cleanup * fix linter * add connect failure error * remove unused err * convert test over
This commit is contained in:
parent
7ffefc069f
commit
7694fff0ef
@ -18,7 +18,7 @@ import (
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
type clientV2 struct {
|
||||
type ClientV2 struct {
|
||||
grpcplugin.DiagnosticsClient
|
||||
grpcplugin.ResourceClient
|
||||
grpcplugin.DataClient
|
||||
@ -52,7 +52,7 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c := clientV2{}
|
||||
c := ClientV2{}
|
||||
if rawDiagnostics != nil {
|
||||
if diagnosticsClient, ok := rawDiagnostics.(grpcplugin.DiagnosticsClient); ok {
|
||||
c.DiagnosticsClient = diagnosticsClient
|
||||
@ -92,7 +92,7 @@ func newClientV2(descriptor PluginDescriptor, logger log.Logger, rpcClient plugi
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (c *clientV2) CollectMetrics(ctx context.Context) (*backend.CollectMetricsResult, error) {
|
||||
func (c *ClientV2) CollectMetrics(ctx context.Context) (*backend.CollectMetricsResult, error) {
|
||||
if c.DiagnosticsClient == nil {
|
||||
return &backend.CollectMetricsResult{}, nil
|
||||
}
|
||||
@ -109,7 +109,7 @@ func (c *clientV2) CollectMetrics(ctx context.Context) (*backend.CollectMetricsR
|
||||
return backend.FromProto().CollectMetricsResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *clientV2) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
func (c *ClientV2) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||
if c.DiagnosticsClient == nil {
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
@ -130,7 +130,7 @@ func (c *clientV2) CheckHealth(ctx context.Context, req *backend.CheckHealthRequ
|
||||
return backend.FromProto().CheckHealthResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *clientV2) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
func (c *ClientV2) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||
if c.DataClient == nil {
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
@ -149,7 +149,7 @@ func (c *clientV2) QueryData(ctx context.Context, req *backend.QueryDataRequest)
|
||||
return backend.FromProto().QueryDataResponse(protoResp)
|
||||
}
|
||||
|
||||
func (c *clientV2) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
func (c *ClientV2) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||
if c.ResourceClient == nil {
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
@ -184,7 +184,7 @@ func (c *clientV2) CallResource(ctx context.Context, req *backend.CallResourceRe
|
||||
}
|
||||
}
|
||||
|
||||
func (c *clientV2) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
|
||||
func (c *ClientV2) SubscribeStream(ctx context.Context, req *backend.SubscribeStreamRequest) (*backend.SubscribeStreamResponse, error) {
|
||||
if c.StreamClient == nil {
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
@ -195,7 +195,7 @@ func (c *clientV2) SubscribeStream(ctx context.Context, req *backend.SubscribeSt
|
||||
return backend.FromProto().SubscribeStreamResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *clientV2) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
|
||||
func (c *ClientV2) PublishStream(ctx context.Context, req *backend.PublishStreamRequest) (*backend.PublishStreamResponse, error) {
|
||||
if c.StreamClient == nil {
|
||||
return nil, backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
@ -206,7 +206,7 @@ func (c *clientV2) PublishStream(ctx context.Context, req *backend.PublishStream
|
||||
return backend.FromProto().PublishStreamResponse(protoResp), nil
|
||||
}
|
||||
|
||||
func (c *clientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
func (c *ClientV2) RunStream(ctx context.Context, req *backend.RunStreamRequest, sender *backend.StreamSender) error {
|
||||
if c.StreamClient == nil {
|
||||
return backendplugin.ErrMethodNotImplemented
|
||||
}
|
||||
|
48
pkg/plugins/backendplugin/provider/provider.go
Normal file
48
pkg/plugins/backendplugin/provider/provider.go
Normal file
@ -0,0 +1,48 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/grpcplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
|
||||
)
|
||||
|
||||
type Service struct{}
|
||||
|
||||
func ProvideService() *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
||||
func (*Service) BackendFactory(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
||||
for _, provider := range []PluginBackendProvider{RendererProvider, DefaultProvider} {
|
||||
if factory := provider(ctx, p); factory != nil {
|
||||
return factory
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
cmd := plugins.ComposeRendererStartCommand()
|
||||
return grpcplugin.NewRendererPlugin(p.ID, filepath.Join(p.PluginDir, cmd),
|
||||
func(pluginID string, renderer pluginextensionv2.RendererPlugin, logger log.Logger) error {
|
||||
p.Renderer = renderer
|
||||
return nil
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
var DefaultProvider PluginBackendProvider = func(_ context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
||||
cmd := plugins.ComposePluginStartCommand(p.Executable)
|
||||
return grpcplugin.NewBackendPlugin(p.ID, filepath.Join(p.PluginDir, cmd))
|
||||
}
|
57
pkg/plugins/config.go
Normal file
57
pkg/plugins/config.go
Normal file
@ -0,0 +1,57 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type Cfg struct {
|
||||
DevMode bool
|
||||
|
||||
PluginsPath string
|
||||
|
||||
PluginSettings setting.PluginSettings
|
||||
PluginsAllowUnsigned []string
|
||||
|
||||
EnterpriseLicensePath string
|
||||
|
||||
// AWS Plugin Auth
|
||||
AWSAllowedAuthProviders []string
|
||||
AWSAssumeRoleEnabled bool
|
||||
|
||||
// Azure Cloud settings
|
||||
Azure setting.AzureSettings
|
||||
|
||||
CheckForUpdates bool
|
||||
|
||||
BuildVersion string // TODO Remove
|
||||
AppSubURL string // TODO Remove
|
||||
}
|
||||
|
||||
func NewCfg() *Cfg {
|
||||
return &Cfg{}
|
||||
}
|
||||
|
||||
func FromGrafanaCfg(grafanaCfg *setting.Cfg) *Cfg {
|
||||
cfg := &Cfg{}
|
||||
|
||||
cfg.DevMode = grafanaCfg.Env == setting.Dev
|
||||
cfg.PluginsPath = grafanaCfg.PluginsPath
|
||||
|
||||
cfg.PluginSettings = grafanaCfg.PluginSettings
|
||||
cfg.PluginsAllowUnsigned = grafanaCfg.PluginsAllowUnsigned
|
||||
cfg.EnterpriseLicensePath = grafanaCfg.EnterpriseLicensePath
|
||||
|
||||
// AWS
|
||||
cfg.AWSAllowedAuthProviders = grafanaCfg.AWSAllowedAuthProviders
|
||||
cfg.AWSAssumeRoleEnabled = grafanaCfg.AWSAssumeRoleEnabled
|
||||
|
||||
// Azure
|
||||
cfg.Azure = grafanaCfg.Azure
|
||||
|
||||
cfg.CheckForUpdates = grafanaCfg.CheckForUpdates
|
||||
|
||||
cfg.BuildVersion = grafanaCfg.BuildVersion
|
||||
cfg.AppSubURL = grafanaCfg.AppSubURL
|
||||
|
||||
return cfg
|
||||
}
|
@ -34,10 +34,10 @@ type AddOpts struct {
|
||||
// Loader is responsible for loading plugins from the file system.
|
||||
type Loader interface {
|
||||
// Load will return a list of plugins found in the provided file system paths.
|
||||
Load(paths []string, ignore map[string]struct{}) ([]*Plugin, error)
|
||||
Load(ctx context.Context, class Class, paths []string, ignore map[string]struct{}) ([]*Plugin, error)
|
||||
// LoadWithFactory will return a plugin found in the provided file system path and use the provided factory to
|
||||
// construct the plugin backend client.
|
||||
LoadWithFactory(path string, factory backendplugin.PluginFactoryFunc) (*Plugin, error)
|
||||
LoadWithFactory(ctx context.Context, class Class, path string, factory backendplugin.PluginFactoryFunc) (*Plugin, error)
|
||||
}
|
||||
|
||||
// Installer is responsible for managing plugins (add / remove) on the file system.
|
||||
@ -65,6 +65,11 @@ type Client interface {
|
||||
CollectMetrics(ctx context.Context, pluginID string) (*backend.CollectMetricsResult, error)
|
||||
}
|
||||
|
||||
// BackendFactoryProvider provides a backend factory for a provided plugin.
|
||||
type BackendFactoryProvider interface {
|
||||
BackendFactory(ctx context.Context, p *Plugin) backendplugin.PluginFactoryFunc
|
||||
}
|
||||
|
||||
type RendererManager interface {
|
||||
// Renderer returns a renderer plugin.
|
||||
Renderer() *Plugin
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
@ -91,8 +92,10 @@ func pluginScenario(t *testing.T, desc string, fn func(*testing.T, *PluginManage
|
||||
},
|
||||
},
|
||||
}
|
||||
pm := newManager(cfg, nil, loader.New(nil, cfg, &signature.UnsignedPluginAuthorizer{Cfg: cfg}), &sqlstore.SQLStore{})
|
||||
err := pm.init()
|
||||
|
||||
pmCfg := plugins.FromGrafanaCfg(cfg)
|
||||
pm, err := ProvideService(cfg, nil, loader.New(pmCfg, nil,
|
||||
&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, &provider.Service{}), &sqlstore.SQLStore{})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run(desc, func(t *testing.T) {
|
||||
|
@ -7,6 +7,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
@ -23,8 +25,9 @@ func TestGetPluginDashboards(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
pm := newManager(cfg, nil, loader.New(nil, cfg, &signature.UnsignedPluginAuthorizer{Cfg: cfg}), &sqlstore.SQLStore{})
|
||||
err := pm.init()
|
||||
pmCfg := plugins.FromGrafanaCfg(cfg)
|
||||
pm, err := ProvideService(cfg, nil, loader.New(pmCfg, nil,
|
||||
&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, &provider.Service{}), &sqlstore.SQLStore{})
|
||||
require.NoError(t, err)
|
||||
|
||||
bus.AddHandler("test", func(ctx context.Context, query *models.GetDashboardQuery) error {
|
||||
|
@ -8,17 +8,15 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/fs"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type Finder struct {
|
||||
cfg *setting.Cfg
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func New(cfg *setting.Cfg) Finder {
|
||||
return Finder{cfg: cfg, log: log.New("plugin.finder")}
|
||||
func New() Finder {
|
||||
return Finder{log: log.New("plugin.finder")}
|
||||
}
|
||||
|
||||
func (f *Finder) Find(pluginDirs []string) ([]string, error) {
|
||||
|
@ -51,7 +51,7 @@ func TestFinder_Find(t *testing.T) {
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
f := New(tc.cfg)
|
||||
f := New()
|
||||
pluginPaths, err := f.Find(tc.pluginDirs)
|
||||
if (err != nil) && !errors.Is(err, tc.err) {
|
||||
t.Errorf("Find() error = %v, expected error %v", err, tc.err)
|
||||
|
@ -1,104 +1,43 @@
|
||||
package initializer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-aws-sdk/pkg/awsds"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"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/grpcplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/pluginextensionv2"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
type Initializer struct {
|
||||
cfg *setting.Cfg
|
||||
license models.Licensing
|
||||
log log.Logger
|
||||
cfg *plugins.Cfg
|
||||
license models.Licensing
|
||||
backendProvider plugins.BackendFactoryProvider
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func New(cfg *setting.Cfg, license models.Licensing) Initializer {
|
||||
func New(cfg *plugins.Cfg, backendProvider plugins.BackendFactoryProvider, license models.Licensing) Initializer {
|
||||
return Initializer{
|
||||
cfg: cfg,
|
||||
license: license,
|
||||
log: log.New("plugin.initializer"),
|
||||
cfg: cfg,
|
||||
license: license,
|
||||
backendProvider: backendProvider,
|
||||
log: log.New("plugin.initializer"),
|
||||
}
|
||||
}
|
||||
|
||||
func (i *Initializer) Initialize(p *plugins.Plugin) error {
|
||||
if len(p.Dependencies.Plugins) == 0 {
|
||||
p.Dependencies.Plugins = []plugins.Dependency{}
|
||||
}
|
||||
|
||||
if p.Dependencies.GrafanaVersion == "" {
|
||||
p.Dependencies.GrafanaVersion = "*"
|
||||
}
|
||||
|
||||
for _, include := range p.Includes {
|
||||
if include.Role == "" {
|
||||
include.Role = models.ROLE_VIEWER
|
||||
}
|
||||
}
|
||||
|
||||
i.handleModuleDefaults(p)
|
||||
|
||||
p.Info.Logos.Small = pluginLogoURL(p.Type, p.Info.Logos.Small, p.BaseURL)
|
||||
p.Info.Logos.Large = pluginLogoURL(p.Type, p.Info.Logos.Large, p.BaseURL)
|
||||
|
||||
for i := 0; i < len(p.Info.Screenshots); i++ {
|
||||
p.Info.Screenshots[i].Path = evalRelativePluginURLPath(p.Info.Screenshots[i].Path, p.BaseURL, p.Type)
|
||||
}
|
||||
|
||||
if p.IsApp() {
|
||||
for _, child := range p.Children {
|
||||
i.setPathsBasedOnApp(p, child)
|
||||
}
|
||||
|
||||
// slugify pages
|
||||
for _, include := range p.Includes {
|
||||
if include.Slug == "" {
|
||||
include.Slug = slug.Make(include.Name)
|
||||
}
|
||||
if include.Type == "page" && include.DefaultNav {
|
||||
p.DefaultNavURL = i.cfg.AppSubURL + "/plugins/" + p.ID + "/page/" + include.Slug
|
||||
}
|
||||
if include.Type == "dashboard" && include.DefaultNav {
|
||||
p.DefaultNavURL = i.cfg.AppSubURL + "/dashboard/db/" + include.Slug
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pluginLog := i.log.New("pluginID", p.ID)
|
||||
p.SetLogger(pluginLog)
|
||||
|
||||
func (i *Initializer) Initialize(ctx context.Context, p *plugins.Plugin) error {
|
||||
if p.Backend {
|
||||
var backendFactory backendplugin.PluginFactoryFunc
|
||||
if p.IsRenderer() {
|
||||
cmd := plugins.ComposeRendererStartCommand()
|
||||
backendFactory = grpcplugin.NewRendererPlugin(p.ID, filepath.Join(p.PluginDir, cmd),
|
||||
func(pluginID string, renderer pluginextensionv2.RendererPlugin, logger log.Logger) error {
|
||||
p.Renderer = renderer
|
||||
return nil
|
||||
},
|
||||
)
|
||||
} else {
|
||||
cmd := plugins.ComposePluginStartCommand(p.Executable)
|
||||
backendFactory = grpcplugin.NewBackendPlugin(p.ID, filepath.Join(p.PluginDir, cmd))
|
||||
backendFactory := i.backendProvider.BackendFactory(ctx, p)
|
||||
if backendFactory == nil {
|
||||
return fmt.Errorf("could not find backend factory for plugin")
|
||||
}
|
||||
|
||||
if backendClient, err := backendFactory(p.ID, pluginLog, i.envVars(p)); err != nil {
|
||||
if backendClient, err := backendFactory(p.ID, p.Logger(), i.envVars(p)); err != nil {
|
||||
return err
|
||||
} else {
|
||||
p.RegisterClient(backendClient)
|
||||
@ -109,88 +48,20 @@ func (i *Initializer) Initialize(p *plugins.Plugin) error {
|
||||
}
|
||||
|
||||
func (i *Initializer) InitializeWithFactory(p *plugins.Plugin, factory backendplugin.PluginFactoryFunc) error {
|
||||
err := i.Initialize(p)
|
||||
if factory == nil {
|
||||
return fmt.Errorf("could not initialize plugin %s", p.ID)
|
||||
}
|
||||
|
||||
f, err := factory(p.ID, log.New("pluginID", p.ID), []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if factory != nil {
|
||||
var err error
|
||||
|
||||
f, err := factory(p.ID, log.New("pluginID", p.ID), []string{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p.RegisterClient(f)
|
||||
} else {
|
||||
i.log.Warn("Could not initialize core plugin process", "pluginID", p.ID)
|
||||
return fmt.Errorf("could not initialize plugin %s", p.ID)
|
||||
}
|
||||
p.RegisterClient(f)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Initializer) handleModuleDefaults(p *plugins.Plugin) {
|
||||
if p.IsCorePlugin() {
|
||||
// Previously there was an assumption that the Core plugins directory
|
||||
// should be public/app/plugins/<plugin type>/<plugin id>
|
||||
// However this can be an issue if the Core plugins directory is renamed
|
||||
baseDir := filepath.Base(p.PluginDir)
|
||||
|
||||
// use path package for the following statements because these are not file paths
|
||||
p.Module = path.Join("app/plugins", string(p.Type), baseDir, "module")
|
||||
p.BaseURL = path.Join("public/app/plugins", string(p.Type), baseDir)
|
||||
return
|
||||
}
|
||||
|
||||
metrics.SetPluginBuildInformation(p.ID, string(p.Type), p.Info.Version, string(p.Signature))
|
||||
|
||||
p.Module = path.Join("plugins", p.ID, "module")
|
||||
p.BaseURL = path.Join("public/plugins", p.ID)
|
||||
}
|
||||
|
||||
func (i *Initializer) setPathsBasedOnApp(parent *plugins.Plugin, child *plugins.Plugin) {
|
||||
appSubPath := strings.ReplaceAll(strings.Replace(child.PluginDir, parent.PluginDir, "", 1), "\\", "/")
|
||||
child.IncludedInAppID = parent.ID
|
||||
child.BaseURL = parent.BaseURL
|
||||
|
||||
if parent.IsCorePlugin() {
|
||||
child.Module = util.JoinURLFragments("app/plugins/app/"+parent.ID, appSubPath) + "/module"
|
||||
} else {
|
||||
child.Module = util.JoinURLFragments("plugins/"+parent.ID, appSubPath) + "/module"
|
||||
}
|
||||
}
|
||||
|
||||
func pluginLogoURL(pluginType plugins.Type, path, baseURL string) string {
|
||||
if path == "" {
|
||||
return defaultLogoPath(pluginType)
|
||||
}
|
||||
|
||||
return evalRelativePluginURLPath(path, baseURL, pluginType)
|
||||
}
|
||||
|
||||
func defaultLogoPath(pluginType plugins.Type) string {
|
||||
return "public/img/icn-" + string(pluginType) + ".svg"
|
||||
}
|
||||
|
||||
func evalRelativePluginURLPath(pathStr, baseURL string, pluginType plugins.Type) string {
|
||||
if pathStr == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
u, _ := url.Parse(pathStr)
|
||||
if u.IsAbs() {
|
||||
return pathStr
|
||||
}
|
||||
|
||||
// is set as default or has already been prefixed with base path
|
||||
if pathStr == defaultLogoPath(pluginType) || strings.HasPrefix(pathStr, baseURL) {
|
||||
return pathStr
|
||||
}
|
||||
|
||||
return path.Join(baseURL, pathStr)
|
||||
}
|
||||
|
||||
func (i *Initializer) envVars(plugin *plugins.Plugin) []string {
|
||||
hostEnv := []string{
|
||||
fmt.Sprintf("GF_VERSION=%s", i.cfg.BuildVersion),
|
||||
@ -260,7 +131,7 @@ func (ps pluginSettings) asEnvVar(prefix string, hostEnv []string) []string {
|
||||
return env
|
||||
}
|
||||
|
||||
func getPluginSettings(pluginID string, cfg *setting.Cfg) pluginSettings {
|
||||
func getPluginSettings(pluginID string, cfg *plugins.Cfg) pluginSettings {
|
||||
ps := pluginSettings{}
|
||||
for k, v := range cfg.PluginSettings[pluginID] {
|
||||
if k == "path" || strings.ToLower(k) == "id" {
|
||||
|
@ -1,17 +1,15 @@
|
||||
package initializer
|
||||
|
||||
import (
|
||||
"path"
|
||||
"context"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"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/setting"
|
||||
)
|
||||
|
||||
func TestInitializer_Initialize(t *testing.T) {
|
||||
@ -36,21 +34,16 @@ func TestInitializer_Initialize(t *testing.T) {
|
||||
}
|
||||
|
||||
i := &Initializer{
|
||||
cfg: setting.NewCfg(),
|
||||
cfg: plugins.NewCfg(),
|
||||
log: &fakeLogger{},
|
||||
backendProvider: &fakeBackendProvider{
|
||||
plugin: p,
|
||||
},
|
||||
}
|
||||
|
||||
err := i.Initialize(p)
|
||||
err := i.Initialize(context.Background(), p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "public/img/icn-datasource.svg", p.Info.Logos.Small)
|
||||
assert.Equal(t, "public/img/icn-datasource.svg", p.Info.Logos.Large)
|
||||
assert.Equal(t, "*", p.Dependencies.GrafanaVersion)
|
||||
assert.Len(t, p.Includes, 1)
|
||||
assert.Equal(t, models.ROLE_VIEWER, p.Includes[0].Role)
|
||||
assert.Equal(t, filepath.Join("app/plugins/datasource", filepath.Base(p.PluginDir), "module"), p.Module)
|
||||
assert.Equal(t, path.Join("public/app/plugins/datasource", filepath.Base(p.PluginDir)), p.BaseURL)
|
||||
assert.NotNil(t, p.Logger())
|
||||
c, exists := p.Client()
|
||||
assert.True(t, exists)
|
||||
assert.NotNil(t, c)
|
||||
@ -71,101 +64,50 @@ func TestInitializer_Initialize(t *testing.T) {
|
||||
}
|
||||
|
||||
i := &Initializer{
|
||||
cfg: setting.NewCfg(),
|
||||
cfg: plugins.NewCfg(),
|
||||
log: fakeLogger{},
|
||||
backendProvider: &fakeBackendProvider{
|
||||
plugin: p,
|
||||
},
|
||||
}
|
||||
|
||||
err := i.Initialize(p)
|
||||
err := i.Initialize(context.Background(), p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// TODO add default img to project
|
||||
assert.Equal(t, "public/img/icn-renderer.svg", p.Info.Logos.Small)
|
||||
assert.Equal(t, "public/img/icn-renderer.svg", p.Info.Logos.Large)
|
||||
assert.Equal(t, ">=8.x", p.Dependencies.GrafanaVersion)
|
||||
assert.Equal(t, "plugins/test/module", p.Module)
|
||||
assert.Equal(t, "public/plugins/test", p.BaseURL)
|
||||
assert.NotNil(t, p.Logger())
|
||||
c, exists := p.Client()
|
||||
assert.True(t, exists)
|
||||
assert.NotNil(t, c)
|
||||
})
|
||||
|
||||
t.Run("external app", func(t *testing.T) {
|
||||
t.Run("non backend plugin app", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "parent-plugin",
|
||||
Type: plugins.App,
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
Type: "page",
|
||||
DefaultNav: true,
|
||||
Slug: "myCustomSlug",
|
||||
},
|
||||
},
|
||||
},
|
||||
PluginDir: absCurPath,
|
||||
Class: plugins.External,
|
||||
Children: []*plugins.Plugin{
|
||||
{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "child-plugin",
|
||||
},
|
||||
PluginDir: absCurPath,
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
}
|
||||
|
||||
i := &Initializer{
|
||||
cfg: &setting.Cfg{
|
||||
AppSubURL: "appSubURL",
|
||||
},
|
||||
cfg: &plugins.Cfg{},
|
||||
log: fakeLogger{},
|
||||
backendProvider: &fakeBackendProvider{
|
||||
plugin: p,
|
||||
},
|
||||
}
|
||||
|
||||
err := i.Initialize(p)
|
||||
err := i.Initialize(context.Background(), p)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "public/img/icn-app.svg", p.Info.Logos.Small)
|
||||
assert.Equal(t, "public/img/icn-app.svg", p.Info.Logos.Large)
|
||||
assert.Equal(t, "*", p.Dependencies.GrafanaVersion)
|
||||
assert.Len(t, p.Includes, 1)
|
||||
assert.Equal(t, models.ROLE_VIEWER, p.Includes[0].Role)
|
||||
assert.Equal(t, filepath.Join("plugins", p.ID, "module"), p.Module)
|
||||
assert.Equal(t, "public/plugins/parent-plugin", p.BaseURL)
|
||||
assert.NotNil(t, p.Logger())
|
||||
c, exists := p.Client()
|
||||
assert.False(t, exists)
|
||||
assert.Nil(t, c)
|
||||
|
||||
assert.Len(t, p.Children, 1)
|
||||
assert.Equal(t, p.ID, p.Children[0].IncludedInAppID)
|
||||
assert.Equal(t, "public/plugins/parent-plugin", p.Children[0].BaseURL)
|
||||
assert.Equal(t, "plugins/parent-plugin/module", p.Children[0].Module)
|
||||
assert.Equal(t, "appSubURL/plugins/parent-plugin/page/myCustomSlug", p.DefaultNavURL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInitializer_InitializeWithFactory(t *testing.T) {
|
||||
t.Run("happy path", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-plugin",
|
||||
Type: plugins.App,
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
Type: "page",
|
||||
DefaultNav: true,
|
||||
Slug: "myCustomSlug",
|
||||
},
|
||||
},
|
||||
},
|
||||
PluginDir: "test/folder",
|
||||
Class: plugins.External,
|
||||
}
|
||||
p := &plugins.Plugin{}
|
||||
i := &Initializer{
|
||||
cfg: &setting.Cfg{
|
||||
AppSubURL: "appSubURL",
|
||||
},
|
||||
cfg: &plugins.Cfg{},
|
||||
log: fakeLogger{},
|
||||
}
|
||||
|
||||
@ -180,33 +122,19 @@ func TestInitializer_InitializeWithFactory(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.True(t, factoryInvoked)
|
||||
assert.NotNil(t, p.Logger())
|
||||
client, exists := p.Client()
|
||||
assert.True(t, exists)
|
||||
assert.NotNil(t, client.(testPlugin))
|
||||
})
|
||||
|
||||
t.Run("invalid factory", func(t *testing.T) {
|
||||
p := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "test-plugin",
|
||||
Type: plugins.App,
|
||||
Includes: []*plugins.Includes{
|
||||
{
|
||||
Type: "page",
|
||||
DefaultNav: true,
|
||||
Slug: "myCustomSlug",
|
||||
},
|
||||
},
|
||||
},
|
||||
PluginDir: "test/folder",
|
||||
Class: plugins.External,
|
||||
}
|
||||
p := &plugins.Plugin{}
|
||||
i := &Initializer{
|
||||
cfg: &setting.Cfg{
|
||||
AppSubURL: "appSubURL",
|
||||
},
|
||||
cfg: &plugins.Cfg{},
|
||||
log: fakeLogger{},
|
||||
backendProvider: &fakeBackendProvider{
|
||||
plugin: p,
|
||||
},
|
||||
}
|
||||
|
||||
err := i.InitializeWithFactory(p, nil)
|
||||
@ -232,7 +160,7 @@ func TestInitializer_envVars(t *testing.T) {
|
||||
}
|
||||
|
||||
i := &Initializer{
|
||||
cfg: &setting.Cfg{
|
||||
cfg: &plugins.Cfg{
|
||||
EnterpriseLicensePath: "/path/to/ent/license",
|
||||
PluginSettings: map[string]map[string]string{
|
||||
"test": {
|
||||
@ -242,6 +170,9 @@ func TestInitializer_envVars(t *testing.T) {
|
||||
},
|
||||
license: licensing,
|
||||
log: fakeLogger{},
|
||||
backendProvider: &fakeBackendProvider{
|
||||
plugin: p,
|
||||
},
|
||||
}
|
||||
|
||||
envVars := i.envVars(p)
|
||||
@ -254,33 +185,6 @@ func TestInitializer_envVars(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestInitializer_setPathsBasedOnApp(t *testing.T) {
|
||||
t.Run("When setting paths based on core plugin on Windows", func(t *testing.T) {
|
||||
i := &Initializer{
|
||||
cfg: setting.NewCfg(),
|
||||
log: fakeLogger{},
|
||||
}
|
||||
|
||||
child := &plugins.Plugin{
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata\\datasources\\datasource",
|
||||
}
|
||||
parent := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "testdata",
|
||||
},
|
||||
Class: plugins.Core,
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata",
|
||||
BaseURL: "public/app/plugins/app/testdata",
|
||||
}
|
||||
|
||||
i.setPathsBasedOnApp(parent, child)
|
||||
|
||||
assert.Equal(t, "app/plugins/app/testdata/datasources/datasource/module", child.Module)
|
||||
assert.Equal(t, "testdata", child.IncludedInAppID)
|
||||
assert.Equal(t, "public/app/plugins/app/testdata", child.BaseURL)
|
||||
})
|
||||
}
|
||||
|
||||
func TestInitializer_getAWSEnvironmentVariables(t *testing.T) {
|
||||
|
||||
}
|
||||
@ -334,7 +238,7 @@ func (t *testLicensingService) ContentDeliveryPrefix() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *testLicensingService) LicenseURL(showAdminLicensingPage bool) string {
|
||||
func (t *testLicensingService) LicenseURL(_ bool) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
@ -365,3 +269,15 @@ func (f fakeLogger) New(_ ...interface{}) log.MultiLoggers {
|
||||
func (f fakeLogger) Warn(_ string, _ ...interface{}) {
|
||||
|
||||
}
|
||||
|
||||
type fakeBackendProvider struct {
|
||||
plugins.BackendFactoryProvider
|
||||
|
||||
plugin *plugins.Plugin
|
||||
}
|
||||
|
||||
func (f *fakeBackendProvider) BackendFactory(_ context.Context, _ *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
||||
return func(_ string, _ log.Logger, _ []string) (backendplugin.Plugin, error) {
|
||||
return f.plugin, nil
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,22 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/gosimple/slug"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/fs"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
@ -18,6 +24,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/initializer"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -28,7 +35,7 @@ var (
|
||||
var _ plugins.ErrorResolver = (*Loader)(nil)
|
||||
|
||||
type Loader struct {
|
||||
cfg *setting.Cfg
|
||||
cfg *plugins.Cfg
|
||||
pluginFinder finder.Finder
|
||||
pluginInitializer initializer.Initializer
|
||||
signatureValidator signature.Validator
|
||||
@ -37,32 +44,34 @@ type Loader struct {
|
||||
errs map[string]*plugins.SignatureError
|
||||
}
|
||||
|
||||
func ProvideService(license models.Licensing, cfg *setting.Cfg, authorizer plugins.PluginLoaderAuthorizer) (*Loader, error) {
|
||||
return New(license, cfg, authorizer), nil
|
||||
func ProvideService(cfg *setting.Cfg, license models.Licensing, authorizer plugins.PluginLoaderAuthorizer,
|
||||
backendProvider plugins.BackendFactoryProvider) (*Loader, error) {
|
||||
return New(plugins.FromGrafanaCfg(cfg), license, authorizer, backendProvider), nil
|
||||
}
|
||||
|
||||
func New(license models.Licensing, cfg *setting.Cfg, authorizer plugins.PluginLoaderAuthorizer) *Loader {
|
||||
func New(cfg *plugins.Cfg, license models.Licensing, authorizer plugins.PluginLoaderAuthorizer,
|
||||
backendProvider plugins.BackendFactoryProvider) *Loader {
|
||||
return &Loader{
|
||||
cfg: cfg,
|
||||
pluginFinder: finder.New(cfg),
|
||||
pluginInitializer: initializer.New(cfg, license),
|
||||
pluginFinder: finder.New(),
|
||||
pluginInitializer: initializer.New(cfg, backendProvider, license),
|
||||
signatureValidator: signature.NewValidator(authorizer),
|
||||
errs: make(map[string]*plugins.SignatureError),
|
||||
log: log.New("plugin.loader"),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loader) Load(paths []string, ignore map[string]struct{}) ([]*plugins.Plugin, error) {
|
||||
func (l *Loader) Load(ctx context.Context, class plugins.Class, paths []string, ignore map[string]struct{}) ([]*plugins.Plugin, error) {
|
||||
pluginJSONPaths, err := l.pluginFinder.Find(paths)
|
||||
if err != nil {
|
||||
l.log.Error("plugin finder encountered an error", "err", err)
|
||||
}
|
||||
|
||||
return l.loadPlugins(pluginJSONPaths, ignore)
|
||||
return l.loadPlugins(ctx, class, pluginJSONPaths, ignore)
|
||||
}
|
||||
|
||||
func (l *Loader) LoadWithFactory(path string, factory backendplugin.PluginFactoryFunc) (*plugins.Plugin, error) {
|
||||
p, err := l.load(path, map[string]struct{}{})
|
||||
func (l *Loader) LoadWithFactory(ctx context.Context, class plugins.Class, path string, factory backendplugin.PluginFactoryFunc) (*plugins.Plugin, error) {
|
||||
p, err := l.load(ctx, class, path, map[string]struct{}{})
|
||||
if err != nil {
|
||||
l.log.Error("failed to load core plugin", "err", err)
|
||||
return nil, err
|
||||
@ -73,14 +82,14 @@ func (l *Loader) LoadWithFactory(path string, factory backendplugin.PluginFactor
|
||||
return p, err
|
||||
}
|
||||
|
||||
func (l *Loader) load(path string, ignore map[string]struct{}) (*plugins.Plugin, error) {
|
||||
func (l *Loader) load(ctx context.Context, class plugins.Class, path string, ignore map[string]struct{}) (*plugins.Plugin, error) {
|
||||
pluginJSONPaths, err := l.pluginFinder.Find([]string{path})
|
||||
if err != nil {
|
||||
l.log.Error("failed to find plugin", "err", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loadedPlugins, err := l.loadPlugins(pluginJSONPaths, ignore)
|
||||
loadedPlugins, err := l.loadPlugins(ctx, class, pluginJSONPaths, ignore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -92,7 +101,7 @@ func (l *Loader) load(path string, ignore map[string]struct{}) (*plugins.Plugin,
|
||||
return loadedPlugins[0], nil
|
||||
}
|
||||
|
||||
func (l *Loader) loadPlugins(pluginJSONPaths []string, existingPlugins map[string]struct{}) ([]*plugins.Plugin, error) {
|
||||
func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, pluginJSONPaths []string, existingPlugins map[string]struct{}) ([]*plugins.Plugin, error) {
|
||||
var foundPlugins = foundPlugins{}
|
||||
|
||||
// load plugin.json files and map directory to JSON data
|
||||
@ -124,8 +133,10 @@ func (l *Loader) loadPlugins(pluginJSONPaths []string, existingPlugins map[strin
|
||||
plugin := &plugins.Plugin{
|
||||
JSONData: pluginJSON,
|
||||
PluginDir: pluginDir,
|
||||
Class: l.pluginClass(pluginDir),
|
||||
Class: class,
|
||||
}
|
||||
l.setDefaults(plugin)
|
||||
plugin.SetLogger(l.log.New("pluginID", plugin.ID))
|
||||
|
||||
sig, err := signature.Calculate(l.log, plugin)
|
||||
if err != nil {
|
||||
@ -160,7 +171,7 @@ func (l *Loader) loadPlugins(pluginJSONPaths []string, existingPlugins map[strin
|
||||
}
|
||||
|
||||
// validate signatures
|
||||
verifiedPlugins := []*plugins.Plugin{}
|
||||
verifiedPlugins := make([]*plugins.Plugin, 0)
|
||||
for _, plugin := range loadedPlugins {
|
||||
signingError := l.signatureValidator.Validate(plugin)
|
||||
if signingError != nil {
|
||||
@ -192,7 +203,7 @@ func (l *Loader) loadPlugins(pluginJSONPaths []string, existingPlugins map[strin
|
||||
}
|
||||
|
||||
for _, p := range verifiedPlugins {
|
||||
err := l.pluginInitializer.Initialize(p)
|
||||
err := l.pluginInitializer.Initialize(ctx, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -233,9 +244,114 @@ func (l *Loader) readPluginJSON(pluginJSONPath string) (plugins.JSONData, error)
|
||||
plugin.Name = "Pie Chart (old)"
|
||||
}
|
||||
|
||||
if len(plugin.Dependencies.Plugins) == 0 {
|
||||
plugin.Dependencies.Plugins = []plugins.Dependency{}
|
||||
}
|
||||
|
||||
if plugin.Dependencies.GrafanaVersion == "" {
|
||||
plugin.Dependencies.GrafanaVersion = "*"
|
||||
}
|
||||
|
||||
for _, include := range plugin.Includes {
|
||||
if include.Role == "" {
|
||||
include.Role = models.ROLE_VIEWER
|
||||
}
|
||||
}
|
||||
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func (l *Loader) setDefaults(p *plugins.Plugin) {
|
||||
setModule(p)
|
||||
|
||||
p.Info.Logos.Small = pluginLogoURL(p.Type, p.Info.Logos.Small, p.BaseURL)
|
||||
p.Info.Logos.Large = pluginLogoURL(p.Type, p.Info.Logos.Large, p.BaseURL)
|
||||
|
||||
for i := 0; i < len(p.Info.Screenshots); i++ {
|
||||
p.Info.Screenshots[i].Path = evalRelativePluginURLPath(p.Info.Screenshots[i].Path, p.BaseURL, p.Type)
|
||||
}
|
||||
|
||||
if p.IsApp() {
|
||||
for _, child := range p.Children {
|
||||
setChildModule(p, child)
|
||||
}
|
||||
|
||||
// slugify pages
|
||||
for _, include := range p.Includes {
|
||||
if include.Slug == "" {
|
||||
include.Slug = slug.Make(include.Name)
|
||||
}
|
||||
if include.Type == "page" && include.DefaultNav {
|
||||
p.DefaultNavURL = l.cfg.AppSubURL + "/plugins/" + p.ID + "/page/" + include.Slug
|
||||
}
|
||||
if include.Type == "dashboard" && include.DefaultNav {
|
||||
p.DefaultNavURL = l.cfg.AppSubURL + "/dashboard/db/" + include.Slug
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setModule(p *plugins.Plugin) {
|
||||
if p.IsCorePlugin() {
|
||||
// Previously there was an assumption that the Core plugins directory
|
||||
// should be public/app/plugins/<plugin type>/<plugin id>
|
||||
// However this can be an issue if the Core plugins directory is renamed
|
||||
baseDir := filepath.Base(p.PluginDir)
|
||||
|
||||
// use path package for the following statements because these are not file paths
|
||||
p.Module = path.Join("app/plugins", string(p.Type), baseDir, "module")
|
||||
p.BaseURL = path.Join("public/app/plugins", string(p.Type), baseDir)
|
||||
return
|
||||
}
|
||||
|
||||
metrics.SetPluginBuildInformation(p.ID, string(p.Type), p.Info.Version, string(p.Signature))
|
||||
|
||||
p.Module = path.Join("plugins", p.ID, "module")
|
||||
p.BaseURL = path.Join("public/plugins", p.ID)
|
||||
}
|
||||
|
||||
func setChildModule(parent *plugins.Plugin, child *plugins.Plugin) {
|
||||
appSubPath := strings.ReplaceAll(strings.Replace(child.PluginDir, parent.PluginDir, "", 1), "\\", "/")
|
||||
child.IncludedInAppID = parent.ID
|
||||
child.BaseURL = parent.BaseURL
|
||||
|
||||
if parent.IsCorePlugin() {
|
||||
child.Module = util.JoinURLFragments("app/plugins/app/"+parent.ID, appSubPath) + "/module"
|
||||
} else {
|
||||
child.Module = util.JoinURLFragments("plugins/"+parent.ID, appSubPath) + "/module"
|
||||
}
|
||||
}
|
||||
|
||||
func pluginLogoURL(pluginType plugins.Type, path, baseURL string) string {
|
||||
if path == "" {
|
||||
return defaultLogoPath(pluginType)
|
||||
}
|
||||
|
||||
return evalRelativePluginURLPath(path, baseURL, pluginType)
|
||||
}
|
||||
|
||||
func defaultLogoPath(pluginType plugins.Type) string {
|
||||
return "public/img/icn-" + string(pluginType) + ".svg"
|
||||
}
|
||||
|
||||
func evalRelativePluginURLPath(pathStr, baseURL string, pluginType plugins.Type) string {
|
||||
if pathStr == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
u, _ := url.Parse(pathStr)
|
||||
if u.IsAbs() {
|
||||
return pathStr
|
||||
}
|
||||
|
||||
// is set as default or has already been prefixed with base path
|
||||
if pathStr == defaultLogoPath(pluginType) || strings.HasPrefix(pathStr, baseURL) {
|
||||
return pathStr
|
||||
}
|
||||
|
||||
return path.Join(baseURL, pathStr)
|
||||
}
|
||||
|
||||
func (l *Loader) PluginErrors() []*plugins.Error {
|
||||
errs := make([]*plugins.Error, 0)
|
||||
for _, err := range l.errs {
|
||||
@ -255,41 +371,15 @@ func validatePluginJSON(data plugins.JSONData) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loader) pluginClass(pluginDir string) plugins.Class {
|
||||
isSubDir := func(base, target string) bool {
|
||||
path, err := filepath.Rel(base, target)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(path, "..") {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
corePluginsDir := filepath.Join(l.cfg.StaticRootPath, "app/plugins")
|
||||
if isSubDir(corePluginsDir, pluginDir) {
|
||||
return plugins.Core
|
||||
}
|
||||
|
||||
if isSubDir(l.cfg.BundledPluginsPath, pluginDir) {
|
||||
return plugins.Bundled
|
||||
}
|
||||
|
||||
return plugins.External
|
||||
}
|
||||
|
||||
type foundPlugins map[string]plugins.JSONData
|
||||
|
||||
// stripDuplicates will strip duplicate plugins or plugins that already exist
|
||||
func (f *foundPlugins) stripDuplicates(existingPlugins map[string]struct{}, log log.Logger) {
|
||||
pluginsByID := make(map[string]struct{})
|
||||
for path, scannedPlugin := range *f {
|
||||
for k, scannedPlugin := range *f {
|
||||
if _, existing := existingPlugins[scannedPlugin.ID]; existing {
|
||||
log.Debug("Skipping plugin as it's already installed", "plugin", scannedPlugin.ID)
|
||||
delete(*f, path)
|
||||
delete(*f, k)
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -1,19 +1,21 @@
|
||||
package loader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"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/provider"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/initializer"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
@ -35,16 +37,18 @@ func TestLoader_Load(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg *setting.Cfg
|
||||
class plugins.Class
|
||||
cfg *plugins.Cfg
|
||||
pluginPaths []string
|
||||
existingPlugins map[string]struct{}
|
||||
want []*plugins.Plugin
|
||||
pluginErrors map[string]*plugins.Error
|
||||
}{
|
||||
{
|
||||
name: "Load a Core plugin",
|
||||
cfg: &setting.Cfg{
|
||||
StaticRootPath: corePluginDir,
|
||||
name: "Load a Core plugin",
|
||||
class: plugins.Core,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: corePluginDir,
|
||||
},
|
||||
pluginPaths: []string{filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch")},
|
||||
want: []*plugins.Plugin{
|
||||
@ -86,15 +90,16 @@ func TestLoader_Load(t *testing.T) {
|
||||
Module: "app/plugins/datasource/cloudwatch/module",
|
||||
BaseURL: "public/app/plugins/datasource/cloudwatch",
|
||||
PluginDir: filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch"),
|
||||
Signature: "internal",
|
||||
Class: "core",
|
||||
Signature: plugins.SignatureInternal,
|
||||
Class: plugins.Core,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load a Bundled plugin",
|
||||
cfg: &setting.Cfg{
|
||||
BundledPluginsPath: filepath.Join(parentDir, "testdata"),
|
||||
name: "Load a Bundled plugin",
|
||||
class: plugins.Bundled,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir, "testdata"),
|
||||
},
|
||||
pluginPaths: []string{"../testdata/valid-v2-signature"},
|
||||
want: []*plugins.Plugin{
|
||||
@ -129,12 +134,13 @@ func TestLoader_Load(t *testing.T) {
|
||||
Signature: "valid",
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
Class: "bundled",
|
||||
Class: plugins.Bundled,
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Load an External plugin",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load plugin with symbolic links",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
},
|
||||
pluginPaths: []string{"../testdata/symbolic-plugin-dirs"},
|
||||
@ -210,10 +216,11 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Load an unsigned plugin (development)",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin (development)",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
DevMode: true,
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "development",
|
||||
},
|
||||
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
||||
want: []*plugins.Plugin{
|
||||
@ -248,10 +255,10 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "Load an unsigned plugin (production)",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin (production)",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "production",
|
||||
},
|
||||
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
||||
want: []*plugins.Plugin{},
|
||||
@ -263,10 +270,10 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an unsigned plugin using PluginsAllowUnsigned config (production)",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin using PluginsAllowUnsigned config (production)",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "production",
|
||||
PluginsAllowUnsigned: []string{"test"},
|
||||
},
|
||||
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
||||
@ -298,15 +305,15 @@ func TestLoader_Load(t *testing.T) {
|
||||
Module: "plugins/test/module",
|
||||
BaseURL: "public/plugins/test",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/unsigned-datasource/plugin"),
|
||||
Signature: "unsigned",
|
||||
Signature: plugins.SignatureUnsigned,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an unsigned plugin with modified signature (production)",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin with modified signature (production)",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "production",
|
||||
},
|
||||
pluginPaths: []string{"../testdata/lacking-files"},
|
||||
want: []*plugins.Plugin{},
|
||||
@ -318,10 +325,10 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an unsigned plugin with modified signature using PluginsAllowUnsigned config (production) still includes a signing error",
|
||||
cfg: &setting.Cfg{
|
||||
name: "Load an unsigned plugin with modified signature using PluginsAllowUnsigned config (production) still includes a signing error",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
Env: "production",
|
||||
PluginsAllowUnsigned: []string{"test"},
|
||||
},
|
||||
pluginPaths: []string{"../testdata/lacking-files"},
|
||||
@ -333,11 +340,61 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Load an app with includes",
|
||||
class: plugins.External,
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
PluginsAllowUnsigned: []string{"test-app"},
|
||||
},
|
||||
pluginPaths: []string{"../testdata/test-app-with-includes"},
|
||||
want: []*plugins.Plugin{
|
||||
{JSONData: plugins.JSONData{
|
||||
ID: "test-app",
|
||||
Type: "app",
|
||||
Name: "Test App",
|
||||
Info: plugins.Info{
|
||||
Author: plugins.InfoLink{
|
||||
Name: "Test Inc.",
|
||||
URL: "http://test.com",
|
||||
},
|
||||
Description: "Official Grafana Test App & Dashboard bundle",
|
||||
Version: "1.0.0",
|
||||
Links: []plugins.InfoLink{
|
||||
{Name: "Project site", URL: "http://project.com"},
|
||||
{Name: "License & Terms", URL: "http://license.com"},
|
||||
},
|
||||
Logos: plugins.Logos{
|
||||
Small: "public/img/icn-app.svg",
|
||||
Large: "public/img/icn-app.svg",
|
||||
},
|
||||
Updated: "2015-02-10",
|
||||
},
|
||||
Dependencies: plugins.Dependencies{
|
||||
GrafanaDependency: ">=8.0.0",
|
||||
GrafanaVersion: "*",
|
||||
Plugins: []plugins.Dependency{},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-memory", DefaultNav: true},
|
||||
{Name: "Root Page (react)", Type: "page", Role: "Viewer", Path: "/a/my-simple-app", DefaultNav: true, AddToNav: true, Slug: "root-page-react"},
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
DefaultNavURL: "/plugins/test-app/page/root-page-react",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/test-app-with-includes"),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureUnsigned,
|
||||
Module: "plugins/test-app/module",
|
||||
BaseURL: "public/plugins/test-app",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
l := newLoader(tt.cfg)
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := l.Load(tt.pluginPaths, tt.existingPlugins)
|
||||
got, err := l.Load(context.Background(), tt.class, tt.pluginPaths, tt.existingPlugins)
|
||||
require.NoError(t, err)
|
||||
if !cmp.Equal(got, tt.want, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts))
|
||||
@ -362,7 +419,7 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
t.Run("Load multiple", func(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg *setting.Cfg
|
||||
cfg *plugins.Cfg
|
||||
pluginPaths []string
|
||||
appURL string
|
||||
existingPlugins map[string]struct{}
|
||||
@ -371,8 +428,7 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
name: "Load multiple plugins (broken, valid, unsigned)",
|
||||
cfg: &setting.Cfg{
|
||||
Env: "production",
|
||||
cfg: &plugins.Cfg{
|
||||
PluginsPath: filepath.Join(parentDir),
|
||||
},
|
||||
appURL: "http://localhost:3000",
|
||||
@ -434,7 +490,7 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
})
|
||||
setting.AppUrl = tt.appURL
|
||||
|
||||
got, err := l.Load(tt.pluginPaths, tt.existingPlugins)
|
||||
got, err := l.Load(context.Background(), plugins.External, tt.pluginPaths, tt.existingPlugins)
|
||||
require.NoError(t, err)
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
@ -488,17 +544,17 @@ func TestLoader_Signature_RootURL(t *testing.T) {
|
||||
Executable: "test",
|
||||
},
|
||||
PluginDir: filepath.Join(parentDir, "/testdata/valid-v2-pvt-signature-root-url-uri/plugin"),
|
||||
Class: "external",
|
||||
Signature: "valid",
|
||||
SignatureType: "private",
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.PrivateSignature,
|
||||
SignatureOrg: "Will Browne",
|
||||
Module: "plugins/test/module",
|
||||
BaseURL: "public/plugins/test",
|
||||
},
|
||||
}
|
||||
|
||||
l := newLoader(&setting.Cfg{PluginsPath: filepath.Join(parentDir)})
|
||||
got, err := l.Load(paths, map[string]struct{}{})
|
||||
l := newLoader(&plugins.Cfg{PluginsPath: filepath.Join(parentDir)})
|
||||
got, err := l.Load(context.Background(), plugins.External, paths, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
@ -566,11 +622,11 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
l := newLoader(&setting.Cfg{
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: filepath.Dir(pluginDir),
|
||||
})
|
||||
|
||||
got, err := l.Load([]string{pluginDir, pluginDir}, map[string]struct{}{})
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{pluginDir, pluginDir}, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
@ -612,10 +668,10 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
Module: "plugins/test-ds/module",
|
||||
BaseURL: "public/plugins/test-ds",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/nested-plugins/parent"),
|
||||
Signature: "valid",
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
Class: "external",
|
||||
Class: plugins.External,
|
||||
}
|
||||
|
||||
child := &plugins.Plugin{
|
||||
@ -644,10 +700,10 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
Module: "plugins/test-panel/module",
|
||||
BaseURL: "public/plugins/test-panel",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/nested-plugins/parent/nested"),
|
||||
Signature: "valid",
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
Class: "external",
|
||||
Class: plugins.External,
|
||||
}
|
||||
|
||||
parent.Children = []*plugins.Plugin{child}
|
||||
@ -655,11 +711,11 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
|
||||
t.Run("Load nested External plugins", func(t *testing.T) {
|
||||
expected := []*plugins.Plugin{parent, child}
|
||||
l := newLoader(&setting.Cfg{
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: parentDir,
|
||||
})
|
||||
|
||||
got, err := l.Load([]string{"../testdata/nested-plugins"}, map[string]struct{}{})
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"}, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
@ -677,11 +733,11 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
parent.Children = nil
|
||||
expected := []*plugins.Plugin{parent}
|
||||
|
||||
l := newLoader(&setting.Cfg{
|
||||
l := newLoader(&plugins.Cfg{
|
||||
PluginsPath: parentDir,
|
||||
})
|
||||
|
||||
got, err := l.Load([]string{"../testdata/nested-plugins"}, map[string]struct{}{
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"}, map[string]struct{}{
|
||||
"test-panel": {},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
@ -740,10 +796,10 @@ func TestLoader_readPluginJSON(t *testing.T) {
|
||||
},
|
||||
},
|
||||
Includes: []*plugins.Includes{
|
||||
{Name: "Nginx Connections", Path: "dashboards/connections.json", Type: "dashboard"},
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard"},
|
||||
{Name: "Nginx Panel", Type: "panel"},
|
||||
{Name: "Nginx Datasource", Type: "datasource"},
|
||||
{Name: "Nginx Connections", Path: "dashboards/connections.json", Type: "dashboard", Role: models.ROLE_VIEWER},
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: models.ROLE_VIEWER},
|
||||
{Name: "Nginx Panel", Type: "panel", Role: models.ROLE_VIEWER},
|
||||
{Name: "Nginx Datasource", Type: "datasource", Role: models.ROLE_VIEWER},
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
@ -822,71 +878,33 @@ func Test_validatePluginJSON(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_pluginClass(t *testing.T) {
|
||||
type args struct {
|
||||
pluginDir string
|
||||
cfg *setting.Cfg
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
expected plugins.Class
|
||||
}{
|
||||
{
|
||||
name: "Core plugin class",
|
||||
args: args{
|
||||
pluginDir: "/root/app/plugins/test-app",
|
||||
cfg: &setting.Cfg{
|
||||
StaticRootPath: "/root",
|
||||
},
|
||||
func Test_setPathsBasedOnApp(t *testing.T) {
|
||||
t.Run("When setting paths based on core plugin on Windows", func(t *testing.T) {
|
||||
child := &plugins.Plugin{
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata\\datasources\\datasource",
|
||||
}
|
||||
parent := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: "testdata",
|
||||
},
|
||||
expected: plugins.Core,
|
||||
},
|
||||
{
|
||||
name: "Bundled plugin class",
|
||||
args: args{
|
||||
pluginDir: "/test-app",
|
||||
cfg: &setting.Cfg{
|
||||
BundledPluginsPath: "/test-app",
|
||||
},
|
||||
},
|
||||
expected: plugins.Bundled,
|
||||
},
|
||||
{
|
||||
name: "External plugin class",
|
||||
args: args{
|
||||
pluginDir: "/test-app",
|
||||
cfg: &setting.Cfg{
|
||||
PluginsPath: "/test-app",
|
||||
},
|
||||
},
|
||||
expected: plugins.External,
|
||||
},
|
||||
{
|
||||
name: "External plugin class",
|
||||
args: args{
|
||||
pluginDir: "/test-app",
|
||||
cfg: &setting.Cfg{
|
||||
PluginsPath: "/root",
|
||||
},
|
||||
},
|
||||
expected: plugins.External,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := newLoader(tt.args.cfg)
|
||||
got := l.pluginClass(tt.args.pluginDir)
|
||||
assert.Equal(t, tt.expected, got)
|
||||
})
|
||||
}
|
||||
Class: plugins.Core,
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata",
|
||||
BaseURL: "public/app/plugins/app/testdata",
|
||||
}
|
||||
|
||||
setChildModule(parent, child)
|
||||
|
||||
assert.Equal(t, "app/plugins/app/testdata/datasources/datasource/module", child.Module)
|
||||
assert.Equal(t, "testdata", child.IncludedInAppID)
|
||||
assert.Equal(t, "public/app/plugins/app/testdata", child.BaseURL)
|
||||
})
|
||||
}
|
||||
|
||||
func newLoader(cfg *setting.Cfg) *Loader {
|
||||
func newLoader(cfg *plugins.Cfg) *Loader {
|
||||
return &Loader{
|
||||
cfg: cfg,
|
||||
pluginFinder: finder.New(cfg),
|
||||
pluginInitializer: initializer.New(cfg, &fakeLicensingService{}),
|
||||
pluginFinder: finder.New(),
|
||||
pluginInitializer: initializer.New(cfg, &provider.Service{}, &fakeLicensingService{}),
|
||||
signatureValidator: signature.NewValidator(&signature.UnsignedPluginAuthorizer{Cfg: cfg}),
|
||||
errs: make(map[string]*plugins.SignatureError),
|
||||
log: &fakeLogger{},
|
||||
@ -934,6 +952,10 @@ type fakeLogger struct {
|
||||
log.Logger
|
||||
}
|
||||
|
||||
func (fl fakeLogger) New(_ ...interface{}) log.MultiLoggers {
|
||||
return log.MultiLoggers{}
|
||||
}
|
||||
|
||||
func (fl fakeLogger) Info(_ string, _ ...interface{}) {
|
||||
|
||||
}
|
||||
|
@ -5,14 +5,12 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/fs"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
@ -35,79 +33,52 @@ var _ plugins.StaticRouteResolver = (*PluginManager)(nil)
|
||||
var _ plugins.RendererManager = (*PluginManager)(nil)
|
||||
|
||||
type PluginManager struct {
|
||||
cfg *setting.Cfg
|
||||
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
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, requestValidator models.PluginRequestValidator, pluginLoader plugins.Loader,
|
||||
func ProvideService(grafanaCfg *setting.Cfg, requestValidator models.PluginRequestValidator, pluginLoader plugins.Loader,
|
||||
sqlStore *sqlstore.SQLStore) (*PluginManager, error) {
|
||||
pm := newManager(cfg, requestValidator, pluginLoader, sqlStore)
|
||||
if err := pm.init(); err != nil {
|
||||
pm := New(plugins.FromGrafanaCfg(grafanaCfg), requestValidator, map[plugins.Class][]string{
|
||||
plugins.Core: corePluginPaths(grafanaCfg),
|
||||
plugins.Bundled: {grafanaCfg.BundledPluginsPath},
|
||||
plugins.External: append([]string{grafanaCfg.PluginsPath}, pluginSettingPaths(grafanaCfg)...),
|
||||
}, pluginLoader, sqlStore)
|
||||
if err := pm.Init(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
func newManager(cfg *setting.Cfg, pluginRequestValidator models.PluginRequestValidator, pluginLoader plugins.Loader,
|
||||
sqlStore *sqlstore.SQLStore) *PluginManager {
|
||||
func New(cfg *plugins.Cfg, requestValidator models.PluginRequestValidator, pluginPaths map[plugins.Class][]string,
|
||||
pluginLoader plugins.Loader, sqlStore *sqlstore.SQLStore) *PluginManager {
|
||||
return &PluginManager{
|
||||
cfg: cfg,
|
||||
requestValidator: pluginRequestValidator,
|
||||
sqlStore: sqlStore,
|
||||
requestValidator: requestValidator,
|
||||
pluginLoader: pluginLoader,
|
||||
store: map[string]*plugins.Plugin{},
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *PluginManager) init() error {
|
||||
// create external plugin's path if not exists
|
||||
exists, err := fs.Exists(m.cfg.PluginsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !exists {
|
||||
if err = os.MkdirAll(m.cfg.PluginsPath, os.ModePerm); err != nil {
|
||||
m.log.Error("Failed to create external plugins directory", "dir", m.cfg.PluginsPath, "error", err)
|
||||
} else {
|
||||
m.log.Debug("External plugins directory created", "dir", m.cfg.PluginsPath)
|
||||
func (m *PluginManager) Init() error {
|
||||
for class, paths := range m.pluginPaths {
|
||||
err := m.loadPlugins(context.Background(), class, paths...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
m.log.Info("Initialising plugins")
|
||||
|
||||
// install Core plugins
|
||||
err = m.loadPlugins(m.corePluginPaths()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// install Bundled plugins
|
||||
err = m.loadPlugins(m.cfg.BundledPluginsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// install External plugins
|
||||
err = m.loadPlugins(m.cfg.PluginsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// install plugins from cfg.PluginSettings
|
||||
err = m.loadPlugins(m.pluginSettingPaths()...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -161,7 +132,7 @@ func (m *PluginManager) plugins() []*plugins.Plugin {
|
||||
return res
|
||||
}
|
||||
|
||||
func (m *PluginManager) loadPlugins(paths ...string) error {
|
||||
func (m *PluginManager) loadPlugins(ctx context.Context, class plugins.Class, paths ...string) error {
|
||||
if len(paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -173,7 +144,7 @@ func (m *PluginManager) loadPlugins(paths ...string) error {
|
||||
}
|
||||
}
|
||||
|
||||
loadedPlugins, err := m.pluginLoader.Load(pluginPaths, m.registeredPlugins())
|
||||
loadedPlugins, err := m.pluginLoader.Load(ctx, class, pluginPaths, m.registeredPlugins())
|
||||
if err != nil {
|
||||
m.log.Error("Could not load plugins", "paths", pluginPaths, "err", err)
|
||||
return err
|
||||
@ -499,24 +470,24 @@ func (m *PluginManager) shutdown(ctx context.Context) {
|
||||
}
|
||||
|
||||
// corePluginPaths provides a list of the Core plugin paths which need to be scanned on init()
|
||||
func (m *PluginManager) corePluginPaths() []string {
|
||||
func corePluginPaths(cfg *setting.Cfg) []string {
|
||||
datasourcePaths := []string{
|
||||
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/alertmanager"),
|
||||
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/dashboard"),
|
||||
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/jaeger"),
|
||||
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/mixed"),
|
||||
filepath.Join(m.cfg.StaticRootPath, "app/plugins/datasource/zipkin"),
|
||||
filepath.Join(cfg.StaticRootPath, "app/plugins/datasource/alertmanager"),
|
||||
filepath.Join(cfg.StaticRootPath, "app/plugins/datasource/dashboard"),
|
||||
filepath.Join(cfg.StaticRootPath, "app/plugins/datasource/jaeger"),
|
||||
filepath.Join(cfg.StaticRootPath, "app/plugins/datasource/mixed"),
|
||||
filepath.Join(cfg.StaticRootPath, "app/plugins/datasource/zipkin"),
|
||||
}
|
||||
|
||||
panelsPath := filepath.Join(m.cfg.StaticRootPath, "app/plugins/panel")
|
||||
panelsPath := filepath.Join(cfg.StaticRootPath, "app/plugins/panel")
|
||||
|
||||
return append(datasourcePaths, panelsPath)
|
||||
}
|
||||
|
||||
// pluginSettingPaths provides a plugin paths defined in cfg.PluginSettings which need to be scanned on init()
|
||||
func (m *PluginManager) pluginSettingPaths() []string {
|
||||
func pluginSettingPaths(cfg *setting.Cfg) []string {
|
||||
var pluginSettingDirs []string
|
||||
for _, settings := range m.cfg.PluginSettings {
|
||||
for _, settings := range cfg.PluginSettings {
|
||||
path, exists := settings["path"]
|
||||
if !exists || path == "" {
|
||||
continue
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/services/licensing"
|
||||
@ -42,9 +43,10 @@ func TestPluginManager_int_init(t *testing.T) {
|
||||
license := &licensing.OSSLicensingService{
|
||||
Cfg: cfg,
|
||||
}
|
||||
pm := newManager(cfg, nil, loader.New(license, cfg, &signature.UnsignedPluginAuthorizer{Cfg: cfg}), nil)
|
||||
|
||||
err = pm.init()
|
||||
pmCfg := plugins.FromGrafanaCfg(cfg)
|
||||
pm, err := ProvideService(cfg, nil, loader.New(pmCfg, license,
|
||||
&signature.UnsignedPluginAuthorizer{Cfg: pmCfg}, &provider.Service{}), nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
verifyCorePluginCatalogue(t, pm)
|
||||
|
@ -3,56 +3,24 @@ package manager
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/fs"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
const (
|
||||
testPluginID = "test-plugin"
|
||||
)
|
||||
|
||||
func TestPluginManager_init(t *testing.T) {
|
||||
t.Run("Plugin folder will be created if not exists", func(t *testing.T) {
|
||||
testDir := "plugin-test-dir"
|
||||
|
||||
exists, err := fs.Exists(testDir)
|
||||
require.NoError(t, err)
|
||||
assert.False(t, exists)
|
||||
|
||||
pm := createManager(t, func(pm *PluginManager) {
|
||||
pm.cfg.PluginsPath = testDir
|
||||
})
|
||||
|
||||
err = pm.init()
|
||||
require.NoError(t, err)
|
||||
|
||||
exists, err = fs.Exists(testDir)
|
||||
require.NoError(t, err)
|
||||
assert.True(t, exists)
|
||||
|
||||
t.Cleanup(func() {
|
||||
err = os.Remove(testDir)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestPluginManager_loadPlugins(t *testing.T) {
|
||||
t.Run("Managed backend plugin", func(t *testing.T) {
|
||||
p, pc := createPlugin(testPluginID, "", plugins.External, true, true)
|
||||
@ -64,7 +32,7 @@ func TestPluginManager_loadPlugins(t *testing.T) {
|
||||
pm := createManager(t, func(pm *PluginManager) {
|
||||
pm.pluginLoader = loader
|
||||
})
|
||||
err := pm.loadPlugins("test/path")
|
||||
err := pm.loadPlugins(context.Background(), plugins.External, "test/path")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, pc.startCount)
|
||||
@ -90,7 +58,7 @@ func TestPluginManager_loadPlugins(t *testing.T) {
|
||||
pm := createManager(t, func(pm *PluginManager) {
|
||||
pm.pluginLoader = loader
|
||||
})
|
||||
err := pm.loadPlugins("test/path")
|
||||
err := pm.loadPlugins(context.Background(), plugins.External, "test/path")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, pc.startCount)
|
||||
@ -116,7 +84,7 @@ func TestPluginManager_loadPlugins(t *testing.T) {
|
||||
pm := createManager(t, func(pm *PluginManager) {
|
||||
pm.pluginLoader = loader
|
||||
})
|
||||
err := pm.loadPlugins("test/path")
|
||||
err := pm.loadPlugins(context.Background(), plugins.External, "test/path")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, pc.startCount)
|
||||
@ -142,7 +110,7 @@ func TestPluginManager_loadPlugins(t *testing.T) {
|
||||
pm := createManager(t, func(pm *PluginManager) {
|
||||
pm.pluginLoader = loader
|
||||
})
|
||||
err := pm.loadPlugins("test/path")
|
||||
err := pm.loadPlugins(context.Background(), plugins.External, "test/path")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 0, pc.startCount)
|
||||
@ -257,7 +225,7 @@ func TestPluginManager_Installer(t *testing.T) {
|
||||
pm := createManager(t, func(pm *PluginManager) {
|
||||
pm.pluginLoader = loader
|
||||
})
|
||||
err := pm.loadPlugins("test/path")
|
||||
err := pm.loadPlugins(context.Background(), plugins.Core, "test/path")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, pc.startCount)
|
||||
@ -291,7 +259,7 @@ func TestPluginManager_Installer(t *testing.T) {
|
||||
pm := createManager(t, func(pm *PluginManager) {
|
||||
pm.pluginLoader = loader
|
||||
})
|
||||
err := pm.loadPlugins("test/path")
|
||||
err := pm.loadPlugins(context.Background(), plugins.Bundled, "test/path")
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, 1, pc.startCount)
|
||||
@ -499,18 +467,8 @@ func TestPluginManager_lifecycle_unmanaged(t *testing.T) {
|
||||
func createManager(t *testing.T, cbs ...func(*PluginManager)) *PluginManager {
|
||||
t.Helper()
|
||||
|
||||
staticRootPath, err := filepath.Abs("../../../public/")
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &setting.Cfg{
|
||||
Raw: ini.Empty(),
|
||||
Env: setting.Prod,
|
||||
StaticRootPath: staticRootPath,
|
||||
}
|
||||
|
||||
requestValidator := &testPluginRequestValidator{}
|
||||
loader := &fakeLoader{}
|
||||
pm := newManager(cfg, requestValidator, loader, &sqlstore.SQLStore{})
|
||||
pm := New(&plugins.Cfg{}, requestValidator, nil, &fakeLoader{}, &sqlstore.SQLStore{})
|
||||
|
||||
for _, cb := range cbs {
|
||||
cb(pm)
|
||||
@ -555,7 +513,7 @@ type managerScenarioCtx struct {
|
||||
|
||||
func newScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerScenarioCtx)) {
|
||||
t.Helper()
|
||||
cfg := setting.NewCfg()
|
||||
cfg := &plugins.Cfg{}
|
||||
cfg.AWSAllowedAuthProviders = []string{"keys", "credentials"}
|
||||
cfg.AWSAssumeRoleEnabled = true
|
||||
|
||||
@ -563,13 +521,9 @@ func newScenario(t *testing.T, managed bool, fn func(t *testing.T, ctx *managerS
|
||||
cfg.Azure.Cloud = "AzureCloud"
|
||||
cfg.Azure.ManagedIdentityClientId = "client-id"
|
||||
|
||||
staticRootPath, err := filepath.Abs("../../../public")
|
||||
require.NoError(t, err)
|
||||
cfg.StaticRootPath = staticRootPath
|
||||
|
||||
requestValidator := &testPluginRequestValidator{}
|
||||
loader := &fakeLoader{}
|
||||
manager := newManager(cfg, requestValidator, loader, nil)
|
||||
manager := New(cfg, requestValidator, nil, loader, nil)
|
||||
manager.pluginLoader = loader
|
||||
ctx := &managerScenarioCtx{
|
||||
manager: manager,
|
||||
@ -616,13 +570,13 @@ type fakeLoader struct {
|
||||
plugins.Loader
|
||||
}
|
||||
|
||||
func (l *fakeLoader) Load(paths []string, _ map[string]struct{}) ([]*plugins.Plugin, error) {
|
||||
func (l *fakeLoader) Load(_ context.Context, _ plugins.Class, paths []string, _ map[string]struct{}) ([]*plugins.Plugin, error) {
|
||||
l.loadedPaths = append(l.loadedPaths, paths...)
|
||||
|
||||
return l.mockedLoadedPlugins, nil
|
||||
}
|
||||
|
||||
func (l *fakeLoader) LoadWithFactory(path string, _ backendplugin.PluginFactoryFunc) (*plugins.Plugin, error) {
|
||||
func (l *fakeLoader) LoadWithFactory(_ context.Context, _ plugins.Class, path string, _ backendplugin.PluginFactoryFunc) (*plugins.Plugin, error) {
|
||||
l.loadedPaths = append(l.loadedPaths, path)
|
||||
|
||||
return l.mockedFactoryLoadedPlugin, nil
|
||||
|
@ -7,12 +7,12 @@ import (
|
||||
|
||||
func ProvideService(cfg *setting.Cfg) (*UnsignedPluginAuthorizer, error) {
|
||||
return &UnsignedPluginAuthorizer{
|
||||
Cfg: cfg,
|
||||
Cfg: plugins.FromGrafanaCfg(cfg),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type UnsignedPluginAuthorizer struct {
|
||||
Cfg *setting.Cfg
|
||||
Cfg *plugins.Cfg
|
||||
}
|
||||
|
||||
func (u *UnsignedPluginAuthorizer) CanLoadPlugin(p *plugins.Plugin) bool {
|
||||
@ -20,7 +20,7 @@ func (u *UnsignedPluginAuthorizer) CanLoadPlugin(p *plugins.Plugin) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if u.Cfg.Env == setting.Dev {
|
||||
if u.Cfg.DevMode {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -75,7 +75,7 @@ func (m *PluginManager) Add(ctx context.Context, pluginID, version string) error
|
||||
return err
|
||||
}
|
||||
|
||||
err = m.loadPlugins(m.cfg.PluginsPath)
|
||||
err = m.loadPlugins(context.Background(), plugins.External, m.cfg.PluginsPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -83,7 +83,7 @@ func (m *PluginManager) Add(ctx context.Context, pluginID, version string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PluginManager) AddWithFactory(_ context.Context, pluginID string, factory backendplugin.PluginFactoryFunc,
|
||||
func (m *PluginManager) AddWithFactory(ctx context.Context, pluginID string, factory backendplugin.PluginFactoryFunc,
|
||||
pathResolver plugins.PluginPathResolver) error {
|
||||
if m.isRegistered(pluginID) {
|
||||
return fmt.Errorf("plugin %s is already registered", pluginID)
|
||||
@ -94,7 +94,7 @@ func (m *PluginManager) AddWithFactory(_ context.Context, pluginID string, facto
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := m.pluginLoader.LoadWithFactory(path, factory)
|
||||
p, err := m.pluginLoader.LoadWithFactory(ctx, plugins.Core, path, factory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
5
pkg/plugins/manager/testdata/test-app-with-includes/dashboards/memory.json
vendored
Normal file
5
pkg/plugins/manager/testdata/test-app-with-includes/dashboards/memory.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"title": "Nginx Memory",
|
||||
"revision": 2,
|
||||
"schemaVersion": 11
|
||||
}
|
38
pkg/plugins/manager/testdata/test-app-with-includes/plugin.json
vendored
Normal file
38
pkg/plugins/manager/testdata/test-app-with-includes/plugin.json
vendored
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"type": "app",
|
||||
"name": "Test App",
|
||||
"id": "test-app",
|
||||
"info": {
|
||||
"description": "Official Grafana Test App & Dashboard bundle",
|
||||
"author": {
|
||||
"name": "Test Inc.",
|
||||
"url": "http://test.com"
|
||||
},
|
||||
"keywords": ["test"],
|
||||
"links": [
|
||||
{"name": "Project site", "url": "http://project.com"},
|
||||
{"name": "License & Terms", "url": "http://license.com"}
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"updated": "2015-02-10"
|
||||
},
|
||||
"includes": [
|
||||
{
|
||||
"type": "dashboard",
|
||||
"name": "Nginx Memory",
|
||||
"path": "dashboards/memory.json",
|
||||
"defaultNav": true
|
||||
},
|
||||
{
|
||||
"type": "page",
|
||||
"name": "Root Page (react)",
|
||||
"path": "/a/my-simple-app",
|
||||
"role": "Viewer",
|
||||
"addToNav": true,
|
||||
"defaultNav": true
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"grafanaDependency": ">=8.0.0"
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/google/wire"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/server/backgroundsvcs"
|
||||
@ -61,6 +62,8 @@ var wireExtsBasicSet = wire.NewSet(
|
||||
wire.Bind(new(searchusers.Service), new(*searchusers.OSSService)),
|
||||
signature.ProvideService,
|
||||
wire.Bind(new(plugins.PluginLoaderAuthorizer), new(*signature.UnsignedPluginAuthorizer)),
|
||||
provider.ProvideService,
|
||||
wire.Bind(new(plugins.BackendFactoryProvider), new(*provider.Service)),
|
||||
acdb.ProvideService,
|
||||
wire.Bind(new(accesscontrol.ResourcePermissionsStore), new(*acdb.AccessControlStore)),
|
||||
wire.Bind(new(accesscontrol.PermissionsProvider), new(*acdb.AccessControlStore)),
|
||||
|
Loading…
Reference in New Issue
Block a user