|
1 | 1 | from __future__ import unicode_literals
|
2 | 2 | from __future__ import absolute_import
|
| 3 | +from collections import namedtuple |
3 | 4 | from .packages.docker.errors import APIError
|
4 | 5 | import logging
|
5 | 6 | import re
|
@@ -39,6 +40,9 @@ class ConfigError(ValueError):
|
39 | 40 | pass
|
40 | 41 |
|
41 | 42 |
|
| 43 | +VolumeSpec = namedtuple('VolumeSpec', 'external internal mode') |
| 44 | + |
| 45 | + |
42 | 46 | class Service(object):
|
43 | 47 | def __init__(self, name, client=None, project='default', links=None, volumes_from=None, **options):
|
44 | 48 | if not re.match('^%s+$' % VALID_NAME_CHARS, name):
|
@@ -214,37 +218,22 @@ def start_container_if_stopped(self, container, **options):
|
214 | 218 | return self.start_container(container, **options)
|
215 | 219 |
|
216 | 220 | def start_container(self, container=None, intermediate_container=None, **override_options):
|
217 |
| - if container is None: |
218 |
| - container = self.create_container(**override_options) |
219 |
| - |
220 |
| - options = self.options.copy() |
221 |
| - options.update(override_options) |
222 |
| - |
223 |
| - port_bindings = {} |
224 |
| - |
225 |
| - if options.get('ports', None) is not None: |
226 |
| - for port in options['ports']: |
227 |
| - internal_port, external_port = split_port(port) |
228 |
| - port_bindings[internal_port] = external_port |
229 |
| - |
230 |
| - volume_bindings = {} |
| 221 | + container = container or self.create_container(**override_options) |
| 222 | + options = dict(self.options, **override_options) |
| 223 | + ports = dict(split_port(port) for port in options.get('ports') or []) |
231 | 224 |
|
232 |
| - if options.get('volumes', None) is not None: |
233 |
| - for volume in options['volumes']: |
234 |
| - if ':' in volume: |
235 |
| - external_dir, internal_dir = volume.split(':') |
236 |
| - volume_bindings[os.path.abspath(external_dir)] = { |
237 |
| - 'bind': internal_dir, |
238 |
| - 'ro': False, |
239 |
| - } |
| 225 | + volume_bindings = dict( |
| 226 | + build_volume_binding(parse_volume_spec(volume)) |
| 227 | + for volume in options.get('volumes') or [] |
| 228 | + if ':' in volume) |
240 | 229 |
|
241 | 230 | privileged = options.get('privileged', False)
|
242 | 231 | net = options.get('net', 'bridge')
|
243 | 232 | dns = options.get('dns', None)
|
244 | 233 |
|
245 | 234 | container.start(
|
246 |
| - links=self._get_links(link_to_self=override_options.get('one_off', False)), |
247 |
| - port_bindings=port_bindings, |
| 235 | + links=self._get_links(link_to_self=options.get('one_off', False)), |
| 236 | + port_bindings=ports, |
248 | 237 | binds=volume_bindings,
|
249 | 238 | volumes_from=self._get_volumes_from(intermediate_container),
|
250 | 239 | privileged=privileged,
|
@@ -338,7 +327,9 @@ def _get_container_create_options(self, override_options, one_off=False):
|
338 | 327 | container_options['ports'] = ports
|
339 | 328 |
|
340 | 329 | if 'volumes' in container_options:
|
341 |
| - container_options['volumes'] = dict((split_volume(v)[1], {}) for v in container_options['volumes']) |
| 330 | + container_options['volumes'] = dict( |
| 331 | + (parse_volume_spec(v).internal, {}) |
| 332 | + for v in container_options['volumes']) |
342 | 333 |
|
343 | 334 | if 'environment' in container_options:
|
344 | 335 | if isinstance(container_options['environment'], list):
|
@@ -433,15 +424,30 @@ def get_container_name(container):
|
433 | 424 | return name[1:]
|
434 | 425 |
|
435 | 426 |
|
436 |
| -def split_volume(v): |
437 |
| - """ |
438 |
| - If v is of the format EXTERNAL:INTERNAL, returns (EXTERNAL, INTERNAL). |
439 |
| - If v is of the format INTERNAL, returns (None, INTERNAL). |
440 |
| - """ |
441 |
| - if ':' in v: |
442 |
| - return v.split(':', 1) |
443 |
| - else: |
444 |
| - return (None, v) |
| 427 | +def parse_volume_spec(volume_config): |
| 428 | + parts = volume_config.split(':') |
| 429 | + if len(parts) > 3: |
| 430 | + raise ConfigError("Volume %s has incorrect format, should be " |
| 431 | + "external:internal[:mode]" % volume_config) |
| 432 | + |
| 433 | + if len(parts) == 1: |
| 434 | + return VolumeSpec(None, parts[0], 'rw') |
| 435 | + |
| 436 | + if len(parts) == 2: |
| 437 | + parts.append('rw') |
| 438 | + |
| 439 | + external, internal, mode = parts |
| 440 | + if mode not in ('rw', 'ro'): |
| 441 | + raise ConfigError("Volume %s has invalid mode (%s), should be " |
| 442 | + "one of: rw, ro." % (volume_config, mode)) |
| 443 | + |
| 444 | + return VolumeSpec(external, internal, mode) |
| 445 | + |
| 446 | + |
| 447 | +def build_volume_binding(volume_spec): |
| 448 | + internal = {'bind': volume_spec.internal, 'ro': volume_spec.mode == 'ro'} |
| 449 | + external = os.path.expanduser(volume_spec.external) |
| 450 | + return os.path.abspath(os.path.expandvars(external)), internal |
445 | 451 |
|
446 | 452 |
|
447 | 453 | def split_port(port):
|
|
0 commit comments