diff --git a/ms_enclave/sandbox/boxes/docker_sandbox.py b/ms_enclave/sandbox/boxes/docker_sandbox.py index 3d9e6ce..a271475 100644 --- a/ms_enclave/sandbox/boxes/docker_sandbox.py +++ b/ms_enclave/sandbox/boxes/docker_sandbox.py @@ -475,6 +475,11 @@ async def _create_container(self) -> None: elif self.config.network: container_config['network'] = self.config.network + # Extra /etc/hosts entries (e.g. host.docker.internal -> host-gateway + # on Linux so containerised agents can reach services on the host). + if self.config.extra_hosts: + container_config['extra_hosts'] = dict(self.config.extra_hosts) + # Privileged mode container_config['privileged'] = self.config.privileged diff --git a/ms_enclave/sandbox/manager/local_manager.py b/ms_enclave/sandbox/manager/local_manager.py index 5b60a4f..b7057da 100644 --- a/ms_enclave/sandbox/manager/local_manager.py +++ b/ms_enclave/sandbox/manager/local_manager.py @@ -55,13 +55,28 @@ async def stop(self) -> None: self._running = False - # Cancel cleanup task - if self._cleanup_task: - self._cleanup_task.cancel() + # Cancel cleanup task. The task may be bound to an asyncio loop that + # has already been closed (e.g. when the worker thread that first + # called start() has exited and torn down its per-thread loop before + # atexit-driven shutdown reaches us). In that case we can't drive the + # cancellation to completion, so suppress asyncio's "Task was + # destroyed but it is pending" noise via _log_destroy_pending. + if self._cleanup_task and not self._cleanup_task.done(): try: - await self._cleanup_task - except asyncio.CancelledError: - pass + task_loop = self._cleanup_task.get_loop() + except RuntimeError: + task_loop = None + if task_loop is None or task_loop.is_closed(): + self._cleanup_task._log_destroy_pending = False + else: + try: + self._cleanup_task.cancel() + await self._cleanup_task + except asyncio.CancelledError: + pass + except RuntimeError: + self._cleanup_task._log_destroy_pending = False + self._cleanup_task = None # Stop and cleanup all sandboxes await self.cleanup_all_sandboxes() diff --git a/ms_enclave/sandbox/model/config.py b/ms_enclave/sandbox/model/config.py index 955e7f5..fe7d1a0 100644 --- a/ms_enclave/sandbox/model/config.py +++ b/ms_enclave/sandbox/model/config.py @@ -65,6 +65,16 @@ class DockerSandboxConfig(SandboxConfig): ) ports: Dict[str, Union[int, str, Tuple[str, int]]] = Field(default_factory=dict, description='Port mappings') network: Optional[str] = Field('bridge', description='Network name') + extra_hosts: Dict[str, str] = Field( + default_factory=dict, + description=( + 'Additional ``hostname -> ip`` entries written to the container\'s /etc/hosts. ' + 'Use the special value ``host-gateway`` to resolve a hostname to the Docker host ' + '(e.g. ``{"host.docker.internal": "host-gateway"}`` so processes inside the ' + 'container can reach a service on the host on Linux, where the alias is not ' + 'provided automatically). Maps directly onto the docker-py ``extra_hosts`` kwarg.' + ), + ) platform: Optional[str] = Field( default=None, description='Docker platform (e.g. "linux/amd64"). Required when the image arch differs from the host '