diff --git a/docker/models/containers.py b/docker/models/containers.py index 789fa93f..b33a718f 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -844,7 +844,7 @@ class ContainerCollection(Collection): return self.prepare_model(resp) def list(self, all=False, before=None, filters=None, limit=-1, since=None, - sparse=False): + sparse=False, ignore_removed=False): """ List containers. Similar to the ``docker ps`` command. @@ -882,6 +882,10 @@ class ContainerCollection(Collection): information, but guaranteed not to block. Use :py:meth:`Container.reload` on resulting objects to retrieve all attributes. Default: ``False`` + ignore_removed (bool): Ignore failures due to missing containers + when attempting to inspect containers from the original list. + Set to ``True`` if race conditions are likely. Has no effect + if ``sparse=True``. Default: ``False`` Returns: (list of :py:class:`Container`) @@ -902,7 +906,8 @@ class ContainerCollection(Collection): containers.append(self.get(r['Id'])) # a container may have been removed while iterating except NotFound: - pass + if not ignore_removed: + raise return containers def prune(self, filters=None): diff --git a/tests/unit/fake_api_client.py b/tests/unit/fake_api_client.py index 15b60eaa..2147bfdf 100644 --- a/tests/unit/fake_api_client.py +++ b/tests/unit/fake_api_client.py @@ -20,15 +20,18 @@ class CopyReturnMagicMock(mock.MagicMock): return ret -def make_fake_api_client(): +def make_fake_api_client(overrides=None): """ Returns non-complete fake APIClient. This returns most of the default cases correctly, but most arguments that change behaviour will not work. """ + + if overrides is None: + overrides = {} api_client = docker.APIClient() - mock_client = CopyReturnMagicMock(**{ + mock_attrs = { 'build.return_value': fake_api.FAKE_IMAGE_ID, 'commit.return_value': fake_api.post_fake_commit()[1], 'containers.return_value': fake_api.get_fake_containers()[1], @@ -47,15 +50,18 @@ def make_fake_api_client(): 'networks.return_value': fake_api.get_fake_network_list()[1], 'start.return_value': None, 'wait.return_value': {'StatusCode': 0}, - }) + } + mock_attrs.update(overrides) + mock_client = CopyReturnMagicMock(**mock_attrs) + mock_client._version = docker.constants.DEFAULT_DOCKER_API_VERSION return mock_client -def make_fake_client(): +def make_fake_client(overrides=None): """ Returns a Client with a fake APIClient. """ client = docker.DockerClient() - client.api = make_fake_api_client() + client.api = make_fake_api_client(overrides) return client diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index 2b0b499e..48a52888 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -359,6 +359,18 @@ class ContainerCollectionTest(unittest.TestCase): assert isinstance(containers[0], Container) assert containers[0].id == FAKE_CONTAINER_ID + def test_list_ignore_removed(self): + def side_effect(*args, **kwargs): + raise docker.errors.NotFound('Container not found') + client = make_fake_client({ + 'inspect_container.side_effect': side_effect + }) + + with pytest.raises(docker.errors.NotFound): + client.containers.list(all=True, ignore_removed=False) + + assert client.containers.list(all=True, ignore_removed=True) == [] + class ContainerTest(unittest.TestCase): def test_name(self):