From acc26364cc98e130db867c6606fcc29476d9ec90 Mon Sep 17 00:00:00 2001 From: Filipe Utzig Date: Thu, 12 Sep 2024 16:42:29 -0300 Subject: [PATCH] Remove and return container when start fails When the container fail to start and the user has set the `remove` flag we should remove the containter, also if the flag is not set, we lose track of the container, leaving it dangling on the system. By adding this execption return whenever the container fails to start we provide means to the user to remove the container if needed. Signed-off-by: Filipe Utzig --- docker/errors.py | 13 +++++++++++++ docker/models/containers.py | 16 +++++++++++++++- tests/integration/models_containers_test.py | 6 +++--- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/docker/errors.py b/docker/errors.py index d03e10f6..8b2762ab 100644 --- a/docker/errors.py +++ b/docker/errors.py @@ -150,6 +150,19 @@ class ContainerError(DockerException): ) +class ContainerStartError(DockerException): + """ + Represents a container that has failed to start. + """ + def __init__(self, container, reason): + self.container = container + self.msg = reason + + super().__init__( + f"Container '{container.short_id}' failed to start: {reason}" + ) + + class StreamParseError(RuntimeError): def __init__(self, reason): self.msg = reason diff --git a/docker/models/containers.py b/docker/models/containers.py index 4795523a..99d614b7 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -5,7 +5,9 @@ from collections import namedtuple from ..api import APIClient from ..constants import DEFAULT_DATA_CHUNK_SIZE from ..errors import ( + APIError, ContainerError, + ContainerStartError, DockerException, ImageNotFound, NotFound, @@ -842,6 +844,8 @@ class ContainerCollection(Collection): :py:class:`docker.errors.ContainerError` If the container exits with a non-zero exit code and ``detach`` is ``False``. + :py:class:`docker.errors.ContainerStartError` + If the container fails to start. :py:class:`docker.errors.ImageNotFound` If the specified image does not exist. :py:class:`docker.errors.APIError` @@ -880,7 +884,17 @@ class ContainerCollection(Collection): container = self.create(image=image, command=command, detach=detach, **kwargs) - container.start() + try: + container.start() + except APIError as e: + if remove: + container.remove() + + if e.explanation: + error = e.explanation + else: + error = e + raise ContainerStartError(container, error) from e if detach: return container diff --git a/tests/integration/models_containers_test.py b/tests/integration/models_containers_test.py index 476263ae..478c1bd3 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -158,13 +158,13 @@ class ContainerCollectionTest(BaseIntegrationTest): ), } - with pytest.raises(docker.errors.APIError): - container = client.containers.run( + with pytest.raises(docker.errors.ContainerStartError) as err: + client.containers.run( 'alpine', 'echo hello world', network=net_name, networking_config=networking_config, detach=True ) - self.tmp_containers.append(container.id) + self.tmp_containers.append(err.container.id) def test_run_with_networking_config_only_undeclared_network(self): net_name = random_name()