1
0
Эх сурвалжийг харах

Match named volumes in service definitions with declared volumes

Raise ConfigurationError for undeclared named volumes
Test new behavior

Signed-off-by: Joffrey F <[email protected]>
Joffrey F 9 жил өмнө
parent
commit
9e67eae311

+ 6 - 0
compose/config/types.py

@@ -163,3 +163,9 @@ class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')):
     def repr(self):
         external = self.external + ':' if self.external else ''
         return '{ext}{v.internal}:{v.mode}'.format(ext=external, v=self)
+
+    @property
+    def is_named_volume(self):
+        return self.external and not (
+            self.external.startswith('.') or self.external.startswith('/')
+        )

+ 33 - 12
compose/project.py

@@ -74,6 +74,17 @@ class Project(object):
         if 'default' not in network_config:
             all_networks.append(project.default_network)
 
+        if config_data.volumes:
+            for vol_name, data in config_data.volumes.items():
+                project.volumes.append(
+                    Volume(
+                        client=client, project=name, name=vol_name,
+                        driver=data.get('driver'),
+                        driver_opts=data.get('driver_opts'),
+                        external_name=data.get('external_name')
+                    )
+                )
+
         for service_dict in config_data.services:
             if use_networking:
                 networks = get_networks(service_dict, all_networks)
@@ -86,6 +97,9 @@ class Project(object):
 
             volumes_from = get_volumes_from(project, service_dict)
 
+            if config_data.version == 2:
+                match_named_volumes(service_dict, project.volumes)
+
             project.services.append(
                 Service(
                     client=client,
@@ -95,23 +109,13 @@ class Project(object):
                     links=links,
                     net=net,
                     volumes_from=volumes_from,
-                    **service_dict))
+                    **service_dict)
+            )
 
         project.networks += custom_networks
         if 'default' not in network_config and project.uses_default_network():
             project.networks.append(project.default_network)
 
-        if config_data.volumes:
-            for vol_name, data in config_data.volumes.items():
-                project.volumes.append(
-                    Volume(
-                        client=client, project=name, name=vol_name,
-                        driver=data.get('driver'),
-                        driver_opts=data.get('driver_opts'),
-                        external_name=data.get('external_name')
-                    )
-                )
-
         return project
 
     @property
@@ -473,6 +477,23 @@ def get_networks(service_dict, network_definitions):
     return networks
 
 
+def match_named_volumes(service_dict, project_volumes):
+    for volume_spec in service_dict.get('volumes', []):
+        if volume_spec.is_named_volume:
+            declared_volume = next(
+                (v for v in project_volumes if v.name == volume_spec.external),
+                None
+            )
+            if not declared_volume:
+                raise ConfigurationError(
+                    'Named volume "{0}" is used in service "{1}" but no'
+                    ' declaration was found in the volumes section.'.format(
+                        volume_spec.repr(), service_dict.get('name')
+                    )
+                )
+            volume_spec._replace(external=declared_volume.full_name)
+
+
 def get_volumes_from(project, service_dict):
     volumes_from = service_dict.pop('volumes_from', None)
     if not volumes_from:

+ 36 - 0
tests/integration/project_test.py

@@ -813,3 +813,39 @@ class ProjectTest(DockerClientTestCase):
         assert 'Volume {0} declared as external'.format(
             vol_name
         ) in str(e.exception)
+
+    @v2_only()
+    def test_project_up_named_volumes_in_binds(self):
+        vol_name = '{0:x}'.format(random.getrandbits(32))
+        full_vol_name = 'composetest_{0}'.format(vol_name)
+
+        base_file = config.ConfigFile(
+            'base.yml',
+            {
+                'version': 2,
+                'services': {
+                    'simple': {
+                        'image': 'busybox:latest',
+                        'command': 'top',
+                        'volumes': ['{0}:/data'.format(vol_name)]
+                    },
+                },
+                'volumes': {
+                    vol_name: {'driver': 'local'}
+                }
+
+            })
+        config_details = config.ConfigDetails('.', [base_file])
+        config_data = config.load(config_details)
+        project = Project.from_config(
+            name='composetest', config_data=config_data, client=self.client
+        )
+        service = project.services[0]
+        self.assertEqual(service.name, 'simple')
+        volumes = service.options.get('volumes')
+        self.assertEqual(len(volumes), 1)
+        self.assertEqual(volumes[0].external, full_vol_name)
+        project.up()
+        engine_volumes = self.client.volumes()
+        self.assertIsNone(next(v for v in engine_volumes if v['Name'] == vol_name))
+        self.assertIsNotNone(next(v for v in engine_volumes if v['Name'] == full_vol_name))

+ 43 - 0
tests/unit/project_test.py

@@ -7,8 +7,10 @@ import docker
 
 from .. import mock
 from .. import unittest
+from compose.config import ConfigurationError
 from compose.config.config import Config
 from compose.config.types import VolumeFromSpec
+from compose.config.types import VolumeSpec
 from compose.const import LABEL_SERVICE
 from compose.container import Container
 from compose.project import Project
@@ -476,3 +478,44 @@ class ProjectTest(unittest.TestCase):
             ),
         )
         self.assertEqual([c.id for c in project.containers()], ['1'])
+
+    def test_undeclared_volume_v2(self):
+        config = Config(
+            version=2,
+            services=[
+                {
+                    'name': 'web',
+                    'image': 'busybox:latest',
+                    'volumes': [VolumeSpec.parse('data0028:/data:ro')],
+                },
+            ],
+            networks=None,
+            volumes=None,
+        )
+        with self.assertRaises(ConfigurationError):
+            Project.from_config('composetest', config, None)
+
+        config = Config(
+            version=2,
+            services=[
+                {
+                    'name': 'web',
+                    'image': 'busybox:latest',
+                    'volumes': [VolumeSpec.parse('./data0028:/data:ro')],
+                },
+            ], networks=None, volumes=None,
+        )
+        Project.from_config('composetest', config, None)
+
+    def test_undeclared_volume_v1(self):
+        config = Config(
+            version=1,
+            services=[
+                {
+                    'name': 'web',
+                    'image': 'busybox:latest',
+                    'volumes': [VolumeSpec.parse('data0028:/data:ro')],
+                },
+            ], networks=None, volumes=None,
+        )
+        Project.from_config('composetest', config, None)