diff --git a/docs/sandbox.md b/docs/sandbox.md index d5d1a9bc..6d7d002e 100644 --- a/docs/sandbox.md +++ b/docs/sandbox.md @@ -1093,8 +1093,8 @@ Create a new sandbox instance. file paths to file contents. Values can be plain strings (default permissions 0644) or ``ConfigFile`` instances for custom permissions (e.g., {"/etc/myapp/config.yaml": "key: value", "/etc/myapp/cert.pem": ConfigFile(content="...", permissions="0600")}) -- `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) +- `region` - Region to deploy to. Defaults to MISTRAL_REGION or MISTRAL_REGION or KOYEB_REGION env var, or "na" if not set. +- `api_token` - Koyeb API token (if None, will try to get from MISTRAL_API_TOKEN or MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) - `timeout` - Timeout for sandbox creation in seconds - `idle_timeout` - Sleep timeout in seconds. Behavior depends on _experimental_enable_light_sleep: - If _experimental_enable_light_sleep is True: sets light_sleep value (deep_sleep=3900) @@ -1116,7 +1116,7 @@ Create a new sandbox instance. - `poll_interval` - Time between health checks in seconds when wait_ready is True (default: 0.5) - `entrypoint` - Override the default entrypoint of the Docker image (e.g., ["/bin/sh", "-c"]) - `command` - Override the default command of the Docker image (e.g., "python app.py") -- `host` - Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) +- `host` - Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) - `block_network` - If True, block all outbound network access from the sandbox - `outbound_allowlist` - List of IPs/CIDRs allowed as outbound destinations; all other outbound traffic is blocked. Bare IPs are normalized to @@ -1164,8 +1164,8 @@ Get a sandbox by service ID. **Arguments**: - `id` - Service ID of the sandbox -- `api_token` - Koyeb API token (if None, will try to get from KOYEB_API_TOKEN env var) -- `host` - Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) +- `api_token` - Koyeb API token (if None, will try to get from MISTRAL_API_TOKEN or MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) +- `host` - Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) **Returns**: @@ -1623,8 +1623,8 @@ Get a sandbox by service ID asynchronously. **Arguments**: - `id` - Service ID of the sandbox -- `api_token` - Koyeb API token (if None, will try to get from KOYEB_API_TOKEN env var) -- `host` - Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) +- `api_token` - Koyeb API token (if None, will try to get from MISTRAL_API_TOKEN or MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) +- `host` - Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) **Returns**: @@ -1690,8 +1690,8 @@ Create a new sandbox instance with async support. file paths to file contents. Values can be plain strings (default permissions 0644) or ``ConfigFile`` instances for custom permissions (e.g., {"/etc/myapp/config.yaml": "key: value", "/etc/myapp/cert.pem": ConfigFile(content="...", permissions="0600")}) -- `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) +- `region` - Region to deploy to. Defaults to MISTRAL_REGION or MISTRAL_REGION or KOYEB_REGION env var, or "na" if not set. +- `api_token` - Koyeb API token (if None, will try to get from MISTRAL_API_TOKEN or MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) - `timeout` - Timeout for sandbox creation in seconds - `idle_timeout` - Sleep timeout in seconds. Behavior depends on _experimental_enable_light_sleep: - If _experimental_enable_light_sleep is True: sets light_sleep value (deep_sleep uses _experimental_deep_sleep_value) @@ -1715,7 +1715,7 @@ Create a new sandbox instance with async support. - `poll_interval` - Time between health checks in seconds when wait_ready is True (default: 0.5) - `entrypoint` - Override the default entrypoint of the Docker image (e.g., ["/bin/sh", "-c"]) - `command` - Override the default command of the Docker image (e.g., "python app.py") -- `host` - Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) +- `host` - Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) - `block_network` - If True, block all outbound network access from the sandbox - `outbound_allowlist` - List of IPs/CIDRs allowed as outbound destinations; all other outbound traffic is blocked. Bare IPs are normalized to @@ -2001,8 +2001,8 @@ Caches clients by (token, host) to reuse the underlying HTTP connection pool. **Arguments**: -- `api_token` - Koyeb API token. If not provided, will try to get from KOYEB_API_TOKEN env var -- `host` - Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) +- `api_token` - Koyeb API token. If not provided, will try to get from MISTRAL_API_TOKEN or MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var +- `host` - Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) **Returns**: @@ -2040,8 +2040,8 @@ Caches clients by (token, host) to reuse the underlying HTTP connection pool. **Arguments**: -- `api_token` - Koyeb API token. If not provided, will try to get from KOYEB_API_TOKEN env var -- `host` - Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var +- `api_token` - Koyeb API token. If not provided, will try to get from MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var +- `host` - Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or KOYEB_API_HOST env var **Returns**: @@ -2220,7 +2220,7 @@ Create deployment definition for a sandbox service. - `exposed_port_protocol` - Protocol to expose ports with ("http" or "http2"). If None, defaults to "http". If provided, must be one of "http" or "http2". -- `region` - Region to deploy to. Defaults to KOYEB_REGION env var, or "na" if not set. +- `region` - Region to deploy to. Defaults to MISTRAL_REGION or MISTRAL_REGION or KOYEB_REGION env var, or "na" if not set. - `routes` - List of routes for public access - `idle_timeout` - Number of seconds to wait before sleeping the instance if it receives no traffic - `enable_tcp_proxy` - If True, enables TCP proxy for direct TCP access to port 3031 diff --git a/examples/01_create_sandbox.py b/examples/01_create_sandbox.py index 7165ffc8..c457c131 100644 --- a/examples/01_create_sandbox.py +++ b/examples/01_create_sandbox.py @@ -10,9 +10,9 @@ def main(): - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = os.getenv("MISTRAL_API_TOKEN") or os.getenv("KOYEB_API_TOKEN") if not api_token: - print("Error: KOYEB_API_TOKEN not set") + print("Error: MISTRAL_API_TOKEN or KOYEB_API_TOKEN not set") return 1 sandbox = None diff --git a/examples/01_create_sandbox_async.py b/examples/01_create_sandbox_async.py index 06138dbc..e4863aa6 100644 --- a/examples/01_create_sandbox_async.py +++ b/examples/01_create_sandbox_async.py @@ -12,9 +12,9 @@ async def main(): - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = os.getenv("MISTRAL_API_TOKEN") or os.getenv("KOYEB_API_TOKEN") if not api_token: - print("Error: KOYEB_API_TOKEN not set") + print("Error: MISTRAL_API_TOKEN or KOYEB_API_TOKEN not set") return 1 sandbox = None diff --git a/examples/README.md b/examples/README.md index 4641a5e5..b10a0a54 100644 --- a/examples/README.md +++ b/examples/README.md @@ -5,7 +5,9 @@ A collection of examples demonstrating the Koyeb Sandbox SDK capabilities. ## Quick Start ```bash -# Set your API token +# Set your API token (MISTRAL_API_TOKEN takes precedence over KOYEB_API_TOKEN) +export MISTRAL_API_TOKEN=your_api_token_here +# or export KOYEB_API_TOKEN=your_api_token_here # Run individual examples diff --git a/koyeb/sandbox/sandbox.py b/koyeb/sandbox/sandbox.py index b858e260..c7d714f3 100644 --- a/koyeb/sandbox/sandbox.py +++ b/koyeb/sandbox/sandbox.py @@ -38,6 +38,7 @@ create_koyeb_sandbox_routes, create_sandbox_client, get_api_clients, + get_api_token, logger, validate_port, ) @@ -165,8 +166,8 @@ def create( file paths to file contents. Values can be plain strings (default permissions 0644) or ``ConfigFile`` instances for custom permissions (e.g., {"/etc/myapp/config.yaml": "key: value", "/etc/myapp/cert.pem": ConfigFile(content="...", permissions="0600")}) - 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) + region: Region to deploy to. Defaults to MISTRAL_REGION or KOYEB_REGION env var, or "na" if not set. + api_token: Koyeb API token (if None, will try to get from MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) timeout: Timeout for sandbox creation in seconds idle_timeout: Sleep timeout in seconds. Behavior depends on _experimental_enable_light_sleep: - If _experimental_enable_light_sleep is True: sets light_sleep value (deep_sleep=3900) @@ -188,7 +189,7 @@ def create( poll_interval: Time between health checks in seconds when wait_ready is True (default: 0.5) entrypoint: Override the default entrypoint of the Docker image (e.g., ["/bin/sh", "-c"]) command: Override the default command of the Docker image (e.g., "python app.py") - host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) + host: Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) block_network: If True, block all outbound network access from the sandbox outbound_allowlist: List of IPs/CIDRs allowed as outbound destinations; all other outbound traffic is blocked. Bare IPs are normalized to @@ -234,10 +235,10 @@ def create( ... ) """ if api_token is None: - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = get_api_token() if not api_token: raise ValueError( - "API token is required. Set KOYEB_API_TOKEN environment variable or pass api_token parameter" + "API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable or pass api_token parameter" ) # Handle snapshot parameter (can be Snapshot object or snapshot name/ID string) @@ -502,8 +503,8 @@ def get_from_id( Args: id: Service ID of the sandbox - api_token: Koyeb API token (if None, will try to get from KOYEB_API_TOKEN env var) - host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) + api_token: Koyeb API token (if None, will try to get from MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) + host: Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) Returns: Sandbox: The Sandbox instance @@ -513,10 +514,10 @@ def get_from_id( SandboxError: If sandbox is not found or retrieval fails """ if api_token is None: - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = get_api_token() if not api_token: raise ValueError( - "API token is required. Set KOYEB_API_TOKEN environment variable or pass api_token parameter" + "API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable or pass api_token parameter" ) if not id: @@ -1538,8 +1539,8 @@ async def get_from_id( Args: id: Service ID of the sandbox - api_token: Koyeb API token (if None, will try to get from KOYEB_API_TOKEN env var) - host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) + api_token: Koyeb API token (if None, will try to get from MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) + host: Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) Returns: AsyncSandbox: The AsyncSandbox instance @@ -1549,10 +1550,10 @@ async def get_from_id( SandboxError: If sandbox is not found or retrieval fails """ if api_token is None: - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = get_api_token() if not api_token: raise ValueError( - "API token is required. Set KOYEB_API_TOKEN environment variable or pass api_token parameter" + "API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable or pass api_token parameter" ) if not id: @@ -1675,8 +1676,8 @@ async def create( file paths to file contents. Values can be plain strings (default permissions 0644) or ``ConfigFile`` instances for custom permissions (e.g., {"/etc/myapp/config.yaml": "key: value", "/etc/myapp/cert.pem": ConfigFile(content="...", permissions="0600")}) - 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) + region: Region to deploy to. Defaults to MISTRAL_REGION or KOYEB_REGION env var, or "na" if not set. + api_token: Koyeb API token (if None, will try to get from MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) timeout: Timeout for sandbox creation in seconds idle_timeout: Sleep timeout in seconds. Behavior depends on _experimental_enable_light_sleep: - If _experimental_enable_light_sleep is True: sets light_sleep value (deep_sleep uses _experimental_deep_sleep_value) @@ -1700,7 +1701,7 @@ async def create( poll_interval: Time between health checks in seconds when wait_ready is True (default: 0.5) entrypoint: Override the default entrypoint of the Docker image (e.g., ["/bin/sh", "-c"]) command: Override the default command of the Docker image (e.g., "python app.py") - host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) + host: Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) block_network: If True, block all outbound network access from the sandbox outbound_allowlist: List of IPs/CIDRs allowed as outbound destinations; all other outbound traffic is blocked. Bare IPs are normalized to @@ -1720,10 +1721,10 @@ async def create( or an allowlist entry is not a valid IP address or CIDR """ if api_token is None: - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = get_api_token() if not api_token: raise ValueError( - "API token is required. Set KOYEB_API_TOKEN environment variable or pass api_token parameter" + "API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable or pass api_token parameter" ) # Handle snapshot parameter (can be Snapshot object or snapshot name/ID string) diff --git a/koyeb/sandbox/snapshot.py b/koyeb/sandbox/snapshot.py index c2395936..93920302 100644 --- a/koyeb/sandbox/snapshot.py +++ b/koyeb/sandbox/snapshot.py @@ -13,7 +13,7 @@ from typing import Any, ClassVar, Dict, List, Optional, Union from .sandbox import Sandbox -from .utils import SandboxError, get_api_clients +from .utils import SandboxError, get_api_clients, get_api_token class SnapshotType(Enum): @@ -77,7 +77,7 @@ def get( Args: snapshot_id: The ID of the snapshot to retrieve - api_token: Koyeb API token (falls back to KOYEB_API_TOKEN env var) + api_token: Koyeb API token (falls back to MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) host: Koyeb API host Returns: @@ -86,12 +86,10 @@ def get( Raises: SandboxError: If snapshot cannot be retrieved """ - import os - if not api_token: - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = get_api_token() if not api_token: - raise SandboxError("API token is required. Set KOYEB_API_TOKEN environment variable.") + raise SandboxError("API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable.") try: clients = get_api_clients(api_token, host) @@ -128,18 +126,16 @@ def list( status: Filter by snapshot status limit: Maximum number of snapshots to return offset: Offset for pagination - api_token: Koyeb API token + api_token: Koyeb API token (falls back to MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var) host: Koyeb API host Returns: List of Snapshot objects """ - import os - if not api_token: - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = get_api_token() if not api_token: - raise SandboxError("API token is required. Set KOYEB_API_TOKEN environment variable.") + raise SandboxError("API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable.") try: from koyeb.api.models.instance_snapshot_type import InstanceSnapshotType @@ -381,12 +377,10 @@ def __init__( host: Koyeb API host delete_builder: Whether to delete the builder sandbox after creating the snapshot (default: True) """ - import os - if not api_token: - api_token = os.getenv("KOYEB_API_TOKEN") + api_token = get_api_token() if not api_token: - raise SandboxError("API token is required. Set KOYEB_API_TOKEN environment variable.") + raise SandboxError("API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable.") self._name = name self._image = image diff --git a/koyeb/sandbox/utils.py b/koyeb/sandbox/utils.py index 525d126f..cd9db45e 100644 --- a/koyeb/sandbox/utils.py +++ b/koyeb/sandbox/utils.py @@ -62,6 +62,61 @@ "DIR_NOT_EMPTY": ["not empty", "Directory not empty"], } + +# Environment variable helpers +# Support both KOYEB_ and MISTRAL_ environment variables +# MISTRAL_ variables take precedence over KOYEB_ variables + +def get_env_var_mistral_fallback(koeb_var: str, mistral_var: str, default: Optional[str] = None) -> Optional[str]: + """ + Get environment variable with MISTRAL_ fallback to KOYEB_. + + Args: + koeb_var: The KOYEB_ environment variable name (e.g., "KOYEB_API_TOKEN") + mistral_var: The MISTRAL_ environment variable name (e.g., "MISTRAL_API_TOKEN") + default: Default value if neither variable is set + + Returns: + The value of MISTRAL_ var if set, otherwise KOYEB_ var, otherwise default + """ + return os.getenv(mistral_var) or os.getenv(koeb_var) or default + + +def get_api_token() -> Optional[str]: + """ + Get API token from environment, checking MISTRAL_API_TOKEN first, then KOYEB_API_TOKEN. + + Returns: + API token string or None if not set + """ + return get_env_var_mistral_fallback("KOYEB_API_TOKEN", "MISTRAL_API_TOKEN") + + +def get_api_host(default: Optional[str] = None) -> Optional[str]: + """ + Get API host from environment, checking MISTRAL_API_HOST first, then KOYEB_API_HOST. + + Args: + default: Default value if neither variable is set + + Returns: + API host URL or default + """ + return get_env_var_mistral_fallback("KOYEB_API_HOST", "MISTRAL_API_HOST", default) + + +def get_region(default: Optional[str] = None) -> Optional[str]: + """ + Get region from environment, checking MISTRAL_REGION first, then KOYEB_REGION. + + Args: + default: Default value if neither variable is set + + Returns: + Region string or default + """ + return get_env_var_mistral_fallback("KOYEB_REGION", "MISTRAL_REGION", default) + # Valid protocols for DeploymentPort (from OpenAPI spec: http, http2, tcp) # For sandboxes, we only support http and http2 VALID_DEPLOYMENT_PORT_PROTOCOLS = ("http", "http2") @@ -123,8 +178,8 @@ def get_api_clients( Caches clients by (token, host) to reuse the underlying HTTP connection pool. Args: - api_token: Koyeb API token. If not provided, will try to get from KOYEB_API_TOKEN env var - host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var (defaults to https://app.koyeb.com) + api_token: Koyeb API token. If not provided, will try to get from MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var + host: Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or KOYEB_API_HOST env var (defaults to https://app.koyeb.com) Returns: ApiClients with apps, services, instances, catalog_instances, deployments, and secrets attributes @@ -132,13 +187,13 @@ def get_api_clients( Raises: ValueError: If API token is not provided """ - token = api_token or os.getenv("KOYEB_API_TOKEN") + token = api_token or get_api_token() if not token: raise ValueError( - "API token is required. Set KOYEB_API_TOKEN environment variable or pass api_token parameter" + "API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable or pass api_token parameter" ) - api_host = os.getenv("KOYEB_API_HOST", host) + api_host = get_api_host(host) if not api_host: api_host = "https://app.koyeb.com" cache_key = (token, api_host) @@ -204,8 +259,8 @@ def get_async_api_clients( Caches clients by (token, host) to reuse the underlying HTTP connection pool. Args: - api_token: Koyeb API token. If not provided, will try to get from KOYEB_API_TOKEN env var - host: Koyeb API host URL. If not provided, will try to get from KOYEB_API_HOST env var + api_token: Koyeb API token. If not provided, will try to get from MISTRAL_API_TOKEN or KOYEB_API_TOKEN env var + host: Koyeb API host URL. If not provided, will try to get from MISTRAL_API_HOST or KOYEB_API_HOST env var Returns: AsyncApiClients with async API client instances @@ -213,13 +268,13 @@ def get_async_api_clients( Raises: ValueError: If API token is not provided """ - token = api_token or os.getenv("KOYEB_API_TOKEN") + token = api_token or get_api_token() if not token: raise ValueError( - "API token is required. Set KOYEB_API_TOKEN environment variable or pass api_token parameter" + "API token is required. Set MISTRAL_API_TOKEN or KOYEB_API_TOKEN environment variable or pass api_token parameter" ) - api_host = os.getenv("KOYEB_API_HOST", host) + api_host = get_api_host(host) if not api_host: api_host = "https://app.koyeb.com" cache_key = (token, api_host) @@ -432,7 +487,7 @@ def create_deployment_definition( exposed_port_protocol: Protocol to expose ports with ("http" or "http2"). If None, defaults to "http". If provided, must be one of "http" or "http2". - region: Region to deploy to. Defaults to KOYEB_REGION env var, or "na" if not set. + region: Region to deploy to. Defaults to MISTRAL_REGION or KOYEB_REGION env var, or "na" if not set. routes: List of routes for public access idle_timeout: Number of seconds to wait before sleeping the instance if it receives no traffic enable_tcp_proxy: If True, enables TCP proxy for direct TCP access to port 3031 @@ -447,7 +502,7 @@ def create_deployment_definition( DeploymentDefinition object """ if region is None: - region = os.getenv("KOYEB_REGION", "na") + region = get_region("na") # Convert single region string to list for API regions_list = [region]