2016-02-15 21:09:34 +08:00
package commands
import (
2021-05-13 02:05:16 +08:00
"context"
2016-02-15 21:09:34 +08:00
"errors"
2016-03-10 21:43:21 +08:00
"fmt"
2016-02-15 21:09:34 +08:00
"os"
2019-07-29 16:44:58 +08:00
"runtime"
2016-03-13 18:29:43 +08:00
"strings"
2016-03-29 03:42:26 +08:00
2023-07-17 16:22:28 +08:00
"github.com/Masterminds/semver/v3"
2023-02-22 16:24:13 +08:00
"github.com/fatih/color"
2023-07-14 17:49:05 +08:00
2023-02-22 16:24:13 +08:00
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
2021-04-26 22:13:40 +08:00
"github.com/grafana/grafana/pkg/cmd/grafana-cli/models"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/services"
2019-05-27 16:47:21 +08:00
"github.com/grafana/grafana/pkg/cmd/grafana-cli/utils"
2023-05-03 20:52:57 +08:00
"github.com/grafana/grafana/pkg/plugins"
2022-08-23 17:50:50 +08:00
"github.com/grafana/grafana/pkg/plugins/repo"
"github.com/grafana/grafana/pkg/plugins/storage"
2016-02-15 21:09:34 +08:00
)
2023-07-12 19:52:12 +08:00
const installArgsSize = 2
func validateInput ( c utils . CommandLine ) error {
2023-07-17 16:22:28 +08:00
args := c . Args ( )
argsLen := args . Len ( )
if argsLen > installArgsSize {
2023-07-12 19:52:12 +08:00
logger . Info ( color . RedString ( "Please specify the correct format. For example ./grafana cli (<command arguments>) plugins install <plugin ID> (<plugin version>)\n\n" ) )
return errors . New ( "install only supports 2 arguments: plugin and version" )
}
2023-07-17 16:22:28 +08:00
arg := args . First ( )
2016-02-15 21:09:34 +08:00
if arg == "" {
return errors . New ( "please specify plugin to install" )
}
2023-07-17 16:22:28 +08:00
if argsLen == installArgsSize {
version := args . Get ( 1 )
_ , err := semver . NewVersion ( version )
if err != nil {
2023-07-19 22:55:31 +08:00
logger . Info ( color . YellowString ( "The provided version doesn't use semantic versioning format\n\n" ) )
2023-07-17 16:22:28 +08:00
}
}
2016-06-25 02:14:58 +08:00
pluginsDir := c . PluginDirectory ( )
2016-03-29 03:42:26 +08:00
if pluginsDir == "" {
return errors . New ( "missing pluginsDir flag" )
2016-02-15 21:09:34 +08:00
}
2016-03-29 03:42:26 +08:00
fileInfo , err := os . Stat ( pluginsDir )
2016-03-11 21:11:25 +08:00
if err != nil {
2016-03-29 03:42:26 +08:00
if err = os . MkdirAll ( pluginsDir , os . ModePerm ) ; err != nil {
2018-04-17 02:25:48 +08:00
return fmt . Errorf ( "pluginsDir (%s) is not a writable directory" , pluginsDir )
2016-03-11 21:11:25 +08:00
}
return nil
}
if ! fileInfo . IsDir ( ) {
2016-02-15 21:09:34 +08:00
return errors . New ( "path is not a directory" )
}
return nil
}
2023-02-22 16:24:13 +08:00
func logRestartNotice ( ) {
logger . Info ( color . GreenString ( "Please restart Grafana after installing or removing plugins. Refer to Grafana documentation for instructions if necessary.\n\n" ) )
}
2023-05-04 16:52:09 +08:00
func installCommand ( c utils . CommandLine ) error {
2023-07-12 19:52:12 +08:00
if err := validateInput ( c ) ; err != nil {
2016-02-15 21:09:34 +08:00
return err
}
2021-04-26 22:13:40 +08:00
pluginID := c . Args ( ) . First ( )
2016-02-15 21:09:34 +08:00
version := c . Args ( ) . Get ( 1 )
2023-02-22 16:24:13 +08:00
err := installPlugin ( context . Background ( ) , pluginID , version , c )
if err == nil {
logRestartNotice ( )
}
return err
2016-02-15 21:09:34 +08:00
}
2022-08-23 17:50:50 +08:00
// 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 {
2023-07-14 17:49:05 +08:00
// If a version is specified, check if it is already installed
if version != "" {
if services . PluginVersionInstalled ( pluginID , version , c . PluginDirectory ( ) ) {
services . Logger . Successf ( "Plugin %s v%s already installed." , pluginID , version )
return nil
}
}
2023-05-30 17:48:52 +08:00
repository := repo . NewManager ( repo . ManagerCfg {
SkipTLSVerify : c . Bool ( "insecure" ) ,
BaseURL : c . PluginRepoURL ( ) ,
Logger : services . Logger ,
} )
2018-02-16 16:49:29 +08:00
2022-08-23 17:50:50 +08:00
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 ( ) )
2023-07-14 17:49:05 +08:00
extractedArchive , err := pluginFs . Extract ( ctx , pluginID , storage . SimpleDirNameGeneratorFunc , archive . File )
2022-08-23 17:50:50 +08:00
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 )
}
2023-07-14 17:49:05 +08:00
_ , err = pluginFs . Extract ( ctx , dep . ID , storage . SimpleDirNameGeneratorFunc , d . File )
2022-08-23 17:50:50 +08:00
if err != nil {
return err
}
}
return nil
2016-02-15 21:09:34 +08:00
}
2023-05-03 20:52:57 +08:00
// uninstallPlugin removes the plugin directory
func uninstallPlugin ( _ context . Context , pluginID string , c utils . CommandLine ) error {
2023-07-14 17:49:05 +08:00
for _ , bundle := range services . GetLocalPlugins ( c . PluginDirectory ( ) ) {
if bundle . Primary . JSONData . ID == pluginID {
logger . Infof ( "Removing plugin: %v\n" , pluginID )
if remover , ok := bundle . Primary . FS . ( plugins . FSRemover ) ; ok {
logger . Debugf ( "Removing directory %v\n\n" , bundle . Primary . FS . Base ( ) )
if err := remover . Remove ( ) ; err != nil {
return err
}
return nil
} else {
return fmt . Errorf ( "plugin %v is immutable and therefore cannot be uninstalled" , pluginID )
}
}
2023-05-03 20:52:57 +08:00
}
2023-07-14 17:49:05 +08:00
2023-05-03 20:52:57 +08:00
return nil
}
2019-07-29 16:44:58 +08:00
func osAndArchString ( ) string {
osString := strings . ToLower ( runtime . GOOS )
arch := runtime . GOARCH
return osString + "-" + arch
}
2023-05-08 16:58:47 +08:00
func supportsCurrentArch ( version models . Version ) bool {
2019-07-29 16:44:58 +08:00
if version . Arch == nil {
return true
}
for arch := range version . Arch {
if arch == osAndArchString ( ) || arch == "any" {
return true
}
}
return false
}
2023-05-08 16:58:47 +08:00
func latestSupportedVersion ( plugin models . Plugin ) * models . Version {
2020-06-26 14:46:08 +08:00
for _ , v := range plugin . Versions {
ver := v
2023-05-08 16:58:47 +08:00
if supportsCurrentArch ( ver ) {
2019-07-29 16:44:58 +08:00
return & ver
}
}
return nil
}