Plugins: Add plugins auto update feature (#104112)

This commit is contained in:
Hugo Kiyodi Oshiro
2025-05-09 15:58:04 +02:00
committed by GitHub
parent 42028a1b03
commit 43748e43bb
24 changed files with 446 additions and 220 deletions
@@ -1,36 +0,0 @@
package plugininstaller
import "github.com/grafana/grafana/pkg/setting"
type Preinstall interface {
IsPreinstalled(pluginID string) bool
IsPinned(pluginID string) bool
}
func ProvidePreinstall(
cfg *setting.Cfg,
) *PreinstallImpl {
plugins := make(map[string]*setting.InstallPlugin)
for _, p := range cfg.PreinstallPlugins {
plugins[p.ID] = &p
}
return &PreinstallImpl{
plugins: plugins,
}
}
type PreinstallImpl struct {
plugins map[string]*setting.InstallPlugin
}
func (c *PreinstallImpl) IsPreinstalled(pluginID string) bool {
_, ok := c.plugins[pluginID]
return ok
}
func (c *PreinstallImpl) IsPinned(pluginID string) bool {
if p, ok := c.plugins[pluginID]; ok {
return p.Version != ""
}
return false
}
@@ -1,36 +0,0 @@
package plugininstaller
import (
"testing"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
)
func TestIsPreinstalled(t *testing.T) {
cfg := &setting.Cfg{
PreinstallPlugins: []setting.InstallPlugin{
{ID: "plugin1"},
{ID: "plugin2"},
},
}
preinstall := ProvidePreinstall(cfg)
assert.True(t, preinstall.IsPreinstalled("plugin1"))
assert.True(t, preinstall.IsPreinstalled("plugin2"))
assert.False(t, preinstall.IsPreinstalled("plugin3"))
}
func TestIsPinned(t *testing.T) {
cfg := &setting.Cfg{
PreinstallPlugins: []setting.InstallPlugin{
{ID: "plugin1", Version: "1.0.0"},
{ID: "plugin2"},
},
}
preinstall := ProvidePreinstall(cfg)
assert.True(t, preinstall.IsPinned("plugin1"))
assert.False(t, preinstall.IsPinned("plugin2"))
assert.False(t, preinstall.IsPinned("plugin3"))
}
@@ -8,11 +8,11 @@ import (
"sync"
"time"
"github.com/Masterminds/semver/v3"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/repo"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
@@ -43,6 +43,7 @@ type Service struct {
pluginRepo repo.Service
features featuremgmt.FeatureToggles
failOnErr bool
updateChecker pluginchecker.PluginUpdateChecker
}
func ProvideService(
@@ -52,6 +53,7 @@ func ProvideService(
promReg prometheus.Registerer,
pluginRepo repo.Service,
features featuremgmt.FeatureToggles,
updateChecker pluginchecker.PluginUpdateChecker,
) (*Service, error) {
once.Do(func() {
promReg.MustRegister(installRequestCounter)
@@ -66,6 +68,7 @@ func ProvideService(
failOnErr: !cfg.PreinstallPluginsAsync, // Fail on error if preinstall is synchronous
pluginRepo: pluginRepo,
features: features,
updateChecker: updateChecker,
}
if !cfg.PreinstallPluginsAsync {
// Block initialization process until plugins are installed
@@ -108,34 +111,7 @@ func (s *Service) shouldUpdate(ctx context.Context, pluginID, currentVersion str
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
}
if parsedCurrentVersion.Compare(parsedLatestVersion) >= 0 {
s.log.Debug("No update available", "pluginId", pluginID, "version", info.Version)
return false
}
// We should update the plugin
return true
return s.updateChecker.CanUpdate(pluginID, currentVersion, info.Version, true)
}
func (s *Service) installPlugins(ctx context.Context) error {
@@ -10,7 +10,10 @@ import (
"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/managedplugins"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
@@ -29,6 +32,7 @@ func TestService_IsDisabled(t *testing.T) {
prometheus.NewRegistry(),
&fakes.FakePluginRepo{},
featuremgmt.WithFeatures(),
&pluginchecker.FakePluginUpdateChecker{},
)
require.NoError(t, err)
@@ -180,6 +184,11 @@ func TestService_Run(t *testing.T) {
},
},
featuremgmt.WithFeatures(featuremgmt.FlagPreinstallAutoUpdate),
pluginchecker.ProvideService(
managedplugins.NewNoop(),
provisionedplugins.NewNoop(),
&pluginchecker.FakePluginPreinstall{},
),
)
if tt.blocking && !tt.shouldInstall {
require.ErrorContains(t, err, "Failed to install plugin")