From bf48a781dbc4d82e8b9fa940522b68b78e4c12e3 Mon Sep 17 00:00:00 2001 From: Evgeniy Dobrohvalov Date: Fri, 2 Oct 2015 20:07:44 +0300 Subject: [PATCH] Add flag for stops all containers if any container was stopped. Signed-off-by: Evgeniy Dobrohvalov --- compose/cli/log_printer.py | 5 +++-- compose/cli/main.py | 38 ++++++++++++++++++++-------------- compose/cli/multiplexer.py | 8 +++++-- docs/reference/up.md | 28 ++++++++++++++----------- tests/unit/cli/main_test.py | 4 ++-- tests/unit/multiplexer_test.py | 18 ++++++++++++++++ 6 files changed, 68 insertions(+), 33 deletions(-) diff --git a/compose/cli/log_printer.py b/compose/cli/log_printer.py index 864657a4c6..85fef794f0 100644 --- a/compose/cli/log_printer.py +++ b/compose/cli/log_printer.py @@ -13,10 +13,11 @@ from compose.utils import split_buffer class LogPrinter(object): """Print logs from many containers to a single output stream.""" - def __init__(self, containers, output=sys.stdout, monochrome=False): + def __init__(self, containers, output=sys.stdout, monochrome=False, cascade_stop=False): self.containers = containers self.output = utils.get_output_stream(output) self.monochrome = monochrome + self.cascade_stop = cascade_stop def run(self): if not self.containers: @@ -24,7 +25,7 @@ class LogPrinter(object): prefix_width = max_name_width(self.containers) generators = list(self._make_log_generators(self.monochrome, prefix_width)) - for line in Multiplexer(generators).loop(): + for line in Multiplexer(generators, cascade_stop=self.cascade_stop).loop(): self.output.write(line) self.output.flush() diff --git a/compose/cli/main.py b/compose/cli/main.py index 7c6d1cb5a3..a46521f31b 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -590,25 +590,33 @@ class TopLevelCommand(DocoptCommand): Usage: up [options] [SERVICE...] Options: - -d Detached mode: Run containers in the background, - print new container names. - --no-color Produce monochrome output. - --no-deps Don't start linked services. - --force-recreate Recreate containers even if their configuration and - image haven't changed. Incompatible with --no-recreate. - --no-recreate If containers already exist, don't recreate them. - Incompatible with --force-recreate. - --no-build Don't build an image, even if it's missing - -t, --timeout TIMEOUT Use this timeout in seconds for container shutdown - when attached or when containers are already - running. (default: 10) + -d Detached mode: Run containers in the background, + print new container names. + Incompatible with --abort-on-container-exit. + --no-color Produce monochrome output. + --no-deps Don't start linked services. + --force-recreate Recreate containers even if their configuration + and image haven't changed. + Incompatible with --no-recreate. + --no-recreate If containers already exist, don't recreate them. + Incompatible with --force-recreate. + --no-build Don't build an image, even if it's missing + --abort-on-container-exit Stops all containers if any container was stopped. + Incompatible with -d. + -t, --timeout TIMEOUT Use this timeout in seconds for container shutdown + when attached or when containers are already + running. (default: 10) """ monochrome = options['--no-color'] start_deps = not options['--no-deps'] + cascade_stop = options['--abort-on-container-exit'] service_names = options['SERVICE'] timeout = int(options.get('--timeout') or DEFAULT_TIMEOUT) detached = options.get('-d') + if detached and cascade_stop: + raise UserError("--abort-on-container-exit and -d cannot be combined.") + to_attach = project.up( service_names=service_names, start_deps=start_deps, @@ -619,7 +627,7 @@ class TopLevelCommand(DocoptCommand): ) if not detached: - log_printer = build_log_printer(to_attach, service_names, monochrome) + log_printer = build_log_printer(to_attach, service_names, monochrome, cascade_stop) attach_to_logs(project, log_printer, service_names, timeout) def version(self, project, options): @@ -695,13 +703,13 @@ def run_one_off_container(container_options, project, service, options): sys.exit(exit_code) -def build_log_printer(containers, service_names, monochrome): +def build_log_printer(containers, service_names, monochrome, cascade_stop): if service_names: containers = [ container for container in containers if container.service in service_names ] - return LogPrinter(containers, monochrome=monochrome) + return LogPrinter(containers, monochrome=monochrome, cascade_stop=cascade_stop) def attach_to_logs(project, log_printer, service_names, timeout): diff --git a/compose/cli/multiplexer.py b/compose/cli/multiplexer.py index 5e8d91a47f..e6e63f24b5 100644 --- a/compose/cli/multiplexer.py +++ b/compose/cli/multiplexer.py @@ -20,8 +20,9 @@ class Multiplexer(object): parallel and yielding results as they come in. """ - def __init__(self, iterators): + def __init__(self, iterators, cascade_stop=False): self.iterators = iterators + self.cascade_stop = cascade_stop self._num_running = len(iterators) self.queue = Queue() @@ -36,7 +37,10 @@ class Multiplexer(object): raise exception if item is STOP: - self._num_running -= 1 + if self.cascade_stop is True: + break + else: + self._num_running -= 1 else: yield item except Empty: diff --git a/docs/reference/up.md b/docs/reference/up.md index 966aff1e95..a02358ec78 100644 --- a/docs/reference/up.md +++ b/docs/reference/up.md @@ -15,18 +15,22 @@ parent = "smn_compose_cli" Usage: up [options] [SERVICE...] Options: --d Detached mode: Run containers in the background, - print new container names. ---no-color Produce monochrome output. ---no-deps Don't start linked services. ---force-recreate Recreate containers even if their configuration and - image haven't changed. Incompatible with --no-recreate. ---no-recreate If containers already exist, don't recreate them. - Incompatible with --force-recreate. ---no-build Don't build an image, even if it's missing --t, --timeout TIMEOUT Use this timeout in seconds for container shutdown - when attached or when containers are already - running. (default: 10) +-d Detached mode: Run containers in the background, + print new container names. + Incompatible with --abort-on-container-exit. +--no-color Produce monochrome output. +--no-deps Don't start linked services. +--force-recreate Recreate containers even if their configuration + and image haven't changed. + Incompatible with --no-recreate. +--no-recreate If containers already exist, don't recreate them. + Incompatible with --force-recreate. +--no-build Don't build an image, even if it's missing +--abort-on-container-exit Stops all containers if any container was stopped. + Incompatible with -d. +-t, --timeout TIMEOUT Use this timeout in seconds for container shutdown + when attached or when containers are already + running. (default: 10) ``` Builds, (re)creates, starts, and attaches to containers for a service. diff --git a/tests/unit/cli/main_test.py b/tests/unit/cli/main_test.py index f62b2bcc3b..6f5dd3ca97 100644 --- a/tests/unit/cli/main_test.py +++ b/tests/unit/cli/main_test.py @@ -36,7 +36,7 @@ class CLIMainTestCase(unittest.TestCase): mock_container('another', 1), ] service_names = ['web', 'db'] - log_printer = build_log_printer(containers, service_names, True) + log_printer = build_log_printer(containers, service_names, True, False) self.assertEqual(log_printer.containers, containers[:3]) def test_build_log_printer_all_services(self): @@ -46,7 +46,7 @@ class CLIMainTestCase(unittest.TestCase): mock_container('other', 1), ] service_names = [] - log_printer = build_log_printer(containers, service_names, True) + log_printer = build_log_printer(containers, service_names, True, False) self.assertEqual(log_printer.containers, containers) def test_attach_to_logs(self): diff --git a/tests/unit/multiplexer_test.py b/tests/unit/multiplexer_test.py index c56ece1bd0..750faad882 100644 --- a/tests/unit/multiplexer_test.py +++ b/tests/unit/multiplexer_test.py @@ -2,6 +2,7 @@ from __future__ import absolute_import from __future__ import unicode_literals import unittest +from time import sleep from compose.cli.multiplexer import Multiplexer @@ -46,3 +47,20 @@ class MultiplexerTest(unittest.TestCase): with self.assertRaises(Problem): list(mux.loop()) + + def test_cascade_stop(self): + mux = Multiplexer([ + ((lambda x: sleep(0.01) or x)(x) for x in ['after 0.01 sec T1', + 'after 0.02 sec T1', + 'after 0.03 sec T1']), + ((lambda x: sleep(0.02) or x)(x) for x in ['after 0.02 sec T2', + 'after 0.04 sec T2', + 'after 0.06 sec T2']), + ], cascade_stop=True) + + self.assertEqual( + ['after 0.01 sec T1', + 'after 0.02 sec T1', + 'after 0.02 sec T2', + 'after 0.03 sec T1'], + sorted(list(mux.loop())))