Plugins: Create single point of entry for adding / removing plugins (#55463)
* split out plugin manager * remove whitespace * fix tests * split up tests * updating naming conventions * simplify manager * tidy * explorations * fix build * tidy * fix tests * add logger helper * pass the tests * tidying * fix tests * tidy and re-add test * store depends on loader * enrich tests * fix test * undo gomod changes
This commit is contained in:
@@ -9,5 +9,7 @@ import (
|
||||
// Service is responsible for loading plugins from the file system.
|
||||
type Service interface {
|
||||
// Load will return a list of plugins found in the provided file system paths.
|
||||
Load(ctx context.Context, class plugins.Class, paths []string, ignore map[string]struct{}) ([]*plugins.Plugin, error)
|
||||
Load(ctx context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error)
|
||||
// Unload will unload a specified plugin from the file system.
|
||||
Unload(ctx context.Context, pluginID string) error
|
||||
}
|
||||
|
||||
@@ -20,9 +20,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/config"
|
||||
"github.com/grafana/grafana/pkg/plugins/logger"
|
||||
"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/process"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/plugins/storage"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@@ -36,39 +40,47 @@ var _ plugins.ErrorResolver = (*Loader)(nil)
|
||||
|
||||
type Loader struct {
|
||||
pluginFinder finder.Finder
|
||||
processManager process.Service
|
||||
pluginRegistry registry.Service
|
||||
pluginInitializer initializer.Initializer
|
||||
signatureValidator signature.Validator
|
||||
pluginStorage storage.Manager
|
||||
log log.Logger
|
||||
|
||||
errs map[string]*plugins.SignatureError
|
||||
}
|
||||
|
||||
func ProvideService(cfg *config.Cfg, license models.Licensing, authorizer plugins.PluginLoaderAuthorizer,
|
||||
backendProvider plugins.BackendFactoryProvider) (*Loader, error) {
|
||||
return New(cfg, license, authorizer, backendProvider), nil
|
||||
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider) *Loader {
|
||||
return New(cfg, license, authorizer, pluginRegistry, backendProvider, process.NewManager(pluginRegistry),
|
||||
storage.FileSystem(logger.NewLogger("loader.fs"), cfg.PluginsPath))
|
||||
}
|
||||
|
||||
func New(cfg *config.Cfg, license models.Licensing, authorizer plugins.PluginLoaderAuthorizer,
|
||||
backendProvider plugins.BackendFactoryProvider) *Loader {
|
||||
pluginRegistry registry.Service, backendProvider plugins.BackendFactoryProvider,
|
||||
processManager process.Service, pluginStorage storage.Manager) *Loader {
|
||||
return &Loader{
|
||||
pluginFinder: finder.New(),
|
||||
pluginRegistry: pluginRegistry,
|
||||
pluginInitializer: initializer.New(cfg, backendProvider, license),
|
||||
signatureValidator: signature.NewValidator(authorizer),
|
||||
processManager: processManager,
|
||||
pluginStorage: pluginStorage,
|
||||
errs: make(map[string]*plugins.SignatureError),
|
||||
log: log.New("plugin.loader"),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Loader) Load(ctx context.Context, class plugins.Class, paths []string, ignore map[string]struct{}) ([]*plugins.Plugin, error) {
|
||||
func (l *Loader) Load(ctx context.Context, class plugins.Class, paths []string) ([]*plugins.Plugin, error) {
|
||||
pluginJSONPaths, err := l.pluginFinder.Find(paths)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return l.loadPlugins(ctx, class, pluginJSONPaths, ignore)
|
||||
return l.loadPlugins(ctx, class, pluginJSONPaths)
|
||||
}
|
||||
|
||||
func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, pluginJSONPaths []string, existingPlugins map[string]struct{}) ([]*plugins.Plugin, error) {
|
||||
func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, pluginJSONPaths []string) ([]*plugins.Plugin, error) {
|
||||
var foundPlugins = foundPlugins{}
|
||||
|
||||
// load plugin.json files and map directory to JSON data
|
||||
@@ -92,12 +104,18 @@ func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, pluginJSO
|
||||
foundPlugins[filepath.Dir(pluginJSONAbsPath)] = plugin
|
||||
}
|
||||
|
||||
foundPlugins.stripDuplicates(existingPlugins, l.log)
|
||||
// get all registered plugins
|
||||
registeredPlugins := make(map[string]struct{})
|
||||
for _, p := range l.pluginRegistry.Plugins(ctx) {
|
||||
registeredPlugins[p.ID] = struct{}{}
|
||||
}
|
||||
|
||||
foundPlugins.stripDuplicates(registeredPlugins, l.log)
|
||||
|
||||
// calculate initial signature state
|
||||
loadedPlugins := make(map[string]*plugins.Plugin)
|
||||
for pluginDir, pluginJSON := range foundPlugins {
|
||||
plugin := createPluginBase(pluginJSON, class, pluginDir, l.log)
|
||||
plugin := createPluginBase(pluginJSON, class, pluginDir)
|
||||
|
||||
sig, err := signature.Calculate(l.log, plugin)
|
||||
if err != nil {
|
||||
@@ -179,9 +197,68 @@ func (l *Loader) loadPlugins(ctx context.Context, class plugins.Class, pluginJSO
|
||||
metrics.SetPluginBuildInformation(p.ID, string(p.Type), p.Info.Version, string(p.Signature))
|
||||
}
|
||||
|
||||
for _, p := range verifiedPlugins {
|
||||
if err := l.load(ctx, p); err != nil {
|
||||
l.log.Error("Could not start plugin", "pluginId", p.ID, "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
return verifiedPlugins, nil
|
||||
}
|
||||
|
||||
func (l *Loader) Unload(ctx context.Context, pluginID string) error {
|
||||
plugin, exists := l.pluginRegistry.Plugin(ctx, pluginID)
|
||||
if !exists {
|
||||
return plugins.ErrPluginNotInstalled
|
||||
}
|
||||
|
||||
if !plugin.IsExternalPlugin() {
|
||||
return plugins.ErrUninstallCorePlugin
|
||||
}
|
||||
|
||||
if err := l.unload(ctx, plugin); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loader) load(ctx context.Context, p *plugins.Plugin) error {
|
||||
if err := l.pluginRegistry.Add(ctx, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !p.IsCorePlugin() {
|
||||
l.log.Info("Plugin registered", "pluginID", p.ID)
|
||||
}
|
||||
|
||||
if p.IsExternalPlugin() {
|
||||
if err := l.pluginStorage.Register(ctx, p.ID, p.PluginDir); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return l.processManager.Start(ctx, p.ID)
|
||||
}
|
||||
|
||||
func (l *Loader) unload(ctx context.Context, p *plugins.Plugin) error {
|
||||
l.log.Debug("Stopping plugin process", "pluginId", p.ID)
|
||||
|
||||
// TODO confirm the sequence of events is safe
|
||||
if err := l.processManager.Stop(ctx, p.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := l.pluginRegistry.Remove(ctx, p.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
l.log.Debug("Plugin unregistered", "pluginId", p.ID)
|
||||
|
||||
if err := l.pluginStorage.Remove(ctx, p.ID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *Loader) readPluginJSON(pluginJSONPath string) (plugins.JSONData, error) {
|
||||
l.log.Debug("Loading plugin", "path", pluginJSONPath)
|
||||
|
||||
@@ -198,15 +275,15 @@ func (l *Loader) readPluginJSON(pluginJSONPath string) (plugins.JSONData, error)
|
||||
}
|
||||
|
||||
plugin := plugins.JSONData{}
|
||||
if err := json.NewDecoder(reader).Decode(&plugin); err != nil {
|
||||
if err = json.NewDecoder(reader).Decode(&plugin); err != nil {
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
|
||||
if err := reader.Close(); err != nil {
|
||||
if err = reader.Close(); err != nil {
|
||||
l.log.Warn("Failed to close JSON file", "path", pluginJSONPath, "err", err)
|
||||
}
|
||||
|
||||
if err := validatePluginJSON(plugin); err != nil {
|
||||
if err = validatePluginJSON(plugin); err != nil {
|
||||
return plugins.JSONData{}, err
|
||||
}
|
||||
|
||||
@@ -231,7 +308,7 @@ func (l *Loader) readPluginJSON(pluginJSONPath string) (plugins.JSONData, error)
|
||||
return plugin, nil
|
||||
}
|
||||
|
||||
func createPluginBase(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string, logger log.Logger) *plugins.Plugin {
|
||||
func createPluginBase(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string) *plugins.Plugin {
|
||||
plugin := &plugins.Plugin{
|
||||
JSONData: pluginJSON,
|
||||
PluginDir: pluginDir,
|
||||
@@ -342,7 +419,6 @@ func baseURL(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string)
|
||||
if class == plugins.Core {
|
||||
return path.Join("public/app/plugins", string(pluginJSON.Type), filepath.Base(pluginDir))
|
||||
}
|
||||
|
||||
return path.Join("public/plugins", pluginJSON.ID)
|
||||
}
|
||||
|
||||
@@ -350,7 +426,6 @@ func module(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string)
|
||||
if class == plugins.Core {
|
||||
return path.Join("app/plugins", string(pluginJSON.Type), filepath.Base(pluginDir), "module")
|
||||
}
|
||||
|
||||
return path.Join("plugins", pluginJSON.ID, "module")
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,12 @@ import (
|
||||
|
||||
"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/logtest"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/coreplugin"
|
||||
"github.com/grafana/grafana/pkg/plugins/backendplugin/provider"
|
||||
"github.com/grafana/grafana/pkg/plugins/config"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/initializer"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
@@ -39,13 +35,12 @@ func TestLoader_Load(t *testing.T) {
|
||||
return
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
class plugins.Class
|
||||
cfg *config.Cfg
|
||||
pluginPaths []string
|
||||
existingPlugins map[string]struct{}
|
||||
want []*plugins.Plugin
|
||||
pluginErrors map[string]*plugins.Error
|
||||
name string
|
||||
class plugins.Class
|
||||
cfg *config.Cfg
|
||||
pluginPaths []string
|
||||
want []*plugins.Plugin
|
||||
pluginErrors map[string]*plugins.Error
|
||||
}{
|
||||
{
|
||||
name: "Load a Core plugin",
|
||||
@@ -411,19 +406,31 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
l := newLoader(tt.cfg)
|
||||
reg := fakes.NewFakePluginRegistry()
|
||||
storage := fakes.NewFakePluginStorage()
|
||||
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
||||
procMgr := fakes.NewFakeProcessManager()
|
||||
l := newLoader(tt.cfg, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(tt.cfg, procPrvdr, &fakes.FakeLicensingService{})
|
||||
})
|
||||
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := l.Load(context.Background(), tt.class, tt.pluginPaths, tt.existingPlugins)
|
||||
got, err := l.Load(context.Background(), tt.class, tt.pluginPaths)
|
||||
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))
|
||||
}
|
||||
|
||||
pluginErrs := l.PluginErrors()
|
||||
assert.Equal(t, len(tt.pluginErrors), len(pluginErrs))
|
||||
require.Equal(t, len(tt.pluginErrors), len(pluginErrs))
|
||||
for _, pluginErr := range pluginErrs {
|
||||
assert.Equal(t, tt.pluginErrors[pluginErr.PluginID], pluginErr)
|
||||
require.Equal(t, tt.pluginErrors[pluginErr.PluginID], pluginErr)
|
||||
}
|
||||
|
||||
verifyState(t, tt.want, reg, procPrvdr, storage, procMgr)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -554,7 +561,16 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
l := newLoader(tt.cfg)
|
||||
reg := fakes.NewFakePluginRegistry()
|
||||
storage := fakes.NewFakePluginStorage()
|
||||
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
||||
procMgr := fakes.NewFakeProcessManager()
|
||||
l := newLoader(tt.cfg, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(tt.cfg, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
origAppURL := setting.AppUrl
|
||||
t.Cleanup(func() {
|
||||
@@ -562,7 +578,7 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
})
|
||||
setting.AppUrl = tt.appURL
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, tt.pluginPaths, tt.existingPlugins)
|
||||
got, err := l.Load(context.Background(), plugins.External, tt.pluginPaths)
|
||||
require.NoError(t, err)
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
@@ -575,12 +591,13 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
for _, pluginErr := range pluginErrs {
|
||||
require.Equal(t, tt.pluginErrors[pluginErr.PluginID], pluginErr)
|
||||
}
|
||||
verifyState(t, tt.want, reg, procPrvdr, storage, procMgr)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_Signature_RootURL(t *testing.T) {
|
||||
func TestLoader_Load_Signature_RootURL(t *testing.T) {
|
||||
const defaultAppURL = "http://localhost:3000/grafana"
|
||||
|
||||
parentDir, err := filepath.Abs("../")
|
||||
@@ -630,13 +647,23 @@ func TestLoader_Signature_RootURL(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
l := newLoader(&config.Cfg{})
|
||||
got, err := l.Load(context.Background(), plugins.External, paths, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
reg := fakes.NewFakePluginRegistry()
|
||||
storage := fakes.NewFakePluginStorage()
|
||||
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
||||
procMgr := fakes.NewFakeProcessManager()
|
||||
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err := l.Load(context.Background(), plugins.External, paths)
|
||||
require.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -699,18 +726,28 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
l := newLoader(&config.Cfg{})
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{pluginDir, pluginDir}, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
reg := fakes.NewFakePluginRegistry()
|
||||
storage := fakes.NewFakePluginStorage()
|
||||
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
||||
procMgr := fakes.NewFakeProcessManager()
|
||||
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{pluginDir, pluginDir})
|
||||
require.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
rootDir, err := filepath.Abs("../")
|
||||
if err != nil {
|
||||
t.Errorf("could not construct absolute path of root dir")
|
||||
@@ -785,42 +822,47 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
child.Parent = parent
|
||||
|
||||
t.Run("Load nested External plugins", func(t *testing.T) {
|
||||
reg := fakes.NewFakePluginRegistry()
|
||||
storage := fakes.NewFakePluginStorage()
|
||||
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
||||
procMgr := fakes.NewFakeProcessManager()
|
||||
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
})
|
||||
|
||||
expected := []*plugins.Plugin{parent, child}
|
||||
l := newLoader(&config.Cfg{})
|
||||
|
||||
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
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
})
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Load will exclude plugins that already exist", func(t *testing.T) {
|
||||
// parent/child links will not be created when either plugins are provided in the existingPlugins map
|
||||
parent.Children = nil
|
||||
expected := []*plugins.Plugin{parent}
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
|
||||
l := newLoader(&config.Cfg{})
|
||||
t.Run("Load will exclude plugins that already exist", func(t *testing.T) {
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"})
|
||||
require.NoError(t, err)
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/nested-plugins"}, map[string]struct{}{
|
||||
"test-panel": {},
|
||||
// to ensure we can compare with expected
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
})
|
||||
|
||||
if !cmp.Equal(got, []*plugins.Plugin{}, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
return got[i].ID < got[j].ID
|
||||
})
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Plugin child field `IncludedInAppID` is set to parent app's plugin ID", func(t *testing.T) {
|
||||
@@ -944,12 +986,20 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
|
||||
parent.Children = []*plugins.Plugin{child}
|
||||
child.Parent = parent
|
||||
|
||||
expected := []*plugins.Plugin{parent, child}
|
||||
l := newLoader(&config.Cfg{})
|
||||
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/app-with-child"}, map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
reg := fakes.NewFakePluginRegistry()
|
||||
storage := fakes.NewFakePluginStorage()
|
||||
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
||||
procMgr := fakes.NewFakeProcessManager()
|
||||
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{"../testdata/app-with-child"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
@@ -960,14 +1010,24 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
|
||||
t.Run("order of loaded parent and child plugins gives same output", func(t *testing.T) {
|
||||
parentPluginJSON := filepath.Join(rootDir, "testdata/app-with-child/dist/plugin.json")
|
||||
childPluginJSON := filepath.Join(rootDir, "testdata/app-with-child/dist/child/plugin.json")
|
||||
|
||||
got, err := l.loadPlugins(context.Background(), plugins.External, []string{
|
||||
parentPluginJSON, childPluginJSON},
|
||||
map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
reg = fakes.NewFakePluginRegistry()
|
||||
storage = fakes.NewFakePluginStorage()
|
||||
procPrvdr = fakes.NewFakeBackendProcessProvider()
|
||||
procMgr = fakes.NewFakeProcessManager()
|
||||
l = newLoader(&config.Cfg{}, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err = l.loadPlugins(context.Background(), plugins.External, []string{parentPluginJSON, childPluginJSON})
|
||||
require.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
@@ -978,10 +1038,20 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
|
||||
got, err = l.loadPlugins(context.Background(), plugins.External, []string{
|
||||
childPluginJSON, parentPluginJSON},
|
||||
map[string]struct{}{})
|
||||
assert.NoError(t, err)
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
|
||||
reg = fakes.NewFakePluginRegistry()
|
||||
storage = fakes.NewFakePluginStorage()
|
||||
procPrvdr = fakes.NewFakeBackendProcessProvider()
|
||||
procMgr = fakes.NewFakeProcessManager()
|
||||
l = newLoader(&config.Cfg{}, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err = l.loadPlugins(context.Background(), plugins.External, []string{childPluginJSON, parentPluginJSON})
|
||||
require.NoError(t, err)
|
||||
|
||||
// to ensure we can compare with expected
|
||||
sort.SliceStable(got, func(i, j int) bool {
|
||||
@@ -991,6 +1061,8 @@ func TestLoader_loadNestedPlugins(t *testing.T) {
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
}
|
||||
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -1137,55 +1209,48 @@ func Test_setPathsBasedOnApp(t *testing.T) {
|
||||
|
||||
configureAppChildOPlugin(parent, child)
|
||||
|
||||
assert.Equal(t, "app/plugins/app/testdata-app/datasources/datasource/module", child.Module)
|
||||
assert.Equal(t, "testdata-app", child.IncludedInAppID)
|
||||
assert.Equal(t, "public/app/plugins/app/testdata-app", child.BaseURL)
|
||||
require.Equal(t, "app/plugins/app/testdata-app/datasources/datasource/module", child.Module)
|
||||
require.Equal(t, "testdata-app", child.IncludedInAppID)
|
||||
require.Equal(t, "public/app/plugins/app/testdata-app", child.BaseURL)
|
||||
})
|
||||
}
|
||||
|
||||
func newLoader(cfg *config.Cfg) *Loader {
|
||||
return &Loader{
|
||||
pluginFinder: finder.New(),
|
||||
pluginInitializer: initializer.New(cfg, provider.ProvideService(coreplugin.NewRegistry(make(map[string]backendplugin.PluginFactoryFunc))), &fakeLicensingService{}),
|
||||
signatureValidator: signature.NewValidator(signature.NewUnsignedAuthorizer(cfg)),
|
||||
errs: make(map[string]*plugins.SignatureError),
|
||||
log: &logtest.Fake{},
|
||||
func newLoader(cfg *config.Cfg, cbs ...func(loader *Loader)) *Loader {
|
||||
l := New(cfg, &fakes.FakeLicensingService{}, signature.NewUnsignedAuthorizer(cfg), fakes.NewFakePluginRegistry(),
|
||||
fakes.NewFakeBackendProcessProvider(), fakes.NewFakeProcessManager(), fakes.NewFakePluginStorage())
|
||||
|
||||
for _, cb := range cbs {
|
||||
cb(l)
|
||||
}
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func verifyState(t *testing.T, ps []*plugins.Plugin, reg *fakes.FakePluginRegistry,
|
||||
procPrvdr *fakes.FakeBackendProcessProvider, storage *fakes.FakePluginStorage, procMngr *fakes.FakeProcessManager) {
|
||||
t.Helper()
|
||||
|
||||
for _, p := range ps {
|
||||
if !cmp.Equal(p, reg.Store[p.ID], compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(p, reg.Store[p.ID], compareOpts))
|
||||
}
|
||||
|
||||
if p.Backend {
|
||||
require.Equal(t, 1, procPrvdr.Requested[p.ID])
|
||||
require.Equal(t, 1, procPrvdr.Invoked[p.ID])
|
||||
} else {
|
||||
require.Zero(t, procPrvdr.Requested[p.ID])
|
||||
require.Zero(t, procPrvdr.Invoked[p.ID])
|
||||
}
|
||||
|
||||
_, exists := storage.Store[p.ID]
|
||||
if p.IsExternalPlugin() {
|
||||
require.True(t, exists)
|
||||
} else {
|
||||
require.False(t, exists)
|
||||
}
|
||||
|
||||
require.Equal(t, 1, procMngr.Started[p.ID])
|
||||
require.Zero(t, procMngr.Stopped[p.ID])
|
||||
}
|
||||
}
|
||||
|
||||
type fakeLicensingService struct {
|
||||
edition string
|
||||
tokenRaw string
|
||||
}
|
||||
|
||||
func (t *fakeLicensingService) Expiry() int64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
func (t *fakeLicensingService) Edition() string {
|
||||
return t.edition
|
||||
}
|
||||
|
||||
func (t *fakeLicensingService) StateInfo() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *fakeLicensingService) ContentDeliveryPrefix() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *fakeLicensingService) LicenseURL(_ bool) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t *fakeLicensingService) Environment() map[string]string {
|
||||
return map[string]string{"GF_ENTERPRISE_LICENSE_TEXT": t.tokenRaw}
|
||||
}
|
||||
|
||||
func (*fakeLicensingService) EnabledFeatures() map[string]bool {
|
||||
return map[string]bool{}
|
||||
}
|
||||
|
||||
func (*fakeLicensingService) FeatureEnabled(feature string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user