command_spaces

package
v0.18.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 1, 2025 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var CreateCmd = &cli.Command{
	Name:        "create",
	Usage:       "Create a space",
	Description: `Create a new space from the given template. The new space is not started automatically.`,
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the new space to create",
			Required: true,
		},
		&cli.StringArg{
			Name:     "template",
			Usage:    "The name of the template to use for the space",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:         "shell",
			Usage:        "The shell to use for the space (sh, bash, zsh or fish).",
			ConfigPath:   []string{"shell"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_SHELL"},
			DefaultValue: "bash",
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {

		shell := cmd.GetString("shell")
		if shell != "bash" && shell != "zsh" && shell != "fish" && shell != "sh" {
			return fmt.Errorf("Invalid shell: %s", shell)
		}

		fmt.Println("Creating space: ", cmd.GetStringArg("space"), " from template: ", cmd.GetStringArg("template"))

		alias := cmd.GetString("alias")
		cfg := config.GetServerAddr(alias, cmd)
		client, err := apiclient.NewClient(cfg.HttpServer, cfg.ApiToken, cmd.GetBool("tls-skip-verify"))
		if err != nil {
			return fmt.Errorf("Failed to create API client: %w", err)
		}

		templates, _, err := client.GetTemplates(context.Background())
		if err != nil {
			return fmt.Errorf("Error getting templates: %w", err)
		}

		// Find the ID of the template from the name
		var templateId string = ""
		for _, template := range templates.Templates {
			if template.Name == cmd.GetStringArg("template") {
				templateId = template.Id
				break
			}
		}

		if templateId == "" {
			return fmt.Errorf("Template not found: %s", cmd.GetStringArg("template"))
		}

		space := &apiclient.SpaceRequest{
			Name:        cmd.GetStringArg("space"),
			Description: "",
			TemplateId:  templateId,
			Shell:       shell,
			UserId:      "",
			AltNames:    []string{},
		}

		_, _, err = client.CreateSpace(context.Background(), space)
		if err != nil {
			return fmt.Errorf("Error creating space: %w", err)
		}

		fmt.Println("Space created: ", cmd.GetStringArg("space"))
		return nil
	},
}
View Source
var DeleteCmd = &cli.Command{
	Name:        "delete",
	Usage:       "Delete a space",
	Description: "Delete a stopped space, all data will be lost.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the new space to create",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceName := cmd.GetStringArg("space")

		// Prompt the user to confirm the deletion
		var confirm string
		fmt.Printf("Are you sure you want to delete the space %s and all data? (yes/no): ", spaceName)
		fmt.Scanln(&confirm)
		if confirm != "yes" {
			fmt.Println("Deletion cancelled.")
			return nil
		}

		alias := cmd.GetString("alias")
		cfg := config.GetServerAddr(alias, cmd)
		client, err := apiclient.NewClient(cfg.HttpServer, cfg.ApiToken, cmd.GetBool("tls-skip-verify"))
		if err != nil {
			return fmt.Errorf("Failed to create API client: %w", err)
		}

		spaces, _, err := client.GetSpaces(context.Background(), "")
		if err != nil {
			return fmt.Errorf("Error getting spaces: %w", err)
		}

		// Find the space by name
		var spaceId string
		for _, space := range spaces.Spaces {
			if space.Name == spaceName {
				spaceId = space.Id
				break
			}
		}

		if spaceId == "" {
			return fmt.Errorf("Space not found: %s", spaceName)
		}

		_, err = client.DeleteSpace(context.Background(), spaceId)
		if err != nil {
			return fmt.Errorf("Error deleting space: %w", err)
		}

		fmt.Println("Space deleting: ", spaceName)
		return nil
	},
}
View Source
var ListCmd = &cli.Command{
	Name:        "list",
	Usage:       "List the available spaces and their status",
	Description: "Lists the available spaces for the logged in user and the state of each space.",
	MaxArgs:     cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		alias := cmd.GetString("alias")
		cfg := config.GetServerAddr(alias, cmd)
		client, err := apiclient.NewClient(cfg.HttpServer, cfg.ApiToken, cmd.GetBool("tls-skip-verify"))
		if err != nil {
			fmt.Println("Failed to create API client:", err)
			os.Exit(1)
		}

		user, err := client.WhoAmI(context.Background())
		if err != nil {
			fmt.Println("Error getting user: ", err)
			return nil
		}

		spaces, _, err := client.GetSpaces(context.Background(), user.Id)
		if err != nil {
			fmt.Println("Error getting spaces: ", err)
			return nil
		}

		data := [][]string{{"Name", "Template", "Zone", "Status", "Ports"}}
		for _, space := range spaces.Spaces {
			status := ""
			ports := make([]string, 0)

			if space.IsRemote {
				status = "Remote "
			}

			if space.IsDeployed {
				if space.IsPending {
					status = status + "Stopping"
				} else {
					status = status + "Running"
				}
			} else if space.IsDeleting {
				status = status + "Deleting"
			} else if space.IsPending {
				status = status + "Starting"
			}

			for port, desc := range space.HttpPorts {
				var p string
				if port == desc {
					p = port
				} else {
					p = fmt.Sprintf("%s (%s)", desc, port)
				}
				ports = append(ports, p)
			}

			for port, desc := range space.TcpPorts {
				var p string
				if port == desc {
					p = port
				} else {
					p = fmt.Sprintf("%s (%s)", desc, port)
				}
				ports = append(ports, p)
			}

			portText := ""
			if len(ports) > 0 {
				portText = ports[0]
				for i := 1; i < len(ports); i++ {
					portText = portText + ", " + ports[i]
				}
			}

			data = append(data, []string{space.Name, space.TemplateName, space.Zone, status, portText})
		}

		util.PrintTable(data)
		return nil
	},
}
View Source
var LogsCmd = &cli.Command{
	Name:        "logs",
	Usage:       "Show the logs from a space",
	Description: "Display the logs for a space.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the space to show logs for",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:         "follow",
			Aliases:      []string{"f"},
			Usage:        "Follow the logs.",
			DefaultValue: false,
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceName := cmd.GetStringArg("space")
		follow := cmd.GetBool("follow")
		alias := cmd.GetString("alias")
		cfg := config.GetServerAddr(alias, cmd)
		client, err := apiclient.NewClient(cfg.HttpServer, cfg.ApiToken, cmd.GetBool("tls-skip-verify"))
		if err != nil {
			return fmt.Errorf("Failed to create API client: %w", err)
		}

		user, err := client.WhoAmI(context.Background())
		if err != nil {
			return fmt.Errorf("Error getting user: %w", err)
		}

		spaces, _, err := client.GetSpaces(context.Background(), user.Id)
		if err != nil {
			return fmt.Errorf("Error getting spaces: %w", err)
		}

		// Find the space by name
		var spaceId string
		for _, space := range spaces.Spaces {
			if space.Name == spaceName {
				spaceId = space.Id
				break
			}
		}

		if spaceId == "" {
			return fmt.Errorf("Space not found: %s", spaceName)
		}

		wsUrl := fmt.Sprintf("%s/logs/%s/stream", cfg.WsServer, spaceId)
		header := http.Header{"Authorization": []string{fmt.Sprintf("Bearer %s", cfg.ApiToken)}}
		dialer := websocket.DefaultDialer
		dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: cmd.GetBool("tls-skip-verify")}
		dialer.HandshakeTimeout = 5 * time.Second
		ws, response, err := dialer.Dial(wsUrl, header)
		if err != nil {
			if response != nil && response.StatusCode == http.StatusUnauthorized {
				return fmt.Errorf("failed to authenticate with server, check remote token")
			} else if response != nil && response.StatusCode == http.StatusForbidden {
				return fmt.Errorf("no permission to view logs")
			}
			return fmt.Errorf("Error connecting to websocket: %w", err)
		}
		defer ws.Close()

		for {
			_, message, err := ws.ReadMessage()
			if err != nil {
				fmt.Println("Error reading message: ", err)
				break
			}

			if len(message) == 1 && message[0] == 0 && !follow {
				break
			}

			fmt.Print(string(message))
		}
		return nil
	},
}
View Source
var RestartCmd = &cli.Command{
	Name:        "restart",
	Usage:       "Restart a space",
	Description: "Restart the named space.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the space to restart",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceName := cmd.GetStringArg("space")
		fmt.Println("Restarting space: ", spaceName)

		alias := cmd.GetString("alias")
		cfg := config.GetServerAddr(alias, cmd)
		client, err := apiclient.NewClient(cfg.HttpServer, cfg.ApiToken, cmd.GetBool("tls-skip-verify"))
		if err != nil {
			return fmt.Errorf("Failed to create API client: %w", err)
		}

		user, err := client.WhoAmI(context.Background())
		if err != nil {
			return fmt.Errorf("Error getting user: %w", err)
		}

		spaces, _, err := client.GetSpaces(context.Background(), user.Id)
		if err != nil {
			return fmt.Errorf("Error getting spaces: %w", err)
		}

		// Find the space by name
		var spaceId string
		for _, space := range spaces.Spaces {
			if space.Name == spaceName {
				spaceId = space.Id
				break
			}
		}

		if spaceId == "" {
			return fmt.Errorf("Space not found: %s", spaceName)
		}

		_, err = client.RestartSpace(context.Background(), spaceId)
		if err != nil {
			return fmt.Errorf("Error restarting space: %w", err)
		}

		fmt.Println("Space restarting: ", spaceName)
		return nil
	},
}
View Source
var SpacesCmd = &cli.Command{
	Name:        "space",
	Usage:       "Manage spaces",
	Description: "Manage your spaces from the command line.",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:    "server",
			Aliases: []string{"s"},
			Usage:   "The address of the remote server to manage spaces on.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_SERVER"},
			Global:  true,
		},
		&cli.StringFlag{
			Name:    "token",
			Aliases: []string{"t"},
			Usage:   "The token to use for authentication.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TOKEN"},
			Global:  true,
		},
		&cli.BoolFlag{
			Name:         "tls-skip-verify",
			Usage:        "Skip TLS verification when talking to server.",
			ConfigPath:   []string{"tls.skip_verify"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TLS_SKIP_VERIFY"},
			DefaultValue: true,
			Global:       true,
		},
		&cli.StringFlag{
			Name:         "alias",
			Aliases:      []string{"a"},
			Usage:        "The server alias to use.",
			DefaultValue: "default",
			Global:       true,
		},
	},
	Commands: []*cli.Command{
		ListCmd,
		StartCmd,
		StopCmd,
		RestartCmd,
		CreateCmd,
		DeleteCmd,
		LogsCmd,
		TunnelPortCmd,
	},
}
View Source
var StartCmd = &cli.Command{
	Name:        "start",
	Usage:       "Start a space",
	Description: "Start the named space.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the space to start",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceName := cmd.GetStringArg("space")
		fmt.Println("Starting space: ", spaceName)

		alias := cmd.GetString("alias")
		cfg := config.GetServerAddr(alias, cmd)
		client, err := apiclient.NewClient(cfg.HttpServer, cfg.ApiToken, cmd.GetBool("tls-skip-verify"))
		if err != nil {
			return fmt.Errorf("Failed to create API client: %w", err)
		}

		user, err := client.WhoAmI(context.Background())
		if err != nil {
			return fmt.Errorf("Error getting user: %w", err)
		}

		spaces, _, err := client.GetSpaces(context.Background(), user.Id)
		if err != nil {
			return fmt.Errorf("Error getting spaces: %w", err)
		}

		// Find the space by name
		var spaceId string
		for _, space := range spaces.Spaces {
			if space.Name == spaceName {
				spaceId = space.Id
				break
			}
		}

		if spaceId == "" {
			return fmt.Errorf("Space not found: %s", spaceName)
		}

		code, err := client.StartSpace(context.Background(), spaceId)
		if err != nil {
			if code == 503 {
				return fmt.Errorf("Cannot start space as outside of schedule")
			} else if code == 507 {
				return fmt.Errorf("Cannot start space as resource quota exceeded")
			} else {
				return fmt.Errorf("Error starting space: %w", err)
			}
			return nil
		}

		fmt.Println("Space started: ", spaceName)
		return nil
	},
}
View Source
var StopCmd = &cli.Command{
	Name:        "stop",
	Usage:       "Stop a space",
	Description: "Stop the named space.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the space to stop",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceName := cmd.GetStringArg("space")
		fmt.Println("Stopping space: ", spaceName)

		alias := cmd.GetString("alias")
		cfg := config.GetServerAddr(alias, cmd)
		client, err := apiclient.NewClient(cfg.HttpServer, cfg.ApiToken, cmd.GetBool("tls-skip-verify"))
		if err != nil {
			return fmt.Errorf("Failed to create API client: %w", err)
		}

		user, err := client.WhoAmI(context.Background())
		if err != nil {
			return fmt.Errorf("Error getting user: %w", err)
		}

		spaces, _, err := client.GetSpaces(context.Background(), user.Id)
		if err != nil {
			return fmt.Errorf("Error getting spaces: %w", err)
		}

		// Find the space by name
		var spaceId string
		for _, space := range spaces.Spaces {
			if space.Name == spaceName {
				spaceId = space.Id
				break
			}
		}

		if spaceId == "" {
			return fmt.Errorf("Space not found: %s", spaceName)
		}

		_, err = client.StopSpace(context.Background(), spaceId)
		if err != nil {
			return fmt.Errorf("Error stopping space: %w", err)
		}

		fmt.Println("Space stopped: ", spaceName)
		return nil
	},
}
View Source
var TunnelPortCmd = &cli.Command{
	Name:        "tunnel",
	Usage:       "<space> <listen> <port>",
	Description: `Open a tunnel between a port inside a space and a port on the local machine.`,
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the space to tunnel from",
			Required: true,
		},
		&cli.IntArg{
			Name:     "listen",
			Usage:    "The port to listen on within the space",
			Required: true,
		},
		&cli.IntArg{
			Name:     "port",
			Usage:    "The local port to connect to",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:         "tls",
			Usage:        "Enable TLS encryption for the tunnel.",
			ConfigPath:   []string{"tls"},
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_TUNNEL_TLS"},
			DefaultValue: false,
		},
		&cli.StringFlag{
			Name:    "port-tls-name",
			Usage:   "The name to present to local port when using.",
			EnvVars: []string{config.CONFIG_ENV_PREFIX + "_TLS_NAME"},
		},
		&cli.BoolFlag{
			Name:         "port-tls-skip-verify",
			Usage:        "Skip TLS verification when talking to local port via https, this allows self signed certificates.",
			EnvVars:      []string{config.CONFIG_ENV_PREFIX + "_PORT_TLS_SKIP_VERIFY"},
			DefaultValue: true,
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		alias := cmd.GetString("alias")
		cfg := config.GetServerAddr(alias, cmd)

		spaceName := cmd.GetStringArg("space")

		listenPort := cmd.GetIntArg("listen")
		if listenPort < 1 || listenPort > 65535 {
			return fmt.Errorf("Invalid port number, port numbers must be between 1 and 65535", 1)
		}

		localPort := cmd.GetIntArg("port")
		if localPort < 1 || localPort > 65535 {
			return fmt.Errorf("Invalid port number, port numbers must be between 1 and 65535", 1)
		}

		opts := tunnel_server.TunnelOpts{
			Type:          tunnel_server.PortTunnel,
			Protocol:      "tcp",
			LocalPort:     uint16(localPort),
			SpaceName:     spaceName,
			SpacePort:     uint16(listenPort),
			TlsName:       cmd.GetString("port-tls-name"),
			TlsSkipVerify: cmd.GetBool("port-tls-skip-verify"),
		}

		if cmd.GetBool("tls") {
			opts.Protocol = "tls"
		}

		client := tunnel_server.NewTunnelClient(
			cfg.WsServer,
			cfg.HttpServer,
			cfg.ApiToken,
			cmd.GetBool("tls-skip-verify"),
			&opts,
		)
		if err := client.ConnectAndServe(); err != nil {
			return fmt.Errorf("Failed to create tunnel: %w", err)
		}

		c := make(chan os.Signal, 1)
		signal.Notify(c, os.Interrupt, syscall.SIGTERM)

		select {
		case <-client.GetCtx().Done():
		case <-c:
		}

		client.Shutdown()

		fmt.Println("\r")
		log.Info().Msg("Tunnel shutdown")

		return nil
	},
}

Functions

This section is empty.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL