mirror of https://github.com/docker/compose.git
Merge pull request #13 from orchardup/recreate-containers
Recreate containers on each `fig up`
This commit is contained in:
commit
c06456da37
|
@ -200,7 +200,7 @@ Run `fig [COMMAND] --help` for full usage.
|
|||
|
||||
Build or rebuild services.
|
||||
|
||||
Services are built once and then tagged as `project_service`. If you change a service's `Dockerfile` or its configuration in `fig.yml`, you will probably need to run `fig build` to rebuild it, then run `fig rm` to make `fig up` recreate your containers.
|
||||
Services are built once and then tagged as `project_service`, e.g. `figtest_db`. If you change a service's `Dockerfile` or the contents of its build directory, you can run `fig build` to rebuild it.
|
||||
|
||||
#### kill
|
||||
|
||||
|
@ -237,9 +237,11 @@ Stop running containers without removing them. They can be started again with `f
|
|||
|
||||
#### up
|
||||
|
||||
Build, create, start and attach to containers for a service.
|
||||
Build, (re)create, start and attach to containers for a service.
|
||||
|
||||
If there are stopped containers for a service, `fig up` will start those again instead of creating new containers. When it exits, the containers it started will be stopped. This means if you want to recreate containers, you will need to explicitly run `fig rm`.
|
||||
By default, `fig up` will aggregate the output of each container, and when it exits, all containers will be stopped. If you run `fig up -d`, it'll start the containers in the background and leave them running.
|
||||
|
||||
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.
|
||||
|
||||
### Environment variables
|
||||
|
||||
|
@ -265,3 +267,4 @@ Fully qualified container name, e.g. `MYAPP_DB_1_NAME=/myapp_web_1/myapp_db_1`
|
|||
|
||||
|
||||
[Docker links]: http://docs.docker.io/en/latest/use/port_redirection/#linking-a-container
|
||||
[volumes-from]: http://docs.docker.io/en/latest/use/working_with_volumes/
|
||||
|
|
|
@ -5,3 +5,6 @@ from textwrap import dedent
|
|||
class UserError(Exception):
|
||||
def __init__(self, msg):
|
||||
self.msg = dedent(msg).strip()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.msg
|
||||
|
|
|
@ -220,14 +220,18 @@ class TopLevelCommand(Command):
|
|||
"""
|
||||
detached = options['-d']
|
||||
|
||||
self.project.create_containers(service_names=options['SERVICE'])
|
||||
containers = self.project.containers(service_names=options['SERVICE'], stopped=True)
|
||||
(old, new) = self.project.recreate_containers(service_names=options['SERVICE'])
|
||||
|
||||
if not detached:
|
||||
print("Attaching to", list_containers(containers))
|
||||
log_printer = LogPrinter(containers)
|
||||
to_attach = [c for (s, c) in new]
|
||||
print("Attaching to", list_containers(to_attach))
|
||||
log_printer = LogPrinter(to_attach)
|
||||
|
||||
self.project.start(service_names=options['SERVICE'])
|
||||
for (service, container) in new:
|
||||
service.start_container(container)
|
||||
|
||||
for (service, container) in old:
|
||||
container.remove()
|
||||
|
||||
if not detached:
|
||||
try:
|
||||
|
|
|
@ -1,8 +1,5 @@
|
|||
from __future__ import unicode_literals
|
||||
from __future__ import absolute_import
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
class Container(object):
|
||||
"""
|
||||
|
@ -91,19 +88,15 @@ class Container(object):
|
|||
return self.dictionary['State']['Running']
|
||||
|
||||
def start(self, **options):
|
||||
log.info("Starting %s..." % self.name)
|
||||
return self.client.start(self.id, **options)
|
||||
|
||||
def stop(self, **options):
|
||||
log.info("Stopping %s..." % self.name)
|
||||
return self.client.stop(self.id, **options)
|
||||
|
||||
def kill(self):
|
||||
log.info("Killing %s..." % self.name)
|
||||
return self.client.kill(self.id)
|
||||
|
||||
def remove(self):
|
||||
log.info("Removing %s..." % self.name)
|
||||
return self.client.remove_container(self.id)
|
||||
|
||||
def inspect_if_not_inspected(self):
|
||||
|
|
|
@ -79,13 +79,22 @@ class Project(object):
|
|||
unsorted = [self.get_service(name) for name in service_names]
|
||||
return [s for s in self.services if s in unsorted]
|
||||
|
||||
def create_containers(self, service_names=None):
|
||||
def recreate_containers(self, service_names=None):
|
||||
"""
|
||||
For each service, creates a container if there are none.
|
||||
For each service, create or recreate their containers.
|
||||
Returns a tuple with two lists. The first is a list of
|
||||
(service, old_container) tuples; the second is a list
|
||||
of (service, new_container) tuples.
|
||||
"""
|
||||
old = []
|
||||
new = []
|
||||
|
||||
for service in self.get_services(service_names):
|
||||
if len(service.containers(stopped=True)) == 0:
|
||||
service.create_container()
|
||||
(s_old, s_new) = service.recreate_containers()
|
||||
old += [(service, container) for container in s_old]
|
||||
new += [(service, container) for container in s_new]
|
||||
|
||||
return (old, new)
|
||||
|
||||
def start(self, service_names=None, **options):
|
||||
for service in self.get_services(service_names):
|
||||
|
|
|
@ -43,19 +43,23 @@ 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)
|
||||
|
||||
def stop(self, **options):
|
||||
for c in self.containers():
|
||||
log.info("Stopping %s..." % c.name)
|
||||
c.stop(**options)
|
||||
|
||||
def kill(self, **options):
|
||||
for c in self.containers():
|
||||
log.info("Killing %s..." % c.name)
|
||||
c.kill(**options)
|
||||
|
||||
def remove_stopped(self, **options):
|
||||
for c in self.containers(stopped=True):
|
||||
if not c.is_running:
|
||||
log.info("Removing %s..." % c.name)
|
||||
c.remove(**options)
|
||||
|
||||
def create_container(self, one_off=False, **override_options):
|
||||
|
@ -73,6 +77,48 @@ class Service(object):
|
|||
return Container.create(self.client, **container_options)
|
||||
raise
|
||||
|
||||
def recreate_containers(self, **override_options):
|
||||
"""
|
||||
If a container for this service doesn't exist, create one. If there are
|
||||
any, stop them and create new ones. Does not remove the old containers.
|
||||
"""
|
||||
containers = self.containers(stopped=True)
|
||||
|
||||
if len(containers) == 0:
|
||||
log.info("Creating %s..." % self.next_container_name())
|
||||
return ([], [self.create_container(**override_options)])
|
||||
else:
|
||||
old_containers = []
|
||||
new_containers = []
|
||||
|
||||
for c in containers:
|
||||
log.info("Recreating %s..." % c.name)
|
||||
(old_container, new_container) = self.recreate_container(c, **override_options)
|
||||
old_containers.append(old_container)
|
||||
new_containers.append(new_container)
|
||||
|
||||
return (old_containers, new_containers)
|
||||
|
||||
def recreate_container(self, container, **override_options):
|
||||
if container.is_running:
|
||||
container.stop(timeout=1)
|
||||
|
||||
intermediate_container = Container.create(
|
||||
self.client,
|
||||
image='ubuntu',
|
||||
command='echo',
|
||||
volumes_from=container.id,
|
||||
)
|
||||
intermediate_container.start()
|
||||
intermediate_container.wait()
|
||||
container.remove()
|
||||
|
||||
options = dict(override_options)
|
||||
options['volumes_from'] = intermediate_container.id
|
||||
new_container = self.create_container(**options)
|
||||
|
||||
return (intermediate_container, new_container)
|
||||
|
||||
def start_container(self, container=None, **override_options):
|
||||
if container is None:
|
||||
container = self.create_container(**override_options)
|
||||
|
@ -95,8 +141,9 @@ class Service(object):
|
|||
|
||||
if options.get('volumes', None) is not None:
|
||||
for volume in options['volumes']:
|
||||
external_dir, internal_dir = volume.split(':')
|
||||
volume_bindings[os.path.abspath(external_dir)] = internal_dir
|
||||
if ':' in volume:
|
||||
external_dir, internal_dir = volume.split(':')
|
||||
volume_bindings[os.path.abspath(external_dir)] = internal_dir
|
||||
|
||||
container.start(
|
||||
links=self._get_links(),
|
||||
|
@ -143,7 +190,7 @@ class Service(object):
|
|||
container_options['ports'] = ports
|
||||
|
||||
if 'volumes' in container_options:
|
||||
container_options['volumes'] = dict((v.split(':')[1], {}) for v in container_options['volumes'])
|
||||
container_options['volumes'] = dict((split_volume(v)[1], {}) for v in container_options['volumes'])
|
||||
|
||||
if self.can_be_built():
|
||||
if len(self.client.images(name=self._build_tag_name())) == 0:
|
||||
|
@ -214,3 +261,14 @@ def get_container_name(container):
|
|||
for name in container['Names']:
|
||||
if len(name.split('/')) == 2:
|
||||
return name[1:]
|
||||
|
||||
|
||||
def split_volume(v):
|
||||
"""
|
||||
If v is of the format EXTERNAL:INTERNAL, returns (EXTERNAL, INTERNAL).
|
||||
If v is of the format INTERNAL, returns (None, INTERNAL).
|
||||
"""
|
||||
if ':' in v:
|
||||
return v.split(':', 1)
|
||||
else:
|
||||
return (None, v)
|
||||
|
|
|
@ -42,16 +42,22 @@ class ProjectTest(DockerClientTestCase):
|
|||
project = Project('test', [web], self.client)
|
||||
self.assertEqual(project.get_service('web'), web)
|
||||
|
||||
def test_create_containers(self):
|
||||
def test_recreate_containers(self):
|
||||
web = self.create_service('web')
|
||||
db = self.create_service('db')
|
||||
project = Project('test', [web, db], self.client)
|
||||
|
||||
project.create_containers(service_names=['web'])
|
||||
old_web_container = web.create_container()
|
||||
self.assertEqual(len(web.containers(stopped=True)), 1)
|
||||
self.assertEqual(len(db.containers(stopped=True)), 0)
|
||||
|
||||
project.create_containers()
|
||||
(old, new) = project.recreate_containers()
|
||||
self.assertEqual(len(old), 1)
|
||||
self.assertEqual(old[0][0], web)
|
||||
self.assertEqual(len(new), 2)
|
||||
self.assertEqual(new[0][0], web)
|
||||
self.assertEqual(new[1][0], db)
|
||||
|
||||
self.assertEqual(len(web.containers(stopped=True)), 1)
|
||||
self.assertEqual(len(db.containers(stopped=True)), 1)
|
||||
|
||||
|
|
|
@ -102,6 +102,36 @@ class ServiceTest(DockerClientTestCase):
|
|||
container = db.create_container(one_off=True)
|
||||
self.assertEqual(container.name, 'figtest_db_run_1')
|
||||
|
||||
def test_create_container_with_unspecified_volume(self):
|
||||
service = self.create_service('db', volumes=['/var/db'])
|
||||
container = service.create_container()
|
||||
service.start_container(container)
|
||||
self.assertIn('/var/db', container.inspect()['Volumes'])
|
||||
|
||||
def test_recreate_containers(self):
|
||||
service = self.create_service('db', environment={'FOO': '1'}, volumes=['/var/db'])
|
||||
old_container = service.create_container()
|
||||
self.assertEqual(old_container.dictionary['Config']['Env'], ['FOO=1'])
|
||||
self.assertEqual(old_container.name, 'figtest_db_1')
|
||||
service.start_container(old_container)
|
||||
volume_path = old_container.inspect()['Volumes']['/var/db']
|
||||
|
||||
num_containers_before = len(self.client.containers(all=True))
|
||||
|
||||
service.options['environment']['FOO'] = '2'
|
||||
(old, new) = service.recreate_containers()
|
||||
self.assertEqual(len(old), 1)
|
||||
self.assertEqual(len(new), 1)
|
||||
|
||||
new_container = new[0]
|
||||
self.assertEqual(new_container.dictionary['Config']['Env'], ['FOO=2'])
|
||||
self.assertEqual(new_container.name, 'figtest_db_1')
|
||||
service.start_container(new_container)
|
||||
self.assertEqual(new_container.inspect()['Volumes']['/var/db'], volume_path)
|
||||
|
||||
self.assertEqual(len(self.client.containers(all=True)), num_containers_before + 1)
|
||||
self.assertNotEqual(old_container.id, new_container.id)
|
||||
|
||||
def test_start_container_passes_through_options(self):
|
||||
db = self.create_service('db')
|
||||
db.start_container(environment={'FOO': 'BAR'})
|
||||
|
|
Loading…
Reference in New Issue