mirror of
https://github.com/cirruslabs/orchard.git
synced 2026-10-19 08:07:58 +02:00
--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.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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", "",
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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),
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 <VM name> [<local port>]:<remote port>",
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
|
||||
+4
-169
@@ -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 <VM name>",
|
||||
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"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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 <VM name>",
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <service-account-name-for-workers>`")
|
||||
|
||||
Reference in New Issue
Block a user