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:
Will Browne
2022-09-23 14:27:01 +02:00
committed by GitHub
parent 003a1cdaa0
commit d0d8544ded
21 changed files with 785 additions and 622 deletions

View File

@ -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")
}