From 6cd042ed166888a31853477e023dcb504d06f50d Mon Sep 17 00:00:00 2001 From: Will Browne Date: Wed, 3 May 2023 14:52:57 +0200 Subject: [PATCH] Plugins: Remove dead CLI code and use pkg/plugins for uninstall process (#67711) * remove dead code and use pkg/plugins for uninstall process * fix linter --- pkg/cmd/grafana-cli/commands/cli.go | 2 +- .../grafana-cli/commands/install_command.go | 17 ++++ .../grafana-cli/commands/remove_command.go | 14 +--- .../commands/upgrade_all_command.go | 5 +- .../grafana-cli/commands/upgrade_command.go | 20 ++--- pkg/cmd/grafana-cli/services/api_client.go | 78 +------------------ pkg/cmd/grafana-cli/services/services.go | 28 ++----- pkg/cmd/grafana-cli/utils/command_line.go | 3 - 8 files changed, 42 insertions(+), 125 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/cli.go b/pkg/cmd/grafana-cli/commands/cli.go index 4d421fedd6d..9865c137800 100644 --- a/pkg/cmd/grafana-cli/commands/cli.go +++ b/pkg/cmd/grafana-cli/commands/cli.go @@ -11,7 +11,7 @@ import ( "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" ) -// RunCLI is the entrypoint for the grafana-cli command. It returns the exit code for the grafana-cli program. +// CLICommand is the entrypoint for the grafana-cli command. It returns the exit code for the grafana-cli program. func CLICommand(version string) *cli.Command { return &cli.Command{ Name: "cli", diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 35a8cd2a0ec..f799a847088 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "os" + "path/filepath" "runtime" "strings" @@ -13,6 +14,7 @@ import ( "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" ) @@ -104,6 +106,21 @@ func installPlugin(ctx context.Context, pluginID, version string, c utils.Comman 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 diff --git a/pkg/cmd/grafana-cli/commands/remove_command.go b/pkg/cmd/grafana-cli/commands/remove_command.go index 953bb7e7583..2c29dfd709f 100644 --- a/pkg/cmd/grafana-cli/commands/remove_command.go +++ b/pkg/cmd/grafana-cli/commands/remove_command.go @@ -1,31 +1,25 @@ package commands import ( + "context" "errors" "fmt" "strings" - "github.com/grafana/grafana/pkg/cmd/grafana-cli/services" "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" ) -var removePlugin func(pluginPath, id string) error = services.RemoveInstalledPlugin - func (cmd Command) removeCommand(c utils.CommandLine) error { - pluginPath := c.PluginDirectory() - - plugin := c.Args().First() - if plugin == "" { + pluginID := c.Args().First() + if pluginID == "" { return errors.New("missing plugin parameter") } - err := removePlugin(pluginPath, plugin) - + err := uninstallPlugin(context.Background(), pluginID, c) if err != nil { if strings.Contains(err.Error(), "no such file or directory") { return fmt.Errorf("plugin does not exist") } - return err } else { logRestartNotice() diff --git a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go index a2309877a33..e3333073775 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_all_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_all_command.go @@ -49,15 +49,16 @@ func (cmd Command) upgradeAllCommand(c utils.CommandLine) error { } } + ctx := context.Background() for _, p := range pluginsToUpgrade { logger.Infof("Updating %v \n", p.ID) - err := services.RemoveInstalledPlugin(pluginsDir, p.ID) + err = uninstallPlugin(ctx, p.ID, c) if err != nil { return err } - err = installPlugin(context.Background(), p.ID, "", c) + err = installPlugin(ctx, p.ID, "", c) if err != nil { return err } diff --git a/pkg/cmd/grafana-cli/commands/upgrade_command.go b/pkg/cmd/grafana-cli/commands/upgrade_command.go index 81ba757c4ac..d80a6beb908 100644 --- a/pkg/cmd/grafana-cli/commands/upgrade_command.go +++ b/pkg/cmd/grafana-cli/commands/upgrade_command.go @@ -12,32 +12,32 @@ import ( ) func (cmd Command) upgradeCommand(c utils.CommandLine) error { + ctx := context.Background() pluginsDir := c.PluginDirectory() - pluginName := c.Args().First() - - localPlugin, err := services.ReadPlugin(pluginsDir, pluginName) + pluginID := c.Args().First() + localPlugin, err := services.ReadPlugin(pluginsDir, pluginID) if err != nil { return err } - plugin, err2 := cmd.Client.GetPlugin(pluginName, c.PluginRepoURL()) - if err2 != nil { - return err2 + plugin, err := cmd.Client.GetPlugin(pluginID, c.PluginRepoURL()) + if err != nil { + return err } if shouldUpgrade(localPlugin.Info.Version, &plugin) { - if err := services.RemoveInstalledPlugin(pluginsDir, pluginName); err != nil { - return fmt.Errorf("failed to remove plugin '%s': %w", pluginName, err) + if err = uninstallPlugin(ctx, pluginID, c); err != nil { + return fmt.Errorf("failed to remove plugin '%s': %w", pluginID, err) } - err := installPlugin(context.Background(), pluginName, "", c) + err = installPlugin(ctx, pluginID, "", c) if err == nil { logRestartNotice() } return err } - logger.Infof("%s %s is up to date \n", color.GreenString("✔"), pluginName) + logger.Infof("%s %s is up to date \n", color.GreenString("✔"), pluginID) return nil } diff --git a/pkg/cmd/grafana-cli/services/api_client.go b/pkg/cmd/grafana-cli/services/api_client.go index 8fa93c24390..c9f8e9a506a 100644 --- a/pkg/cmd/grafana-cli/services/api_client.go +++ b/pkg/cmd/grafana-cli/services/api_client.go @@ -1,15 +1,12 @@ package services import ( - "bufio" - "crypto/sha256" "encoding/json" "errors" "fmt" "io" "net/http" "net/url" - "os" "path" "runtime" @@ -17,9 +14,7 @@ import ( "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" ) -type GrafanaComClient struct { - retryCount int -} +type GrafanaComClient struct{} func (client *GrafanaComClient) GetPlugin(pluginId, repoUrl string) (models.Plugin, error) { logger.Debugf("getting plugin metadata from: %v pluginId: %v \n", repoUrl, pluginId) @@ -42,77 +37,6 @@ func (client *GrafanaComClient) GetPlugin(pluginId, repoUrl string) (models.Plug return data, nil } -func (client *GrafanaComClient) DownloadFile(pluginName string, tmpFile *os.File, url string, checksum string) (err error) { - // Try handling URL as a local file path first - if _, err := os.Stat(url); err == nil { - // We can ignore this gosec G304 warning since `url` stems from command line flag "pluginUrl". If the - // user shouldn't be able to read the file, it should be handled through filesystem permissions. - // nolint:gosec - f, err := os.Open(url) - if err != nil { - return fmt.Errorf("%v: %w", "Failed to read plugin archive", err) - } - _, err = io.Copy(tmpFile, f) - if err != nil { - return fmt.Errorf("%v: %w", "Failed to copy plugin archive", err) - } - return nil - } - - client.retryCount = 0 - - defer func() { - if r := recover(); r != nil { - client.retryCount++ - if client.retryCount < 3 { - logger.Info("Failed downloading. Will retry once.") - err = tmpFile.Truncate(0) - if err != nil { - return - } - _, err = tmpFile.Seek(0, 0) - if err != nil { - return - } - err = client.DownloadFile(pluginName, tmpFile, url, checksum) - } else { - client.retryCount = 0 - failure := fmt.Sprintf("%v", r) - if failure == "runtime error: makeslice: len out of range" { - err = fmt.Errorf("corrupt HTTP response from source, please try again") - } else { - panic(r) - } - } - } - }() - - // Using no timeout here as some plugins can be bigger and smaller timeout would prevent to download a plugin on - // slow network. As this is CLI operation hanging is not a big of an issue as user can just abort. - bodyReader, err := sendRequest(HttpClientNoTimeout, url) - if err != nil { - return fmt.Errorf("%v: %w", "Failed to send request", err) - } - defer func() { - if err := bodyReader.Close(); err != nil { - logger.Warn("Failed to close body", "err", err) - } - }() - - w := bufio.NewWriter(tmpFile) - h := sha256.New() - if _, err = io.Copy(w, io.TeeReader(bodyReader, h)); err != nil { - return fmt.Errorf("%v: %w", "failed to compute SHA256 checksum", err) - } - if err := w.Flush(); err != nil { - return fmt.Errorf("failed to write to %q: %w", tmpFile.Name(), err) - } - if len(checksum) > 0 && checksum != fmt.Sprintf("%x", h.Sum(nil)) { - return fmt.Errorf("expected SHA256 checksum does not match the downloaded archive - please contact security@grafana.com") - } - return nil -} - func (client *GrafanaComClient) ListAllPlugins(repoUrl string) (models.PluginRepo, error) { body, err := sendRequestGetBytes(HttpClient, repoUrl, "repo") diff --git a/pkg/cmd/grafana-cli/services/services.go b/pkg/cmd/grafana-cli/services/services.go index 90d14838e48..119e9f91199 100644 --- a/pkg/cmd/grafana-cli/services/services.go +++ b/pkg/cmd/grafana-cli/services/services.go @@ -15,12 +15,11 @@ import ( ) var ( - IoHelper models.IoUtil = IoUtilImp{} - HttpClient http.Client - HttpClientNoTimeout http.Client - GrafanaVersion string - ErrNotFoundError = errors.New("404 not found error") - Logger *logger.CLILogger + IoHelper models.IoUtil = IoUtilImp{} + HttpClient http.Client + GrafanaVersion string + ErrNotFoundError = errors.New("404 not found error") + Logger *logger.CLILogger ) type BadRequestError struct { @@ -37,9 +36,7 @@ func (e *BadRequestError) Error() string { func Init(version string, skipTLSVerify bool, debugMode bool) { GrafanaVersion = version - HttpClient = makeHttpClient(skipTLSVerify, 10*time.Second) - HttpClientNoTimeout = makeHttpClient(skipTLSVerify, 0) Logger = logger.New(debugMode) } @@ -73,7 +70,7 @@ func ReadPlugin(pluginDir, pluginName string) (models.InstalledPlugin, error) { pluginDataPath := filepath.Join(pluginDir, pluginName, "plugin.json") data, err = IoHelper.ReadFile(pluginDataPath) if err != nil { - return models.InstalledPlugin{}, errors.New("Could not find dist/plugin.json or plugin.json on " + pluginName + " in " + pluginDir) + return models.InstalledPlugin{}, errors.New("Could not find dist/plugin.json or plugin.json for " + pluginName + " in " + pluginDir) } } @@ -105,16 +102,3 @@ func GetLocalPlugins(pluginDir string) []models.InstalledPlugin { return result } - -func RemoveInstalledPlugin(pluginPath, pluginName string) error { - logger.Infof("Removing plugin: %v\n", pluginName) - pluginDir := filepath.Join(pluginPath, pluginName) - - _, err := IoHelper.Stat(pluginDir) - if err != nil { - return err - } - - logger.Debugf("Removing directory %v\n", pluginDir) - return IoHelper.RemoveAll(pluginDir) -} diff --git a/pkg/cmd/grafana-cli/utils/command_line.go b/pkg/cmd/grafana-cli/utils/command_line.go index d8faff51969..cf80714aee7 100644 --- a/pkg/cmd/grafana-cli/utils/command_line.go +++ b/pkg/cmd/grafana-cli/utils/command_line.go @@ -1,8 +1,6 @@ package utils import ( - "os" - "github.com/urfave/cli/v2" "github.com/grafana/grafana/pkg/cmd/grafana-cli/models" @@ -27,7 +25,6 @@ type CommandLine interface { type ApiClient interface { GetPlugin(pluginId, repoUrl string) (models.Plugin, error) - DownloadFile(pluginName string, tmpFile *os.File, url string, checksum string) (err error) ListAllPlugins(repoUrl string) (models.PluginRepo, error) }