From 187ea4cd814a3de1201afe5a50097935183d7f9f Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Tue, 15 Mar 2016 17:00:24 -0700 Subject: [PATCH 1/6] Add --all option to rm command - remove one-off containers Signed-off-by: Joffrey F --- compose/cli/main.py | 9 +++++++-- compose/project.py | 16 ++++++++++------ docs/reference/rm.md | 1 + tests/acceptance/cli_test.py | 22 ++++++++++++++++++++++ 4 files changed, 40 insertions(+), 8 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index ea3054abb..5ca8d23db 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -491,8 +491,12 @@ class TopLevelCommand(object): Options: -f, --force Don't ask to confirm removal -v Remove volumes associated with containers + -a, --all Also remove one-off containers """ - all_containers = self.project.containers(service_names=options['SERVICE'], stopped=True) + all_containers = self.project.containers( + service_names=options['SERVICE'], stopped=True, + one_off=(None if options.get('--all') else False) + ) stopped_containers = [c for c in all_containers if not c.is_running] if len(stopped_containers) > 0: @@ -501,7 +505,8 @@ class TopLevelCommand(object): or yesno("Are you sure? [yN] ", default=False): self.project.remove_stopped( service_names=options['SERVICE'], - v=options.get('-v', False) + v=options.get('-v', False), + one_off=options.get('--all') ) else: print("No stopped containers") diff --git a/compose/project.py b/compose/project.py index dbfe6a121..298396dec 100644 --- a/compose/project.py +++ b/compose/project.py @@ -47,10 +47,12 @@ class Project(object): self.networks = networks or ProjectNetworks({}, False) def labels(self, one_off=False): - return [ - '{0}={1}'.format(LABEL_PROJECT, self.name), - '{0}={1}'.format(LABEL_ONE_OFF, "True" if one_off else "False"), - ] + labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)] + if one_off is not None: + labels.append( + '{0}={1}'.format(LABEL_ONE_OFF, "True" if one_off else "False") + ) + return labels @classmethod def from_config(cls, name, config_data, client): @@ -249,8 +251,10 @@ class Project(object): def kill(self, service_names=None, **options): parallel.parallel_kill(self.containers(service_names), options) - def remove_stopped(self, service_names=None, **options): - parallel.parallel_remove(self.containers(service_names, stopped=True), options) + def remove_stopped(self, service_names=None, one_off=False, **options): + parallel.parallel_remove(self.containers( + service_names, stopped=True, one_off=(None if one_off else False) + ), options) def down(self, remove_image_type, include_volumes, remove_orphans=False): self.stop() diff --git a/docs/reference/rm.md b/docs/reference/rm.md index f84792243..97698b58b 100644 --- a/docs/reference/rm.md +++ b/docs/reference/rm.md @@ -17,6 +17,7 @@ Usage: rm [options] [SERVICE...] Options: -f, --force Don't ask to confirm removal -v Remove volumes associated with containers +-a, --all Also remove one-off containers ``` Removes stopped service containers. diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index f06b32801..778c8ff4a 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1125,6 +1125,28 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['rm', '-f'], None) self.assertEqual(len(service.containers(stopped=True)), 0) + def test_rm_all(self): + service = self.project.get_service('simple') + service.create_container(one_off=False) + service.create_container(one_off=True) + kill_service(service) + self.assertEqual(len(service.containers(stopped=True)), 1) + self.assertEqual(len(service.containers(stopped=True, one_off=True)), 1) + self.dispatch(['rm', '-f'], None) + self.assertEqual(len(service.containers(stopped=True)), 0) + self.assertEqual(len(service.containers(stopped=True, one_off=True)), 1) + self.dispatch(['rm', '-f', '-a'], None) + self.assertEqual(len(service.containers(stopped=True, one_off=True)), 0) + + service.create_container(one_off=False) + service.create_container(one_off=True) + kill_service(service) + self.assertEqual(len(service.containers(stopped=True)), 1) + self.assertEqual(len(service.containers(stopped=True, one_off=True)), 1) + self.dispatch(['rm', '-f', '--all'], None) + self.assertEqual(len(service.containers(stopped=True)), 0) + self.assertEqual(len(service.containers(stopped=True, one_off=True)), 0) + def test_stop(self): self.dispatch(['up', '-d'], None) service = self.project.get_service('simple') From 5826a2147b1817c278dd8918e9cc8bbce6844b9e Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 16 Mar 2016 19:47:46 -0700 Subject: [PATCH 2/6] Use enum to represent 3 possible states of the one_off filter Signed-off-by: Joffrey F --- compose/cli/main.py | 13 ++++++---- compose/project.py | 23 +++++++++++++---- tests/acceptance/cli_test.py | 43 ++++++++++++++++--------------- tests/integration/service_test.py | 5 ++-- tests/unit/service_test.py | 3 ++- 5 files changed, 53 insertions(+), 34 deletions(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index 5ca8d23db..f481d5847 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -22,6 +22,7 @@ from ..const import DEFAULT_TIMEOUT from ..const import IS_WINDOWS_PLATFORM from ..progress_stream import StreamOutputError from ..project import NoSuchService +from ..project import OneOffFilter from ..service import BuildAction from ..service import BuildError from ..service import ConvergenceStrategy @@ -437,7 +438,7 @@ class TopLevelCommand(object): """ containers = sorted( self.project.containers(service_names=options['SERVICE'], stopped=True) + - self.project.containers(service_names=options['SERVICE'], one_off=True), + self.project.containers(service_names=options['SERVICE'], one_off=OneOffFilter.only), key=attrgetter('name')) if options['-q']: @@ -491,11 +492,13 @@ class TopLevelCommand(object): Options: -f, --force Don't ask to confirm removal -v Remove volumes associated with containers - -a, --all Also remove one-off containers + -a, --all Also remove one-off containers created by + docker-compose run """ + one_off = OneOffFilter.include if options.get('--all') else OneOffFilter.exclude + all_containers = self.project.containers( - service_names=options['SERVICE'], stopped=True, - one_off=(None if options.get('--all') else False) + service_names=options['SERVICE'], stopped=True, one_off=one_off ) stopped_containers = [c for c in all_containers if not c.is_running] @@ -506,7 +509,7 @@ class TopLevelCommand(object): self.project.remove_stopped( service_names=options['SERVICE'], v=options.get('-v', False), - one_off=options.get('--all') + one_off=one_off ) else: print("No stopped containers") diff --git a/compose/project.py b/compose/project.py index 298396dec..aef556e92 100644 --- a/compose/project.py +++ b/compose/project.py @@ -6,6 +6,7 @@ import logging import operator from functools import reduce +import enum from docker.errors import APIError from . import parallel @@ -35,6 +36,20 @@ from .volume import ProjectVolumes log = logging.getLogger(__name__) +@enum.unique +class OneOffFilter(enum.Enum): + include = 0 + exclude = 1 + only = 2 + + @classmethod + def update_labels(cls, value, labels): + if value == cls.only: + labels.append('{0}={1}'.format(LABEL_ONE_OFF, "True")) + elif value == cls.exclude or value is False: + labels.append('{0}={1}'.format(LABEL_ONE_OFF, "False")) + + class Project(object): """ A collection of services. @@ -48,10 +63,8 @@ class Project(object): def labels(self, one_off=False): labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)] - if one_off is not None: - labels.append( - '{0}={1}'.format(LABEL_ONE_OFF, "True" if one_off else "False") - ) + + OneOffFilter.update_labels(one_off, labels) return labels @classmethod @@ -253,7 +266,7 @@ class Project(object): def remove_stopped(self, service_names=None, one_off=False, **options): parallel.parallel_remove(self.containers( - service_names, stopped=True, one_off=(None if one_off else False) + service_names, stopped=True, one_off=one_off ), options) def down(self, remove_image_type, include_volumes, remove_orphans=False): diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 778c8ff4a..382fa8870 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -18,6 +18,7 @@ from docker import errors from .. import mock from compose.cli.command import get_project from compose.container import Container +from compose.project import OneOffFilter from tests.integration.testcases import DockerClientTestCase from tests.integration.testcases import get_links from tests.integration.testcases import pull_busybox @@ -105,7 +106,7 @@ class CLITestCase(DockerClientTestCase): self.project.kill() self.project.remove_stopped() - for container in self.project.containers(stopped=True, one_off=True): + for container in self.project.containers(stopped=True, one_off=OneOffFilter.only): container.remove(force=True) networks = self.client.networks() @@ -802,7 +803,7 @@ class CLITestCase(DockerClientTestCase): self.assertEqual(len(self.project.containers()), 0) # Ensure stdin/out was open - container = self.project.containers(stopped=True, one_off=True)[0] + container = self.project.containers(stopped=True, one_off=OneOffFilter.only)[0] config = container.inspect()['Config'] self.assertTrue(config['AttachStderr']) self.assertTrue(config['AttachStdout']) @@ -852,7 +853,7 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['run', 'implicit']) service = self.project.get_service('implicit') - containers = service.containers(stopped=True, one_off=True) + containers = service.containers(stopped=True, one_off=OneOffFilter.only) self.assertEqual( [c.human_readable_command for c in containers], [u'/bin/sh -c echo "success"'], @@ -860,7 +861,7 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['run', 'explicit']) service = self.project.get_service('explicit') - containers = service.containers(stopped=True, one_off=True) + containers = service.containers(stopped=True, one_off=OneOffFilter.only) self.assertEqual( [c.human_readable_command for c in containers], [u'/bin/true'], @@ -871,7 +872,7 @@ class CLITestCase(DockerClientTestCase): name = 'service' self.dispatch(['run', '--entrypoint', '/bin/echo', name, 'helloworld']) service = self.project.get_service(name) - container = service.containers(stopped=True, one_off=True)[0] + container = service.containers(stopped=True, one_off=OneOffFilter.only)[0] self.assertEqual( shlex.split(container.human_readable_command), [u'/bin/echo', u'helloworld'], @@ -883,7 +884,7 @@ class CLITestCase(DockerClientTestCase): user = 'sshd' self.dispatch(['run', '--user={user}'.format(user=user), name], returncode=1) service = self.project.get_service(name) - container = service.containers(stopped=True, one_off=True)[0] + container = service.containers(stopped=True, one_off=OneOffFilter.only)[0] self.assertEqual(user, container.get('Config.User')) def test_run_service_with_user_overridden_short_form(self): @@ -892,7 +893,7 @@ class CLITestCase(DockerClientTestCase): user = 'sshd' self.dispatch(['run', '-u', user, name], returncode=1) service = self.project.get_service(name) - container = service.containers(stopped=True, one_off=True)[0] + container = service.containers(stopped=True, one_off=OneOffFilter.only)[0] self.assertEqual(user, container.get('Config.User')) def test_run_service_with_environement_overridden(self): @@ -906,7 +907,7 @@ class CLITestCase(DockerClientTestCase): '/bin/true', ]) service = self.project.get_service(name) - container = service.containers(stopped=True, one_off=True)[0] + container = service.containers(stopped=True, one_off=OneOffFilter.only)[0] # env overriden self.assertEqual('notbar', container.environment['foo']) # keep environement from yaml @@ -920,7 +921,7 @@ class CLITestCase(DockerClientTestCase): # create one off container self.base_dir = 'tests/fixtures/ports-composefile' self.dispatch(['run', '-d', 'simple']) - container = self.project.get_service('simple').containers(one_off=True)[0] + container = self.project.get_service('simple').containers(one_off=OneOffFilter.only)[0] # get port information port_random = container.get_local_port(3000) @@ -937,7 +938,7 @@ class CLITestCase(DockerClientTestCase): # create one off container self.base_dir = 'tests/fixtures/ports-composefile' self.dispatch(['run', '-d', '--service-ports', 'simple']) - container = self.project.get_service('simple').containers(one_off=True)[0] + container = self.project.get_service('simple').containers(one_off=OneOffFilter.only)[0] # get port information port_random = container.get_local_port(3000) @@ -958,7 +959,7 @@ class CLITestCase(DockerClientTestCase): # create one off container self.base_dir = 'tests/fixtures/ports-composefile' self.dispatch(['run', '-d', '-p', '30000:3000', '--publish', '30001:3001', 'simple']) - container = self.project.get_service('simple').containers(one_off=True)[0] + container = self.project.get_service('simple').containers(one_off=OneOffFilter.only)[0] # get port information port_short = container.get_local_port(3000) @@ -980,7 +981,7 @@ class CLITestCase(DockerClientTestCase): '--publish', '127.0.0.1:30001:3001', 'simple' ]) - container = self.project.get_service('simple').containers(one_off=True)[0] + container = self.project.get_service('simple').containers(one_off=OneOffFilter.only)[0] # get port information port_short = container.get_local_port(3000) @@ -997,7 +998,7 @@ class CLITestCase(DockerClientTestCase): # create one off container self.base_dir = 'tests/fixtures/expose-composefile' self.dispatch(['run', '-d', '--service-ports', 'simple']) - container = self.project.get_service('simple').containers(one_off=True)[0] + container = self.project.get_service('simple').containers(one_off=OneOffFilter.only)[0] ports = container.ports self.assertEqual(len(ports), 9) @@ -1021,7 +1022,7 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['run', '--name', name, 'service', '/bin/true']) service = self.project.get_service('service') - container, = service.containers(stopped=True, one_off=True) + container, = service.containers(stopped=True, one_off=OneOffFilter.only) self.assertEqual(container.name, name) def test_run_service_with_workdir_overridden(self): @@ -1051,7 +1052,7 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['run', 'app', 'nslookup', 'db']) containers = self.project.get_service('app').containers( - stopped=True, one_off=True) + stopped=True, one_off=OneOffFilter.only) assert len(containers) == 2 for container in containers: @@ -1071,7 +1072,7 @@ class CLITestCase(DockerClientTestCase): self.dispatch(['up', '-d']) self.dispatch(['run', '-d', 'app', 'top']) - container = self.project.get_service('app').containers(one_off=True)[0] + container = self.project.get_service('app').containers(one_off=OneOffFilter.only)[0] networks = container.get('NetworkSettings.Networks') assert sorted(list(networks)) == [ @@ -1131,21 +1132,21 @@ class CLITestCase(DockerClientTestCase): service.create_container(one_off=True) kill_service(service) self.assertEqual(len(service.containers(stopped=True)), 1) - self.assertEqual(len(service.containers(stopped=True, one_off=True)), 1) + self.assertEqual(len(service.containers(stopped=True, one_off=OneOffFilter.only)), 1) self.dispatch(['rm', '-f'], None) self.assertEqual(len(service.containers(stopped=True)), 0) - self.assertEqual(len(service.containers(stopped=True, one_off=True)), 1) + self.assertEqual(len(service.containers(stopped=True, one_off=OneOffFilter.only)), 1) self.dispatch(['rm', '-f', '-a'], None) - self.assertEqual(len(service.containers(stopped=True, one_off=True)), 0) + self.assertEqual(len(service.containers(stopped=True, one_off=OneOffFilter.only)), 0) service.create_container(one_off=False) service.create_container(one_off=True) kill_service(service) self.assertEqual(len(service.containers(stopped=True)), 1) - self.assertEqual(len(service.containers(stopped=True, one_off=True)), 1) + self.assertEqual(len(service.containers(stopped=True, one_off=OneOffFilter.only)), 1) self.dispatch(['rm', '-f', '--all'], None) self.assertEqual(len(service.containers(stopped=True)), 0) - self.assertEqual(len(service.containers(stopped=True, one_off=True)), 0) + self.assertEqual(len(service.containers(stopped=True, one_off=OneOffFilter.only)), 0) def test_stop(self): self.dispatch(['up', '-d'], None) diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index 6d0c97db3..2682e59da 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -24,6 +24,7 @@ from compose.const import LABEL_PROJECT from compose.const import LABEL_SERVICE from compose.const import LABEL_VERSION from compose.container import Container +from compose.project import OneOffFilter from compose.service import ConvergencePlan from compose.service import ConvergenceStrategy from compose.service import NetworkMode @@ -60,7 +61,7 @@ class ServiceTest(DockerClientTestCase): db = self.create_service('db') container = db.create_container(one_off=True) self.assertEqual(db.containers(stopped=True), []) - self.assertEqual(db.containers(one_off=True, stopped=True), [container]) + self.assertEqual(db.containers(one_off=OneOffFilter.only, stopped=True), [container]) def test_project_is_added_to_container_name(self): service = self.create_service('web') @@ -494,7 +495,7 @@ class ServiceTest(DockerClientTestCase): create_and_start_container(db) create_and_start_container(db) - c = create_and_start_container(db, one_off=True) + c = create_and_start_container(db, one_off=OneOffFilter.only) self.assertEqual( set(get_links(c)), diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index 199aeeb43..0e3e8a86c 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -14,6 +14,7 @@ from compose.const import LABEL_ONE_OFF from compose.const import LABEL_PROJECT from compose.const import LABEL_SERVICE from compose.container import Container +from compose.project import OneOffFilter from compose.service import build_ulimits from compose.service import build_volume_binding from compose.service import BuildAction @@ -256,7 +257,7 @@ class ServiceTest(unittest.TestCase): opts = service._get_container_create_options( {'name': name}, 1, - one_off=True) + one_off=OneOffFilter.only) self.assertEqual(opts['name'], name) def test_get_container_create_options_does_not_mutate_options(self): From 1bc946967497d848e4ac18a8420fddd793236a31 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Mon, 21 Mar 2016 14:42:57 +0000 Subject: [PATCH 3/6] Don't allow boolean values for one_off in Project methods Signed-off-by: Aanand Prasad --- compose/project.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/compose/project.py b/compose/project.py index aef556e92..c3283db98 100644 --- a/compose/project.py +++ b/compose/project.py @@ -46,8 +46,12 @@ class OneOffFilter(enum.Enum): def update_labels(cls, value, labels): if value == cls.only: labels.append('{0}={1}'.format(LABEL_ONE_OFF, "True")) - elif value == cls.exclude or value is False: + elif value == cls.exclude: labels.append('{0}={1}'.format(LABEL_ONE_OFF, "False")) + elif value == cls.include: + pass + else: + raise ValueError("Invalid value for one_off: {}".format(repr(value))) class Project(object): @@ -61,7 +65,7 @@ class Project(object): self.volumes = volumes or ProjectVolumes({}) self.networks = networks or ProjectNetworks({}, False) - def labels(self, one_off=False): + def labels(self, one_off=OneOffFilter.exclude): labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)] OneOffFilter.update_labels(one_off, labels) @@ -264,7 +268,7 @@ class Project(object): def kill(self, service_names=None, **options): parallel.parallel_kill(self.containers(service_names), options) - def remove_stopped(self, service_names=None, one_off=False, **options): + def remove_stopped(self, service_names=None, one_off=OneOffFilter.exclude, **options): parallel.parallel_remove(self.containers( service_names, stopped=True, one_off=one_off ), options) @@ -429,7 +433,7 @@ class Project(object): for service in self.get_services(service_names, include_deps=False): service.pull(ignore_pull_failures) - def _labeled_containers(self, stopped=False, one_off=False): + def _labeled_containers(self, stopped=False, one_off=OneOffFilter.exclude): return list(filter(None, [ Container.from_ps(self.client, container) for container in self.client.containers( @@ -437,7 +441,7 @@ class Project(object): filters={'label': self.labels(one_off=one_off)})]) ) - def containers(self, service_names=None, stopped=False, one_off=False): + def containers(self, service_names=None, stopped=False, one_off=OneOffFilter.exclude): if service_names: self.validate_service_names(service_names) else: From 81f6d86ad9121e022b61c03be8977bd79e1b2fdd Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Mon, 21 Mar 2016 15:14:31 +0000 Subject: [PATCH 4/6] Warn when --all is not passed to rm Signed-off-by: Aanand Prasad --- compose/cli/main.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/compose/cli/main.py b/compose/cli/main.py index f481d5847..a978579c0 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -495,7 +495,14 @@ class TopLevelCommand(object): -a, --all Also remove one-off containers created by docker-compose run """ - one_off = OneOffFilter.include if options.get('--all') else OneOffFilter.exclude + if options.get('--all'): + one_off = OneOffFilter.include + else: + log.warn( + 'Not including one-off containers created by `docker-compose run`.\n' + 'To include them, use `docker-compose rm --all`.\n' + 'This will be the default behavior in the next version of Compose.\n') + one_off = OneOffFilter.exclude all_containers = self.project.containers( service_names=options['SERVICE'], stopped=True, one_off=one_off From a2317dfac26d5709bf460671bd7e054567fc94de Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Mon, 21 Mar 2016 16:15:49 +0000 Subject: [PATCH 5/6] Remove one-off containers in 'docker-compose down' Signed-off-by: Aanand Prasad --- compose/project.py | 2 +- tests/acceptance/cli_test.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/compose/project.py b/compose/project.py index c3283db98..f0b4f1c68 100644 --- a/compose/project.py +++ b/compose/project.py @@ -276,7 +276,7 @@ class Project(object): def down(self, remove_image_type, include_volumes, remove_orphans=False): self.stop() self.find_orphan_containers(remove_orphans) - self.remove_stopped(v=include_volumes) + self.remove_stopped(v=include_volumes, one_off=OneOffFilter.include) self.networks.remove() diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 382fa8870..153515023 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -366,14 +366,19 @@ class CLITestCase(DockerClientTestCase): @v2_only() def test_down(self): self.base_dir = 'tests/fixtures/v2-full' + self.dispatch(['up', '-d']) wait_on_condition(ContainerCountCondition(self.project, 2)) + self.dispatch(['run', 'web', 'true']) + assert len(self.project.containers(one_off=OneOffFilter.only, stopped=True)) == 1 + result = self.dispatch(['down', '--rmi=local', '--volumes']) assert 'Stopping v2full_web_1' in result.stderr assert 'Stopping v2full_other_1' in result.stderr assert 'Removing v2full_web_1' in result.stderr assert 'Removing v2full_other_1' in result.stderr + assert 'Removing v2full_web_run_1' in result.stderr assert 'Removing volume v2full_data' in result.stderr assert 'Removing image v2full_web' in result.stderr assert 'Removing image busybox' not in result.stderr From 2bf5e468574f5863ed57a1b5327668e61a578130 Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Mon, 21 Mar 2016 18:08:07 +0000 Subject: [PATCH 6/6] Stop and remove still-running one-off containers in 'down' Signed-off-by: Aanand Prasad --- compose/project.py | 6 +++--- tests/acceptance/cli_test.py | 5 ++++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/compose/project.py b/compose/project.py index f0b4f1c68..8aa487319 100644 --- a/compose/project.py +++ b/compose/project.py @@ -239,8 +239,8 @@ class Project(object): return containers - def stop(self, service_names=None, **options): - containers = self.containers(service_names) + def stop(self, service_names=None, one_off=OneOffFilter.exclude, **options): + containers = self.containers(service_names, one_off=one_off) def get_deps(container): # actually returning inversed dependencies @@ -274,7 +274,7 @@ class Project(object): ), options) def down(self, remove_image_type, include_volumes, remove_orphans=False): - self.stop() + self.stop(one_off=OneOffFilter.include) self.find_orphan_containers(remove_orphans) self.remove_stopped(v=include_volumes, one_off=OneOffFilter.include) diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 153515023..b81af68d0 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -371,14 +371,17 @@ class CLITestCase(DockerClientTestCase): wait_on_condition(ContainerCountCondition(self.project, 2)) self.dispatch(['run', 'web', 'true']) - assert len(self.project.containers(one_off=OneOffFilter.only, stopped=True)) == 1 + self.dispatch(['run', '-d', 'web', 'tail', '-f', '/dev/null']) + assert len(self.project.containers(one_off=OneOffFilter.only, stopped=True)) == 2 result = self.dispatch(['down', '--rmi=local', '--volumes']) assert 'Stopping v2full_web_1' in result.stderr assert 'Stopping v2full_other_1' in result.stderr + assert 'Stopping v2full_web_run_2' in result.stderr assert 'Removing v2full_web_1' in result.stderr assert 'Removing v2full_other_1' in result.stderr assert 'Removing v2full_web_run_1' in result.stderr + assert 'Removing v2full_web_run_2' in result.stderr assert 'Removing volume v2full_data' in result.stderr assert 'Removing image v2full_web' in result.stderr assert 'Removing image busybox' not in result.stderr