mirror of https://github.com/docker/compose.git
Implement depends_on to define an order for services in the v2 format.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
parent
33bb7c4e02
commit
6e5c312768
|
@ -27,6 +27,7 @@ from .types import VolumeFromSpec
|
|||
from .types import VolumeSpec
|
||||
from .validation import validate_against_fields_schema
|
||||
from .validation import validate_against_service_schema
|
||||
from .validation import validate_depends_on
|
||||
from .validation import validate_extends_file_path
|
||||
from .validation import validate_top_level_object
|
||||
from .validation import validate_top_level_service_objects
|
||||
|
@ -312,9 +313,10 @@ def load_services(working_dir, config_file, service_configs):
|
|||
resolver = ServiceExtendsResolver(service_config, config_file)
|
||||
service_dict = process_service(resolver.run())
|
||||
|
||||
validate_service(service_dict, service_config.name, config_file.version)
|
||||
service_config = service_config._replace(config=service_dict)
|
||||
validate_service(service_config, service_names, config_file.version)
|
||||
service_dict = finalize_service(
|
||||
service_config._replace(config=service_dict),
|
||||
service_config,
|
||||
service_names,
|
||||
config_file.version)
|
||||
return service_dict
|
||||
|
@ -481,6 +483,10 @@ def validate_extended_service_dict(service_dict, filename, service):
|
|||
raise ConfigurationError(
|
||||
"%s services with 'net: container' cannot be extended" % error_prefix)
|
||||
|
||||
if 'depends_on' in service_dict:
|
||||
raise ConfigurationError(
|
||||
"%s services with 'depends_on' cannot be extended" % error_prefix)
|
||||
|
||||
|
||||
def validate_ulimits(ulimit_config):
|
||||
for limit_name, soft_hard_values in six.iteritems(ulimit_config):
|
||||
|
@ -491,13 +497,16 @@ def validate_ulimits(ulimit_config):
|
|||
"than 'hard' value".format(ulimit_config))
|
||||
|
||||
|
||||
def validate_service(service_dict, service_name, version):
|
||||
def validate_service(service_config, service_names, version):
|
||||
service_dict, service_name = service_config.config, service_config.name
|
||||
validate_against_service_schema(service_dict, service_name, version)
|
||||
validate_paths(service_dict)
|
||||
|
||||
if 'ulimits' in service_dict:
|
||||
validate_ulimits(service_dict['ulimits'])
|
||||
|
||||
validate_depends_on(service_config, service_names)
|
||||
|
||||
if not service_dict.get('image') and has_uppercase(service_name):
|
||||
raise ConfigurationError(
|
||||
"Service '{name}' contains uppercase characters which are not valid "
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
"cpu_shares": {"type": ["number", "string"]},
|
||||
"cpu_quota": {"type": ["number", "string"]},
|
||||
"cpuset": {"type": "string"},
|
||||
"depends_on": {"$ref": "#/definitions/list_of_strings"},
|
||||
"devices": {"type": "array", "items": {"type": "string"}, "uniqueItems": true},
|
||||
"dns": {"$ref": "#/definitions/string_or_list"},
|
||||
"dns_search": {"$ref": "#/definitions/string_or_list"},
|
||||
|
|
|
@ -33,7 +33,8 @@ def sort_service_dicts(services):
|
|||
service for service in services
|
||||
if (name in get_service_names(service.get('links', [])) or
|
||||
name in get_service_names_from_volumes_from(service.get('volumes_from', [])) or
|
||||
name == get_service_name_from_net(service.get('net')))
|
||||
name == get_service_name_from_net(service.get('net')) or
|
||||
name in service.get('depends_on', []))
|
||||
]
|
||||
|
||||
def visit(n):
|
||||
|
@ -42,8 +43,10 @@ def sort_service_dicts(services):
|
|||
raise DependencyError('A service can not link to itself: %s' % n['name'])
|
||||
if n['name'] in n.get('volumes_from', []):
|
||||
raise DependencyError('A service can not mount itself as volume: %s' % n['name'])
|
||||
else:
|
||||
raise DependencyError('Circular import between %s' % ' and '.join(temporary_marked))
|
||||
if n['name'] in n.get('depends_on', []):
|
||||
raise DependencyError('A service can not depend on itself: %s' % n['name'])
|
||||
raise DependencyError('Circular dependency between %s' % ' and '.join(temporary_marked))
|
||||
|
||||
if n in unmarked:
|
||||
temporary_marked.add(n['name'])
|
||||
for m in get_service_dependents(n, services):
|
||||
|
|
|
@ -123,6 +123,14 @@ def validate_extends_file_path(service_name, extends_options, filename):
|
|||
)
|
||||
|
||||
|
||||
def validate_depends_on(service_config, service_names):
|
||||
for dependency in service_config.config.get('depends_on', []):
|
||||
if dependency not in service_names:
|
||||
raise ConfigurationError(
|
||||
"Service '{s.name}' depends on service '{dep}' which is "
|
||||
"undefined.".format(s=service_config, dep=dependency))
|
||||
|
||||
|
||||
def get_unsupported_config_msg(service_name, error_key):
|
||||
msg = "Unsupported config option for '{}' service: '{}'".format(service_name, error_key)
|
||||
if error_key in DOCKER_CONFIG_HINTS:
|
||||
|
|
|
@ -471,7 +471,8 @@ class Service(object):
|
|||
net_name = self.net.service_name
|
||||
return (self.get_linked_service_names() +
|
||||
self.get_volumes_from_names() +
|
||||
([net_name] if net_name else []))
|
||||
([net_name] if net_name else []) +
|
||||
self.options.get('depends_on', []))
|
||||
|
||||
def get_linked_service_names(self):
|
||||
return [service.name for (service, _) in self.links]
|
||||
|
|
|
@ -894,9 +894,35 @@ class ConfigTest(unittest.TestCase):
|
|||
'ext': {'external': True, 'driver': 'foo'}
|
||||
}
|
||||
})
|
||||
with self.assertRaises(ConfigurationError):
|
||||
with pytest.raises(ConfigurationError):
|
||||
config.load(config_details)
|
||||
|
||||
def test_depends_on_orders_services(self):
|
||||
config_details = build_config_details({
|
||||
'version': 2,
|
||||
'services': {
|
||||
'one': {'image': 'busybox', 'depends_on': ['three', 'two']},
|
||||
'two': {'image': 'busybox', 'depends_on': ['three']},
|
||||
'three': {'image': 'busybox'},
|
||||
},
|
||||
})
|
||||
actual = config.load(config_details)
|
||||
assert (
|
||||
[service['name'] for service in actual.services] ==
|
||||
['three', 'two', 'one']
|
||||
)
|
||||
|
||||
def test_depends_on_unknown_service_errors(self):
|
||||
config_details = build_config_details({
|
||||
'version': 2,
|
||||
'services': {
|
||||
'one': {'image': 'busybox', 'depends_on': ['three']},
|
||||
},
|
||||
})
|
||||
with pytest.raises(ConfigurationError) as exc:
|
||||
config.load(config_details)
|
||||
assert "Service 'one' depends on service 'three'" in exc.exconly()
|
||||
|
||||
|
||||
class PortsTest(unittest.TestCase):
|
||||
INVALID_PORTS_TYPES = [
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pytest
|
||||
|
||||
from compose.config.errors import DependencyError
|
||||
from compose.config.sort_services import sort_service_dicts
|
||||
from compose.config.types import VolumeFromSpec
|
||||
|
@ -240,3 +242,15 @@ class SortServiceTest(unittest.TestCase):
|
|||
self.assertIn('web', e.msg)
|
||||
else:
|
||||
self.fail('Should have thrown an DependencyError')
|
||||
|
||||
def test_sort_service_dicts_depends_on_self(self):
|
||||
services = [
|
||||
{
|
||||
'depends_on': ['web'],
|
||||
'name': 'web'
|
||||
},
|
||||
]
|
||||
|
||||
with pytest.raises(DependencyError) as exc:
|
||||
sort_service_dicts(services)
|
||||
assert 'A service can not depend on itself: web' in exc.exconly()
|
||||
|
|
Loading…
Reference in New Issue