11from __future__ import unicode_literals
22from __future__ import absolute_import
3+ from collections import namedtuple
34from .packages .docker .errors import APIError
45import logging
56import re
@@ -39,6 +40,9 @@ class ConfigError(ValueError):
3940 pass
4041
4142
43+ VolumeSpec = namedtuple ('VolumeSpec' , 'external internal mode' )
44+
45+
4246class Service (object ):
4347 def __init__ (self , name , client = None , project = 'default' , links = None , volumes_from = None , ** options ):
4448 if not re .match ('^%s+$' % VALID_NAME_CHARS , name ):
@@ -214,37 +218,22 @@ def start_container_if_stopped(self, container, **options):
214218 return self .start_container (container , ** options )
215219
216220 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 = {}
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 [])
224224
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 = {}
231-
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 )
240229
241230 privileged = options .get ('privileged' , False )
242231 net = options .get ('net' , 'bridge' )
243232 dns = options .get ('dns' , None )
244233
245234 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 ,
248237 binds = volume_bindings ,
249238 volumes_from = self ._get_volumes_from (intermediate_container ),
250239 privileged = privileged ,
@@ -256,7 +245,7 @@ def start_container(self, container=None, intermediate_container=None, **overrid
256245 def start_or_create_containers (self ):
257246 containers = self .containers (stopped = True )
258247
259- if len ( containers ) == 0 :
248+ if not containers :
260249 log .info ("Creating %s..." % self .next_container_name ())
261250 new_container = self .create_container ()
262251 return [self .start_container (new_container )]
@@ -338,7 +327,9 @@ def _get_container_create_options(self, override_options, one_off=False):
338327 container_options ['ports' ] = ports
339328
340329 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' ])
342333
343334 if 'environment' in container_options :
344335 if isinstance (container_options ['environment' ], list ):
@@ -433,32 +424,47 @@ def get_container_name(container):
433424 return name [1 :]
434425
435426
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
445451
446452
447453def split_port (port ):
448- port = str (port )
449- external_ip = None
450- if ':' in port :
451- external_port , internal_port = port . rsplit ( ':' , 1 )
452- if ':' in external_port :
453- external_ip , external_port = external_port . split ( ':' , 1 )
454- else :
455- external_port , internal_port = ( None , port )
456- if external_ip :
457- if external_port :
458- external_port = ( external_ip , external_port )
459- else :
460- external_port = ( external_ip ,)
461- return internal_port , external_port
454+ parts = str (port ). split ( ':' )
455+ if not 1 <= len ( parts ) <= 3 :
456+ raise ConfigError ( 'Invalid port "%s", should be '
457+ '[[remote_ip:]remote_port:]port[/protocol]' % port )
458+
459+ if len ( parts ) == 1 :
460+ internal_port , = parts
461+ return internal_port , None
462+ if len ( parts ) == 2 :
463+ external_port , internal_port = parts
464+ return internal_port , external_port
465+
466+ external_ip , external_port , internal_port = parts
467+ return internal_port , ( external_ip , external_port or None )
462468
463469
464470def split_env (env ):
0 commit comments