mirror of
https://github.com/grafana/grafana.git
synced 2024-11-28 11:44:26 -06:00
26dfdd5af3
* installer -> repo * add semver format checking * add plugin callbacks in test * remove newline * post install only scans new directories * remove unused stuff * everything in own package * add missing cli params * make grafana version part of the API * resolve conflicts * tidy up logger * fix cli and tidy log statements * rename log package * update struct name * fix linter issue * fs -> filestore * reorder imports * alias import * fix test * fix test * inline var * revert jsonc file * make repo dep of manager * actually inject the thing * accept all args for compatability checks * accept compat from store * pass os + arch vals * don't inject fs * tidy up * tidy up * merge with main and tidy fs storage * fix test * fix packages * fix comment + field name * update fs naming * fixed wire * remove unused func * fix mocks * fix storage test * renaming * fix log line * fix test * re-order field * tidying * add test for update with same version * fix wire for CLI * remove use of ioutil * don't pass field * small tidy * ignore code scanning warn * fix testdata link * update lgtm code
184 lines
4.9 KiB
Go
184 lines
4.9 KiB
Go
package repo
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
|
|
"github.com/grafana/grafana/pkg/plugins/logger"
|
|
)
|
|
|
|
type Manager struct {
|
|
client *Client
|
|
baseURL string
|
|
|
|
log logger.Logger
|
|
}
|
|
|
|
func ProvideService() *Manager {
|
|
defaultBaseURL := "https://grafana.com/api/plugins"
|
|
return New(false, defaultBaseURL, logger.NewLogger("plugin.repository"))
|
|
}
|
|
|
|
func New(skipTLSVerify bool, baseURL string, logger logger.Logger) *Manager {
|
|
return &Manager{
|
|
client: newClient(skipTLSVerify, logger),
|
|
baseURL: baseURL,
|
|
log: logger,
|
|
}
|
|
}
|
|
|
|
// GetPluginArchive fetches the requested plugin archive
|
|
func (m *Manager) GetPluginArchive(ctx context.Context, pluginID, version string, compatOpts CompatOpts) (*PluginArchive, error) {
|
|
dlOpts, err := m.GetPluginDownloadOptions(ctx, pluginID, version, compatOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return m.client.download(ctx, dlOpts.PluginZipURL, dlOpts.Checksum, compatOpts)
|
|
}
|
|
|
|
// GetPluginArchiveByURL fetches the requested plugin archive from the provided `pluginZipURL`
|
|
func (m *Manager) GetPluginArchiveByURL(ctx context.Context, pluginZipURL string, compatOpts CompatOpts) (*PluginArchive, error) {
|
|
return m.client.download(ctx, pluginZipURL, "", compatOpts)
|
|
}
|
|
|
|
// GetPluginDownloadOptions returns the options for downloading the requested plugin (with optional `version`)
|
|
func (m *Manager) GetPluginDownloadOptions(_ context.Context, pluginID, version string, compatOpts CompatOpts) (*PluginDownloadOptions, error) {
|
|
plugin, err := m.pluginMetadata(pluginID, compatOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
v, err := m.selectVersion(&plugin, version, compatOpts)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Plugins which are downloaded just as sourcecode zipball from GitHub do not have checksum
|
|
var checksum string
|
|
if v.Arch != nil {
|
|
archMeta, exists := v.Arch[compatOpts.OSAndArch()]
|
|
if !exists {
|
|
archMeta = v.Arch["any"]
|
|
}
|
|
checksum = archMeta.SHA256
|
|
}
|
|
|
|
return &PluginDownloadOptions{
|
|
Version: v.Version,
|
|
Checksum: checksum,
|
|
PluginZipURL: fmt.Sprintf("%s/%s/versions/%s/download", m.baseURL, pluginID, v.Version),
|
|
}, nil
|
|
}
|
|
|
|
func (m *Manager) pluginMetadata(pluginID string, compatOpts CompatOpts) (Plugin, error) {
|
|
m.log.Debugf("Fetching metadata for plugin \"%s\" from repo %s", pluginID, m.baseURL)
|
|
|
|
u, err := url.Parse(m.baseURL)
|
|
if err != nil {
|
|
return Plugin{}, err
|
|
}
|
|
u.Path = path.Join(u.Path, "repo", pluginID)
|
|
|
|
body, err := m.client.sendReq(u, compatOpts)
|
|
if err != nil {
|
|
return Plugin{}, err
|
|
}
|
|
|
|
var data Plugin
|
|
err = json.Unmarshal(body, &data)
|
|
if err != nil {
|
|
m.log.Error("Failed to unmarshal plugin repo response error", err)
|
|
return Plugin{}, err
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
// selectVersion selects the most appropriate plugin version
|
|
// returns the specified version if supported.
|
|
// returns the latest version if no specific version is specified.
|
|
// 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 (m *Manager) selectVersion(plugin *Plugin, version string, compatOpts CompatOpts) (*Version, error) {
|
|
version = normalizeVersion(version)
|
|
|
|
var ver Version
|
|
latestForArch := latestSupportedVersion(plugin, compatOpts)
|
|
if latestForArch == nil {
|
|
return nil, ErrVersionUnsupported{
|
|
PluginID: plugin.ID,
|
|
RequestedVersion: version,
|
|
SystemInfo: compatOpts.String(),
|
|
}
|
|
}
|
|
|
|
if version == "" {
|
|
return latestForArch, nil
|
|
}
|
|
for _, v := range plugin.Versions {
|
|
if v.Version == version {
|
|
ver = v
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(ver.Version) == 0 {
|
|
m.log.Debugf("Requested plugin version %s v%s not found but potential fallback version '%s' was found",
|
|
plugin.ID, version, latestForArch.Version)
|
|
return nil, ErrVersionNotFound{
|
|
PluginID: plugin.ID,
|
|
RequestedVersion: version,
|
|
SystemInfo: compatOpts.String(),
|
|
}
|
|
}
|
|
|
|
if !supportsCurrentArch(&ver, compatOpts) {
|
|
m.log.Debugf("Requested plugin version %s v%s is not supported on your system but potential fallback version '%s' was found",
|
|
plugin.ID, version, latestForArch.Version)
|
|
return nil, ErrVersionUnsupported{
|
|
PluginID: plugin.ID,
|
|
RequestedVersion: version,
|
|
SystemInfo: compatOpts.String(),
|
|
}
|
|
}
|
|
|
|
return &ver, nil
|
|
}
|
|
|
|
func supportsCurrentArch(version *Version, compatOpts CompatOpts) bool {
|
|
if version.Arch == nil {
|
|
return true
|
|
}
|
|
for arch := range version.Arch {
|
|
if arch == compatOpts.OSAndArch() || arch == "any" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func latestSupportedVersion(plugin *Plugin, compatOpts CompatOpts) *Version {
|
|
for _, v := range plugin.Versions {
|
|
ver := v
|
|
if supportsCurrentArch(&ver, compatOpts) {
|
|
return &ver
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func normalizeVersion(version string) string {
|
|
normalized := strings.ReplaceAll(version, " ", "")
|
|
if strings.HasPrefix(normalized, "^") || strings.HasPrefix(normalized, "v") {
|
|
return normalized[1:]
|
|
}
|
|
|
|
return normalized
|
|
}
|