Move named volumes matching to config validation phase

Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
Joffrey F 2016-01-22 17:42:24 -08:00 committed by Aanand Prasad
parent 3da25aa463
commit 3f28472ebc
5 changed files with 83 additions and 74 deletions

View File

@ -25,6 +25,7 @@ from .types import parse_extra_hosts
from .types import parse_restart_spec from .types import parse_restart_spec
from .types import VolumeFromSpec from .types import VolumeFromSpec
from .types import VolumeSpec from .types import VolumeSpec
from .validation import match_named_volumes
from .validation import validate_against_fields_schema from .validation import validate_against_fields_schema
from .validation import validate_against_service_schema from .validation import validate_against_service_schema
from .validation import validate_depends_on from .validation import validate_depends_on
@ -274,6 +275,11 @@ def load(config_details):
config_details.working_dir, config_details.working_dir,
main_file, main_file,
[file.get_service_dicts() for file in config_details.config_files]) [file.get_service_dicts() for file in config_details.config_files])
if main_file.version >= 2:
for service_dict in service_dicts:
match_named_volumes(service_dict, volumes)
return Config(main_file.version, service_dicts, volumes, networks) return Config(main_file.version, service_dicts, volumes, networks)

View File

@ -77,6 +77,18 @@ def format_boolean_in_environment(instance):
return True return True
def match_named_volumes(service_dict, project_volumes):
service_volumes = service_dict.get('volumes', [])
for volume_spec in service_volumes:
if volume_spec.is_named_volume and volume_spec.external not in project_volumes:
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')
)
)
def validate_top_level_service_objects(filename, service_dicts): def validate_top_level_service_objects(filename, service_dicts):
"""Perform some high level validation of the service name and value. """Perform some high level validation of the service name and value.

View File

@ -42,7 +42,7 @@ class Project(object):
self.use_networking = use_networking self.use_networking = use_networking
self.network_driver = network_driver self.network_driver = network_driver
self.networks = networks or [] self.networks = networks or []
self.volumes = volumes or [] self.volumes = volumes or {}
def labels(self, one_off=False): def labels(self, one_off=False):
return [ return [
@ -76,14 +76,12 @@ class Project(object):
if config_data.volumes: if config_data.volumes:
for vol_name, data in config_data.volumes.items(): for vol_name, data in config_data.volumes.items():
project.volumes.append( project.volumes[vol_name] = Volume(
Volume(
client=client, project=name, name=vol_name, client=client, project=name, name=vol_name,
driver=data.get('driver'), driver=data.get('driver'),
driver_opts=data.get('driver_opts'), driver_opts=data.get('driver_opts'),
external_name=data.get('external_name') external_name=data.get('external_name')
) )
)
for service_dict in config_data.services: for service_dict in config_data.services:
if use_networking: if use_networking:
@ -97,7 +95,13 @@ class Project(object):
volumes_from = get_volumes_from(project, service_dict) volumes_from = get_volumes_from(project, service_dict)
if config_data.version == 2: if config_data.version == 2:
match_named_volumes(service_dict, project.volumes) service_volumes = service_dict.get('volumes', [])
for volume_spec in service_volumes:
if volume_spec.is_named_volume:
declared_volume = project.volumes[volume_spec.external]
service_volumes[service_volumes.index(volume_spec)] = (
volume_spec._replace(external=declared_volume.full_name)
)
project.services.append( project.services.append(
Service( Service(
@ -243,7 +247,7 @@ class Project(object):
def initialize_volumes(self): def initialize_volumes(self):
try: try:
for volume in self.volumes: for volume in self.volumes.values():
if volume.external: if volume.external:
log.debug( log.debug(
'Volume {0} declared as external. No new ' 'Volume {0} declared as external. No new '
@ -298,7 +302,7 @@ class Project(object):
network.remove() network.remove()
def remove_volumes(self): def remove_volumes(self):
for volume in self.volumes: for volume in self.volumes.values():
volume.remove() volume.remove()
def initialize_networks(self): def initialize_networks(self):
@ -476,26 +480,6 @@ def get_networks(service_dict, network_definitions):
return networks return networks
def match_named_volumes(service_dict, project_volumes):
service_volumes = service_dict.get('volumes', [])
for volume_spec in service_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')
)
)
service_volumes[service_volumes.index(volume_spec)] = (
volume_spec._replace(external=declared_volume.full_name)
)
def get_volumes_from(project, service_dict): def get_volumes_from(project, service_dict):
volumes_from = service_dict.pop('volumes_from', None) volumes_from = service_dict.pop('volumes_from', None)
if not volumes_from: if not volumes_from:

View File

@ -564,6 +564,56 @@ class ConfigTest(unittest.TestCase):
] ]
assert service_sort(service_dicts) == service_sort(expected) assert service_sort(service_dicts) == service_sort(expected)
def test_undeclared_volume_v2(self):
base_file = config.ConfigFile(
'base.yaml',
{
'version': 2,
'services': {
'web': {
'image': 'busybox:latest',
'volumes': ['data0028:/data:ro'],
},
},
}
)
details = config.ConfigDetails('.', [base_file])
with self.assertRaises(ConfigurationError):
config.load(details)
base_file = config.ConfigFile(
'base.yaml',
{
'version': 2,
'services': {
'web': {
'image': 'busybox:latest',
'volumes': ['./data0028:/data:ro'],
},
},
}
)
details = config.ConfigDetails('.', [base_file])
config_data = config.load(details)
volume = config_data.services[0].get('volumes')[0]
assert not volume.is_named_volume
def test_undeclared_volume_v1(self):
base_file = config.ConfigFile(
'base.yaml',
{
'web': {
'image': 'busybox:latest',
'volumes': ['data0028:/data:ro'],
},
}
)
details = config.ConfigDetails('.', [base_file])
config_data = config.load(details)
volume = config_data.services[0].get('volumes')[0]
assert volume.external == 'data0028'
assert volume.is_named_volume
def test_config_valid_service_names(self): def test_config_valid_service_names(self):
for valid_name in ['_', '-', '.__.', '_what-up.', 'what_.up----', 'whatup']: for valid_name in ['_', '-', '.__.', '_what-up.', 'what_.up----', 'whatup']:
services = config.load( services = config.load(

View File

@ -7,10 +7,8 @@ import docker
from .. import mock from .. import mock
from .. import unittest from .. import unittest
from compose.config import ConfigurationError
from compose.config.config import Config from compose.config.config import Config
from compose.config.types import VolumeFromSpec from compose.config.types import VolumeFromSpec
from compose.config.types import VolumeSpec
from compose.const import LABEL_SERVICE from compose.const import LABEL_SERVICE
from compose.container import Container from compose.container import Container
from compose.project import Project from compose.project import Project
@ -478,44 +476,3 @@ class ProjectTest(unittest.TestCase):
), ),
) )
self.assertEqual([c.id for c in project.containers()], ['1']) 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)