Files
grafana/pkg/cmd/grafana-cli/commands/install_command.go
Will Browne 12dc56ad0c Plugins: Refactor plugin repository API (#69063)
* support grafana wildcard version

* undo go.mod changes

* tidy

* flesh out tests

* refactor

* add tests

* tidy naming

* undo some changes

* split interfaces

* separation

* update new signature

* simplify

* update var namings

* unexport types

* introduce opts pattern

* reorder test

* fix compat checks

* middle ground

* unexport client

* move back

* fix tests

* inline logger

* make client usable

* use fake logger

* tidy errors

* remove unused types

* fix test

* review fixes

* rework compatibility

* adjust installer

* fix tests

* opts => cfg

* remove unused var

* fix var name
2023-05-30 11:48:52 +02:00

154 lines
3.9 KiB
Go

package commands
import (
"context"
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/fatih/color"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/repo"
"github.com/grafana/grafana/pkg/plugins/storage"
)
func validateInput(c utils.CommandLine, pluginFolder string) error {
arg := c.Args().First()
if arg == "" {
return errors.New("please specify plugin to install")
}
pluginsDir := c.PluginDirectory()
if pluginsDir == "" {
return errors.New("missing pluginsDir flag")
}
fileInfo, err := os.Stat(pluginsDir)
if err != nil {
if err = os.MkdirAll(pluginsDir, os.ModePerm); err != nil {
return fmt.Errorf("pluginsDir (%s) is not a writable directory", pluginsDir)
}
return nil
}
if !fileInfo.IsDir() {
return errors.New("path is not a directory")
}
return nil
}
func logRestartNotice() {
logger.Info(color.GreenString("Please restart Grafana after installing or removing plugins. Refer to Grafana documentation for instructions if necessary.\n\n"))
}
func installCommand(c utils.CommandLine) error {
pluginFolder := c.PluginDirectory()
if err := validateInput(c, pluginFolder); err != nil {
return err
}
pluginID := c.Args().First()
version := c.Args().Get(1)
err := installPlugin(context.Background(), pluginID, version, c)
if err == nil {
logRestartNotice()
}
return err
}
// installPlugin downloads the plugin code as a zip file from the Grafana.com API
// and then extracts the zip into the plugin's directory.
func installPlugin(ctx context.Context, pluginID, version string, c utils.CommandLine) error {
repository := repo.NewManager(repo.ManagerCfg{
SkipTLSVerify: c.Bool("insecure"),
BaseURL: c.PluginRepoURL(),
Logger: services.Logger,
})
compatOpts := repo.NewCompatOpts(services.GrafanaVersion, runtime.GOOS, runtime.GOARCH)
var archive *repo.PluginArchive
var err error
pluginZipURL := c.PluginURL()
if pluginZipURL != "" {
if archive, err = repository.GetPluginArchiveByURL(ctx, pluginZipURL, compatOpts); err != nil {
return err
}
} else {
if archive, err = repository.GetPluginArchive(ctx, pluginID, version, compatOpts); err != nil {
return err
}
}
pluginFs := storage.FileSystem(services.Logger, c.PluginDirectory())
extractedArchive, err := pluginFs.Extract(ctx, pluginID, archive.File)
if err != nil {
return err
}
for _, dep := range extractedArchive.Dependencies {
services.Logger.Infof("Fetching %s dependency...", dep.ID)
d, err := repository.GetPluginArchive(ctx, dep.ID, dep.Version, compatOpts)
if err != nil {
return fmt.Errorf("%v: %w", fmt.Sprintf("failed to download plugin %s from repository", dep.ID), err)
}
_, err = pluginFs.Extract(ctx, dep.ID, d.File)
if err != nil {
return err
}
}
return nil
}
// uninstallPlugin removes the plugin directory
func uninstallPlugin(_ context.Context, pluginID string, c utils.CommandLine) error {
logger.Infof("Removing plugin: %v\n", pluginID)
pluginPath := filepath.Join(c.PluginDirectory(), pluginID)
fs := plugins.NewLocalFS(pluginPath)
logger.Debugf("Removing directory %v\n", pluginPath)
err := fs.Remove()
if err != nil {
return err
}
return nil
}
func osAndArchString() string {
osString := strings.ToLower(runtime.GOOS)
arch := runtime.GOARCH
return osString + "-" + arch
}
func supportsCurrentArch(version models.Version) bool {
if version.Arch == nil {
return true
}
for arch := range version.Arch {
if arch == osAndArchString() || arch == "any" {
return true
}
}
return false
}
func latestSupportedVersion(plugin models.Plugin) *models.Version {
for _, v := range plugin.Versions {
ver := v
if supportsCurrentArch(ver) {
return &ver
}
}
return nil
}