mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Change endpoint to check versions in cli (#78008)
This commit is contained in:
committed by
GitHub
parent
97c79f2c34
commit
f8a6380510
@@ -79,3 +79,11 @@ type ErrChecksumMismatch struct {
|
|||||||
func (e ErrChecksumMismatch) Error() string {
|
func (e ErrChecksumMismatch) Error() string {
|
||||||
return fmt.Sprintf("expected SHA256 checksum does not match the downloaded archive (%s) - please contact security@grafana.com", e.archiveURL)
|
return fmt.Sprintf("expected SHA256 checksum does not match the downloaded archive (%s) - please contact security@grafana.com", e.archiveURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ErrCorePlugin struct {
|
||||||
|
id string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrCorePlugin) Error() string {
|
||||||
|
return fmt.Sprintf("plugin %s is a core plugin and cannot be installed separately", e.id)
|
||||||
|
}
|
||||||
|
@@ -12,14 +12,15 @@ type PluginArchiveInfo struct {
|
|||||||
Checksum string
|
Checksum string
|
||||||
}
|
}
|
||||||
|
|
||||||
// PluginRepo is (a subset of) the JSON response from /api/plugins/repo/$pluginID
|
// PluginVersions is the JSON response from /api/plugins/$pluginID/versions
|
||||||
type PluginRepo struct {
|
type PluginVersions struct {
|
||||||
Versions []Version `json:"versions"`
|
Versions []Version `json:"items"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Version struct {
|
type Version struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Arch map[string]ArchMeta `json:"arch"`
|
Arch map[string]ArchMeta `json:"packages"`
|
||||||
|
URL string `json:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArchMeta struct {
|
type ArchMeta struct {
|
||||||
|
@@ -5,8 +5,10 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/plugins/config"
|
"github.com/grafana/grafana/pkg/plugins/config"
|
||||||
"github.com/grafana/grafana/pkg/plugins/log"
|
"github.com/grafana/grafana/pkg/plugins/log"
|
||||||
@@ -87,7 +89,19 @@ func (m *Manager) PluginVersion(pluginID, version string, compatOpts CompatOpts)
|
|||||||
return VersionData{}, errors.New("no system compatibility requirements set")
|
return VersionData{}, errors.New("no system compatibility requirements set")
|
||||||
}
|
}
|
||||||
|
|
||||||
return SelectSystemCompatibleVersion(m.log, versions, pluginID, version, sysCompatOpts)
|
compatibleVer, err := SelectSystemCompatibleVersion(m.log, versions, pluginID, version, sysCompatOpts)
|
||||||
|
if err != nil {
|
||||||
|
return VersionData{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
isGrafanaCorePlugin := strings.HasPrefix(compatibleVer.URL, "https://github.com/grafana/grafana/tree/main/public/app/plugins/")
|
||||||
|
_, hasAnyArch := compatibleVer.Arch["any"]
|
||||||
|
if isGrafanaCorePlugin && hasAnyArch {
|
||||||
|
// Trying to install a coupled core plugin
|
||||||
|
return VersionData{}, ErrCorePlugin{id: pluginID}
|
||||||
|
}
|
||||||
|
|
||||||
|
return compatibleVer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) downloadURL(pluginID, version string) string {
|
func (m *Manager) downloadURL(pluginID, version string) string {
|
||||||
@@ -102,19 +116,25 @@ func (m *Manager) grafanaCompatiblePluginVersions(pluginID string, compatOpts Co
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
u.Path = path.Join(u.Path, "repo", pluginID)
|
u.Path = path.Join(u.Path, pluginID, "versions")
|
||||||
|
|
||||||
body, err := m.client.SendReq(u, compatOpts)
|
body, err := m.client.SendReq(u, compatOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var v PluginRepo
|
var v PluginVersions
|
||||||
err = json.Unmarshal(body, &v)
|
err = json.Unmarshal(body, &v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.log.Error("Failed to unmarshal plugin repo response", err)
|
m.log.Error("Failed to unmarshal plugin repo response", err)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(v.Versions) == 0 {
|
||||||
|
// /plugins/{pluginId}/versions returns 200 even if the plugin doesn't exists
|
||||||
|
// but the response is empty. In this case we return 404.
|
||||||
|
return nil, newErrResponse4xx(http.StatusNotFound).withMessage("Plugin not found")
|
||||||
|
}
|
||||||
|
|
||||||
return v.Versions, nil
|
return v.Versions, nil
|
||||||
}
|
}
|
||||||
|
@@ -21,9 +21,12 @@ const (
|
|||||||
|
|
||||||
func TestGetPluginArchive(t *testing.T) {
|
func TestGetPluginArchive(t *testing.T) {
|
||||||
tcs := []struct {
|
tcs := []struct {
|
||||||
name string
|
name string
|
||||||
sha string
|
sha string
|
||||||
err error
|
apiOpSys string
|
||||||
|
apiArch string
|
||||||
|
apiUrl string
|
||||||
|
err error
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "Happy path",
|
name: "Happy path",
|
||||||
@@ -34,6 +37,18 @@ func TestGetPluginArchive(t *testing.T) {
|
|||||||
sha: "1a2b3c",
|
sha: "1a2b3c",
|
||||||
err: &ErrChecksumMismatch{},
|
err: &ErrChecksumMismatch{},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "Core plugin",
|
||||||
|
sha: "69f698961b6ea651211a187874434821c4727cc22de022e3a7059116d21c75b1",
|
||||||
|
apiOpSys: "any",
|
||||||
|
apiUrl: "https://github.com/grafana/grafana/tree/main/public/app/plugins/test",
|
||||||
|
err: &ErrCorePlugin{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Decoupled core plugin",
|
||||||
|
sha: "69f698961b6ea651211a187874434821c4727cc22de022e3a7059116d21c75b1",
|
||||||
|
apiUrl: "https://github.com/grafana/grafana/tree/main/public/app/plugins/test",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pluginZip := createPluginArchive(t)
|
pluginZip := createPluginArchive(t)
|
||||||
@@ -57,17 +72,23 @@ func TestGetPluginArchive(t *testing.T) {
|
|||||||
grafanaVersion = "10.0.0"
|
grafanaVersion = "10.0.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
srv := mockPluginRepoAPI(t,
|
srvd := srvData{
|
||||||
srvData{
|
pluginID: pluginID,
|
||||||
pluginID: pluginID,
|
version: version,
|
||||||
version: version,
|
opSys: tc.apiOpSys,
|
||||||
opSys: opSys,
|
arch: tc.apiArch,
|
||||||
arch: arch,
|
url: tc.apiUrl,
|
||||||
grafanaVersion: grafanaVersion,
|
grafanaVersion: grafanaVersion,
|
||||||
sha: tc.sha,
|
sha: tc.sha,
|
||||||
archive: d,
|
archive: d,
|
||||||
},
|
}
|
||||||
)
|
if srvd.opSys == "" {
|
||||||
|
srvd.opSys = opSys
|
||||||
|
}
|
||||||
|
if srvd.arch == "" && srvd.opSys != "any" {
|
||||||
|
srvd.arch = arch
|
||||||
|
}
|
||||||
|
srv := mockPluginVersionsAPI(t, srvd)
|
||||||
t.Cleanup(srv.Close)
|
t.Cleanup(srv.Close)
|
||||||
|
|
||||||
m := NewManager(ManagerCfg{
|
m := NewManager(ManagerCfg{
|
||||||
@@ -125,34 +146,38 @@ type srvData struct {
|
|||||||
sha string
|
sha string
|
||||||
grafanaVersion string
|
grafanaVersion string
|
||||||
archive []byte
|
archive []byte
|
||||||
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
func mockPluginRepoAPI(t *testing.T, data srvData) *httptest.Server {
|
func mockPluginVersionsAPI(t *testing.T, data srvData) *httptest.Server {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
// mock plugin version data
|
// mock plugin version data
|
||||||
mux.HandleFunc(fmt.Sprintf("/repo/%s", data.pluginID), func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc(fmt.Sprintf("/%s/versions", data.pluginID), func(w http.ResponseWriter, r *http.Request) {
|
||||||
require.Equal(t, data.grafanaVersion, r.Header.Get("grafana-version"))
|
require.Equal(t, data.grafanaVersion, r.Header.Get("grafana-version"))
|
||||||
require.Equal(t, data.opSys, r.Header.Get("grafana-os"))
|
|
||||||
require.Equal(t, data.arch, r.Header.Get("grafana-arch"))
|
|
||||||
require.NotNil(t, fmt.Sprintf("grafana %s", data.grafanaVersion), r.Header.Get("User-Agent"))
|
require.NotNil(t, fmt.Sprintf("grafana %s", data.grafanaVersion), r.Header.Get("User-Agent"))
|
||||||
|
|
||||||
w.WriteHeader(http.StatusOK)
|
w.WriteHeader(http.StatusOK)
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
platform := data.opSys
|
||||||
|
if data.arch != "" {
|
||||||
|
platform += "-" + data.arch
|
||||||
|
}
|
||||||
_, _ = w.Write([]byte(fmt.Sprintf(`
|
_, _ = w.Write([]byte(fmt.Sprintf(`
|
||||||
{
|
{
|
||||||
"versions": [{
|
"items": [{
|
||||||
"version": "%s",
|
"version": "%s",
|
||||||
"arch": {
|
"packages": {
|
||||||
"%s-%s": {
|
"%s": {
|
||||||
"sha256": "%s"
|
"sha256": "%s"
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"url": "%s"
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
`, data.version, data.opSys, data.arch, data.sha),
|
`, data.version, platform, data.sha, data.url),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@@ -9,6 +9,8 @@ import (
|
|||||||
type VersionData struct {
|
type VersionData struct {
|
||||||
Version string
|
Version string
|
||||||
Checksum string
|
Checksum string
|
||||||
|
Arch map[string]ArchMeta
|
||||||
|
URL string
|
||||||
}
|
}
|
||||||
|
|
||||||
// SelectSystemCompatibleVersion selects the most appropriate plugin version based on os + architecture
|
// SelectSystemCompatibleVersion selects the most appropriate plugin version based on os + architecture
|
||||||
@@ -33,6 +35,8 @@ func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, plu
|
|||||||
return VersionData{
|
return VersionData{
|
||||||
Version: latestForArch.Version,
|
Version: latestForArch.Version,
|
||||||
Checksum: checksum(latestForArch, compatOpts),
|
Checksum: checksum(latestForArch, compatOpts),
|
||||||
|
Arch: latestForArch.Arch,
|
||||||
|
URL: latestForArch.URL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
for _, v := range versions {
|
for _, v := range versions {
|
||||||
@@ -65,6 +69,8 @@ func SelectSystemCompatibleVersion(log log.PrettyLogger, versions []Version, plu
|
|||||||
return VersionData{
|
return VersionData{
|
||||||
Version: ver.Version,
|
Version: ver.Version,
|
||||||
Checksum: checksum(ver, compatOpts),
|
Checksum: checksum(ver, compatOpts),
|
||||||
|
Arch: ver.Arch,
|
||||||
|
URL: ver.URL,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user