From 6c3641fdb7c757980dcd04b44d0a573536e149b8 Mon Sep 17 00:00:00 2001 From: kbroadwater Date: Fri, 10 Feb 2017 10:35:08 -0800 Subject: [PATCH] Returing 1 when a container exits with a non-zero exit code with --abort-on-container-exit is set. Signed-off-by: Kevin Broadwater Catching the first container to exit Signed-off-by: Kevin Broadwater Addressing feedback and fixing tests Signed-off-by: Kevin Broadwater Adding break and removing extra fixture files Signed-off-by: Kevin Broadwater Moving break Signed-off-by: Kevin Broadwater --- compose/cli/log_printer.py | 21 +++++++++++-------- compose/cli/main.py | 9 +++++++- tests/acceptance/cli_test.py | 14 ++++++++++--- .../docker-compose.yml | 6 ++++++ .../docker-compose.yml | 6 ++++++ tests/unit/cli/log_printer_test.py | 6 ++++-- 6 files changed, 47 insertions(+), 15 deletions(-) create mode 100644 tests/fixtures/abort-on-container-exit-0/docker-compose.yml create mode 100644 tests/fixtures/abort-on-container-exit-1/docker-compose.yml diff --git a/compose/cli/log_printer.py b/compose/cli/log_printer.py index 299ddea46..043d3d068 100644 --- a/compose/cli/log_printer.py +++ b/compose/cli/log_printer.py @@ -87,6 +87,13 @@ class LogPrinter(object): for line in consume_queue(queue, self.cascade_stop): remove_stopped_threads(thread_map) + if self.cascade_stop: + matching_container = [cont.name for cont in self.containers if cont.name == line] + if line in matching_container: + # Returning the name of the container that started the + # the cascade_stop so we can return the correct exit code + return line + if not line: if not thread_map: # There are no running containers left to tail, so exit @@ -132,8 +139,8 @@ class QueueItem(namedtuple('_QueueItem', 'item is_stop exc')): return cls(None, None, exc) @classmethod - def stop(cls): - return cls(None, True, None) + def stop(cls, item=None): + return cls(item, True, None) def tail_container_logs(container, presenter, queue, log_args): @@ -145,10 +152,9 @@ def tail_container_logs(container, presenter, queue, log_args): except Exception as e: queue.put(QueueItem.exception(e)) return - if log_args.get('follow'): queue.put(QueueItem.new(presenter.color_func(wait_on_exit(container)))) - queue.put(QueueItem.stop()) + queue.put(QueueItem.stop(container.name)) def get_log_generator(container): @@ -228,10 +234,7 @@ def consume_queue(queue, cascade_stop): if item.exc: raise item.exc - if item.is_stop: - if cascade_stop: - raise StopIteration - else: - continue + if item.is_stop and not cascade_stop: + continue yield item.item diff --git a/compose/cli/main.py b/compose/cli/main.py index d0cf03cba..f5d2429f3 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -890,11 +890,18 @@ class TopLevelCommand(object): cascade_stop, event_stream=self.project.events(service_names=service_names)) print("Attaching to", list_containers(log_printer.containers)) - log_printer.run() + cascade_starter = log_printer.run() if cascade_stop: print("Aborting on container exit...") + exit_code = 0 + for e in self.project.containers(service_names=options['SERVICE'], stopped=True): + if (not e.is_running and cascade_starter == e.name): + if not e.exit_code == 0: + exit_code = e.exit_code + break self.project.stop(service_names=service_names, timeout=timeout) + sys.exit(exit_code) @classmethod def version(cls, options): diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 417eadb5c..9379a6c31 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1085,10 +1085,18 @@ class CLITestCase(DockerClientTestCase): wait_on_condition(ContainerCountCondition(self.project, 0)) def test_up_handles_abort_on_container_exit(self): - start_process(self.base_dir, ['up', '--abort-on-container-exit']) - wait_on_condition(ContainerCountCondition(self.project, 2)) - self.project.stop(['simple']) + self.base_dir = 'tests/fixtures/abort-on-container-exit-0' + proc = start_process(self.base_dir, ['up', '--abort-on-container-exit']) wait_on_condition(ContainerCountCondition(self.project, 0)) + proc.wait() + self.assertEqual(proc.returncode, 0) + + def test_up_handles_abort_on_container_exit_code(self): + self.base_dir = 'tests/fixtures/abort-on-container-exit-1' + proc = start_process(self.base_dir, ['up', '--abort-on-container-exit']) + wait_on_condition(ContainerCountCondition(self.project, 0)) + proc.wait() + self.assertEqual(proc.returncode, 1) def test_exec_without_tty(self): self.base_dir = 'tests/fixtures/links-composefile' diff --git a/tests/fixtures/abort-on-container-exit-0/docker-compose.yml b/tests/fixtures/abort-on-container-exit-0/docker-compose.yml new file mode 100644 index 000000000..ce41697bc --- /dev/null +++ b/tests/fixtures/abort-on-container-exit-0/docker-compose.yml @@ -0,0 +1,6 @@ +simple: + image: busybox:latest + command: top +another: + image: busybox:latest + command: ls . diff --git a/tests/fixtures/abort-on-container-exit-1/docker-compose.yml b/tests/fixtures/abort-on-container-exit-1/docker-compose.yml new file mode 100644 index 000000000..7ec9b7e11 --- /dev/null +++ b/tests/fixtures/abort-on-container-exit-1/docker-compose.yml @@ -0,0 +1,6 @@ +simple: + image: busybox:latest + command: top +another: + image: busybox:latest + command: ls /thecakeisalie diff --git a/tests/unit/cli/log_printer_test.py b/tests/unit/cli/log_printer_test.py index b908eb68b..d0c4b56be 100644 --- a/tests/unit/cli/log_printer_test.py +++ b/tests/unit/cli/log_printer_test.py @@ -187,11 +187,13 @@ class TestConsumeQueue(object): assert next(generator) == 'b' def test_item_is_stop_with_cascade_stop(self): + """Return the name of the container that caused the cascade_stop""" queue = Queue() - for item in QueueItem.stop(), QueueItem.new('a'), QueueItem.new('b'): + for item in QueueItem.stop('foobar-1'), QueueItem.new('a'), QueueItem.new('b'): queue.put(item) - assert list(consume_queue(queue, True)) == [] + generator = consume_queue(queue, True) + assert next(generator) is 'foobar-1' def test_item_is_none_when_timeout_is_hit(self): queue = Queue()