Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -879,10 +879,12 @@ hyp delete hyp-space-template --name <template-name>

#### Space Access

Create remote access to spaces:
Create remote access to spaces. The `--connection-type` accepts `web-ui` or any `{ide}-remote` pattern (e.g. `vscode-remote`, `kiro-remote`, `cursor-remote`):

```bash
hyp create hyp-space-access --name myspace --connection-type vscode-remote
hyp create hyp-space-access --name myspace --connection-type kiro-remote
hyp create hyp-space-access --name myspace --connection-type cursor-remote
hyp create hyp-space-access --name myspace --connection-type web-ui
```

Expand Down Expand Up @@ -1389,6 +1391,10 @@ space = HPSpace.get(name="myspace")
vscode_access = space.create_space_access(connection_type="vscode-remote")
print(f"VS Code URL: {vscode_access['SpaceConnectionUrl']}")

# Create Kiro remote access
kiro_access = space.create_space_access(connection_type="kiro-remote")
print(f"Kiro URL: {kiro_access['SpaceConnectionUrl']}")

# Create web UI access
web_access = space.create_space_access(connection_type="web-ui")
print(f"Web UI URL: {web_access['SpaceConnectionUrl']}")
Expand Down
3 changes: 2 additions & 1 deletion doc/cli/space/cli_space.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,12 +326,13 @@ hyp create hyp-space-access [OPTIONS]
|-----------|------|----------|-------------|
| `--name` | TEXT | Yes | Name of the space to create access for |
| `--namespace, -n` | TEXT | No | Kubernetes namespace (default: "default") |
| `--connection-type, -t` | TEXT | No | Remote access type: vscode-remote or web-ui (default: "vscode-remote") |
| `--connection-type, -t` | TEXT | No | Connection type: 'web-ui' or any '{ide}-remote' pattern (e.g. vscode-remote, kiro-remote, cursor-remote). Default: "vscode-remote" |

#### Example

```bash
hyp create hyp-space-access --name my-space --namespace default --connection-type vscode-remote
hyp create hyp-space-access --name my-space --connection-type kiro-remote
```

## Space Template Commands
Expand Down
7 changes: 7 additions & 0 deletions doc/getting_started/space.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ Access the space via `http://localhost:<local-port>` after port forwarding is es
# Create VS Code remote access
hyp create hyp-space-access --name myspace --connection-type vscode-remote

# Create Kiro remote access
hyp create hyp-space-access --name myspace --connection-type kiro-remote

# Create web UI access
hyp create hyp-space-access --name myspace --connection-type web-ui
```
Expand All @@ -259,6 +262,10 @@ space = HPSpace.get(name="myspace")
vscode_access = space.create_space_access(connection_type="vscode-remote")
print(f"VS Code URL: {vscode_access['SpaceConnectionUrl']}")

# Create Kiro remote access
kiro_access = space.create_space_access(connection_type="kiro-remote")
print(f"Kiro URL: {kiro_access['SpaceConnectionUrl']}")

# Create web UI access
web_access = space.create_space_access(connection_type="web-ui")
print(f"Web UI URL: {web_access['SpaceConnectionUrl']}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,12 @@ This will print a `JSON`-formatted output to the console that contains a `SpaceC

![VSCode connected](../images/vscode-connected.png)

You can also connect using other IDEs that support the `{ide}-remote` pattern, such as Kiro or Cursor:
```bash
hyp create hyp-space-access --name $SPACE_NAME --connection-type kiro-remote
hyp create hyp-space-access --name $SPACE_NAME --connection-type cursor-remote
```

Lastly let's generate a JupyterLab web UI URL (if the web ui has been enabled in your environment):
```bash
hyp create hyp-space-access --name $SPACE_NAME --connection-type web-ui
Expand Down
2 changes: 1 addition & 1 deletion src/sagemaker/hyperpod/cli/commands/space_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
@click.option("--connection-type", "-t",
required=False,
default="vscode-remote",
help="Remote access type supported values: [vscode-remote, web-ui] [default: vscode-remote]"
help="Connection type: 'web-ui' or '{ide}-remote' pattern (e.g. vscode-remote, kiro-remote, cursor-remote) [default: vscode-remote]"
)
@_hyperpod_telemetry_emitter(Feature.HYPERPOD_CLI, "create_space_access")
@handle_cli_exceptions()
Expand Down
23 changes: 18 additions & 5 deletions src/sagemaker/hyperpod/space/hyperpod_space.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import logging
import re
import yaml
import boto3
from sagemaker.hyperpod.common.utils import create_boto3_client
Expand Down Expand Up @@ -819,12 +820,15 @@ def get_logs(self, pod_name: Optional[str] = None, container: Optional[str] = No
except Exception as e:
handle_exception(e, pod_name, self.config.namespace)

# Validates the {ide}-remote pattern: alphanumeric segments separated by single hyphens.
_remote_connection_type_regex = re.compile(r"^[a-zA-Z0-9]+(?:-[a-zA-Z0-9]+)*-remote$")

@_hyperpod_telemetry_emitter(Feature.HYPERPOD, "create_space_access")
def create_space_access(self, connection_type: str = "vscode-remote") -> Dict[str, str]:
"""Create a space access for this space.

Creates a space access resource that provides remote connection capabilities
to the space. Supports VS Code remote development and web UI access types.
to the space. Supports IDE remote development and web UI access types.

**Parameters:**

Expand All @@ -837,15 +841,17 @@ def create_space_access(self, connection_type: str = "vscode-remote") -> Dict[st
- Description
* - connection_type
- str, optional
- The IDE type for remote access. Must be "vscode-remote" or "web-ui" (default: "vscode-remote")
- The connection type for remote access. Must be "web-ui" or follow the
'{ide}-remote' pattern (e.g. "vscode-remote", "kiro-remote", "cursor-remote").
Default: "vscode-remote"

**Returns:**

Dict[str, str]: Dictionary containing 'SpaceConnectionType' and 'SpaceConnectionUrl' keys

**Raises:**

ValueError: If connection_type is not "vscode-remote" or "web-ui"
ValueError: If connection_type is not "web-ui" or a valid '{ide}-remote' pattern
Exception: If the space access creation fails or Kubernetes API call fails

.. dropdown:: Usage Examples
Expand All @@ -858,15 +864,22 @@ def create_space_access(self, connection_type: str = "vscode-remote") -> Dict[st
>>> access = space.create_space_access("vscode-remote")
>>> print(f"Connection URL: {access['SpaceConnectionUrl']}")

>>> # Create Kiro remote access
>>> access = space.create_space_access("kiro-remote")
>>> print(f"Connection URL: {access['SpaceConnectionUrl']}")

>>> # Create web UI access
>>> access = space.create_space_access("web-ui")
>>> print(f"Web UI URL: {access['SpaceConnectionUrl']}")
"""
self.verify_kube_config()
logger = self.get_logger()

if connection_type not in {"vscode-remote", "web-ui"}:
raise ValueError("--connection-type must be 'vscode-remote' or 'web-ui'.")
if connection_type != "web-ui" and not self._remote_connection_type_regex.match(connection_type):
Comment thread
earaghbidikashani marked this conversation as resolved.
raise ValueError(
f"--connection-type must be 'web-ui' or follow the '{{ide}}-remote' pattern "
f"(e.g. 'vscode-remote', 'kiro-remote', 'cursor-remote')."
)

config = {
"metadata": {
Expand Down
57 changes: 57 additions & 0 deletions test/unit_tests/cli/test_space_access.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,60 @@ def test_space_access_create_default_values(self, mock_hp_space_class):
mock_hp_space_class.get.assert_called_once_with(name='test-space', namespace='default')
mock_space_instance.create_space_access.assert_called_once_with(connection_type='vscode-remote')

@patch('sagemaker.hyperpod.cli.commands.space_access.HPSpace')
Comment thread
earaghbidikashani marked this conversation as resolved.
def test_space_access_create_kiro_remote(self, mock_hp_space_class):
"""Test space access creation with kiro-remote connection type"""
mock_space_instance = Mock()
mock_space_instance.create_space_access.return_value = {
"SpaceConnectionType": "kiro-remote",
"SpaceConnectionUrl": "https://kiro-url.com"
}
mock_hp_space_class.get.return_value = mock_space_instance

result = self.runner.invoke(space_access_create, [
'--name', 'test-space',
'--connection-type', 'kiro-remote'
])

assert result.exit_code == 0
assert "https://kiro-url.com" in result.output
mock_space_instance.create_space_access.assert_called_once_with(connection_type='kiro-remote')

@patch('sagemaker.hyperpod.cli.commands.space_access.HPSpace')
def test_space_access_create_cursor_remote(self, mock_hp_space_class):
"""Test space access creation with cursor-remote connection type"""
mock_space_instance = Mock()
mock_space_instance.create_space_access.return_value = {
"SpaceConnectionType": "cursor-remote",
"SpaceConnectionUrl": "https://cursor-url.com"
}
mock_hp_space_class.get.return_value = mock_space_instance

result = self.runner.invoke(space_access_create, [
'--name', 'test-space',
'--connection-type', 'cursor-remote'
])

assert result.exit_code == 0
assert "https://cursor-url.com" in result.output
mock_space_instance.create_space_access.assert_called_once_with(connection_type='cursor-remote')


@pytest.mark.parametrize("invalid_type", [
"invalid-type", "-remote", "remote", "my--vscode-remote", "vscode_remote", "",
])
@patch('sagemaker.hyperpod.cli.commands.space_access.HPSpace')
def test_space_access_create_invalid_connection_type(self, mock_hp_space_class, invalid_type):
"""Test space access creation rejects invalid connection type patterns"""
mock_space_instance = Mock()
mock_space_instance.create_space_access.side_effect = ValueError(
"--connection-type must be 'web-ui' or follow the '{ide}-remote' pattern"
)
mock_hp_space_class.get.return_value = mock_space_instance

result = self.runner.invoke(space_access_create, [
'--name', 'test-space',
'--connection-type', invalid_type
] if invalid_type else ['--name', 'test-space', '--connection-type', ''])

assert result.exit_code != 0
38 changes: 38 additions & 0 deletions test/unit_tests/test_hyperpod_space.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,44 @@ def test_create_space_access_custom_ide(self, mock_verify_config, mock_custom_ap
)
self.assertEqual(result, {"SpaceConnectionType": "web-ui", "SpaceConnectionUrl": "https://example.com/webui-access"})

@patch('sagemaker.hyperpod.space.hyperpod_space.client.CustomObjectsApi')
@patch.object(HPSpace, 'verify_kube_config')
def test_create_space_access_kiro_remote(self, mock_verify_config, mock_custom_api_class):
"""Test space access creation with kiro-remote connection type"""
mock_custom_api = Mock()
mock_custom_api_class.return_value = mock_custom_api
mock_custom_api.create_namespaced_custom_object.return_value = {
"status": {"workspaceConnectionUrl": "https://example.com/kiro-access"}
}

result = self.hp_space.create_space_access(connection_type="kiro-remote")

self.assertEqual(result["SpaceConnectionType"], "kiro-remote")
self.assertEqual(result["SpaceConnectionUrl"], "https://example.com/kiro-access")

@patch('sagemaker.hyperpod.space.hyperpod_space.client.CustomObjectsApi')
@patch.object(HPSpace, 'verify_kube_config')
def test_create_space_access_cursor_remote(self, mock_verify_config, mock_custom_api_class):
"""Test space access creation with cursor-remote connection type"""
mock_custom_api = Mock()
mock_custom_api_class.return_value = mock_custom_api
mock_custom_api.create_namespaced_custom_object.return_value = {
"status": {"workspaceConnectionUrl": "https://example.com/cursor-access"}
}

result = self.hp_space.create_space_access(connection_type="cursor-remote")

self.assertEqual(result["SpaceConnectionType"], "cursor-remote")
self.assertEqual(result["SpaceConnectionUrl"], "https://example.com/cursor-access")

@patch.object(HPSpace, 'verify_kube_config')
def test_create_space_access_invalid_pattern(self, mock_verify_config):
"""Test space access creation rejects invalid connection type patterns"""
invalid_types = ["invalid-type", "-remote", "remote", "", "my--vscode-remote", "vscode_remote"]
for invalid_type in invalid_types:
with self.assertRaises(ValueError):
self.hp_space.create_space_access(connection_type=invalid_type)

@patch('sagemaker.hyperpod.space.hyperpod_space.client.CustomObjectsApi')
@patch.object(HPSpace, 'verify_kube_config')
@patch('sagemaker.hyperpod.space.hyperpod_space.handle_exception')
Expand Down
Loading