2014-01-06 03:26:32 +01:00
from __future__ import unicode_literals
from __future__ import absolute_import
2014-08-26 04:16:37 +02:00
from collections import namedtuple
2013-12-16 11:51:22 +01:00
import logging
2013-12-09 13:19:27 +01:00
import re
2013-12-19 16:53:39 +01:00
import os
2014-08-30 17:33:25 +02:00
from operator import attrgetter
2013-12-18 19:45:25 +01:00
import sys
2014-08-24 20:55:38 +02:00
from docker . errors import APIError
2015-01-16 18:24:29 +01:00
from . container import Container , get_container_name
2014-05-29 12:19:30 +02:00
from . progress_stream import stream_output , StreamOutputError
2013-12-09 13:19:27 +01:00
2013-12-16 11:51:22 +01:00
log = logging . getLogger ( __name__ )
2013-12-09 13:19:27 +01:00
2013-12-18 17:12:53 +01:00
2014-12-11 19:08:39 +01:00
DOCKER_CONFIG_KEYS = [
' cap_add ' ,
' cap_drop ' ,
2015-01-11 19:58:08 +01:00
' cpu_shares ' ,
2014-12-11 19:08:39 +01:00
' command ' ,
' detach ' ,
' dns ' ,
2014-08-17 15:18:24 +02:00
' dns_search ' ,
2014-12-11 19:08:39 +01:00
' domainname ' ,
' entrypoint ' ,
' env_file ' ,
' environment ' ,
' hostname ' ,
' image ' ,
' mem_limit ' ,
' net ' ,
' ports ' ,
' privileged ' ,
' restart ' ,
' stdin_open ' ,
' tty ' ,
' user ' ,
' volumes ' ,
' volumes_from ' ,
' working_dir ' ,
]
2014-02-05 01:33:29 +01:00
DOCKER_CONFIG_HINTS = {
2015-01-11 19:58:08 +01:00
' cpu_share ' : ' cpu_shares ' ,
2014-03-04 00:51:24 +01:00
' link ' : ' links ' ,
' port ' : ' ports ' ,
' privilege ' : ' privileged ' ,
' priviliged ' : ' privileged ' ,
' privilige ' : ' privileged ' ,
' volume ' : ' volumes ' ,
2014-07-06 18:09:30 +02:00
' workdir ' : ' working_dir ' ,
2014-02-05 01:33:29 +01:00
}
2014-10-26 18:22:16 +01:00
DOCKER_START_KEYS = [
' cap_add ' ,
' cap_drop ' ,
' dns ' ,
2014-10-26 18:23:15 +01:00
' dns_search ' ,
2014-10-26 18:22:16 +01:00
' env_file ' ,
' net ' ,
' privileged ' ,
' restart ' ,
]
2014-05-02 16:07:20 +02:00
VALID_NAME_CHARS = ' [a-zA-Z0-9] '
2014-02-05 01:33:29 +01:00
2013-12-18 17:12:53 +01:00
class BuildError ( Exception ) :
2014-04-30 12:53:23 +02:00
def __init__ ( self , service , reason ) :
2014-03-25 13:19:42 +01:00
self . service = service
2014-04-30 12:53:23 +02:00
self . reason = reason
2013-12-18 17:12:53 +01:00
2014-01-16 18:58:53 +01:00
class CannotBeScaledError ( Exception ) :
pass
2014-02-05 01:33:29 +01:00
class ConfigError ( ValueError ) :
pass
2014-08-26 04:16:37 +02:00
VolumeSpec = namedtuple ( ' VolumeSpec ' , ' external internal mode ' )
2014-08-24 20:55:38 +02:00
ServiceName = namedtuple ( ' ServiceName ' , ' project service number ' )
2013-12-09 12:41:05 +01:00
class Service ( object ) :
2014-10-09 04:31:04 +02:00
def __init__ ( self , name , client = None , project = ' default ' , links = None , external_links = None , volumes_from = None , * * options ) :
2014-05-02 16:07:20 +02:00
if not re . match ( ' ^ %s +$ ' % VALID_NAME_CHARS , name ) :
raise ConfigError ( ' Invalid service name " %s " - only %s are allowed ' % ( name , VALID_NAME_CHARS ) )
if not re . match ( ' ^ %s +$ ' % VALID_NAME_CHARS , project ) :
raise ConfigError ( ' Invalid project name " %s " - only %s are allowed ' % ( project , VALID_NAME_CHARS ) )
2013-12-13 21:36:10 +01:00
if ' image ' in options and ' build ' in options :
2014-02-05 01:33:29 +01:00
raise ConfigError ( ' Service %s has both an image and build path specified. A service can either be built to image or use an existing image, not both. ' % name )
2014-10-09 04:31:04 +02:00
supported_options = DOCKER_CONFIG_KEYS + [ ' build ' , ' expose ' ,
' external_links ' ]
2014-02-05 01:33:29 +01:00
for k in options :
if k not in supported_options :
msg = " Unsupported config option for %s service: ' %s ' " % ( name , k )
if k in DOCKER_CONFIG_HINTS :
msg + = " (did you mean ' %s ' ?) " % DOCKER_CONFIG_HINTS [ k ]
raise ConfigError ( msg )
2013-12-09 13:19:27 +01:00
self . name = name
2013-12-09 12:41:05 +01:00
self . client = client
2013-12-19 16:16:17 +01:00
self . project = project
2013-12-09 15:09:18 +01:00
self . links = links or [ ]
2014-10-09 04:31:04 +02:00
self . external_links = external_links or [ ]
2014-05-29 09:40:11 +02:00
self . volumes_from = volumes_from or [ ]
2013-12-10 21:51:55 +01:00
self . options = options
2013-12-09 12:41:05 +01:00
2013-12-20 11:46:55 +01:00
def containers ( self , stopped = False , one_off = False ) :
2014-08-02 22:31:08 +02:00
return [ Container . from_ps ( self . client , container )
for container in self . client . containers ( all = stopped )
if self . has_container ( container , one_off = one_off ) ]
def has_container ( self , container , one_off = False ) :
""" Return True if `container` was created to fulfill this service. """
name = get_container_name ( container )
if not name or not is_valid_name ( name , one_off ) :
return False
2014-08-08 18:41:52 +02:00
project , name , _number = parse_name ( name )
2014-08-02 22:31:08 +02:00
return project == self . project and name == self . name
2013-12-09 12:41:05 +01:00
2014-08-08 18:41:52 +02:00
def get_container ( self , number = 1 ) :
""" Return a :class:`fig.container.Container` for this service. The
container must be active , and match ` number ` .
"""
for container in self . client . containers ( ) :
if not self . has_container ( container ) :
continue
_ , _ , container_number = parse_name ( get_container_name ( container ) )
if container_number == number :
return Container . from_ps ( self . client , container )
raise ValueError ( " No container found for %s _ %s " % ( self . name , number ) )
2013-12-20 17:22:54 +01:00
def start ( self , * * options ) :
for c in self . containers ( stopped = True ) :
2014-06-08 11:48:59 +02:00
self . start_container_if_stopped ( c , * * options )
2013-12-09 12:41:05 +01:00
2013-12-20 17:22:54 +01:00
def stop ( self , * * options ) :
for c in self . containers ( ) :
2014-01-15 19:06:49 +01:00
log . info ( " Stopping %s ... " % c . name )
2013-12-20 17:22:54 +01:00
c . stop ( * * options )
2013-12-09 12:41:05 +01:00
2013-12-20 17:53:07 +01:00
def kill ( self , * * options ) :
for c in self . containers ( ) :
2014-01-15 19:06:49 +01:00
log . info ( " Killing %s ... " % c . name )
2013-12-20 17:53:07 +01:00
c . kill ( * * options )
2014-08-08 05:22:33 +02:00
def restart ( self , * * options ) :
for c in self . containers ( ) :
log . info ( " Restarting %s ... " % c . name )
c . restart ( * * options )
2014-01-16 18:58:53 +01:00
def scale ( self , desired_num ) :
2014-03-21 19:34:19 +01:00
"""
2014-10-26 18:22:16 +01:00
Adjusts the number of containers to the specified number and ensures
they are running .
2014-03-21 19:34:19 +01:00
- creates containers until there are at least ` desired_num `
- stops containers until there are at most ` desired_num ` running
- starts containers until there are at least ` desired_num ` running
- removes all stopped containers
"""
2014-01-16 18:58:53 +01:00
if not self . can_be_scaled ( ) :
raise CannotBeScaledError ( )
# Create enough containers
containers = self . containers ( stopped = True )
while len ( containers ) < desired_num :
2014-12-18 03:31:22 +01:00
containers . append ( self . create_container ( detach = True ) )
2014-01-16 18:58:53 +01:00
running_containers = [ ]
stopped_containers = [ ]
for c in containers :
if c . is_running :
running_containers . append ( c )
else :
stopped_containers . append ( c )
running_containers . sort ( key = lambda c : c . number )
stopped_containers . sort ( key = lambda c : c . number )
# Stop containers
while len ( running_containers ) > desired_num :
c = running_containers . pop ( )
log . info ( " Stopping %s ... " % c . name )
c . stop ( timeout = 1 )
stopped_containers . append ( c )
# Start containers
while len ( running_containers ) < desired_num :
c = stopped_containers . pop ( 0 )
log . info ( " Starting %s ... " % c . name )
2014-02-17 22:33:05 +01:00
self . start_container ( c )
2014-01-16 18:58:53 +01:00
running_containers . append ( c )
2014-03-21 19:34:19 +01:00
self . remove_stopped ( )
2013-12-20 17:53:07 +01:00
def remove_stopped ( self , * * options ) :
for c in self . containers ( stopped = True ) :
if not c . is_running :
2014-01-15 19:06:49 +01:00
log . info ( " Removing %s ... " % c . name )
2013-12-20 17:53:07 +01:00
c . remove ( * * options )
2014-10-26 18:22:16 +01:00
def create_container ( self ,
one_off = False ,
insecure_registry = False ,
do_build = True ,
* * override_options ) :
2013-12-18 12:37:51 +01:00
"""
Create a container for this service . If the image doesn ' t exist, attempt to pull
it .
"""
2014-10-26 18:22:16 +01:00
container_options = self . _get_container_create_options (
override_options ,
one_off = one_off )
if ( do_build and
self . can_be_built ( ) and
not self . client . images ( name = self . full_name ) ) :
self . build ( )
2013-12-18 12:37:51 +01:00
try :
2013-12-18 19:37:48 +01:00
return Container . create ( self . client , * * container_options )
2014-01-06 03:26:32 +01:00
except APIError as e :
2014-01-06 04:32:06 +01:00
if e . response . status_code == 404 and e . explanation and ' No such image ' in str ( e . explanation ) :
2013-12-18 12:37:51 +01:00
log . info ( ' Pulling image %s ... ' % container_options [ ' image ' ] )
2014-10-22 16:57:43 +02:00
output = self . client . pull (
container_options [ ' image ' ] ,
stream = True ,
insecure_registry = insecure_registry
)
2014-03-25 18:12:59 +01:00
stream_output ( output , sys . stdout )
2013-12-18 19:37:48 +01:00
return Container . create ( self . client , * * container_options )
2013-12-18 12:37:51 +01:00
raise
2013-12-17 15:13:12 +01:00
2014-10-26 18:23:15 +01:00
def recreate_containers ( self , insecure_registry = False , do_build = True , * * override_options ) :
2014-01-03 12:18:59 +01:00
"""
2015-01-20 21:01:50 +01:00
If a container for this service doesn ' t exist, create and start one. If there are
any , stop them , create + start new ones , and remove the old containers .
2014-01-03 12:18:59 +01:00
"""
2014-01-15 17:15:46 +01:00
containers = self . containers ( stopped = True )
2014-08-24 20:55:38 +02:00
if not containers :
log . info ( " Creating %s ... " % self . _next_container_name ( containers ) )
2014-10-26 18:23:15 +01:00
container = self . create_container (
insecure_registry = insecure_registry ,
do_build = do_build ,
* * override_options )
2014-04-23 16:46:26 +02:00
self . start_container ( container )
2015-01-20 21:01:50 +01:00
return [ ( None , container ) ]
2014-01-03 12:18:59 +01:00
else :
2015-01-20 21:01:50 +01:00
tuples = [ ]
for c in containers :
log . info ( " Recreating %s ... " % c . name )
tuples . append ( self . recreate_container ( c , insecure_registry = insecure_registry , * * override_options ) )
return tuples
2014-01-03 12:18:59 +01:00
2014-01-15 17:15:46 +01:00
def recreate_container ( self , container , * * override_options ) :
2014-08-31 19:07:49 +02:00
""" Recreate a container. An intermediate container is created so that
the new container has the same name , while still supporting
` volumes - from ` the original container .
"""
2014-07-11 23:52:47 +02:00
try :
2014-07-11 23:53:56 +02:00
container . stop ( )
2014-07-11 23:52:47 +02:00
except APIError as e :
if ( e . response . status_code == 500
and e . explanation
and ' no such process ' in str ( e . explanation ) ) :
pass
else :
raise
2014-01-15 17:15:46 +01:00
2014-01-20 17:10:54 +01:00
intermediate_container = Container . create (
self . client ,
image = container . image ,
2014-10-10 12:10:39 +02:00
entrypoint = [ ' /bin/echo ' ] ,
2014-02-21 19:12:51 +01:00
command = [ ] ,
2014-12-18 03:31:22 +01:00
detach = True ,
2014-01-20 17:10:54 +01:00
)
2015-01-20 21:01:50 +01:00
intermediate_container . start ( volumes_from = container . id )
2014-01-15 18:06:16 +01:00
intermediate_container . wait ( )
container . remove ( )
2014-01-15 17:15:46 +01:00
options = dict ( override_options )
2014-10-26 18:23:15 +01:00
new_container = self . create_container ( do_build = False , * * options )
2015-01-20 21:01:50 +01:00
self . start_container ( new_container , intermediate_container = intermediate_container )
2014-04-23 16:46:26 +02:00
intermediate_container . remove ( )
2015-01-20 21:01:50 +01:00
return ( intermediate_container , new_container )
2014-01-15 17:15:46 +01:00
2014-06-08 11:48:59 +02:00
def start_container_if_stopped ( self , container , * * options ) :
if container . is_running :
return container
else :
log . info ( " Starting %s ... " % container . name )
return self . start_container ( container , * * options )
2014-10-26 18:22:16 +01:00
def start_container ( self , container , intermediate_container = None , * * override_options ) :
2014-08-26 04:16:37 +02:00
options = dict ( self . options , * * override_options )
2014-11-07 02:54:45 +01:00
port_bindings = build_port_bindings ( options . get ( ' ports ' ) or [ ] )
2015-01-20 21:01:50 +01:00
volume_bindings = dict (
build_volume_binding ( parse_volume_spec ( volume ) )
for volume in options . get ( ' volumes ' ) or [ ]
if ' : ' in volume )
2014-03-04 00:51:24 +01:00
privileged = options . get ( ' privileged ' , False )
2014-06-19 12:57:55 +02:00
net = options . get ( ' net ' , ' bridge ' )
2014-07-18 03:11:50 +02:00
dns = options . get ( ' dns ' , None )
2014-08-17 15:18:24 +02:00
dns_search = options . get ( ' dns_search ' , None )
2014-11-06 20:38:58 +01:00
cap_add = options . get ( ' cap_add ' , None )
cap_drop = options . get ( ' cap_drop ' , None )
2014-03-04 00:51:24 +01:00
2014-10-28 16:31:09 +01:00
restart = parse_restart_spec ( options . get ( ' restart ' , None ) )
2013-12-18 19:37:48 +01:00
container . start (
2014-08-26 04:16:37 +02:00
links = self . _get_links ( link_to_self = options . get ( ' one_off ' , False ) ) ,
2014-11-07 02:54:45 +01:00
port_bindings = port_bindings ,
2015-01-20 21:01:50 +01:00
binds = volume_bindings ,
volumes_from = self . _get_volumes_from ( intermediate_container ) ,
2014-03-04 00:51:24 +01:00
privileged = privileged ,
2014-06-19 12:57:55 +02:00
network_mode = net ,
2014-07-18 03:11:50 +02:00
dns = dns ,
2014-08-17 15:18:24 +02:00
dns_search = dns_search ,
2014-11-06 20:38:58 +01:00
restart_policy = restart ,
cap_add = cap_add ,
cap_drop = cap_drop ,
2013-12-09 22:39:11 +01:00
)
2013-12-17 15:13:12 +01:00
return container
2013-12-09 12:41:05 +01:00
2014-10-26 18:23:15 +01:00
def start_or_create_containers (
self ,
insecure_registry = False ,
detach = False ,
do_build = True ) :
2014-06-09 00:47:09 +02:00
containers = self . containers ( stopped = True )
2014-08-26 04:17:33 +02:00
if not containers :
2014-08-24 20:55:38 +02:00
log . info ( " Creating %s ... " % self . _next_container_name ( containers ) )
2014-12-18 03:31:22 +01:00
new_container = self . create_container (
insecure_registry = insecure_registry ,
2014-10-26 18:23:15 +01:00
detach = detach ,
do_build = do_build ,
2014-12-18 03:31:22 +01:00
)
2014-06-09 00:47:09 +02:00
return [ self . start_container ( new_container ) ]
else :
return [ self . start_container_if_stopped ( c ) for c in containers ]
2014-06-08 09:03:18 +02:00
def get_linked_names ( self ) :
return [ s . name for ( s , _ ) in self . links ]
2014-08-24 20:55:38 +02:00
def _next_container_name ( self , all_containers , one_off = False ) :
2013-12-20 11:46:55 +01:00
bits = [ self . project , self . name ]
if one_off :
bits . append ( ' run ' )
2014-08-24 20:55:38 +02:00
return ' _ ' . join ( bits + [ str ( self . _next_container_number ( all_containers ) ) ] )
2013-12-19 16:16:17 +01:00
2014-08-24 20:55:38 +02:00
def _next_container_number ( self , all_containers ) :
numbers = [ parse_name ( c . name ) . number for c in all_containers ]
return 1 if not numbers else max ( numbers ) + 1
2013-12-09 16:00:41 +01:00
2014-03-06 19:59:24 +01:00
def _get_links ( self , link_to_self ) :
2014-01-27 16:29:58 +01:00
links = [ ]
2014-03-01 17:17:19 +01:00
for service , link_name in self . links :
2013-12-18 19:37:48 +01:00
for container in service . containers ( ) :
2014-07-29 03:37:17 +02:00
links . append ( ( container . name , link_name or service . name ) )
2014-01-27 16:29:58 +01:00
links . append ( ( container . name , container . name ) )
links . append ( ( container . name , container . name_without_project ) )
2014-03-06 19:59:24 +01:00
if link_to_self :
for container in self . containers ( ) :
2014-07-29 03:37:17 +02:00
links . append ( ( container . name , self . name ) )
2014-03-06 19:59:24 +01:00
links . append ( ( container . name , container . name ) )
links . append ( ( container . name , container . name_without_project ) )
2014-10-09 04:31:04 +02:00
for external_link in self . external_links :
if ' : ' not in external_link :
link_name = external_link
else :
external_link , link_name = external_link . split ( ' : ' )
links . append ( ( external_link , link_name ) )
2013-12-09 22:39:11 +01:00
return links
2015-01-20 21:01:50 +01:00
def _get_volumes_from ( self , intermediate_container = None ) :
2014-05-29 09:40:11 +02:00
volumes_from = [ ]
2014-08-30 17:33:25 +02:00
for volume_source in self . volumes_from :
if isinstance ( volume_source , Service ) :
containers = volume_source . containers ( stopped = True )
if not containers :
volumes_from . append ( volume_source . create_container ( ) . id )
else :
volumes_from . extend ( map ( attrgetter ( ' id ' ) , containers ) )
elif isinstance ( volume_source , Container ) :
volumes_from . append ( volume_source . id )
2014-05-29 09:40:11 +02:00
2015-01-20 21:01:50 +01:00
if intermediate_container :
volumes_from . append ( intermediate_container . id )
2014-05-29 09:40:11 +02:00
return volumes_from
2014-03-04 00:51:24 +01:00
def _get_container_create_options ( self , override_options , one_off = False ) :
2014-12-11 19:08:39 +01:00
container_options = dict (
( k , self . options [ k ] )
for k in DOCKER_CONFIG_KEYS if k in self . options )
2013-12-13 21:36:10 +01:00
container_options . update ( override_options )
2014-08-24 20:55:38 +02:00
container_options [ ' name ' ] = self . _next_container_name (
self . containers ( stopped = True , one_off = one_off ) ,
one_off )
2013-12-13 21:36:10 +01:00
2014-07-11 00:56:38 +02:00
# If a qualified hostname was given, split it into an
# unqualified hostname and a domainname unless domainname
# was also given explicitly. This matches the behavior of
# the official Docker CLI in that scenario.
if ( ' hostname ' in container_options
and ' domainname ' not in container_options
and ' . ' in container_options [ ' hostname ' ] ) :
parts = container_options [ ' hostname ' ] . partition ( ' . ' )
container_options [ ' hostname ' ] = parts [ 0 ]
container_options [ ' domainname ' ] = parts [ 2 ]
2014-03-04 18:59:42 +01:00
if ' ports ' in container_options or ' expose ' in self . options :
2014-01-16 02:54:05 +01:00
ports = [ ]
2014-03-04 18:59:42 +01:00
all_ports = container_options . get ( ' ports ' , [ ] ) + self . options . get ( ' expose ' , [ ] )
for port in all_ports :
2014-01-16 02:54:05 +01:00
port = str ( port )
if ' : ' in port :
port = port . split ( ' : ' ) [ - 1 ]
2014-01-22 18:01:10 +01:00
if ' / ' in port :
port = tuple ( port . split ( ' / ' ) )
2014-01-16 02:54:05 +01:00
ports . append ( port )
container_options [ ' ports ' ] = ports
2013-12-18 12:14:14 +01:00
2013-12-19 16:53:39 +01:00
if ' volumes ' in container_options :
2014-08-26 04:16:37 +02:00
container_options [ ' volumes ' ] = dict (
( parse_volume_spec ( v ) . internal , { } )
for v in container_options [ ' volumes ' ] )
2013-12-19 16:53:39 +01:00
2014-09-12 05:57:23 +02:00
container_options [ ' environment ' ] = merge_environment ( container_options )
2014-07-11 19:18:05 +02:00
2014-01-02 16:28:33 +01:00
if self . can_be_built ( ) :
2014-10-26 18:22:16 +01:00
container_options [ ' image ' ] = self . full_name
2014-12-09 01:03:42 +01:00
else :
container_options [ ' image ' ] = self . _get_image_name ( container_options [ ' image ' ] )
2013-12-13 21:36:10 +01:00
2014-07-18 03:11:50 +02:00
# Delete options which are only used when starting
2014-10-26 18:22:16 +01:00
for key in DOCKER_START_KEYS :
container_options . pop ( key , None )
2014-06-19 12:57:55 +02:00
2013-12-13 21:36:10 +01:00
return container_options
2013-12-09 22:39:11 +01:00
2014-12-09 01:03:42 +01:00
def _get_image_name ( self , image ) :
repo , tag = parse_repository_tag ( image )
if tag == " " :
tag = " latest "
return ' %s : %s ' % ( repo , tag )
2014-07-01 05:08:37 +02:00
def build ( self , no_cache = False ) :
2013-12-18 19:46:53 +01:00
log . info ( ' Building %s ... ' % self . name )
2013-12-18 17:12:53 +01:00
2013-12-20 17:23:40 +01:00
build_output = self . client . build (
self . options [ ' build ' ] ,
2014-10-26 18:22:16 +01:00
tag = self . full_name ,
2014-05-01 16:41:36 +02:00
stream = True ,
2014-07-01 05:08:37 +02:00
rm = True ,
nocache = no_cache ,
2013-12-20 17:23:40 +01:00
)
2013-12-18 17:12:53 +01:00
2014-04-30 12:53:23 +02:00
try :
all_events = stream_output ( build_output , sys . stdout )
except StreamOutputError , e :
raise BuildError ( self , unicode ( e ) )
2014-03-25 18:12:59 +01:00
2013-12-18 17:12:53 +01:00
image_id = None
2014-03-25 18:12:59 +01:00
for event in all_events :
if ' stream ' in event :
match = re . search ( r ' Successfully built ([0-9a-f]+) ' , event . get ( ' stream ' , ' ' ) )
2013-12-18 17:12:53 +01:00
if match :
image_id = match . group ( 1 )
if image_id is None :
2014-10-26 18:22:16 +01:00
raise BuildError ( self , event if all_events else ' Unknown ' )
2013-12-18 17:12:53 +01:00
return image_id
2014-01-02 16:28:33 +01:00
def can_be_built ( self ) :
return ' build ' in self . options
2014-10-26 18:22:16 +01:00
@property
def full_name ( self ) :
2013-12-20 17:23:40 +01:00
"""
The tag to give to images built for this service .
"""
return ' %s _ %s ' % ( self . project , self . name )
2014-01-16 18:58:53 +01:00
def can_be_scaled ( self ) :
for port in self . options . get ( ' ports ' , [ ] ) :
if ' : ' in str ( port ) :
return False
return True
2014-09-18 21:37:52 +02:00
def pull ( self , insecure_registry = False ) :
2014-08-15 16:21:41 +02:00
if ' image ' in self . options :
2014-12-09 01:03:42 +01:00
image_name = self . _get_image_name ( self . options [ ' image ' ] )
log . info ( ' Pulling %s ( %s )... ' % ( self . name , image_name ) )
2014-09-18 21:37:52 +02:00
self . client . pull (
2014-12-09 01:03:42 +01:00
image_name ,
2014-09-18 21:37:52 +02:00
insecure_registry = insecure_registry
)
2014-08-15 16:21:41 +02:00
2013-12-09 16:00:41 +01:00
2013-12-20 11:46:55 +01:00
NAME_RE = re . compile ( r ' ^([^_]+)_([^_]+)_(run_)?( \ d+)$ ' )
2013-12-09 16:00:41 +01:00
2013-12-20 11:46:55 +01:00
def is_valid_name ( name , one_off = False ) :
match = NAME_RE . match ( name )
if match is None :
return False
if one_off :
return match . group ( 3 ) == ' run_ '
else :
return match . group ( 3 ) is None
2013-12-09 16:00:41 +01:00
2014-08-24 20:55:38 +02:00
def parse_name ( name ) :
2013-12-19 21:09:54 +01:00
match = NAME_RE . match ( name )
2013-12-20 11:46:55 +01:00
( project , service_name , _ , suffix ) = match . groups ( )
2014-08-24 20:55:38 +02:00
return ServiceName ( project , service_name , int ( suffix ) )
2013-12-09 16:00:41 +01:00
2014-10-28 16:31:09 +01:00
def parse_restart_spec ( restart_config ) :
if not restart_config :
return None
parts = restart_config . split ( ' : ' )
if len ( parts ) > 2 :
raise ConfigError ( " Restart %s has incorrect format, should be "
" mode[:max_retry] " % restart_config )
if len ( parts ) == 2 :
name , max_retry_count = parts
else :
name , = parts
max_retry_count = 0
return { ' Name ' : name , ' MaximumRetryCount ' : int ( max_retry_count ) }
2014-08-26 04:16:37 +02:00
def parse_volume_spec ( volume_config ) :
parts = volume_config . split ( ' : ' )
if len ( parts ) > 3 :
raise ConfigError ( " Volume %s has incorrect format, should be "
" external:internal[:mode] " % volume_config )
if len ( parts ) == 1 :
return VolumeSpec ( None , parts [ 0 ] , ' rw ' )
if len ( parts ) == 2 :
parts . append ( ' rw ' )
external , internal , mode = parts
if mode not in ( ' rw ' , ' ro ' ) :
raise ConfigError ( " Volume %s has invalid mode ( %s ), should be "
" one of: rw, ro. " % ( volume_config , mode ) )
return VolumeSpec ( external , internal , mode )
2014-12-09 01:03:42 +01:00
def parse_repository_tag ( s ) :
if " : " not in s :
return s , " "
repo , tag = s . rsplit ( " : " , 1 )
if " / " in tag :
return s , " "
return repo , tag
2014-08-26 04:16:37 +02:00
def build_volume_binding ( volume_spec ) :
internal = { ' bind ' : volume_spec . internal , ' ro ' : volume_spec . mode == ' ro ' }
external = os . path . expanduser ( volume_spec . external )
return os . path . abspath ( os . path . expandvars ( external ) ) , internal
2014-06-25 12:49:54 +02:00
2014-11-07 02:54:45 +01:00
def build_port_bindings ( ports ) :
port_bindings = { }
for port in ports :
internal_port , external = split_port ( port )
if internal_port in port_bindings :
port_bindings [ internal_port ] . append ( external )
else :
port_bindings [ internal_port ] = [ external ]
return port_bindings
2014-06-25 12:49:54 +02:00
def split_port ( port ) :
2014-08-26 04:17:33 +02:00
parts = str ( port ) . split ( ' : ' )
if not 1 < = len ( parts ) < = 3 :
raise ConfigError ( ' Invalid port " %s " , should be '
' [[remote_ip:]remote_port:]port[/protocol] ' % port )
if len ( parts ) == 1 :
internal_port , = parts
return internal_port , None
if len ( parts ) == 2 :
external_port , internal_port = parts
return internal_port , external_port
external_ip , external_port , internal_port = parts
return internal_port , ( external_ip , external_port or None )
2014-06-25 12:49:54 +02:00
2014-07-15 22:22:16 +02:00
2014-09-12 05:57:23 +02:00
def merge_environment ( options ) :
env = { }
if ' env_file ' in options :
if isinstance ( options [ ' env_file ' ] , list ) :
for f in options [ ' env_file ' ] :
env . update ( env_vars_from_file ( f ) )
else :
env . update ( env_vars_from_file ( options [ ' env_file ' ] ) )
if ' environment ' in options :
if isinstance ( options [ ' environment ' ] , list ) :
env . update ( dict ( split_env ( e ) for e in options [ ' environment ' ] ) )
else :
env . update ( options [ ' environment ' ] )
return dict ( resolve_env ( k , v ) for k , v in env . iteritems ( ) )
2014-07-11 19:18:05 +02:00
def split_env ( env ) :
if ' = ' in env :
return env . split ( ' = ' , 1 )
else :
return env , None
2014-07-15 22:22:16 +02:00
def resolve_env ( key , val ) :
2014-07-11 19:18:05 +02:00
if val is not None :
return key , val
elif key in os . environ :
return key , os . environ [ key ]
else :
return key , ' '
2014-09-12 05:57:23 +02:00
def env_vars_from_file ( filename ) :
"""
Read in a line delimited file of environment variables .
"""
env = { }
for line in open ( filename , ' r ' ) :
line = line . strip ( )
if line and not line . startswith ( ' # ' ) :
k , v = split_env ( line )
env [ k ] = v
return env