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,
|
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 archive *repo.PluginArchive
|
||||||
var err error
|
var err error
|
||||||
|
@ -67,6 +67,10 @@ var (
|
|||||||
ErrCorePluginMsg = "plugin {{.Public.PluginID}} is a core plugin and cannot be installed separately"
|
ErrCorePluginMsg = "plugin {{.Public.PluginID}} is a core plugin and cannot be installed separately"
|
||||||
ErrCorePluginBase = errutil.Forbidden("plugin.forbiddenCorePluginInstall").
|
ErrCorePluginBase = errutil.Forbidden("plugin.forbiddenCorePluginInstall").
|
||||||
MustTemplate(ErrCorePluginMsg, errutil.WithPublic(ErrCorePluginMsg))
|
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 {
|
func ErrVersionUnsupported(pluginID, requestedVersion, systemInfo string) error {
|
||||||
@ -88,3 +92,7 @@ func ErrChecksumMismatch(archiveURL string) error {
|
|||||||
func ErrCorePlugin(pluginID string) error {
|
func ErrCorePlugin(pluginID string) error {
|
||||||
return ErrCorePluginBase.Build(errutil.TemplateData{Public: map[string]any{"PluginID": pluginID}})
|
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 {
|
type Version struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Arch map[string]ArchMeta `json:"packages"`
|
Arch map[string]ArchMeta `json:"packages"`
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
|
CreatedAt string `json:"createdAt"`
|
||||||
|
IsCompatible *bool `json:"isCompatible,omitempty"`
|
||||||
|
GrafanaDependency string `json:"grafanaDependency"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArchMeta struct {
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@ -84,12 +83,7 @@ func (m *Manager) PluginVersion(pluginID, version string, compatOpts CompatOpts)
|
|||||||
return VersionData{}, err
|
return VersionData{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sysCompatOpts, exists := compatOpts.System()
|
compatibleVer, err := SelectSystemCompatibleVersion(m.log, versions, pluginID, version, compatOpts)
|
||||||
if !exists {
|
|
||||||
return VersionData{}, errors.New("no system compatibility requirements set")
|
|
||||||
}
|
|
||||||
|
|
||||||
compatibleVer, err := SelectSystemCompatibleVersion(m.log, versions, pluginID, version, sysCompatOpts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return VersionData{}, err
|
return VersionData{}, err
|
||||||
}
|
}
|
||||||
|
@ -174,7 +174,8 @@ func mockPluginVersionsAPI(t *testing.T, data srvData) *httptest.Server {
|
|||||||
"sha256": "%s"
|
"sha256": "%s"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"url": "%s"
|
"url": "%s",
|
||||||
|
"isCompatible": true
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
`, data.version, platform, data.sha, data.url),
|
`, data.version, platform, data.sha, data.url),
|
||||||
@ -192,15 +193,17 @@ func mockPluginVersionsAPI(t *testing.T, data srvData) *httptest.Server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type versionArg struct {
|
type versionArg struct {
|
||||||
version string
|
version string
|
||||||
arch []string
|
arch []string
|
||||||
|
isCompatible *bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPluginVersions(versions ...versionArg) []Version {
|
func createPluginVersions(versions ...versionArg) []Version {
|
||||||
vs := make([]Version, len(versions))
|
vs := make([]Version, len(versions))
|
||||||
for i, version := range versions {
|
for i, version := range versions {
|
||||||
ver := Version{
|
ver := Version{
|
||||||
Version: version.version,
|
Version: version.version,
|
||||||
|
IsCompatible: version.isCompatible,
|
||||||
}
|
}
|
||||||
if version.arch != nil {
|
if version.arch != nil {
|
||||||
ver.Arch = map[string]ArchMeta{}
|
ver.Arch = map[string]ArchMeta{}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/plugins/log"
|
"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 the supplied version does not exist.
|
||||||
// returns error if supplied version exists but is not supported.
|
// returns error if supplied version exists but is not supported.
|
||||||
// NOTE: It expects plugin.Versions to be sorted so the newest version is first.
|
// 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)
|
version = normalizeVersion(version)
|
||||||
|
|
||||||
var ver Version
|
sysCompatOpts, exists := compatOpts.System()
|
||||||
latestForArch, exists := latestSupportedVersion(versions, compatOpts)
|
|
||||||
if !exists {
|
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 == "" {
|
if version == "" {
|
||||||
return VersionData{
|
return VersionData{
|
||||||
Version: latestForArch.Version,
|
Version: latestForArch.Version,
|
||||||
Checksum: checksum(latestForArch, compatOpts),
|
Checksum: checksum(latestForArch, sysCompatOpts),
|
||||||
Arch: latestForArch.Arch,
|
Arch: latestForArch.Arch,
|
||||||
URL: latestForArch.URL,
|
URL: latestForArch.URL,
|
||||||
}, nil
|
}, nil
|
||||||
@ -46,18 +53,18 @@ func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, plu
|
|||||||
if len(ver.Version) == 0 {
|
if len(ver.Version) == 0 {
|
||||||
log.Debugf("Requested plugin version %s v%s not found but potential fallback version '%s' was found",
|
log.Debugf("Requested plugin version %s v%s not found but potential fallback version '%s' was found",
|
||||||
pluginID, version, latestForArch.Version)
|
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",
|
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)
|
pluginID, version, latestForArch.Version)
|
||||||
return VersionData{}, ErrVersionUnsupported(pluginID, version, compatOpts.OSAndArch())
|
return VersionData{}, ErrVersionUnsupported(pluginID, version, sysCompatOpts.OSAndArch())
|
||||||
}
|
}
|
||||||
|
|
||||||
return VersionData{
|
return VersionData{
|
||||||
Version: ver.Version,
|
Version: ver.Version,
|
||||||
Checksum: checksum(ver, compatOpts),
|
Checksum: checksum(ver, sysCompatOpts),
|
||||||
Arch: ver.Arch,
|
Arch: ver.Arch,
|
||||||
URL: ver.URL,
|
URL: ver.URL,
|
||||||
}, nil
|
}, nil
|
||||||
@ -86,13 +93,22 @@ func supportsCurrentArch(version Version, compatOpts SystemCompatOpts) bool {
|
|||||||
return false
|
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 {
|
for _, v := range versions {
|
||||||
if supportsCurrentArch(v, compatOpts) {
|
if supportsCurrentArch(v, compatOpts.system) {
|
||||||
return v, true
|
return v, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Version{}, false
|
return Version{}, ErrArcNotFound(pluginID, compatOpts.system.OSAndArch())
|
||||||
}
|
}
|
||||||
|
|
||||||
func normalizeVersion(version string) string {
|
func normalizeVersion(version string) string {
|
||||||
|
@ -8,15 +8,25 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/plugins/log"
|
"github.com/grafana/grafana/pkg/plugins/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func fakeCompatOpts() CompatOpts {
|
||||||
|
return NewCompatOpts("7.0.0", "linux", "amd64")
|
||||||
|
}
|
||||||
|
|
||||||
func TestSelectSystemCompatibleVersion(t *testing.T) {
|
func TestSelectSystemCompatibleVersion(t *testing.T) {
|
||||||
logger := log.NewTestPrettyLogger()
|
logger := log.NewTestPrettyLogger()
|
||||||
t.Run("Should return error when requested version does not exist", func(t *testing.T) {
|
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)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should return error when no version supports current arch", func(t *testing.T) {
|
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)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -24,7 +34,7 @@ func TestSelectSystemCompatibleVersion(t *testing.T) {
|
|||||||
_, err := SelectSystemCompatibleVersion(logger, createPluginVersions(
|
_, err := SelectSystemCompatibleVersion(logger, createPluginVersions(
|
||||||
versionArg{version: "2.0.0"},
|
versionArg{version: "2.0.0"},
|
||||||
versionArg{version: "1.1.1", arch: []string{"non-existent"}},
|
versionArg{version: "1.1.1", arch: []string{"non-existent"}},
|
||||||
), "test", "1.1.1", SystemCompatOpts{})
|
), "test", "1.1.1", fakeCompatOpts())
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -32,20 +42,35 @@ func TestSelectSystemCompatibleVersion(t *testing.T) {
|
|||||||
ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(
|
ver, err := SelectSystemCompatibleVersion(logger, createPluginVersions(
|
||||||
versionArg{version: "2.0.0", arch: []string{"non-existent"}},
|
versionArg{version: "2.0.0", arch: []string{"non-existent"}},
|
||||||
versionArg{version: "1.0.0"},
|
versionArg{version: "1.0.0"},
|
||||||
), "test", "", SystemCompatOpts{})
|
), "test", "", fakeCompatOpts())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "1.0.0", ver.Version)
|
require.Equal(t, "1.0.0", ver.Version)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should return latest version when no version specified", func(t *testing.T) {
|
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.NoError(t, err)
|
||||||
require.Equal(t, "2.0.0", ver.Version)
|
require.Equal(t, "2.0.0", ver.Version)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("Should return requested version", func(t *testing.T) {
|
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.NoError(t, err)
|
||||||
require.Equal(t, "1.0.0", ver.Version)
|
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