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
54 changes: 45 additions & 9 deletions docs/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -992,6 +992,7 @@ def create(cls,
instance_type: str = "micro",
exposed_port_protocol: Optional[str] = None,
env: Optional[Dict[str, str]] = None,
config_files: Optional[Dict[str, str]] = None,
region: Optional[str] = None,
api_token: Optional[str] = None,
timeout: int = 300,
Expand Down Expand Up @@ -1020,6 +1021,8 @@ Create a new sandbox instance.
If None, defaults to "http".
If provided, must be one of "http" or "http2".
- `env` - Environment variables
- `config_files` - Config files to create in the sandbox, as a dictionary mapping
file paths to file contents (e.g., {"/etc/myapp/config.yaml": "key: value"})
- `region` - Region to deploy to. Defaults to KOYEB_REGION env var, or "na" if not set.
- `api_token` - Koyeb API token (if None, will try to get from KOYEB_API_TOKEN env var)
- `timeout` - Timeout for sandbox creation in seconds
Expand Down Expand Up @@ -1518,6 +1521,7 @@ async def create(cls,
instance_type: str = "micro",
exposed_port_protocol: Optional[str] = None,
env: Optional[Dict[str, str]] = None,
config_files: Optional[Dict[str, str]] = None,
region: Optional[str] = None,
api_token: Optional[str] = None,
timeout: int = 300,
Expand Down Expand Up @@ -1546,6 +1550,8 @@ Create a new sandbox instance with async support.
If None, defaults to "http".
If provided, must be one of "http" or "http2".
- `env` - Environment variables
- `config_files` - Config files to create in the sandbox, as a dictionary mapping
file paths to file contents (e.g., {"/etc/myapp/config.yaml": "key: value"})
- `region` - Region to deploy to. Defaults to KOYEB_REGION env var, or "na" if not set.
- `api_token` - Koyeb API token (if None, will try to get from KOYEB_API_TOKEN env var)
- `timeout` - Timeout for sandbox creation in seconds
Expand Down Expand Up @@ -1802,16 +1808,24 @@ seconds

seconds for HTTP requests

<a id="koyeb/sandbox.utils.get_api_client"></a>
<a id="koyeb/sandbox.utils.ApiClients"></a>

#### get\_api\_client
## ApiClients Objects

```python
def get_api_client(
api_token: Optional[str] = None,
host: Optional[str] = None
) -> tuple[AppsApi, ServicesApi, InstancesApi, CatalogInstancesApi,
DeploymentsApi]
@dataclass(frozen=True)
class ApiClients()
```

Bundle of Koyeb API clients sharing a single underlying ApiClient.

<a id="koyeb/sandbox.utils.get_api_clients"></a>

#### get\_api\_clients

```python
def get_api_clients(api_token: Optional[str] = None,
host: Optional[str] = None) -> ApiClients
```

Get configured API clients for Koyeb operations.
Expand All @@ -1824,7 +1838,7 @@ Get configured API clients for Koyeb operations.

**Returns**:

Tuple of (AppsApi, ServicesApi, InstancesApi, CatalogInstancesApi) instances
ApiClients with apps, services, instances, catalog_instances, deployments, and secrets attributes


**Raises**:
Expand All @@ -1850,6 +1864,26 @@ Build environment variables list from dictionary.

List of DeploymentEnv objects

<a id="koyeb/sandbox.utils.build_config_files"></a>

#### build\_config\_files

```python
def build_config_files(
config_files: Optional[Dict[str, str]]) -> List[ConfigFile]
```

Build config files list from dictionary.

**Arguments**:

- `config_files` - Dictionary mapping file paths to file contents


**Returns**:

List of ConfigFile objects

<a id="koyeb/sandbox.utils.create_docker_source"></a>

#### create\_docker\_source
Expand Down Expand Up @@ -1952,7 +1986,9 @@ def create_deployment_definition(
enable_tcp_proxy: bool = False,
_experimental_enable_light_sleep: bool = False,
_experimental_deep_sleep_value: int = 3900,
enable_mesh: bool = None) -> DeploymentDefinition
enable_mesh: bool = None,
config_files: Optional[List[ConfigFile]] = None
) -> DeploymentDefinition
```

Create deployment definition for a sandbox service.
Expand Down
1 change: 0 additions & 1 deletion examples/02_create_sandbox_with_timing.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ def main(run_long_tests=False):
name=f"example-sandbox-timed-{suffix}",
wait_ready=True,
api_token=api_token,
# poll_interval=0.1,
)
create_duration = time.time() - create_start
tracker.record("Sandbox creation", create_duration, "setup")
Expand Down
58 changes: 44 additions & 14 deletions examples/05_environment_variables.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
#!/usr/bin/env python3
"""Environment variables in commands"""
"""Environment variables with secrets and interpolation"""

import os
import sys


import random
import string

from koyeb import Sandbox
from koyeb.api.models.create_secret import CreateSecret
from koyeb.sandbox.utils import get_api_clients


def main():
Expand All @@ -16,36 +17,65 @@ def main():
print("Error: KOYEB_API_TOKEN not set")
return 1

sandbox = None
suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
secret_name = f"test-secret-{suffix}"
secret_value = "secret-value-123"

secrets_api = get_api_clients(api_token).secrets

sandbox = None
secret_id = None
try:
# Create a secret
secret_response = secrets_api.create_secret(
secret=CreateSecret(name=secret_name, value=secret_value)
)
secret_id = secret_response.secret.id
print(f"Created secret: {secret_name}")

# Create sandbox with env vars referencing the secret and using interpolation
sandbox = Sandbox.create(
image="koyeb/sandbox",
name=f"env-vars-{suffix}",
wait_ready=True,
api_token=api_token,
env={
"SECRET_VAL": "{{ secret." + secret_name + " }}",
"X": "2",
"Y": "{{ X }}",
},
)

# Check secret reference
result = sandbox.exec('echo "$SECRET_VAL"')
assert result.stdout.strip() == secret_value, (
f"Expected '{secret_value}', got '{result.stdout.strip()}'"
)
print(f"SECRET_VAL={result.stdout.strip()}")

# Set environment variables
env_vars = {"MY_VAR": "Hello", "DEBUG": "true"}
result = sandbox.exec("env | grep MY_VAR", env=env_vars)
print(result.stdout.strip())
# Check direct value
result = sandbox.exec('echo "$X"')
assert result.stdout.strip() == "2", (
f"Expected '2', got '{result.stdout.strip()}'"
)
print(f"X={result.stdout.strip()}")

# Use in Python command
result = sandbox.exec(
'python3 -c "import os; print(os.getenv(\'MY_VAR\'))"',
env={"MY_VAR": "Hello from Python!"},
# Check interpolation
result = sandbox.exec('echo "$Y"')
assert result.stdout.strip() == "2", (
f"Expected '2', got '{result.stdout.strip()}'"
)
print(result.stdout.strip())
print(f"Y={result.stdout.strip()}")

return 0
except Exception as e:
print(f"Error: {e}")
return 1

finally:
if sandbox:
sandbox.delete()
if secret_id:
secrets_api.delete_secret(id=secret_id)


if __name__ == "__main__":
Expand Down
59 changes: 44 additions & 15 deletions examples/05_environment_variables_async.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
#!/usr/bin/env python3
"""Environment variables in commands (async variant)"""
"""Environment variables with secrets and interpolation (async variant)"""

import asyncio
import sys
import os
import sys


import random
import string

from koyeb import AsyncSandbox
from koyeb.api.models.create_secret import CreateSecret
from koyeb.sandbox.utils import get_api_clients


async def main():
Expand All @@ -18,36 +18,65 @@ async def main():
print("Error: KOYEB_API_TOKEN not set")
return 1

sandbox = None
suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=8))
secret_name = f"test-secret-{suffix}"
secret_value = "secret-value-123"

secrets_api = get_api_clients(api_token).secrets

sandbox = None
secret_id = None
try:
# Create a secret
secret_response = secrets_api.create_secret(
secret=CreateSecret(name=secret_name, value=secret_value)
)
secret_id = secret_response.secret.id
print(f"Created secret: {secret_name}")

# Create sandbox with env vars referencing the secret and using interpolation
sandbox = await AsyncSandbox.create(
image="koyeb/sandbox",
name=f"env-vars-{suffix}",
wait_ready=True,
api_token=api_token,
env={
"SECRET_VAL": "{{ secret." + secret_name + " }}",
"X": "2",
"Y": "{{ X }}",
},
)

# Set environment variables
env_vars = {"MY_VAR": "Hello", "DEBUG": "true"}
result = await sandbox.exec("env | grep MY_VAR", env=env_vars)
print(result.stdout.strip())
# Check secret reference
result = await sandbox.exec('echo "$SECRET_VAL"')
assert result.stdout.strip() == secret_value, (
f"Expected '{secret_value}', got '{result.stdout.strip()}'"
)
print(f"SECRET_VAL={result.stdout.strip()}")

# Use in Python command
result = await sandbox.exec(
'python3 -c "import os; print(os.getenv(\'MY_VAR\'))"',
env={"MY_VAR": "Hello from Python!"},
# Check direct value
result = await sandbox.exec('echo "$X"')
assert result.stdout.strip() == "2", (
f"Expected '2', got '{result.stdout.strip()}'"
)
print(result.stdout.strip())
print(f"X={result.stdout.strip()}")

# Check interpolation
result = await sandbox.exec('echo "$Y"')
assert result.stdout.strip() == "2", (
f"Expected '2', got '{result.stdout.strip()}'"
)
print(f"Y={result.stdout.strip()}")

return 0
except Exception as e:
print(f"Error: {e}")
return 1

finally:
if sandbox:
await sandbox.delete()
if secret_id:
secrets_api.delete_secret(id=secret_id)


if __name__ == "__main__":
Expand Down
4 changes: 2 additions & 2 deletions examples/16_create_sandbox_with_auto_delete_simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
import random
import string
from koyeb import Sandbox
from koyeb.sandbox.utils import get_api_client
from koyeb.sandbox.utils import get_api_clients


def service_exists(api_token: str, service_id: str) -> bool:
"""Check if a service still exists"""
try:
_, services_api, _, _, _ = get_api_client(api_token)
services_api = get_api_clients(api_token).services
services_api.get_service(service_id)
return True
except Exception:
Expand Down
6 changes: 3 additions & 3 deletions examples/17_create_sandbox_with_auto_delete.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from datetime import datetime

from koyeb import Sandbox
from koyeb.sandbox.utils import get_api_client
from koyeb.sandbox.utils import get_api_clients


class TimingTracker:
Expand Down Expand Up @@ -66,15 +66,15 @@ def print_recap(self):

def get_service_lifecycle(api_token: str, service_id: str):
"""Fetch and return the service lifecycle settings from the API"""
_, services_api, _, _, _ = get_api_client(api_token)
services_api = get_api_clients(api_token).services
service_response = services_api.get_service(service_id)
return service_response.service.life_cycle


def service_exists(api_token: str, service_id: str) -> bool:
"""Check if a service still exists"""
try:
_, services_api, _, _, _ = get_api_client(api_token)
services_api = get_api_clients(api_token).services
services_api.get_service(service_id)
return True
except Exception:
Expand Down
4 changes: 2 additions & 2 deletions examples/18_create_sandbox_with_existing_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from koyeb import Sandbox
from koyeb.api.models.create_app import CreateApp
from koyeb.sandbox.utils import get_api_client
from koyeb.sandbox.utils import get_api_clients


def main():
Expand All @@ -29,7 +29,7 @@ def main():
print("=" * 60)
print()

apps_api, _, _, _, _ = get_api_client(api_token)
apps_api = get_api_clients(api_token).apps

app_name = f"my-sandbox-app-{int(time.time())}"
print(f" Creating app: {app_name}")
Expand Down
Loading
Loading