2014-01-06 03:26:32 +01:00
from __future__ import unicode_literals
from __future__ import absolute_import
2014-01-03 00:27:47 +01:00
import logging
2013-12-09 15:10:23 +01:00
from . service import Service
2014-01-03 00:27:47 +01:00
log = logging . getLogger ( __name__ )
2014-01-23 23:27:54 +01:00
2013-12-09 15:10:23 +01:00
def sort_service_dicts ( services ) :
2014-02-12 10:09:55 +01:00
# Topological sort (Cormen/Tarjan algorithm).
unmarked = services [ : ]
temporary_marked = set ( )
2014-01-23 23:27:54 +01:00
sorted_services = [ ]
2014-03-01 17:17:19 +01:00
get_service_names = lambda links : [ link . split ( ' : ' ) [ 0 ] for link in links ]
2014-02-12 10:09:55 +01:00
def visit ( n ) :
if n [ ' name ' ] in temporary_marked :
2014-03-01 17:17:19 +01:00
if n [ ' name ' ] in get_service_names ( n . get ( ' links ' , [ ] ) ) :
2014-02-12 10:09:55 +01:00
raise DependencyError ( ' A service can not link to itself: %s ' % n [ ' name ' ] )
else :
raise DependencyError ( ' Circular import between %s ' % ' and ' . join ( temporary_marked ) )
if n in unmarked :
temporary_marked . add ( n [ ' name ' ] )
2014-03-01 17:17:19 +01:00
dependents = [ m for m in services if n [ ' name ' ] in get_service_names ( m . get ( ' links ' , [ ] ) ) ]
2014-02-12 10:09:55 +01:00
for m in dependents :
visit ( m )
temporary_marked . remove ( n [ ' name ' ] )
unmarked . remove ( n )
sorted_services . insert ( 0 , n )
while unmarked :
visit ( unmarked [ - 1 ] )
return sorted_services
2013-12-09 15:10:23 +01:00
2013-12-19 17:55:12 +01:00
class Project ( object ) :
"""
A collection of services .
"""
def __init__ ( self , name , services , client ) :
self . name = name
self . services = services
self . client = client
2013-12-09 15:10:23 +01:00
@classmethod
2013-12-19 17:55:12 +01:00
def from_dicts ( cls , name , service_dicts , client ) :
2013-12-09 15:10:23 +01:00
"""
Construct a ServiceCollection from a list of dicts representing services .
"""
2013-12-19 17:55:12 +01:00
project = cls ( name , [ ] , client )
2013-12-09 15:10:23 +01:00
for service_dict in sort_service_dicts ( service_dicts ) :
# Reference links by object
links = [ ]
if ' links ' in service_dict :
2014-03-01 17:17:19 +01:00
for link in service_dict . get ( ' links ' , [ ] ) :
if ' : ' in link :
service_name , link_name = link . split ( ' : ' , 1 )
else :
service_name , link_name = link , None
2014-04-04 14:06:52 +02:00
try :
links . append ( ( project . get_service ( service_name ) , link_name ) )
except NoSuchService :
raise ConfigurationError ( ' Service " %s " has a link to service " %s " which does not exist. ' % ( service_dict [ ' name ' ] , service_name ) )
2013-12-09 15:10:23 +01:00
del service_dict [ ' links ' ]
2014-06-07 10:22:27 +02:00
2014-06-07 15:03:53 +02:00
project . services . append ( Service ( client = client , project = name , links = links , * * service_dict ) )
2013-12-19 17:55:12 +01:00
return project
2013-12-09 15:10:23 +01:00
2013-12-11 15:25:32 +01:00
@classmethod
2013-12-19 17:55:12 +01:00
def from_config ( cls , name , config , client ) :
2013-12-11 15:25:32 +01:00
dicts = [ ]
2014-01-06 03:26:32 +01:00
for service_name , service in list ( config . items ( ) ) :
2014-03-03 17:21:42 +01:00
if not isinstance ( service , dict ) :
raise ConfigurationError ( ' Service " %s " doesn \' t have any configuration options. All top level keys in your fig.yml must map to a dictionary of configuration options. ' )
2013-12-19 17:56:58 +01:00
service [ ' name ' ] = service_name
2013-12-11 15:25:32 +01:00
dicts . append ( service )
2013-12-19 17:55:12 +01:00
return cls . from_dicts ( name , dicts , client )
2013-12-11 15:25:32 +01:00
2013-12-19 17:55:12 +01:00
def get_service ( self , name ) :
2013-12-20 19:30:23 +01:00
"""
Retrieve a service by name . Raises NoSuchService
if the named service does not exist .
"""
2013-12-19 17:55:12 +01:00
for service in self . services :
2013-12-09 15:10:23 +01:00
if service . name == name :
return service
2013-12-20 19:30:23 +01:00
raise NoSuchService ( name )
2014-06-08 10:51:09 +02:00
def get_services ( self , service_names = None , include_links = False ) :
2013-12-20 19:30:23 +01:00
"""
Returns a list of this project ' s services filtered
2014-06-21 12:39:36 +02:00
by the provided list of names , or all services if service_names is None
or [ ] .
2013-12-20 19:30:23 +01:00
2014-06-08 10:55:14 +02:00
If include_links is specified , returns a list including the links for
service_names , in order of dependency .
2014-06-08 10:51:09 +02:00
2014-06-08 10:55:14 +02:00
Preserves the original order of self . services where possible ,
reordering as needed to resolve links .
2013-12-20 19:30:23 +01:00
2014-06-08 10:55:14 +02:00
Raises NoSuchService if any of the named services do not exist .
2013-12-20 19:30:23 +01:00
"""
if service_names is None or len ( service_names ) == 0 :
2014-06-08 11:09:57 +02:00
return self . get_services (
2014-06-21 12:39:36 +02:00
service_names = [ s . name for s in self . services ] ,
2014-06-08 11:09:57 +02:00
include_links = include_links
)
2013-12-20 19:30:23 +01:00
else :
unsorted = [ self . get_service ( name ) for name in service_names ]
2014-06-08 10:51:09 +02:00
services = [ s for s in self . services if s in unsorted ]
if include_links :
2014-06-08 12:08:40 +02:00
services = reduce ( self . _inject_links , services , [ ] )
2014-06-08 10:51:09 +02:00
uniques = [ ]
[ uniques . append ( s ) for s in services if s not in uniques ]
return uniques
2013-12-20 19:30:23 +01:00
def start ( self , service_names = None , * * options ) :
for service in self . get_services ( service_names ) :
2013-12-20 17:22:54 +01:00
service . start ( * * options )
2013-12-09 18:48:15 +01:00
2013-12-20 19:30:23 +01:00
def stop ( self , service_names = None , * * options ) :
2014-02-10 01:01:13 +01:00
for service in reversed ( self . get_services ( service_names ) ) :
2013-12-20 17:22:54 +01:00
service . stop ( * * options )
2013-12-09 18:48:15 +01:00
2013-12-20 19:30:23 +01:00
def kill ( self , service_names = None , * * options ) :
2014-02-10 01:01:13 +01:00
for service in reversed ( self . get_services ( service_names ) ) :
2013-12-20 17:53:07 +01:00
service . kill ( * * options )
2014-01-02 16:28:33 +01:00
def build ( self , service_names = None , * * options ) :
for service in self . get_services ( service_names ) :
if service . can_be_built ( ) :
service . build ( * * options )
else :
2014-01-03 00:28:18 +01:00
log . info ( ' %s uses an image, skipping ' % service . name )
2014-01-02 16:28:33 +01:00
2014-06-09 01:20:51 +02:00
def up ( self , service_names = None , start_links = True , recreate = True ) :
2014-06-09 00:47:09 +02:00
running_containers = [ ]
2014-03-21 19:34:19 +01:00
2014-06-08 10:51:09 +02:00
for service in self . get_services ( service_names , include_links = start_links ) :
2014-06-09 01:20:51 +02:00
if recreate :
for ( _ , container ) in service . recreate_containers ( ) :
2014-06-09 00:47:09 +02:00
running_containers . append ( container )
else :
2014-06-09 01:20:51 +02:00
for container in service . start_or_create_containers ( ) :
2014-06-09 00:47:09 +02:00
running_containers . append ( container )
2014-03-21 19:34:19 +01:00
2014-06-09 00:47:09 +02:00
return running_containers
2014-03-21 19:34:19 +01:00
2013-12-20 19:30:23 +01:00
def remove_stopped ( self , service_names = None , * * options ) :
for service in self . get_services ( service_names ) :
2013-12-20 17:53:07 +01:00
service . remove_stopped ( * * options )
2013-12-20 19:30:23 +01:00
def containers ( self , service_names = None , * args , * * kwargs ) :
2013-12-19 14:02:04 +01:00
l = [ ]
2013-12-20 19:30:23 +01:00
for service in self . get_services ( service_names ) :
2013-12-19 14:02:04 +01:00
for container in service . containers ( * args , * * kwargs ) :
l . append ( container )
return l
2013-12-09 18:48:15 +01:00
2014-06-08 12:08:40 +02:00
def _inject_links ( self , acc , service ) :
2014-06-08 11:09:57 +02:00
linked_names = service . get_linked_names ( )
if len ( linked_names ) > 0 :
linked_services = self . get_services (
service_names = linked_names ,
include_links = True
)
else :
linked_services = [ ]
2014-06-08 10:51:09 +02:00
linked_services . append ( service )
return acc + linked_services
2013-12-09 15:10:23 +01:00
2013-12-20 19:30:23 +01:00
class NoSuchService ( Exception ) :
def __init__ ( self , name ) :
self . name = name
self . msg = " No such service: %s " % self . name
def __str__ ( self ) :
return self . msg
2014-01-23 23:27:54 +01:00
2014-03-03 17:21:42 +01:00
class ConfigurationError ( Exception ) :
2014-01-23 23:27:54 +01:00
def __init__ ( self , msg ) :
self . msg = msg
def __str__ ( self ) :
2014-02-10 01:01:13 +01:00
return self . msg
2014-03-03 17:21:42 +01:00
class DependencyError ( ConfigurationError ) :
pass