diff --git a/compose/cli/main.py b/compose/cli/main.py index 1a2f3c725..a9d04472f 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -439,9 +439,9 @@ class TopLevelCommand(Command): image needs to be updated. (EXPERIMENTAL) --no-recreate If containers already exist, don't recreate them. --no-build Don't build an image, even if it's missing - -t, --timeout TIMEOUT When attached, use this timeout in seconds - for the shutdown. (default: 10) - + -t, --timeout TIMEOUT Use this timeout in seconds for container shutdown + when attached or when containers are already + running. (default: 10) """ insecure_registry = options['--allow-insecure-ssl'] detached = options['-d'] @@ -452,6 +452,7 @@ class TopLevelCommand(Command): allow_recreate = not options['--no-recreate'] smart_recreate = options['--x-smart-recreate'] service_names = options['SERVICE'] + timeout = int(options['--timeout']) if options['--timeout'] is not None else None project.up( service_names=service_names, @@ -460,6 +461,7 @@ class TopLevelCommand(Command): smart_recreate=smart_recreate, insecure_registry=insecure_registry, do_build=not options['--no-build'], + timeout=timeout ) to_attach = [c for s in project.get_services(service_names) for c in s.containers()] @@ -477,8 +479,7 @@ class TopLevelCommand(Command): signal.signal(signal.SIGINT, handler) print("Gracefully stopping... (press Ctrl+C again to force)") - timeout = options.get('--timeout') - params = {} if timeout is None else {'timeout': int(timeout)} + params = {} if timeout is None else {'timeout': timeout} project.stop(service_names=service_names, **params) def migrate_to_labels(self, project, _options): diff --git a/compose/project.py b/compose/project.py index bc093628c..ddf681d5d 100644 --- a/compose/project.py +++ b/compose/project.py @@ -211,7 +211,8 @@ class Project(object): allow_recreate=True, smart_recreate=False, insecure_registry=False, - do_build=True): + do_build=True, + timeout=None): services = self.get_services(service_names, include_deps=start_deps) @@ -228,6 +229,7 @@ class Project(object): plans[service.name], insecure_registry=insecure_registry, do_build=do_build, + timeout=timeout ) ] diff --git a/compose/service.py b/compose/service.py index 1e91a9f23..7a0264c2d 100644 --- a/compose/service.py +++ b/compose/service.py @@ -311,7 +311,8 @@ class Service(object): def execute_convergence_plan(self, plan, insecure_registry=False, - do_build=True): + do_build=True, + timeout=None): (action, containers) = plan if action == 'create': @@ -328,6 +329,7 @@ class Service(object): self.recreate_container( c, insecure_registry=insecure_registry, + timeout=timeout ) for c in containers ] @@ -349,7 +351,8 @@ class Service(object): def recreate_container(self, container, - insecure_registry=False): + insecure_registry=False, + timeout=None): """Recreate a container. The original container is renamed to a temporary name so that data @@ -358,7 +361,8 @@ class Service(object): """ log.info("Recreating %s..." % container.name) try: - container.stop() + stop_params = {} if timeout is None else {'timeout': timeout} + container.stop(**stop_params) except APIError as e: if (e.response.status_code == 500 and e.explanation diff --git a/tests/integration/cli_test.py b/tests/integration/cli_test.py index e9650668f..f9b251e19 100644 --- a/tests/integration/cli_test.py +++ b/tests/integration/cli_test.py @@ -162,6 +162,19 @@ class CLITestCase(DockerClientTestCase): self.assertEqual(old_ids, new_ids) + def test_up_with_timeout(self): + self.command.dispatch(['up', '-d', '-t', '1'], None) + service = self.project.get_service('simple') + another = self.project.get_service('another') + self.assertEqual(len(service.containers()), 1) + self.assertEqual(len(another.containers()), 1) + + # Ensure containers don't have stdin and stdout connected in -d mode + config = service.containers()[0].inspect()['Config'] + self.assertFalse(config['AttachStderr']) + self.assertFalse(config['AttachStdout']) + self.assertFalse(config['AttachStdin']) + @patch('dockerpty.start') def test_run_service_without_links(self, mock_stdout): self.command.base_dir = 'tests/fixtures/links-composefile' diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index fb3a7fcbb..595b9d373 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -254,6 +254,15 @@ class ServiceTest(unittest.TestCase): new_container.start.assert_called_once_with() mock_container.remove.assert_called_once_with() + @mock.patch('compose.service.Container', autospec=True) + def test_recreate_container_with_timeout(self, _): + mock_container = mock.create_autospec(Container) + self.mock_client.inspect_image.return_value = {'Id': 'abc123'} + service = Service('foo', client=self.mock_client, image='someimage') + service.recreate_container(mock_container, timeout=1) + + mock_container.stop.assert_called_once_with(timeout=1) + def test_parse_repository_tag(self): self.assertEqual(parse_repository_tag("root"), ("root", "")) self.assertEqual(parse_repository_tag("root:tag"), ("root", "tag"))