From 8a601ce77718036b404c1cd5a41e6c1aa0204a8a Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 11 Apr 2024 18:46:03 +0200 Subject: [PATCH 1/2] fix: allow passing multiple args to server ssh command Related to #711 --- internal/cmd/server/ssh.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/cmd/server/ssh.go b/internal/cmd/server/ssh.go index 3c4d5f4b..6da33b5d 100644 --- a/internal/cmd/server/ssh.go +++ b/internal/cmd/server/ssh.go @@ -11,6 +11,7 @@ import ( "github.com/hetznercloud/cli/internal/cmd/base" "github.com/hetznercloud/cli/internal/cmd/cmpl" + "github.com/hetznercloud/cli/internal/cmd/util" "github.com/hetznercloud/cli/internal/hcapi2" "github.com/hetznercloud/cli/internal/state" ) @@ -18,8 +19,9 @@ import ( var SSHCmd = base.Cmd{ BaseCobraCommand: func(client hcapi2.Client) *cobra.Command { cmd := &cobra.Command{ - Use: "ssh [options] [command]", + Use: "ssh [options] [command...]", Short: "Spawn an SSH connection for the server", + Args: util.ValidateLenient, ValidArgsFunction: cmpl.SuggestArgs(cmpl.SuggestCandidatesF(client.Server().Names)), TraverseChildren: true, DisableFlagsInUseLine: true, From acc48540214b2eadb8d0dd36a1922e02a9f62ae0 Mon Sep 17 00:00:00 2001 From: jo Date: Thu, 11 Apr 2024 20:18:54 +0200 Subject: [PATCH 2/2] test: add test for server ssh command --- internal/cmd/server/ssh.go | 2 +- internal/cmd/server/ssh_test.go | 47 +++++++++++++++++++++++++ internal/state/config/config.go | 6 ++++ internal/state/config/zz_config_mock.go | 14 ++++++++ internal/testutil/cmd.go | 5 +++ 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 internal/cmd/server/ssh_test.go diff --git a/internal/cmd/server/ssh.go b/internal/cmd/server/ssh.go index 6da33b5d..99ff0636 100644 --- a/internal/cmd/server/ssh.go +++ b/internal/cmd/server/ssh.go @@ -57,7 +57,7 @@ var SSHCmd = base.Cmd{ } sshArgs := []string{"-l", user, "-p", strconv.Itoa(port), ipAddress.String()} - sshCommand := exec.Command("ssh", append(sshArgs, args[1:]...)...) + sshCommand := exec.Command(s.Config().SSHPath(), append(sshArgs, args[1:]...)...) sshCommand.Stdin = os.Stdin sshCommand.Stdout = os.Stdout sshCommand.Stderr = os.Stderr diff --git a/internal/cmd/server/ssh_test.go b/internal/cmd/server/ssh_test.go new file mode 100644 index 00000000..73dddd23 --- /dev/null +++ b/internal/cmd/server/ssh_test.go @@ -0,0 +1,47 @@ +package server_test + +import ( + "net" + "testing" + + "github.com/golang/mock/gomock" + + "github.com/hetznercloud/cli/internal/cmd/server" + "github.com/hetznercloud/cli/internal/testutil" + "github.com/hetznercloud/hcloud-go/v2/hcloud" +) + +func TestSSH(t *testing.T) { + srv := hcloud.Server{ + ID: 42, + Name: "server1", + Status: hcloud.ServerStatusRunning, + PublicNet: hcloud.ServerPublicNet{ + IPv4: hcloud.ServerPublicNetIPv4{ + IP: net.ParseIP("192.168.0.2"), + }, + }, + } + + preRun := func(t *testing.T, fx *testutil.Fixture) { + fx.Client.ServerClient.EXPECT(). + Get(gomock.Any(), srv.Name). + Return(&srv, nil, nil) + + fx.Config.EXPECT().SSHPath().Return("echo") + } + + testutil.TestCommand(t, &server.SSHCmd, map[string]testutil.TestCase{ + "single arg": { + Args: []string{"ssh", srv.Name}, + PreRun: preRun, + ExpOut: "-l root -p 22 192.168.0.2\n", + }, + "many args": { + Args: []string{"ssh", srv.Name, "ls", "-al"}, + PreRun: preRun, + ExpOut: "-l root -p 22 192.168.0.2 ls -al\n", + }, + }) + +} diff --git a/internal/state/config/config.go b/internal/state/config/config.go index a145fd18..4187e65c 100644 --- a/internal/state/config/config.go +++ b/internal/state/config/config.go @@ -19,6 +19,8 @@ type Config interface { SetContexts([]*Context) Endpoint() string SetEndpoint(string) + + SSHPath() string } type Context struct { @@ -94,6 +96,10 @@ func (cfg *config) SetEndpoint(endpoint string) { cfg.endpoint = endpoint } +func (cfg *config) SSHPath() string { + return "ssh" +} + func ContextNames(cfg Config) []string { ctxs := cfg.Contexts() names := make([]string, len(ctxs)) diff --git a/internal/state/config/zz_config_mock.go b/internal/state/config/zz_config_mock.go index 8de33a54..63416270 100644 --- a/internal/state/config/zz_config_mock.go +++ b/internal/state/config/zz_config_mock.go @@ -75,6 +75,20 @@ func (mr *MockConfigMockRecorder) Endpoint() *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Endpoint", reflect.TypeOf((*MockConfig)(nil).Endpoint)) } +// SSHPath mocks base method. +func (m *MockConfig) SSHPath() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SSHPath") + ret0, _ := ret[0].(string) + return ret0 +} + +// SSHPath indicates an expected call of SSHPath. +func (mr *MockConfigMockRecorder) SSHPath() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SSHPath", reflect.TypeOf((*MockConfig)(nil).SSHPath)) +} + // SetActiveContext mocks base method. func (m *MockConfig) SetActiveContext(arg0 *Context) { m.ctrl.T.Helper() diff --git a/internal/testutil/cmd.go b/internal/testutil/cmd.go index b0745b21..ad31a459 100644 --- a/internal/testutil/cmd.go +++ b/internal/testutil/cmd.go @@ -17,6 +17,7 @@ type TestableCommand interface { type TestCase struct { Args []string + PreRun func(t *testing.T, fx *Fixture) ExpOut string ExpOutType DataType ExpErrOut string @@ -65,6 +66,10 @@ func TestCommand(t *testing.T, cmd TestableCommand, cases map[string]TestCase) { rootCmd.AddCommand(cmd.CobraCommand(fx.State())) + if testCase.PreRun != nil { + testCase.PreRun(t, fx) + } + out, errOut, err := fx.Run(rootCmd, testCase.Args) assert.NoError(t, err)