Pārlūkot izejas kodu

Merge pull request #5479 from docker/bump-1.18.0

Bump 1.18.0
Joffrey F 7 gadi atpakaļ
vecāks
revīzija
cae6bf9557

+ 10 - 9
CHANGELOG.md

@@ -9,7 +9,7 @@ Change log
 #### Compose file version 3.5
 
 - Introduced version 3.5 of the `docker-compose.yml` specification.
-  This version requires to be used with Docker Engine 17.06.0 or above
+  This version requires Docker Engine 17.06.0 or above
 
 - Added support for the `shm_size` parameter in build configurations
 
@@ -21,20 +21,15 @@ Change log
 
 - Added support for `extra_hosts` in build configuration
 
-- Added support for the
-  [long syntax](https://docs.docker.com/compose/compose-file/#long-syntax-3)
-  for volume entries, as previously introduced in the 3.2 format.
-  Note that using this syntax will create
-  [mounts](https://docs.docker.com/engine/admin/volumes/bind-mounts/)
-  instead of volumes.
+- Added support for the [long syntax](https://docs.docker.com/compose/compose-file/#long-syntax-3) for volume entries, as previously introduced in the 3.2 format.
+  Note that using this syntax will create [mounts](https://docs.docker.com/engine/admin/volumes/bind-mounts/) instead of volumes.
 
 #### Compose file version 2.1 and up
 
 - Added support for the `oom_kill_disable` parameter in service definitions
   (2.x only)
 
-- Added support for custom names for network, secret and config definitions
-  (2.x only)
+- Added support for custom names for network definitions (2.x only)
 
 
 #### All formats
@@ -62,6 +57,12 @@ Change log
 - Fixed an issue with CLI logging causing duplicate messages and inelegant
   output to occur
 
+- Fixed an issue that caused `stop_grace_period` to be ignored when using
+  multiple Compose files
+
+- Fixed a bug that caused `docker-compose images` to crash when using
+  untagged images
+
 - Fixed a bug where the valid `${VAR:-}` syntax would cause Compose to
   error out
 

+ 1 - 1
compose/__init__.py

@@ -1,4 +1,4 @@
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
-__version__ = '1.18.0-rc2'
+__version__ = '1.18.0'

+ 15 - 12
compose/cli/main.py

@@ -365,17 +365,17 @@ class TopLevelCommand(object):
         Usage: down [options]
 
         Options:
-            --rmi type          Remove images. Type must be one of:
-                                'all': Remove all images used by any service.
-                                'local': Remove only images that don't have a custom tag
-                                set by the `image` field.
-            -v, --volumes       Remove named volumes declared in the `volumes` section
-                                of the Compose file and anonymous volumes
-                                attached to containers.
-            --remove-orphans    Remove containers for services not defined in the
-                                Compose file
-            -t, --timeout TIMEOUT      Specify a shutdown timeout in seconds.
-                                     (default: 10)
+            --rmi type              Remove images. Type must be one of:
+                                      'all': Remove all images used by any service.
+                                      'local': Remove only images that don't have a
+                                      custom tag set by the `image` field.
+            -v, --volumes           Remove named volumes declared in the `volumes`
+                                    section of the Compose file and anonymous volumes
+                                    attached to containers.
+            --remove-orphans        Remove containers for services not defined in the
+                                    Compose file
+            -t, --timeout TIMEOUT   Specify a shutdown timeout in seconds.
+                                    (default: 10)
         """
         image_type = image_type_from_opt('--rmi', options['--rmi'])
         timeout = timeout_from_opts(options)
@@ -511,7 +511,10 @@ class TopLevelCommand(object):
             rows = []
             for container in containers:
                 image_config = container.image_config
-                repo_tags = image_config['RepoTags'][0].rsplit(':', 1)
+                repo_tags = (
+                    image_config['RepoTags'][0].rsplit(':', 1) if image_config['RepoTags']
+                    else ('<none>', '<none>')
+                )
                 image_id = image_config['Id'].split(':')[1][:12]
                 size = human_readable_file_size(image_config['Size'])
                 rows.append([

+ 1 - 0
compose/config/config.py

@@ -126,6 +126,7 @@ ALLOWED_KEYS = DOCKER_CONFIG_KEYS + [
     'network_mode',
     'init',
     'scale',
+    'stop_grace_period',
 ]
 
 DOCKER_VALID_URL_PREFIXES = (

+ 35 - 24
compose/service.py

@@ -785,6 +785,35 @@ class Service(object):
             self.options.get('labels'),
             override_options.get('labels'))
 
+        container_options, override_options = self._build_container_volume_options(
+            previous_container, container_options, override_options
+        )
+
+        container_options['image'] = self.image_name
+
+        container_options['labels'] = build_container_labels(
+            container_options.get('labels', {}),
+            self.labels(one_off=one_off),
+            number,
+            self.config_hash if add_config_hash else None)
+
+        # Delete options which are only used in HostConfig
+        for key in HOST_CONFIG_KEYS:
+            container_options.pop(key, None)
+
+        container_options['host_config'] = self._get_container_host_config(
+            override_options,
+            one_off=one_off)
+
+        networking_config = self.build_default_networking_config()
+        if networking_config:
+            container_options['networking_config'] = networking_config
+
+        container_options['environment'] = format_environment(
+            container_options['environment'])
+        return container_options
+
+    def _build_container_volume_options(self, previous_container, container_options, override_options):
         container_volumes = []
         container_mounts = []
         if 'volumes' in container_options:
@@ -801,7 +830,11 @@ class Service(object):
         container_options['environment'].update(affinity)
 
         container_options['volumes'] = dict((v.internal, {}) for v in container_volumes or {})
-        override_options['mounts'] = [build_mount(v) for v in container_mounts] or None
+        if version_gte(self.client.api_version, '1.30'):
+            override_options['mounts'] = [build_mount(v) for v in container_mounts] or None
+        else:
+            override_options['binds'].extend(m.legacy_repr() for m in container_mounts)
+            container_options['volumes'].update((m.target, {}) for m in container_mounts)
 
         secret_volumes = self.get_secret_volumes()
         if secret_volumes:
@@ -814,29 +847,7 @@ class Service(object):
                 override_options['mounts'] = override_options.get('mounts') or []
                 override_options['mounts'].extend([build_mount(v) for v in secret_volumes])
 
-        container_options['image'] = self.image_name
-
-        container_options['labels'] = build_container_labels(
-            container_options.get('labels', {}),
-            self.labels(one_off=one_off),
-            number,
-            self.config_hash if add_config_hash else None)
-
-        # Delete options which are only used in HostConfig
-        for key in HOST_CONFIG_KEYS:
-            container_options.pop(key, None)
-
-        container_options['host_config'] = self._get_container_host_config(
-            override_options,
-            one_off=one_off)
-
-        networking_config = self.build_default_networking_config()
-        if networking_config:
-            container_options['networking_config'] = networking_config
-
-        container_options['environment'] = format_environment(
-            container_options['environment'])
-        return container_options
+        return container_options, override_options
 
     def _get_container_host_config(self, override_options, one_off=False):
         options = dict(self.options, **override_options)

+ 1 - 1
script/run/run.sh

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

+ 21 - 5
tests/acceptance/cli_test.py

@@ -2447,14 +2447,30 @@ class CLITestCase(DockerClientTestCase):
         assert 'multiplecomposefiles_another_1' in result.stdout
         assert 'multiplecomposefiles_simple_1' in result.stdout
 
+    @mock.patch.dict(os.environ)
+    def test_images_tagless_image(self):
+        self.base_dir = 'tests/fixtures/tagless-image'
+        stream = self.client.build(self.base_dir, decode=True)
+        img_id = None
+        for data in stream:
+            if 'aux' in data:
+                img_id = data['aux']['ID']
+                break
+            if 'stream' in data and 'Successfully built' in data['stream']:
+                img_id = self.client.inspect_image(data['stream'].split(' ')[2].strip())['Id']
+
+        assert img_id
+
+        os.environ['IMAGE_ID'] = img_id
+        self.project.get_service('foo').create_container()
+        result = self.dispatch(['images'])
+        assert '<none>' in result.stdout
+        assert 'taglessimage_foo_1' in result.stdout
+
     def test_up_with_override_yaml(self):
         self.base_dir = 'tests/fixtures/override-yaml-files'
         self._project = get_project(self.base_dir, [])
-        self.dispatch(
-            [
-                'up', '-d',
-            ],
-            None)
+        self.dispatch(['up', '-d'], None)
 
         containers = self.project.containers()
         self.assertEqual(len(containers), 2)

+ 2 - 0
tests/fixtures/tagless-image/Dockerfile

@@ -0,0 +1,2 @@
+FROM busybox:latest
+RUN touch /blah

+ 5 - 0
tests/fixtures/tagless-image/docker-compose.yml

@@ -0,0 +1,5 @@
+version: '2.3'
+services:
+  foo:
+    image: ${IMAGE_ID}
+    command: top

+ 18 - 0
tests/integration/service_test.py

@@ -13,6 +13,7 @@ from six import StringIO
 from six import text_type
 
 from .. import mock
+from .testcases import docker_client
 from .testcases import DockerClientTestCase
 from .testcases import get_links
 from .testcases import pull_busybox
@@ -326,6 +327,23 @@ class ServiceTest(DockerClientTestCase):
         assert mount
         assert mount['Name'] == volume_name
 
+    @v3_only()
+    def test_create_container_with_legacy_mount(self):
+        # Ensure mounts are converted to volumes if API version < 1.30
+        # Needed to support long syntax in the 3.2 format
+        client = docker_client({}, version='1.25')
+        container_path = '/container-volume'
+        volume_name = 'composetest_abcde'
+        self.client.create_volume(volume_name)
+        service = Service('db', client=client, volumes=[
+            MountSpec(type='volume', source=volume_name, target=container_path)
+        ], image='busybox:latest', command=['top'], project='composetest')
+        container = service.create_container()
+        service.start_container(container)
+        mount = container.get_mount(container_path)
+        assert mount
+        assert mount['Name'] == volume_name
+
     def test_create_container_with_healthcheck_config(self):
         one_second = parse_nanoseconds_int('1s')
         healthcheck = {

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

@@ -1150,7 +1150,8 @@ class ConfigTest(unittest.TestCase):
                         'volumes': [
                             {'source': '/a', 'target': '/b', 'type': 'bind'},
                             {'source': 'vol', 'target': '/x', 'type': 'volume', 'read_only': True}
-                        ]
+                        ],
+                        'stop_grace_period': '30s',
                     }
                 },
                 'volumes': {'vol': {}}
@@ -1177,6 +1178,7 @@ class ConfigTest(unittest.TestCase):
                 '/c:/b:rw',
                 {'source': 'vol', 'target': '/x', 'type': 'volume', 'read_only': True}
             ]
+        assert service_dicts[0]['stop_grace_period'] == '30s'
 
     @mock.patch.dict(os.environ)
     def test_volume_mode_override(self):