|
|
@@ -6,7 +6,6 @@ import logging
|
|
|
from functools import reduce
|
|
|
|
|
|
from docker.errors import APIError
|
|
|
-from docker.errors import NotFound
|
|
|
|
|
|
from . import parallel
|
|
|
from .config import ConfigurationError
|
|
|
@@ -19,14 +18,16 @@ 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 .network import build_networks
|
|
|
+from .network import get_networks
|
|
|
+from .network import ProjectNetworks
|
|
|
from .service import ContainerNetworkMode
|
|
|
from .service import ConvergenceStrategy
|
|
|
from .service import NetworkMode
|
|
|
from .service import Service
|
|
|
from .service import ServiceNetworkMode
|
|
|
from .utils import microseconds_from_time_nano
|
|
|
-from .volume import Volume
|
|
|
+from .volume import ProjectVolumes
|
|
|
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
@@ -36,15 +37,12 @@ class Project(object):
|
|
|
"""
|
|
|
A collection of services.
|
|
|
"""
|
|
|
- def __init__(self, name, services, client, networks=None, volumes=None,
|
|
|
- use_networking=False, network_driver=None):
|
|
|
+ def __init__(self, name, services, client, networks=None, volumes=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 {}
|
|
|
+ self.volumes = volumes or ProjectVolumes({})
|
|
|
+ self.networks = networks or ProjectNetworks({}, False)
|
|
|
|
|
|
def labels(self, one_off=False):
|
|
|
return [
|
|
|
@@ -58,68 +56,45 @@ class Project(object):
|
|
|
Construct a Project from a config.Config object.
|
|
|
"""
|
|
|
use_networking = (config_data.version and config_data.version != V1)
|
|
|
- project = cls(name, [], client, use_networking=use_networking)
|
|
|
-
|
|
|
- network_config = config_data.networks or {}
|
|
|
- custom_networks = [
|
|
|
- Network(
|
|
|
- client=client, project=name, name=network_name,
|
|
|
- driver=data.get('driver'),
|
|
|
- driver_opts=data.get('driver_opts'),
|
|
|
- ipam=data.get('ipam'),
|
|
|
- external_name=data.get('external_name'),
|
|
|
- )
|
|
|
- for network_name, data in network_config.items()
|
|
|
- ]
|
|
|
-
|
|
|
- all_networks = custom_networks[:]
|
|
|
- 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[vol_name] = Volume(
|
|
|
- client=client, project=name, name=vol_name,
|
|
|
- driver=data.get('driver'),
|
|
|
- driver_opts=data.get('driver_opts'),
|
|
|
- external_name=data.get('external_name')
|
|
|
- )
|
|
|
+ networks = build_networks(name, config_data, client)
|
|
|
+ project_networks = ProjectNetworks.from_services(
|
|
|
+ config_data.services,
|
|
|
+ networks,
|
|
|
+ use_networking)
|
|
|
+ volumes = ProjectVolumes.from_config(name, config_data, client)
|
|
|
+ project = cls(name, [], client, project_networks, volumes)
|
|
|
|
|
|
for service_dict in config_data.services:
|
|
|
+ service_dict = dict(service_dict)
|
|
|
if use_networking:
|
|
|
- networks = get_networks(service_dict, all_networks)
|
|
|
+ service_networks = get_networks(service_dict, networks)
|
|
|
else:
|
|
|
- networks = []
|
|
|
+ service_networks = []
|
|
|
|
|
|
+ service_dict.pop('networks', None)
|
|
|
links = project.get_links(service_dict)
|
|
|
- network_mode = project.get_network_mode(service_dict, networks)
|
|
|
+ network_mode = project.get_network_mode(service_dict, service_networks)
|
|
|
volumes_from = get_volumes_from(project, service_dict)
|
|
|
|
|
|
if config_data.version != V1:
|
|
|
- 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)
|
|
|
- )
|
|
|
+ service_dict['volumes'] = [
|
|
|
+ volumes.namespace_spec(volume_spec)
|
|
|
+ for volume_spec in service_dict.get('volumes', [])
|
|
|
+ ]
|
|
|
|
|
|
project.services.append(
|
|
|
Service(
|
|
|
+ service_dict.pop('name'),
|
|
|
client=client,
|
|
|
project=name,
|
|
|
use_networking=use_networking,
|
|
|
- networks=networks,
|
|
|
+ networks=service_networks,
|
|
|
links=links,
|
|
|
network_mode=network_mode,
|
|
|
volumes_from=volumes_from,
|
|
|
**service_dict)
|
|
|
)
|
|
|
|
|
|
- project.networks += custom_networks
|
|
|
- if 'default' not in network_config and project.uses_default_network():
|
|
|
- project.networks.append(project.default_network)
|
|
|
-
|
|
|
return project
|
|
|
|
|
|
@property
|
|
|
@@ -201,7 +176,7 @@ class Project(object):
|
|
|
def get_network_mode(self, service_dict, networks):
|
|
|
network_mode = service_dict.pop('network_mode', None)
|
|
|
if not network_mode:
|
|
|
- if self.use_networking:
|
|
|
+ if self.networks.use_networking:
|
|
|
return NetworkMode(networks[0]) if networks else NetworkMode('none')
|
|
|
return NetworkMode(None)
|
|
|
|
|
|
@@ -246,49 +221,13 @@ class Project(object):
|
|
|
def remove_stopped(self, service_names=None, **options):
|
|
|
parallel.parallel_remove(self.containers(service_names, stopped=True), options)
|
|
|
|
|
|
- def initialize_volumes(self):
|
|
|
- try:
|
|
|
- for volume in self.volumes.values():
|
|
|
- if volume.external:
|
|
|
- log.debug(
|
|
|
- 'Volume {0} declared as external. No new '
|
|
|
- 'volume will be created.'.format(volume.name)
|
|
|
- )
|
|
|
- if not volume.exists():
|
|
|
- raise ConfigurationError(
|
|
|
- 'Volume {name} declared as external, but could'
|
|
|
- ' not be found. Please create the volume manually'
|
|
|
- ' using `{command}{name}` and try again.'.format(
|
|
|
- name=volume.full_name,
|
|
|
- command='docker volume create --name='
|
|
|
- )
|
|
|
- )
|
|
|
- continue
|
|
|
- volume.create()
|
|
|
- except NotFound:
|
|
|
- raise ConfigurationError(
|
|
|
- 'Volume %s specifies nonexistent driver %s' % (volume.name, volume.driver)
|
|
|
- )
|
|
|
- except APIError as e:
|
|
|
- if 'Choose a different volume name' in str(e):
|
|
|
- raise ConfigurationError(
|
|
|
- 'Configuration for volume {0} specifies driver {1}, but '
|
|
|
- 'a volume with the same name uses a different driver '
|
|
|
- '({3}). If you wish to use the new configuration, please '
|
|
|
- 'remove the existing volume "{2}" first:\n'
|
|
|
- '$ docker volume rm {2}'.format(
|
|
|
- volume.name, volume.driver, volume.full_name,
|
|
|
- volume.inspect()['Driver']
|
|
|
- )
|
|
|
- )
|
|
|
-
|
|
|
def down(self, remove_image_type, include_volumes):
|
|
|
self.stop()
|
|
|
self.remove_stopped(v=include_volumes)
|
|
|
- self.remove_networks()
|
|
|
+ self.networks.remove()
|
|
|
|
|
|
if include_volumes:
|
|
|
- self.remove_volumes()
|
|
|
+ self.volumes.remove()
|
|
|
|
|
|
self.remove_images(remove_image_type)
|
|
|
|
|
|
@@ -296,33 +235,6 @@ class Project(object):
|
|
|
for service in self.get_services():
|
|
|
service.remove_image(remove_image_type)
|
|
|
|
|
|
- def remove_networks(self):
|
|
|
- if not self.use_networking:
|
|
|
- return
|
|
|
- for network in self.networks:
|
|
|
- network.remove()
|
|
|
-
|
|
|
- def remove_volumes(self):
|
|
|
- for volume in self.volumes.values():
|
|
|
- volume.remove()
|
|
|
-
|
|
|
- def initialize_networks(self):
|
|
|
- if not self.use_networking:
|
|
|
- return
|
|
|
-
|
|
|
- for network in self.networks:
|
|
|
- network.ensure()
|
|
|
-
|
|
|
- def uses_default_network(self):
|
|
|
- return any(
|
|
|
- self.default_network.full_name in service.networks
|
|
|
- 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)
|
|
|
@@ -388,13 +300,12 @@ class Project(object):
|
|
|
timeout=DEFAULT_TIMEOUT,
|
|
|
detached=False):
|
|
|
|
|
|
- services = self.get_services_without_duplicate(service_names, include_deps=start_deps)
|
|
|
+ self.initialize()
|
|
|
+ services = self.get_services_without_duplicate(
|
|
|
+ service_names,
|
|
|
+ include_deps=start_deps)
|
|
|
|
|
|
plans = self._get_convergence_plans(services, strategy)
|
|
|
-
|
|
|
- self.initialize_networks()
|
|
|
- self.initialize_volumes()
|
|
|
-
|
|
|
return [
|
|
|
container
|
|
|
for service in services
|
|
|
@@ -406,6 +317,10 @@ class Project(object):
|
|
|
)
|
|
|
]
|
|
|
|
|
|
+ def initialize(self):
|
|
|
+ self.networks.initialize()
|
|
|
+ self.volumes.initialize()
|
|
|
+
|
|
|
def _get_convergence_plans(self, services, strategy):
|
|
|
plans = {}
|
|
|
|
|
|
@@ -465,22 +380,6 @@ class Project(object):
|
|
|
return acc + dep_services
|
|
|
|
|
|
|
|
|
-def get_networks(service_dict, network_definitions):
|
|
|
- if 'network_mode' in service_dict:
|
|
|
- return []
|
|
|
-
|
|
|
- networks = []
|
|
|
- for name in service_dict.pop('networks', ['default']):
|
|
|
- matches = [n for n in network_definitions if n.name == name]
|
|
|
- if matches:
|
|
|
- networks.append(matches[0].full_name)
|
|
|
- else:
|
|
|
- raise ConfigurationError(
|
|
|
- 'Service "{}" uses an undefined network "{}"'
|
|
|
- .format(service_dict['name'], name))
|
|
|
- return networks
|
|
|
-
|
|
|
-
|
|
|
def get_volumes_from(project, service_dict):
|
|
|
volumes_from = service_dict.pop('volumes_from', None)
|
|
|
if not volumes_from:
|