From e1b7510e4a8588df320f87ec4e24623a8e9493c4 Mon Sep 17 00:00:00 2001 From: Tomas Tomecek Date: Thu, 21 Apr 2016 14:03:02 +0200 Subject: [PATCH] service: detailed error messages for create and start Fixes: #3355 Signed-off-by: Tomas Tomecek --- compose/cli/main.py | 4 +++- compose/errors.py | 7 +++++++ compose/parallel.py | 4 ++++ compose/service.py | 12 ++++++++++-- tests/integration/service_test.py | 5 ++++- 5 files changed, 28 insertions(+), 4 deletions(-) create mode 100644 compose/errors.py diff --git a/compose/cli/main.py b/compose/cli/main.py index f4c171677..7ca9eac95 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -32,6 +32,7 @@ from ..service import BuildError from ..service import ConvergenceStrategy from ..service import ImageType from ..service import NeedsBuildError +from ..service import OperationFailedError from .command import get_config_from_options from .command import project_from_options from .docopt_command import DocoptDispatcher @@ -61,7 +62,8 @@ def main(): except (KeyboardInterrupt, signals.ShutdownException): log.error("Aborting.") sys.exit(1) - except (UserError, NoSuchService, ConfigurationError, ProjectError) as e: + except (UserError, NoSuchService, ConfigurationError, + ProjectError, OperationFailedError) as e: log.error(e.msg) sys.exit(1) except BuildError as e: diff --git a/compose/errors.py b/compose/errors.py new file mode 100644 index 000000000..9f68760d3 --- /dev/null +++ b/compose/errors.py @@ -0,0 +1,7 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + + +class OperationFailedError(Exception): + def __init__(self, reason): + self.msg = reason diff --git a/compose/parallel.py b/compose/parallel.py index 50b2dbeaf..7ac66b37a 100644 --- a/compose/parallel.py +++ b/compose/parallel.py @@ -12,6 +12,7 @@ from six.moves.queue import Empty from six.moves.queue import Queue from compose.cli.signals import ShutdownException +from compose.errors import OperationFailedError from compose.utils import get_output_stream @@ -47,6 +48,9 @@ def parallel_execute(objects, func, get_name, msg, get_deps=None): elif isinstance(exception, APIError): errors[get_name(obj)] = exception.explanation writer.write(get_name(obj), 'error') + elif isinstance(exception, OperationFailedError): + errors[get_name(obj)] = exception.msg + writer.write(get_name(obj), 'error') elif isinstance(exception, UpstreamError): writer.write(get_name(obj), 'error') else: diff --git a/compose/service.py b/compose/service.py index af572e5b5..7bb36cd64 100644 --- a/compose/service.py +++ b/compose/service.py @@ -27,6 +27,7 @@ from .const import LABEL_PROJECT from .const import LABEL_SERVICE from .const import LABEL_VERSION from .container import Container +from .errors import OperationFailedError from .parallel import parallel_execute from .parallel import parallel_start from .progress_stream import stream_output @@ -277,7 +278,11 @@ class Service(object): if 'name' in container_options and not quiet: log.info("Creating %s" % container_options['name']) - return Container.create(self.client, **container_options) + try: + return Container.create(self.client, **container_options) + except APIError as ex: + raise OperationFailedError("Cannot create container for service %s: %s" % + (self.name, ex.explanation)) def ensure_image_exists(self, do_build=BuildAction.none): if self.can_be_built() and do_build == BuildAction.force: @@ -447,7 +452,10 @@ class Service(object): def start_container(self, container): self.connect_container_to_networks(container) - container.start() + try: + container.start() + except APIError as ex: + raise OperationFailedError("Cannot start service %s: %s" % (self.name, ex.explanation)) return container def connect_container_to_networks(self, container): diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index 1801f5bfc..053dee1b5 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -738,7 +738,10 @@ class ServiceTest(DockerClientTestCase): self.assertEqual(len(service.containers()), 1) self.assertTrue(service.containers()[0].is_running) - self.assertIn("ERROR: for composetest_web_2 Boom", mock_stderr.getvalue()) + self.assertIn( + "ERROR: for composetest_web_2 Cannot create container for service web: Boom", + mock_stderr.getvalue() + ) def test_scale_with_unexpected_exception(self): """Test that when scaling if the API returns an error, that is not of type