mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Preinstall: Automatic updates if no version is specified (#95970)
This commit is contained in:
parent
6c9afba607
commit
a415c0b831
@ -215,6 +215,7 @@ Experimental features might be changed or removed without prior notice.
|
|||||||
| `unifiedStorageBigObjectsSupport` | Enables to save big objects in blob storage |
|
| `unifiedStorageBigObjectsSupport` | Enables to save big objects in blob storage |
|
||||||
| `timeRangeProvider` | Enables time pickers sync |
|
| `timeRangeProvider` | Enables time pickers sync |
|
||||||
| `prometheusUsesCombobox` | Use new combobox component for Prometheus query editor |
|
| `prometheusUsesCombobox` | Use new combobox component for Prometheus query editor |
|
||||||
|
| `preinstallAutoUpdate` | Enables automatic updates for pre-installed plugins |
|
||||||
| `dashboardSchemaV2` | Enables the new dashboard schema version 2, implementing changes necessary for dynamic dashboards and dashboards as code. |
|
| `dashboardSchemaV2` | Enables the new dashboard schema version 2, implementing changes necessary for dynamic dashboards and dashboards as code. |
|
||||||
| `playlistsWatcher` | Enables experimental watcher for playlists |
|
| `playlistsWatcher` | Enables experimental watcher for playlists |
|
||||||
| `enableExtensionsAdminPage` | Enables the extension admin page regardless of development mode |
|
| `enableExtensionsAdminPage` | Enables the extension admin page regardless of development mode |
|
||||||
|
@ -227,6 +227,7 @@ export interface FeatureToggles {
|
|||||||
timeRangeProvider?: boolean;
|
timeRangeProvider?: boolean;
|
||||||
prometheusUsesCombobox?: boolean;
|
prometheusUsesCombobox?: boolean;
|
||||||
azureMonitorDisableLogLimit?: boolean;
|
azureMonitorDisableLogLimit?: boolean;
|
||||||
|
preinstallAutoUpdate?: boolean;
|
||||||
dashboardSchemaV2?: boolean;
|
dashboardSchemaV2?: boolean;
|
||||||
playlistsWatcher?: boolean;
|
playlistsWatcher?: boolean;
|
||||||
exploreMetricsRelatedLogs?: boolean;
|
exploreMetricsRelatedLogs?: boolean;
|
||||||
|
@ -1567,6 +1567,12 @@ var (
|
|||||||
Owner: grafanaPartnerPluginsSquad,
|
Owner: grafanaPartnerPluginsSquad,
|
||||||
Expression: "false",
|
Expression: "false",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "preinstallAutoUpdate",
|
||||||
|
Description: "Enables automatic updates for pre-installed plugins",
|
||||||
|
Stage: FeatureStageExperimental,
|
||||||
|
Owner: grafanaPluginsPlatformSquad,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
Name: "dashboardSchemaV2",
|
Name: "dashboardSchemaV2",
|
||||||
Description: "Enables the new dashboard schema version 2, implementing changes necessary for dynamic dashboards and dashboards as code.",
|
Description: "Enables the new dashboard schema version 2, implementing changes necessary for dynamic dashboards and dashboards as code.",
|
||||||
|
@ -208,6 +208,7 @@ unifiedStorageBigObjectsSupport,experimental,@grafana/search-and-storage,false,f
|
|||||||
timeRangeProvider,experimental,@grafana/grafana-frontend-platform,false,false,false
|
timeRangeProvider,experimental,@grafana/grafana-frontend-platform,false,false,false
|
||||||
prometheusUsesCombobox,experimental,@grafana/observability-metrics,false,false,false
|
prometheusUsesCombobox,experimental,@grafana/observability-metrics,false,false,false
|
||||||
azureMonitorDisableLogLimit,GA,@grafana/partner-datasources,false,false,false
|
azureMonitorDisableLogLimit,GA,@grafana/partner-datasources,false,false,false
|
||||||
|
preinstallAutoUpdate,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||||
dashboardSchemaV2,experimental,@grafana/dashboards-squad,false,false,true
|
dashboardSchemaV2,experimental,@grafana/dashboards-squad,false,false,true
|
||||||
playlistsWatcher,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
playlistsWatcher,experimental,@grafana/grafana-app-platform-squad,false,true,false
|
||||||
exploreMetricsRelatedLogs,experimental,@grafana/observability-metrics,false,false,true
|
exploreMetricsRelatedLogs,experimental,@grafana/observability-metrics,false,false,true
|
||||||
|
|
@ -843,6 +843,10 @@ const (
|
|||||||
// Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default.
|
// Disables the log limit restriction for Azure Monitor when true. The limit is enabled by default.
|
||||||
FlagAzureMonitorDisableLogLimit = "azureMonitorDisableLogLimit"
|
FlagAzureMonitorDisableLogLimit = "azureMonitorDisableLogLimit"
|
||||||
|
|
||||||
|
// FlagPreinstallAutoUpdate
|
||||||
|
// Enables automatic updates for pre-installed plugins
|
||||||
|
FlagPreinstallAutoUpdate = "preinstallAutoUpdate"
|
||||||
|
|
||||||
// FlagDashboardSchemaV2
|
// FlagDashboardSchemaV2
|
||||||
// Enables the new dashboard schema version 2, implementing changes necessary for dynamic dashboards and dashboards as code.
|
// Enables the new dashboard schema version 2, implementing changes necessary for dynamic dashboards and dashboards as code.
|
||||||
FlagDashboardSchemaV2 = "dashboardSchemaV2"
|
FlagDashboardSchemaV2 = "dashboardSchemaV2"
|
||||||
|
@ -2557,6 +2557,18 @@
|
|||||||
"codeowner": "@grafana/plugins-platform-backend"
|
"codeowner": "@grafana/plugins-platform-backend"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"metadata": {
|
||||||
|
"name": "preinstallAutoUpdate",
|
||||||
|
"resourceVersion": "1730904343525",
|
||||||
|
"creationTimestamp": "2024-11-06T14:45:43Z"
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"description": "Enables automatic updates for pre-installed plugins",
|
||||||
|
"stage": "experimental",
|
||||||
|
"codeowner": "@grafana/plugins-platform-backend"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": "preserveDashboardStateWhenNavigating",
|
"name": "preserveDashboardStateWhenNavigating",
|
||||||
|
@ -8,9 +8,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@ -38,10 +40,19 @@ type Service struct {
|
|||||||
log log.Logger
|
log log.Logger
|
||||||
pluginInstaller plugins.Installer
|
pluginInstaller plugins.Installer
|
||||||
pluginStore pluginstore.Store
|
pluginStore pluginstore.Store
|
||||||
|
pluginRepo repo.Service
|
||||||
|
features featuremgmt.FeatureToggles
|
||||||
failOnErr bool
|
failOnErr bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(cfg *setting.Cfg, pluginStore pluginstore.Store, pluginInstaller plugins.Installer, promReg prometheus.Registerer) (*Service, error) {
|
func ProvideService(
|
||||||
|
cfg *setting.Cfg,
|
||||||
|
pluginStore pluginstore.Store,
|
||||||
|
pluginInstaller plugins.Installer,
|
||||||
|
promReg prometheus.Registerer,
|
||||||
|
pluginRepo repo.Service,
|
||||||
|
features featuremgmt.FeatureToggles,
|
||||||
|
) (*Service, error) {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
promReg.MustRegister(installRequestCounter)
|
promReg.MustRegister(installRequestCounter)
|
||||||
promReg.MustRegister(installRequestDuration)
|
promReg.MustRegister(installRequestDuration)
|
||||||
@ -53,6 +64,8 @@ func ProvideService(cfg *setting.Cfg, pluginStore pluginstore.Store, pluginInsta
|
|||||||
pluginInstaller: pluginInstaller,
|
pluginInstaller: pluginInstaller,
|
||||||
pluginStore: pluginStore,
|
pluginStore: pluginStore,
|
||||||
failOnErr: !cfg.PreinstallPluginsAsync, // Fail on error if preinstall is synchronous
|
failOnErr: !cfg.PreinstallPluginsAsync, // Fail on error if preinstall is synchronous
|
||||||
|
pluginRepo: pluginRepo,
|
||||||
|
features: features,
|
||||||
}
|
}
|
||||||
if !cfg.PreinstallPluginsAsync {
|
if !cfg.PreinstallPluginsAsync {
|
||||||
// Block initialization process until plugins are installed
|
// Block initialization process until plugins are installed
|
||||||
@ -88,6 +101,39 @@ func (s *Service) installPluginsWithTimeout() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) shouldUpdate(ctx context.Context, pluginID, currentVersion string) bool {
|
||||||
|
info, err := s.pluginRepo.GetPluginArchiveInfo(ctx, pluginID, "", repo.NewCompatOpts(s.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH))
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("Failed to get plugin info", "pluginId", pluginID, "error", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are already on the latest version, skip the installation
|
||||||
|
if info.Version == currentVersion {
|
||||||
|
s.log.Debug("Latest plugin already installed", "pluginId", pluginID, "version", info.Version)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the latest version is a new major version, skip the installation
|
||||||
|
parsedLatestVersion, err := semver.NewVersion(info.Version)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("Failed to parse latest version, skipping potential update", "pluginId", pluginID, "version", info.Version, "error", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
parsedCurrentVersion, err := semver.NewVersion(currentVersion)
|
||||||
|
if err != nil {
|
||||||
|
s.log.Error("Failed to parse current version, skipping potential update", "pluginId", pluginID, "version", currentVersion, "error", err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if parsedLatestVersion.Major() > parsedCurrentVersion.Major() {
|
||||||
|
s.log.Debug("New major version available, skipping update due to possible breaking changes", "pluginId", pluginID, "version", info.Version)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// We should update the plugin
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) installPlugins(ctx context.Context) error {
|
func (s *Service) installPlugins(ctx context.Context) error {
|
||||||
compatOpts := plugins.NewCompatOpts(s.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH)
|
compatOpts := plugins.NewCompatOpts(s.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH)
|
||||||
|
|
||||||
@ -96,10 +142,21 @@ func (s *Service) installPlugins(ctx context.Context) error {
|
|||||||
p, exists := s.pluginStore.Plugin(ctx, installPlugin.ID)
|
p, exists := s.pluginStore.Plugin(ctx, installPlugin.ID)
|
||||||
if exists {
|
if exists {
|
||||||
// If it's installed, check if we are looking for a specific version
|
// If it's installed, check if we are looking for a specific version
|
||||||
if installPlugin.Version == "" || p.Info.Version == installPlugin.Version {
|
if p.Info.Version == installPlugin.Version {
|
||||||
s.log.Debug("Plugin already installed", "pluginId", installPlugin.ID, "version", installPlugin.Version)
|
s.log.Debug("Plugin already installed", "pluginId", installPlugin.ID, "version", installPlugin.Version)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
if installPlugin.Version == "" {
|
||||||
|
if !s.features.IsEnabled(ctx, featuremgmt.FlagPreinstallAutoUpdate) {
|
||||||
|
// Skip updating the plugin if the feature flag is disabled
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The plugin is installed but it's not pinned to a specific version
|
||||||
|
// Check if there is a newer version available
|
||||||
|
if !s.shouldUpdate(ctx, installPlugin.ID, p.Info.Version) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.log.Info("Installing plugin", "pluginId", installPlugin.ID, "version", installPlugin.Version)
|
s.log.Info("Installing plugin", "pluginId", installPlugin.ID, "version", installPlugin.Version)
|
||||||
|
@ -2,11 +2,14 @@ package plugininstaller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||||
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
||||||
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
@ -24,6 +27,8 @@ func TestService_IsDisabled(t *testing.T) {
|
|||||||
pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}),
|
pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}),
|
||||||
&fakes.FakePluginInstaller{},
|
&fakes.FakePluginInstaller{},
|
||||||
prometheus.NewRegistry(),
|
prometheus.NewRegistry(),
|
||||||
|
&fakes.FakePluginRepo{},
|
||||||
|
featuremgmt.WithFeatures(),
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -34,196 +39,137 @@ func TestService_IsDisabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestService_Run(t *testing.T) {
|
func TestService_Run(t *testing.T) {
|
||||||
t.Run("Installs a plugin", func(t *testing.T) {
|
tests := []struct {
|
||||||
installed := false
|
name string
|
||||||
s, err := ProvideService(
|
shouldInstall bool
|
||||||
&setting.Cfg{
|
pluginsToInstall []setting.InstallPlugin
|
||||||
PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}},
|
existingPlugins []*plugins.Plugin
|
||||||
},
|
pluginsToFail []string
|
||||||
pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}),
|
blocking bool
|
||||||
&fakes.FakePluginInstaller{
|
latestPlugin *repo.PluginArchiveInfo
|
||||||
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
}{
|
||||||
installed = true
|
{
|
||||||
return nil
|
name: "Installs a plugin",
|
||||||
|
shouldInstall: true,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Install a plugin with version",
|
||||||
|
shouldInstall: true,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: "1.0.0"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Skips already installed plugin",
|
||||||
|
shouldInstall: false,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin"}},
|
||||||
|
existingPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Still installs a plugin if the plugin version does not match",
|
||||||
|
shouldInstall: true,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: "2.0.0"}},
|
||||||
|
existingPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Install multiple plugins",
|
||||||
|
shouldInstall: true,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Fails to install a plugin but install the rest",
|
||||||
|
shouldInstall: true,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}},
|
||||||
|
pluginsToFail: []string{"myplugin1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Install a blocking plugin",
|
||||||
|
shouldInstall: true,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin"}},
|
||||||
|
blocking: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Fails to install a blocking plugin",
|
||||||
|
shouldInstall: false,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin"}},
|
||||||
|
blocking: true,
|
||||||
|
pluginsToFail: []string{"myplugin"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Updates a plugin",
|
||||||
|
shouldInstall: true,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: ""}},
|
||||||
|
existingPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}},
|
||||||
|
latestPlugin: &repo.PluginArchiveInfo{Version: "1.0.1"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should not update a plugin if the latest version is installed",
|
||||||
|
shouldInstall: false,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: ""}},
|
||||||
|
existingPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}},
|
||||||
|
latestPlugin: &repo.PluginArchiveInfo{Version: "1.0.0"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Should not update a plugin if the latest version is a major version",
|
||||||
|
shouldInstall: false,
|
||||||
|
pluginsToInstall: []setting.InstallPlugin{{ID: "myplugin", Version: ""}},
|
||||||
|
existingPlugins: []*plugins.Plugin{{JSONData: plugins.JSONData{ID: "myplugin", Info: plugins.Info{Version: "1.0.0"}}}},
|
||||||
|
latestPlugin: &repo.PluginArchiveInfo{Version: "2.0.0"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
preg := registry.NewInMemory()
|
||||||
|
for _, plugin := range tt.existingPlugins {
|
||||||
|
err := preg.Add(context.Background(), plugin)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
installed := 0
|
||||||
|
s, err := ProvideService(
|
||||||
|
&setting.Cfg{
|
||||||
|
PreinstallPlugins: tt.pluginsToInstall,
|
||||||
|
PreinstallPluginsAsync: !tt.blocking,
|
||||||
},
|
},
|
||||||
},
|
pluginstore.New(preg, &fakes.FakeLoader{}),
|
||||||
prometheus.NewRegistry(),
|
&fakes.FakePluginInstaller{
|
||||||
)
|
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
||||||
require.NoError(t, err)
|
for _, plugin := range tt.pluginsToFail {
|
||||||
|
if plugin == pluginID {
|
||||||
err = s.Run(context.Background())
|
return errors.New("Failed to install plugin")
|
||||||
require.NoError(t, err)
|
}
|
||||||
require.True(t, installed)
|
}
|
||||||
})
|
if !tt.shouldInstall {
|
||||||
|
t.Fatal("Should not install plugin")
|
||||||
t.Run("Install a plugin with version", func(t *testing.T) {
|
return errors.New("Should not install plugin")
|
||||||
installed := false
|
}
|
||||||
s, err := ProvideService(
|
for _, plugin := range tt.pluginsToInstall {
|
||||||
&setting.Cfg{
|
if plugin.ID == pluginID && plugin.Version == version {
|
||||||
PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin", Version: "1.0.0"}},
|
installed++
|
||||||
PreinstallPluginsAsync: true,
|
}
|
||||||
},
|
}
|
||||||
pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}),
|
return nil
|
||||||
&fakes.FakePluginInstaller{
|
},
|
||||||
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
|
||||||
if pluginID == "myplugin" && version == "1.0.0" {
|
|
||||||
installed = true
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
},
|
prometheus.NewRegistry(),
|
||||||
prometheus.NewRegistry(),
|
&fakes.FakePluginRepo{
|
||||||
)
|
GetPluginArchiveInfoFunc: func(_ context.Context, pluginID, version string, _ repo.CompatOpts) (*repo.PluginArchiveInfo, error) {
|
||||||
require.NoError(t, err)
|
return tt.latestPlugin, nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
featuremgmt.WithFeatures(featuremgmt.FlagPreinstallAutoUpdate),
|
||||||
|
)
|
||||||
|
if tt.blocking && !tt.shouldInstall {
|
||||||
|
require.ErrorContains(t, err, "Failed to install plugin")
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
err = s.Run(context.Background())
|
if !tt.blocking {
|
||||||
require.NoError(t, err)
|
err = s.Run(context.Background())
|
||||||
require.True(t, installed)
|
require.NoError(t, err)
|
||||||
})
|
}
|
||||||
|
if tt.shouldInstall {
|
||||||
t.Run("Skips already installed plugin", func(t *testing.T) {
|
require.Equal(t, len(tt.pluginsToInstall)-len(tt.pluginsToFail), installed)
|
||||||
preg := registry.NewInMemory()
|
}
|
||||||
err := preg.Add(context.Background(), &plugins.Plugin{
|
|
||||||
JSONData: plugins.JSONData{
|
|
||||||
ID: "myplugin",
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
}
|
||||||
s, err := ProvideService(
|
|
||||||
&setting.Cfg{
|
|
||||||
PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}},
|
|
||||||
PreinstallPluginsAsync: true,
|
|
||||||
},
|
|
||||||
pluginstore.New(preg, &fakes.FakeLoader{}),
|
|
||||||
&fakes.FakePluginInstaller{
|
|
||||||
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
|
||||||
t.Fatal("Should not install plugin")
|
|
||||||
return plugins.DuplicateError{}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prometheus.NewRegistry(),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Run(context.Background())
|
|
||||||
require.NoError(t, err)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Still installs a plugin if the plugin version does not match", func(t *testing.T) {
|
|
||||||
installed := false
|
|
||||||
preg := registry.NewInMemory()
|
|
||||||
err := preg.Add(context.Background(), &plugins.Plugin{
|
|
||||||
JSONData: plugins.JSONData{
|
|
||||||
ID: "myplugin",
|
|
||||||
Info: plugins.Info{
|
|
||||||
Version: "1.0.0",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.NoError(t, err)
|
|
||||||
s, err := ProvideService(
|
|
||||||
&setting.Cfg{
|
|
||||||
PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin", Version: "2.0.0"}},
|
|
||||||
PreinstallPluginsAsync: true,
|
|
||||||
},
|
|
||||||
pluginstore.New(preg, &fakes.FakeLoader{}),
|
|
||||||
&fakes.FakePluginInstaller{
|
|
||||||
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
|
||||||
installed = true
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prometheus.NewRegistry(),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Run(context.Background())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, installed)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Install multiple plugins", func(t *testing.T) {
|
|
||||||
installed := 0
|
|
||||||
s, err := ProvideService(
|
|
||||||
&setting.Cfg{
|
|
||||||
PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}},
|
|
||||||
PreinstallPluginsAsync: true,
|
|
||||||
},
|
|
||||||
pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}),
|
|
||||||
&fakes.FakePluginInstaller{
|
|
||||||
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
|
||||||
installed++
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prometheus.NewRegistry(),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = s.Run(context.Background())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 2, installed)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Fails to install a plugin but install the rest", func(t *testing.T) {
|
|
||||||
installed := 0
|
|
||||||
s, err := ProvideService(
|
|
||||||
&setting.Cfg{
|
|
||||||
PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin1"}, {ID: "myplugin2"}},
|
|
||||||
PreinstallPluginsAsync: true,
|
|
||||||
},
|
|
||||||
pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}),
|
|
||||||
&fakes.FakePluginInstaller{
|
|
||||||
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
|
||||||
if pluginID == "myplugin1" {
|
|
||||||
return plugins.NotFoundError{}
|
|
||||||
}
|
|
||||||
installed++
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prometheus.NewRegistry(),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
err = s.Run(context.Background())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.Equal(t, 1, installed)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Install a blocking plugin", func(t *testing.T) {
|
|
||||||
installed := false
|
|
||||||
_, err := ProvideService(
|
|
||||||
&setting.Cfg{
|
|
||||||
PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}},
|
|
||||||
PreinstallPluginsAsync: false,
|
|
||||||
},
|
|
||||||
pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}),
|
|
||||||
&fakes.FakePluginInstaller{
|
|
||||||
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
|
||||||
installed = true
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prometheus.NewRegistry(),
|
|
||||||
)
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.True(t, installed)
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("Fails to install a blocking plugin", func(t *testing.T) {
|
|
||||||
_, err := ProvideService(
|
|
||||||
&setting.Cfg{
|
|
||||||
PreinstallPlugins: []setting.InstallPlugin{{ID: "myplugin"}},
|
|
||||||
PreinstallPluginsAsync: false,
|
|
||||||
},
|
|
||||||
pluginstore.New(registry.NewInMemory(), &fakes.FakeLoader{}),
|
|
||||||
&fakes.FakePluginInstaller{
|
|
||||||
AddFunc: func(ctx context.Context, pluginID string, version string, opts plugins.CompatOpts) error {
|
|
||||||
return plugins.NotFoundError{}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
prometheus.NewRegistry(),
|
|
||||||
)
|
|
||||||
require.ErrorAs(t, err, &plugins.NotFoundError{})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user