Merge pull request #2547 from seguins/1125-docker-compose-create

Add docker-compose create command.
This commit is contained in:
Aanand Prasad 2015-12-21 14:20:56 +00:00
commit a2d2915a64
6 changed files with 179 additions and 11 deletions

View File

@ -130,6 +130,7 @@ class TopLevelCommand(DocoptCommand):
Commands: Commands:
build Build or rebuild services build Build or rebuild services
config Validate and view the compose file config Validate and view the compose file
create Create services
help Get help on a command help Get help on a command
kill Kill containers kill Kill containers
logs View output from containers logs View output from containers
@ -221,6 +222,27 @@ class TopLevelCommand(DocoptCommand):
indent=2, indent=2,
width=80)) width=80))
def create(self, project, options):
"""
Creates containers for a service.
Usage: create [options] [SERVICE...]
Options:
--force-recreate Recreate containers even if their configuration and
image haven't changed. Incompatible with --no-recreate.
--no-recreate If containers already exist, don't recreate them.
Incompatible with --force-recreate.
--no-build Don't build an image, even if it's missing
"""
service_names = options['SERVICE']
project.create(
service_names=service_names,
strategy=convergence_strategy_from_opts(options),
do_build=not options['--no-build']
)
def help(self, project, options): def help(self, project, options):
""" """
Get help on a command. Get help on a command.

View File

@ -123,6 +123,12 @@ class Project(object):
[uniques.append(s) for s in services if s not in uniques] [uniques.append(s) for s in services if s not in uniques]
return uniques return uniques
def get_services_without_duplicate(self, service_names=None, include_deps=False):
services = self.get_services(service_names, include_deps)
for service in services:
service.remove_duplicate_containers()
return services
def get_links(self, service_dict): def get_links(self, service_dict):
links = [] links = []
if 'links' in service_dict: if 'links' in service_dict:
@ -224,6 +230,14 @@ class Project(object):
else: else:
log.info('%s uses an image, skipping' % service.name) log.info('%s uses an image, skipping' % service.name)
def create(self, service_names=None, strategy=ConvergenceStrategy.changed, do_build=True):
services = self.get_services_without_duplicate(service_names, include_deps=True)
plans = self._get_convergence_plans(services, strategy)
for service in services:
service.execute_convergence_plan(plans[service.name], do_build, detached=True, start=False)
def up(self, def up(self,
service_names=None, service_names=None,
start_deps=True, start_deps=True,
@ -232,10 +246,7 @@ class Project(object):
timeout=DEFAULT_TIMEOUT, timeout=DEFAULT_TIMEOUT,
detached=False): detached=False):
services = self.get_services(service_names, include_deps=start_deps) services = self.get_services_without_duplicate(service_names, include_deps=start_deps)
for service in services:
service.remove_duplicate_containers()
plans = self._get_convergence_plans(services, strategy) plans = self._get_convergence_plans(services, strategy)

View File

@ -331,7 +331,8 @@ class Service(object):
plan, plan,
do_build=True, do_build=True,
timeout=DEFAULT_TIMEOUT, timeout=DEFAULT_TIMEOUT,
detached=False): detached=False,
start=True):
(action, containers) = plan (action, containers) = plan
should_attach_logs = not detached should_attach_logs = not detached
@ -341,6 +342,7 @@ class Service(object):
if should_attach_logs: if should_attach_logs:
container.attach_log_stream() container.attach_log_stream()
if start:
container.start() container.start()
return [container] return [container]
@ -351,12 +353,14 @@ class Service(object):
container, container,
do_build=do_build, do_build=do_build,
timeout=timeout, timeout=timeout,
attach_logs=should_attach_logs attach_logs=should_attach_logs,
start_new_container=start
) )
for container in containers for container in containers
] ]
elif action == 'start': elif action == 'start':
if start:
for container in containers: for container in containers:
self.start_container_if_stopped(container, attach_logs=should_attach_logs) self.start_container_if_stopped(container, attach_logs=should_attach_logs)
@ -376,7 +380,8 @@ class Service(object):
container, container,
do_build=False, do_build=False,
timeout=DEFAULT_TIMEOUT, timeout=DEFAULT_TIMEOUT,
attach_logs=False): attach_logs=False,
start_new_container=True):
"""Recreate a container. """Recreate a container.
The original container is renamed to a temporary name so that data The original container is renamed to a temporary name so that data
@ -395,6 +400,7 @@ class Service(object):
) )
if attach_logs: if attach_logs:
new_container.attach_log_stream() new_container.attach_log_stream()
if start_new_container:
new_container.start() new_container.start()
container.remove() container.remove()
return new_container return new_container

View File

@ -264,6 +264,52 @@ class CLITestCase(DockerClientTestCase):
] ]
assert not containers assert not containers
def test_create(self):
self.dispatch(['create'])
service = self.project.get_service('simple')
another = self.project.get_service('another')
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(another.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
self.assertEqual(len(another.containers(stopped=True)), 1)
def test_create_with_force_recreate(self):
self.dispatch(['create'], None)
service = self.project.get_service('simple')
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
old_ids = [c.id for c in service.containers(stopped=True)]
self.dispatch(['create', '--force-recreate'], None)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
new_ids = [c.id for c in service.containers(stopped=True)]
self.assertNotEqual(old_ids, new_ids)
def test_create_with_no_recreate(self):
self.dispatch(['create'], None)
service = self.project.get_service('simple')
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
old_ids = [c.id for c in service.containers(stopped=True)]
self.dispatch(['create', '--no-recreate'], None)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
new_ids = [c.id for c in service.containers(stopped=True)]
self.assertEqual(old_ids, new_ids)
def test_create_with_force_recreate_and_no_recreate(self):
self.dispatch(
['create', '--force-recreate', '--no-recreate'],
returncode=1)
def test_up_detached(self): def test_up_detached(self):
self.dispatch(['up', '-d']) self.dispatch(['up', '-d'])
service = self.project.get_service('simple') service = self.project.get_service('simple')

View File

@ -213,6 +213,71 @@ class ProjectTest(DockerClientTestCase):
project.remove_stopped() project.remove_stopped()
self.assertEqual(len(project.containers(stopped=True)), 0) self.assertEqual(len(project.containers(stopped=True)), 0)
def test_create(self):
web = self.create_service('web')
db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
project = Project('composetest', [web, db], self.client)
project.create(['db'])
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 1)
self.assertEqual(len(db.containers()), 0)
self.assertEqual(len(db.containers(stopped=True)), 1)
self.assertEqual(len(web.containers(stopped=True)), 0)
def test_create_twice(self):
web = self.create_service('web')
db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])
project = Project('composetest', [web, db], self.client)
project.create(['db', 'web'])
project.create(['db', 'web'])
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 2)
self.assertEqual(len(db.containers()), 0)
self.assertEqual(len(db.containers(stopped=True)), 1)
self.assertEqual(len(web.containers()), 0)
self.assertEqual(len(web.containers(stopped=True)), 1)
def test_create_with_links(self):
db = self.create_service('db')
web = self.create_service('web', links=[(db, 'db')])
project = Project('composetest', [db, web], self.client)
project.create(['web'])
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 2)
self.assertEqual(len(db.containers()), 0)
self.assertEqual(len(db.containers(stopped=True)), 1)
self.assertEqual(len(web.containers()), 0)
self.assertEqual(len(web.containers(stopped=True)), 1)
def test_create_strategy_always(self):
db = self.create_service('db')
project = Project('composetest', [db], self.client)
project.create(['db'])
old_id = project.containers(stopped=True)[0].id
project.create(['db'], strategy=ConvergenceStrategy.always)
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 1)
db_container = project.containers(stopped=True)[0]
self.assertNotEqual(db_container.id, old_id)
def test_create_strategy_never(self):
db = self.create_service('db')
project = Project('composetest', [db], self.client)
project.create(['db'])
old_id = project.containers(stopped=True)[0].id
project.create(['db'], strategy=ConvergenceStrategy.never)
self.assertEqual(len(project.containers()), 0)
self.assertEqual(len(project.containers(stopped=True)), 1)
db_container = project.containers(stopped=True)[0]
self.assertEqual(db_container.id, old_id)
def test_project_up(self): def test_project_up(self):
web = self.create_service('web') web = self.create_service('web')
db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')]) db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])

View File

@ -339,6 +339,24 @@ class ServiceTest(DockerClientTestCase):
self.assertEqual(list(new_container.get('Volumes')), ['/data']) self.assertEqual(list(new_container.get('Volumes')), ['/data'])
self.assertEqual(new_container.get('Volumes')['/data'], volume_path) self.assertEqual(new_container.get('Volumes')['/data'], volume_path)
def test_execute_convergence_plan_without_start(self):
service = self.create_service(
'db',
build='tests/fixtures/dockerfile-with-volume'
)
containers = service.execute_convergence_plan(ConvergencePlan('create', []), start=False)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
containers = service.execute_convergence_plan(ConvergencePlan('recreate', containers), start=False)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
service.execute_convergence_plan(ConvergencePlan('start', containers), start=False)
self.assertEqual(len(service.containers()), 0)
self.assertEqual(len(service.containers(stopped=True)), 1)
def test_start_container_passes_through_options(self): def test_start_container_passes_through_options(self):
db = self.create_service('db') db = self.create_service('db')
create_and_start_container(db, environment={'FOO': 'BAR'}) create_and_start_container(db, environment={'FOO': 'BAR'})