Browse Source

Add support for expanded mount/volume notation

Signed-off-by: Joffrey F <[email protected]>
Joffrey F 8 years ago
parent
commit
69d0c0e3a0

+ 12 - 2
compose/config/config.py

@@ -1030,7 +1030,13 @@ def resolve_volume_paths(working_dir, service_dict):
 
 
 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.startswith('.'):
@@ -1112,6 +1118,8 @@ def split_path_mapping(volume_path):
     path. Using splitdrive so windows absolute paths won't cause issues with
     splitting on ':'.
     """
+    if isinstance(volume_path, dict):
+        return (volume_path.get('target'), volume_path)
     drive, volume_config = splitdrive(volume_path)
 
     if ':' in volume_config:
@@ -1123,7 +1131,9 @@ def split_path_mapping(volume_path):
 
 def join_path_mapping(pair):
     (container, host) = pair
-    if host is None:
+    if isinstance(host, dict):
+        return host
+    elif host is None:
         return container
     else:
         return ":".join((host, container))

+ 6 - 1
tests/acceptance/cli_test.py

@@ -321,7 +321,7 @@ class CLITestCase(DockerClientTestCase):
         result = self.dispatch(['config'])
 
         assert yaml.load(result.stdout) == {
-            'version': '3.0',
+            'version': '3.2',
             'networks': {},
             'volumes': {
                 'foobar': {
@@ -371,6 +371,11 @@ class CLITestCase(DockerClientTestCase):
                         'timeout': '1s',
                         'retries': 5,
                     },
+                    'volumes': [
+                        '/host/path:/container/path:ro',
+                        'foobar:/container/volumepath:rw',
+                        '/anonymous'
+                    ],
 
                     'stop_grace_period': '20s',
                 },

+ 12 - 1
tests/fixtures/v3-full/docker-compose.yml

@@ -1,4 +1,4 @@
-version: "3"
+version: "3.2"
 services:
   web:
     image: busybox
@@ -34,6 +34,17 @@ services:
       timeout: 1s
       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
 volumes:
   foobar:

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

@@ -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_V3_0 as V3_0
 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.utils import nanoseconds_from_time_seconds
 from tests import mock
@@ -964,6 +965,44 @@ class ConfigTest(unittest.TestCase):
         ]
         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):
         base_file = config.ConfigFile(
             'base.yaml',
@@ -1544,6 +1583,29 @@ class ConfigTest(unittest.TestCase):
             '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):
         base = {
             'image': 'alpine:edge',