mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
feat: make connection method using system 'ssh' the default (add ssh+go:// fallback)
This commit is contained in:
+52
-50
@@ -263,8 +263,8 @@ func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions)
|
||||
SSHKeyFile: opts.RemoteMachine.KeyPath,
|
||||
MachineID: resp.Machine.Id,
|
||||
}
|
||||
if opts.RemoteMachine.UseSSHCLI {
|
||||
connCfg.SSHCLI = config.NewSSHDestination(
|
||||
if opts.RemoteMachine.UseSSHGo {
|
||||
connCfg.SSHGo = config.NewSSHDestination(
|
||||
opts.RemoteMachine.User,
|
||||
opts.RemoteMachine.Host,
|
||||
opts.RemoteMachine.Port,
|
||||
@@ -467,8 +467,8 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
|
||||
SSHKeyFile: opts.RemoteMachine.KeyPath,
|
||||
MachineID: addResp.Machine.Id,
|
||||
}
|
||||
if opts.RemoteMachine.UseSSHCLI {
|
||||
connCfg.SSHCLI = config.NewSSHDestination(
|
||||
if opts.RemoteMachine.UseSSHGo {
|
||||
connCfg.SSHGo = config.NewSSHDestination(
|
||||
opts.RemoteMachine.User,
|
||||
opts.RemoteMachine.Host,
|
||||
opts.RemoteMachine.Port,
|
||||
@@ -498,73 +498,75 @@ func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client
|
||||
func provisionOrConnectRemoteMachine(
|
||||
ctx context.Context, remoteMachine *RemoteMachine, skipInstall bool, version string,
|
||||
) (*client.Client, error) {
|
||||
// Use SSH CLI
|
||||
if remoteMachine.UseSSHCLI {
|
||||
exec := sshexec.NewSSHCLIRemote(
|
||||
remoteMachine.User,
|
||||
remoteMachine.Host,
|
||||
remoteMachine.Port,
|
||||
remoteMachine.KeyPath,
|
||||
// Use Go's built-in SSH library.
|
||||
if remoteMachine.UseSSHGo {
|
||||
sshClient, err := sshexec.Connect(
|
||||
remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath,
|
||||
)
|
||||
// If the SSH connection using SSH agent fails and no key path is provided, try to use the default SSH key.
|
||||
if err != nil && remoteMachine.KeyPath == "" {
|
||||
remoteMachine.KeyPath = DefaultSSHKeyPath
|
||||
sshClient, err = sshexec.Connect(
|
||||
remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"SSH login to remote machine %s: %w",
|
||||
config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port), err,
|
||||
)
|
||||
}
|
||||
|
||||
if !skipInstall {
|
||||
if err := provisionMachine(ctx, exec, version); err != nil {
|
||||
// Provision the remote machine by installing the Uncloud daemon and dependencies over SSH.
|
||||
exec := sshexec.NewRemote(sshClient)
|
||||
if err = provisionMachine(ctx, exec, version); err != nil {
|
||||
return nil, fmt.Errorf("provision machine: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
sshConfig := &connector.SSHConnectorConfig{
|
||||
User: remoteMachine.User,
|
||||
Host: remoteMachine.Host,
|
||||
Port: remoteMachine.Port,
|
||||
KeyPath: remoteMachine.KeyPath,
|
||||
var machineClient *client.Client
|
||||
if remoteMachine.User == "root" || skipInstall {
|
||||
// Create a machine API client over the established SSH connection to the remote machine.
|
||||
machineClient, err = client.New(ctx, connector.NewSSHConnectorFromClient(sshClient))
|
||||
} else {
|
||||
// Since the user is not root, we need to establish a new SSH connection to make the user's addition
|
||||
// to the uncloud group effective, thus allowing access to the Uncloud daemon Unix socket.
|
||||
sshConfig := &connector.SSHConnectorConfig{
|
||||
User: remoteMachine.User,
|
||||
Host: remoteMachine.Host,
|
||||
Port: remoteMachine.Port,
|
||||
KeyPath: remoteMachine.KeyPath,
|
||||
}
|
||||
machineClient, err = client.New(ctx, connector.NewSSHConnector(sshConfig))
|
||||
}
|
||||
machineClient, err := client.New(ctx, connector.NewSSHCLIConnector(sshConfig))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect to remote machine: %w", err)
|
||||
}
|
||||
return machineClient, nil
|
||||
}
|
||||
|
||||
// Use Go SSH
|
||||
sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath)
|
||||
// If the SSH connection using SSH agent fails and no key path is provided, try to use the default SSH key.
|
||||
if err != nil && remoteMachine.KeyPath == "" {
|
||||
remoteMachine.KeyPath = DefaultSSHKeyPath
|
||||
sshClient, err = sshexec.Connect(
|
||||
remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath,
|
||||
)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf(
|
||||
"SSH login to remote machine %s: %w",
|
||||
config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port), err,
|
||||
)
|
||||
}
|
||||
// Use the system 'ssh' command (default).
|
||||
exec := sshexec.NewSSHCLIRemote(
|
||||
remoteMachine.User,
|
||||
remoteMachine.Host,
|
||||
remoteMachine.Port,
|
||||
remoteMachine.KeyPath,
|
||||
)
|
||||
|
||||
if !skipInstall {
|
||||
// Provision the remote machine by installing the Uncloud daemon and dependencies over SSH.
|
||||
exec := sshexec.NewRemote(sshClient)
|
||||
if err = provisionMachine(ctx, exec, version); err != nil {
|
||||
if err := provisionMachine(ctx, exec, version); err != nil {
|
||||
return nil, fmt.Errorf("provision machine: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
var machineClient *client.Client
|
||||
if remoteMachine.User == "root" || skipInstall {
|
||||
// Create a machine API client over the established SSH connection to the remote machine.
|
||||
machineClient, err = client.New(ctx, connector.NewSSHConnectorFromClient(sshClient))
|
||||
} else {
|
||||
// Since the user is not root, we need to establish a new SSH connection to make the user's addition
|
||||
// to the uncloud group effective, thus allowing access to the Uncloud daemon Unix socket.
|
||||
sshConfig := &connector.SSHConnectorConfig{
|
||||
User: remoteMachine.User,
|
||||
Host: remoteMachine.Host,
|
||||
Port: remoteMachine.Port,
|
||||
KeyPath: remoteMachine.KeyPath,
|
||||
}
|
||||
machineClient, err = client.New(ctx, connector.NewSSHConnector(sshConfig))
|
||||
sshConfig := &connector.SSHConnectorConfig{
|
||||
User: remoteMachine.User,
|
||||
Host: remoteMachine.Host,
|
||||
Port: remoteMachine.Port,
|
||||
KeyPath: remoteMachine.KeyPath,
|
||||
}
|
||||
machineClient, err := client.New(ctx, connector.NewSSHCLIConnector(sshConfig))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("connect to remote machine: %w", err)
|
||||
}
|
||||
|
||||
@@ -12,8 +12,12 @@ import (
|
||||
)
|
||||
|
||||
type MachineConnection struct {
|
||||
SSH SSHDestination `yaml:"ssh,omitempty"`
|
||||
SSHCLI SSHDestination `yaml:"ssh_cli,omitempty"`
|
||||
// SSH uses the system ssh CLI command to connect. This is the default SSH connection method.
|
||||
SSH SSHDestination `yaml:"ssh,omitempty"`
|
||||
// SSHCLI is a backward-compatible alias for SSH.
|
||||
SSHCLI SSHDestination `yaml:"ssh_cli,omitempty"`
|
||||
// SSHGo uses Go's built-in SSH library to connect.
|
||||
SSHGo SSHDestination `yaml:"ssh_go,omitempty"`
|
||||
SSHKeyFile string `yaml:"ssh_key_file,omitempty"`
|
||||
// TCP is the address and port of the machine's API server.
|
||||
// The pointer is used to omit the field when not set. Otherwise, yaml marshalling includes an empty object.
|
||||
@@ -29,7 +33,9 @@ func (c *MachineConnection) String() string {
|
||||
if c.SSH != "" {
|
||||
return "ssh://" + string(c.SSH)
|
||||
} else if c.SSHCLI != "" {
|
||||
return "ssh+cli://" + string(c.SSHCLI)
|
||||
return "ssh://" + string(c.SSHCLI)
|
||||
} else if c.SSHGo != "" {
|
||||
return "ssh+go://" + string(c.SSHGo)
|
||||
} else if c.TCP != nil && c.TCP.IsValid() {
|
||||
return fmt.Sprintf("tcp://%s", c.TCP)
|
||||
} else if c.Unix != "" {
|
||||
@@ -46,6 +52,9 @@ func (c *MachineConnection) Validate() error {
|
||||
if c.SSHCLI != "" {
|
||||
setCount++
|
||||
}
|
||||
if c.SSHGo != "" {
|
||||
setCount++
|
||||
}
|
||||
if c.TCP != nil && c.TCP.IsValid() {
|
||||
setCount++
|
||||
}
|
||||
@@ -54,10 +63,10 @@ func (c *MachineConnection) Validate() error {
|
||||
}
|
||||
|
||||
if setCount == 0 {
|
||||
return errors.New("no connection method specified (ssh, ssh_cli, tcp, or unix required)")
|
||||
return errors.New("no connection method specified (ssh, ssh_go, tcp, or unix required)")
|
||||
}
|
||||
if setCount > 1 {
|
||||
return errors.New("only one connection method allowed per connection (ssh, ssh_cli, tcp, or unix)")
|
||||
return errors.New("only one connection method allowed per connection (ssh, ssh_go, tcp, or unix)")
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -30,18 +30,32 @@ func TestMachineConnection_String(t *testing.T) {
|
||||
want: "ssh://user@host.com:2222",
|
||||
},
|
||||
{
|
||||
name: "ssh_cli connection",
|
||||
name: "ssh_cli connection (backward compat alias for ssh)",
|
||||
conn: MachineConnection{
|
||||
SSHCLI: "user@host.com",
|
||||
},
|
||||
want: "ssh+cli://user@host.com",
|
||||
want: "ssh://user@host.com",
|
||||
},
|
||||
{
|
||||
name: "ssh_cli connection with port",
|
||||
name: "ssh_cli connection with port (backward compat alias for ssh)",
|
||||
conn: MachineConnection{
|
||||
SSHCLI: "user@host.com:2222",
|
||||
},
|
||||
want: "ssh+cli://user@host.com:2222",
|
||||
want: "ssh://user@host.com:2222",
|
||||
},
|
||||
{
|
||||
name: "ssh_go connection",
|
||||
conn: MachineConnection{
|
||||
SSHGo: "user@host.com",
|
||||
},
|
||||
want: "ssh+go://user@host.com",
|
||||
},
|
||||
{
|
||||
name: "ssh_go connection with port",
|
||||
conn: MachineConnection{
|
||||
SSHGo: "user@host.com:2222",
|
||||
},
|
||||
want: "ssh+go://user@host.com:2222",
|
||||
},
|
||||
{
|
||||
name: "tcp connection",
|
||||
@@ -103,12 +117,19 @@ func TestMachineConnection_Validate(t *testing.T) {
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ssh_cli only - valid",
|
||||
name: "ssh_cli only - valid (backward compat)",
|
||||
conn: MachineConnection{
|
||||
SSHCLI: "user@host",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "ssh_go only - valid",
|
||||
conn: MachineConnection{
|
||||
SSHGo: "user@host",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "tcp only - valid",
|
||||
conn: MachineConnection{
|
||||
@@ -141,6 +162,15 @@ func TestMachineConnection_Validate(t *testing.T) {
|
||||
wantErr: true,
|
||||
errMsg: "only one connection method allowed",
|
||||
},
|
||||
{
|
||||
name: "ssh and ssh_go - error",
|
||||
conn: MachineConnection{
|
||||
SSH: "user@host",
|
||||
SSHGo: "user@host",
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "only one connection method allowed",
|
||||
},
|
||||
{
|
||||
name: "ssh and unix - error",
|
||||
conn: MachineConnection{
|
||||
|
||||
+12
-8
@@ -57,9 +57,9 @@ func connectClusterWithProgress(ctx context.Context, conn config.MachineConnecti
|
||||
}
|
||||
|
||||
func connectCluster(ctx context.Context, conn config.MachineConnection) (*client.Client, error) {
|
||||
// Determine which SSH type is configured
|
||||
// Determine which SSH type is configured.
|
||||
var sshDest config.SSHDestination
|
||||
var useSSHCLI bool
|
||||
var useGoSSH bool
|
||||
|
||||
// Validate connection configuration early to provide clear error messages.
|
||||
if err := conn.Validate(); err != nil {
|
||||
@@ -67,11 +67,15 @@ func connectCluster(ctx context.Context, conn config.MachineConnection) (*client
|
||||
}
|
||||
|
||||
if conn.SSH != "" {
|
||||
// SSH uses the system ssh CLI command (default).
|
||||
sshDest = conn.SSH
|
||||
useSSHCLI = false
|
||||
} else if conn.SSHCLI != "" {
|
||||
// SSHCLI is a backward-compatible alias for SSH.
|
||||
sshDest = conn.SSHCLI
|
||||
useSSHCLI = true
|
||||
} else if conn.SSHGo != "" {
|
||||
// SSHGo uses Go's built-in SSH library.
|
||||
sshDest = conn.SSHGo
|
||||
useGoSSH = true
|
||||
} else if conn.TCP != nil && conn.TCP.IsValid() {
|
||||
return client.New(ctx, connector.NewTCPConnector(*conn.TCP))
|
||||
} else if conn.Unix != "" {
|
||||
@@ -95,11 +99,11 @@ func connectCluster(ctx context.Context, conn config.MachineConnection) (*client
|
||||
KeyPath: keyPath,
|
||||
}
|
||||
|
||||
// Create appropriate connector based on type
|
||||
if useSSHCLI {
|
||||
return client.New(ctx, connector.NewSSHCLIConnector(sshConfig))
|
||||
// Create appropriate connector based on type.
|
||||
if useGoSSH {
|
||||
return client.New(ctx, connector.NewSSHConnector(sshConfig))
|
||||
}
|
||||
return client.New(ctx, connector.NewSSHConnector(sshConfig))
|
||||
return client.New(ctx, connector.NewSSHCLIConnector(sshConfig))
|
||||
}
|
||||
|
||||
// connectModel is a TUI model for connecting to a cluster with a progress spinner.
|
||||
|
||||
@@ -23,11 +23,11 @@ const (
|
||||
)
|
||||
|
||||
type RemoteMachine struct {
|
||||
User string
|
||||
Host string
|
||||
Port int
|
||||
KeyPath string
|
||||
UseSSHCLI bool // indicates ssh+cli:// should be used
|
||||
User string
|
||||
Host string
|
||||
Port int
|
||||
KeyPath string
|
||||
UseSSHGo bool // Use Go's built-in SSH library instead of the system ssh CLI command.
|
||||
}
|
||||
|
||||
func installCmd(user string, version string) string {
|
||||
|
||||
Reference in New Issue
Block a user