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
2 changes: 2 additions & 0 deletions src/blaxel/core/sandbox/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
SandboxConfiguration,
SandboxCreateConfiguration,
SandboxFilesystemFile,
SandboxUpdateNetwork,
SessionCreateOptions,
SessionWithToken,
StreamHandle,
Expand All @@ -59,6 +60,7 @@
"AsyncStreamHandle",
"SandboxFilesystemFile",
"CopyResponse",
"SandboxUpdateNetwork",
"Sandbox",
"SandboxFileSystem",
"SandboxPreviews",
Expand Down
37 changes: 37 additions & 0 deletions src/blaxel/core/sandbox/default/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
SandboxConfiguration,
SandboxCreateConfiguration,
SandboxUpdateMetadata,
SandboxUpdateNetwork,
SessionWithToken,
)
from .codegen import SandboxCodegen
Expand Down Expand Up @@ -477,6 +478,42 @@ async def update_lifecycle(

return cls(response)

@classmethod
async def update_network(
cls, sandbox_name: str, network: SandboxUpdateNetwork
) -> "SandboxInstance":
"""Update sandbox network configuration without recreating it.

Args:
sandbox_name: The name of the sandbox to update
network: The new network configuration

Returns:
A new SandboxInstance with updated network configuration
"""
sandbox_instance = await cls.get(sandbox_name)
sandbox = sandbox_instance.sandbox

updated_sandbox = Sandbox.from_dict(sandbox.to_dict())
if updated_sandbox.spec is None:
raise ValueError(f"Sandbox {sandbox_name} has invalid spec")

if network.network is not None:
if isinstance(network.network, dict):
updated_sandbox.spec.network = SandboxNetworkModel.from_dict(network.network)
else:
updated_sandbox.spec.network = network.network
else:
updated_sandbox.spec.network = UNSET

response = await update_sandbox(
sandbox_name=sandbox_name,
client=client,
body=updated_sandbox,
)

return cls(response)

@classmethod
async def create_if_not_exists(
cls, sandbox: Union[Sandbox, SandboxCreateConfiguration, Dict[str, Any]]
Expand Down
37 changes: 37 additions & 0 deletions src/blaxel/core/sandbox/sync/sandbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
SandboxConfiguration,
SandboxCreateConfiguration,
SandboxUpdateMetadata,
SandboxUpdateNetwork,
SessionWithToken,
)
from .codegen import SyncSandboxCodegen
Expand Down Expand Up @@ -392,6 +393,42 @@ def update_lifecycle(

return cls(response)

@classmethod
def update_network(
cls, sandbox_name: str, network: SandboxUpdateNetwork
) -> "SyncSandboxInstance":
"""Update sandbox network configuration without recreating it.

Args:
sandbox_name: The name of the sandbox to update
network: The new network configuration

Returns:
A new SyncSandboxInstance with updated network configuration
"""
sandbox_instance = cls.get(sandbox_name)
sandbox = sandbox_instance.sandbox

updated_sandbox = Sandbox.from_dict(sandbox.to_dict())
if updated_sandbox.spec is None:
raise ValueError(f"Sandbox {sandbox_name} has invalid spec")

if network.network is not None:
if isinstance(network.network, dict):
updated_sandbox.spec.network = SandboxNetworkModel.from_dict(network.network)
else:
updated_sandbox.spec.network = network.network
else:
updated_sandbox.spec.network = UNSET

response = update_sandbox(
sandbox_name=sandbox_name,
client=client,
body=updated_sandbox,
)

return cls(response)

@classmethod
def create_if_not_exists(
cls, sandbox: Union[Sandbox, SandboxCreateConfiguration, Dict[str, Any]]
Expand Down
10 changes: 10 additions & 0 deletions src/blaxel/core/sandbox/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,16 @@ def __init__(
self.display_name = display_name


class SandboxUpdateNetwork:
"""Configuration for updating sandbox network configuration."""

def __init__(
self,
network: Union[SandboxNetwork, Dict[str, Any]] | None = None,
):
self.network = network


class SandboxCreateConfiguration:
"""Simplified configuration for creating sandboxes with default values."""

Expand Down
111 changes: 111 additions & 0 deletions tests/integration/core/sandbox/test_network.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import pytest

from blaxel.core import SandboxInstance
from blaxel.core.sandbox.types import SandboxUpdateNetwork
from tests.helpers import (
default_image,
default_labels,
unique_name,
)

# =============================================================================
# Sandbox UpdateNetwork Tests
# =============================================================================


@pytest.mark.asyncio(loop_scope="class")
class TestSandboxUpdateNetwork:
"""Test sandbox update_network operations."""

async def test_updates_sandbox_network_with_allowed_domains(self):
"""Test updating sandbox network with allowed domains using httpbin."""
name = unique_name("update-net-allow")
await SandboxInstance.create(
{
"name": name,
"image": default_image,
"labels": default_labels,
}
)

try:
updated = await SandboxInstance.update_network(
name,
SandboxUpdateNetwork(network={"allowedDomains": ["httpbin.org", "*.httpbin.org"]}),
)
assert updated.spec.network is not None
assert set(updated.spec.network.allowed_domains) == {"httpbin.org", "*.httpbin.org"}
finally:
await SandboxInstance.delete(name)

async def test_updates_sandbox_network_with_forbidden_domains(self):
"""Test updating sandbox network with forbidden domains using httpbin."""
name = unique_name("update-net-forbid")
await SandboxInstance.create(
{
"name": name,
"image": default_image,
"labels": default_labels,
}
)

try:
updated = await SandboxInstance.update_network(
name,
SandboxUpdateNetwork(
network={"forbiddenDomains": ["httpbin.org", "*.httpbin.org"]}
),
)
assert updated.spec.network is not None
assert set(updated.spec.network.forbidden_domains) == {"httpbin.org", "*.httpbin.org"}
finally:
await SandboxInstance.delete(name)

async def test_updates_sandbox_network_with_model_object(self):
"""Test updating sandbox network using SandboxNetwork model object."""
from blaxel.core.client.models import SandboxNetwork

name = unique_name("update-net-model")
await SandboxInstance.create(
{
"name": name,
"image": default_image,
"labels": default_labels,
}
)

try:
network_config = SandboxNetwork(
allowed_domains=["httpbin.org"],
)
updated = await SandboxInstance.update_network(
name,
SandboxUpdateNetwork(network=network_config),
)
assert updated.spec.network is not None
assert updated.spec.network.allowed_domains == ["httpbin.org"]
finally:
await SandboxInstance.delete(name)

async def test_clears_sandbox_network_config(self):
"""Test clearing sandbox network configuration by passing network=None."""
name = unique_name("update-net-clear")
await SandboxInstance.create(
{
"name": name,
"image": default_image,
"network": {"allowedDomains": ["httpbin.org"]},
"labels": default_labels,
}
)

try:
updated = await SandboxInstance.update_network(
name,
SandboxUpdateNetwork(network=None),
)
from blaxel.core.client.types import UNSET

assert updated.spec.network is UNSET or updated.spec.network is None
finally:
await SandboxInstance.delete(name)
Loading