mirror of
https://github.com/docker/compose.git
synced 2025-07-09 14:54:27 +02:00
Move named volumes matching to config validation phase
Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
3da25aa463
commit
3f28472ebc
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -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.
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -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(
|
||||||
|
@ -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)
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user