Add docker-compose create command.

Closes #1125

Signed-off-by: Stéphane Seguin <stephseguin93@gmail.com>
This commit is contained in:
Stéphane Seguin 2015-12-14 22:46:13 +01:00
parent a1217dc050
commit 3c76d5a467
6 changed files with 179 additions and 11 deletions

View File

@ -130,6 +130,7 @@ class TopLevelCommand(DocoptCommand):
Commands:
build Build or rebuild services
config Validate and view the compose file
create Create services
help Get help on a command
kill Kill containers
logs View output from containers
@ -221,6 +222,27 @@ class TopLevelCommand(DocoptCommand):
indent=2,
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):
"""
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]
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):
links = []
if 'links' in service_dict:
@ -215,6 +221,14 @@ class Project(object):
else:
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,
service_names=None,
start_deps=True,
@ -223,10 +237,7 @@ class Project(object):
timeout=DEFAULT_TIMEOUT,
detached=False):
services = self.get_services(service_names, include_deps=start_deps)
for service in services:
service.remove_duplicate_containers()
services = self.get_services_without_duplicate(service_names, include_deps=start_deps)
plans = self._get_convergence_plans(services, strategy)

View File

@ -328,7 +328,8 @@ class Service(object):
plan,
do_build=True,
timeout=DEFAULT_TIMEOUT,
detached=False):
detached=False,
start=True):
(action, containers) = plan
should_attach_logs = not detached
@ -338,7 +339,8 @@ class Service(object):
if should_attach_logs:
container.attach_log_stream()
container.start()
if start:
container.start()
return [container]
@ -348,14 +350,16 @@ class Service(object):
container,
do_build=do_build,
timeout=timeout,
attach_logs=should_attach_logs
attach_logs=should_attach_logs,
start_new_container=start
)
for container in containers
]
elif action == 'start':
for container in containers:
self.start_container_if_stopped(container, attach_logs=should_attach_logs)
if start:
for container in containers:
self.start_container_if_stopped(container, attach_logs=should_attach_logs)
return containers
@ -373,7 +377,8 @@ class Service(object):
container,
do_build=False,
timeout=DEFAULT_TIMEOUT,
attach_logs=False):
attach_logs=False,
start_new_container=True):
"""Recreate a container.
The original container is renamed to a temporary name so that data
@ -392,7 +397,8 @@ class Service(object):
)
if attach_logs:
new_container.attach_log_stream()
new_container.start()
if start_new_container:
new_container.start()
container.remove()
return new_container

View File

@ -264,6 +264,52 @@ class CLITestCase(DockerClientTestCase):
]
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):
self.dispatch(['up', '-d'])
service = self.project.get_service('simple')

View File

@ -213,6 +213,71 @@ class ProjectTest(DockerClientTestCase):
project.remove_stopped()
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):
web = self.create_service('web')
db = self.create_service('db', volumes=[VolumeSpec.parse('/var/db')])

View File

@ -333,6 +333,24 @@ class ServiceTest(DockerClientTestCase):
self.assertEqual(list(new_container.get('Volumes')), ['/data'])
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):
db = self.create_service('db')
create_and_start_container(db, environment={'FOO': 'BAR'})