Преглед изворни кода

Merge pull request #4785 from docker/bump-1.13.0

Bump 1.13.0
Joffrey F пре 8 година
родитељ
комит
10267a83dc

+ 10 - 1
CHANGELOG.md

@@ -1,7 +1,7 @@
 Change log
 ==========
 
-1.13.0 (2017-05-01)
+1.13.0 (2017-05-02)
 -------------------
 
 ### Breaking changes
@@ -47,6 +47,15 @@ Change log
 - Fixed an issue where paths containing unicode characters passed via the `-f`
   flag were causing Compose to crash
 
+- Fixed an issue where the output of `docker-compose config` would be invalid
+  if the Compose file contained external secrets
+
+- Fixed a bug where using `--exit-code-from` with `up` would fail if Compose
+  was installed in a Python 3 environment
+
+- Fixed a bug where recreating containers using a combination of `tmpfs` and
+  `volumes` would result in an invalid config state
+
 
 1.12.0 (2017-04-04)
 -------------------

+ 1 - 1
compose/__init__.py

@@ -1,4 +1,4 @@
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
-__version__ = '1.13.0-rc1'
+__version__ = '1.13.0'

+ 2 - 2
compose/cli/main.py

@@ -944,9 +944,9 @@ class TopLevelCommand(object):
 
                 exit_code = 0
                 if exit_value_from:
-                    candidates = filter(
+                    candidates = list(filter(
                         lambda c: c.service == exit_value_from,
-                        attached_containers)
+                        attached_containers))
                     if not candidates:
                         log.error(
                             'No containers matching the spec "{0}" '

+ 7 - 0
compose/config/config_schema_v2.2.json

@@ -300,6 +300,13 @@
                 "driver": {"type": "string"},
                 "config": {
                     "type": "array"
+                },
+                "options": {
+                  "type": "object",
+                  "patternProperties": {
+                    "^.+$": {"type": "string"}
+                  },
+                  "additionalProperties": false
                 }
             },
             "additionalProperties": false

+ 4 - 1
compose/config/serialize.py

@@ -51,7 +51,10 @@ def denormalize_config(config, image_digests=None):
             del vol_conf['external_name']
 
     if config.version in (V3_1, V3_2):
-        result['secrets'] = config.secrets
+        result['secrets'] = config.secrets.copy()
+        for secret_name, secret_conf in result['secrets'].items():
+            if 'external_name' in secret_conf:
+                del secret_conf['external_name']
     return result
 
 

+ 2 - 2
compose/network.py

@@ -158,8 +158,8 @@ def check_remote_ipam_config(remote, local):
             if sorted(lc.get('AuxiliaryAddresses')) != sorted(rc.get('AuxiliaryAddresses')):
                 raise NetworkConfigChangedError(local.full_name, 'IPAM config aux_addresses')
 
-    remote_opts = remote_ipam.get('Options', {})
-    local_opts = local.ipam.get('options', {})
+    remote_opts = remote_ipam.get('Options') or {}
+    local_opts = local.ipam.get('options') or {}
     for k in set.union(set(remote_opts.keys()), set(local_opts.keys())):
         if remote_opts.get(k) != local_opts.get(k):
             raise NetworkConfigChangedError(local.full_name, 'IPAM option "{}"'.format(k))

+ 9 - 3
compose/service.py

@@ -16,6 +16,7 @@ from docker.errors import NotFound
 from docker.types import LogConfig
 from docker.utils.ports import build_port_bindings
 from docker.utils.ports import split_port
+from docker.utils.utils import convert_tmpfs_mounts
 
 from . import __version__
 from . import const
@@ -744,6 +745,7 @@ class Service(object):
 
         binds, affinity = merge_volume_bindings(
             container_options.get('volumes') or [],
+            self.options.get('tmpfs') or [],
             previous_container)
         override_options['binds'] = binds
         container_options['environment'].update(affinity)
@@ -1126,7 +1128,7 @@ def parse_repository_tag(repo_path):
 # Volumes
 
 
-def merge_volume_bindings(volumes, previous_container):
+def merge_volume_bindings(volumes, tmpfs, previous_container):
     """Return a list of volume bindings for a container. Container data volumes
     are replaced by those from the previous container.
     """
@@ -1138,7 +1140,7 @@ def merge_volume_bindings(volumes, previous_container):
         if volume.external)
 
     if previous_container:
-        old_volumes = get_container_data_volumes(previous_container, volumes)
+        old_volumes = get_container_data_volumes(previous_container, volumes, tmpfs)
         warn_on_masked_volume(volumes, old_volumes, previous_container.service)
         volume_bindings.update(
             build_volume_binding(volume) for volume in old_volumes)
@@ -1149,7 +1151,7 @@ def merge_volume_bindings(volumes, previous_container):
     return list(volume_bindings.values()), affinity
 
 
-def get_container_data_volumes(container, volumes_option):
+def get_container_data_volumes(container, volumes_option, tmpfs_option):
     """Find the container data volumes that are in `volumes_option`, and return
     a mapping of volume bindings for those volumes.
     """
@@ -1172,6 +1174,10 @@ def get_container_data_volumes(container, volumes_option):
         if volume.external:
             continue
 
+        # Attempting to rebind tmpfs volumes breaks: https://github.com/docker/compose/issues/4751
+        if volume.internal in convert_tmpfs_mounts(tmpfs_option).keys():
+            continue
+
         mount = container_mounts.get(volume.internal)
 
         # New volume, doesn't exist in the old container

+ 10 - 1
contrib/completion/bash/docker-compose

@@ -498,10 +498,19 @@ _docker_compose_unpause() {
 
 _docker_compose_up() {
 	case "$prev" in
+		=)
+			COMPREPLY=("$cur")
+			return
+			;;
 		--exit-code-from)
 			__docker_compose_services_all
 			return
 			;;
+		--scale)
+			COMPREPLY=( $(compgen -S "=" -W "$(___docker_compose_all_services_in_compose_file)" -- "$cur") )
+			__docker_compose_nospace
+			return
+			;;
 		--timeout|-t)
 			return
 			;;
@@ -509,7 +518,7 @@ _docker_compose_up() {
 
 	case "$cur" in
 		-*)
-			COMPREPLY=( $( compgen -W "--abort-on-container-exit --build -d --exit-code-from --force-recreate --help --no-build --no-color --no-deps --no-recreate --timeout -t --remove-orphans" -- "$cur" ) )
+			COMPREPLY=( $( compgen -W "--abort-on-container-exit --build -d --exit-code-from --force-recreate --help --no-build --no-color --no-deps --no-recreate --remove-orphans --scale --timeout -t" -- "$cur" ) )
 			;;
 		*)
 			__docker_compose_services_all

+ 1 - 1
script/run/run.sh

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

+ 18 - 0
tests/integration/project_test.py

@@ -552,6 +552,24 @@ class ProjectTest(DockerClientTestCase):
         self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
         self.assertEqual(len(project.get_service('console').containers()), 0)
 
+    def test_project_up_recreate_with_tmpfs_volume(self):
+        # https://github.com/docker/compose/issues/4751
+        project = Project.from_config(
+            name='composetest',
+            config_data=load_config({
+                'version': '2.1',
+                'services': {
+                    'foo': {
+                        'image': 'busybox:latest',
+                        'tmpfs': ['/dev/shm'],
+                        'volumes': ['/dev/shm']
+                    }
+                }
+            }), client=self.client
+        )
+        project.up()
+        project.up(strategy=ConvergenceStrategy.always)
+
     def test_unscale_after_restart(self):
         web = self.create_service('web')
         project = Project('composetest', [web], self.client)

+ 3 - 1
tests/unit/config/config_test.py

@@ -3825,7 +3825,8 @@ class SerializeTest(unittest.TestCase):
         }
         secrets_dict = {
             'one': {'file': '/one.txt'},
-            'source': {'file': '/source.pem'}
+            'source': {'file': '/source.pem'},
+            'two': {'external': True},
         }
         config_dict = config.load(build_config_details({
             'version': '3.1',
@@ -3837,6 +3838,7 @@ class SerializeTest(unittest.TestCase):
         serialized_service = serialized_config['services']['web']
         assert secret_sort(serialized_service['secrets']) == secret_sort(service_dict['secrets'])
         assert 'secrets' in serialized_config
+        assert serialized_config['secrets']['two'] == secrets_dict['two']
 
     def test_serialize_ports(self):
         config_dict = config.Config(version='2.0', services=[

+ 38 - 0
tests/unit/network_test.py

@@ -100,6 +100,44 @@ class NetworkTest(unittest.TestCase):
             {'Driver': 'overlay', 'Options': None}, net
         )
 
+    def test_check_remote_network_config_null_remote_ipam_options(self):
+        ipam_config = {
+            'driver': 'default',
+            'config': [
+                {'subnet': '172.0.0.1/16', },
+                {
+                    'subnet': '156.0.0.1/25',
+                    'gateway': '156.0.0.1',
+                    'aux_addresses': ['11.0.0.1', '24.25.26.27'],
+                    'ip_range': '156.0.0.1-254'
+                }
+            ]
+        }
+        net = Network(
+            None, 'compose_test', 'net1', 'bridge', ipam=ipam_config,
+        )
+
+        check_remote_network_config(
+            {
+                'Driver': 'bridge',
+                'Attachable': True,
+                'IPAM': {
+                    'Driver': 'default',
+                    'Config': [{
+                        'Subnet': '156.0.0.1/25',
+                        'Gateway': '156.0.0.1',
+                        'AuxiliaryAddresses': ['24.25.26.27', '11.0.0.1'],
+                        'IPRange': '156.0.0.1-254'
+                    }, {
+                        'Subnet': '172.0.0.1/16',
+                        'Gateway': '172.0.0.1'
+                    }],
+                    'Options': None
+                },
+            },
+            net
+        )
+
     def test_check_remote_network_labels_mismatch(self):
         net = Network(None, 'compose_test', 'net1', 'overlay', labels={
             'com.project.touhou.character': 'sakuya.izayoi'

+ 10 - 6
tests/unit/service_test.py

@@ -858,6 +858,7 @@ class ServiceVolumesTest(unittest.TestCase):
             '/new/volume',
             '/existing/volume',
             'named:/named/vol',
+            '/dev/tmpfs'
         ]]
 
         self.mock_client.inspect_image.return_value = {
@@ -903,15 +904,18 @@ class ServiceVolumesTest(unittest.TestCase):
             VolumeSpec.parse('imagedata:/mnt/image/data:rw'),
         ]
 
-        volumes = get_container_data_volumes(container, options)
+        volumes = get_container_data_volumes(container, options, ['/dev/tmpfs'])
         assert sorted(volumes) == sorted(expected)
 
     def test_merge_volume_bindings(self):
         options = [
-            VolumeSpec.parse('/host/volume:/host/volume:ro', True),
-            VolumeSpec.parse('/host/rw/volume:/host/rw/volume', True),
-            VolumeSpec.parse('/new/volume', True),
-            VolumeSpec.parse('/existing/volume', True),
+            VolumeSpec.parse(v, True) for v in [
+                '/host/volume:/host/volume:ro',
+                '/host/rw/volume:/host/rw/volume',
+                '/new/volume',
+                '/existing/volume',
+                '/dev/tmpfs'
+            ]
         ]
 
         self.mock_client.inspect_image.return_value = {
@@ -936,7 +940,7 @@ class ServiceVolumesTest(unittest.TestCase):
             'existingvolume:/existing/volume:rw',
         ]
 
-        binds, affinity = merge_volume_bindings(options, previous_container)
+        binds, affinity = merge_volume_bindings(options, ['/dev/tmpfs'], previous_container)
         assert sorted(binds) == sorted(expected)
         assert affinity == {'affinity:container': '=cdefab'}