mirror of
https://github.com/docker/compose.git
synced 2025-07-21 20:54:32 +02:00
Add support for expanded mount/volume notation
Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
73aff2b50f
commit
69d0c0e3a0
@ -1030,7 +1030,13 @@ def resolve_volume_paths(working_dir, service_dict):
|
|||||||
|
|
||||||
|
|
||||||
def resolve_volume_path(working_dir, volume):
|
def resolve_volume_path(working_dir, volume):
|
||||||
container_path, host_path = split_path_mapping(volume)
|
if isinstance(volume, dict):
|
||||||
|
host_path = volume.get('source')
|
||||||
|
container_path = volume.get('target')
|
||||||
|
if host_path and volume.get('read_only'):
|
||||||
|
container_path += ':ro'
|
||||||
|
else:
|
||||||
|
container_path, host_path = split_path_mapping(volume)
|
||||||
|
|
||||||
if host_path is not None:
|
if host_path is not None:
|
||||||
if host_path.startswith('.'):
|
if host_path.startswith('.'):
|
||||||
@ -1112,6 +1118,8 @@ def split_path_mapping(volume_path):
|
|||||||
path. Using splitdrive so windows absolute paths won't cause issues with
|
path. Using splitdrive so windows absolute paths won't cause issues with
|
||||||
splitting on ':'.
|
splitting on ':'.
|
||||||
"""
|
"""
|
||||||
|
if isinstance(volume_path, dict):
|
||||||
|
return (volume_path.get('target'), volume_path)
|
||||||
drive, volume_config = splitdrive(volume_path)
|
drive, volume_config = splitdrive(volume_path)
|
||||||
|
|
||||||
if ':' in volume_config:
|
if ':' in volume_config:
|
||||||
@ -1123,7 +1131,9 @@ def split_path_mapping(volume_path):
|
|||||||
|
|
||||||
def join_path_mapping(pair):
|
def join_path_mapping(pair):
|
||||||
(container, host) = pair
|
(container, host) = pair
|
||||||
if host is None:
|
if isinstance(host, dict):
|
||||||
|
return host
|
||||||
|
elif host is None:
|
||||||
return container
|
return container
|
||||||
else:
|
else:
|
||||||
return ":".join((host, container))
|
return ":".join((host, container))
|
||||||
|
@ -321,7 +321,7 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
result = self.dispatch(['config'])
|
result = self.dispatch(['config'])
|
||||||
|
|
||||||
assert yaml.load(result.stdout) == {
|
assert yaml.load(result.stdout) == {
|
||||||
'version': '3.0',
|
'version': '3.2',
|
||||||
'networks': {},
|
'networks': {},
|
||||||
'volumes': {
|
'volumes': {
|
||||||
'foobar': {
|
'foobar': {
|
||||||
@ -371,6 +371,11 @@ class CLITestCase(DockerClientTestCase):
|
|||||||
'timeout': '1s',
|
'timeout': '1s',
|
||||||
'retries': 5,
|
'retries': 5,
|
||||||
},
|
},
|
||||||
|
'volumes': [
|
||||||
|
'/host/path:/container/path:ro',
|
||||||
|
'foobar:/container/volumepath:rw',
|
||||||
|
'/anonymous'
|
||||||
|
],
|
||||||
|
|
||||||
'stop_grace_period': '20s',
|
'stop_grace_period': '20s',
|
||||||
},
|
},
|
||||||
|
13
tests/fixtures/v3-full/docker-compose.yml
vendored
13
tests/fixtures/v3-full/docker-compose.yml
vendored
@ -1,4 +1,4 @@
|
|||||||
version: "3"
|
version: "3.2"
|
||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: busybox
|
image: busybox
|
||||||
@ -34,6 +34,17 @@ services:
|
|||||||
timeout: 1s
|
timeout: 1s
|
||||||
retries: 5
|
retries: 5
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- source: /host/path
|
||||||
|
target: /container/path
|
||||||
|
type: bind
|
||||||
|
read_only: true
|
||||||
|
- source: foobar
|
||||||
|
type: volume
|
||||||
|
target: /container/volumepath
|
||||||
|
- type: volume
|
||||||
|
target: /anonymous
|
||||||
|
|
||||||
stop_grace_period: 20s
|
stop_grace_period: 20s
|
||||||
volumes:
|
volumes:
|
||||||
foobar:
|
foobar:
|
||||||
|
@ -29,6 +29,7 @@ from compose.const import COMPOSEFILE_V2_0 as V2_0
|
|||||||
from compose.const import COMPOSEFILE_V2_1 as V2_1
|
from compose.const import COMPOSEFILE_V2_1 as V2_1
|
||||||
from compose.const import COMPOSEFILE_V3_0 as V3_0
|
from compose.const import COMPOSEFILE_V3_0 as V3_0
|
||||||
from compose.const import COMPOSEFILE_V3_1 as V3_1
|
from compose.const import COMPOSEFILE_V3_1 as V3_1
|
||||||
|
from compose.const import COMPOSEFILE_V3_2 as V3_2
|
||||||
from compose.const import IS_WINDOWS_PLATFORM
|
from compose.const import IS_WINDOWS_PLATFORM
|
||||||
from compose.utils import nanoseconds_from_time_seconds
|
from compose.utils import nanoseconds_from_time_seconds
|
||||||
from tests import mock
|
from tests import mock
|
||||||
@ -964,6 +965,44 @@ class ConfigTest(unittest.TestCase):
|
|||||||
]
|
]
|
||||||
assert service_sort(service_dicts) == service_sort(expected)
|
assert service_sort(service_dicts) == service_sort(expected)
|
||||||
|
|
||||||
|
@mock.patch.dict(os.environ)
|
||||||
|
def test_load_with_multiple_files_v3_2(self):
|
||||||
|
os.environ['COMPOSE_CONVERT_WINDOWS_PATHS'] = 'true'
|
||||||
|
base_file = config.ConfigFile(
|
||||||
|
'base.yaml',
|
||||||
|
{
|
||||||
|
'version': '3.2',
|
||||||
|
'services': {
|
||||||
|
'web': {
|
||||||
|
'image': 'example/web',
|
||||||
|
'volumes': [
|
||||||
|
{'source': '/a', 'target': '/b', 'type': 'bind'},
|
||||||
|
{'source': 'vol', 'target': '/x', 'type': 'volume', 'read_only': True}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'volumes': {'vol': {}}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
override_file = config.ConfigFile(
|
||||||
|
'override.yaml',
|
||||||
|
{
|
||||||
|
'version': '3.2',
|
||||||
|
'services': {
|
||||||
|
'web': {
|
||||||
|
'volumes': ['/c:/b', '/anonymous']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
details = config.ConfigDetails('.', [base_file, override_file])
|
||||||
|
service_dicts = config.load(details).services
|
||||||
|
svc_volumes = map(lambda v: v.repr(), service_dicts[0]['volumes'])
|
||||||
|
assert sorted(svc_volumes) == sorted(
|
||||||
|
['/anonymous', '/c:/b:rw', 'vol:/x:ro']
|
||||||
|
)
|
||||||
|
|
||||||
def test_undeclared_volume_v2(self):
|
def test_undeclared_volume_v2(self):
|
||||||
base_file = config.ConfigFile(
|
base_file = config.ConfigFile(
|
||||||
'base.yaml',
|
'base.yaml',
|
||||||
@ -1544,6 +1583,29 @@ class ConfigTest(unittest.TestCase):
|
|||||||
'ports': types.ServicePort.parse('5432')
|
'ports': types.ServicePort.parse('5432')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def test_merge_service_dicts_heterogeneous_volumes(self):
|
||||||
|
base = {
|
||||||
|
'volumes': ['/a:/b', '/x:/z'],
|
||||||
|
}
|
||||||
|
|
||||||
|
override = {
|
||||||
|
'image': 'alpine:edge',
|
||||||
|
'volumes': [
|
||||||
|
{'source': '/e', 'target': '/b', 'type': 'bind'},
|
||||||
|
{'source': '/c', 'target': '/d', 'type': 'bind'}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
actual = config.merge_service_dicts_from_files(
|
||||||
|
base, override, V3_2
|
||||||
|
)
|
||||||
|
|
||||||
|
assert actual['volumes'] == [
|
||||||
|
{'source': '/e', 'target': '/b', 'type': 'bind'},
|
||||||
|
{'source': '/c', 'target': '/d', 'type': 'bind'},
|
||||||
|
'/x:/z'
|
||||||
|
]
|
||||||
|
|
||||||
def test_merge_logging_v1(self):
|
def test_merge_logging_v1(self):
|
||||||
base = {
|
base = {
|
||||||
'image': 'alpine:edge',
|
'image': 'alpine:edge',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user