mirror of
https://github.com/docker/compose.git
synced 2025-07-23 13:45:00 +02:00
commit
cae6bf9557
19
CHANGELOG.md
19
CHANGELOG.md
@ -9,7 +9,7 @@ Change log
|
|||||||
#### Compose file version 3.5
|
#### Compose file version 3.5
|
||||||
|
|
||||||
- Introduced version 3.5 of the `docker-compose.yml` specification.
|
- 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
|
- 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 `extra_hosts` in build configuration
|
||||||
|
|
||||||
- Added support for the
|
- 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.
|
||||||
[long syntax](https://docs.docker.com/compose/compose-file/#long-syntax-3)
|
Note that using this syntax will create [mounts](https://docs.docker.com/engine/admin/volumes/bind-mounts/) instead of volumes.
|
||||||
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
|
#### Compose file version 2.1 and up
|
||||||
|
|
||||||
- Added support for the `oom_kill_disable` parameter in service definitions
|
- Added support for the `oom_kill_disable` parameter in service definitions
|
||||||
(2.x only)
|
(2.x only)
|
||||||
|
|
||||||
- Added support for custom names for network, secret and config definitions
|
- Added support for custom names for network definitions (2.x only)
|
||||||
(2.x only)
|
|
||||||
|
|
||||||
|
|
||||||
#### All formats
|
#### All formats
|
||||||
@ -62,6 +57,12 @@ Change log
|
|||||||
- Fixed an issue with CLI logging causing duplicate messages and inelegant
|
- Fixed an issue with CLI logging causing duplicate messages and inelegant
|
||||||
output to occur
|
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
|
- Fixed a bug where the valid `${VAR:-}` syntax would cause Compose to
|
||||||
error out
|
error out
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__version__ = '1.18.0-rc2'
|
__version__ = '1.18.0'
|
||||||
|
@ -365,17 +365,17 @@ class TopLevelCommand(object):
|
|||||||
Usage: down [options]
|
Usage: down [options]
|
||||||
|
|
||||||
Options:
|
Options:
|
||||||
--rmi type Remove images. Type must be one of:
|
--rmi type Remove images. Type must be one of:
|
||||||
'all': Remove all images used by any service.
|
'all': Remove all images used by any service.
|
||||||
'local': Remove only images that don't have a custom tag
|
'local': Remove only images that don't have a
|
||||||
set by the `image` field.
|
custom tag set by the `image` field.
|
||||||
-v, --volumes Remove named volumes declared in the `volumes` section
|
-v, --volumes Remove named volumes declared in the `volumes`
|
||||||
of the Compose file and anonymous volumes
|
section of the Compose file and anonymous volumes
|
||||||
attached to containers.
|
attached to containers.
|
||||||
--remove-orphans Remove containers for services not defined in the
|
--remove-orphans Remove containers for services not defined in the
|
||||||
Compose file
|
Compose file
|
||||||
-t, --timeout TIMEOUT Specify a shutdown timeout in seconds.
|
-t, --timeout TIMEOUT Specify a shutdown timeout in seconds.
|
||||||
(default: 10)
|
(default: 10)
|
||||||
"""
|
"""
|
||||||
image_type = image_type_from_opt('--rmi', options['--rmi'])
|
image_type = image_type_from_opt('--rmi', options['--rmi'])
|
||||||
timeout = timeout_from_opts(options)
|
timeout = timeout_from_opts(options)
|
||||||
@ -511,7 +511,10 @@ class TopLevelCommand(object):
|
|||||||
rows = []
|
rows = []
|
||||||
for container in containers:
|
for container in containers:
|
||||||
image_config = container.image_config
|
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]
|
image_id = image_config['Id'].split(':')[1][:12]
|
||||||
size = human_readable_file_size(image_config['Size'])
|
size = human_readable_file_size(image_config['Size'])
|
||||||
rows.append([
|
rows.append([
|
||||||
|
@ -126,6 +126,7 @@ ALLOWED_KEYS = DOCKER_CONFIG_KEYS + [
|
|||||||
'network_mode',
|
'network_mode',
|
||||||
'init',
|
'init',
|
||||||
'scale',
|
'scale',
|
||||||
|
'stop_grace_period',
|
||||||
]
|
]
|
||||||
|
|
||||||
DOCKER_VALID_URL_PREFIXES = (
|
DOCKER_VALID_URL_PREFIXES = (
|
||||||
|
@ -785,34 +785,9 @@ class Service(object):
|
|||||||
self.options.get('labels'),
|
self.options.get('labels'),
|
||||||
override_options.get('labels'))
|
override_options.get('labels'))
|
||||||
|
|
||||||
container_volumes = []
|
container_options, override_options = self._build_container_volume_options(
|
||||||
container_mounts = []
|
previous_container, container_options, override_options
|
||||||
if 'volumes' in container_options:
|
|
||||||
container_volumes = [
|
|
||||||
v for v in container_options.get('volumes') if isinstance(v, VolumeSpec)
|
|
||||||
]
|
|
||||||
container_mounts = [v for v in container_options.get('volumes') if isinstance(v, MountSpec)]
|
|
||||||
|
|
||||||
binds, affinity = merge_volume_bindings(
|
|
||||||
container_volumes, self.options.get('tmpfs') or [], previous_container,
|
|
||||||
container_mounts
|
|
||||||
)
|
)
|
||||||
override_options['binds'] = binds
|
|
||||||
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
|
|
||||||
|
|
||||||
secret_volumes = self.get_secret_volumes()
|
|
||||||
if secret_volumes:
|
|
||||||
if version_lt(self.client.api_version, '1.30'):
|
|
||||||
override_options['binds'].extend(v.legacy_repr() for v in secret_volumes)
|
|
||||||
container_options['volumes'].update(
|
|
||||||
(v.target, {}) for v in secret_volumes
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
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['image'] = self.image_name
|
||||||
|
|
||||||
@ -838,6 +813,42 @@ class Service(object):
|
|||||||
container_options['environment'])
|
container_options['environment'])
|
||||||
return container_options
|
return container_options
|
||||||
|
|
||||||
|
def _build_container_volume_options(self, previous_container, container_options, override_options):
|
||||||
|
container_volumes = []
|
||||||
|
container_mounts = []
|
||||||
|
if 'volumes' in container_options:
|
||||||
|
container_volumes = [
|
||||||
|
v for v in container_options.get('volumes') if isinstance(v, VolumeSpec)
|
||||||
|
]
|
||||||
|
container_mounts = [v for v in container_options.get('volumes') if isinstance(v, MountSpec)]
|
||||||
|
|
||||||
|
binds, affinity = merge_volume_bindings(
|
||||||
|
container_volumes, self.options.get('tmpfs') or [], previous_container,
|
||||||
|
container_mounts
|
||||||
|
)
|
||||||
|
override_options['binds'] = binds
|
||||||
|
container_options['environment'].update(affinity)
|
||||||
|
|
||||||
|
container_options['volumes'] = dict((v.internal, {}) for v in container_volumes or {})
|
||||||
|
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:
|
||||||
|
if version_lt(self.client.api_version, '1.30'):
|
||||||
|
override_options['binds'].extend(v.legacy_repr() for v in secret_volumes)
|
||||||
|
container_options['volumes'].update(
|
||||||
|
(v.target, {}) for v in secret_volumes
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
override_options['mounts'] = override_options.get('mounts') or []
|
||||||
|
override_options['mounts'].extend([build_mount(v) for v in secret_volumes])
|
||||||
|
|
||||||
|
return container_options, override_options
|
||||||
|
|
||||||
def _get_container_host_config(self, override_options, one_off=False):
|
def _get_container_host_config(self, override_options, one_off=False):
|
||||||
options = dict(self.options, **override_options)
|
options = dict(self.options, **override_options)
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
VERSION="1.18.0-rc2"
|
VERSION="1.18.0"
|
||||||
IMAGE="docker/compose:$VERSION"
|
IMAGE="docker/compose:$VERSION"
|
||||||
|
|
||||||
|
|
||||||
|
@ -2447,14 +2447,30 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
assert 'multiplecomposefiles_another_1' in result.stdout
|
assert 'multiplecomposefiles_another_1' in result.stdout
|
||||||
assert 'multiplecomposefiles_simple_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):
|
def test_up_with_override_yaml(self):
|
||||||
self.base_dir = 'tests/fixtures/override-yaml-files'
|
self.base_dir = 'tests/fixtures/override-yaml-files'
|
||||||
self._project = get_project(self.base_dir, [])
|
self._project = get_project(self.base_dir, [])
|
||||||
self.dispatch(
|
self.dispatch(['up', '-d'], None)
|
||||||
[
|
|
||||||
'up', '-d',
|
|
||||||
],
|
|
||||||
None)
|
|
||||||
|
|
||||||
containers = self.project.containers()
|
containers = self.project.containers()
|
||||||
self.assertEqual(len(containers), 2)
|
self.assertEqual(len(containers), 2)
|
||||||
|
2
tests/fixtures/tagless-image/Dockerfile
vendored
Normal file
2
tests/fixtures/tagless-image/Dockerfile
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
FROM busybox:latest
|
||||||
|
RUN touch /blah
|
5
tests/fixtures/tagless-image/docker-compose.yml
vendored
Normal file
5
tests/fixtures/tagless-image/docker-compose.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
version: '2.3'
|
||||||
|
services:
|
||||||
|
foo:
|
||||||
|
image: ${IMAGE_ID}
|
||||||
|
command: top
|
@ -13,6 +13,7 @@ from six import StringIO
|
|||||||
from six import text_type
|
from six import text_type
|
||||||
|
|
||||||
from .. import mock
|
from .. import mock
|
||||||
|
from .testcases import docker_client
|
||||||
from .testcases import DockerClientTestCase
|
from .testcases import DockerClientTestCase
|
||||||
from .testcases import get_links
|
from .testcases import get_links
|
||||||
from .testcases import pull_busybox
|
from .testcases import pull_busybox
|
||||||
@ -326,6 +327,23 @@ class ServiceTest(DockerClientTestCase):
|
|||||||
assert mount
|
assert mount
|
||||||
assert mount['Name'] == volume_name
|
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):
|
def test_create_container_with_healthcheck_config(self):
|
||||||
one_second = parse_nanoseconds_int('1s')
|
one_second = parse_nanoseconds_int('1s')
|
||||||
healthcheck = {
|
healthcheck = {
|
||||||
|
@ -1150,7 +1150,8 @@ class ConfigTest(unittest.TestCase):
|
|||||||
'volumes': [
|
'volumes': [
|
||||||
{'source': '/a', 'target': '/b', 'type': 'bind'},
|
{'source': '/a', 'target': '/b', 'type': 'bind'},
|
||||||
{'source': 'vol', 'target': '/x', 'type': 'volume', 'read_only': True}
|
{'source': 'vol', 'target': '/x', 'type': 'volume', 'read_only': True}
|
||||||
]
|
],
|
||||||
|
'stop_grace_period': '30s',
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'volumes': {'vol': {}}
|
'volumes': {'vol': {}}
|
||||||
@ -1177,6 +1178,7 @@ class ConfigTest(unittest.TestCase):
|
|||||||
'/c:/b:rw',
|
'/c:/b:rw',
|
||||||
{'source': 'vol', 'target': '/x', 'type': 'volume', 'read_only': True}
|
{'source': 'vol', 'target': '/x', 'type': 'volume', 'read_only': True}
|
||||||
]
|
]
|
||||||
|
assert service_dicts[0]['stop_grace_period'] == '30s'
|
||||||
|
|
||||||
@mock.patch.dict(os.environ)
|
@mock.patch.dict(os.environ)
|
||||||
def test_volume_mode_override(self):
|
def test_volume_mode_override(self):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user