Chore: Change endpoint to check versions in cli (#78008)

This commit is contained in:
Andres Martinez Gotor
2023-11-21 12:46:26 +01:00
committed by GitHub
parent 97c79f2c34
commit f8a6380510
5 changed files with 90 additions and 30 deletions

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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
} }

View File

@@ -23,6 +23,9 @@ func TestGetPluginArchive(t *testing.T) {
tcs := []struct { tcs := []struct {
name string name string
sha string sha string
apiOpSys string
apiArch string
apiUrl string
err error err error
}{ }{
{ {
@@ -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: opSys, opSys: tc.apiOpSys,
arch: arch, arch: tc.apiArch,
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),
)) ))
}) })

View File

@@ -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
} }