Add support for synchronous plugin installation (#92129)
This commit is contained in:
@@ -3,8 +3,10 @@ package plugininstaller
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
"cuelang.org/go/pkg/time"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
@@ -18,29 +20,57 @@ type Service struct {
|
||||
log log.Logger
|
||||
pluginInstaller plugins.Installer
|
||||
pluginStore pluginstore.Store
|
||||
failOnErr bool
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, pluginStore pluginstore.Store, pluginInstaller plugins.Installer) *Service {
|
||||
func ProvideService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, pluginStore pluginstore.Store, pluginInstaller plugins.Installer) (*Service, error) {
|
||||
s := &Service{
|
||||
features: features,
|
||||
log: log.New("plugin.backgroundinstaller"),
|
||||
cfg: cfg,
|
||||
pluginInstaller: pluginInstaller,
|
||||
pluginStore: pluginStore,
|
||||
failOnErr: !cfg.PreinstallPluginsAsync, // Fail on error if preinstall is synchronous
|
||||
}
|
||||
return s
|
||||
if !cfg.PreinstallPluginsAsync {
|
||||
// Block initialization process until plugins are installed
|
||||
err := s.installPluginsWithTimeout()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// IsDisabled disables background installation of plugins.
|
||||
func (s *Service) IsDisabled() bool {
|
||||
return !s.features.IsEnabled(context.Background(), featuremgmt.FlagBackgroundPluginInstaller) ||
|
||||
len(s.cfg.InstallPlugins) == 0
|
||||
len(s.cfg.PreinstallPlugins) == 0 ||
|
||||
!s.cfg.PreinstallPluginsAsync
|
||||
}
|
||||
|
||||
func (s *Service) Run(ctx context.Context) error {
|
||||
func (s *Service) installPluginsWithTimeout() error {
|
||||
// Installation process does not timeout by default nor reuses the context
|
||||
// passed to the request so we need to handle the timeout here.
|
||||
// We could make this timeout configurable in the future.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||
defer cancel()
|
||||
done := make(chan struct{ err error })
|
||||
go func() {
|
||||
done <- struct{ err error }{err: s.installPlugins(ctx)}
|
||||
}()
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return fmt.Errorf("failed to install plugins: %w", ctx.Err())
|
||||
case d := <-done:
|
||||
return d.err
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) installPlugins(ctx context.Context) error {
|
||||
compatOpts := plugins.NewCompatOpts(s.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH)
|
||||
|
||||
for _, installPlugin := range s.cfg.InstallPlugins {
|
||||
for _, installPlugin := range s.cfg.PreinstallPlugins {
|
||||
// Check if the plugin is already installed
|
||||
p, exists := s.pluginStore.Plugin(ctx, installPlugin.ID)
|
||||
if exists {
|
||||
@@ -59,6 +89,10 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
s.log.Debug("Plugin already installed", "pluginId", installPlugin.ID, "version", installPlugin.Version)
|
||||
continue
|
||||
}
|
||||
if s.failOnErr {
|
||||
// Halt execution in the synchronous scenario
|
||||
return fmt.Errorf("failed to install plugin %s@%s: %w", installPlugin.ID, installPlugin.Version, err)
|
||||
}
|
||||
s.log.Error("Failed to install plugin", "pluginId", installPlugin.ID, "version", installPlugin.Version, "error", err)
|
||||
continue
|
||||
}
|
||||
@@ -67,3 +101,12 @@ func (s *Service) Run(ctx context.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) Run(ctx context.Context) error {
|
||||
err := s.installPlugins(ctx)
|
||||
if err != nil {
|
||||
// Unexpected error, asynchronous installation should not return errors
|
||||
s.log.Error("Failed to install plugins", "error", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user