mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Check version compatibilty when installing a plugin (#92200)
This commit is contained in:
parent
f188da7b65
commit
1b336e94c8
@ -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
|
||||
|
@ -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}})
|
||||
}
|
||||
|
@ -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"`
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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{}
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
})
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user