mirror of https://github.com/docker/compose.git
Specify networks in Compose file
There's not yet a proper way for services to join networks Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
This commit is contained in:
parent
8fca4f1628
commit
69ed5f9c48
|
@ -696,8 +696,7 @@ def run_one_off_container(container_options, project, service, options):
|
|||
start_deps=True,
|
||||
strategy=ConvergenceStrategy.never)
|
||||
|
||||
if project.use_networking:
|
||||
project.ensure_network_exists()
|
||||
project.initialize_networks()
|
||||
|
||||
container = service.create_container(
|
||||
quiet=True,
|
||||
|
|
|
@ -139,14 +139,16 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
|
|||
return {} if self.version == 1 else self.config.get('volumes', {})
|
||||
|
||||
|
||||
class Config(namedtuple('_Config', 'version services volumes')):
|
||||
class Config(namedtuple('_Config', 'version services volumes networks')):
|
||||
"""
|
||||
:param version: configuration version
|
||||
:type version: int
|
||||
:param services: List of service description dictionaries
|
||||
:type services: :class:`list`
|
||||
:param volumes: List of volume description dictionaries
|
||||
:type volumes: :class:`list`
|
||||
:param volumes: Dictionary mapping volume names to description dictionaries
|
||||
:type volumes: :class:`dict`
|
||||
:param networks: Dictionary mapping network names to description dictionaries
|
||||
:type networks: :class:`dict`
|
||||
"""
|
||||
|
||||
|
||||
|
@ -256,39 +258,44 @@ def load(config_details):
|
|||
config_details = config_details._replace(config_files=processed_files)
|
||||
|
||||
main_file = config_details.config_files[0]
|
||||
volumes = load_volumes(config_details.config_files)
|
||||
volumes = load_mapping(config_details.config_files, 'volumes', 'Volume')
|
||||
networks = load_mapping(config_details.config_files, 'networks', 'Network')
|
||||
service_dicts = load_services(
|
||||
config_details.working_dir,
|
||||
main_file.filename,
|
||||
[file.get_service_dicts() for file in config_details.config_files],
|
||||
main_file.version)
|
||||
return Config(main_file.version, service_dicts, volumes)
|
||||
return Config(main_file.version, service_dicts, volumes, networks)
|
||||
|
||||
|
||||
def load_volumes(config_files):
|
||||
volumes = {}
|
||||
def load_mapping(config_files, key, entity_type):
|
||||
mapping = {}
|
||||
|
||||
for config_file in config_files:
|
||||
for name, volume_config in config_file.get_volumes().items():
|
||||
volumes[name] = volume_config or {}
|
||||
if not volume_config:
|
||||
for name, config in config_file.config.get(key, {}).items():
|
||||
mapping[name] = config or {}
|
||||
if not config:
|
||||
continue
|
||||
|
||||
external = volume_config.get('external')
|
||||
external = config.get('external')
|
||||
if external:
|
||||
if len(volume_config.keys()) > 1:
|
||||
if len(config.keys()) > 1:
|
||||
raise ConfigurationError(
|
||||
'Volume {0} declared as external but specifies'
|
||||
' additional attributes ({1}). '.format(
|
||||
'{} {} declared as external but specifies'
|
||||
' additional attributes ({}). '.format(
|
||||
entity_type,
|
||||
name,
|
||||
', '.join([k for k in volume_config.keys() if k != 'external'])
|
||||
', '.join([k for k in config.keys() if k != 'external'])
|
||||
)
|
||||
)
|
||||
if isinstance(external, dict):
|
||||
volume_config['external_name'] = external.get('name')
|
||||
config['external_name'] = external.get('name')
|
||||
else:
|
||||
volume_config['external_name'] = name
|
||||
config['external_name'] = name
|
||||
|
||||
return volumes
|
||||
mapping[name] = config
|
||||
|
||||
return mapping
|
||||
|
||||
|
||||
def load_services(working_dir, filename, service_configs, version):
|
||||
|
|
|
@ -17,6 +17,15 @@
|
|||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"networks": {
|
||||
"id": "#/properties/networks",
|
||||
"type": "object",
|
||||
"patternProperties": {
|
||||
"^[a-zA-Z0-9._-]+$": {
|
||||
"$ref": "#/definitions/network"
|
||||
}
|
||||
}
|
||||
},
|
||||
"volumes": {
|
||||
"id": "#/properties/volumes",
|
||||
"type": "object",
|
||||
|
@ -30,6 +39,10 @@
|
|||
},
|
||||
|
||||
"definitions": {
|
||||
"network": {
|
||||
"id": "#/definitions/network",
|
||||
"type": "object"
|
||||
},
|
||||
"volume": {
|
||||
"id": "#/definitions/volume",
|
||||
"type": ["object", "null"],
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import logging
|
||||
|
||||
from docker.errors import NotFound
|
||||
|
||||
from .config import ConfigurationError
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Network(object):
|
||||
def __init__(self, client, project, name, driver=None, driver_opts=None):
|
||||
self.client = client
|
||||
self.project = project
|
||||
self.name = name
|
||||
self.driver = driver
|
||||
self.driver_opts = driver_opts
|
||||
|
||||
def ensure(self):
|
||||
try:
|
||||
data = self.inspect()
|
||||
if self.driver and data['Driver'] != self.driver:
|
||||
raise ConfigurationError(
|
||||
'Network {} needs to be recreated - driver has changed'
|
||||
.format(self.full_name))
|
||||
if data['Options'] != (self.driver_opts or {}):
|
||||
raise ConfigurationError(
|
||||
'Network {} needs to be recreated - options have changed'
|
||||
.format(self.full_name))
|
||||
except NotFound:
|
||||
driver_name = 'the default driver'
|
||||
if self.driver:
|
||||
driver_name = 'driver "{}"'.format(self.driver)
|
||||
|
||||
log.info(
|
||||
'Creating network "{}" with {}'
|
||||
.format(self.full_name, driver_name)
|
||||
)
|
||||
|
||||
self.client.create_network(
|
||||
self.full_name, self.driver, self.driver_opts
|
||||
)
|
||||
|
||||
def remove(self):
|
||||
# TODO: don't remove external networks
|
||||
log.info("Removing network {}".format(self.full_name))
|
||||
self.client.remove_network(self.full_name)
|
||||
|
||||
def inspect(self):
|
||||
return self.client.inspect_network(self.full_name)
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
return '{0}_{1}'.format(self.project, self.name)
|
|
@ -17,6 +17,7 @@ from .const import LABEL_ONE_OFF
|
|||
from .const import LABEL_PROJECT
|
||||
from .const import LABEL_SERVICE
|
||||
from .container import Container
|
||||
from .network import Network
|
||||
from .service import ContainerNet
|
||||
from .service import ConvergenceStrategy
|
||||
from .service import Net
|
||||
|
@ -33,12 +34,14 @@ class Project(object):
|
|||
"""
|
||||
A collection of services.
|
||||
"""
|
||||
def __init__(self, name, services, client, volumes=None, use_networking=False, network_driver=None):
|
||||
def __init__(self, name, services, client, networks=None, volumes=None,
|
||||
use_networking=False, network_driver=None):
|
||||
self.name = name
|
||||
self.services = services
|
||||
self.client = client
|
||||
self.use_networking = use_networking
|
||||
self.network_driver = network_driver
|
||||
self.networks = networks or []
|
||||
self.volumes = volumes or []
|
||||
|
||||
def labels(self, one_off=False):
|
||||
|
@ -55,9 +58,6 @@ class Project(object):
|
|||
use_networking = (config_data.version and config_data.version >= 2)
|
||||
project = cls(name, [], client, use_networking=use_networking)
|
||||
|
||||
if use_networking:
|
||||
remove_links(config_data.services)
|
||||
|
||||
for service_dict in config_data.services:
|
||||
links = project.get_links(service_dict)
|
||||
volumes_from = get_volumes_from(project, service_dict)
|
||||
|
@ -72,6 +72,16 @@ class Project(object):
|
|||
net=net,
|
||||
volumes_from=volumes_from,
|
||||
**service_dict))
|
||||
|
||||
if config_data.networks:
|
||||
for network_name, data in config_data.networks.items():
|
||||
project.networks.append(
|
||||
Network(
|
||||
client=client, project=name, name=network_name,
|
||||
driver=data.get('driver'), driver_opts=data.get('driver_opts')
|
||||
)
|
||||
)
|
||||
|
||||
if config_data.volumes:
|
||||
for vol_name, data in config_data.volumes.items():
|
||||
project.volumes.append(
|
||||
|
@ -82,6 +92,7 @@ class Project(object):
|
|||
external_name=data.get('external_name')
|
||||
)
|
||||
)
|
||||
|
||||
return project
|
||||
|
||||
@property
|
||||
|
@ -124,20 +135,18 @@ class Project(object):
|
|||
Raises NoSuchService if any of the named services do not exist.
|
||||
"""
|
||||
if service_names is None or len(service_names) == 0:
|
||||
return self.get_services(
|
||||
service_names=self.service_names,
|
||||
include_deps=include_deps
|
||||
)
|
||||
else:
|
||||
unsorted = [self.get_service(name) for name in service_names]
|
||||
services = [s for s in self.services if s in unsorted]
|
||||
service_names = self.service_names
|
||||
|
||||
if include_deps:
|
||||
services = reduce(self._inject_deps, services, [])
|
||||
unsorted = [self.get_service(name) for name in service_names]
|
||||
services = [s for s in self.services if s in unsorted]
|
||||
|
||||
uniques = []
|
||||
[uniques.append(s) for s in services if s not in uniques]
|
||||
return uniques
|
||||
if include_deps:
|
||||
services = reduce(self._inject_deps, services, [])
|
||||
|
||||
uniques = []
|
||||
[uniques.append(s) for s in services if s not in uniques]
|
||||
|
||||
return uniques
|
||||
|
||||
def get_services_without_duplicate(self, service_names=None, include_deps=False):
|
||||
services = self.get_services(service_names, include_deps)
|
||||
|
@ -166,7 +175,7 @@ class Project(object):
|
|||
net = service_dict.pop('net', None)
|
||||
if not net:
|
||||
if self.use_networking:
|
||||
return Net(self.default_network_name)
|
||||
return Net(self.default_network.full_name)
|
||||
return Net(None)
|
||||
|
||||
net_name = get_service_name_from_net(net)
|
||||
|
@ -251,7 +260,7 @@ class Project(object):
|
|||
def down(self, remove_image_type, include_volumes):
|
||||
self.stop()
|
||||
self.remove_stopped(v=include_volumes)
|
||||
self.remove_network()
|
||||
self.remove_default_network()
|
||||
|
||||
if include_volumes:
|
||||
self.remove_volumes()
|
||||
|
@ -262,10 +271,34 @@ class Project(object):
|
|||
for service in self.get_services():
|
||||
service.remove_image(remove_image_type)
|
||||
|
||||
def remove_default_network(self):
|
||||
if not self.use_networking:
|
||||
return
|
||||
if self.uses_default_network():
|
||||
self.default_network.remove()
|
||||
|
||||
def remove_volumes(self):
|
||||
for volume in self.volumes:
|
||||
volume.remove()
|
||||
|
||||
def initialize_networks(self):
|
||||
networks = self.networks
|
||||
if self.uses_default_network():
|
||||
networks.append(self.default_network)
|
||||
|
||||
for network in networks:
|
||||
network.ensure()
|
||||
|
||||
def uses_default_network(self):
|
||||
return any(
|
||||
service.net.mode == self.default_network.full_name
|
||||
for service in self.services
|
||||
)
|
||||
|
||||
@property
|
||||
def default_network(self):
|
||||
return Network(client=self.client, project=self.name, name='default')
|
||||
|
||||
def restart(self, service_names=None, **options):
|
||||
containers = self.containers(service_names, stopped=True)
|
||||
parallel.parallel_restart(containers, options)
|
||||
|
@ -335,9 +368,7 @@ class Project(object):
|
|||
|
||||
plans = self._get_convergence_plans(services, strategy)
|
||||
|
||||
if self.use_networking and self.uses_default_network():
|
||||
self.ensure_network_exists()
|
||||
|
||||
self.initialize_networks()
|
||||
self.initialize_volumes()
|
||||
|
||||
return [
|
||||
|
@ -395,40 +426,6 @@ class Project(object):
|
|||
|
||||
return [c for c in containers if matches_service_names(c)]
|
||||
|
||||
def get_network(self):
|
||||
try:
|
||||
return self.client.inspect_network(self.default_network_name)
|
||||
except NotFound:
|
||||
return None
|
||||
|
||||
def ensure_network_exists(self):
|
||||
# TODO: recreate network if driver has changed?
|
||||
if self.get_network() is None:
|
||||
driver_name = 'the default driver'
|
||||
if self.network_driver:
|
||||
driver_name = 'driver "{}"'.format(self.network_driver)
|
||||
|
||||
log.info(
|
||||
'Creating network "{}" with {}'
|
||||
.format(self.default_network_name, driver_name)
|
||||
)
|
||||
self.client.create_network(self.default_network_name, driver=self.network_driver)
|
||||
|
||||
def remove_network(self):
|
||||
if not self.use_networking:
|
||||
return
|
||||
network = self.get_network()
|
||||
if network:
|
||||
log.info("Removing network %s", self.default_network_name)
|
||||
self.client.remove_network(network['Id'])
|
||||
|
||||
def uses_default_network(self):
|
||||
return any(service.net.mode == self.default_network_name for service in self.services)
|
||||
|
||||
@property
|
||||
def default_network_name(self):
|
||||
return '{}_default'.format(self.name)
|
||||
|
||||
def _inject_deps(self, acc, service):
|
||||
dep_names = service.get_dependency_names()
|
||||
|
||||
|
@ -444,26 +441,6 @@ class Project(object):
|
|||
return acc + dep_services
|
||||
|
||||
|
||||
def remove_links(service_dicts):
|
||||
services_with_links = [s for s in service_dicts if 'links' in s]
|
||||
if not services_with_links:
|
||||
return
|
||||
|
||||
if len(services_with_links) == 1:
|
||||
prefix = '"{}" defines'.format(services_with_links[0]['name'])
|
||||
else:
|
||||
prefix = 'Some services ({}) define'.format(
|
||||
", ".join('"{}"'.format(s['name']) for s in services_with_links))
|
||||
|
||||
log.warn(
|
||||
'\n{} links, which are not compatible with Docker networking and will be ignored.\n'
|
||||
'Future versions of Docker will not support links - you should remove them for '
|
||||
'forwards-compatibility.\n'.format(prefix))
|
||||
|
||||
for s in services_with_links:
|
||||
del s['links']
|
||||
|
||||
|
||||
def get_volumes_from(project, service_dict):
|
||||
volumes_from = service_dict.pop('volumes_from', None)
|
||||
if not volumes_from:
|
||||
|
|
|
@ -354,7 +354,7 @@ class CLITestCase(DockerClientTestCase):
|
|||
self.base_dir = 'tests/fixtures/links-composefile'
|
||||
self.dispatch(['up', '-d'], None)
|
||||
|
||||
networks = self.client.networks(names=[self.project.default_network_name])
|
||||
networks = self.client.networks(names=[self.project.default_network.full_name])
|
||||
self.assertEqual(len(networks), 0)
|
||||
|
||||
for service in self.project.get_services():
|
||||
|
@ -371,7 +371,7 @@ class CLITestCase(DockerClientTestCase):
|
|||
|
||||
services = self.project.get_services()
|
||||
|
||||
networks = self.client.networks(names=[self.project.default_network_name])
|
||||
networks = self.client.networks(names=[self.project.default_network.full_name])
|
||||
for n in networks:
|
||||
self.addCleanup(self.client.remove_network, n['Id'])
|
||||
self.assertEqual(len(networks), 1)
|
||||
|
@ -388,6 +388,19 @@ class CLITestCase(DockerClientTestCase):
|
|||
web_container = self.project.get_service('simple').containers()[0]
|
||||
self.assertFalse(web_container.get('HostConfig.Links'))
|
||||
|
||||
def test_up_with_networks(self):
|
||||
self.base_dir = 'tests/fixtures/networks'
|
||||
self.dispatch(['up', '-d'], None)
|
||||
|
||||
networks = self.client.networks(names=[
|
||||
'{}_{}'.format(self.project.name, n)
|
||||
for n in ['foo', 'bar']])
|
||||
|
||||
self.assertEqual(len(networks), 2)
|
||||
|
||||
for net in networks:
|
||||
self.assertEqual(net['Driver'], 'bridge')
|
||||
|
||||
def test_up_with_links_is_invalid(self):
|
||||
self.base_dir = 'tests/fixtures/v2-simple'
|
||||
|
||||
|
@ -698,7 +711,7 @@ class CLITestCase(DockerClientTestCase):
|
|||
self.dispatch(['run', 'simple', 'true'], None)
|
||||
service = self.project.get_service('simple')
|
||||
container, = service.containers(stopped=True, one_off=True)
|
||||
networks = self.client.networks(names=[self.project.default_network_name])
|
||||
networks = self.client.networks(names=[self.project.default_network.full_name])
|
||||
for n in networks:
|
||||
self.addCleanup(self.client.remove_network, n['Id'])
|
||||
self.assertEqual(len(networks), 1)
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
version: 2
|
||||
|
||||
networks:
|
||||
foo:
|
||||
driver:
|
||||
|
||||
bar: {}
|
|
@ -0,0 +1,9 @@
|
|||
db:
|
||||
image: busybox:latest
|
||||
command: top
|
||||
web:
|
||||
image: busybox:latest
|
||||
command: top
|
||||
console:
|
||||
image: busybox:latest
|
||||
command: top
|
|
@ -14,7 +14,6 @@ from compose.const import LABEL_PROJECT
|
|||
from compose.container import Container
|
||||
from compose.project import Project
|
||||
from compose.service import ConvergenceStrategy
|
||||
from compose.service import Net
|
||||
|
||||
|
||||
def build_service_dicts(service_config):
|
||||
|
@ -104,21 +103,6 @@ class ProjectTest(DockerClientTestCase):
|
|||
db = project.get_service('db')
|
||||
self.assertEqual(db._get_volumes_from(), [data_container.id + ':rw'])
|
||||
|
||||
def test_get_network_does_not_exist(self):
|
||||
project = Project('composetest', [], self.client)
|
||||
assert project.get_network() is None
|
||||
|
||||
def test_get_network(self):
|
||||
project_name = 'network_does_exist'
|
||||
network_name = '{}_default'.format(project_name)
|
||||
|
||||
project = Project(project_name, [], self.client)
|
||||
self.client.create_network(network_name)
|
||||
self.addCleanup(self.client.remove_network, network_name)
|
||||
|
||||
assert isinstance(project.get_network(), dict)
|
||||
assert project.get_network()['Name'] == network_name
|
||||
|
||||
def test_net_from_service(self):
|
||||
project = Project.from_config(
|
||||
name='composetest',
|
||||
|
@ -473,18 +457,6 @@ class ProjectTest(DockerClientTestCase):
|
|||
self.assertEqual(len(project.get_service('data').containers(stopped=True)), 1)
|
||||
self.assertEqual(len(project.get_service('console').containers()), 0)
|
||||
|
||||
def test_project_up_with_custom_network(self):
|
||||
network_name = 'composetest-custom'
|
||||
|
||||
self.client.create_network(network_name)
|
||||
self.addCleanup(self.client.remove_network, network_name)
|
||||
|
||||
web = self.create_service('web', net=Net(network_name))
|
||||
project = Project('composetest', [web], self.client, use_networking=True)
|
||||
project.up()
|
||||
|
||||
assert project.get_network() is None
|
||||
|
||||
def test_unscale_after_restart(self):
|
||||
web = self.create_service('web')
|
||||
project = Project('composetest', [web], self.client)
|
||||
|
@ -510,15 +482,50 @@ class ProjectTest(DockerClientTestCase):
|
|||
service = project.get_service('web')
|
||||
self.assertEqual(len(service.containers()), 1)
|
||||
|
||||
def test_project_up_networks(self):
|
||||
config_data = config.Config(
|
||||
version=2,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top',
|
||||
}],
|
||||
volumes={},
|
||||
networks={
|
||||
'foo': {'driver': 'bridge'},
|
||||
'bar': {'driver': None},
|
||||
'baz': {},
|
||||
},
|
||||
)
|
||||
|
||||
project = Project.from_config(
|
||||
client=self.client,
|
||||
name='composetest',
|
||||
config_data=config_data,
|
||||
)
|
||||
project.up()
|
||||
self.assertEqual(len(project.containers()), 1)
|
||||
|
||||
for net_name in ['foo', 'bar', 'baz']:
|
||||
full_net_name = 'composetest_{}'.format(net_name)
|
||||
network_data = self.client.inspect_network(full_net_name)
|
||||
self.assertEqual(network_data['Name'], full_net_name)
|
||||
|
||||
foo_data = self.client.inspect_network('composetest_foo')
|
||||
self.assertEqual(foo_data['Driver'], 'bridge')
|
||||
|
||||
def test_project_up_volumes(self):
|
||||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||
config_data = config.Config(
|
||||
version=2, services=[{
|
||||
version=2,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top'
|
||||
}], volumes={vol_name: {'driver': 'local'}}
|
||||
}],
|
||||
volumes={vol_name: {'driver': 'local'}},
|
||||
networks={},
|
||||
)
|
||||
|
||||
project = Project.from_config(
|
||||
|
@ -587,11 +594,14 @@ class ProjectTest(DockerClientTestCase):
|
|||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||
config_data = config.Config(
|
||||
version=2, services=[{
|
||||
version=2,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top'
|
||||
}], volumes={vol_name: {}}
|
||||
}],
|
||||
volumes={vol_name: {}},
|
||||
networks={},
|
||||
)
|
||||
|
||||
project = Project.from_config(
|
||||
|
@ -608,11 +618,14 @@ class ProjectTest(DockerClientTestCase):
|
|||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||
config_data = config.Config(
|
||||
version=2, services=[{
|
||||
version=2,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top'
|
||||
}], volumes={vol_name: {}}
|
||||
}],
|
||||
volumes={vol_name: {}},
|
||||
networks={},
|
||||
)
|
||||
|
||||
project = Project.from_config(
|
||||
|
@ -629,11 +642,14 @@ class ProjectTest(DockerClientTestCase):
|
|||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||
|
||||
config_data = config.Config(
|
||||
version=2, services=[{
|
||||
version=2,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top'
|
||||
}], volumes={vol_name: {'driver': 'foobar'}}
|
||||
}],
|
||||
volumes={vol_name: {'driver': 'foobar'}},
|
||||
networks={},
|
||||
)
|
||||
|
||||
project = Project.from_config(
|
||||
|
@ -648,11 +664,14 @@ class ProjectTest(DockerClientTestCase):
|
|||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||
|
||||
config_data = config.Config(
|
||||
version=2, services=[{
|
||||
version=2,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top'
|
||||
}], volumes={vol_name: {'driver': 'local'}}
|
||||
}],
|
||||
volumes={vol_name: {'driver': 'local'}},
|
||||
networks={},
|
||||
)
|
||||
project = Project.from_config(
|
||||
name='composetest',
|
||||
|
@ -683,13 +702,16 @@ class ProjectTest(DockerClientTestCase):
|
|||
full_vol_name = 'composetest_{0}'.format(vol_name)
|
||||
self.client.create_volume(vol_name)
|
||||
config_data = config.Config(
|
||||
version=2, services=[{
|
||||
version=2,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top'
|
||||
}], volumes={
|
||||
}],
|
||||
volumes={
|
||||
vol_name: {'external': True, 'external_name': vol_name}
|
||||
}
|
||||
},
|
||||
networks=None,
|
||||
)
|
||||
project = Project.from_config(
|
||||
name='composetest',
|
||||
|
@ -704,13 +726,16 @@ class ProjectTest(DockerClientTestCase):
|
|||
vol_name = '{0:x}'.format(random.getrandbits(32))
|
||||
|
||||
config_data = config.Config(
|
||||
version=2, services=[{
|
||||
version=2,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
'command': 'top'
|
||||
}], volumes={
|
||||
}],
|
||||
volumes={
|
||||
vol_name: {'external': True, 'external_name': vol_name}
|
||||
}
|
||||
},
|
||||
networks=None,
|
||||
)
|
||||
project = Project.from_config(
|
||||
name='composetest',
|
||||
|
|
|
@ -21,35 +21,27 @@ class ProjectTest(unittest.TestCase):
|
|||
def setUp(self):
|
||||
self.mock_client = mock.create_autospec(docker.Client)
|
||||
|
||||
def test_from_dict(self):
|
||||
project = Project.from_config('composetest', Config(None, [
|
||||
{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest'
|
||||
},
|
||||
{
|
||||
'name': 'db',
|
||||
'image': 'busybox:latest'
|
||||
},
|
||||
], None), None)
|
||||
self.assertEqual(len(project.services), 2)
|
||||
self.assertEqual(project.get_service('web').name, 'web')
|
||||
self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
|
||||
self.assertEqual(project.get_service('db').name, 'db')
|
||||
self.assertEqual(project.get_service('db').options['image'], 'busybox:latest')
|
||||
|
||||
def test_from_config(self):
|
||||
config = Config(None, [
|
||||
{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
},
|
||||
{
|
||||
'name': 'db',
|
||||
'image': 'busybox:latest',
|
||||
},
|
||||
], None)
|
||||
project = Project.from_config('composetest', config, None)
|
||||
config = Config(
|
||||
version=None,
|
||||
services=[
|
||||
{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
},
|
||||
{
|
||||
'name': 'db',
|
||||
'image': 'busybox:latest',
|
||||
},
|
||||
],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
)
|
||||
project = Project.from_config(
|
||||
name='composetest',
|
||||
config_data=config,
|
||||
client=None,
|
||||
)
|
||||
self.assertEqual(len(project.services), 2)
|
||||
self.assertEqual(project.get_service('web').name, 'web')
|
||||
self.assertEqual(project.get_service('web').options['image'], 'busybox:latest')
|
||||
|
@ -58,16 +50,21 @@ class ProjectTest(unittest.TestCase):
|
|||
self.assertFalse(project.use_networking)
|
||||
|
||||
def test_from_config_v2(self):
|
||||
config = Config(2, [
|
||||
{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
},
|
||||
{
|
||||
'name': 'db',
|
||||
'image': 'busybox:latest',
|
||||
},
|
||||
], None)
|
||||
config = Config(
|
||||
version=2,
|
||||
services=[
|
||||
{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
},
|
||||
{
|
||||
'name': 'db',
|
||||
'image': 'busybox:latest',
|
||||
},
|
||||
],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
)
|
||||
project = Project.from_config('composetest', config, None)
|
||||
self.assertEqual(len(project.services), 2)
|
||||
self.assertTrue(project.use_networking)
|
||||
|
@ -161,13 +158,20 @@ class ProjectTest(unittest.TestCase):
|
|||
container_id = 'aabbccddee'
|
||||
container_dict = dict(Name='aaa', Id=container_id)
|
||||
self.mock_client.inspect_container.return_value = container_dict
|
||||
project = Project.from_config('test', Config(None, [
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'volumes_from': [VolumeFromSpec('aaa', 'rw', 'container')]
|
||||
}
|
||||
], None), self.mock_client)
|
||||
project = Project.from_config(
|
||||
name='test',
|
||||
client=self.mock_client,
|
||||
config_data=Config(
|
||||
version=None,
|
||||
services=[{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'volumes_from': [VolumeFromSpec('aaa', 'rw', 'container')]
|
||||
}],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
),
|
||||
)
|
||||
assert project.get_service('test')._get_volumes_from() == [container_id + ":rw"]
|
||||
|
||||
def test_use_volumes_from_service_no_container(self):
|
||||
|
@ -180,33 +184,51 @@ class ProjectTest(unittest.TestCase):
|
|||
"Image": 'busybox:latest'
|
||||
}
|
||||
]
|
||||
project = Project.from_config('test', Config(None, [
|
||||
{
|
||||
'name': 'vol',
|
||||
'image': 'busybox:latest'
|
||||
},
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
|
||||
}
|
||||
], None), self.mock_client)
|
||||
project = Project.from_config(
|
||||
name='test',
|
||||
client=self.mock_client,
|
||||
config_data=Config(
|
||||
version=None,
|
||||
services=[
|
||||
{
|
||||
'name': 'vol',
|
||||
'image': 'busybox:latest'
|
||||
},
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
|
||||
}
|
||||
],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
),
|
||||
)
|
||||
assert project.get_service('test')._get_volumes_from() == [container_name + ":rw"]
|
||||
|
||||
def test_use_volumes_from_service_container(self):
|
||||
container_ids = ['aabbccddee', '12345']
|
||||
|
||||
project = Project.from_config('test', Config(None, [
|
||||
{
|
||||
'name': 'vol',
|
||||
'image': 'busybox:latest'
|
||||
},
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
|
||||
}
|
||||
], None), None)
|
||||
project = Project.from_config(
|
||||
name='test',
|
||||
client=None,
|
||||
config_data=Config(
|
||||
version=None,
|
||||
services=[
|
||||
{
|
||||
'name': 'vol',
|
||||
'image': 'busybox:latest'
|
||||
},
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'volumes_from': [VolumeFromSpec('vol', 'rw', 'service')]
|
||||
}
|
||||
],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
),
|
||||
)
|
||||
with mock.patch.object(Service, 'containers') as mock_return:
|
||||
mock_return.return_value = [
|
||||
mock.Mock(id=container_id, spec=Container)
|
||||
|
@ -313,12 +335,21 @@ class ProjectTest(unittest.TestCase):
|
|||
]
|
||||
|
||||
def test_net_unset(self):
|
||||
project = Project.from_config('test', Config(None, [
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
}
|
||||
], None), self.mock_client)
|
||||
project = Project.from_config(
|
||||
name='test',
|
||||
client=self.mock_client,
|
||||
config_data=Config(
|
||||
version=None,
|
||||
services=[
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
}
|
||||
],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
),
|
||||
)
|
||||
service = project.get_service('test')
|
||||
self.assertEqual(service.net.id, None)
|
||||
self.assertNotIn('NetworkMode', service._get_container_host_config({}))
|
||||
|
@ -327,13 +358,22 @@ class ProjectTest(unittest.TestCase):
|
|||
container_id = 'aabbccddee'
|
||||
container_dict = dict(Name='aaa', Id=container_id)
|
||||
self.mock_client.inspect_container.return_value = container_dict
|
||||
project = Project.from_config('test', Config(None, [
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'net': 'container:aaa'
|
||||
}
|
||||
], None), self.mock_client)
|
||||
project = Project.from_config(
|
||||
name='test',
|
||||
client=self.mock_client,
|
||||
config_data=Config(
|
||||
version=None,
|
||||
services=[
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'net': 'container:aaa'
|
||||
},
|
||||
],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
),
|
||||
)
|
||||
service = project.get_service('test')
|
||||
self.assertEqual(service.net.mode, 'container:' + container_id)
|
||||
|
||||
|
@ -347,17 +387,26 @@ class ProjectTest(unittest.TestCase):
|
|||
"Image": 'busybox:latest'
|
||||
}
|
||||
]
|
||||
project = Project.from_config('test', Config(None, [
|
||||
{
|
||||
'name': 'aaa',
|
||||
'image': 'busybox:latest'
|
||||
},
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'net': 'container:aaa'
|
||||
}
|
||||
], None), self.mock_client)
|
||||
project = Project.from_config(
|
||||
name='test',
|
||||
client=self.mock_client,
|
||||
config_data=Config(
|
||||
version=None,
|
||||
services=[
|
||||
{
|
||||
'name': 'aaa',
|
||||
'image': 'busybox:latest'
|
||||
},
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'busybox:latest',
|
||||
'net': 'container:aaa'
|
||||
},
|
||||
],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
),
|
||||
)
|
||||
|
||||
service = project.get_service('test')
|
||||
self.assertEqual(service.net.mode, 'container:' + container_name)
|
||||
|
@ -403,11 +452,16 @@ class ProjectTest(unittest.TestCase):
|
|||
},
|
||||
}
|
||||
project = Project.from_config(
|
||||
'test',
|
||||
Config(None, [{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
}], None),
|
||||
self.mock_client,
|
||||
name='test',
|
||||
client=self.mock_client,
|
||||
config_data=Config(
|
||||
version=None,
|
||||
services=[{
|
||||
'name': 'web',
|
||||
'image': 'busybox:latest',
|
||||
}],
|
||||
networks=None,
|
||||
volumes=None,
|
||||
),
|
||||
)
|
||||
self.assertEqual([c.id for c in project.containers()], ['1'])
|
||||
|
|
Loading…
Reference in New Issue