command_spaces

package
v0.22.3 Latest Latest
Warning

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

Go to latest
Published: Jan 8, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var CopyCmd = &cli.Command{
	Name:        "copy",
	Usage:       "Copy files between local machine and space",
	Description: "Copy files to or from a running space. Use spacename:path format for space files.",
	Flags: []cli.Flag{
		&cli.StringFlag{
			Name:         "workdir",
			Aliases:      []string{"w"},
			Usage:        "Working directory for relative paths in space",
			DefaultValue: "",
		},
	},
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "source",
			Required: true,
			Usage:    "Source file path (use spacename:path for space files)",
		},
		&cli.StringArg{
			Name:     "dest",
			Required: true,
			Usage:    "Destination file path (use spacename:path for space files)",
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		workdir := cmd.GetString("workdir")
		source := cmd.GetStringArg("source")
		dest := cmd.GetStringArg("dest")

		// Determine direction and extract space name from the path with space: prefix
		var direction, localPath, spacePath, spaceName string

		if colonIndex := strings.Index(source, ":"); colonIndex > 0 {

			direction = "from_space"
			spaceName = source[:colonIndex]
			spacePath = source[colonIndex+1:]
			localPath = dest
			if spacePath == "" {
				return fmt.Errorf("Space path cannot be empty after '%s:'", spaceName)
			}
		} else if colonIndex := strings.Index(dest, ":"); colonIndex > 0 {

			direction = "to_space"
			spaceName = dest[:colonIndex]
			spacePath = dest[colonIndex+1:]
			localPath = source
			if spacePath == "" {
				return fmt.Errorf("Space path cannot be empty after '%s:'", spaceName)
			}
		} else {
			return fmt.Errorf("One path must use the format 'spacename:path'")
		}

		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/space-io/%s/copy", 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 copy files in this space")
			}
			return fmt.Errorf("Error connecting to websocket: %w", err)
		}
		defer ws.Close()

		var copyRequest apiclient.CopyFileRequest
		copyRequest.Direction = direction
		copyRequest.Workdir = workdir

		if direction == "to_space" {

			content, err := os.ReadFile(localPath)
			if err != nil {
				return fmt.Errorf("Error reading local file: %w", err)
			}

			copyRequest.DestPath = spacePath
			copyRequest.Content = content

			fmt.Printf("Copying %s to %s:%s...\n", localPath, spaceName, spacePath)
		} else {

			copyRequest.SourcePath = spacePath
			fmt.Printf("Copying %s:%s to %s...\n", spaceName, spacePath, localPath)
		}

		err = ws.WriteJSON(copyRequest)
		if err != nil {
			return fmt.Errorf("Error sending copy request: %w", err)
		}

		// Read the response
		var result map[string]interface{}
		err = ws.ReadJSON(&result)
		if err != nil {
			return fmt.Errorf("Error reading response: %w", err)
		}

		success, ok := result["success"].(bool)
		if !ok || !success {
			errorMsg, _ := result["error"].(string)
			return fmt.Errorf("Copy failed: %s", errorMsg)
		}

		if direction == "from_space" {
			// Write content to local file
			var content []byte
			if contentStr, ok := result["content"].(string); ok {
				// Decode base64 content
				var err error
				content, err = base64.StdEncoding.DecodeString(contentStr)
				if err != nil {
					return fmt.Errorf("Error decoding file content: %w", err)
				}
			} else {
				return fmt.Errorf("Invalid content format in response")
			}

			localDir := filepath.Dir(localPath)
			if err := os.MkdirAll(localDir, 0755); err != nil {
				return fmt.Errorf("Error creating local directory: %w", err)
			}

			err = os.WriteFile(localPath, content, 0644)
			if err != nil {
				return fmt.Errorf("Error writing local file: %w", err)
			}
		}

		fmt.Println("Copy completed successfully")
		return nil
	},
}
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,
	Flags: []cli.Flag{
		&cli.BoolFlag{
			Name:    "force",
			Aliases: []string{"f"},
			Usage:   "Skip confirmation prompt.",
		},
	},
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceName := cmd.GetStringArg("space")

		if !cmd.GetBool("force") {
			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)
		}

		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 {
			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 GetFieldCmd = &cli.Command{
	Name:        "get-field",
	Usage:       "Get a custom field from a space",
	Description: "Get a custom field value from an existing space.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the space to get the field from",
			Required: true,
		},
		&cli.StringArg{
			Name:     "field",
			Usage:    "The name of the custom field to get",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceName := cmd.GetStringArg("space")
		fieldName := cmd.GetStringArg("field")

		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)
		}

		response, _, err := client.GetSpaceCustomField(context.Background(), spaceId, fieldName)
		if err != nil {
			return fmt.Errorf("Error getting custom field: %w", err)
		}

		fmt.Println(response.Value)
		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)
		}

		pingResponse, err := client.Ping(context.Background())
		if err != nil {
			fmt.Println("Error getting server info:", err)
			os.Exit(1)
		}
		zone := pingResponse.Zone

		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 {

			if zone != "" && space.Zone != "" && space.Zone != zone {
				continue
			}

			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 RunCmd = &cli.Command{
	Name:        "run",
	Usage:       "Run a command in a space",
	Description: "Execute a command within a running space and stream the output.",
	Flags: []cli.Flag{
		&cli.IntFlag{
			Name:         "timeout",
			Aliases:      []string{"t"},
			Usage:        "Command timeout in seconds",
			DefaultValue: 30,
		},
		&cli.StringFlag{
			Name:         "workdir",
			Aliases:      []string{"w"},
			Usage:        "Working directory for the command",
			DefaultValue: "",
		},
	},
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Required: true,
			Usage:    "The name of the space to run the command in",
		},
		&cli.StringArg{
			Name:     "command",
			Required: true,
			Usage:    "The command to run in the space",
		},
	},
	MinArgs: 1,
	MaxArgs: cli.UnlimitedArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		timeout := cmd.GetInt("timeout")
		workdir := cmd.GetString("workdir")
		spaceName := cmd.GetStringArg("space")
		command := cmd.GetStringArg("command")

		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/space-io/%s/run", 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 run commands in this space")
			}
			return fmt.Errorf("Error connecting to websocket: %w", err)
		}
		defer ws.Close()

		execRequest := apiclient.RunCommandRequest{
			Command: command,
			Args:    cmd.GetArgs(),
			Timeout: timeout,
			Workdir: workdir,
		}

		err = ws.WriteJSON(execRequest)
		if err != nil {
			return fmt.Errorf("Error sending command: %w", err)
		}

		for {
			_, message, err := ws.ReadMessage()
			if err != nil {

				if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseGoingAway) {
					break
				}
				return fmt.Errorf("Error reading message: %w", err)
			}

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

			fmt.Print(string(message))
		}

		return nil
	},
}
View Source
var SetFieldCmd = &cli.Command{
	Name:        "set-field",
	Usage:       "Set a custom field on a space",
	Description: "Set or update a custom field value on an existing space.",
	Arguments: []cli.Argument{
		&cli.StringArg{
			Name:     "space",
			Usage:    "The name of the space to update",
			Required: true,
		},
		&cli.StringArg{
			Name:     "field",
			Usage:    "The name of the custom field to set",
			Required: true,
		},
		&cli.StringArg{
			Name:     "value",
			Usage:    "The value to set for the custom field",
			Required: true,
		},
	},
	MaxArgs: cli.NoArgs,
	Run: func(ctx context.Context, cmd *cli.Command) error {
		spaceName := cmd.GetStringArg("space")
		fieldName := cmd.GetStringArg("field")
		fieldValue := cmd.GetStringArg("value")

		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.SetSpaceCustomField(context.Background(), spaceId, fieldName, fieldValue)
		if err != nil {
			return fmt.Errorf("Error setting custom field: %w", err)
		}

		fmt.Printf("Custom field '%s' set to '%s' on space '%s'\n", fieldName, fieldValue, 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,
		RunCmd,
		CopyCmd,
		TunnelPortCmd,
		SetFieldCmd,
		GetFieldCmd,
	},
}
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)
			}
		}

		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:       "Tunnel a port to the local machine",
	Description: `Open a tunnel from a port inside a space to 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")
		}

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

		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("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