mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-27 19:43:34 +00:00
feat(ssh+cli): Support SSH CLI on machine init and add commands (#173)
* Add SSHCLIRemote type with buildSSHArgs Implement's Executor interface (Run, Stream, Close) * Add UseSSHCLI field to RemoteMachine struct Allow us to differentiate regular ssh from ssh+cli. * Parse ssh+cli:// scheme on machine init/add * Support SSH CLI when provisioning * Update docs to reflect ssh+cli:// support * Revert removal of `init` examples Fixes copy & pasta mistake when documenting the new ssh+cli connection methods.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package sshexec
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type SSHCLIRemote struct {
|
||||
user string
|
||||
host string
|
||||
port int
|
||||
keyPath string
|
||||
}
|
||||
|
||||
func NewSSHCLIRemote(user, host string, port int, keyPath string) *SSHCLIRemote {
|
||||
return &SSHCLIRemote{
|
||||
user: user,
|
||||
host: host,
|
||||
port: port,
|
||||
keyPath: keyPath,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Refactor and reuse this with buildDialArgs and buildSSHArgs from
|
||||
// SSHCLI Connector.
|
||||
func (r *SSHCLIRemote) buildSSHArgs() []string {
|
||||
args := []string{"-o", "ConnectTimeout=5"}
|
||||
|
||||
if r.port != 0 && r.port != 22 {
|
||||
args = append(args, "-p", strconv.Itoa(r.port))
|
||||
}
|
||||
|
||||
if r.keyPath != "" {
|
||||
args = append(args, "-i", r.keyPath)
|
||||
}
|
||||
|
||||
args = append(args, r.user+"@"+r.host)
|
||||
return args
|
||||
}
|
||||
|
||||
func (r *SSHCLIRemote) Run(ctx context.Context, cmd string) (string, error) {
|
||||
args := r.buildSSHArgs()
|
||||
args = append(args, cmd)
|
||||
|
||||
execCmd := exec.CommandContext(ctx, "ssh", args...)
|
||||
output, err := execCmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return strings.TrimSpace(string(output)),
|
||||
fmt.Errorf("run command on remote host: %w: %s", err, string(output))
|
||||
}
|
||||
return strings.TrimSpace(string(output)), nil
|
||||
}
|
||||
|
||||
func (r *SSHCLIRemote) Stream(ctx context.Context, cmd string, stdout, stderr io.Writer) error {
|
||||
args := r.buildSSHArgs()
|
||||
args = append(args, cmd)
|
||||
|
||||
execCmd := exec.CommandContext(ctx, "ssh", args...)
|
||||
execCmd.Stdout = stdout
|
||||
execCmd.Stderr = stderr
|
||||
|
||||
return execCmd.Run()
|
||||
}
|
||||
|
||||
// no-op as there is no persistent connection.
|
||||
func (r *SSHCLIRemote) Close() error {
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package sshexec
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestSSHCLIRemote_buildSSHArgs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
user string
|
||||
host string
|
||||
port int
|
||||
keyPath string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "default port, no key",
|
||||
user: "root",
|
||||
host: "example.com",
|
||||
port: 22,
|
||||
keyPath: "",
|
||||
expected: []string{"-o", "ConnectTimeout=5", "root@example.com"},
|
||||
},
|
||||
{
|
||||
name: "custom port",
|
||||
user: "ubuntu",
|
||||
host: "192.168.1.10",
|
||||
port: 2222,
|
||||
keyPath: "",
|
||||
expected: []string{"-o", "ConnectTimeout=5", "-p", "2222", "ubuntu@192.168.1.10"},
|
||||
},
|
||||
{
|
||||
name: "with key path",
|
||||
user: "root",
|
||||
host: "example.com",
|
||||
port: 22,
|
||||
keyPath: "/path/to/key",
|
||||
expected: []string{"-o", "ConnectTimeout=5", "-i", "/path/to/key", "root@example.com"},
|
||||
},
|
||||
{
|
||||
name: "all options",
|
||||
user: "admin",
|
||||
host: "server.local",
|
||||
port: 2222,
|
||||
keyPath: "~/.ssh/id_rsa",
|
||||
expected: []string{"-o", "ConnectTimeout=5", "-p", "2222", "-i", "~/.ssh/id_rsa", "admin@server.local"},
|
||||
},
|
||||
{
|
||||
name: "port zero (default)",
|
||||
user: "root",
|
||||
host: "example.com",
|
||||
port: 0,
|
||||
keyPath: "",
|
||||
expected: []string{"-o", "ConnectTimeout=5", "root@example.com"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
remote := &SSHCLIRemote{
|
||||
user: tt.user,
|
||||
host: tt.host,
|
||||
port: tt.port,
|
||||
keyPath: tt.keyPath,
|
||||
}
|
||||
result := remote.buildSSHArgs()
|
||||
assert.Equal(t, tt.expected, result)
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user