mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 11:03:34 +00:00
chore: add --version flag for 'machine init|add' to specify uncloud daemon version
This commit is contained in:
@@ -25,6 +25,7 @@ type addOptions struct {
|
|||||||
publicIP string
|
publicIP string
|
||||||
sshKey string
|
sshKey string
|
||||||
context string
|
context string
|
||||||
|
version string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAddCommand() *cobra.Command {
|
func NewAddCommand() *cobra.Command {
|
||||||
@@ -64,10 +65,15 @@ func NewAddCommand() *cobra.Command {
|
|||||||
&opts.sshKey, "ssh-key", "i", "",
|
&opts.sshKey, "ssh-key", "i", "",
|
||||||
"path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
|
"path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
|
||||||
)
|
)
|
||||||
|
cmd.Flags().StringVar(
|
||||||
|
&opts.version, "version", "latest",
|
||||||
|
"Version of the Uncloud daemon to install on the machine.",
|
||||||
|
)
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.context, "context", "c", "",
|
&opts.context, "context", "c", "",
|
||||||
"Name of the cluster context to add the machine to. (default is the current context)",
|
"Name of the cluster context to add the machine to. (default is the current context)",
|
||||||
)
|
)
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,7 +92,13 @@ func add(ctx context.Context, uncli *cli.CLI, remoteMachine cli.RemoteMachine, o
|
|||||||
publicIP = &ip
|
publicIP = &ip
|
||||||
}
|
}
|
||||||
|
|
||||||
machineClient, err := uncli.AddMachine(ctx, remoteMachine, opts.context, opts.name, publicIP)
|
machineClient, err := uncli.AddMachine(ctx, cli.AddMachineOptions{
|
||||||
|
Context: opts.context,
|
||||||
|
MachineName: opts.name,
|
||||||
|
PublicIP: publicIP,
|
||||||
|
RemoteMachine: remoteMachine,
|
||||||
|
Version: opts.version,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ type initOptions struct {
|
|||||||
noDNS bool
|
noDNS bool
|
||||||
publicIP string
|
publicIP string
|
||||||
sshKey string
|
sshKey string
|
||||||
|
version string
|
||||||
context string
|
context string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,6 +83,10 @@ func NewInitCommand() *cobra.Command {
|
|||||||
&opts.sshKey, "ssh-key", "i", "",
|
&opts.sshKey, "ssh-key", "i", "",
|
||||||
"Path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
|
"Path to SSH private key for SSH remote login. (default ~/.ssh/id_*)",
|
||||||
)
|
)
|
||||||
|
cmd.Flags().StringVar(
|
||||||
|
&opts.version, "version", "latest",
|
||||||
|
"Version of the Uncloud daemon to install on the machine.",
|
||||||
|
)
|
||||||
cmd.Flags().StringVarP(
|
cmd.Flags().StringVarP(
|
||||||
&opts.context, "context", "c", "default",
|
&opts.context, "context", "c", "default",
|
||||||
"Name of the created context for the initialised cluster in the Uncloud config.",
|
"Name of the created context for the initialised cluster in the Uncloud config.",
|
||||||
@@ -110,7 +115,14 @@ func initCluster(ctx context.Context, uncli *cli.CLI, remoteMachine *cli.RemoteM
|
|||||||
publicIP = &ip
|
publicIP = &ip
|
||||||
}
|
}
|
||||||
|
|
||||||
client, err := uncli.InitCluster(ctx, remoteMachine, opts.context, opts.name, netPrefix, publicIP)
|
client, err := uncli.InitCluster(ctx, cli.InitClusterOptions{
|
||||||
|
Context: opts.context,
|
||||||
|
MachineName: opts.name,
|
||||||
|
Network: netPrefix,
|
||||||
|
PublicIP: publicIP,
|
||||||
|
RemoteMachine: remoteMachine,
|
||||||
|
Version: opts.version,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
+47
-38
@@ -144,31 +144,27 @@ func connectCluster(ctx context.Context, conn config.MachineConnection) (*client
|
|||||||
return nil, errors.New("connection configuration is invalid")
|
return nil, errors.New("connection configuration is invalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InitClusterOptions struct {
|
||||||
|
Context string
|
||||||
|
MachineName string
|
||||||
|
Network netip.Prefix
|
||||||
|
PublicIP *netip.Addr
|
||||||
|
RemoteMachine *RemoteMachine
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
// InitCluster initialises a new cluster on a remote machine and returns a client to interact with the cluster.
|
// InitCluster initialises a new cluster on a remote machine and returns a client to interact with the cluster.
|
||||||
// The client should be closed after use by the caller.
|
// The client should be closed after use by the caller.
|
||||||
func (cli *CLI) InitCluster(
|
func (cli *CLI) InitCluster(ctx context.Context, opts InitClusterOptions) (*client.Client, error) {
|
||||||
ctx context.Context,
|
if opts.RemoteMachine != nil {
|
||||||
remoteMachine *RemoteMachine,
|
return cli.initRemoteMachine(ctx, opts)
|
||||||
contextName,
|
|
||||||
machineName string,
|
|
||||||
netPrefix netip.Prefix,
|
|
||||||
publicIP *netip.Addr,
|
|
||||||
) (*client.Client, error) {
|
|
||||||
if remoteMachine != nil {
|
|
||||||
return cli.initRemoteMachine(ctx, *remoteMachine, contextName, machineName, netPrefix, publicIP)
|
|
||||||
}
|
}
|
||||||
// TODO: implement local machine initialisation
|
// TODO: implement local machine initialisation
|
||||||
return nil, fmt.Errorf("local machine initialisation is not implemented yet")
|
return nil, fmt.Errorf("local machine initialisation is not implemented yet")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cli *CLI) initRemoteMachine(
|
func (cli *CLI) initRemoteMachine(ctx context.Context, opts InitClusterOptions) (*client.Client, error) {
|
||||||
ctx context.Context,
|
contextName := opts.Context
|
||||||
remoteMachine RemoteMachine,
|
|
||||||
contextName,
|
|
||||||
machineName string,
|
|
||||||
netPrefix netip.Prefix,
|
|
||||||
publicIP *netip.Addr,
|
|
||||||
) (*client.Client, error) {
|
|
||||||
if contextName == "" {
|
if contextName == "" {
|
||||||
contextName = defaultContextName
|
contextName = defaultContextName
|
||||||
}
|
}
|
||||||
@@ -176,7 +172,7 @@ func (cli *CLI) initRemoteMachine(
|
|||||||
return nil, fmt.Errorf("cluster context '%s' already exists", contextName)
|
return nil, fmt.Errorf("cluster context '%s' already exists", contextName)
|
||||||
}
|
}
|
||||||
|
|
||||||
machineClient, err := cli.provisionRemoteMachine(ctx, remoteMachine)
|
machineClient, err := cli.provisionRemoteMachine(ctx, *opts.RemoteMachine, opts.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -199,12 +195,12 @@ func (cli *CLI) initRemoteMachine(
|
|||||||
}
|
}
|
||||||
|
|
||||||
req := &pb.InitClusterRequest{
|
req := &pb.InitClusterRequest{
|
||||||
MachineName: machineName,
|
MachineName: opts.MachineName,
|
||||||
Network: pb.NewIPPrefix(netPrefix),
|
Network: pb.NewIPPrefix(opts.Network),
|
||||||
}
|
}
|
||||||
if publicIP != nil {
|
if opts.PublicIP != nil {
|
||||||
if publicIP.IsValid() {
|
if opts.PublicIP.IsValid() {
|
||||||
req.PublicIpConfig = &pb.InitClusterRequest_PublicIp{PublicIp: pb.NewIP(*publicIP)}
|
req.PublicIpConfig = &pb.InitClusterRequest_PublicIp{PublicIp: pb.NewIP(*opts.PublicIP)}
|
||||||
} else {
|
} else {
|
||||||
// Invalid or in other words zero IP means to automatically detect the public IP.
|
// Invalid or in other words zero IP means to automatically detect the public IP.
|
||||||
req.PublicIpConfig = &pb.InitClusterRequest_PublicIpAuto{PublicIpAuto: true}
|
req.PublicIpConfig = &pb.InitClusterRequest_PublicIpAuto{PublicIpAuto: true}
|
||||||
@@ -227,8 +223,8 @@ func (cli *CLI) initRemoteMachine(
|
|||||||
|
|
||||||
// Save the machine's SSH connection details in the context config.
|
// Save the machine's SSH connection details in the context config.
|
||||||
connCfg := config.MachineConnection{
|
connCfg := config.MachineConnection{
|
||||||
SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port),
|
SSH: config.NewSSHDestination(opts.RemoteMachine.User, opts.RemoteMachine.Host, opts.RemoteMachine.Port),
|
||||||
SSHKeyFile: remoteMachine.KeyPath,
|
SSHKeyFile: opts.RemoteMachine.KeyPath,
|
||||||
}
|
}
|
||||||
cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg)
|
cli.Config.Contexts[contextName].Connections = append(cli.Config.Contexts[contextName].Connections, connCfg)
|
||||||
if err = cli.Config.Save(); err != nil {
|
if err = cli.Config.Save(); err != nil {
|
||||||
@@ -237,18 +233,28 @@ func (cli *CLI) initRemoteMachine(
|
|||||||
return machineClient, nil
|
return machineClient, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type AddMachineOptions struct {
|
||||||
|
Context string
|
||||||
|
MachineName string
|
||||||
|
PublicIP *netip.Addr
|
||||||
|
RemoteMachine RemoteMachine
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
// AddMachine provisions a remote machine and adds it to the cluster. It returns a client to interact with the machine
|
// AddMachine provisions a remote machine and adds it to the cluster. It returns a client to interact with the machine
|
||||||
// which should be closed after use by the caller.
|
// which should be closed after use by the caller.
|
||||||
func (cli *CLI) AddMachine(
|
func (cli *CLI) AddMachine(ctx context.Context, opts AddMachineOptions) (*client.Client, error) {
|
||||||
ctx context.Context, remoteMachine RemoteMachine, contextName, machineName string, publicIP *netip.Addr,
|
contextName := opts.Context
|
||||||
) (*client.Client, error) {
|
if contextName == "" {
|
||||||
|
contextName = cli.Config.CurrentContext
|
||||||
|
}
|
||||||
c, err := cli.ConnectCluster(ctx, contextName)
|
c, err := cli.ConnectCluster(ctx, contextName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("connect to cluster (context '%s'): %w", contextName, err)
|
return nil, fmt.Errorf("connect to cluster (context '%s'): %w", contextName, err)
|
||||||
}
|
}
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
|
|
||||||
machineClient, err := cli.provisionRemoteMachine(ctx, remoteMachine)
|
machineClient, err := cli.provisionRemoteMachine(ctx, opts.RemoteMachine, opts.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -284,15 +290,15 @@ func (cli *CLI) AddMachine(
|
|||||||
endpoints[i] = pb.NewIPPort(addrPort)
|
endpoints[i] = pb.NewIPPort(addrPort)
|
||||||
}
|
}
|
||||||
addReq := &pb.AddMachineRequest{
|
addReq := &pb.AddMachineRequest{
|
||||||
Name: machineName,
|
Name: opts.MachineName,
|
||||||
Network: &pb.NetworkConfig{
|
Network: &pb.NetworkConfig{
|
||||||
Endpoints: endpoints,
|
Endpoints: endpoints,
|
||||||
PublicKey: token.PublicKey,
|
PublicKey: token.PublicKey,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if publicIP != nil {
|
if opts.PublicIP != nil {
|
||||||
if publicIP.IsValid() {
|
if opts.PublicIP.IsValid() {
|
||||||
addReq.PublicIp = pb.NewIP(*publicIP)
|
addReq.PublicIp = pb.NewIP(*opts.PublicIP)
|
||||||
} else if token.PublicIP.IsValid() {
|
} else if token.PublicIP.IsValid() {
|
||||||
// Invalid or in other words zero IP means to use an automatically detected public IP from the token.
|
// Invalid or in other words zero IP means to use an automatically detected public IP from the token.
|
||||||
addReq.PublicIp = pb.NewIP(token.PublicIP)
|
addReq.PublicIp = pb.NewIP(token.PublicIP)
|
||||||
@@ -330,8 +336,8 @@ func (cli *CLI) AddMachine(
|
|||||||
|
|
||||||
// Save the machine's SSH connection details in the context config.
|
// Save the machine's SSH connection details in the context config.
|
||||||
connCfg := config.MachineConnection{
|
connCfg := config.MachineConnection{
|
||||||
SSH: config.NewSSHDestination(remoteMachine.User, remoteMachine.Host, remoteMachine.Port),
|
SSH: config.NewSSHDestination(opts.RemoteMachine.User, opts.RemoteMachine.Host, opts.RemoteMachine.Port),
|
||||||
SSHKeyFile: remoteMachine.KeyPath,
|
SSHKeyFile: opts.RemoteMachine.KeyPath,
|
||||||
}
|
}
|
||||||
if contextName == "" {
|
if contextName == "" {
|
||||||
contextName = cli.Config.CurrentContext
|
contextName = cli.Config.CurrentContext
|
||||||
@@ -346,7 +352,10 @@ func (cli *CLI) AddMachine(
|
|||||||
|
|
||||||
// provisionRemoteMachine installs the Uncloud daemon and dependencies on the remote machine over SSH and returns
|
// provisionRemoteMachine installs the Uncloud daemon and dependencies on the remote machine over SSH and returns
|
||||||
// a machine API client to interact with the machine. The client should be closed after use by the caller.
|
// a machine API client to interact with the machine. The client should be closed after use by the caller.
|
||||||
func (cli *CLI) provisionRemoteMachine(ctx context.Context, remoteMachine RemoteMachine) (*client.Client, error) {
|
// The version parameter specifies the version of the Uncloud daemon to install. If empty, the latest version is used.
|
||||||
|
func (cli *CLI) provisionRemoteMachine(
|
||||||
|
ctx context.Context, remoteMachine RemoteMachine, version string,
|
||||||
|
) (*client.Client, error) {
|
||||||
// Provision the remote machine by installing the Uncloud daemon and dependencies over SSH.
|
// Provision the remote machine by installing the Uncloud daemon and dependencies over SSH.
|
||||||
sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath)
|
sshClient, err := sshexec.Connect(remoteMachine.User, remoteMachine.Host, remoteMachine.Port, remoteMachine.KeyPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -357,7 +366,7 @@ func (cli *CLI) provisionRemoteMachine(ctx context.Context, remoteMachine Remote
|
|||||||
}
|
}
|
||||||
exec := sshexec.NewRemote(sshClient)
|
exec := sshexec.NewRemote(sshClient)
|
||||||
// Install and run the Uncloud daemon and dependencies on the remote machine.
|
// Install and run the Uncloud daemon and dependencies on the remote machine.
|
||||||
if err = provisionMachine(ctx, exec); err != nil {
|
if err = provisionMachine(ctx, exec, version); err != nil {
|
||||||
return nil, fmt.Errorf("provision machine: %w", err)
|
return nil, fmt.Errorf("provision machine: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-15
@@ -4,6 +4,8 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/sshexec"
|
"github.com/psviderski/uncloud/internal/sshexec"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,36 +19,38 @@ type RemoteMachine struct {
|
|||||||
KeyPath string
|
KeyPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func installCmd(user string) string {
|
func installCmd(user string, version string) string {
|
||||||
sudoPrefix := "sudo"
|
sudoPrefix := ""
|
||||||
|
var env []string
|
||||||
|
|
||||||
// Add the SSH user (non-root) to the uncloud group to allow access to the Uncloud daemon unix socket.
|
// Add the SSH user (non-root) to the uncloud group to allow access to the Uncloud daemon unix socket.
|
||||||
env := "UNCLOUD_GROUP_ADD_USER=" + user
|
if user != "root" {
|
||||||
|
sudoPrefix = "sudo"
|
||||||
curlBashCmd := fmt.Sprintf(
|
env = append(env, "UNCLOUD_GROUP_ADD_USER="+sshexec.Quote(user))
|
||||||
"curl -fsSL %s | %s %s bash", sshexec.Quote(installScriptURL), sudoPrefix, sshexec.Quote(env),
|
|
||||||
)
|
|
||||||
|
|
||||||
if user == "root" {
|
|
||||||
curlBashCmd = fmt.Sprintf(
|
|
||||||
"curl -fsSL %s | bash", sshexec.Quote(installScriptURL),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
if version != "" {
|
||||||
|
env = append(env, "UNCLOUD_VERSION="+sshexec.Quote(version))
|
||||||
|
}
|
||||||
|
|
||||||
|
envCmd := strings.Join(env, " ")
|
||||||
|
curlBashCmd := fmt.Sprintf("curl -fsSL %s | %s %s bash", sshexec.Quote(installScriptURL), sudoPrefix, envCmd)
|
||||||
|
|
||||||
return curlBashCmd
|
return curlBashCmd
|
||||||
}
|
}
|
||||||
|
|
||||||
// provisionMachine provisions the remote machine by downloading the Uncloud install script from GitHub and running it.
|
// provisionMachine provisions the remote machine by downloading the Uncloud install script from GitHub and running it.
|
||||||
func provisionMachine(ctx context.Context, exec sshexec.Executor) error {
|
// If version is specified, it will be passed to the install script as UNCLOUD_VERSION environment variable.
|
||||||
|
func provisionMachine(ctx context.Context, exec sshexec.Executor, version string) error {
|
||||||
user, err := exec.Run(ctx, "whoami")
|
user, err := exec.Run(ctx, "whoami")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("run whoami: %w", err)
|
return fmt.Errorf("run whoami: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := installCmd(user)
|
cmd := installCmd(user, version)
|
||||||
|
|
||||||
fmt.Println("Downloading Uncloud install script:", installScriptURL)
|
fmt.Println("Downloading Uncloud install script:", installScriptURL)
|
||||||
|
|
||||||
cmd := sshexec.QuoteCommand("bash", "-c", "set -o pipefail; "+installCmd)
|
cmd = sshexec.QuoteCommand("bash", "-c", "set -o pipefail; "+cmd)
|
||||||
if err = exec.Stream(ctx, cmd, os.Stdout, os.Stderr); err != nil {
|
if err = exec.Stream(ctx, cmd, os.Stdout, os.Stderr); err != nil {
|
||||||
return fmt.Errorf("download and run install script: %w", err)
|
return fmt.Errorf("download and run install script: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user