Handle both SIGINT and SIGTERM for docker-compose run.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2015-11-10 19:43:05 -05:00
parent ea4230e7a2
commit 6236bb0019
2 changed files with 102 additions and 44 deletions

View File

@ -368,7 +368,6 @@ class TopLevelCommand(DocoptCommand):
allocates a TTY. allocates a TTY.
""" """
service = project.get_service(options['SERVICE']) service = project.get_service(options['SERVICE'])
detach = options['-d'] detach = options['-d']
if IS_WINDOWS_PLATFORM and not detach: if IS_WINDOWS_PLATFORM and not detach:
@ -380,22 +379,6 @@ class TopLevelCommand(DocoptCommand):
if options['--allow-insecure-ssl']: if options['--allow-insecure-ssl']:
log.warn(INSECURE_SSL_WARNING) log.warn(INSECURE_SSL_WARNING)
if not options['--no-deps']:
deps = service.get_linked_service_names()
if len(deps) > 0:
project.up(
service_names=deps,
start_deps=True,
strategy=ConvergenceStrategy.never,
)
elif project.use_networking:
project.ensure_network_exists()
tty = True
if detach or options['-T'] or not sys.stdin.isatty():
tty = False
if options['COMMAND']: if options['COMMAND']:
command = [options['COMMAND']] + options['ARGS'] command = [options['COMMAND']] + options['ARGS']
else: else:
@ -403,7 +386,7 @@ class TopLevelCommand(DocoptCommand):
container_options = { container_options = {
'command': command, 'command': command,
'tty': tty, 'tty': not (detach or options['-T'] or not sys.stdin.isatty()),
'stdin_open': not detach, 'stdin_open': not detach,
'detach': detach, 'detach': detach,
} }
@ -435,31 +418,7 @@ class TopLevelCommand(DocoptCommand):
if options['--name']: if options['--name']:
container_options['name'] = options['--name'] container_options['name'] = options['--name']
try: run_one_off_container(container_options, project, service, options)
container = service.create_container(
quiet=True,
one_off=True,
**container_options
)
except APIError as e:
legacy.check_for_legacy_containers(
project.client,
project.name,
[service.name],
allow_one_off=False,
)
raise e
if detach:
container.start()
print(container.name)
else:
dockerpty.start(project.client, container.id, interactive=not options['-T'])
exit_code = container.wait()
if options['--rm']:
project.client.remove_container(container.id)
sys.exit(exit_code)
def scale(self, project, options): def scale(self, project, options):
""" """
@ -647,6 +606,58 @@ def convergence_strategy_from_opts(options):
return ConvergenceStrategy.changed return ConvergenceStrategy.changed
def run_one_off_container(container_options, project, service, options):
if not options['--no-deps']:
deps = service.get_linked_service_names()
if deps:
project.up(
service_names=deps,
start_deps=True,
strategy=ConvergenceStrategy.never)
if project.use_networking:
project.ensure_network_exists()
try:
container = service.create_container(
quiet=True,
one_off=True,
**container_options)
except APIError:
legacy.check_for_legacy_containers(
project.client,
project.name,
[service.name],
allow_one_off=False)
raise
if options['-d']:
container.start()
print(container.name)
return
def remove_container(force=False):
if options['--rm']:
project.client.remove_container(container.id, force=True)
def force_shutdown(signal, frame):
project.client.kill(container.id)
remove_container(force=True)
sys.exit(2)
def shutdown(signal, frame):
set_signal_handler(force_shutdown)
project.client.stop(container.id)
remove_container()
sys.exit(1)
set_signal_handler(shutdown)
dockerpty.start(project.client, container.id, interactive=not options['-T'])
exit_code = container.wait()
remove_container()
sys.exit(exit_code)
def build_log_printer(containers, service_names, monochrome): def build_log_printer(containers, service_names, monochrome):
if service_names: if service_names:
containers = [ containers = [
@ -657,7 +668,6 @@ def build_log_printer(containers, service_names, monochrome):
def attach_to_logs(project, log_printer, service_names, timeout): def attach_to_logs(project, log_printer, service_names, timeout):
print("Attaching to", list_containers(log_printer.containers))
def force_shutdown(signal, frame): def force_shutdown(signal, frame):
project.kill(service_names=service_names) project.kill(service_names=service_names)
@ -668,6 +678,7 @@ def attach_to_logs(project, log_printer, service_names, timeout):
print("Gracefully stopping... (press Ctrl+C again to force)") print("Gracefully stopping... (press Ctrl+C again to force)")
project.stop(service_names=service_names, timeout=timeout) project.stop(service_names=service_names, timeout=timeout)
print("Attaching to", list_containers(log_printer.containers))
set_signal_handler(shutdown) set_signal_handler(shutdown)
log_printer.run() log_printer.run()

View File

@ -8,6 +8,8 @@ import time
from collections import namedtuple from collections import namedtuple
from operator import attrgetter from operator import attrgetter
from docker import errors
from .. import mock from .. import mock
from compose.cli.command import get_project from compose.cli.command import get_project
from compose.cli.docker_client import docker_client from compose.cli.docker_client import docker_client
@ -61,6 +63,25 @@ class ContainerCountCondition(object):
return "waiting for counter count == %s" % self.expected return "waiting for counter count == %s" % self.expected
class ContainerStateCondition(object):
def __init__(self, client, name, running):
self.client = client
self.name = name
self.running = running
# State.Running == true
def __call__(self):
try:
container = self.client.inspect_container(self.name)
return container['State']['Running'] == self.running
except errors.APIError:
return False
def __str__(self):
return "waiting for container to have state %s" % self.expected
class CLITestCase(DockerClientTestCase): class CLITestCase(DockerClientTestCase):
def setUp(self): def setUp(self):
@ -554,6 +575,32 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(len(networks), 1) self.assertEqual(len(networks), 1)
self.assertEqual(container.human_readable_command, u'true') self.assertEqual(container.human_readable_command, u'true')
def test_run_handles_sigint(self):
proc = start_process(self.base_dir, ['run', '-T', 'simple', 'top'])
wait_on_condition(ContainerStateCondition(
self.project.client,
'simplecomposefile_simple_run_1',
running=True))
os.kill(proc.pid, signal.SIGINT)
wait_on_condition(ContainerStateCondition(
self.project.client,
'simplecomposefile_simple_run_1',
running=False))
def test_run_handles_sigterm(self):
proc = start_process(self.base_dir, ['run', '-T', 'simple', 'top'])
wait_on_condition(ContainerStateCondition(
self.project.client,
'simplecomposefile_simple_run_1',
running=True))
os.kill(proc.pid, signal.SIGTERM)
wait_on_condition(ContainerStateCondition(
self.project.client,
'simplecomposefile_simple_run_1',
running=False))
def test_rm(self): def test_rm(self):
service = self.project.get_service('simple') service = self.project.get_service('simple')
service.create_container() service.create_container()