mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-28 03:53:33 +00:00
update init command to send RPC calls over SSH tunnel
This commit is contained in:
@@ -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
@@ -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,4 +1,4 @@
|
|||||||
package cmdexec
|
package sshexec
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user