From b46d6f9bea5113c847904845dbd5fbe0165167a9 Mon Sep 17 00:00:00 2001 From: Yunnglin Date: Mon, 3 Aug 2026 17:25:46 +0800 Subject: [PATCH] feat: support overriding docker image entrypoint --- ms_enclave/sandbox/boxes/docker_sandbox.py | 4 +++ ms_enclave/sandbox/model/config.py | 4 +++ tests/test_sandbox.py | 29 +++++++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ms_enclave/sandbox/boxes/docker_sandbox.py b/ms_enclave/sandbox/boxes/docker_sandbox.py index ad6ea38..b1ce44b 100644 --- a/ms_enclave/sandbox/boxes/docker_sandbox.py +++ b/ms_enclave/sandbox/boxes/docker_sandbox.py @@ -536,6 +536,10 @@ async def _create_container(self) -> None: 'stdin_open': True, } + # Check against None so an empty list can explicitly clear the image entrypoint. + if self.config.entrypoint is not None: + container_config['entrypoint'] = self.config.entrypoint + # Add command if specified if self.config.command: container_config['command'] = self.config.command diff --git a/ms_enclave/sandbox/model/config.py b/ms_enclave/sandbox/model/config.py index fe7d1a0..07f0b31 100644 --- a/ms_enclave/sandbox/model/config.py +++ b/ms_enclave/sandbox/model/config.py @@ -58,6 +58,10 @@ class DockerSandboxConfig(SandboxConfig): """Docker-specific sandbox configuration.""" image: str = Field('python:3.11-slim', description='Docker image name') + entrypoint: Optional[Union[str, List[str]]] = Field( + None, + description='Container entrypoint override. Use an empty list to clear the image entrypoint.', + ) command: Optional[Union[str, List[str]]] = Field(None, description='Container command') volumes: Dict[str, Dict[str, str]] = Field( default_factory=dict, diff --git a/tests/test_sandbox.py b/tests/test_sandbox.py index 5031a9d..3e4a824 100644 --- a/tests/test_sandbox.py +++ b/tests/test_sandbox.py @@ -4,7 +4,7 @@ import unittest from unittest.mock import AsyncMock, MagicMock, patch -from ms_enclave.sandbox.boxes import SandboxFactory +from ms_enclave.sandbox.boxes import DockerSandbox, SandboxFactory from ms_enclave.sandbox.model import DockerSandboxConfig, SandboxStatus, SandboxType from ms_enclave.sandbox.tools import ToolFactory @@ -90,6 +90,33 @@ async def test_sandbox_available_tools(self): self.assertEqual(info.type, SandboxType.DOCKER) self.assertIsNotNone(info.status) + async def test_docker_entrypoint_override(self): + """Docker config passes entrypoint overrides, including an explicit empty list.""" + for entrypoint in ('/bin/sh', ['/bin/sh', '-c'], []): + config = DockerSandboxConfig(image='example:latest', entrypoint=entrypoint) + restored = DockerSandboxConfig.model_validate_json(config.model_dump_json()) + self.assertEqual(restored.entrypoint, entrypoint) + + sandbox = DockerSandbox(config) + sandbox.client = MagicMock() + sandbox._run_blocking = AsyncMock(return_value=MagicMock()) + try: + await sandbox._create_container() + self.assertEqual(sandbox._run_blocking.await_args.kwargs['entrypoint'], entrypoint) + finally: + sandbox._executor.shutdown(wait=False, cancel_futures=True) + + async def test_docker_entrypoint_inherits_image_by_default(self): + """An unset entrypoint is omitted so Docker inherits the image configuration.""" + sandbox = DockerSandbox(DockerSandboxConfig(image='example:latest')) + sandbox.client = MagicMock() + sandbox._run_blocking = AsyncMock(return_value=MagicMock()) + try: + await sandbox._create_container() + self.assertNotIn('entrypoint', sandbox._run_blocking.await_args.kwargs) + finally: + sandbox._executor.shutdown(wait=False, cancel_futures=True) + if __name__ == '__main__':