Chore: Check version compatibilty when installing a plugin (#92200)

This commit is contained in:
Andres Martinez Gotor 2024-08-22 13:47:00 +02:00 committed by GitHub
parent f188da7b65
commit 1b336e94c8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 89 additions and 35 deletions

View File

@ -134,7 +134,9 @@ func doInstallPlugin(ctx context.Context, pluginID, version string, o pluginInst
Logger: services.Logger,
})
compatOpts := repo.NewCompatOpts(services.GrafanaVersion, runtime.GOOS, runtime.GOARCH)
// FIXME: Re-enable grafanaVersion. This check was broken in 10.2 so disabling it for the moment.
// Expected to be re-enabled in 12.x.
compatOpts := repo.NewCompatOpts("", runtime.GOOS, runtime.GOARCH)
var archive *repo.PluginArchive
var err error

View File

@ -67,6 +67,10 @@ var (
ErrCorePluginMsg = "plugin {{.Public.PluginID}} is a core plugin and cannot be installed separately"
ErrCorePluginBase = errutil.Forbidden("plugin.forbiddenCorePluginInstall").
MustTemplate(ErrCorePluginMsg, errutil.WithPublic(ErrCorePluginMsg))
ErrNotCompatibledMsg = "{{.Public.PluginID}} is not compatible with your Grafana version: {{.Public.GrafanaVersion}}"
ErrNotCompatibleBase = errutil.NotFound("plugin.grafanaVersionNotCompatible").
MustTemplate(ErrNotCompatibledMsg, errutil.WithPublic(ErrNotCompatibledMsg))
)
func ErrVersionUnsupported(pluginID, requestedVersion, systemInfo string) error {
@ -88,3 +92,7 @@ func ErrChecksumMismatch(archiveURL string) error {
func ErrCorePlugin(pluginID string) error {
return ErrCorePluginBase.Build(errutil.TemplateData{Public: map[string]any{"PluginID": pluginID}})
}
func ErrNoCompatibleVersions(pluginID, grafanaVersion string) error {
return ErrNotCompatibleBase.Build(errutil.TemplateData{Public: map[string]any{"PluginID": pluginID, "GrafanaVersion": grafanaVersion}})
}

View File

@ -18,11 +18,17 @@ type PluginVersions struct {
}
type Version struct {
Version string `json:"version"`
Arch map[string]ArchMeta `json:"packages"`
URL string `json:"url"`
Version string `json:"version"`
Arch map[string]ArchMeta `json:"packages"`
URL string `json:"url"`
CreatedAt string `json:"createdAt"`
IsCompatible *bool `json:"isCompatible,omitempty"`
GrafanaDependency string `json:"grafanaDependency"`
}
type ArchMeta struct {
SHA256 string `json:"sha256"`
SHA256 string `json:"sha256"`
MD5 string `json:"md5"`
PackageName string `json:"packageName"`
DownloadURL string `json:"downloadUrl"`
}

View File

@ -3,7 +3,6 @@ package repo
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/url"
@ -84,12 +83,7 @@ func (m *Manager) PluginVersion(pluginID, version string, compatOpts CompatOpts)
return VersionData{}, err
}
sysCompatOpts, exists := compatOpts.System()
if !exists {
return VersionData{}, errors.New("no system compatibility requirements set")
}
compatibleVer, err := SelectSystemCompatibleVersion(m.log, versions, pluginID, version, sysCompatOpts)
compatibleVer, err := SelectSystemCompatibleVersion(m.log, versions, pluginID, version, compatOpts)
if err != nil {
return VersionData{}, err
}

View File

@ -174,7 +174,8 @@ func mockPluginVersionsAPI(t *testing.T, data srvData) *httptest.Server {
"sha256": "%s"
}
},
"url": "%s"
"url": "%s",
"isCompatible": true
}]
}
`, data.version, platform, data.sha, data.url),
@ -192,15 +193,17 @@ func mockPluginVersionsAPI(t *testing.T, data srvData) *httptest.Server {
}
type versionArg struct {
version string
arch []string
version string
arch []string
isCompatible *bool
}
func createPluginVersions(versions ...versionArg) []Version {
vs := make([]Version, len(versions))
for i, version := range versions {
ver := Version{
Version: version.version,
Version: version.version,
IsCompatible: version.isCompatible,
}
if version.arch != nil {
ver.Arch = map[string]ArchMeta{}

View File

@ -1,6 +1,8 @@
package repo
import (
"errors"
"slices"
"strings"
"github.com/grafana/grafana/pkg/plugins/log"
@ -19,19 +21,24 @@ type VersionData struct {
// returns error if the supplied version does not exist.
// returns error if supplied version exists but is not supported.
// NOTE: It expects plugin.Versions to be sorted so the newest version is first.
func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, pluginID, version string, compatOpts SystemCompatOpts) (VersionData, error) {
func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, pluginID, version string, compatOpts CompatOpts) (VersionData, error) {
version = normalizeVersion(version)
var ver Version
latestForArch, exists := latestSupportedVersion(versions, compatOpts)
sysCompatOpts, exists := compatOpts.System()
if !exists {
return VersionData{}, ErrArcNotFound(pluginID, compatOpts.OSAndArch())
return VersionData{}, errors.New("no system compatibility requirements set")
}
var ver Version
latestForArch, err := latestSupportedVersion(pluginID, versions, compatOpts)
if err != nil {
return VersionData{}, err
}
if version == "" {
return VersionData{
Version: latestForArch.Version,
Checksum: checksum(latestForArch, compatOpts),
Checksum: checksum(latestForArch, sysCompatOpts),
Arch: latestForArch.Arch,
URL: latestForArch.URL,
}, nil
@ -46,18 +53,18 @@ func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, plu
if len(ver.Version) == 0 {
log.Debugf("Requested plugin version %s v%s not found but potential fallback version '%s' was found",
pluginID, version, latestForArch.Version)
return VersionData{}, ErrVersionNotFound(pluginID, version, compatOpts.OSAndArch())
return VersionData{}, ErrVersionNotFound(pluginID, version, sysCompatOpts.OSAndArch())
}
if !supportsCurrentArch(ver, compatOpts) {
if !supportsCurrentArch(ver, sysCompatOpts) {
log.Debugf("Requested plugin version %s v%s is not supported on your system but potential fallback version '%s' was found",
pluginID, version, latestForArch.Version)
return VersionData{}, ErrVersionUnsupported(pluginID, version, compatOpts.OSAndArch())
return VersionData{}, ErrVersionUnsupported(pluginID, version, sysCompatOpts.OSAndArch())
}
return VersionData{
Version: ver.Version,
Checksum: checksum(ver, compatOpts),
Checksum: checksum(ver, sysCompatOpts),
Arch: ver.Arch,
URL: ver.URL,
}, nil
@ -86,13 +93,22 @@ func supportsCurrentArch(version Version, compatOpts SystemCompatOpts) bool {
return false
}
func latestSupportedVersion(versions []Version, compatOpts SystemCompatOpts) (Version, bool) {
func latestSupportedVersion(pluginID string, versions []Version, compatOpts CompatOpts) (Version, error) {
// First check if the version are compatible with the current Grafana version
versions = slices.DeleteFunc(versions, func(v Version) bool {
return v.IsCompatible != nil && !*v.IsCompatible
})
if len(versions) == 0 {
return Version{}, ErrNoCompatibleVersions(pluginID, compatOpts.grafanaVersion)
}
// Then check if the version are compatible with the current system
for _, v := range versions {
if supportsCurrentArch(v, compatOpts) {
return v, true
if supportsCurrentArch(v, compatOpts.system) {
return v, nil
}
}
return Version{}, false
return Version{}, ErrArcNotFound(pluginID, compatOpts.system.OSAndArch())
}
func normalizeVersion(version string) string {

View File

@ -8,15 +8,25 @@ import (
"github.com/grafana/grafana/pkg/plugins/log"
)
func fakeCompatOpts() CompatOpts {
return NewCompatOpts("7.0.0", "linux", "amd64")
}
func TestSelectSystemCompatibleVersion(t *testing.T) {
logger := log.NewTestPrettyLogger()
t.Run("Should return error when requested version does not exist", func(t *testing.T) {
_, err := SelectSystemCompatibleVersion(log.NewTestPrettyLogger(), createPluginVersions(versionArg{version: "version"}), "test", "1.1.1", SystemCompatOpts{})
_, err := SelectSystemCompatibleVersion(
log.NewTestPrettyLogger(),
createPluginVersions(versionArg{version: "version"}),
"test", "1.1.1", fakeCompatOpts())
require.Error(t, err)
})
t.Run("Should return error when no version supports current arch", func(t *testing.T) {
_, err := SelectSystemCompatibleVersion(logger, createPluginVersions(versionArg{version: "version", arch: []string{"non-existent"}}), "test", "", SystemCompatOpts{})
_, err := SelectSystemCompatibleVersion(
logger,
createPluginVersions(versionArg{version: "version", arch: []string{"non-existent"}}),
"test", "", fakeCompatOpts())
require.Error(t, err)
})
@ -24,7 +34,7 @@ func TestSelectSystemCompatibleVersion(t *testing.T) {
_, err := SelectSystemCompatibleVersion(logger, createPluginVersions(
versionArg{version: "2.0.0"},
versionArg{version: "1.1.1", arch: []string{"non-existent"}},
), "test", "1.1.1", SystemCompatOpts{})
), "test", "1.1.1", fakeCompatOpts())
require.Error(t, err)
})
@ -32,20 +42,35 @@ func TestSelectSystemCompatibleVersion(t *testing.T) {
ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(
versionArg{version: "2.0.0", arch: []string{"non-existent"}},
versionArg{version: "1.0.0"},
), "test", "", SystemCompatOpts{})
), "test", "", fakeCompatOpts())
require.NoError(t, err)
require.Equal(t, "1.0.0", ver.Version)
})
t.Run("Should return latest version when no version specified", func(t *testing.T) {
ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(versionArg{version: "2.0.0"}, versionArg{version: "1.0.0"}), "test", "", SystemCompatOpts{})
ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(
versionArg{version: "2.0.0"},
versionArg{version: "1.0.0"}),
"test", "", fakeCompatOpts())
require.NoError(t, err)
require.Equal(t, "2.0.0", ver.Version)
})
t.Run("Should return requested version", func(t *testing.T) {
ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(versionArg{version: "2.0.0"}, versionArg{version: "1.0.0"}), "test", "1.0.0", SystemCompatOpts{})
ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(
versionArg{version: "2.0.0"},
versionArg{version: "1.0.0"}),
"test", "1.0.0", fakeCompatOpts())
require.NoError(t, err)
require.Equal(t, "1.0.0", ver.Version)
})
t.Run("Should return error when requested version is not compatible", func(t *testing.T) {
isCompatible := false
_, err := SelectSystemCompatibleVersion(logger,
createPluginVersions(versionArg{version: "2.0.0", isCompatible: &isCompatible}),
"test", "2.0.0", fakeCompatOpts(),
)
require.ErrorContains(t, err, "not compatible")
})
}