Merge pull request #246 from d11wtq/feature/auto_start

Feature: `fig up` and `fig run` now start linked containers (closes #31).
This commit is contained in:
Ben Firshman 2014-06-24 14:46:47 +01:00
commit 95aa61cfe5
8 changed files with 400 additions and 34 deletions

View file

@ -202,21 +202,30 @@ class TopLevelCommand(Command):
$ fig run web python manage.py shell
Note that this will not start any services that the command's service
links to. So if, for example, your one-off command talks to your
database, you will need to run `fig up -d db` first.
By default, linked services will be started, unless they are already
running. If you do not want to start linked services, use
`fig run --no-deps SERVICE COMMAND [ARGS...]`.
Usage: run [options] SERVICE COMMAND [ARGS...]
Options:
-d Detached mode: Run container in the background, print new
container name
-T Disable pseudo-tty allocation. By default `fig run`
allocates a TTY.
--rm Remove container after run. Ignored in detached mode.
-d Detached mode: Run container in the background, print
new container name.
-T Disable pseudo-tty allocation. By default `fig run`
allocates a TTY.
--rm Remove container after run. Ignored in detached mode.
--no-deps Don't start linked services.
"""
service = self.project.get_service(options['SERVICE'])
if not options['--no-deps']:
self.project.up(
service_names=service.get_linked_names(),
start_links=True,
recreate=False
)
tty = True
if options['-d'] or options['-T'] or not sys.stdin.isatty():
tty = False
@ -293,17 +302,29 @@ class TopLevelCommand(Command):
If there are existing containers for a service, `fig up` will stop
and recreate them (preserving mounted volumes with volumes-from),
so that changes in `fig.yml` are picked up.
so that changes in `fig.yml` are picked up. If you do not want existing
containers to be recreated, `fig up --no-recreate` will re-use existing
containers.
Usage: up [options] [SERVICE...]
Options:
-d Detached mode: Run containers in the background, print new
container names
-d Detached mode: Run containers in the background,
print new container names.
--no-deps Don't start linked services.
--no-recreate If containers already exist, don't recreate them.
"""
detached = options['-d']
to_attach = self.project.up(service_names=options['SERVICE'])
start_links = not options['--no-deps']
recreate = not options['--no-recreate']
service_names = options['SERVICE']
to_attach = self.project.up(
service_names=service_names,
start_links=start_links,
recreate=recreate
)
if not detached:
print("Attaching to", list_containers(to_attach))
@ -313,12 +334,12 @@ class TopLevelCommand(Command):
log_printer.run()
finally:
def handler(signal, frame):
self.project.kill(service_names=options['SERVICE'])
self.project.kill(service_names=service_names)
sys.exit(0)
signal.signal(signal.SIGINT, handler)
print("Gracefully stopping... (press Ctrl+C again to force)")
self.project.stop(service_names=options['SERVICE'])
self.project.stop(service_names=service_names)
def _attach_to_container(self, container_id, raw=False):
socket_in = self.client.attach_socket(container_id, params={'stdin': 1, 'stream': 1})

View file

@ -64,6 +64,7 @@ class Project(object):
raise ConfigurationError('Service "%s" has a link to service "%s" which does not exist.' % (service_dict['name'], service_name))
del service_dict['links']
project.services.append(Service(client=client, project=name, links=links, **service_dict))
return project
@ -88,22 +89,35 @@ class Project(object):
raise NoSuchService(name)
def get_services(self, service_names=None):
def get_services(self, service_names=None, include_links=False):
"""
Returns a list of this project's services filtered
by the provided list of names, or all services if
service_names is None or [].
by the provided list of names, or all services if service_names is None
or [].
Preserves the original order of self.services.
If include_links is specified, returns a list including the links for
service_names, in order of dependency.
Raises NoSuchService if any of the named services
do not exist.
Preserves the original order of self.services where possible,
reordering as needed to resolve links.
Raises NoSuchService if any of the named services do not exist.
"""
if service_names is None or len(service_names) == 0:
return self.services
return self.get_services(
service_names=[s.name for s in self.services],
include_links=include_links
)
else:
unsorted = [self.get_service(name) for name in service_names]
return [s for s in self.services if s in unsorted]
services = [s for s in self.services if s in unsorted]
if include_links:
services = reduce(self._inject_links, services, [])
uniques = []
[uniques.append(s) for s in services if s not in uniques]
return uniques
def start(self, service_names=None, **options):
for service in self.get_services(service_names):
@ -124,14 +138,18 @@ class Project(object):
else:
log.info('%s uses an image, skipping' % service.name)
def up(self, service_names=None):
new_containers = []
def up(self, service_names=None, start_links=True, recreate=True):
running_containers = []
for service in self.get_services(service_names):
for (_, new) in service.recreate_containers():
new_containers.append(new)
for service in self.get_services(service_names, include_links=start_links):
if recreate:
for (_, container) in service.recreate_containers():
running_containers.append(container)
else:
for container in service.start_or_create_containers():
running_containers.append(container)
return new_containers
return running_containers
def remove_stopped(self, service_names=None, **options):
for service in self.get_services(service_names):
@ -144,6 +162,20 @@ class Project(object):
l.append(container)
return l
def _inject_links(self, acc, service):
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 = []
linked_services.append(service)
return acc + linked_services
class NoSuchService(Exception):
def __init__(self, name):

View file

@ -75,9 +75,7 @@ class Service(object):
def start(self, **options):
for c in self.containers(stopped=True):
if not c.is_running:
log.info("Starting %s..." % c.name)
self.start_container(c, **options)
self.start_container_if_stopped(c, **options)
def stop(self, **options):
for c in self.containers():
@ -200,6 +198,13 @@ class Service(object):
return (intermediate_container, new_container)
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)
def start_container(self, container=None, volumes_from=None, **override_options):
if container is None:
container = self.create_container(**override_options)
@ -243,6 +248,19 @@ class Service(object):
)
return container
def start_or_create_containers(self):
containers = self.containers(stopped=True)
if len(containers) == 0:
log.info("Creating %s..." % self.next_container_name())
new_container = self.create_container()
return [self.start_container(new_container)]
else:
return [self.start_container_if_stopped(c) for c in containers]
def get_linked_names(self):
return [s.name for (s, _) in self.links]
def next_container_name(self, one_off=False):
bits = [self.project, self.name]
if one_off: