From 5b912082e1fac0da43313c552baf857a50a38e76 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 18 Jan 2017 17:52:03 -0800 Subject: [PATCH 1/8] depends_on merge now retains condition information when present Signed-off-by: Joffrey F --- compose/config/config.py | 6 +++++- tests/unit/config/config_test.py | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/compose/config/config.py b/compose/config/config.py index c11460fa5..7e77421e5 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -818,6 +818,7 @@ def merge_service_dicts(base, override, version): md.merge_mapping('ulimits', parse_ulimits) md.merge_mapping('networks', parse_networks) md.merge_mapping('sysctls', parse_sysctls) + md.merge_mapping('depends_on', parse_depends_on) md.merge_sequence('links', ServiceLink.parse) for field in ['volumes', 'devices']: @@ -825,7 +826,7 @@ def merge_service_dicts(base, override, version): for field in [ 'ports', 'cap_add', 'cap_drop', 'expose', 'external_links', - 'security_opt', 'volumes_from', 'depends_on', + 'security_opt', 'volumes_from', ]: md.merge_field(field, merge_unique_items_lists, default=[]) @@ -920,6 +921,9 @@ parse_environment = functools.partial(parse_dict_or_list, split_env, 'environmen parse_labels = functools.partial(parse_dict_or_list, split_kv, 'labels') parse_networks = functools.partial(parse_dict_or_list, lambda k: (k, None), 'networks') parse_sysctls = functools.partial(parse_dict_or_list, split_kv, 'sysctls') +parse_depends_on = functools.partial( + parse_dict_or_list, lambda k: (k, {'condition': 'service_started'}), 'depends_on' +) def parse_ulimits(ulimits): diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index ca7c61683..ab8bfcfcc 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -1713,6 +1713,40 @@ class ConfigTest(unittest.TestCase): } } + def test_merge_depends_on_no_override(self): + base = { + 'image': 'busybox', + 'depends_on': { + 'app1': {'condition': 'service_started'}, + 'app2': {'condition': 'service_healthy'} + } + } + override = {} + actual = config.merge_service_dicts(base, override, V2_1) + assert actual == base + + def test_merge_depends_on_mixed_syntax(self): + base = { + 'image': 'busybox', + 'depends_on': { + 'app1': {'condition': 'service_started'}, + 'app2': {'condition': 'service_healthy'} + } + } + override = { + 'depends_on': ['app3'] + } + + actual = config.merge_service_dicts(base, override, V2_1) + assert actual == { + 'image': 'busybox', + 'depends_on': { + 'app1': {'condition': 'service_started'}, + 'app2': {'condition': 'service_healthy'}, + 'app3': {'condition': 'service_started'} + } + } + def test_external_volume_config(self): config_details = build_config_details({ 'version': '2', From e035931f2ee073df04c4d1b4abfa0b0cf1dbde4c Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 19 Jan 2017 15:41:31 -0800 Subject: [PATCH 2/8] Fix volume definition in v3 schema Signed-off-by: Joffrey F --- compose/config/config_schema_v3.0.json | 7 ++++--- tests/acceptance/cli_test.py | 8 +++++++- tests/fixtures/v3-full/docker-compose.yml | 4 ++++ tests/integration/testcases.py | 7 +++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/compose/config/config_schema_v3.0.json b/compose/config/config_schema_v3.0.json index df321b20c..584b6ef5d 100644 --- a/compose/config/config_schema_v3.0.json +++ b/compose/config/config_schema_v3.0.json @@ -328,10 +328,11 @@ "type": ["boolean", "object"], "properties": { "name": {"type": "string"} - } - } + }, + "additionalProperties": false + }, + "labels": {"$ref": "#/definitions/list_or_dict"} }, - "labels": {"$ref": "#/definitions/list_or_dict"}, "additionalProperties": false }, diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index b9766226d..ce31dd18f 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -295,7 +295,13 @@ class CLITestCase(DockerClientTestCase): assert yaml.load(result.stdout) == { 'version': '3.0', 'networks': {}, - 'volumes': {}, + 'volumes': { + 'foobar': { + 'labels': { + 'com.docker.compose.test': 'true', + }, + }, + }, 'services': { 'web': { 'image': 'busybox', diff --git a/tests/fixtures/v3-full/docker-compose.yml b/tests/fixtures/v3-full/docker-compose.yml index b4d1b6422..a1661ab93 100644 --- a/tests/fixtures/v3-full/docker-compose.yml +++ b/tests/fixtures/v3-full/docker-compose.yml @@ -35,3 +35,7 @@ services: retries: 5 stop_grace_period: 20s +volumes: + foobar: + labels: + com.docker.compose.test: 'true' diff --git a/tests/integration/testcases.py b/tests/integration/testcases.py index f6bc402bf..230bd2d92 100644 --- a/tests/integration/testcases.py +++ b/tests/integration/testcases.py @@ -13,6 +13,7 @@ from compose.config.config import resolve_environment from compose.config.config import V1 from compose.config.config import V2_0 from compose.config.config import V2_1 +from compose.config.config import V3_0 from compose.config.environment import Environment from compose.const import API_VERSIONS from compose.const import LABEL_PROJECT @@ -36,13 +37,15 @@ def get_links(container): def engine_max_version(): if 'DOCKER_VERSION' not in os.environ: - return V2_1 + return V3_0 version = os.environ['DOCKER_VERSION'].partition('-')[0] if version_lt(version, '1.10'): return V1 elif version_lt(version, '1.12'): return V2_0 - return V2_1 + elif version_lt(version, '1.13'): + return V2_1 + return V3_0 def build_version_required_decorator(ignored_versions): From ce2219ec37c7a2a7eb747bf45d5a8a26ed6dff75 Mon Sep 17 00:00:00 2001 From: Daniel Nephin Date: Fri, 20 Jan 2017 12:55:59 -0500 Subject: [PATCH 3/8] Add missing network.internal to v3 schema. Signed-off-by: Daniel Nephin --- compose/config/config_schema_v3.0.json | 1 + 1 file changed, 1 insertion(+) diff --git a/compose/config/config_schema_v3.0.json b/compose/config/config_schema_v3.0.json index 584b6ef5d..fbcd8bb85 100644 --- a/compose/config/config_schema_v3.0.json +++ b/compose/config/config_schema_v3.0.json @@ -308,6 +308,7 @@ }, "additionalProperties": false }, + "internal": {"type": "boolean"}, "labels": {"$ref": "#/definitions/list_or_dict"} }, "additionalProperties": false From 10365278cc36ce974468fe042af1054c364f89d2 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Fri, 20 Jan 2017 15:05:53 -0800 Subject: [PATCH 4/8] Don't encode build context path on Windows Signed-off-by: Joffrey F --- compose/service.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compose/service.py b/compose/service.py index 20a40c684..724e05652 100644 --- a/compose/service.py +++ b/compose/service.py @@ -22,6 +22,7 @@ from .config import DOCKER_CONFIG_KEYS from .config import merge_environment from .config.types import VolumeSpec from .const import DEFAULT_TIMEOUT +from .const import IS_WINDOWS_PLATFORM from .const import LABEL_CONFIG_HASH from .const import LABEL_CONTAINER_NUMBER from .const import LABEL_ONE_OFF @@ -769,9 +770,9 @@ class Service(object): build_opts = self.options.get('build', {}) path = build_opts.get('context') - # python2 os.path() doesn't support unicode, so we need to encode it to - # a byte string - if not six.PY3: + # python2 os.stat() doesn't support unicode on some UNIX, so we + # encode it to a bytestring to be safe + if not six.PY3 and not IS_WINDOWS_PLATFORM: path = path.encode('utf8') build_output = self.client.build( From d454a1d3fb1d7138d2b0b30f6f44fb520ce9e248 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Tue, 24 Jan 2017 15:02:27 -0800 Subject: [PATCH 5/8] Detect conflicting version of the docker python SDK and prevent execution until issue is fixed Signed-off-by: Joffrey F --- compose/cli/main.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/compose/cli/main.py b/compose/cli/main.py index c25ccbfa4..db068272a 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -14,6 +14,30 @@ from distutils.spawn import find_executable from inspect import getdoc from operator import attrgetter + +# Attempt to detect https://github.com/docker/compose/issues/4344 +try: + # A regular import statement causes PyInstaller to freak out while + # trying to load pip. This way it is simply ignored. + pip = __import__('pip') + pip_packages = pip.get_installed_distributions() + if 'docker-py' in [pkg.project_name for pkg in pip_packages]: + from .colors import red + print( + red('ERROR:'), + "Dependency conflict: an older version of the 'docker-py' package " + "is polluting the namespace. " + "Run the following command to remedy the issue:\n" + "pip uninstall docker docker-py; pip install docker", + file=sys.stderr + ) + sys.exit(1) +except ImportError: + # pip is not available, which indicates it's probably the binary + # distribution of Compose which is not affected + pass + + from . import errors from . import signals from .. import __version__ From 507e0d7a64ebe20249eb30b471c3d392d10d7a9b Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 25 Jan 2017 18:00:09 -0800 Subject: [PATCH 6/8] Convert time data back to string values when serializing config Signed-off-by: Joffrey F --- compose/config/serialize.py | 29 +++++++++++++++++++++++++ tests/acceptance/cli_test.py | 4 ++-- tests/unit/config/config_test.py | 36 ++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/compose/config/serialize.py b/compose/config/serialize.py index 05ac0d60d..edf553537 100644 --- a/compose/config/serialize.py +++ b/compose/config/serialize.py @@ -52,6 +52,25 @@ def serialize_config(config): width=80) +def serialize_ns_time_value(value): + result = (value, 'ns') + table = [ + (1000., 'us'), + (1000., 'ms'), + (1000., 's'), + (60., 'm'), + (60., 'h') + ] + for stage in table: + tmp = value / stage[0] + if tmp == int(value / stage[0]): + value = tmp + result = (int(value), stage[1]) + else: + break + return '{0}{1}'.format(*result) + + def denormalize_service_dict(service_dict, version): service_dict = service_dict.copy() @@ -68,4 +87,14 @@ def denormalize_service_dict(service_dict, version): svc for svc in service_dict['depends_on'].keys() ]) + if 'healthcheck' in service_dict: + if 'interval' in service_dict['healthcheck']: + service_dict['healthcheck']['interval'] = serialize_ns_time_value( + service_dict['healthcheck']['interval'] + ) + if 'timeout' in service_dict['healthcheck']: + service_dict['healthcheck']['timeout'] = serialize_ns_time_value( + service_dict['healthcheck']['timeout'] + ) + return service_dict diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index ce31dd18f..7a2b7fb57 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -339,8 +339,8 @@ class CLITestCase(DockerClientTestCase): 'healthcheck': { 'test': 'cat /etc/passwd', - 'interval': 10000000000, - 'timeout': 1000000000, + 'interval': '10s', + 'timeout': '1s', 'retries': 5, }, diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index ab8bfcfcc..d7947a4e8 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -23,6 +23,7 @@ from compose.config.environment import Environment from compose.config.errors import ConfigurationError from compose.config.errors import VERSION_EXPLANATION from compose.config.serialize import denormalize_service_dict +from compose.config.serialize import serialize_ns_time_value from compose.config.types import VolumeSpec from compose.const import IS_WINDOWS_PLATFORM from compose.utils import nanoseconds_from_time_seconds @@ -3334,3 +3335,38 @@ class SerializeTest(unittest.TestCase): } assert denormalize_service_dict(service_dict, V2_1) == service_dict + + def test_serialize_time(self): + data = { + 9: '9ns', + 9000: '9us', + 9000000: '9ms', + 90000000: '90ms', + 900000000: '900ms', + 999999999: '999999999ns', + 1000000000: '1s', + 60000000000: '1m', + 60000000001: '60000000001ns', + 9000000000000: '150m', + 90000000000000: '25h', + } + + for k, v in data.items(): + assert serialize_ns_time_value(k) == v + + def test_denormalize_healthcheck(self): + service_dict = { + 'image': 'test', + 'healthcheck': { + 'test': 'exit 1', + 'interval': '1m40s', + 'timeout': '30s', + 'retries': 5 + } + } + processed_service = config.process_service(config.ServiceConfig( + '.', 'test', 'test', service_dict + )) + denormalized_service = denormalize_service_dict(processed_service, V2_1) + assert denormalized_service['healthcheck']['interval'] == '100s' + assert denormalized_service['healthcheck']['timeout'] == '30s' From 586637b6a885ce6e0117ab83416e203621a97c3f Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Mon, 30 Jan 2017 16:26:35 -0800 Subject: [PATCH 7/8] Bump docker SDK version Signed-off-by: Joffrey F --- requirements.txt | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 4b7c7b760..3b06bff45 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ PyYAML==3.11 backports.ssl-match-hostname==3.5.0.1; python_version < '3' cached-property==1.2.0 colorama==0.3.7 -docker==2.0.1 +docker==2.0.2 dockerpty==0.4.1 docopt==0.6.1 enum34==1.0.4; python_version < '3.4' diff --git a/setup.py b/setup.py index 2f2ba7429..0b1d4e08f 100644 --- a/setup.py +++ b/setup.py @@ -37,7 +37,7 @@ install_requires = [ 'requests >= 2.6.1, != 2.11.0, < 2.12', 'texttable >= 0.8.1, < 0.9', 'websocket-client >= 0.32.0, < 1.0', - 'docker >= 2.0.1, < 3.0', + 'docker >= 2.0.2, < 3.0', 'dockerpty >= 0.4.1, < 0.5', 'six >= 1.3.0, < 2', 'jsonschema >= 2.5.1, < 3', From b25273892d7c03d8bfa25239b1fc5d5a2e43353d Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Mon, 30 Jan 2017 16:20:59 -0800 Subject: [PATCH 8/8] Bump 1.10.1 Signed-off-by: Joffrey F --- CHANGELOG.md | 23 +++++++++++++++++++++++ compose/__init__.py | 2 +- script/run/run.sh | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f14bc99e8..768e6c49a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,29 @@ Change log ========== +1.10.1 (2017-02-01) +------------------ + +### Bugfixes + +- Fixed an issue where presence of older versions of the docker-py + package would cause unexpected crashes while running Compose + +- Fixed an issue where healthcheck dependencies would be lost when + using multiple compose files for a project + +- Fixed a few issues that made the output of the `config` command + invalid + +- Fixed an issue where adding volume labels to v3 Compose files would + result in an error + +- Fixed an issue on Windows where build context paths containing unicode + characters were being improperly encoded + +- Fixed a bug where Compose would occasionally crash while streaming logs + when containers would stop or restart + 1.10.0 (2017-01-18) ------------------- diff --git a/compose/__init__.py b/compose/__init__.py index 4c7da2aca..6d2e41a20 100644 --- a/compose/__init__.py +++ b/compose/__init__.py @@ -1,4 +1,4 @@ from __future__ import absolute_import from __future__ import unicode_literals -__version__ = '1.10.0' +__version__ = '1.10.1' diff --git a/script/run/run.sh b/script/run/run.sh index f965b9f0a..c43055e7b 100755 --- a/script/run/run.sh +++ b/script/run/run.sh @@ -15,7 +15,7 @@ set -e -VERSION="1.10.0" +VERSION="1.10.1" IMAGE="docker/compose:$VERSION"