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 (
"context"
"errors"
"fmt"
"github.com/spf13/cobra"
"uncloud/internal/cli"
)
@@ -19,6 +17,7 @@ type addOptions struct {
func NewAddCommand() *cobra.Command {
opts := addOptions{}
cmd := &cobra.Command{
// TODO: add support for [USER@]HOST[:PORT] syntax
Use: "add HOST",
Short: "Add a new machine to a cluster.",
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 {
var (
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
return uncli.AddMachine(ctx, opts.cluster, opts.name, opts.user, host, opts.port, opts.sshKey)
}
+38 -37
View File
@@ -4,66 +4,67 @@ import (
"fmt"
"github.com/spf13/cobra"
"net/netip"
"uncloud/internal/machine"
"uncloud/internal/machine/api/pb"
"uncloud/internal/cli"
"uncloud/internal/cli/config"
"uncloud/internal/machine/network"
"uncloud/internal/secret"
)
type initOptions struct {
name string
network string
userPublicKey string
dataDir string
sshKey string
cluster string
}
func NewInitCommand() *cobra.Command {
opts := initOptions{}
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.",
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)
if err != nil {
return fmt.Errorf("parse network CIDR: %w", err)
}
var users []*pb.User
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
return uncli.InitCluster(cmd.Context(), remoteMachine, opts.cluster, opts.name, netPrefix)
},
}
cmd.Flags().StringVarP(&opts.name, "name", "n", "", "Assign a name to the machine")
cmd.Flags().StringVar(&opts.network, "network", network.DefaultNetwork.String(),
"IPv4 network CIDR to use for machines and services")
cmd.Flags().StringVarP(&opts.userPublicKey, "user-pubkey", "u", "",
"User's public key which will be able to access the cluster (hex-encoded)")
cmd.Flags().StringVar(
&opts.network, "network", network.DefaultNetwork.String(),
"IPv4 network CIDR to use for machines and services",
)
//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,
"Directory for storing persistent machine state")
_ = cmd.MarkFlagDirname("data-dir")
cmd.Flags().StringVarP(
&opts.sshKey, "ssh-key", "i", "",
"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
}
+1 -1
View File
@@ -1,4 +1,4 @@
package cmdexec
package sshexec
import (
"context"
+5 -1
View File
@@ -1,4 +1,4 @@
package cmdexec
package sshexec
import (
"context"
@@ -11,6 +11,10 @@ type Remote struct {
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
// white space removed.
func (r *Remote) Run(ctx context.Context, cmd string) (string, error) {
+4 -8
View File
@@ -1,4 +1,4 @@
package cmdexec
package sshexec
import (
"fmt"
@@ -10,7 +10,7 @@ import (
"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))
// Try to connect using SSH agent only.
agentAuth, agentClose, agentErr := sshAgentAuth()
@@ -24,9 +24,7 @@ func Connect(user, host string, port int, sshKeyPath string) (*Remote, error) {
}
var client *ssh.Client
if client, agentErr = ssh.Dial("tcp", addr, config); agentErr == nil {
return &Remote{
client: client,
}, nil
return client, nil
}
}
// 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 &Remote{
client: client,
}, nil
return client, nil
}
func sshAgentAuth() (ssh.AuthMethod, func(), error) {