From 2776a000abd4aab59227de1268b6531ef367cd2a Mon Sep 17 00:00:00 2001 From: Hugo Kiyodi Oshiro Date: Mon, 17 Jul 2023 10:22:28 +0200 Subject: [PATCH] Plugin: Validate plugin version on installation (#71488) --- .../grafana-cli/commands/install_command.go | 16 +++++- .../commands/install_command_test.go | 57 ++++++++++++++++++- 2 files changed, 69 insertions(+), 4 deletions(-) diff --git a/pkg/cmd/grafana-cli/commands/install_command.go b/pkg/cmd/grafana-cli/commands/install_command.go index 92969cc3335..4d164d90e70 100644 --- a/pkg/cmd/grafana-cli/commands/install_command.go +++ b/pkg/cmd/grafana-cli/commands/install_command.go @@ -8,6 +8,7 @@ import ( "runtime" "strings" + "github.com/Masterminds/semver/v3" "github.com/fatih/color" "github.com/grafana/grafana/pkg/cmd/grafana-cli/logger" @@ -22,16 +23,27 @@ import ( const installArgsSize = 2 func validateInput(c utils.CommandLine) error { - if c.Args().Len() > installArgsSize { + args := c.Args() + argsLen := args.Len() + + if argsLen > installArgsSize { logger.Info(color.RedString("Please specify the correct format. For example ./grafana cli () plugins install ()\n\n")) return errors.New("install only supports 2 arguments: plugin and version") } - arg := c.Args().First() + arg := args.First() if arg == "" { return errors.New("please specify plugin to install") } + if argsLen == installArgsSize { + version := args.Get(1) + _, err := semver.NewVersion(version) + if err != nil { + return fmt.Errorf("the provided version (%s) is invalid", version) + } + } + pluginsDir := c.PluginDirectory() if pluginsDir == "" { return errors.New("missing pluginsDir flag") diff --git a/pkg/cmd/grafana-cli/commands/install_command_test.go b/pkg/cmd/grafana-cli/commands/install_command_test.go index 5ecac4cb835..d872e10d0b7 100644 --- a/pkg/cmd/grafana-cli/commands/install_command_test.go +++ b/pkg/cmd/grafana-cli/commands/install_command_test.go @@ -1,14 +1,16 @@ package commands import ( + "fmt" "testing" "github.com/grafana/grafana/pkg/cmd/grafana-cli/utils" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) func TestValidateInput(t *testing.T) { - t.Run("should print message for ignored args", func(t *testing.T) { + t.Run("should return an error for wrong args number", func(t *testing.T) { mockCmdLine := &utils.MockCommandLine{} defer mockCmdLine.AssertExpectations(t) @@ -18,9 +20,60 @@ func TestValidateInput(t *testing.T) { defer mockArgs.AssertExpectations(t) mockArgs.On("Len").Return(len(cmdArgs)) - mockCmdLine.On("Args").Return(mockArgs).Times(1) + mockCmdLine.On("Args").Return(mockArgs).Once() err := validateInput(mockCmdLine) require.EqualError(t, err, "install only supports 2 arguments: plugin and version") }) + + tests := []struct { + desc string + versionArg string + shouldSucceed bool + }{ + { + desc: "should successful validate semver arg", + versionArg: "1.2.3", + shouldSucceed: true, + }, + { + desc: "should successful validate complex semver arg", + versionArg: "1.0.0-alpha.0valid", + shouldSucceed: true, + }, + { + desc: "should fail non version arg", + versionArg: "bar", + }, + } + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + mockCmdLine := &utils.MockCommandLine{} + defer mockCmdLine.AssertExpectations(t) + + cmdArgs := []string{"foo", tt.versionArg} + + mockArgs := &utils.MockArgs{} + defer mockArgs.AssertExpectations(t) + + mockArgs.On("Len").Return(len(cmdArgs)) + mockArgs.On("First").Return(cmdArgs[0]) + mockArgs.On("Get", mock.AnythingOfType("int")).Return(cmdArgs[1]) + + mockCmdLine.On("Args").Return(mockArgs).Once() + + if tt.shouldSucceed { + mockCmdLine.On("PluginDirectory").Return("/tmp").Once() + } + + err := validateInput(mockCmdLine) + + if tt.shouldSucceed { + require.NoError(t, err) + } else { + require.EqualError(t, err, fmt.Sprintf("the provided version (%s) is invalid", cmdArgs[1])) + } + }) + } }