[v8.3.x] Plugins: Refactor Grafana and Plugin version update checkers (#44662)

* 44529

* fix test
This commit is contained in:
Will Browne
2022-01-31 16:49:29 +01:00
committed by GitHub
parent 5b37d14b7f
commit ac8f1b5b6b
15 changed files with 554 additions and 264 deletions
+2 -2
View File
@@ -252,8 +252,8 @@ func (hs *HTTPServer) getFrontendSettingsMap(c *models.ReqContext) (map[string]i
"commit": commit,
"buildstamp": buildstamp,
"edition": hs.License.Edition(),
"latestVersion": hs.updateChecker.LatestGrafanaVersion(),
"hasUpdate": hs.updateChecker.GrafanaUpdateAvailable(),
"latestVersion": hs.grafanaUpdateChecker.LatestVersion(),
"hasUpdate": hs.grafanaUpdateChecker.UpdateAvailable(),
"env": setting.Env,
"isEnterprise": hs.License.HasValidLicense(),
},
+4 -4
View File
@@ -44,10 +44,10 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg) (*web.Mux, *HTTPServer
Cfg: cfg,
RendererPluginManager: &fakeRendererManager{},
},
SQLStore: sqlStore,
pluginStore: &fakePluginStore{},
updateChecker: &updatechecker.Service{},
AccessControl: accesscontrolmock.New().WithDisabled(),
SQLStore: sqlStore,
pluginStore: &fakePluginStore{},
grafanaUpdateChecker: &updatechecker.GrafanaService{},
AccessControl: accesscontrolmock.New().WithDisabled(),
}
m := web.New()
+6 -3
View File
@@ -114,7 +114,8 @@ type HTTPServer struct {
cleanUpService *cleanup.CleanUpService
tracingService *tracing.TracingService
internalMetricsSvc *metrics.InternalMetricsService
updateChecker *updatechecker.Service
grafanaUpdateChecker *updatechecker.GrafanaService
pluginsUpdateChecker *updatechecker.PluginsService
searchUsersService searchusers.Service
expressionService *expr.Service
}
@@ -142,7 +143,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
notificationService *notifications.NotificationService, tracingService *tracing.TracingService,
internalMetricsSvc *metrics.InternalMetricsService, quotaService *quota.QuotaService,
socialService social.Service, oauthTokenService oauthtoken.OAuthTokenService,
encryptionService encryption.Internal, updateChecker *updatechecker.Service, searchUsersService searchusers.Service,
grafanaUpdateChecker *updatechecker.GrafanaService, pluginsUpdateChecker *updatechecker.PluginsService,
encryptionService encryption.Internal, searchUsersService searchusers.Service,
dataSourcesService *datasources.Service, secretsService secrets.Service, expressionService *expr.Service) (*HTTPServer, error) {
web.Env = cfg.Env
m := web.New()
@@ -164,7 +166,8 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
pluginStaticRouteResolver: pluginStaticRouteResolver,
pluginDashboardManager: pluginDashboardManager,
pluginErrorResolver: pluginErrorResolver,
updateChecker: updateChecker,
grafanaUpdateChecker: grafanaUpdateChecker,
pluginsUpdateChecker: pluginsUpdateChecker,
SettingsProvider: settingsProvider,
DataSourceCache: dataSourceCache,
AuthTokenService: userTokenService,
+2 -2
View File
@@ -589,8 +589,8 @@ func (hs *HTTPServer) setIndexViewData(c *models.ReqContext) (*dtos.IndexViewDat
GoogleTagManagerId: setting.GoogleTagManagerId,
BuildVersion: setting.BuildVersion,
BuildCommit: setting.BuildCommit,
NewGrafanaVersion: hs.updateChecker.LatestGrafanaVersion(),
NewGrafanaVersionExists: hs.updateChecker.GrafanaUpdateAvailable(),
NewGrafanaVersion: hs.grafanaUpdateChecker.LatestVersion(),
NewGrafanaVersionExists: hs.grafanaUpdateChecker.UpdateAvailable(),
AppName: setting.ApplicationName,
AppNameBodyClass: getAppNameBodyClass(hs.License.HasValidLicense()),
FavIcon: "public/img/fav32.png",
+1
View File
@@ -115,6 +115,7 @@ func setup() *testContext {
nil,
nil,
nil,
nil,
sc,
nil,
)
+12 -4
View File
@@ -69,8 +69,6 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
Category: pluginDef.Category,
Info: pluginDef.Info,
Dependencies: pluginDef.Dependencies,
LatestVersion: pluginDef.GrafanaComVersion,
HasUpdate: pluginDef.GrafanaComHasUpdate,
DefaultNavUrl: pluginDef.DefaultNavURL,
State: pluginDef.State,
Signature: pluginDef.Signature,
@@ -78,6 +76,12 @@ func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
SignatureOrg: pluginDef.SignatureOrg,
}
update, exists := hs.pluginsUpdateChecker.HasUpdate(c.Req.Context(), pluginDef.ID)
if exists {
listItem.LatestVersion = update
listItem.HasUpdate = true
}
if pluginSetting, exists := pluginSettingsMap[pluginDef.ID]; exists {
listItem.Enabled = pluginSetting.Enabled
listItem.Pinned = pluginSetting.Pinned
@@ -122,8 +126,6 @@ func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Respon
BaseUrl: plugin.BaseURL,
Module: plugin.Module,
DefaultNavUrl: plugin.DefaultNavURL,
LatestVersion: plugin.GrafanaComVersion,
HasUpdate: plugin.GrafanaComHasUpdate,
State: plugin.State,
Signature: plugin.Signature,
SignatureType: plugin.SignatureType,
@@ -146,6 +148,12 @@ func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Respon
dto.JsonData = query.Result.JsonData
}
update, exists := hs.pluginsUpdateChecker.HasUpdate(c.Req.Context(), plugin.ID)
if exists {
dto.LatestVersion = update
dto.HasUpdate = true
}
return response.JSON(200, dto)
}
-18
View File
@@ -118,24 +118,6 @@ func (m *PluginManager) init() error {
}
func (m *PluginManager) Run(ctx context.Context) error {
if m.cfg.CheckForUpdates {
go func() {
m.checkForUpdates()
ticker := time.NewTicker(time.Minute * 10)
run := true
for run {
select {
case <-ticker.C:
m.checkForUpdates()
case <-ctx.Done():
run = false
}
}
}()
}
<-ctx.Done()
m.shutdown(ctx)
return ctx.Err()
-84
View File
@@ -1,84 +0,0 @@
package manager
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"time"
"github.com/hashicorp/go-version"
)
var (
httpClient = http.Client{Timeout: 10 * time.Second}
)
type gcomPlugin struct {
Slug string `json:"slug"`
Version string `json:"version"`
}
func (m *PluginManager) checkForUpdates() {
if !m.cfg.CheckForUpdates {
return
}
m.log.Debug("Checking for updates")
pluginIDs := m.pluginsEligibleForVersionCheck()
resp, err := httpClient.Get("https://grafana.com/api/plugins/versioncheck?slugIn=" + strings.Join(pluginIDs, ",") + "&grafanaVersion=" + m.cfg.BuildVersion)
if err != nil {
m.log.Debug("Failed to get plugins repo from grafana.com", "error", err.Error())
return
}
defer func() {
if err := resp.Body.Close(); err != nil {
m.log.Warn("Failed to close response body", "err", err)
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
m.log.Debug("Update check failed, reading response from grafana.com", "error", err.Error())
return
}
var gcomPlugins []gcomPlugin
err = json.Unmarshal(body, &gcomPlugins)
if err != nil {
m.log.Debug("Failed to unmarshal plugin repo, reading response from grafana.com", "error", err.Error())
return
}
for _, localP := range m.Plugins(context.TODO()) {
for _, gcomP := range gcomPlugins {
if gcomP.Slug == localP.ID {
localP.GrafanaComVersion = gcomP.Version
plugVersion, err1 := version.NewVersion(localP.Info.Version)
gplugVersion, err2 := version.NewVersion(gcomP.Version)
if err1 != nil || err2 != nil {
localP.GrafanaComHasUpdate = localP.Info.Version != localP.GrafanaComVersion
} else {
localP.GrafanaComHasUpdate = plugVersion.LessThan(gplugVersion)
}
}
}
}
}
func (m *PluginManager) pluginsEligibleForVersionCheck() []string {
var result []string
for _, p := range m.plugins() {
if p.IsCorePlugin() {
continue
}
result = append(result, p.ID)
}
return result
}
+14 -24
View File
@@ -32,10 +32,6 @@ type Plugin struct {
SignedFiles PluginFiles
SignatureError *SignatureError
// GCOM update checker fields
GrafanaComVersion string
GrafanaComHasUpdate bool
// SystemJS fields
Module string
BaseURL string
@@ -63,10 +59,6 @@ type PluginDTO struct {
SignedFiles PluginFiles
SignatureError *SignatureError
// GCOM update checker fields
GrafanaComVersion string
GrafanaComHasUpdate bool
// SystemJS fields
Module string
BaseURL string
@@ -315,22 +307,20 @@ func (p *Plugin) ToDTO() PluginDTO {
c, _ := p.Client()
return PluginDTO{
JSONData: p.JSONData,
PluginDir: p.PluginDir,
Class: p.Class,
IncludedInAppID: p.IncludedInAppID,
DefaultNavURL: p.DefaultNavURL,
Pinned: p.Pinned,
Signature: p.Signature,
SignatureType: p.SignatureType,
SignatureOrg: p.SignatureOrg,
SignedFiles: p.SignedFiles,
SignatureError: p.SignatureError,
GrafanaComVersion: p.GrafanaComVersion,
GrafanaComHasUpdate: p.GrafanaComHasUpdate,
Module: p.Module,
BaseURL: p.BaseURL,
StreamHandler: c,
JSONData: p.JSONData,
PluginDir: p.PluginDir,
Class: p.Class,
IncludedInAppID: p.IncludedInAppID,
DefaultNavURL: p.DefaultNavURL,
Pinned: p.Pinned,
Signature: p.Signature,
SignatureType: p.SignatureType,
SignatureOrg: p.SignatureOrg,
SignedFiles: p.SignedFiles,
SignatureError: p.SignatureError,
Module: p.Module,
BaseURL: p.BaseURL,
StreamHandler: c,
}
}
@@ -45,8 +45,9 @@ func ProvideBackgroundServiceRegistry(
live *live.GrafanaLive, pushGateway *pushhttp.Gateway, notifications *notifications.NotificationService,
rendering *rendering.RenderingService, tokenService models.UserTokenBackgroundService,
provisioning *provisioning.ProvisioningServiceImpl, alerting *alerting.AlertEngine, pm *manager.PluginManager,
metrics *metrics.InternalMetricsService, usageStats *uss.UsageStats, updateChecker *updatechecker.Service,
metrics *metrics.InternalMetricsService, usageStats *uss.UsageStats,
tracing *tracing.TracingService, remoteCache *remotecache.RemoteCache, secretsService *secretsManager.SecretsService,
grafanaUpdateChecker *updatechecker.GrafanaService, pluginsUpdateChecker *updatechecker.PluginsService,
// Need to make sure these are initialized, is there a better place to put them?
_ *azuremonitor.Service, _ *cloudwatch.CloudWatchService, _ *elasticsearch.Service, _ *graphite.Service,
_ *influxdb.Service, _ *loki.Service, _ *opentsdb.Service, _ *prometheus.Service, _ *tempo.Service,
@@ -66,7 +67,8 @@ func ProvideBackgroundServiceRegistry(
provisioning,
alerting,
pm,
updateChecker,
grafanaUpdateChecker,
pluginsUpdateChecker,
metrics,
usageStats,
tracing,
+2 -1
View File
@@ -96,7 +96,8 @@ var wireBasicSet = wire.NewSet(
hooks.ProvideService,
kvstore.ProvideService,
localcache.ProvideService,
updatechecker.ProvideService,
updatechecker.ProvideGrafanaService,
updatechecker.ProvidePluginsService,
uss.ProvideService,
wire.Bind(new(usagestats.Service), new(*uss.UsageStats)),
manager.ProvideService,
+115
View File
@@ -0,0 +1,115 @@
package updatechecker
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"sync"
"time"
"github.com/hashicorp/go-version"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
)
type GrafanaService struct {
hasUpdate bool
latestVersion string
enabled bool
grafanaVersion string
httpClient http.Client
mutex sync.RWMutex
log log.Logger
}
func ProvideGrafanaService(cfg *setting.Cfg) *GrafanaService {
return &GrafanaService{
enabled: cfg.CheckForUpdates,
grafanaVersion: cfg.BuildVersion,
httpClient: http.Client{Timeout: 10 * time.Second},
log: log.New("grafana.update.checker"),
}
}
func (s *GrafanaService) IsDisabled() bool {
return !s.enabled
}
func (s *GrafanaService) Run(ctx context.Context) error {
s.checkForUpdates()
ticker := time.NewTicker(time.Minute * 10)
run := true
for run {
select {
case <-ticker.C:
s.checkForUpdates()
case <-ctx.Done():
run = false
}
}
return ctx.Err()
}
func (s *GrafanaService) checkForUpdates() {
resp, err := s.httpClient.Get("https://raw.githubusercontent.com/grafana/grafana/main/latest.json")
if err != nil {
s.log.Debug("Failed to get latest.json repo from github.com", "error", err)
return
}
defer func() {
if err := resp.Body.Close(); err != nil {
s.log.Warn("Failed to close response body", "err", err)
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
s.log.Debug("Update check failed, reading response from github.com", "error", err)
return
}
type latestJSON struct {
Stable string `json:"stable"`
Testing string `json:"testing"`
}
var latest latestJSON
err = json.Unmarshal(body, &latest)
if err != nil {
s.log.Debug("Failed to unmarshal latest.json", "error", err)
return
}
s.mutex.Lock()
defer s.mutex.Unlock()
if strings.Contains(s.grafanaVersion, "-") {
s.latestVersion = latest.Testing
s.hasUpdate = !strings.HasPrefix(s.grafanaVersion, latest.Testing)
} else {
s.latestVersion = latest.Stable
s.hasUpdate = latest.Stable != s.grafanaVersion
}
currVersion, err1 := version.NewVersion(s.grafanaVersion)
latestVersion, err2 := version.NewVersion(s.latestVersion)
if err1 == nil && err2 == nil {
s.hasUpdate = currVersion.LessThan(latestVersion)
}
}
func (s *GrafanaService) UpdateAvailable() bool {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.hasUpdate
}
func (s *GrafanaService) LatestVersion() string {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.latestVersion
}
@@ -1,120 +0,0 @@
package updatechecker
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"sync"
"time"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/hashicorp/go-version"
)
var (
httpClient = http.Client{Timeout: 10 * time.Second}
logger = log.New("update.checker")
)
type latestJSON struct {
Stable string `json:"stable"`
Testing string `json:"testing"`
}
type Service struct {
cfg *setting.Cfg
hasUpdate bool
latestVersion string
mutex sync.RWMutex
}
func ProvideService(cfg *setting.Cfg) *Service {
s := newUpdateChecker(cfg)
return s
}
func newUpdateChecker(cfg *setting.Cfg) *Service {
return &Service{
cfg: cfg,
}
}
func (s *Service) IsDisabled() bool {
return !s.cfg.CheckForUpdates
}
func (s *Service) Run(ctx context.Context) error {
s.checkForUpdates()
ticker := time.NewTicker(time.Minute * 10)
run := true
for run {
select {
case <-ticker.C:
s.checkForUpdates()
case <-ctx.Done():
run = false
}
}
return ctx.Err()
}
func (s *Service) checkForUpdates() {
resp, err := httpClient.Get("https://raw.githubusercontent.com/grafana/grafana/main/latest.json")
if err != nil {
logger.Debug("Failed to get latest.json repo from github.com", "error", err)
return
}
defer func() {
if err := resp.Body.Close(); err != nil {
logger.Warn("Failed to close response body", "err", err)
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
logger.Debug("Update check failed, reading response from github.com", "error", err)
return
}
var latest latestJSON
err = json.Unmarshal(body, &latest)
if err != nil {
logger.Debug("Failed to unmarshal latest.json", "error", err)
return
}
s.mutex.Lock()
defer s.mutex.Unlock()
if strings.Contains(s.cfg.BuildVersion, "-") {
s.latestVersion = latest.Testing
s.hasUpdate = !strings.HasPrefix(s.cfg.BuildVersion, latest.Testing)
} else {
s.latestVersion = latest.Stable
s.hasUpdate = latest.Stable != s.cfg.BuildVersion
}
currVersion, err1 := version.NewVersion(s.cfg.BuildVersion)
latestVersion, err2 := version.NewVersion(s.latestVersion)
if err1 == nil && err2 == nil {
s.hasUpdate = currVersion.LessThan(latestVersion)
}
}
func (s *Service) GrafanaUpdateAvailable() bool {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.hasUpdate
}
func (s *Service) LatestGrafanaVersion() string {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.latestVersion
}
+167
View File
@@ -0,0 +1,167 @@
package updatechecker
import (
"context"
"encoding/json"
"io/ioutil"
"net/http"
"strings"
"sync"
"time"
"github.com/hashicorp/go-version"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
)
type PluginsService struct {
availableUpdates map[string]string
enabled bool
grafanaVersion string
pluginStore plugins.Store
httpClient httpClient
mutex sync.RWMutex
log log.Logger
}
func ProvidePluginsService(cfg *setting.Cfg, pluginStore plugins.Store) *PluginsService {
return &PluginsService{
enabled: cfg.CheckForUpdates,
grafanaVersion: cfg.BuildVersion,
httpClient: &http.Client{Timeout: 10 * time.Second},
log: log.New("plugins.update.checker"),
pluginStore: pluginStore,
availableUpdates: make(map[string]string),
}
}
type httpClient interface {
Get(url string) (resp *http.Response, err error)
}
func (s *PluginsService) IsDisabled() bool {
return !s.enabled
}
func (s *PluginsService) Run(ctx context.Context) error {
s.checkForUpdates(ctx)
ticker := time.NewTicker(time.Minute * 10)
run := true
for run {
select {
case <-ticker.C:
s.checkForUpdates(ctx)
case <-ctx.Done():
run = false
}
}
return ctx.Err()
}
func (s *PluginsService) HasUpdate(ctx context.Context, pluginID string) (string, bool) {
s.mutex.RLock()
updateVers, updateAvailable := s.availableUpdates[pluginID]
s.mutex.RUnlock()
if updateAvailable {
// check if plugin has already been updated since the last invocation of `checkForUpdates`
plugin, exists := s.pluginStore.Plugin(ctx, pluginID)
if !exists {
return "", false
}
if canUpdate(plugin.Info.Version, updateVers) {
return updateVers, true
}
}
return "", false
}
func (s *PluginsService) checkForUpdates(ctx context.Context) {
s.log.Debug("Checking for updates")
localPlugins := s.pluginsEligibleForVersionCheck(ctx)
resp, err := s.httpClient.Get("https://grafana.com/api/plugins/versioncheck?slugIn=" +
s.pluginIDsCSV(localPlugins) + "&grafanaVersion=" + s.grafanaVersion)
if err != nil {
s.log.Debug("Failed to get plugins repo from grafana.com", "error", err.Error())
return
}
defer func() {
if err := resp.Body.Close(); err != nil {
s.log.Warn("Failed to close response body", "err", err)
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
s.log.Debug("Update check failed, reading response from grafana.com", "error", err.Error())
return
}
type gcomPlugin struct {
Slug string `json:"slug"`
Version string `json:"version"`
}
var gcomPlugins []gcomPlugin
err = json.Unmarshal(body, &gcomPlugins)
if err != nil {
s.log.Debug("Failed to unmarshal plugin repo, reading response from grafana.com", "error", err.Error())
return
}
availableUpdates := map[string]string{}
for _, gcomP := range gcomPlugins {
if localP, exists := localPlugins[gcomP.Slug]; exists {
if canUpdate(localP.Info.Version, gcomP.Version) {
availableUpdates[localP.ID] = gcomP.Version
}
}
}
if len(availableUpdates) > 0 {
s.mutex.Lock()
s.availableUpdates = availableUpdates
s.mutex.Unlock()
}
}
func canUpdate(v1, v2 string) bool {
ver1, err1 := version.NewVersion(v1)
if err1 != nil {
return false
}
ver2, err2 := version.NewVersion(v2)
if err2 != nil {
return false
}
return ver1.LessThan(ver2)
}
func (s *PluginsService) pluginIDsCSV(m map[string]plugins.PluginDTO) string {
var ids []string
for pluginID := range m {
ids = append(ids, pluginID)
}
return strings.Join(ids, ",")
}
func (s *PluginsService) pluginsEligibleForVersionCheck(ctx context.Context) map[string]plugins.PluginDTO {
result := make(map[string]plugins.PluginDTO)
for _, p := range s.pluginStore.Plugins(ctx) {
if p.IsCorePlugin() {
continue
}
result[p.ID] = p
}
return result
}
+225
View File
@@ -0,0 +1,225 @@
package updatechecker
import (
"context"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
)
func TestPluginUpdateChecker_HasUpdate(t *testing.T) {
t.Run("update is available", func(t *testing.T) {
svc := PluginsService{
availableUpdates: map[string]string{
"test-ds": "1.0.0",
},
pluginStore: fakePluginStore{
plugins: map[string]plugins.PluginDTO{
"test-ds": {
JSONData: plugins.JSONData{
Info: plugins.Info{Version: "0.9.0"},
},
},
},
},
}
update, exists := svc.HasUpdate(context.Background(), "test-ds")
require.True(t, exists)
require.Equal(t, "1.0.0", update)
})
t.Run("update is not available", func(t *testing.T) {
svc := PluginsService{
availableUpdates: map[string]string{
"test-panel": "0.9.0",
"test-app": "0.0.1",
},
pluginStore: fakePluginStore{
plugins: map[string]plugins.PluginDTO{
"test-ds": {
JSONData: plugins.JSONData{
Info: plugins.Info{Version: "0.9.0"},
},
},
"test-panel": {
JSONData: plugins.JSONData{
Info: plugins.Info{Version: "0.9.0"},
},
},
"test-app": {
JSONData: plugins.JSONData{
Info: plugins.Info{Version: "0.9.0"},
},
},
},
},
}
update, exists := svc.HasUpdate(context.Background(), "test-ds")
require.False(t, exists)
require.Empty(t, update)
update, exists = svc.HasUpdate(context.Background(), "test-panel")
require.False(t, exists)
require.Empty(t, update)
update, exists = svc.HasUpdate(context.Background(), "test-app")
require.False(t, exists)
require.Empty(t, update)
})
t.Run("update is available but plugin is not in store", func(t *testing.T) {
svc := PluginsService{
availableUpdates: map[string]string{
"test-panel": "0.9.0",
},
pluginStore: fakePluginStore{
plugins: map[string]plugins.PluginDTO{
"test-ds": {
JSONData: plugins.JSONData{
Info: plugins.Info{Version: "1.0.0"},
},
},
},
},
}
update, exists := svc.HasUpdate(context.Background(), "test-panel")
require.False(t, exists)
require.Empty(t, update)
update, exists = svc.HasUpdate(context.Background(), "test-ds")
require.False(t, exists)
require.Empty(t, update)
})
}
func TestPluginUpdateChecker_checkForUpdates(t *testing.T) {
t.Run("update is available", func(t *testing.T) {
jsonResp := `[
{
"slug": "test-ds",
"version": "1.0.12"
},
{
"slug": "test-panel",
"version": "2.5.7"
},
{
"slug": "test-core-panel",
"version": "1.0.0"
}
]`
svc := PluginsService{
availableUpdates: map[string]string{
"test-app": "1.0.0",
},
pluginStore: fakePluginStore{
plugins: map[string]plugins.PluginDTO{
"test-ds": {
JSONData: plugins.JSONData{
ID: "test-ds",
Info: plugins.Info{Version: "0.9.0"},
},
},
"test-app": {
JSONData: plugins.JSONData{
ID: "test-app",
Info: plugins.Info{Version: "0.5.0"},
},
},
"test-panel": {
JSONData: plugins.JSONData{
ID: "test-panel",
Info: plugins.Info{Version: "2.5.7"},
},
},
"test-core-panel": {
Class: plugins.Core,
JSONData: plugins.JSONData{
ID: "test-core-panel",
Info: plugins.Info{Version: "0.0.1"},
},
},
},
},
httpClient: &fakeHTTPClient{
fakeResp: jsonResp,
},
log: &fakeLogger{},
}
svc.checkForUpdates(context.Background())
require.Equal(t, 1, len(svc.availableUpdates))
require.Equal(t, "1.0.12", svc.availableUpdates["test-ds"])
update, exists := svc.HasUpdate(context.Background(), "test-ds")
require.True(t, exists)
require.Equal(t, "1.0.12", update)
require.Empty(t, svc.availableUpdates["test-app"])
update, exists = svc.HasUpdate(context.Background(), "test-app")
require.False(t, exists)
require.Empty(t, update)
require.Empty(t, svc.availableUpdates["test-panel"])
update, exists = svc.HasUpdate(context.Background(), "test-panel")
require.False(t, exists)
require.Empty(t, update)
require.Empty(t, svc.availableUpdates["test-core-panel"])
})
}
type fakeHTTPClient struct {
fakeResp string
requestURL string
}
func (c *fakeHTTPClient) Get(url string) (*http.Response, error) {
c.requestURL = url
resp := &http.Response{
Body: ioutil.NopCloser(strings.NewReader(c.fakeResp)),
}
return resp, nil
}
type fakePluginStore struct {
plugins.Store
plugins map[string]plugins.PluginDTO
}
func (pr fakePluginStore) Plugin(_ context.Context, pluginID string) (plugins.PluginDTO, bool) {
p, exists := pr.plugins[pluginID]
return p, exists
}
func (pr fakePluginStore) Plugins(_ context.Context, _ ...plugins.Type) []plugins.PluginDTO {
var result []plugins.PluginDTO
for _, p := range pr.plugins {
result = append(result, p)
}
return result
}
type fakeLogger struct {
log.Logger
}
func (l *fakeLogger) Debug(_ string, _ ...interface{}) {}
func (l *fakeLogger) Warn(_ string, _ ...interface{}) {}