diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c07e9ca..e9f466b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,14 @@ Change log ========== +1.11.1 (2017-02-09) +------------------- + +### Bugfixes + +- Fixed a bug where the 3.1 file format was not being recognized as valid + by the Compose parser + 1.11.0 (2017-02-08) ------------------- @@ -620,7 +628,7 @@ Bug Fixes: if at least one container is using the network. - When printings logs during `up` or `logs`, flush the output buffer after - each line to prevent buffering issues from hiding logs. + each line to prevent buffering issues from hideing logs. - Recreate a container if one of its dependencies is being created. Previously a container was only recreated if it's dependencies already diff --git a/README.md b/README.md index 35a10b90..5cf69b05 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ A `docker-compose.yml` looks like this: image: redis For more information about the Compose file, see the -[Compose file reference](https://github.com/docker/docker.github.io/blob/master/compose/compose-file/compose-versioning.md) +[Compose file reference](https://github.com/docker/docker.github.io/blob/master/compose/compose-file.md) Compose has commands for managing the whole lifecycle of your application: diff --git a/compose/__init__.py b/compose/__init__.py index b2ca86f8..8caa1fd2 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.12.0dev' +__version__ = '1.11.1' diff --git a/compose/bundle.py b/compose/bundle.py index 505ce91f..854cc799 100644 --- a/compose/bundle.py +++ b/compose/bundle.py @@ -202,7 +202,7 @@ def convert_service_to_bundle(name, service_dict, image_digest): return container_config -# See https://github.com/docker/swarmkit/blob/agent/exec/container/container.go#L95 +# See https://github.com/docker/swarmkit/blob//agent/exec/container/container.go#L95 def set_command_and_args(config, entrypoint, command): if isinstance(entrypoint, six.string_types): entrypoint = split_command(entrypoint) diff --git a/compose/cli/__init__.py b/compose/cli/__init__.py index c5db4455..e69de29b 100644 --- a/compose/cli/__init__.py +++ b/compose/cli/__init__.py @@ -1,37 +0,0 @@ -from __future__ import absolute_import -from __future__ import print_function -from __future__ import unicode_literals - -import subprocess -import sys - -# Attempt to detect https://github.com/docker/compose/issues/4344 -try: - # We don't try importing pip because it messes with package imports - # on some Linux distros (Ubuntu, Fedora) - # https://github.com/docker/compose/issues/4425 - # https://github.com/docker/compose/issues/4481 - # https://github.com/pypa/pip/blob/master/pip/_vendor/__init__.py - s_cmd = subprocess.Popen( - ['pip', 'freeze'], stderr=subprocess.PIPE, stdout=subprocess.PIPE - ) - packages = s_cmd.communicate()[0].splitlines() - dockerpy_installed = len( - list(filter(lambda p: p.startswith(b'docker-py=='), packages)) - ) > 0 - if dockerpy_installed: - 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 OSError: - # pip command is not available, which indicates it's probably the binary - # distribution of Compose which is not affected - pass diff --git a/compose/cli/main.py b/compose/cli/main.py index 51ba36a0..e2ebce48 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__ diff --git a/compose/config/config.py b/compose/config/config.py index 4c9cf423..09a717be 100644 --- a/compose/config/config.py +++ b/compose/config/config.py @@ -763,11 +763,6 @@ def finalize_service(service_config, service_names, version, environment): if 'restart' in service_dict: service_dict['restart'] = parse_restart_spec(service_dict['restart']) - if 'secrets' in service_dict: - service_dict['secrets'] = [ - types.ServiceSecret.parse(s) for s in service_dict['secrets'] - ] - normalize_build(service_dict, service_config.working_dir, environment) service_dict['name'] = service_config.name diff --git a/compose/config/serialize.py b/compose/config/serialize.py index 46d283f0..3745de82 100644 --- a/compose/config/serialize.py +++ b/compose/config/serialize.py @@ -102,7 +102,4 @@ def denormalize_service_dict(service_dict, version): service_dict['healthcheck']['timeout'] ) - if 'secrets' in service_dict: - service_dict['secrets'] = map(lambda s: s.repr(), service_dict['secrets']) - return service_dict diff --git a/compose/config/types.py b/compose/config/types.py index 811e6c1f..17d5c8b3 100644 --- a/compose/config/types.py +++ b/compose/config/types.py @@ -253,8 +253,3 @@ class ServiceSecret(namedtuple('_ServiceSecret', 'source target uid gid mode')): @property def merge_field(self): return self.source - - def repr(self): - return dict( - [(k, v) for k, v in self._asdict().items() if v is not None] - ) diff --git a/compose/project.py b/compose/project.py index 133071e7..0330ab80 100644 --- a/compose/project.py +++ b/compose/project.py @@ -365,7 +365,7 @@ class Project(object): # TODO: get labels from the API v1.22 , see github issue 2618 try: - # this can fail if the container has been removed + # this can fail if the conatiner has been removed container = Container.from_id(self.client, event['id']) except APIError: continue diff --git a/script/release/utils.sh b/script/release/utils.sh index 321c1fb7..b4e5a2e6 100644 --- a/script/release/utils.sh +++ b/script/release/utils.sh @@ -1,6 +1,6 @@ #!/bin/bash # -# Util functions for release scripts +# Util functions for release scritps # set -e diff --git a/script/run/run.sh b/script/run/run.sh index 4e173894..77ee0a01 100755 --- a/script/run/run.sh +++ b/script/run/run.sh @@ -15,7 +15,7 @@ set -e -VERSION="1.12.0dev" +VERSION="1.11.1" IMAGE="docker/compose:$VERSION" diff --git a/setup.py b/setup.py index eafbc356..0b1d4e08 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,10 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- from __future__ import absolute_import -from __future__ import print_function from __future__ import unicode_literals import codecs +import logging import os import re import sys @@ -64,9 +64,11 @@ try: for key, value in extras_require.items(): if key.startswith(':') and pkg_resources.evaluate_marker(key[1:]): install_requires.extend(value) -except Exception as e: - print("Failed to compute platform dependencies: {}. ".format(e) + - "All dependencies will be installed as a result.", file=sys.stderr) +except Exception: + logging.getLogger(__name__).exception( + 'Failed to compute platform dependencies. All dependencies will be ' + 'installed as a result.' + ) for key, value in extras_require.items(): if key.startswith(':'): install_requires.extend(value) diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index 8366ca75..160e1913 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -1234,7 +1234,7 @@ class CLITestCase(DockerClientTestCase): container = service.containers(stopped=True, one_off=OneOffFilter.only)[0] self.assertEqual(user, container.get('Config.User')) - def test_run_service_with_environment_overridden(self): + def test_run_service_with_environement_overridden(self): name = 'service' self.base_dir = 'tests/fixtures/environment-composefile' self.dispatch([ @@ -1246,9 +1246,9 @@ class CLITestCase(DockerClientTestCase): ]) service = self.project.get_service(name) container = service.containers(stopped=True, one_off=OneOffFilter.only)[0] - # env overridden + # env overriden self.assertEqual('notbar', container.environment['foo']) - # keep environment from yaml + # keep environement from yaml self.assertEqual('world', container.environment['hello']) # added option from command line self.assertEqual('beta', container.environment['alpha']) @@ -1293,7 +1293,7 @@ class CLITestCase(DockerClientTestCase): self.assertEqual(port_range[0], "0.0.0.0:49153") self.assertEqual(port_range[1], "0.0.0.0:49154") - def test_run_service_with_explicitly_mapped_ports(self): + def test_run_service_with_explicitly_maped_ports(self): # create one off container self.base_dir = 'tests/fixtures/ports-composefile' self.dispatch(['run', '-d', '-p', '30000:3000', '--publish', '30001:3001', 'simple']) @@ -1310,7 +1310,7 @@ class CLITestCase(DockerClientTestCase): self.assertEqual(port_short, "0.0.0.0:30000") self.assertEqual(port_full, "0.0.0.0:30001") - def test_run_service_with_explicitly_mapped_ip_ports(self): + def test_run_service_with_explicitly_maped_ip_ports(self): # create one off container self.base_dir = 'tests/fixtures/ports-composefile' self.dispatch([ diff --git a/tests/unit/cli_test.py b/tests/unit/cli_test.py index 317650cb..f9b60bff 100644 --- a/tests/unit/cli_test.py +++ b/tests/unit/cli_test.py @@ -184,7 +184,7 @@ class CLITestCase(unittest.TestCase): mock_client.create_host_config.call_args[1].get('restart_policy') ) - def test_command_manual_and_service_ports_together(self): + def test_command_manula_and_service_ports_together(self): project = Project.from_config( name='composetest', client=None, diff --git a/tests/unit/config/config_test.py b/tests/unit/config/config_test.py index c26272d9..ef57bb57 100644 --- a/tests/unit/config/config_test.py +++ b/tests/unit/config/config_test.py @@ -13,7 +13,6 @@ import pytest from ...helpers import build_config_details from compose.config import config -from compose.config import types from compose.config.config import resolve_build_args from compose.config.config import resolve_environment from compose.config.config import V1 @@ -54,10 +53,6 @@ def service_sort(services): return sorted(services, key=itemgetter('name')) -def secret_sort(secrets): - return sorted(secrets, key=itemgetter('source')) - - class ConfigTest(unittest.TestCase): def test_load(self): service_dicts = config.load( @@ -1775,38 +1770,6 @@ class ConfigTest(unittest.TestCase): 'labels': {'com.docker.compose.test': 'yes'} } - def test_merge_different_secrets(self): - base = { - 'image': 'busybox', - 'secrets': [ - {'source': 'src.txt'} - ] - } - override = {'secrets': ['other-src.txt']} - - actual = config.merge_service_dicts(base, override, V3_1) - assert secret_sort(actual['secrets']) == secret_sort([ - {'source': 'src.txt'}, - {'source': 'other-src.txt'} - ]) - - def test_merge_secrets_override(self): - base = { - 'image': 'busybox', - 'secrets': ['src.txt'], - } - override = { - 'secrets': [ - { - 'source': 'src.txt', - 'target': 'data.txt', - 'mode': 0o400 - } - ] - } - actual = config.merge_service_dicts(base, override, V3_1) - assert actual['secrets'] == override['secrets'] - def test_external_volume_config(self): config_details = build_config_details({ 'version': '2', @@ -1886,91 +1849,6 @@ class ConfigTest(unittest.TestCase): config.load(config_details) assert 'has neither an image nor a build context' in exc.exconly() - def test_load_secrets(self): - base_file = config.ConfigFile( - 'base.yaml', - { - 'version': '3.1', - 'services': { - 'web': { - 'image': 'example/web', - 'secrets': [ - 'one', - { - 'source': 'source', - 'target': 'target', - 'uid': '100', - 'gid': '200', - 'mode': 0o777, - }, - ], - }, - }, - 'secrets': { - 'one': {'file': 'secret.txt'}, - }, - }) - details = config.ConfigDetails('.', [base_file]) - service_dicts = config.load(details).services - expected = [ - { - 'name': 'web', - 'image': 'example/web', - 'secrets': [ - types.ServiceSecret('one', None, None, None, None), - types.ServiceSecret('source', 'target', '100', '200', 0o777), - ], - }, - ] - assert service_sort(service_dicts) == service_sort(expected) - - def test_load_secrets_multi_file(self): - base_file = config.ConfigFile( - 'base.yaml', - { - 'version': '3.1', - 'services': { - 'web': { - 'image': 'example/web', - 'secrets': ['one'], - }, - }, - 'secrets': { - 'one': {'file': 'secret.txt'}, - }, - }) - override_file = config.ConfigFile( - 'base.yaml', - { - 'version': '3.1', - 'services': { - 'web': { - 'secrets': [ - { - 'source': 'source', - 'target': 'target', - 'uid': '100', - 'gid': '200', - 'mode': 0o777, - }, - ], - }, - }, - }) - details = config.ConfigDetails('.', [base_file, override_file]) - service_dicts = config.load(details).services - expected = [ - { - 'name': 'web', - 'image': 'example/web', - 'secrets': [ - types.ServiceSecret('one', None, None, None, None), - types.ServiceSecret('source', 'target', '100', '200', 0o777), - ], - }, - ] - assert service_sort(service_dicts) == service_sort(expected) - class NetworkModeTest(unittest.TestCase): def test_network_mode_standard(self): @@ -3527,24 +3405,3 @@ class SerializeTest(unittest.TestCase): denormalized_service = denormalize_service_dict(processed_service, V2_1) assert denormalized_service['healthcheck']['interval'] == '100s' assert denormalized_service['healthcheck']['timeout'] == '30s' - - def test_denormalize_secrets(self): - service_dict = { - 'name': 'web', - 'image': 'example/web', - 'secrets': [ - types.ServiceSecret('one', None, None, None, None), - types.ServiceSecret('source', 'target', '100', '200', 0o777), - ], - } - denormalized_service = denormalize_service_dict(service_dict, V3_1) - assert secret_sort(denormalized_service['secrets']) == secret_sort([ - {'source': 'one'}, - { - 'source': 'source', - 'target': 'target', - 'uid': '100', - 'gid': '200', - 'mode': 0o777, - }, - ])