update init command to send RPC calls over SSH tunnel

This commit is contained in:
Pavel Sviderski
2024-09-10 13:45:49 +10:00
parent 86d949ae48
commit 2514c804dc
5 changed files with 50 additions and 90 deletions
+2 -43
View File
@@ -2,8 +2,6 @@ package machine
import ( import (
"context" "context"
"errors"
"fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"uncloud/internal/cli" "uncloud/internal/cli"
) )
@@ -19,6 +17,7 @@ type addOptions struct {
func NewAddCommand() *cobra.Command { func NewAddCommand() *cobra.Command {
opts := addOptions{} opts := addOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
// TODO: add support for [USER@]HOST[:PORT] syntax
Use: "add HOST", Use: "add HOST",
Short: "Add a new machine to a cluster.", Short: "Add a new machine to a cluster.",
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
@@ -38,45 +37,5 @@ func NewAddCommand() *cobra.Command {
} }
func add(ctx context.Context, uncli *cli.CLI, host string, opts addOptions) error { func add(ctx context.Context, uncli *cli.CLI, host string, opts addOptions) error {
var ( return uncli.AddMachine(ctx, opts.cluster, opts.name, opts.user, host, opts.port, opts.sshKey)
cluster *cli.Cluster
err error
)
if opts.cluster == "" {
// If the cluster is not specified, use the current cluster. If there are no clusters, create a default one.
cluster, err = uncli.GetCurrentCluster()
if err != nil {
if errors.Is(err, cli.ErrNotFound) {
// Do not create a default cluster if there are already clusters but the current cluster is not set.
clusters, cErr := uncli.ListClusters()
if cErr != nil {
return fmt.Errorf("list clusters: %w", cErr)
}
if len(clusters) > 0 {
return errors.New("the current cluster is not set in the Uncloud config. " +
"Please specify a cluster with the --cluster flag or set current_cluster in the config")
}
cluster, err = uncli.CreateDefaultCluster()
if err != nil {
return fmt.Errorf("create default cluster: %w", err)
}
fmt.Printf("Created %q cluster\n", cluster.Name)
} else {
return fmt.Errorf("get current cluster: %w", err)
}
}
} else {
cluster, err = uncli.GetCluster(opts.cluster)
if err != nil {
return fmt.Errorf("get cluster %q: %w", opts.cluster, err)
}
}
name, err := cluster.AddMachine(ctx, opts.name, opts.user, host, opts.port, opts.sshKey)
if err != nil {
return fmt.Errorf("add machine to cluster %q: %w", cluster.Name, err)
}
fmt.Printf("Machine %q added to cluster %q\n", name, cluster.Name)
return nil
} }
+38 -37
View File
@@ -4,66 +4,67 @@ import (
"fmt" "fmt"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"net/netip" "net/netip"
"uncloud/internal/machine" "uncloud/internal/cli"
"uncloud/internal/machine/api/pb" "uncloud/internal/cli/config"
"uncloud/internal/machine/network" "uncloud/internal/machine/network"
"uncloud/internal/secret"
) )
type initOptions struct { type initOptions struct {
name string name string
network string network string
userPublicKey string userPublicKey string
dataDir string
sshKey string
cluster string
} }
func NewInitCommand() *cobra.Command { func NewInitCommand() *cobra.Command {
opts := initOptions{} opts := initOptions{}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "init", Use: "init [USER@HOST:PORT]",
Args: cobra.MaximumNArgs(1),
// TODO: include usage examples of initialising a local and remote machine.
Short: "Initialise a new cluster that consists of the local or remote machine.", Short: "Initialise a new cluster that consists of the local or remote machine.",
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
uncli := cmd.Context().Value("cli").(*cli.CLI)
var remoteMachine *cli.RemoteMachine
if len(args) > 0 {
user, host, port, err := config.SSHDestination(args[0]).Parse()
if err != nil {
return fmt.Errorf("parse remote machine: %w", err)
}
remoteMachine = &cli.RemoteMachine{
User: user,
Host: host,
Port: port,
KeyPath: opts.sshKey,
}
}
netPrefix, err := netip.ParsePrefix(opts.network) netPrefix, err := netip.ParsePrefix(opts.network)
if err != nil { if err != nil {
return fmt.Errorf("parse network CIDR: %w", err) return fmt.Errorf("parse network CIDR: %w", err)
} }
var users []*pb.User return uncli.InitCluster(cmd.Context(), remoteMachine, opts.cluster, opts.name, netPrefix)
if opts.userPublicKey != "" {
pubKey, uErr := secret.FromHexString(opts.userPublicKey)
if uErr != nil {
return fmt.Errorf("parse user's public key: %w", uErr)
}
user := &pb.User{
Network: &pb.NetworkConfig{
ManagementIp: pb.NewIP(network.ManagementIP(pubKey)),
PublicKey: pubKey,
},
}
users = append(users, user)
}
// TODO: ideally this should be an RPC call to the machine API via unix socket.
config := &machine.Config{DataDir: opts.dataDir}
mach, err := machine.NewMachine(config)
if err != nil {
return fmt.Errorf("init machine: %w", err)
}
if err = mach.InitCluster(opts.name, netPrefix, users); err != nil {
return fmt.Errorf("initialise cluster: %w", err)
}
return nil
}, },
} }
cmd.Flags().StringVarP(&opts.name, "name", "n", "", "Assign a name to the machine") cmd.Flags().StringVarP(&opts.name, "name", "n", "", "Assign a name to the machine")
cmd.Flags().StringVar(&opts.network, "network", network.DefaultNetwork.String(), cmd.Flags().StringVar(
"IPv4 network CIDR to use for machines and services") &opts.network, "network", network.DefaultNetwork.String(),
cmd.Flags().StringVarP(&opts.userPublicKey, "user-pubkey", "u", "", "IPv4 network CIDR to use for machines and services",
"User's public key which will be able to access the cluster (hex-encoded)") )
//cmd.Flags().StringVar(&opts.userPublicKey, "user-pubkey", "",
// "User's public key which will be able to access the cluster (hex-encoded)")
cmd.Flags().StringVarP(&opts.dataDir, "data-dir", "d", machine.DefaultDataDir, cmd.Flags().StringVarP(
"Directory for storing persistent machine state") &opts.sshKey, "ssh-key", "i", "",
_ = cmd.MarkFlagDirname("data-dir") "path to SSH private key for SSH remote login (default ~/.ssh/id_*)",
)
cmd.Flags().StringVarP(
&opts.cluster, "cluster", "c", "",
"Name of the cluster in the local config if initialising a remote machine",
)
return cmd return cmd
} }
+1 -1
View File
@@ -1,4 +1,4 @@
package cmdexec package sshexec
import ( import (
"context" "context"
+5 -1
View File
@@ -1,4 +1,4 @@
package cmdexec package sshexec
import ( import (
"context" "context"
@@ -11,6 +11,10 @@ type Remote struct {
client *ssh.Client client *ssh.Client
} }
func NewRemote(client *ssh.Client) *Remote {
return &Remote{client: client}
}
// Run runs the command on the remote host and returns its output with all leading and trailing // Run runs the command on the remote host and returns its output with all leading and trailing
// white space removed. // white space removed.
func (r *Remote) Run(ctx context.Context, cmd string) (string, error) { func (r *Remote) Run(ctx context.Context, cmd string) (string, error) {
+4 -8
View File
@@ -1,4 +1,4 @@
package cmdexec package sshexec
import ( import (
"fmt" "fmt"
@@ -10,7 +10,7 @@ import (
"time" "time"
) )
func Connect(user, host string, port int, sshKeyPath string) (*Remote, error) { func Connect(user, host string, port int, sshKeyPath string) (*ssh.Client, error) {
addr := net.JoinHostPort(host, strconv.Itoa(port)) addr := net.JoinHostPort(host, strconv.Itoa(port))
// Try to connect using SSH agent only. // Try to connect using SSH agent only.
agentAuth, agentClose, agentErr := sshAgentAuth() agentAuth, agentClose, agentErr := sshAgentAuth()
@@ -24,9 +24,7 @@ func Connect(user, host string, port int, sshKeyPath string) (*Remote, error) {
} }
var client *ssh.Client var client *ssh.Client
if client, agentErr = ssh.Dial("tcp", addr, config); agentErr == nil { if client, agentErr = ssh.Dial("tcp", addr, config); agentErr == nil {
return &Remote{ return client, nil
client: client,
}, nil
} }
} }
// Fall back to using private key as the connection attempt using SSH agent failed. // Fall back to using private key as the connection attempt using SSH agent failed.
@@ -47,9 +45,7 @@ func Connect(user, host string, port int, sshKeyPath string) (*Remote, error) {
return nil, fmt.Errorf("connect using private key %q: %w", sshKeyPath, err) return nil, fmt.Errorf("connect using private key %q: %w", sshKeyPath, err)
} }
return &Remote{ return client, nil
client: client,
}, nil
} }
func sshAgentAuth() (ssh.AuthMethod, func(), error) { func sshAgentAuth() (ssh.AuthMethod, func(), error) {