mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 10:02:04 +08:00
Plugins: Add plugins auto update feature (#104112)
This commit is contained in:

committed by
GitHub

parent
42028a1b03
commit
43748e43bb
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@ -166,7 +166,7 @@
|
||||
/pkg/services/tag/ @grafana/grafana-search-and-storage
|
||||
/pkg/services/team/ @grafana/access-squad
|
||||
/pkg/services/temp_user/ @grafana/grafana-backend-group
|
||||
/pkg/services/updatechecker/ @grafana/grafana-backend-group
|
||||
/pkg/services/updatemanager/ @grafana/grafana-backend-group
|
||||
/pkg/services/user/ @grafana/access-squad
|
||||
/pkg/services/validations/ @grafana/grafana-backend-group
|
||||
/pkg/setting/ @grafana/grafana-backend-services-squad
|
||||
|
@ -9,8 +9,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings"
|
||||
@ -27,7 +27,8 @@ type Service struct {
|
||||
pluginContextProvider *plugincontext.Provider
|
||||
pluginClient plugins.Client
|
||||
pluginRepo repo.Service
|
||||
pluginPreinstall plugininstaller.Preinstall
|
||||
updateChecker pluginchecker.PluginUpdateChecker
|
||||
pluginPreinstall pluginchecker.Preinstall
|
||||
managedPlugins managedplugins.Manager
|
||||
provisionedPlugins provisionedplugins.Manager
|
||||
ssoSettingsSvc ssosettings.Service
|
||||
@ -36,9 +37,9 @@ type Service struct {
|
||||
|
||||
func ProvideService(datasourceSvc datasources.DataSourceService, pluginStore pluginstore.Store,
|
||||
pluginContextProvider *plugincontext.Provider, pluginClient plugins.Client,
|
||||
pluginRepo repo.Service, pluginPreinstall plugininstaller.Preinstall, managedPlugins managedplugins.Manager,
|
||||
updateChecker pluginchecker.PluginUpdateChecker,
|
||||
pluginRepo repo.Service, pluginPreinstall pluginchecker.Preinstall, managedPlugins managedplugins.Manager,
|
||||
provisionedPlugins provisionedplugins.Manager, ssoSettingsSvc ssosettings.Service, settings *setting.Cfg,
|
||||
|
||||
) *Service {
|
||||
return &Service{
|
||||
datasourceSvc: datasourceSvc,
|
||||
@ -46,6 +47,7 @@ func ProvideService(datasourceSvc datasources.DataSourceService, pluginStore plu
|
||||
pluginContextProvider: pluginContextProvider,
|
||||
pluginClient: pluginClient,
|
||||
pluginRepo: pluginRepo,
|
||||
updateChecker: updateChecker,
|
||||
pluginPreinstall: pluginPreinstall,
|
||||
managedPlugins: managedPlugins,
|
||||
provisionedPlugins: provisionedPlugins,
|
||||
@ -67,9 +69,7 @@ func (s *Service) Checks() []checks.Check {
|
||||
plugincheck.New(
|
||||
s.pluginStore,
|
||||
s.pluginRepo,
|
||||
s.pluginPreinstall,
|
||||
s.managedPlugins,
|
||||
s.provisionedPlugins,
|
||||
s.updateChecker,
|
||||
s.GrafanaVersion,
|
||||
),
|
||||
authchecks.New(s.ssoSettingsSvc),
|
||||
|
@ -4,17 +4,14 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
sysruntime "runtime"
|
||||
"slices"
|
||||
|
||||
"github.com/Masterminds/semver/v3"
|
||||
"github.com/grafana/grafana-app-sdk/logging"
|
||||
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -26,28 +23,22 @@ const (
|
||||
func New(
|
||||
pluginStore pluginstore.Store,
|
||||
pluginRepo repo.Service,
|
||||
pluginPreinstall plugininstaller.Preinstall,
|
||||
managedPlugins managedplugins.Manager,
|
||||
provisionedPlugins provisionedplugins.Manager,
|
||||
updateChecker pluginchecker.PluginUpdateChecker,
|
||||
grafanaVersion string,
|
||||
) checks.Check {
|
||||
return &check{
|
||||
PluginStore: pluginStore,
|
||||
PluginRepo: pluginRepo,
|
||||
PluginPreinstall: pluginPreinstall,
|
||||
ManagedPlugins: managedPlugins,
|
||||
ProvisionedPlugins: provisionedPlugins,
|
||||
GrafanaVersion: grafanaVersion,
|
||||
PluginStore: pluginStore,
|
||||
PluginRepo: pluginRepo,
|
||||
GrafanaVersion: grafanaVersion,
|
||||
updateChecker: updateChecker,
|
||||
}
|
||||
}
|
||||
|
||||
type check struct {
|
||||
PluginStore pluginstore.Store
|
||||
PluginRepo repo.Service
|
||||
PluginPreinstall plugininstaller.Preinstall
|
||||
ManagedPlugins managedplugins.Manager
|
||||
ProvisionedPlugins provisionedplugins.Manager
|
||||
GrafanaVersion string
|
||||
PluginStore pluginstore.Store
|
||||
PluginRepo repo.Service
|
||||
updateChecker pluginchecker.PluginUpdateChecker
|
||||
GrafanaVersion string
|
||||
}
|
||||
|
||||
func (c *check) ID() string {
|
||||
@ -74,29 +65,22 @@ func (c *check) Item(ctx context.Context, id string) (any, error) {
|
||||
func (c *check) Steps() []checks.Step {
|
||||
return []checks.Step{
|
||||
&deprecationStep{
|
||||
PluginRepo: c.PluginRepo,
|
||||
PluginPreinstall: c.PluginPreinstall,
|
||||
ManagedPlugins: c.ManagedPlugins,
|
||||
ProvisionedPlugins: c.ProvisionedPlugins,
|
||||
GrafanaVersion: c.GrafanaVersion,
|
||||
PluginRepo: c.PluginRepo,
|
||||
GrafanaVersion: c.GrafanaVersion,
|
||||
updateChecker: c.updateChecker,
|
||||
},
|
||||
&updateStep{
|
||||
PluginRepo: c.PluginRepo,
|
||||
PluginPreinstall: c.PluginPreinstall,
|
||||
ManagedPlugins: c.ManagedPlugins,
|
||||
ProvisionedPlugins: c.ProvisionedPlugins,
|
||||
GrafanaVersion: c.GrafanaVersion,
|
||||
PluginRepo: c.PluginRepo,
|
||||
GrafanaVersion: c.GrafanaVersion,
|
||||
updateChecker: c.updateChecker,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type deprecationStep struct {
|
||||
PluginRepo repo.Service
|
||||
PluginPreinstall plugininstaller.Preinstall
|
||||
ManagedPlugins managedplugins.Manager
|
||||
ProvisionedPlugins provisionedplugins.Manager
|
||||
GrafanaVersion string
|
||||
provisionedPlugins []string
|
||||
PluginRepo repo.Service
|
||||
GrafanaVersion string
|
||||
updateChecker pluginchecker.PluginUpdateChecker
|
||||
}
|
||||
|
||||
func (s *deprecationStep) Title() string {
|
||||
@ -122,21 +106,7 @@ func (s *deprecationStep) Run(ctx context.Context, log logging.Logger, _ *adviso
|
||||
return nil, fmt.Errorf("invalid item type %T", it)
|
||||
}
|
||||
|
||||
// Skip if it's a core plugin
|
||||
if p.IsCorePlugin() {
|
||||
log.Debug("Skipping core plugin", "plugin", p.ID)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Skip if it's managed or pinned
|
||||
if s.isManaged(ctx, p.ID) || s.PluginPreinstall.IsPinned(p.ID) {
|
||||
log.Debug("Skipping managed or pinned plugin", "plugin", p.ID)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Skip if it's provisioned
|
||||
if s.isProvisioned(ctx, p.ID) {
|
||||
log.Debug("Skipping provisioned plugin", "plugin", p.ID)
|
||||
if !s.updateChecker.IsUpdatable(ctx, p) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -165,12 +135,9 @@ func (s *deprecationStep) Run(ctx context.Context, log logging.Logger, _ *adviso
|
||||
}
|
||||
|
||||
type updateStep struct {
|
||||
PluginRepo repo.Service
|
||||
PluginPreinstall plugininstaller.Preinstall
|
||||
ManagedPlugins managedplugins.Manager
|
||||
ProvisionedPlugins provisionedplugins.Manager
|
||||
provisionedPlugins []string
|
||||
GrafanaVersion string
|
||||
PluginRepo repo.Service
|
||||
GrafanaVersion string
|
||||
updateChecker pluginchecker.PluginUpdateChecker
|
||||
}
|
||||
|
||||
func (s *updateStep) Title() string {
|
||||
@ -195,21 +162,7 @@ func (s *updateStep) Run(ctx context.Context, log logging.Logger, _ *advisor.Che
|
||||
return nil, fmt.Errorf("invalid item type %T", i)
|
||||
}
|
||||
|
||||
// Skip if it's a core plugin
|
||||
if p.IsCorePlugin() {
|
||||
log.Debug("Skipping core plugin", "plugin", p.ID)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Skip if it's managed or pinned
|
||||
if s.isManaged(ctx, p.ID) || s.PluginPreinstall.IsPinned(p.ID) {
|
||||
log.Debug("Skipping managed or pinned plugin", "plugin", p.ID)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Skip if it's provisioned
|
||||
if s.isProvisioned(ctx, p.ID) {
|
||||
log.Debug("Skipping provisioned plugin", "plugin", p.ID)
|
||||
if !s.updateChecker.IsUpdatable(ctx, p) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@ -248,44 +201,3 @@ func hasUpdate(current pluginstore.Plugin, latest *repo.PluginArchiveInfo) bool
|
||||
// In other case, assume that a different latest version will always be newer
|
||||
return current.Info.Version != latest.Version
|
||||
}
|
||||
|
||||
func (s *updateStep) isManaged(ctx context.Context, pluginID string) bool {
|
||||
for _, managedPlugin := range s.ManagedPlugins.ManagedPlugins(ctx) {
|
||||
if managedPlugin == pluginID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *updateStep) isProvisioned(ctx context.Context, pluginID string) bool {
|
||||
if s.provisionedPlugins == nil {
|
||||
var err error
|
||||
s.provisionedPlugins, err = s.ProvisionedPlugins.ProvisionedPlugins(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return slices.Contains(s.provisionedPlugins, pluginID)
|
||||
}
|
||||
|
||||
// Temporary duplicated code until there is a common IsUpdatable function
|
||||
func (s *deprecationStep) isManaged(ctx context.Context, pluginID string) bool {
|
||||
for _, managedPlugin := range s.ManagedPlugins.ManagedPlugins(ctx) {
|
||||
if managedPlugin == pluginID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *deprecationStep) isProvisioned(ctx context.Context, pluginID string) bool {
|
||||
if s.provisionedPlugins == nil {
|
||||
var err error
|
||||
s.provisionedPlugins, err = s.ProvisionedPlugins.ProvisionedPlugins(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return slices.Contains(s.provisionedPlugins, pluginID)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
||||
"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/stretchr/testify/assert"
|
||||
@ -163,7 +163,8 @@ func TestRun(t *testing.T) {
|
||||
pluginPreinstall := &mockPluginPreinstall{pinned: tt.pluginPreinstalled}
|
||||
managedPlugins := &mockManagedPlugins{managed: tt.pluginManaged}
|
||||
provisionedPlugins := &mockProvisionedPlugins{provisioned: tt.pluginProvisioned}
|
||||
check := New(pluginStore, pluginRepo, pluginPreinstall, managedPlugins, provisionedPlugins, "12.0.0")
|
||||
updateChecker := pluginchecker.ProvideService(managedPlugins, provisionedPlugins, pluginPreinstall)
|
||||
check := New(pluginStore, pluginRepo, updateChecker, "12.0.0")
|
||||
|
||||
items, err := check.Items(context.Background())
|
||||
assert.NoError(t, err)
|
||||
@ -208,7 +209,7 @@ func (m *mockPluginRepo) GetPluginArchiveInfo(ctx context.Context, id, version s
|
||||
}
|
||||
|
||||
type mockPluginPreinstall struct {
|
||||
plugininstaller.Preinstall
|
||||
pluginchecker.Preinstall
|
||||
pinned []string
|
||||
}
|
||||
|
||||
|
@ -1840,6 +1840,9 @@ preinstall =
|
||||
preinstall_async = true
|
||||
# Disables preinstall feature. It has the same effect as setting preinstall to an empty list.
|
||||
preinstall_disabled = false
|
||||
# Update strategy for plugins.
|
||||
# Available options: "latest", "minor"
|
||||
update_strategy = minor
|
||||
|
||||
#################################### Grafana Live ##########################################
|
||||
[live]
|
||||
|
@ -38,7 +38,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/rendering"
|
||||
"github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
||||
"github.com/grafana/grafana/pkg/services/updatechecker"
|
||||
"github.com/grafana/grafana/pkg/services/updatemanager"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
@ -93,7 +93,7 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
|
||||
SQLStore: db.InitTestDB(t),
|
||||
SettingsProvider: setting.ProvideProvider(cfg),
|
||||
pluginStore: pluginStore,
|
||||
grafanaUpdateChecker: &updatechecker.GrafanaService{},
|
||||
grafanaUpdateChecker: &updatemanager.GrafanaService{},
|
||||
AccessControl: accesscontrolmock.New(),
|
||||
PluginSettings: pluginsSettings,
|
||||
pluginsCDNService: pluginsCDN,
|
||||
|
@ -81,8 +81,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
||||
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
||||
@ -107,7 +107,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/tag"
|
||||
"github.com/grafana/grafana/pkg/services/team"
|
||||
tempUser "github.com/grafana/grafana/pkg/services/temp_user"
|
||||
"github.com/grafana/grafana/pkg/services/updatechecker"
|
||||
"github.com/grafana/grafana/pkg/services/updatemanager"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/validations"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -150,7 +150,7 @@ type HTTPServer struct {
|
||||
pluginStaticRouteResolver plugins.StaticRouteResolver
|
||||
pluginErrorResolver plugins.ErrorResolver
|
||||
pluginAssets *pluginassets.Service
|
||||
pluginPreinstall plugininstaller.Preinstall
|
||||
pluginPreinstall pluginchecker.Preinstall
|
||||
SearchService search.Service
|
||||
ShortURLService shorturls.Service
|
||||
QueryHistoryService queryhistory.Service
|
||||
@ -175,8 +175,8 @@ type HTTPServer struct {
|
||||
DataSourcesService datasources.DataSourceService
|
||||
cleanUpService *cleanup.CleanUpService
|
||||
tracer tracing.Tracer
|
||||
grafanaUpdateChecker *updatechecker.GrafanaService
|
||||
pluginsUpdateChecker *updatechecker.PluginsService
|
||||
grafanaUpdateChecker *updatemanager.GrafanaService
|
||||
pluginsUpdateChecker *updatemanager.PluginsService
|
||||
searchUsersService searchusers.Service
|
||||
queryDataService query.Service
|
||||
serviceAccountsService serviceaccounts.Service
|
||||
@ -249,8 +249,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
contextHandler *contexthandler.ContextHandler, loggerMiddleware loggermw.Logger, features featuremgmt.FeatureToggles,
|
||||
alertNG *ngalert.AlertNG, libraryPanelService librarypanels.Service, libraryElementService libraryelements.Service,
|
||||
quotaService quota.Service, socialService social.Service, tracer tracing.Tracer,
|
||||
encryptionService encryption.Internal, grafanaUpdateChecker *updatechecker.GrafanaService,
|
||||
pluginsUpdateChecker *updatechecker.PluginsService, searchUsersService searchusers.Service,
|
||||
encryptionService encryption.Internal, grafanaUpdateChecker *updatemanager.GrafanaService,
|
||||
pluginsUpdateChecker *updatemanager.PluginsService, searchUsersService searchusers.Service,
|
||||
dataSourcesService datasources.DataSourceService, queryDataService query.Service, pluginFileStore plugins.FileStore,
|
||||
serviceaccountsService serviceaccounts.Service, pluginAssets *pluginassets.Service,
|
||||
authInfoService login.AuthInfoService, storageService store.StorageService,
|
||||
@ -271,7 +271,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
|
||||
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService,
|
||||
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service, promGatherer prometheus.Gatherer,
|
||||
starApi *starApi.API, promRegister prometheus.Registerer, clientConfigProvider grafanaapiserver.DirectRestConfigProvider, anonService anonymous.Service,
|
||||
userVerifier user.Verifier, pluginPreinstall plugininstaller.Preinstall,
|
||||
userVerifier user.Verifier, pluginPreinstall pluginchecker.Preinstall,
|
||||
) (*HTTPServer, error) {
|
||||
web.Env = cfg.Env
|
||||
m := web.New()
|
||||
|
@ -44,11 +44,13 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/updatechecker"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/secrets/kvstore"
|
||||
"github.com/grafana/grafana/pkg/services/updatemanager"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
@ -112,7 +114,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
},
|
||||
})
|
||||
hs.managedPluginsService = managedplugins.NewNoop()
|
||||
hs.pluginPreinstall = plugininstaller.ProvidePreinstall(hs.Cfg)
|
||||
hs.pluginPreinstall = pluginchecker.ProvidePreinstall(hs.Cfg)
|
||||
|
||||
expectedIdentity := &authn.Identity{
|
||||
OrgID: tc.permissionOrg,
|
||||
@ -644,7 +646,14 @@ func Test_PluginsList_AccessControl(t *testing.T) {
|
||||
hs.pluginFileStore = filestore.ProvideService(pluginRegistry)
|
||||
hs.managedPluginsService = managedplugins.NewNoop()
|
||||
var err error
|
||||
hs.pluginsUpdateChecker, err = updatechecker.ProvidePluginsService(hs.Cfg, nil, tracing.InitializeTracerForTest())
|
||||
hs.pluginsUpdateChecker, err = updatemanager.ProvidePluginsService(
|
||||
hs.Cfg,
|
||||
hs.pluginStore,
|
||||
nil, // plugins.Installer
|
||||
tracing.InitializeTracerForTest(),
|
||||
kvstore.NewFakeFeatureToggles(t, true),
|
||||
pluginchecker.ProvideService(hs.managedPluginsService, provisionedplugins.NewNoop(), &pluginchecker.FakePluginPreinstall{}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
@ -836,7 +845,14 @@ func Test_PluginsSettings(t *testing.T) {
|
||||
hs.pluginAssets = pluginassets.ProvideService(pCfg, pluginCDN, sig, hs.pluginStore)
|
||||
hs.pluginErrorResolver = pluginerrs.ProvideStore(errTracker)
|
||||
var err error
|
||||
hs.pluginsUpdateChecker, err = updatechecker.ProvidePluginsService(hs.Cfg, nil, tracing.InitializeTracerForTest())
|
||||
hs.pluginsUpdateChecker, err = updatemanager.ProvidePluginsService(
|
||||
hs.Cfg,
|
||||
hs.pluginStore,
|
||||
&fakes.FakePluginInstaller{},
|
||||
tracing.InitializeTracerForTest(),
|
||||
kvstore.NewFakeFeatureToggles(t, true),
|
||||
pluginchecker.ProvideService(hs.managedPluginsService, provisionedplugins.NewNoop(), &pluginchecker.FakePluginPreinstall{}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
|
@ -47,7 +47,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/store/sanitizer"
|
||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
|
||||
"github.com/grafana/grafana/pkg/services/team/teamapi"
|
||||
"github.com/grafana/grafana/pkg/services/updatechecker"
|
||||
"github.com/grafana/grafana/pkg/services/updatemanager"
|
||||
)
|
||||
|
||||
func ProvideBackgroundServiceRegistry(
|
||||
@ -55,8 +55,8 @@ func ProvideBackgroundServiceRegistry(
|
||||
pushGateway *pushhttp.Gateway, notifications *notifications.NotificationService, pluginStore *pluginStore.Service,
|
||||
rendering *rendering.RenderingService, tokenService auth.UserTokenBackgroundService, tracing *tracing.TracingService,
|
||||
provisioning *provisioning.ProvisioningServiceImpl, usageStats *uss.UsageStats,
|
||||
statsCollector *statscollector.Service, grafanaUpdateChecker *updatechecker.GrafanaService,
|
||||
pluginsUpdateChecker *updatechecker.PluginsService, metrics *metrics.InternalMetricsService,
|
||||
statsCollector *statscollector.Service, grafanaUpdateChecker *updatemanager.GrafanaService,
|
||||
pluginsUpdateChecker *updatemanager.PluginsService, metrics *metrics.InternalMetricsService,
|
||||
secretsService *secretsManager.SecretsService, remoteCache *remotecache.RemoteCache, StorageService store.StorageService, searchService searchV2.SearchService, entityEventsService store.EntityEventsService,
|
||||
saService *samanager.ServiceAccountsService, grpcServerProvider grpcserver.Provider,
|
||||
secretMigrationProvider secretsMigrations.SecretMigrationProvider, loginAttemptService *loginattemptimpl.Service,
|
||||
|
@ -158,7 +158,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
|
||||
"github.com/grafana/grafana/pkg/services/temp_user/tempuserimpl"
|
||||
"github.com/grafana/grafana/pkg/services/updatechecker"
|
||||
"github.com/grafana/grafana/pkg/services/updatemanager"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
@ -215,8 +215,8 @@ var wireBasicSet = wire.NewSet(
|
||||
localcache.ProvideService,
|
||||
bundleregistry.ProvideService,
|
||||
wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)),
|
||||
updatechecker.ProvideGrafanaService,
|
||||
updatechecker.ProvidePluginsService,
|
||||
updatemanager.ProvideGrafanaService,
|
||||
updatemanager.ProvidePluginsService,
|
||||
uss.ProvideService,
|
||||
wire.Bind(new(usagestats.Service), new(*uss.UsageStats)),
|
||||
validator.ProvideService,
|
||||
|
@ -1,4 +1,4 @@
|
||||
package plugininstaller
|
||||
package pluginchecker
|
||||
|
||||
import "github.com/grafana/grafana/pkg/setting"
|
||||
|
@ -1,4 +1,4 @@
|
||||
package plugininstaller
|
||||
package pluginchecker
|
||||
|
||||
import (
|
||||
"testing"
|
45
pkg/services/pluginsintegration/pluginchecker/fake.go
Normal file
45
pkg/services/pluginsintegration/pluginchecker/fake.go
Normal file
@ -0,0 +1,45 @@
|
||||
package pluginchecker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
)
|
||||
|
||||
type FakePluginUpdateChecker struct {
|
||||
IsUpdatableFunc func(ctx context.Context, plugin pluginstore.Plugin) bool
|
||||
CanUpdateFunc func(pluginId string, currentVersion string, targetVersion string, onlyMinor bool) bool
|
||||
}
|
||||
|
||||
func (f *FakePluginUpdateChecker) IsUpdatable(ctx context.Context, plugin pluginstore.Plugin) bool {
|
||||
if f.IsUpdatableFunc != nil {
|
||||
return f.IsUpdatableFunc(ctx, plugin)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *FakePluginUpdateChecker) CanUpdate(pluginId string, currentVersion string, targetVersion string, onlyMinor bool) bool {
|
||||
if f.CanUpdateFunc != nil {
|
||||
return f.CanUpdateFunc(pluginId, currentVersion, targetVersion, onlyMinor)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
type FakePluginPreinstall struct {
|
||||
IsPinnedFunc func(pluginID string) bool
|
||||
IsPreinstalledFunc func(pluginID string) bool
|
||||
}
|
||||
|
||||
func (f *FakePluginPreinstall) IsPinned(pluginID string) bool {
|
||||
if f.IsPinnedFunc != nil {
|
||||
return f.IsPinnedFunc(pluginID)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (f *FakePluginPreinstall) IsPreinstalled(pluginID string) bool {
|
||||
if f.IsPreinstalledFunc != nil {
|
||||
return f.IsPreinstalledFunc(pluginID)
|
||||
}
|
||||
return false
|
||||
}
|
118
pkg/services/pluginsintegration/pluginchecker/service.go
Normal file
118
pkg/services/pluginsintegration/pluginchecker/service.go
Normal file
@ -0,0 +1,118 @@
|
||||
package pluginchecker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/Masterminds/semver"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
||||
)
|
||||
|
||||
type PluginUpdateChecker interface {
|
||||
IsUpdatable(ctx context.Context, plugin pluginstore.Plugin) bool
|
||||
CanUpdate(pluginId string, currentVersion string, targetVersion string, onlyMinor bool) bool
|
||||
}
|
||||
|
||||
var _ PluginUpdateChecker = (*Service)(nil)
|
||||
|
||||
type Service struct {
|
||||
managedPluginsManager managedplugins.Manager
|
||||
provisionedPluginsManager provisionedplugins.Manager
|
||||
pluginPreinstall Preinstall
|
||||
provisionedPlugins []string
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func ProvideService(
|
||||
managedPluginsManager managedplugins.Manager,
|
||||
provisionedPluginsManager provisionedplugins.Manager,
|
||||
pluginPreinstall Preinstall,
|
||||
) *Service {
|
||||
return &Service{
|
||||
managedPluginsManager: managedPluginsManager,
|
||||
provisionedPluginsManager: provisionedPluginsManager,
|
||||
pluginPreinstall: pluginPreinstall,
|
||||
log: log.New("plugin.updatechecker"),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) isManaged(ctx context.Context, pluginID string) bool {
|
||||
for _, managedPlugin := range s.managedPluginsManager.ManagedPlugins(ctx) {
|
||||
if managedPlugin == pluginID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (s *Service) isProvisioned(ctx context.Context, pluginID string) bool {
|
||||
if s.provisionedPlugins == nil {
|
||||
var err error
|
||||
s.provisionedPlugins, err = s.provisionedPluginsManager.ProvisionedPlugins(ctx)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return slices.Contains(s.provisionedPlugins, pluginID)
|
||||
}
|
||||
|
||||
func (s *Service) IsUpdatable(ctx context.Context, plugin pluginstore.Plugin) bool {
|
||||
if plugin.IsCorePlugin() {
|
||||
s.log.Debug("Skipping core plugin", "plugin", plugin.ID)
|
||||
return false
|
||||
}
|
||||
|
||||
if s.isManaged(ctx, plugin.ID) {
|
||||
s.log.Debug("Skipping managed plugin", "plugin", plugin.ID)
|
||||
return false
|
||||
}
|
||||
|
||||
if s.pluginPreinstall.IsPinned(plugin.ID) {
|
||||
s.log.Debug("Skipping pinned plugin", "plugin", plugin.ID)
|
||||
return false
|
||||
}
|
||||
|
||||
if s.isProvisioned(ctx, plugin.ID) {
|
||||
s.log.Debug("Skipping provisioned plugin", "plugin", plugin.ID)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Service) CanUpdate(pluginId string, currentVersion string, targetVersion string, onlyMinor bool) bool {
|
||||
// If we are already on the latest version, skip the installation
|
||||
if currentVersion == targetVersion {
|
||||
s.log.Debug("Latest plugin already installed", "pluginId", pluginId, "version", targetVersion)
|
||||
return false
|
||||
}
|
||||
|
||||
// If the latest version is a new major version, skip the installation
|
||||
parsedLatestVersion, err := semver.NewVersion(targetVersion)
|
||||
if err != nil {
|
||||
s.log.Error("Failed to parse latest version, skipping potential update", "pluginId", pluginId, "version", targetVersion, "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 onlyMinor && (parsedLatestVersion.Major() > parsedCurrentVersion.Major()) {
|
||||
s.log.Debug("New major version available, skipping update due to possible breaking changes", "pluginId", pluginId, "version", targetVersion)
|
||||
return false
|
||||
}
|
||||
|
||||
if parsedCurrentVersion.Compare(parsedLatestVersion) >= 0 {
|
||||
s.log.Debug("No update available", "pluginId", pluginId, "version", targetVersion)
|
||||
return false
|
||||
}
|
||||
|
||||
// We should update the plugin
|
||||
return true
|
||||
}
|
@ -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")
|
||||
|
@ -46,6 +46,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginconfig"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
||||
@ -128,10 +129,12 @@ var WireSet = wire.NewSet(
|
||||
wire.Bind(new(plugincontext.BasePluginContextProvider), new(*plugincontext.BaseProvider)),
|
||||
plugininstaller.ProvideService,
|
||||
pluginassets.ProvideService,
|
||||
plugininstaller.ProvidePreinstall,
|
||||
wire.Bind(new(plugininstaller.Preinstall), new(*plugininstaller.PreinstallImpl)),
|
||||
pluginchecker.ProvidePreinstall,
|
||||
wire.Bind(new(pluginchecker.Preinstall), new(*pluginchecker.PreinstallImpl)),
|
||||
advisor.ProvideService,
|
||||
wire.Bind(new(advisor.AdvisorStats), new(*advisor.Service)),
|
||||
pluginchecker.ProvideService,
|
||||
wire.Bind(new(pluginchecker.PluginUpdateChecker), new(*pluginchecker.Service)),
|
||||
)
|
||||
|
||||
// WireExtensionSet provides a wire.ProviderSet of plugin providers that can be
|
||||
|
@ -1,4 +1,4 @@
|
||||
package updatechecker
|
||||
package updatemanager
|
||||
|
||||
import (
|
||||
"context"
|
@ -1,4 +1,4 @@
|
||||
package updatechecker
|
||||
package updatemanager
|
||||
|
||||
import (
|
||||
"context"
|
@ -1,4 +1,4 @@
|
||||
package updatechecker
|
||||
package updatemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -7,35 +7,54 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||
"github.com/hashicorp/go-version"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"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"
|
||||
)
|
||||
|
||||
type PluginsService struct {
|
||||
availableUpdates map[string]string
|
||||
|
||||
enabled bool
|
||||
grafanaVersion string
|
||||
pluginStore pluginstore.Store
|
||||
httpClient httpClient
|
||||
mutex sync.RWMutex
|
||||
log log.Logger
|
||||
tracer tracing.Tracer
|
||||
updateCheckURL *url.URL
|
||||
type availableUpdate struct {
|
||||
localVersion string
|
||||
availableVersion string
|
||||
}
|
||||
|
||||
func ProvidePluginsService(cfg *setting.Cfg, pluginStore pluginstore.Store, tracer tracing.Tracer) (*PluginsService, error) {
|
||||
type PluginsService struct {
|
||||
availableUpdates map[string]availableUpdate
|
||||
|
||||
enabled bool
|
||||
grafanaVersion string
|
||||
pluginStore pluginstore.Store
|
||||
httpClient httpClient
|
||||
mutex sync.RWMutex
|
||||
log log.Logger
|
||||
tracer tracing.Tracer
|
||||
updateCheckURL *url.URL
|
||||
pluginInstaller plugins.Installer
|
||||
updateChecker *pluginchecker.Service
|
||||
updateStrategy string
|
||||
|
||||
features featuremgmt.FeatureToggles
|
||||
}
|
||||
|
||||
func ProvidePluginsService(cfg *setting.Cfg,
|
||||
pluginStore pluginstore.Store,
|
||||
pluginInstaller plugins.Installer,
|
||||
tracer tracing.Tracer,
|
||||
features featuremgmt.FeatureToggles,
|
||||
updateChecker *pluginchecker.Service,
|
||||
) (*PluginsService, error) {
|
||||
logger := log.New("plugins.update.checker")
|
||||
cl, err := httpclient.New(httpclient.Options{
|
||||
Middlewares: []httpclient.Middleware{
|
||||
@ -63,8 +82,12 @@ func ProvidePluginsService(cfg *setting.Cfg, pluginStore pluginstore.Store, trac
|
||||
log: logger,
|
||||
tracer: tracer,
|
||||
pluginStore: pluginStore,
|
||||
availableUpdates: make(map[string]string),
|
||||
availableUpdates: make(map[string]availableUpdate),
|
||||
updateCheckURL: parsedUpdateCheckURL,
|
||||
pluginInstaller: pluginInstaller,
|
||||
features: features,
|
||||
updateChecker: updateChecker,
|
||||
updateStrategy: cfg.PluginUpdateStrategy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -74,6 +97,9 @@ func (s *PluginsService) IsDisabled() bool {
|
||||
|
||||
func (s *PluginsService) Run(ctx context.Context) error {
|
||||
s.instrumentedCheckForUpdates(ctx)
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagPluginsAutoUpdate) {
|
||||
s.updateAll(ctx)
|
||||
}
|
||||
|
||||
ticker := time.NewTicker(time.Minute * 10)
|
||||
run := true
|
||||
@ -82,6 +108,9 @@ func (s *PluginsService) Run(ctx context.Context) error {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
s.instrumentedCheckForUpdates(ctx)
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagPluginsAutoUpdate) {
|
||||
s.updateAll(ctx)
|
||||
}
|
||||
case <-ctx.Done():
|
||||
run = false
|
||||
}
|
||||
@ -92,7 +121,7 @@ func (s *PluginsService) Run(ctx context.Context) error {
|
||||
|
||||
func (s *PluginsService) HasUpdate(ctx context.Context, pluginID string) (string, bool) {
|
||||
s.mutex.RLock()
|
||||
updateVers, updateAvailable := s.availableUpdates[pluginID]
|
||||
update, updateAvailable := s.availableUpdates[pluginID]
|
||||
s.mutex.RUnlock()
|
||||
if updateAvailable {
|
||||
// check if plugin has already been updated since the last invocation of `checkForUpdates`
|
||||
@ -101,8 +130,8 @@ func (s *PluginsService) HasUpdate(ctx context.Context, pluginID string) (string
|
||||
return "", false
|
||||
}
|
||||
|
||||
if canUpdate(plugin.Info.Version, updateVers) {
|
||||
return updateVers, true
|
||||
if s.canUpdate(ctx, plugin, update.availableVersion) {
|
||||
return update.availableVersion, true
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,11 +193,15 @@ func (s *PluginsService) checkForUpdates(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to unmarshal plugin repo, reading response from grafana.com: %w", err)
|
||||
}
|
||||
|
||||
availableUpdates := map[string]string{}
|
||||
availableUpdates := make(map[string]availableUpdate)
|
||||
|
||||
for _, gcomP := range gcomPlugins {
|
||||
if localP, exists := localPlugins[gcomP.Slug]; exists {
|
||||
if canUpdate(localP.Info.Version, gcomP.Version) {
|
||||
availableUpdates[localP.ID] = gcomP.Version
|
||||
if s.canUpdate(ctx, localP, gcomP.Version) {
|
||||
availableUpdates[localP.ID] = availableUpdate{
|
||||
localVersion: localP.Info.Version,
|
||||
availableVersion: gcomP.Version,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -182,17 +215,20 @@ func (s *PluginsService) checkForUpdates(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func canUpdate(v1, v2 string) bool {
|
||||
ver1, err1 := version.NewVersion(v1)
|
||||
if err1 != nil {
|
||||
return false
|
||||
}
|
||||
ver2, err2 := version.NewVersion(v2)
|
||||
if err2 != nil {
|
||||
func (s *PluginsService) canUpdate(ctx context.Context, plugin pluginstore.Plugin, gcomVersion string) bool {
|
||||
if !s.updateChecker.IsUpdatable(ctx, plugin) {
|
||||
return false
|
||||
}
|
||||
|
||||
return ver1.LessThan(ver2)
|
||||
if plugin.Info.Version == gcomVersion {
|
||||
return false
|
||||
}
|
||||
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagPluginsAutoUpdate) {
|
||||
return s.updateChecker.CanUpdate(plugin.ID, plugin.Info.Version, gcomVersion, s.updateStrategy == setting.PluginUpdateStrategyMinor)
|
||||
}
|
||||
|
||||
return s.updateChecker.CanUpdate(plugin.ID, plugin.Info.Version, gcomVersion, false)
|
||||
}
|
||||
|
||||
func (s *PluginsService) pluginIDsCSV(m map[string]pluginstore.Plugin) string {
|
||||
@ -215,3 +251,24 @@ func (s *PluginsService) pluginsEligibleForVersionCheck(ctx context.Context) map
|
||||
|
||||
return result
|
||||
}
|
||||
func (s *PluginsService) updateAll(ctx context.Context) {
|
||||
ctxLogger := s.log.FromContext(ctx)
|
||||
|
||||
failedUpdates := make(map[string]availableUpdate)
|
||||
|
||||
for pluginID, availableUpdate := range s.availableUpdates {
|
||||
compatOpts := plugins.NewAddOpts(s.grafanaVersion, runtime.GOOS, runtime.GOARCH, "")
|
||||
|
||||
ctxLogger.Info("Auto updating plugin", "pluginID", pluginID, "from", availableUpdate.localVersion, "to", availableUpdate.availableVersion)
|
||||
|
||||
err := s.pluginInstaller.Add(ctx, pluginID, availableUpdate.availableVersion, compatOpts)
|
||||
if err != nil {
|
||||
ctxLogger.Error("Failed to auto update plugin", "pluginID", pluginID, "from", availableUpdate.localVersion, "to", availableUpdate.availableVersion, "error", err)
|
||||
failedUpdates[pluginID] = availableUpdate
|
||||
}
|
||||
}
|
||||
|
||||
s.mutex.Lock()
|
||||
s.availableUpdates = failedUpdates
|
||||
s.mutex.Unlock()
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package updatechecker
|
||||
package updatemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
@ -13,16 +13,32 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"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"
|
||||
)
|
||||
|
||||
type mockPluginPreinstall struct {
|
||||
pluginchecker.Preinstall
|
||||
}
|
||||
|
||||
func (m *mockPluginPreinstall) IsPinned(pluginID string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
|
||||
t.Run("update is available", func(t *testing.T) {
|
||||
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
||||
|
||||
svc := PluginsService{
|
||||
availableUpdates: map[string]string{
|
||||
"test-ds": "1.0.0",
|
||||
availableUpdates: map[string]availableUpdate{
|
||||
"test-ds": {
|
||||
localVersion: "0.9.0",
|
||||
availableVersion: "1.0.0",
|
||||
},
|
||||
},
|
||||
pluginStore: &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
@ -35,6 +51,8 @@ func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
updateCheckURL: updateCheckURL,
|
||||
updateChecker: pluginchecker.ProvideService(managedplugins.NewNoop(), provisionedplugins.NewNoop(), &mockPluginPreinstall{}),
|
||||
features: &featuremgmt.FeatureManager{},
|
||||
}
|
||||
|
||||
update, exists := svc.HasUpdate(context.Background(), "test-ds")
|
||||
@ -46,9 +64,15 @@ func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
|
||||
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
||||
|
||||
svc := PluginsService{
|
||||
availableUpdates: map[string]string{
|
||||
"test-panel": "0.9.0",
|
||||
"test-app": "0.0.1",
|
||||
availableUpdates: map[string]availableUpdate{
|
||||
"test-panel": {
|
||||
localVersion: "0.9.0",
|
||||
availableVersion: "0.9.0",
|
||||
},
|
||||
"test-app": {
|
||||
localVersion: "0.9.0",
|
||||
availableVersion: "0.9.0",
|
||||
},
|
||||
},
|
||||
pluginStore: &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
@ -73,6 +97,7 @@ func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
|
||||
},
|
||||
},
|
||||
updateCheckURL: updateCheckURL,
|
||||
updateChecker: pluginchecker.ProvideService(managedplugins.NewNoop(), provisionedplugins.NewNoop(), &mockPluginPreinstall{}),
|
||||
}
|
||||
|
||||
update, exists := svc.HasUpdate(context.Background(), "test-ds")
|
||||
@ -92,8 +117,11 @@ func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
|
||||
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
||||
|
||||
svc := PluginsService{
|
||||
availableUpdates: map[string]string{
|
||||
"test-panel": "0.9.0",
|
||||
availableUpdates: map[string]availableUpdate{
|
||||
"test-panel": {
|
||||
localVersion: "0.9.0",
|
||||
availableVersion: "0.9.0",
|
||||
},
|
||||
},
|
||||
pluginStore: &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
@ -138,8 +166,11 @@ func TestPluginUpdateChecker_checkForUpdates(t *testing.T) {
|
||||
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
||||
|
||||
svc := PluginsService{
|
||||
availableUpdates: map[string]string{
|
||||
"test-app": "1.0.0",
|
||||
availableUpdates: map[string]availableUpdate{
|
||||
"test-app": {
|
||||
localVersion: "0.5.0",
|
||||
availableVersion: "1.0.0",
|
||||
},
|
||||
},
|
||||
pluginStore: &pluginstore.FakePluginStore{
|
||||
PluginList: []pluginstore.Plugin{
|
||||
@ -183,13 +214,15 @@ func TestPluginUpdateChecker_checkForUpdates(t *testing.T) {
|
||||
log: log.NewNopLogger(),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
updateCheckURL: updateCheckURL,
|
||||
updateChecker: pluginchecker.ProvideService(managedplugins.NewNoop(), provisionedplugins.NewNoop(), &mockPluginPreinstall{}),
|
||||
features: &featuremgmt.FeatureManager{},
|
||||
}
|
||||
|
||||
svc.instrumentedCheckForUpdates(context.Background())
|
||||
|
||||
require.Equal(t, 1, len(svc.availableUpdates))
|
||||
|
||||
require.Equal(t, "1.0.12", svc.availableUpdates["test-ds"])
|
||||
require.Equal(t, "1.0.12", svc.availableUpdates["test-ds"].availableVersion)
|
||||
update, exists := svc.HasUpdate(context.Background(), "test-ds")
|
||||
require.True(t, exists)
|
||||
require.Equal(t, "1.0.12", update)
|
||||
@ -207,6 +240,50 @@ func TestPluginUpdateChecker_checkForUpdates(t *testing.T) {
|
||||
require.Empty(t, svc.availableUpdates["test-core-panel"])
|
||||
})
|
||||
}
|
||||
func TestPluginUpdateChecker_updateAll(t *testing.T) {
|
||||
t.Run("update is available", func(t *testing.T) {
|
||||
pluginsFakeStore := map[string]string{}
|
||||
availableUpdates := map[string]availableUpdate{
|
||||
"test-app-0": {
|
||||
localVersion: "0.9.0",
|
||||
availableVersion: "1.0.0",
|
||||
},
|
||||
"test-app-1": {
|
||||
localVersion: "0.9.0",
|
||||
availableVersion: "1.0.0",
|
||||
},
|
||||
"test-app-2": {
|
||||
localVersion: "0.9.0",
|
||||
availableVersion: "1.0.0",
|
||||
},
|
||||
}
|
||||
|
||||
svc := PluginsService{
|
||||
availableUpdates: availableUpdates,
|
||||
log: log.NewNopLogger(),
|
||||
tracer: tracing.InitializeTracerForTest(),
|
||||
pluginInstaller: &fakes.FakePluginInstaller{
|
||||
AddFunc: func(ctx context.Context, pluginID, version string, opts plugins.AddOpts) error {
|
||||
pluginsFakeStore[pluginID] = version
|
||||
return nil
|
||||
},
|
||||
RemoveFunc: func(ctx context.Context, pluginID, version string) error {
|
||||
delete(pluginsFakeStore, pluginID)
|
||||
return nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
svc.updateAll(context.Background())
|
||||
|
||||
require.Equal(t, 0, len(svc.availableUpdates))
|
||||
require.Equal(t, len(availableUpdates), len(pluginsFakeStore))
|
||||
|
||||
for pluginID, availableUpdate := range availableUpdates {
|
||||
require.Equal(t, availableUpdate.availableVersion, pluginsFakeStore[pluginID])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type fakeHTTPClient struct {
|
||||
fakeResp string
|
@ -1,4 +1,4 @@
|
||||
package updatechecker
|
||||
package updatemanager
|
||||
|
||||
import "net/http"
|
||||
|
@ -206,6 +206,8 @@ type Cfg struct {
|
||||
PluginsCDNURLTemplate string
|
||||
PluginLogBackendRequests bool
|
||||
|
||||
PluginUpdateStrategy string
|
||||
|
||||
// Panels
|
||||
DisableSanitizeHtml bool
|
||||
|
||||
|
@ -8,6 +8,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
const (
|
||||
PluginUpdateStrategyLatest = "latest"
|
||||
PluginUpdateStrategyMinor = "minor"
|
||||
)
|
||||
|
||||
// PluginSettings maps plugin id to map of key/value settings.
|
||||
type PluginSettings map[string]map[string]string
|
||||
|
||||
@ -97,5 +102,7 @@ func (cfg *Cfg) readPluginSettings(iniFile *ini.File) error {
|
||||
cfg.PluginsCDNURLTemplate = strings.TrimRight(pluginsSection.Key("cdn_base_url").MustString(""), "/")
|
||||
cfg.PluginLogBackendRequests = pluginsSection.Key("log_backend_requests").MustBool(false)
|
||||
|
||||
cfg.PluginUpdateStrategy = pluginsSection.Key("update_strategy").In(PluginUpdateStrategyLatest, []string{PluginUpdateStrategyLatest, PluginUpdateStrategyMinor})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user