From 817c76c8e9ebbeb864ef69e8e03819fefb1a784d Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 8 Sep 2016 11:17:39 -0700 Subject: [PATCH 1/5] Fix command hint in bundle to pull services instead of images Signed-off-by: Joffrey F --- compose/bundle.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compose/bundle.py b/compose/bundle.py index afbdabfa81f..854cc79954d 100644 --- a/compose/bundle.py +++ b/compose/bundle.py @@ -46,8 +46,9 @@ def __init__(self, image_name): class NeedsPull(Exception): - def __init__(self, image_name): + def __init__(self, image_name, service_name): self.image_name = image_name + self.service_name = service_name class MissingDigests(Exception): @@ -74,7 +75,7 @@ def get_image_digests(project, allow_push=False): except NeedsPush as e: needs_push.add(e.image_name) except NeedsPull as e: - needs_pull.add(e.image_name) + needs_pull.add(e.service_name) if needs_push or needs_pull: raise MissingDigests(needs_push, needs_pull) @@ -109,7 +110,7 @@ def get_image_digest(service, allow_push=False): return image['RepoDigests'][0] if 'build' not in service.options: - raise NeedsPull(service.image_name) + raise NeedsPull(service.image_name, service.name) if not allow_push: raise NeedsPush(service.image_name) From b64bd07f225dd8af9f2b8fd096dfd957022c3dda Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Fri, 9 Sep 2016 17:19:14 -0700 Subject: [PATCH 2/5] Update docker-py dependency to latest release Signed-off-by: Joffrey F --- requirements.txt | 5 +++-- setup.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 831ed65a9eb..7f28514b1b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,14 +1,15 @@ PyYAML==3.11 backports.ssl-match-hostname==3.5.0.1; python_version < '3' cached-property==1.2.0 -docker-py==1.9.0 +docker-py==1.10.2 dockerpty==0.4.1 docopt==0.6.1 enum34==1.0.4; python_version < '3.4' functools32==3.2.3.post2; python_version < '3.2' ipaddress==1.0.16 jsonschema==2.5.1 +pypiwin32==219; sys_platform == 'win32' requests==2.7.0 -six==1.7.3 +six==1.10.0 texttable==0.8.4 websocket-client==0.32.0 diff --git a/setup.py b/setup.py index 5cb52dae4a3..34b40273817 100644 --- a/setup.py +++ b/setup.py @@ -34,7 +34,7 @@ def find_version(*file_paths): 'requests >= 2.6.1, < 2.8', 'texttable >= 0.8.1, < 0.9', 'websocket-client >= 0.32.0, < 1.0', - 'docker-py >= 1.9.0, < 2.0', + 'docker-py >= 1.10.2, < 2.0', 'dockerpty >= 0.4.1, < 0.5', 'six >= 1.3.0, < 2', 'jsonschema >= 2.5.1, < 3', From 9759f27fa6a970ee9e01d1edd2d8b4a96eb510fa Mon Sep 17 00:00:00 2001 From: Aanand Prasad Date: Tue, 13 Sep 2016 14:50:37 +0100 Subject: [PATCH 3/5] Fix integration test on Docker for Mac Signed-off-by: Aanand Prasad --- tests/unit/cli/errors_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/cli/errors_test.py b/tests/unit/cli/errors_test.py index 71fa9dee5de..1d454a08185 100644 --- a/tests/unit/cli/errors_test.py +++ b/tests/unit/cli/errors_test.py @@ -32,7 +32,7 @@ def test_generic_connection_error(self, mock_logging): raise ConnectionError() _, args, _ = mock_logging.error.mock_calls[0] - assert "Couldn't connect to Docker daemon at" in args[0] + assert "Couldn't connect to Docker daemon" in args[0] def test_api_error_version_mismatch(self, mock_logging): with pytest.raises(errors.ConnectionError): From 7dd2e33057f8dab7bb63ceedc8ea598f993463c4 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Mon, 12 Sep 2016 16:02:58 -0700 Subject: [PATCH 4/5] Only allow log streaming if logdriver is json-file or journald Signed-off-by: Joffrey F --- compose/container.py | 2 +- tests/unit/container_test.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/compose/container.py b/compose/container.py index 2c16863df95..bda4e659fb2 100644 --- a/compose/container.py +++ b/compose/container.py @@ -163,7 +163,7 @@ def log_driver(self): @property def has_api_logs(self): log_type = self.log_driver - return not log_type or log_type != 'none' + return not log_type or log_type in ('json-file', 'journald') def attach_log_stream(self): """A log stream can only be attached if the container uses a json-file diff --git a/tests/unit/container_test.py b/tests/unit/container_test.py index 47f60de8f91..62e3aa2cfc1 100644 --- a/tests/unit/container_test.py +++ b/tests/unit/container_test.py @@ -150,6 +150,34 @@ def test_short_id(self): container = Container(None, self.container_dict, has_been_inspected=True) assert container.short_id == self.container_id[:12] + def test_has_api_logs(self): + container_dict = { + 'HostConfig': { + 'LogConfig': { + 'Type': 'json-file' + } + } + } + + container = Container(None, container_dict, has_been_inspected=True) + assert container.has_api_logs is True + + container_dict['HostConfig']['LogConfig']['Type'] = 'none' + container = Container(None, container_dict, has_been_inspected=True) + assert container.has_api_logs is False + + container_dict['HostConfig']['LogConfig']['Type'] = 'syslog' + container = Container(None, container_dict, has_been_inspected=True) + assert container.has_api_logs is False + + container_dict['HostConfig']['LogConfig']['Type'] = 'journald' + container = Container(None, container_dict, has_been_inspected=True) + assert container.has_api_logs is True + + container_dict['HostConfig']['LogConfig']['Type'] = 'foobar' + container = Container(None, container_dict, has_been_inspected=True) + assert container.has_api_logs is False + class GetContainerNameTestCase(unittest.TestCase): From 3fcd648ba2e63191f72c7bdb53cadc387c90769f Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Mon, 12 Sep 2016 15:49:54 -0700 Subject: [PATCH 5/5] Catch APIError while printing container logs Signed-off-by: Joffrey F --- compose/cli/log_printer.py | 11 +++++++++-- tests/unit/cli/log_printer_test.py | 22 ++++++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/compose/cli/log_printer.py b/compose/cli/log_printer.py index b48462ff57a..299ddea465c 100644 --- a/compose/cli/log_printer.py +++ b/compose/cli/log_printer.py @@ -6,6 +6,7 @@ from itertools import cycle from threading import Thread +from docker.errors import APIError from six.moves import _thread as thread from six.moves.queue import Empty from six.moves.queue import Queue @@ -176,8 +177,14 @@ def build_log_generator(container, log_args): def wait_on_exit(container): - exit_code = container.wait() - return "%s exited with code %s\n" % (container.name, exit_code) + try: + exit_code = container.wait() + return "%s exited with code %s\n" % (container.name, exit_code) + except APIError as e: + return "Unexpected API error for %s (HTTP code %s)\nResponse body:\n%s\n" % ( + container.name, e.response.status_code, + e.response.text or '[empty]' + ) def start_producer_thread(thread_args): diff --git a/tests/unit/cli/log_printer_test.py b/tests/unit/cli/log_printer_test.py index ab48eefc0a1..b908eb68b62 100644 --- a/tests/unit/cli/log_printer_test.py +++ b/tests/unit/cli/log_printer_test.py @@ -4,7 +4,9 @@ import itertools import pytest +import requests import six +from docker.errors import APIError from six.moves.queue import Queue from compose.cli.log_printer import build_log_generator @@ -56,6 +58,26 @@ def test_wait_on_exit(): assert expected == wait_on_exit(mock_container) +def test_wait_on_exit_raises(): + status_code = 500 + + def mock_wait(): + resp = requests.Response() + resp.status_code = status_code + raise APIError('Bad server', resp) + + mock_container = mock.Mock( + spec=Container, + name='cname', + wait=mock_wait + ) + + expected = 'Unexpected API error for {} (HTTP code {})\n'.format( + mock_container.name, status_code, + ) + assert expected in wait_on_exit(mock_container) + + def test_build_no_log_generator(mock_container): mock_container.has_api_logs = False mock_container.log_driver = 'none'