mirror of
https://github.com/grafana/grafana.git
synced 2025-07-28 22:16:06 +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/tag/ @grafana/grafana-search-and-storage
|
||||||
/pkg/services/team/ @grafana/access-squad
|
/pkg/services/team/ @grafana/access-squad
|
||||||
/pkg/services/temp_user/ @grafana/grafana-backend-group
|
/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/user/ @grafana/access-squad
|
||||||
/pkg/services/validations/ @grafana/grafana-backend-group
|
/pkg/services/validations/ @grafana/grafana-backend-group
|
||||||
/pkg/setting/ @grafana/grafana-backend-services-squad
|
/pkg/setting/ @grafana/grafana-backend-services-squad
|
||||||
|
@ -9,8 +9,8 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
"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/plugincontext"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
||||||
"github.com/grafana/grafana/pkg/services/ssosettings"
|
"github.com/grafana/grafana/pkg/services/ssosettings"
|
||||||
@ -27,7 +27,8 @@ type Service struct {
|
|||||||
pluginContextProvider *plugincontext.Provider
|
pluginContextProvider *plugincontext.Provider
|
||||||
pluginClient plugins.Client
|
pluginClient plugins.Client
|
||||||
pluginRepo repo.Service
|
pluginRepo repo.Service
|
||||||
pluginPreinstall plugininstaller.Preinstall
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
|
pluginPreinstall pluginchecker.Preinstall
|
||||||
managedPlugins managedplugins.Manager
|
managedPlugins managedplugins.Manager
|
||||||
provisionedPlugins provisionedplugins.Manager
|
provisionedPlugins provisionedplugins.Manager
|
||||||
ssoSettingsSvc ssosettings.Service
|
ssoSettingsSvc ssosettings.Service
|
||||||
@ -36,9 +37,9 @@ type Service struct {
|
|||||||
|
|
||||||
func ProvideService(datasourceSvc datasources.DataSourceService, pluginStore pluginstore.Store,
|
func ProvideService(datasourceSvc datasources.DataSourceService, pluginStore pluginstore.Store,
|
||||||
pluginContextProvider *plugincontext.Provider, pluginClient plugins.Client,
|
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,
|
provisionedPlugins provisionedplugins.Manager, ssoSettingsSvc ssosettings.Service, settings *setting.Cfg,
|
||||||
|
|
||||||
) *Service {
|
) *Service {
|
||||||
return &Service{
|
return &Service{
|
||||||
datasourceSvc: datasourceSvc,
|
datasourceSvc: datasourceSvc,
|
||||||
@ -46,6 +47,7 @@ func ProvideService(datasourceSvc datasources.DataSourceService, pluginStore plu
|
|||||||
pluginContextProvider: pluginContextProvider,
|
pluginContextProvider: pluginContextProvider,
|
||||||
pluginClient: pluginClient,
|
pluginClient: pluginClient,
|
||||||
pluginRepo: pluginRepo,
|
pluginRepo: pluginRepo,
|
||||||
|
updateChecker: updateChecker,
|
||||||
pluginPreinstall: pluginPreinstall,
|
pluginPreinstall: pluginPreinstall,
|
||||||
managedPlugins: managedPlugins,
|
managedPlugins: managedPlugins,
|
||||||
provisionedPlugins: provisionedPlugins,
|
provisionedPlugins: provisionedPlugins,
|
||||||
@ -67,9 +69,7 @@ func (s *Service) Checks() []checks.Check {
|
|||||||
plugincheck.New(
|
plugincheck.New(
|
||||||
s.pluginStore,
|
s.pluginStore,
|
||||||
s.pluginRepo,
|
s.pluginRepo,
|
||||||
s.pluginPreinstall,
|
s.updateChecker,
|
||||||
s.managedPlugins,
|
|
||||||
s.provisionedPlugins,
|
|
||||||
s.GrafanaVersion,
|
s.GrafanaVersion,
|
||||||
),
|
),
|
||||||
authchecks.New(s.ssoSettingsSvc),
|
authchecks.New(s.ssoSettingsSvc),
|
||||||
|
@ -4,17 +4,14 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
sysruntime "runtime"
|
sysruntime "runtime"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
"github.com/grafana/grafana-app-sdk/logging"
|
"github.com/grafana/grafana-app-sdk/logging"
|
||||||
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
advisor "github.com/grafana/grafana/apps/advisor/pkg/apis/advisor/v0alpha1"
|
||||||
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
"github.com/grafana/grafana/apps/advisor/pkg/app/checks"
|
||||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -26,27 +23,21 @@ const (
|
|||||||
func New(
|
func New(
|
||||||
pluginStore pluginstore.Store,
|
pluginStore pluginstore.Store,
|
||||||
pluginRepo repo.Service,
|
pluginRepo repo.Service,
|
||||||
pluginPreinstall plugininstaller.Preinstall,
|
updateChecker pluginchecker.PluginUpdateChecker,
|
||||||
managedPlugins managedplugins.Manager,
|
|
||||||
provisionedPlugins provisionedplugins.Manager,
|
|
||||||
grafanaVersion string,
|
grafanaVersion string,
|
||||||
) checks.Check {
|
) checks.Check {
|
||||||
return &check{
|
return &check{
|
||||||
PluginStore: pluginStore,
|
PluginStore: pluginStore,
|
||||||
PluginRepo: pluginRepo,
|
PluginRepo: pluginRepo,
|
||||||
PluginPreinstall: pluginPreinstall,
|
|
||||||
ManagedPlugins: managedPlugins,
|
|
||||||
ProvisionedPlugins: provisionedPlugins,
|
|
||||||
GrafanaVersion: grafanaVersion,
|
GrafanaVersion: grafanaVersion,
|
||||||
|
updateChecker: updateChecker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type check struct {
|
type check struct {
|
||||||
PluginStore pluginstore.Store
|
PluginStore pluginstore.Store
|
||||||
PluginRepo repo.Service
|
PluginRepo repo.Service
|
||||||
PluginPreinstall plugininstaller.Preinstall
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
ManagedPlugins managedplugins.Manager
|
|
||||||
ProvisionedPlugins provisionedplugins.Manager
|
|
||||||
GrafanaVersion string
|
GrafanaVersion string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,28 +66,21 @@ func (c *check) Steps() []checks.Step {
|
|||||||
return []checks.Step{
|
return []checks.Step{
|
||||||
&deprecationStep{
|
&deprecationStep{
|
||||||
PluginRepo: c.PluginRepo,
|
PluginRepo: c.PluginRepo,
|
||||||
PluginPreinstall: c.PluginPreinstall,
|
|
||||||
ManagedPlugins: c.ManagedPlugins,
|
|
||||||
ProvisionedPlugins: c.ProvisionedPlugins,
|
|
||||||
GrafanaVersion: c.GrafanaVersion,
|
GrafanaVersion: c.GrafanaVersion,
|
||||||
|
updateChecker: c.updateChecker,
|
||||||
},
|
},
|
||||||
&updateStep{
|
&updateStep{
|
||||||
PluginRepo: c.PluginRepo,
|
PluginRepo: c.PluginRepo,
|
||||||
PluginPreinstall: c.PluginPreinstall,
|
|
||||||
ManagedPlugins: c.ManagedPlugins,
|
|
||||||
ProvisionedPlugins: c.ProvisionedPlugins,
|
|
||||||
GrafanaVersion: c.GrafanaVersion,
|
GrafanaVersion: c.GrafanaVersion,
|
||||||
|
updateChecker: c.updateChecker,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type deprecationStep struct {
|
type deprecationStep struct {
|
||||||
PluginRepo repo.Service
|
PluginRepo repo.Service
|
||||||
PluginPreinstall plugininstaller.Preinstall
|
|
||||||
ManagedPlugins managedplugins.Manager
|
|
||||||
ProvisionedPlugins provisionedplugins.Manager
|
|
||||||
GrafanaVersion string
|
GrafanaVersion string
|
||||||
provisionedPlugins []string
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *deprecationStep) Title() string {
|
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)
|
return nil, fmt.Errorf("invalid item type %T", it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if it's a core plugin
|
if !s.updateChecker.IsUpdatable(ctx, p) {
|
||||||
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)
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,11 +136,8 @@ func (s *deprecationStep) Run(ctx context.Context, log logging.Logger, _ *adviso
|
|||||||
|
|
||||||
type updateStep struct {
|
type updateStep struct {
|
||||||
PluginRepo repo.Service
|
PluginRepo repo.Service
|
||||||
PluginPreinstall plugininstaller.Preinstall
|
|
||||||
ManagedPlugins managedplugins.Manager
|
|
||||||
ProvisionedPlugins provisionedplugins.Manager
|
|
||||||
provisionedPlugins []string
|
|
||||||
GrafanaVersion string
|
GrafanaVersion string
|
||||||
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *updateStep) Title() string {
|
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)
|
return nil, fmt.Errorf("invalid item type %T", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip if it's a core plugin
|
if !s.updateChecker.IsUpdatable(ctx, p) {
|
||||||
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)
|
|
||||||
return nil, nil
|
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
|
// In other case, assume that a different latest version will always be newer
|
||||||
return current.Info.Version != latest.Version
|
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"
|
||||||
"github.com/grafana/grafana/pkg/plugins/repo"
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
"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/pluginstore"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -163,7 +163,8 @@ func TestRun(t *testing.T) {
|
|||||||
pluginPreinstall := &mockPluginPreinstall{pinned: tt.pluginPreinstalled}
|
pluginPreinstall := &mockPluginPreinstall{pinned: tt.pluginPreinstalled}
|
||||||
managedPlugins := &mockManagedPlugins{managed: tt.pluginManaged}
|
managedPlugins := &mockManagedPlugins{managed: tt.pluginManaged}
|
||||||
provisionedPlugins := &mockProvisionedPlugins{provisioned: tt.pluginProvisioned}
|
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())
|
items, err := check.Items(context.Background())
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
@ -208,7 +209,7 @@ func (m *mockPluginRepo) GetPluginArchiveInfo(ctx context.Context, id, version s
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mockPluginPreinstall struct {
|
type mockPluginPreinstall struct {
|
||||||
plugininstaller.Preinstall
|
pluginchecker.Preinstall
|
||||||
pinned []string
|
pinned []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1840,6 +1840,9 @@ preinstall =
|
|||||||
preinstall_async = true
|
preinstall_async = true
|
||||||
# Disables preinstall feature. It has the same effect as setting preinstall to an empty list.
|
# Disables preinstall feature. It has the same effect as setting preinstall to an empty list.
|
||||||
preinstall_disabled = false
|
preinstall_disabled = false
|
||||||
|
# Update strategy for plugins.
|
||||||
|
# Available options: "latest", "minor"
|
||||||
|
update_strategy = minor
|
||||||
|
|
||||||
#################################### Grafana Live ##########################################
|
#################################### Grafana Live ##########################################
|
||||||
[live]
|
[live]
|
||||||
|
@ -38,7 +38,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/rendering"
|
"github.com/grafana/grafana/pkg/services/rendering"
|
||||||
"github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests"
|
"github.com/grafana/grafana/pkg/services/ssosettings/ssosettingstests"
|
||||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
"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/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"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),
|
SQLStore: db.InitTestDB(t),
|
||||||
SettingsProvider: setting.ProvideProvider(cfg),
|
SettingsProvider: setting.ProvideProvider(cfg),
|
||||||
pluginStore: pluginStore,
|
pluginStore: pluginStore,
|
||||||
grafanaUpdateChecker: &updatechecker.GrafanaService{},
|
grafanaUpdateChecker: &updatemanager.GrafanaService{},
|
||||||
AccessControl: accesscontrolmock.New(),
|
AccessControl: accesscontrolmock.New(),
|
||||||
PluginSettings: pluginsSettings,
|
PluginSettings: pluginsSettings,
|
||||||
pluginsCDNService: pluginsCDN,
|
pluginsCDNService: pluginsCDN,
|
||||||
|
@ -81,8 +81,8 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
"github.com/grafana/grafana/pkg/services/plugindashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
"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/plugincontext"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
|
||||||
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
pluginSettings "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
||||||
pref "github.com/grafana/grafana/pkg/services/preference"
|
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/tag"
|
||||||
"github.com/grafana/grafana/pkg/services/team"
|
"github.com/grafana/grafana/pkg/services/team"
|
||||||
tempUser "github.com/grafana/grafana/pkg/services/temp_user"
|
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/user"
|
||||||
"github.com/grafana/grafana/pkg/services/validations"
|
"github.com/grafana/grafana/pkg/services/validations"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
@ -150,7 +150,7 @@ type HTTPServer struct {
|
|||||||
pluginStaticRouteResolver plugins.StaticRouteResolver
|
pluginStaticRouteResolver plugins.StaticRouteResolver
|
||||||
pluginErrorResolver plugins.ErrorResolver
|
pluginErrorResolver plugins.ErrorResolver
|
||||||
pluginAssets *pluginassets.Service
|
pluginAssets *pluginassets.Service
|
||||||
pluginPreinstall plugininstaller.Preinstall
|
pluginPreinstall pluginchecker.Preinstall
|
||||||
SearchService search.Service
|
SearchService search.Service
|
||||||
ShortURLService shorturls.Service
|
ShortURLService shorturls.Service
|
||||||
QueryHistoryService queryhistory.Service
|
QueryHistoryService queryhistory.Service
|
||||||
@ -175,8 +175,8 @@ type HTTPServer struct {
|
|||||||
DataSourcesService datasources.DataSourceService
|
DataSourcesService datasources.DataSourceService
|
||||||
cleanUpService *cleanup.CleanUpService
|
cleanUpService *cleanup.CleanUpService
|
||||||
tracer tracing.Tracer
|
tracer tracing.Tracer
|
||||||
grafanaUpdateChecker *updatechecker.GrafanaService
|
grafanaUpdateChecker *updatemanager.GrafanaService
|
||||||
pluginsUpdateChecker *updatechecker.PluginsService
|
pluginsUpdateChecker *updatemanager.PluginsService
|
||||||
searchUsersService searchusers.Service
|
searchUsersService searchusers.Service
|
||||||
queryDataService query.Service
|
queryDataService query.Service
|
||||||
serviceAccountsService serviceaccounts.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,
|
contextHandler *contexthandler.ContextHandler, loggerMiddleware loggermw.Logger, features featuremgmt.FeatureToggles,
|
||||||
alertNG *ngalert.AlertNG, libraryPanelService librarypanels.Service, libraryElementService libraryelements.Service,
|
alertNG *ngalert.AlertNG, libraryPanelService librarypanels.Service, libraryElementService libraryelements.Service,
|
||||||
quotaService quota.Service, socialService social.Service, tracer tracing.Tracer,
|
quotaService quota.Service, socialService social.Service, tracer tracing.Tracer,
|
||||||
encryptionService encryption.Internal, grafanaUpdateChecker *updatechecker.GrafanaService,
|
encryptionService encryption.Internal, grafanaUpdateChecker *updatemanager.GrafanaService,
|
||||||
pluginsUpdateChecker *updatechecker.PluginsService, searchUsersService searchusers.Service,
|
pluginsUpdateChecker *updatemanager.PluginsService, searchUsersService searchusers.Service,
|
||||||
dataSourcesService datasources.DataSourceService, queryDataService query.Service, pluginFileStore plugins.FileStore,
|
dataSourcesService datasources.DataSourceService, queryDataService query.Service, pluginFileStore plugins.FileStore,
|
||||||
serviceaccountsService serviceaccounts.Service, pluginAssets *pluginassets.Service,
|
serviceaccountsService serviceaccounts.Service, pluginAssets *pluginassets.Service,
|
||||||
authInfoService login.AuthInfoService, storageService store.StorageService,
|
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,
|
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService, oauthTokenService oauthtoken.OAuthTokenService,
|
||||||
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service, promGatherer prometheus.Gatherer,
|
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service, promGatherer prometheus.Gatherer,
|
||||||
starApi *starApi.API, promRegister prometheus.Registerer, clientConfigProvider grafanaapiserver.DirectRestConfigProvider, anonService anonymous.Service,
|
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) {
|
) (*HTTPServer, error) {
|
||||||
web.Env = cfg.Env
|
web.Env = cfg.Env
|
||||||
m := web.New()
|
m := web.New()
|
||||||
|
@ -44,11 +44,13 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/managedplugins"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
"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/pluginerrs"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugininstaller"
|
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
|
"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/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
@ -112,7 +114,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
hs.managedPluginsService = managedplugins.NewNoop()
|
hs.managedPluginsService = managedplugins.NewNoop()
|
||||||
hs.pluginPreinstall = plugininstaller.ProvidePreinstall(hs.Cfg)
|
hs.pluginPreinstall = pluginchecker.ProvidePreinstall(hs.Cfg)
|
||||||
|
|
||||||
expectedIdentity := &authn.Identity{
|
expectedIdentity := &authn.Identity{
|
||||||
OrgID: tc.permissionOrg,
|
OrgID: tc.permissionOrg,
|
||||||
@ -644,7 +646,14 @@ func Test_PluginsList_AccessControl(t *testing.T) {
|
|||||||
hs.pluginFileStore = filestore.ProvideService(pluginRegistry)
|
hs.pluginFileStore = filestore.ProvideService(pluginRegistry)
|
||||||
hs.managedPluginsService = managedplugins.NewNoop()
|
hs.managedPluginsService = managedplugins.NewNoop()
|
||||||
var err error
|
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)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -836,7 +845,14 @@ func Test_PluginsSettings(t *testing.T) {
|
|||||||
hs.pluginAssets = pluginassets.ProvideService(pCfg, pluginCDN, sig, hs.pluginStore)
|
hs.pluginAssets = pluginassets.ProvideService(pCfg, pluginCDN, sig, hs.pluginStore)
|
||||||
hs.pluginErrorResolver = pluginerrs.ProvideStore(errTracker)
|
hs.pluginErrorResolver = pluginerrs.ProvideStore(errTracker)
|
||||||
var err error
|
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)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/store/sanitizer"
|
"github.com/grafana/grafana/pkg/services/store/sanitizer"
|
||||||
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
|
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlesimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/team/teamapi"
|
"github.com/grafana/grafana/pkg/services/team/teamapi"
|
||||||
"github.com/grafana/grafana/pkg/services/updatechecker"
|
"github.com/grafana/grafana/pkg/services/updatemanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ProvideBackgroundServiceRegistry(
|
func ProvideBackgroundServiceRegistry(
|
||||||
@ -55,8 +55,8 @@ func ProvideBackgroundServiceRegistry(
|
|||||||
pushGateway *pushhttp.Gateway, notifications *notifications.NotificationService, pluginStore *pluginStore.Service,
|
pushGateway *pushhttp.Gateway, notifications *notifications.NotificationService, pluginStore *pluginStore.Service,
|
||||||
rendering *rendering.RenderingService, tokenService auth.UserTokenBackgroundService, tracing *tracing.TracingService,
|
rendering *rendering.RenderingService, tokenService auth.UserTokenBackgroundService, tracing *tracing.TracingService,
|
||||||
provisioning *provisioning.ProvisioningServiceImpl, usageStats *uss.UsageStats,
|
provisioning *provisioning.ProvisioningServiceImpl, usageStats *uss.UsageStats,
|
||||||
statsCollector *statscollector.Service, grafanaUpdateChecker *updatechecker.GrafanaService,
|
statsCollector *statscollector.Service, grafanaUpdateChecker *updatemanager.GrafanaService,
|
||||||
pluginsUpdateChecker *updatechecker.PluginsService, metrics *metrics.InternalMetricsService,
|
pluginsUpdateChecker *updatemanager.PluginsService, metrics *metrics.InternalMetricsService,
|
||||||
secretsService *secretsManager.SecretsService, remoteCache *remotecache.RemoteCache, StorageService store.StorageService, searchService searchV2.SearchService, entityEventsService store.EntityEventsService,
|
secretsService *secretsManager.SecretsService, remoteCache *remotecache.RemoteCache, StorageService store.StorageService, searchService searchV2.SearchService, entityEventsService store.EntityEventsService,
|
||||||
saService *samanager.ServiceAccountsService, grpcServerProvider grpcserver.Provider,
|
saService *samanager.ServiceAccountsService, grpcServerProvider grpcserver.Provider,
|
||||||
secretMigrationProvider secretsMigrations.SecretMigrationProvider, loginAttemptService *loginattemptimpl.Service,
|
secretMigrationProvider secretsMigrations.SecretMigrationProvider, loginAttemptService *loginattemptimpl.Service,
|
||||||
|
@ -158,7 +158,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
"github.com/grafana/grafana/pkg/services/team/teamimpl"
|
||||||
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
|
tempuser "github.com/grafana/grafana/pkg/services/temp_user"
|
||||||
"github.com/grafana/grafana/pkg/services/temp_user/tempuserimpl"
|
"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"
|
||||||
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
@ -215,8 +215,8 @@ var wireBasicSet = wire.NewSet(
|
|||||||
localcache.ProvideService,
|
localcache.ProvideService,
|
||||||
bundleregistry.ProvideService,
|
bundleregistry.ProvideService,
|
||||||
wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)),
|
wire.Bind(new(supportbundles.Service), new(*bundleregistry.Service)),
|
||||||
updatechecker.ProvideGrafanaService,
|
updatemanager.ProvideGrafanaService,
|
||||||
updatechecker.ProvidePluginsService,
|
updatemanager.ProvidePluginsService,
|
||||||
uss.ProvideService,
|
uss.ProvideService,
|
||||||
wire.Bind(new(usagestats.Service), new(*uss.UsageStats)),
|
wire.Bind(new(usagestats.Service), new(*uss.UsageStats)),
|
||||||
validator.ProvideService,
|
validator.ProvideService,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package plugininstaller
|
package pluginchecker
|
||||||
|
|
||||||
import "github.com/grafana/grafana/pkg/setting"
|
import "github.com/grafana/grafana/pkg/setting"
|
||||||
|
|
@ -1,4 +1,4 @@
|
|||||||
package plugininstaller
|
package pluginchecker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"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"
|
"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/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginchecker"
|
||||||
"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"
|
||||||
@ -43,6 +43,7 @@ type Service struct {
|
|||||||
pluginRepo repo.Service
|
pluginRepo repo.Service
|
||||||
features featuremgmt.FeatureToggles
|
features featuremgmt.FeatureToggles
|
||||||
failOnErr bool
|
failOnErr bool
|
||||||
|
updateChecker pluginchecker.PluginUpdateChecker
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvideService(
|
func ProvideService(
|
||||||
@ -52,6 +53,7 @@ func ProvideService(
|
|||||||
promReg prometheus.Registerer,
|
promReg prometheus.Registerer,
|
||||||
pluginRepo repo.Service,
|
pluginRepo repo.Service,
|
||||||
features featuremgmt.FeatureToggles,
|
features featuremgmt.FeatureToggles,
|
||||||
|
updateChecker pluginchecker.PluginUpdateChecker,
|
||||||
) (*Service, error) {
|
) (*Service, error) {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
promReg.MustRegister(installRequestCounter)
|
promReg.MustRegister(installRequestCounter)
|
||||||
@ -66,6 +68,7 @@ func ProvideService(
|
|||||||
failOnErr: !cfg.PreinstallPluginsAsync, // Fail on error if preinstall is synchronous
|
failOnErr: !cfg.PreinstallPluginsAsync, // Fail on error if preinstall is synchronous
|
||||||
pluginRepo: pluginRepo,
|
pluginRepo: pluginRepo,
|
||||||
features: features,
|
features: features,
|
||||||
|
updateChecker: updateChecker,
|
||||||
}
|
}
|
||||||
if !cfg.PreinstallPluginsAsync {
|
if !cfg.PreinstallPluginsAsync {
|
||||||
// Block initialization process until plugins are installed
|
// Block initialization process until plugins are installed
|
||||||
@ -108,34 +111,7 @@ func (s *Service) shouldUpdate(ctx context.Context, pluginID, currentVersion str
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are already on the latest version, skip the installation
|
return s.updateChecker.CanUpdate(pluginID, currentVersion, info.Version, true)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) installPlugins(ctx context.Context) error {
|
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/manager/registry"
|
||||||
"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/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/pluginstore"
|
||||||
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/provisionedplugins"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -29,6 +32,7 @@ func TestService_IsDisabled(t *testing.T) {
|
|||||||
prometheus.NewRegistry(),
|
prometheus.NewRegistry(),
|
||||||
&fakes.FakePluginRepo{},
|
&fakes.FakePluginRepo{},
|
||||||
featuremgmt.WithFeatures(),
|
featuremgmt.WithFeatures(),
|
||||||
|
&pluginchecker.FakePluginUpdateChecker{},
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
@ -180,6 +184,11 @@ func TestService_Run(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
featuremgmt.WithFeatures(featuremgmt.FlagPreinstallAutoUpdate),
|
featuremgmt.WithFeatures(featuremgmt.FlagPreinstallAutoUpdate),
|
||||||
|
pluginchecker.ProvideService(
|
||||||
|
managedplugins.NewNoop(),
|
||||||
|
provisionedplugins.NewNoop(),
|
||||||
|
&pluginchecker.FakePluginPreinstall{},
|
||||||
|
),
|
||||||
)
|
)
|
||||||
if tt.blocking && !tt.shouldInstall {
|
if tt.blocking && !tt.shouldInstall {
|
||||||
require.ErrorContains(t, err, "Failed to install plugin")
|
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/managedplugins"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginassets"
|
"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/pluginconfig"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
||||||
@ -128,10 +129,12 @@ var WireSet = wire.NewSet(
|
|||||||
wire.Bind(new(plugincontext.BasePluginContextProvider), new(*plugincontext.BaseProvider)),
|
wire.Bind(new(plugincontext.BasePluginContextProvider), new(*plugincontext.BaseProvider)),
|
||||||
plugininstaller.ProvideService,
|
plugininstaller.ProvideService,
|
||||||
pluginassets.ProvideService,
|
pluginassets.ProvideService,
|
||||||
plugininstaller.ProvidePreinstall,
|
pluginchecker.ProvidePreinstall,
|
||||||
wire.Bind(new(plugininstaller.Preinstall), new(*plugininstaller.PreinstallImpl)),
|
wire.Bind(new(pluginchecker.Preinstall), new(*pluginchecker.PreinstallImpl)),
|
||||||
advisor.ProvideService,
|
advisor.ProvideService,
|
||||||
wire.Bind(new(advisor.AdvisorStats), new(*advisor.Service)),
|
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
|
// WireExtensionSet provides a wire.ProviderSet of plugin providers that can be
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package updatechecker
|
package updatemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,4 @@
|
|||||||
package updatechecker
|
package updatemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@ -1,4 +1,4 @@
|
|||||||
package updatechecker
|
package updatemanager
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -7,23 +7,31 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||||
"github.com/hashicorp/go-version"
|
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
|
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"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/services/pluginsintegration/pluginstore"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type availableUpdate struct {
|
||||||
|
localVersion string
|
||||||
|
availableVersion string
|
||||||
|
}
|
||||||
|
|
||||||
type PluginsService struct {
|
type PluginsService struct {
|
||||||
availableUpdates map[string]string
|
availableUpdates map[string]availableUpdate
|
||||||
|
|
||||||
enabled bool
|
enabled bool
|
||||||
grafanaVersion string
|
grafanaVersion string
|
||||||
@ -33,9 +41,20 @@ type PluginsService struct {
|
|||||||
log log.Logger
|
log log.Logger
|
||||||
tracer tracing.Tracer
|
tracer tracing.Tracer
|
||||||
updateCheckURL *url.URL
|
updateCheckURL *url.URL
|
||||||
|
pluginInstaller plugins.Installer
|
||||||
|
updateChecker *pluginchecker.Service
|
||||||
|
updateStrategy string
|
||||||
|
|
||||||
|
features featuremgmt.FeatureToggles
|
||||||
}
|
}
|
||||||
|
|
||||||
func ProvidePluginsService(cfg *setting.Cfg, pluginStore pluginstore.Store, tracer tracing.Tracer) (*PluginsService, error) {
|
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")
|
logger := log.New("plugins.update.checker")
|
||||||
cl, err := httpclient.New(httpclient.Options{
|
cl, err := httpclient.New(httpclient.Options{
|
||||||
Middlewares: []httpclient.Middleware{
|
Middlewares: []httpclient.Middleware{
|
||||||
@ -63,8 +82,12 @@ func ProvidePluginsService(cfg *setting.Cfg, pluginStore pluginstore.Store, trac
|
|||||||
log: logger,
|
log: logger,
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
pluginStore: pluginStore,
|
pluginStore: pluginStore,
|
||||||
availableUpdates: make(map[string]string),
|
availableUpdates: make(map[string]availableUpdate),
|
||||||
updateCheckURL: parsedUpdateCheckURL,
|
updateCheckURL: parsedUpdateCheckURL,
|
||||||
|
pluginInstaller: pluginInstaller,
|
||||||
|
features: features,
|
||||||
|
updateChecker: updateChecker,
|
||||||
|
updateStrategy: cfg.PluginUpdateStrategy,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,6 +97,9 @@ func (s *PluginsService) IsDisabled() bool {
|
|||||||
|
|
||||||
func (s *PluginsService) Run(ctx context.Context) error {
|
func (s *PluginsService) Run(ctx context.Context) error {
|
||||||
s.instrumentedCheckForUpdates(ctx)
|
s.instrumentedCheckForUpdates(ctx)
|
||||||
|
if s.features.IsEnabledGlobally(featuremgmt.FlagPluginsAutoUpdate) {
|
||||||
|
s.updateAll(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
ticker := time.NewTicker(time.Minute * 10)
|
ticker := time.NewTicker(time.Minute * 10)
|
||||||
run := true
|
run := true
|
||||||
@ -82,6 +108,9 @@ func (s *PluginsService) Run(ctx context.Context) error {
|
|||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
s.instrumentedCheckForUpdates(ctx)
|
s.instrumentedCheckForUpdates(ctx)
|
||||||
|
if s.features.IsEnabledGlobally(featuremgmt.FlagPluginsAutoUpdate) {
|
||||||
|
s.updateAll(ctx)
|
||||||
|
}
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
run = false
|
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) {
|
func (s *PluginsService) HasUpdate(ctx context.Context, pluginID string) (string, bool) {
|
||||||
s.mutex.RLock()
|
s.mutex.RLock()
|
||||||
updateVers, updateAvailable := s.availableUpdates[pluginID]
|
update, updateAvailable := s.availableUpdates[pluginID]
|
||||||
s.mutex.RUnlock()
|
s.mutex.RUnlock()
|
||||||
if updateAvailable {
|
if updateAvailable {
|
||||||
// check if plugin has already been updated since the last invocation of `checkForUpdates`
|
// 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
|
return "", false
|
||||||
}
|
}
|
||||||
|
|
||||||
if canUpdate(plugin.Info.Version, updateVers) {
|
if s.canUpdate(ctx, plugin, update.availableVersion) {
|
||||||
return updateVers, true
|
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)
|
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 {
|
for _, gcomP := range gcomPlugins {
|
||||||
if localP, exists := localPlugins[gcomP.Slug]; exists {
|
if localP, exists := localPlugins[gcomP.Slug]; exists {
|
||||||
if canUpdate(localP.Info.Version, gcomP.Version) {
|
if s.canUpdate(ctx, localP, gcomP.Version) {
|
||||||
availableUpdates[localP.ID] = 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func canUpdate(v1, v2 string) bool {
|
func (s *PluginsService) canUpdate(ctx context.Context, plugin pluginstore.Plugin, gcomVersion string) bool {
|
||||||
ver1, err1 := version.NewVersion(v1)
|
if !s.updateChecker.IsUpdatable(ctx, plugin) {
|
||||||
if err1 != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
ver2, err2 := version.NewVersion(v2)
|
|
||||||
if err2 != nil {
|
|
||||||
return false
|
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 {
|
func (s *PluginsService) pluginIDsCSV(m map[string]pluginstore.Plugin) string {
|
||||||
@ -215,3 +251,24 @@ func (s *PluginsService) pluginsEligibleForVersionCheck(ctx context.Context) map
|
|||||||
|
|
||||||
return result
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -13,16 +13,32 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||||
"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/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/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) {
|
func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
|
||||||
t.Run("update is available", func(t *testing.T) {
|
t.Run("update is available", func(t *testing.T) {
|
||||||
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
||||||
|
|
||||||
svc := PluginsService{
|
svc := PluginsService{
|
||||||
availableUpdates: map[string]string{
|
availableUpdates: map[string]availableUpdate{
|
||||||
"test-ds": "1.0.0",
|
"test-ds": {
|
||||||
|
localVersion: "0.9.0",
|
||||||
|
availableVersion: "1.0.0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
pluginStore: &pluginstore.FakePluginStore{
|
pluginStore: &pluginstore.FakePluginStore{
|
||||||
PluginList: []pluginstore.Plugin{
|
PluginList: []pluginstore.Plugin{
|
||||||
@ -35,6 +51,8 @@ func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
updateCheckURL: updateCheckURL,
|
updateCheckURL: updateCheckURL,
|
||||||
|
updateChecker: pluginchecker.ProvideService(managedplugins.NewNoop(), provisionedplugins.NewNoop(), &mockPluginPreinstall{}),
|
||||||
|
features: &featuremgmt.FeatureManager{},
|
||||||
}
|
}
|
||||||
|
|
||||||
update, exists := svc.HasUpdate(context.Background(), "test-ds")
|
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")
|
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
||||||
|
|
||||||
svc := PluginsService{
|
svc := PluginsService{
|
||||||
availableUpdates: map[string]string{
|
availableUpdates: map[string]availableUpdate{
|
||||||
"test-panel": "0.9.0",
|
"test-panel": {
|
||||||
"test-app": "0.0.1",
|
localVersion: "0.9.0",
|
||||||
|
availableVersion: "0.9.0",
|
||||||
|
},
|
||||||
|
"test-app": {
|
||||||
|
localVersion: "0.9.0",
|
||||||
|
availableVersion: "0.9.0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
pluginStore: &pluginstore.FakePluginStore{
|
pluginStore: &pluginstore.FakePluginStore{
|
||||||
PluginList: []pluginstore.Plugin{
|
PluginList: []pluginstore.Plugin{
|
||||||
@ -73,6 +97,7 @@ func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
updateCheckURL: updateCheckURL,
|
updateCheckURL: updateCheckURL,
|
||||||
|
updateChecker: pluginchecker.ProvideService(managedplugins.NewNoop(), provisionedplugins.NewNoop(), &mockPluginPreinstall{}),
|
||||||
}
|
}
|
||||||
|
|
||||||
update, exists := svc.HasUpdate(context.Background(), "test-ds")
|
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")
|
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
||||||
|
|
||||||
svc := PluginsService{
|
svc := PluginsService{
|
||||||
availableUpdates: map[string]string{
|
availableUpdates: map[string]availableUpdate{
|
||||||
"test-panel": "0.9.0",
|
"test-panel": {
|
||||||
|
localVersion: "0.9.0",
|
||||||
|
availableVersion: "0.9.0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
pluginStore: &pluginstore.FakePluginStore{
|
pluginStore: &pluginstore.FakePluginStore{
|
||||||
PluginList: []pluginstore.Plugin{
|
PluginList: []pluginstore.Plugin{
|
||||||
@ -138,8 +166,11 @@ func TestPluginUpdateChecker_checkForUpdates(t *testing.T) {
|
|||||||
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
updateCheckURL, _ := url.Parse("https://grafana.com/api/plugins/versioncheck")
|
||||||
|
|
||||||
svc := PluginsService{
|
svc := PluginsService{
|
||||||
availableUpdates: map[string]string{
|
availableUpdates: map[string]availableUpdate{
|
||||||
"test-app": "1.0.0",
|
"test-app": {
|
||||||
|
localVersion: "0.5.0",
|
||||||
|
availableVersion: "1.0.0",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
pluginStore: &pluginstore.FakePluginStore{
|
pluginStore: &pluginstore.FakePluginStore{
|
||||||
PluginList: []pluginstore.Plugin{
|
PluginList: []pluginstore.Plugin{
|
||||||
@ -183,13 +214,15 @@ func TestPluginUpdateChecker_checkForUpdates(t *testing.T) {
|
|||||||
log: log.NewNopLogger(),
|
log: log.NewNopLogger(),
|
||||||
tracer: tracing.InitializeTracerForTest(),
|
tracer: tracing.InitializeTracerForTest(),
|
||||||
updateCheckURL: updateCheckURL,
|
updateCheckURL: updateCheckURL,
|
||||||
|
updateChecker: pluginchecker.ProvideService(managedplugins.NewNoop(), provisionedplugins.NewNoop(), &mockPluginPreinstall{}),
|
||||||
|
features: &featuremgmt.FeatureManager{},
|
||||||
}
|
}
|
||||||
|
|
||||||
svc.instrumentedCheckForUpdates(context.Background())
|
svc.instrumentedCheckForUpdates(context.Background())
|
||||||
|
|
||||||
require.Equal(t, 1, len(svc.availableUpdates))
|
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")
|
update, exists := svc.HasUpdate(context.Background(), "test-ds")
|
||||||
require.True(t, exists)
|
require.True(t, exists)
|
||||||
require.Equal(t, "1.0.12", update)
|
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"])
|
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 {
|
type fakeHTTPClient struct {
|
||||||
fakeResp string
|
fakeResp string
|
@ -1,4 +1,4 @@
|
|||||||
package updatechecker
|
package updatemanager
|
||||||
|
|
||||||
import "net/http"
|
import "net/http"
|
||||||
|
|
@ -206,6 +206,8 @@ type Cfg struct {
|
|||||||
PluginsCDNURLTemplate string
|
PluginsCDNURLTemplate string
|
||||||
PluginLogBackendRequests bool
|
PluginLogBackendRequests bool
|
||||||
|
|
||||||
|
PluginUpdateStrategy string
|
||||||
|
|
||||||
// Panels
|
// Panels
|
||||||
DisableSanitizeHtml bool
|
DisableSanitizeHtml bool
|
||||||
|
|
||||||
|
@ -8,6 +8,11 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
PluginUpdateStrategyLatest = "latest"
|
||||||
|
PluginUpdateStrategyMinor = "minor"
|
||||||
|
)
|
||||||
|
|
||||||
// PluginSettings maps plugin id to map of key/value settings.
|
// PluginSettings maps plugin id to map of key/value settings.
|
||||||
type PluginSettings map[string]map[string]string
|
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.PluginsCDNURLTemplate = strings.TrimRight(pluginsSection.Key("cdn_base_url").MustString(""), "/")
|
||||||
cfg.PluginLogBackendRequests = pluginsSection.Key("log_backend_requests").MustBool(false)
|
cfg.PluginLogBackendRequests = pluginsSection.Key("log_backend_requests").MustBool(false)
|
||||||
|
|
||||||
|
cfg.PluginUpdateStrategy = pluginsSection.Key("update_strategy").In(PluginUpdateStrategyLatest, []string{PluginUpdateStrategyLatest, PluginUpdateStrategyMinor})
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user