Răsfoiți Sursa

Merge pull request #4505 from docker/bump-1.11.2

Bump 1.11.2
Joffrey F 8 ani în urmă
părinte
comite
9a46e62873

+ 21 - 0
CHANGELOG.md

@@ -1,6 +1,27 @@
 Change log
 ==========
 
+1.11.2 (2017-02-17)
+-------------------
+
+### Bugfixes
+
+- Fixed a bug that was preventing secrets configuration from being
+  loaded properly
+
+- Fixed a bug where the `docker-compose config` command would fail
+  if the config file contained secrets definitions
+
+- Fixed an issue where Compose on some linux distributions would
+  pick up and load an outdated version of the requests library
+
+- Fixed an issue where socket-type files inside a build folder
+  would cause `docker-compose` to crash when trying to build that
+  service
+
+- Fixed an issue where recursive wildcard patterns `**` were not being
+  recognized in `.dockerignore` files.
+
 1.11.1 (2017-02-09)
 -------------------
 

+ 1 - 1
compose/__init__.py

@@ -1,4 +1,4 @@
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
-__version__ = '1.11.1'
+__version__ = '1.11.2'

+ 37 - 0
compose/cli/__init__.py

@@ -0,0 +1,37 @@
+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

+ 0 - 24
compose/cli/main.py

@@ -14,30 +14,6 @@ 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__

+ 5 - 0
compose/config/config.py

@@ -763,6 +763,11 @@ 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

+ 3 - 0
compose/config/serialize.py

@@ -102,4 +102,7 @@ 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

+ 5 - 0
compose/config/types.py

@@ -253,3 +253,8 @@ 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]
+        )

+ 1 - 1
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.2
+docker==2.1.0
 dockerpty==0.4.1
 docopt==0.6.1
 enum34==1.0.4; python_version < '3.4'

+ 1 - 1
script/run/run.sh

@@ -15,7 +15,7 @@
 
 set -e
 
-VERSION="1.11.1"
+VERSION="1.11.2"
 IMAGE="docker/compose:$VERSION"
 
 

+ 5 - 7
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
@@ -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.2, < 3.0',
+    'docker >= 2.1.0, < 3.0',
     'dockerpty >= 0.4.1, < 0.5',
     'six >= 1.3.0, < 2',
     'jsonschema >= 2.5.1, < 3',
@@ -64,11 +64,9 @@ try:
         for key, value in extras_require.items():
             if key.startswith(':') and pkg_resources.evaluate_marker(key[1:]):
                 install_requires.extend(value)
-except Exception:
-    logging.getLogger(__name__).exception(
-        'Failed to compute platform dependencies. All dependencies will be '
-        'installed as a result.'
-    )
+except Exception as e:
+    print("Failed to compute platform dependencies: {}. ".format(e) +
+          "All dependencies will be installed as a result.", file=sys.stderr)
     for key, value in extras_require.items():
         if key.startswith(':'):
             install_requires.extend(value)

+ 143 - 0
tests/unit/config/config_test.py

@@ -13,6 +13,7 @@ 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
@@ -53,6 +54,10 @@ 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(
@@ -1770,6 +1775,38 @@ 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',
@@ -1849,6 +1886,91 @@ 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):
@@ -3405,3 +3527,24 @@ 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,
+            },
+        ])