establish SSH connection when adding a new machine

This commit is contained in:
Pavel Sviderski
2024-09-11 20:46:43 +10:00
parent 03c2c0df39
commit fb236fb922
4 changed files with 68 additions and 15 deletions
+46 -2
View File
@@ -10,6 +10,7 @@ import (
"uncloud/internal/cli/config" "uncloud/internal/cli/config"
"uncloud/internal/machine/api/pb" "uncloud/internal/machine/api/pb"
"uncloud/internal/secret" "uncloud/internal/secret"
"uncloud/internal/sshexec"
) )
const defaultClusterName = "default" const defaultClusterName = "default"
@@ -196,9 +197,52 @@ func (cli *CLI) AddMachine(ctx context.Context, remoteMachine RemoteMachine, clu
if err != nil { if err != nil {
return fmt.Errorf("connect to cluster: %w", err) return fmt.Errorf("connect to cluster: %w", err)
} }
defer func() {
_ = c.Close()
}()
sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath)
if err != nil {
return fmt.Errorf(
"SSH login to remote machine %s: %w",
config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port), err,
)
}
machineExec := sshexec.NewRemote(sshClient)
// TODO: Check if the machine is already provisioned using machineClient and ask the user to reset it first.
//conn := connector.NewSSHConnectorFromClient(sshClient)
//machineClient, err := client.New(ctx, conn)
//if err != nil {
// return fmt.Errorf("connect to remote machine API: %w", err)
//}
// TODO: Download and install the latest uncloudd binary by running the install shell script from GitHub.
// For now upload the binary using scp manually.
if _, err = machineExec.Run(ctx, "which uncloudd"); err != nil {
return fmt.Errorf("uncloudd binary not found on the remote machine: %w", err)
}
//req := &pb.AddMachineRequest{
// Name: machineName,
// Network: &pb.NetworkConfig{
//
// }
//}
//resp, err := c.AddMachine(ctx, req)
//if err != nil {
// return fmt.Errorf("add machine to cluster: %w", err)
//}
// TODO:
// --1. Establish a client connection to the remote machine.
// --2. Check if the machine is already provisioned and ask the user to reset it first.
// --3. Download and install the latest uncloudd binary by running the install shell script from GitHub.
// 4. Request token from the remote machine.
// 5. Add the machine to the cluster using its token and receive a configuration token.
// 6. Request the machine to join the cluster using the configuration token.
// 7. Save the machine's SSH connection details in the cluster config.
fmt.Println("Adding machine to cluster...", c)
// TODO
//name, connCfg, err := c.AddMachine(ctx, machineName, user, host, port, sshKeyPath) //name, connCfg, err := c.AddMachine(ctx, machineName, user, host, port, sshKeyPath)
//if err != nil { //if err != nil {
// return fmt.Errorf("add machine to cluster %q: %w", cluster.Name(), err) // return fmt.Errorf("add machine to cluster %q: %w", cluster.Name(), err)
-9
View File
@@ -2,7 +2,6 @@ package client
import ( import (
"context" "context"
"crypto/ed25519"
"errors" "errors"
"fmt" "fmt"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -297,11 +296,3 @@ func (c *ClusterClient) registerNewMachine(
} }
return c.client.AddMachine(ctx, req) return c.client.AddMachine(ctx, req)
} }
func privateKeyFromSecret(s secret.Secret) (ed25519.PrivateKey, error) {
// Cluster secret in the config is a hex-encoded private key seed.
if len(s) != ed25519.SeedSize {
return nil, fmt.Errorf("invalid cluster secret length")
}
return ed25519.NewKeyFromSeed(s), nil
}
+10
View File
@@ -41,13 +41,23 @@ func NewSSHConnector(cfg *SSHConnectorConfig) *SSHConnector {
return c return c
} }
func NewSSHConnectorFromClient(client *ssh.Client) *SSHConnector {
return &SSHConnector{client: client}
}
// TODO: handle context cancelation. // TODO: handle context cancelation.
func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) { func (c *SSHConnector) Connect(ctx context.Context) (*grpc.ClientConn, error) {
if c.client == nil {
// Establish an SSH connection if the SSH client is not provided.
if c.config == (SSHConnectorConfig{}) {
return nil, fmt.Errorf("SSH connector not configured")
}
var err error var err error
c.client, err = sshexec.Connect(c.config.User, c.config.Host, c.config.Port, c.config.KeyPath) c.client, err = sshexec.Connect(c.config.User, c.config.Host, c.config.Port, c.config.KeyPath)
if err != nil { if err != nil {
return nil, fmt.Errorf("SSH login to %s@%s:%d: %w", c.config.User, c.config.Host, c.config.Port, err) return nil, fmt.Errorf("SSH login to %s@%s:%d: %w", c.config.User, c.config.Host, c.config.Port, err)
} }
}
conn, err := grpc.NewClient( conn, err := grpc.NewClient(
"unix://"+c.config.APISockPath, "unix://"+c.config.APISockPath,
+8
View File
@@ -18,6 +18,8 @@ type MachineConnection struct {
PublicKey secret.Secret `toml:"public_key,omitempty"` PublicKey secret.Secret `toml:"public_key,omitempty"`
} }
// SSHDestination represents an SSH destination string in the canonical form of "user@host:port".
// The default user "root" and port 22 can be omitted.
type SSHDestination string type SSHDestination string
func NewSSHDestination(user, host string, port int) SSHDestination { func NewSSHDestination(user, host string, port int) SSHDestination {
@@ -37,10 +39,16 @@ func (d SSHDestination) Parse() (user string, host string, port int, err error)
if strings.Contains(host, "@") { if strings.Contains(host, "@") {
user, host, _ = strings.Cut(host, "@") user, host, _ = strings.Cut(host, "@")
} }
if user == "" {
user = DefaultSSHUser
}
h, p, sErr := net.SplitHostPort(host) h, p, sErr := net.SplitHostPort(host)
if sErr == nil { if sErr == nil {
host = h host = h
port, err = strconv.Atoi(p) port, err = strconv.Atoi(p)
} }
if port == 0 {
port = DefaultSSHPort
}
return return
} }