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:
commit
95aa61cfe5
8 changed files with 400 additions and 34 deletions
|
|
@ -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})
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue