From 5c3b47a2470efafb329104562b1a012390518d33 Mon Sep 17 00:00:00 2001 From: Nikolay Edigaryev Date: Thu, 23 Mar 2023 15:59:57 +0400 Subject: [PATCH] --help fixes and new "orchard logs vm" command (#41) * Make sure we list names/VM names as required argument in the --help * Introduce "orchard logs vm" command * Make sure each command has a Short field * Future-proof port-forward, ssh and vnc commands To support not only the VM resource. --- internal/command/context/create.go | 2 +- internal/command/context/default.go | 2 +- internal/command/context/delete.go | 2 +- internal/command/create/service_account.go | 7 +- internal/command/create/vm.go | 7 +- internal/command/deletecmd/service_account.go | 7 +- internal/command/deletecmd/vm.go | 7 +- internal/command/get/bootstrap_token.go | 2 +- internal/command/get/service_account.go | 4 +- internal/command/logs/logs.go | 16 ++ internal/command/logs/vm.go | 38 ++++ internal/command/portforward/portforward.go | 79 +------- internal/command/portforward/vm.go | 83 ++++++++ internal/command/root.go | 2 + internal/command/ssh/ssh.go | 173 +---------------- internal/command/ssh/vm.go | 179 ++++++++++++++++++ internal/command/stop/vm.go | 7 +- internal/command/vnc/vm.go | 97 ++++++++++ internal/command/vnc/vnc.go | 91 +-------- internal/command/worker/run.go | 7 +- 20 files changed, 458 insertions(+), 354 deletions(-) create mode 100644 internal/command/logs/logs.go create mode 100644 internal/command/logs/vm.go create mode 100644 internal/command/portforward/vm.go create mode 100644 internal/command/ssh/vm.go create mode 100644 internal/command/vnc/vm.go diff --git a/internal/command/context/create.go b/internal/command/context/create.go index b3ef690..a1c1ec0 100644 --- a/internal/command/context/create.go +++ b/internal/command/context/create.go @@ -27,7 +27,7 @@ var force bool func newCreateCommand() *cobra.Command { command := &cobra.Command{ - Use: "create", + Use: "create NAME", Short: "Create a new context", Args: cobra.ExactArgs(1), RunE: runCreate, diff --git a/internal/command/context/default.go b/internal/command/context/default.go index 9109c88..59323df 100644 --- a/internal/command/context/default.go +++ b/internal/command/context/default.go @@ -7,7 +7,7 @@ import ( func newDefaultCommand() *cobra.Command { command := &cobra.Command{ - Use: "default", + Use: "default NAME", Short: "Set a default context to use for client/worker commands", Args: cobra.ExactArgs(1), RunE: runDefault, diff --git a/internal/command/context/delete.go b/internal/command/context/delete.go index 4c4c292..c25bc33 100644 --- a/internal/command/context/delete.go +++ b/internal/command/context/delete.go @@ -7,7 +7,7 @@ import ( func newDeleteCommand() *cobra.Command { command := &cobra.Command{ - Use: "delete", + Use: "delete NAME", Short: "Delete a context", Args: cobra.ExactArgs(1), RunE: runDelete, diff --git a/internal/command/create/service_account.go b/internal/command/create/service_account.go index 5259448..6cc8025 100644 --- a/internal/command/create/service_account.go +++ b/internal/command/create/service_account.go @@ -13,9 +13,10 @@ var roles []string func newCreateServiceAccount() *cobra.Command { command := &cobra.Command{ - Use: "service-account", - RunE: runCreateServiceAccount, - Args: cobra.ExactArgs(1), + Use: "service-account NAME", + Short: "Create a service account", + RunE: runCreateServiceAccount, + Args: cobra.ExactArgs(1), } command.PersistentFlags().StringVar(&token, "token", "", diff --git a/internal/command/create/vm.go b/internal/command/create/vm.go index 8e658f1..7a5e204 100644 --- a/internal/command/create/vm.go +++ b/internal/command/create/vm.go @@ -14,9 +14,10 @@ var headless bool func newCreateVMCommand() *cobra.Command { command := &cobra.Command{ - Use: "vm", - RunE: runCreateVM, - Args: cobra.ExactArgs(1), + Use: "vm NAME", + Short: "Create a VM", + RunE: runCreateVM, + Args: cobra.ExactArgs(1), } command.PersistentFlags().StringVar(&image, "image", "ghcr.io/cirruslabs/macos-ventura-base:latest", "image to use") diff --git a/internal/command/deletecmd/service_account.go b/internal/command/deletecmd/service_account.go index 73ce4c6..8f2781b 100644 --- a/internal/command/deletecmd/service_account.go +++ b/internal/command/deletecmd/service_account.go @@ -7,9 +7,10 @@ import ( func newDeleteServiceComandCommand() *cobra.Command { return &cobra.Command{ - Use: "service-account", - Args: cobra.ExactArgs(1), - RunE: runDeleteServiceAccountCommand, + Use: "service-account NAME", + Short: "Delete a service account", + Args: cobra.ExactArgs(1), + RunE: runDeleteServiceAccountCommand, } } diff --git a/internal/command/deletecmd/vm.go b/internal/command/deletecmd/vm.go index 4321fa9..9ad243e 100644 --- a/internal/command/deletecmd/vm.go +++ b/internal/command/deletecmd/vm.go @@ -7,9 +7,10 @@ import ( func newDeleteVMCommand() *cobra.Command { return &cobra.Command{ - Use: "vm", - Args: cobra.ExactArgs(1), - RunE: runDeleteVM, + Use: "vm NAME", + Short: "Delete a VM", + Args: cobra.ExactArgs(1), + RunE: runDeleteVM, } } diff --git a/internal/command/get/bootstrap_token.go b/internal/command/get/bootstrap_token.go index 939b10b..e749941 100644 --- a/internal/command/get/bootstrap_token.go +++ b/internal/command/get/bootstrap_token.go @@ -10,7 +10,7 @@ import ( func newGetBootstrapTokenCommand() *cobra.Command { command := &cobra.Command{ - Use: "bootstrap-token", + Use: "bootstrap-token NAME", Short: "Retrieve a bootstrap token for the specified service account", RunE: runGetBootstrapToken, Args: cobra.ExactArgs(1), diff --git a/internal/command/get/service_account.go b/internal/command/get/service_account.go index 74f3c76..79798d4 100644 --- a/internal/command/get/service_account.go +++ b/internal/command/get/service_account.go @@ -11,8 +11,8 @@ import ( func newGetServiceAccountCommand() *cobra.Command { command := &cobra.Command{ - Use: "service-account", - Short: "Retrieve service account and it's fields", + Use: "service-account NAME", + Short: "Retrieve a service account and it's fields", RunE: runGetServiceAccount, Args: cobra.ExactArgs(1), } diff --git a/internal/command/logs/logs.go b/internal/command/logs/logs.go new file mode 100644 index 0000000..72616af --- /dev/null +++ b/internal/command/logs/logs.go @@ -0,0 +1,16 @@ +package logs + +import ( + "github.com/spf13/cobra" +) + +func NewCommand() *cobra.Command { + command := &cobra.Command{ + Use: "logs", + Short: "Retrieve resource logs from the controller", + } + + command.AddCommand(newLogsVMCommand()) + + return command +} diff --git a/internal/command/logs/vm.go b/internal/command/logs/vm.go new file mode 100644 index 0000000..a2f51fb --- /dev/null +++ b/internal/command/logs/vm.go @@ -0,0 +1,38 @@ +package logs + +import ( + "fmt" + "github.com/cirruslabs/orchard/pkg/client" + "github.com/spf13/cobra" +) + +func newLogsVMCommand() *cobra.Command { + command := &cobra.Command{ + Use: "vm NAME", + Short: "Retrieve VM logs", + RunE: runLogsVM, + Args: cobra.ExactArgs(1), + } + + return command +} + +func runLogsVM(cmd *cobra.Command, args []string) error { + name := args[0] + + client, err := client.New() + if err != nil { + return err + } + + lines, err := client.VMs().Logs(cmd.Context(), name) + if err != nil { + return err + } + + for _, line := range lines { + fmt.Print(line) + } + + return nil +} diff --git a/internal/command/portforward/portforward.go b/internal/command/portforward/portforward.go index 9c21242..a004652 100644 --- a/internal/command/portforward/portforward.go +++ b/internal/command/portforward/portforward.go @@ -1,83 +1,14 @@ package portforward -import ( - "fmt" - "github.com/cirruslabs/orchard/internal/proxy" - "github.com/cirruslabs/orchard/pkg/client" - "github.com/spf13/cobra" - "net" -) +import "github.com/spf13/cobra" func NewCommand() *cobra.Command { command := &cobra.Command{ - Use: "port-forward []:", - Short: "Forward TCP port to the VM", - Args: cobra.ExactArgs(2), - RunE: runPortForward, + Use: "port-forward", + Short: "Forward TCP port to the resources", } + command.AddCommand(newPortForwardVMCommand()) + return command } - -func runPortForward(cmd *cobra.Command, args []string) (err error) { - name := args[0] - portSpecRaw := args[1] - - portSpec, err := NewPortSpec(portSpecRaw) - if err != nil { - return err - } - - client, err := client.New() - if err != nil { - return err - } - - listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", portSpec.LocalPort)) - if err != nil { - return err - } - defer func() { - if listenerErr := listener.Close(); listenerErr != nil && err == nil { - err = listenerErr - } - }() - - fmt.Printf("forwarding %s -> %s:%d...\n", listener.Addr(), name, portSpec.RemotePort) - - errCh := make(chan error, 1) - - go func() { - for { - conn, err := listener.Accept() - if err != nil { - errCh <- err - - return - } - - go func() { - defer conn.Close() - - wsConn, err := client.VMs().PortForward(cmd.Context(), name, portSpec.RemotePort) - if err != nil { - fmt.Printf("failed to forward port: %v\n", err) - - return - } - defer wsConn.Close() - - if err := proxy.Connections(wsConn, conn); err != nil { - fmt.Printf("failed to forward port: %v\n", err) - } - }() - } - }() - - select { - case <-cmd.Context().Done(): - return cmd.Context().Err() - case err := <-errCh: - return err - } -} diff --git a/internal/command/portforward/vm.go b/internal/command/portforward/vm.go new file mode 100644 index 0000000..3f103c2 --- /dev/null +++ b/internal/command/portforward/vm.go @@ -0,0 +1,83 @@ +package portforward + +import ( + "fmt" + "github.com/cirruslabs/orchard/internal/proxy" + "github.com/cirruslabs/orchard/pkg/client" + "github.com/spf13/cobra" + "net" +) + +func newPortForwardVMCommand() *cobra.Command { + command := &cobra.Command{ + Use: "vm VM_NAME [LOCAL_PORT]:REMOTE_PORT", + Short: "Forward TCP port to the VM", + Args: cobra.ExactArgs(2), + RunE: runPortForwardVMCommand, + } + + return command +} + +func runPortForwardVMCommand(cmd *cobra.Command, args []string) (err error) { + name := args[0] + portSpecRaw := args[1] + + portSpec, err := NewPortSpec(portSpecRaw) + if err != nil { + return err + } + + client, err := client.New() + if err != nil { + return err + } + + listener, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", portSpec.LocalPort)) + if err != nil { + return err + } + defer func() { + if listenerErr := listener.Close(); listenerErr != nil && err == nil { + err = listenerErr + } + }() + + fmt.Printf("forwarding %s -> %s:%d...\n", listener.Addr(), name, portSpec.RemotePort) + + errCh := make(chan error, 1) + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + errCh <- err + + return + } + + go func() { + defer conn.Close() + + wsConn, err := client.VMs().PortForward(cmd.Context(), name, portSpec.RemotePort) + if err != nil { + fmt.Printf("failed to forward port: %v\n", err) + + return + } + defer wsConn.Close() + + if err := proxy.Connections(wsConn, conn); err != nil { + fmt.Printf("failed to forward port: %v\n", err) + } + }() + } + }() + + select { + case <-cmd.Context().Done(): + return cmd.Context().Err() + case err := <-errCh: + return err + } +} diff --git a/internal/command/root.go b/internal/command/root.go index b8d035d..738327c 100644 --- a/internal/command/root.go +++ b/internal/command/root.go @@ -8,6 +8,7 @@ import ( "github.com/cirruslabs/orchard/internal/command/dev" "github.com/cirruslabs/orchard/internal/command/get" "github.com/cirruslabs/orchard/internal/command/list" + "github.com/cirruslabs/orchard/internal/command/logs" "github.com/cirruslabs/orchard/internal/command/portforward" "github.com/cirruslabs/orchard/internal/command/ssh" "github.com/cirruslabs/orchard/internal/command/stop" @@ -29,6 +30,7 @@ func NewRootCmd() *cobra.Command { create.NewCommand(), get.NewCommand(), list.NewCommand(), + logs.NewCommand(), portforward.NewCommand(), ssh.NewCommand(), vnc.NewCommand(), diff --git a/internal/command/ssh/ssh.go b/internal/command/ssh/ssh.go index 3ecdcca..a1e38cb 100644 --- a/internal/command/ssh/ssh.go +++ b/internal/command/ssh/ssh.go @@ -1,179 +1,14 @@ package ssh -import ( - "context" - "errors" - "fmt" - "github.com/cirruslabs/orchard/pkg/client" - "github.com/spf13/cobra" - "golang.org/x/crypto/ssh" - "golang.org/x/term" - "io" - "net" - "os" - "time" -) - -var ErrFailed = errors.New("ssh command failed") - -var username string -var password string +import "github.com/spf13/cobra" func NewCommand() *cobra.Command { command := &cobra.Command{ - Use: "ssh ", - Short: "SSH into the VM", - Args: cobra.ExactArgs(1), - RunE: run, + Use: "ssh", + Short: "SSH into resources", } - command.PersistentFlags().StringVarP(&username, "username", "u", "", - "SSH username") - command.PersistentFlags().StringVarP(&password, "password", "p", "", - "SSH password") + command.AddCommand(newSSHVMCommand()) return command } - -func run(cmd *cobra.Command, args []string) error { - name := args[0] - - client, err := client.New() - if err != nil { - return err - } - - wsConn, err := client.VMs().PortForward(cmd.Context(), name, 22) - if err != nil { - fmt.Printf("failed to forward an SSH port to VM %s: %v\n", name, err) - - return err - } - defer wsConn.Close() - - username, password = ChooseUsernameAndPassword(cmd.Context(), client, name, username, password) - - sshConfig := &ssh.ClientConfig{ - HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { - return nil - }, - User: username, - Auth: []ssh.AuthMethod{ - ssh.Password(password), - }, - } - - sshConn, chans, reqs, err := ssh.NewClientConn(wsConn, "", sshConfig) - if err != nil { - return fmt.Errorf("%w: failed to establish an SSH connection: %v", ErrFailed, err) - } - - sshClient := ssh.NewClient(sshConn, chans, reqs) - - sshSess, err := sshClient.NewSession() - if err != nil { - return fmt.Errorf("%w: failed to open an SSH session: %v", ErrFailed, err) - } - - // Switch controlling terminal into raw mode, - // otherwise ANSI escape sequences that allow - // for cursor control and more wouldn't work - terminalFd := int(os.Stdin.Fd()) - state, err := term.MakeRaw(terminalFd) - if err != nil { - return fmt.Errorf("%w: failed to switch controlling terminal into raw mode: %v", ErrFailed, err) - } - defer func() { - _ = term.Restore(terminalFd, state) - }() - - width, height, err := term.GetSize(terminalFd) - if err != nil { - return err - } - - if err := sshSess.RequestPty("xterm-256color", height, width, ssh.TerminalModes{}); err != nil { - return fmt.Errorf("%w: failed to request the PTY from the SSH server: %v", ErrFailed, err) - } - - sshSess.Stdout = os.Stdout - sshSess.Stderr = os.Stderr - sshSessStdinPipe, err := sshSess.StdinPipe() - if err != nil { - return err - } - - go func() { - _, _ = io.Copy(sshSessStdinPipe, os.Stdin) - _ = sshSessStdinPipe.Close() - _ = sshSess.Close() - }() - - // Periodically adjust remote terminal size - go func() { - for { - newWidth, newHeight, err := term.GetSize(terminalFd) - if err != nil { - return - } - - if height == newHeight && width == newWidth { - continue - } - - if err := sshSess.WindowChange(newHeight, newWidth); err != nil { - continue - } - - height = newHeight - width = newWidth - - time.Sleep(time.Second) - } - }() - - if err := sshSess.Shell(); err != nil { - return err - } - - sshErr := make(chan error) - - go func() { - sshErr <- sshSess.Wait() - }() - - select { - case err := <-sshErr: - return err - case <-cmd.Context().Done(): - return cmd.Context().Err() - } -} - -func ChooseUsernameAndPassword( - ctx context.Context, - client *client.Client, - vmName string, - usernameFromUser string, - passwordFromUser string, -) (string, string) { - // User settings override everything - if usernameFromUser != "" || passwordFromUser != "" { - return usernameFromUser, passwordFromUser - } - - // Try to get the credentials from the VM's object stored on controller - vm, err := client.VMs().Get(ctx, vmName) - if err != nil { - fmt.Printf("failed to retrieve VM %s's credentials from the API server: %v\n", vmName, err) - } - - if vm.Username != "" && vm.Password != "" { - return vm.Username, vm.Password - } - - // Fall back - fmt.Println("no credentials specified or found, trying default admin:admin credentials...") - - return "admin", "admin" -} diff --git a/internal/command/ssh/vm.go b/internal/command/ssh/vm.go new file mode 100644 index 0000000..7d15bcd --- /dev/null +++ b/internal/command/ssh/vm.go @@ -0,0 +1,179 @@ +package ssh + +import ( + "context" + "errors" + "fmt" + "github.com/cirruslabs/orchard/pkg/client" + "github.com/spf13/cobra" + "golang.org/x/crypto/ssh" + "golang.org/x/term" + "io" + "net" + "os" + "time" +) + +var ErrFailed = errors.New("ssh command failed") + +var username string +var password string + +func newSSHVMCommand() *cobra.Command { + command := &cobra.Command{ + Use: "vm VM_NAME", + Short: "SSH into the VM", + Args: cobra.ExactArgs(1), + RunE: runSSHVM, + } + + command.PersistentFlags().StringVarP(&username, "username", "u", "", + "SSH username") + command.PersistentFlags().StringVarP(&password, "password", "p", "", + "SSH password") + + return command +} + +func runSSHVM(cmd *cobra.Command, args []string) error { + name := args[0] + + client, err := client.New() + if err != nil { + return err + } + + wsConn, err := client.VMs().PortForward(cmd.Context(), name, 22) + if err != nil { + fmt.Printf("failed to forward an SSH port to VM %s: %v\n", name, err) + + return err + } + defer wsConn.Close() + + username, password = ChooseUsernameAndPassword(cmd.Context(), client, name, username, password) + + sshConfig := &ssh.ClientConfig{ + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + User: username, + Auth: []ssh.AuthMethod{ + ssh.Password(password), + }, + } + + sshConn, chans, reqs, err := ssh.NewClientConn(wsConn, "", sshConfig) + if err != nil { + return fmt.Errorf("%w: failed to establish an SSH connection: %v", ErrFailed, err) + } + + sshClient := ssh.NewClient(sshConn, chans, reqs) + + sshSess, err := sshClient.NewSession() + if err != nil { + return fmt.Errorf("%w: failed to open an SSH session: %v", ErrFailed, err) + } + + // Switch controlling terminal into raw mode, + // otherwise ANSI escape sequences that allow + // for cursor control and more wouldn't work + terminalFd := int(os.Stdin.Fd()) + state, err := term.MakeRaw(terminalFd) + if err != nil { + return fmt.Errorf("%w: failed to switch controlling terminal into raw mode: %v", ErrFailed, err) + } + defer func() { + _ = term.Restore(terminalFd, state) + }() + + width, height, err := term.GetSize(terminalFd) + if err != nil { + return err + } + + if err := sshSess.RequestPty("xterm-256color", height, width, ssh.TerminalModes{}); err != nil { + return fmt.Errorf("%w: failed to request the PTY from the SSH server: %v", ErrFailed, err) + } + + sshSess.Stdout = os.Stdout + sshSess.Stderr = os.Stderr + sshSessStdinPipe, err := sshSess.StdinPipe() + if err != nil { + return err + } + + go func() { + _, _ = io.Copy(sshSessStdinPipe, os.Stdin) + _ = sshSessStdinPipe.Close() + _ = sshSess.Close() + }() + + // Periodically adjust remote terminal size + go func() { + for { + newWidth, newHeight, err := term.GetSize(terminalFd) + if err != nil { + return + } + + if height == newHeight && width == newWidth { + continue + } + + if err := sshSess.WindowChange(newHeight, newWidth); err != nil { + continue + } + + height = newHeight + width = newWidth + + time.Sleep(time.Second) + } + }() + + if err := sshSess.Shell(); err != nil { + return err + } + + sshErr := make(chan error) + + go func() { + sshErr <- sshSess.Wait() + }() + + select { + case err := <-sshErr: + return err + case <-cmd.Context().Done(): + return cmd.Context().Err() + } +} + +func ChooseUsernameAndPassword( + ctx context.Context, + client *client.Client, + vmName string, + usernameFromUser string, + passwordFromUser string, +) (string, string) { + // User settings override everything + if usernameFromUser != "" || passwordFromUser != "" { + return usernameFromUser, passwordFromUser + } + + // Try to get the credentials from the VM's object stored on controller + vm, err := client.VMs().Get(ctx, vmName) + if err != nil { + fmt.Printf("failed to retrieve VM %s's credentials from the API server: %v\n", vmName, err) + } + + if vm.Username != "" && vm.Password != "" { + return vm.Username, vm.Password + } + + // Fall back + fmt.Println("no credentials specified or found, trying default admin:admin credentials...") + + return "admin", "admin" +} diff --git a/internal/command/stop/vm.go b/internal/command/stop/vm.go index a6083c2..cf67fc7 100644 --- a/internal/command/stop/vm.go +++ b/internal/command/stop/vm.go @@ -7,9 +7,10 @@ import ( func newStopVMCommand() *cobra.Command { return &cobra.Command{ - Use: "vm", - Args: cobra.ExactArgs(1), - RunE: runStopVM, + Use: "vm NAME", + Short: "Stop a VM", + Args: cobra.ExactArgs(1), + RunE: runStopVM, } } diff --git a/internal/command/vnc/vm.go b/internal/command/vnc/vm.go new file mode 100644 index 0000000..2373ace --- /dev/null +++ b/internal/command/vnc/vm.go @@ -0,0 +1,97 @@ +package vnc + +import ( + "fmt" + "github.com/cirruslabs/orchard/internal/command/ssh" + "github.com/cirruslabs/orchard/internal/proxy" + "github.com/cirruslabs/orchard/pkg/client" + "github.com/skratchdot/open-golang/open" + "github.com/spf13/cobra" + "net" +) + +const vncPort = 5900 + +var username string +var password string + +func newVNCVMCommand() *cobra.Command { + command := &cobra.Command{ + Use: "vm VM_NAME", + Short: "Open VNC session with the VM", + Args: cobra.ExactArgs(1), + RunE: runVNCVM, + } + + command.PersistentFlags().StringVarP(&username, "username", "u", "", + "VNC username") + command.PersistentFlags().StringVarP(&password, "password", "p", "", + "VNC password") + + return command +} + +func runVNCVM(cmd *cobra.Command, args []string) (err error) { + name := args[0] + + client, err := client.New() + if err != nil { + return err + } + + listener, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return err + } + defer func() { + if listenerErr := listener.Close(); listenerErr != nil && err == nil { + err = listenerErr + } + }() + + fmt.Printf("forwarding %s -> %s:%d...\n", listener.Addr(), name, vncPort) + + errCh := make(chan error, 1) + + go func() { + for { + conn, err := listener.Accept() + if err != nil { + errCh <- err + + return + } + + go func() { + wsConn, err := client.VMs().PortForward(cmd.Context(), name, vncPort) + if err != nil { + fmt.Printf("failed to forward port: %v\n", err) + + return + } + + if err := proxy.Connections(wsConn, conn); err != nil { + fmt.Printf("failed to forward port: %v\n", err) + } + }() + } + }() + + username, password = ssh.ChooseUsernameAndPassword(cmd.Context(), client, name, username, password) + + openURL := fmt.Sprintf("vnc://%s:%s@%s", username, password, listener.Addr().String()) + openURLSanitized := fmt.Sprintf("vnc://%s@%s", username, listener.Addr().String()) + + fmt.Printf("opening %s...\n", openURLSanitized) + + if err := open.Start(openURL); err != nil { + fmt.Printf("failed to open: %v\n", err) + } + + select { + case <-cmd.Context().Done(): + return cmd.Context().Err() + case err := <-errCh: + return err + } +} diff --git a/internal/command/vnc/vnc.go b/internal/command/vnc/vnc.go index 59cc0a0..2312241 100644 --- a/internal/command/vnc/vnc.go +++ b/internal/command/vnc/vnc.go @@ -1,97 +1,14 @@ package vnc -import ( - "fmt" - "github.com/cirruslabs/orchard/internal/command/ssh" - "github.com/cirruslabs/orchard/internal/proxy" - "github.com/cirruslabs/orchard/pkg/client" - "github.com/skratchdot/open-golang/open" - "github.com/spf13/cobra" - "net" -) - -const vncPort = 5900 - -var username string -var password string +import "github.com/spf13/cobra" func NewCommand() *cobra.Command { command := &cobra.Command{ - Use: "vnc ", - Short: "Open VNC session with the VM", - Args: cobra.ExactArgs(1), - RunE: run, + Use: "vnc", + Short: "Open VNC session with the resource", } - command.PersistentFlags().StringVarP(&username, "username", "u", "", - "VNC username") - command.PersistentFlags().StringVarP(&password, "password", "p", "", - "VNC password") + command.AddCommand(newVNCVMCommand()) return command } - -func run(cmd *cobra.Command, args []string) (err error) { - name := args[0] - - client, err := client.New() - if err != nil { - return err - } - - listener, err := net.Listen("tcp", "127.0.0.1:0") - if err != nil { - return err - } - defer func() { - if listenerErr := listener.Close(); listenerErr != nil && err == nil { - err = listenerErr - } - }() - - fmt.Printf("forwarding %s -> %s:%d...\n", listener.Addr(), name, vncPort) - - errCh := make(chan error, 1) - - go func() { - for { - conn, err := listener.Accept() - if err != nil { - errCh <- err - - return - } - - go func() { - wsConn, err := client.VMs().PortForward(cmd.Context(), name, vncPort) - if err != nil { - fmt.Printf("failed to forward port: %v\n", err) - - return - } - - if err := proxy.Connections(wsConn, conn); err != nil { - fmt.Printf("failed to forward port: %v\n", err) - } - }() - } - }() - - username, password = ssh.ChooseUsernameAndPassword(cmd.Context(), client, name, username, password) - - openURL := fmt.Sprintf("vnc://%s:%s@%s", username, password, listener.Addr().String()) - openURLSanitized := fmt.Sprintf("vnc://%s@%s", username, listener.Addr().String()) - - fmt.Printf("opening %s...\n", openURLSanitized) - - if err := open.Start(openURL); err != nil { - fmt.Printf("failed to open: %v\n", err) - } - - select { - case <-cmd.Context().Done(): - return cmd.Context().Err() - case err := <-errCh: - return err - } -} diff --git a/internal/command/worker/run.go b/internal/command/worker/run.go index 1d45dc4..37dec09 100644 --- a/internal/command/worker/run.go +++ b/internal/command/worker/run.go @@ -16,9 +16,10 @@ var bootstrapTokenRaw string func newRunCommand() *cobra.Command { cmd := &cobra.Command{ - Use: "run", - RunE: runWorker, - Args: cobra.ExactArgs(1), + Use: "run CONTROLLER_URL", + Short: "Run worker", + RunE: runWorker, + Args: cobra.ExactArgs(1), } cmd.PersistentFlags().StringVar(&bootstrapTokenRaw, "bootstrap-token", "", "a bootstrap token retrieved via `orchard get bootstrap-token `")