diff --git a/compose/cli/docker_client.py b/compose/cli/docker_client.py index ad67d563..601b0b9a 100644 --- a/compose/cli/docker_client.py +++ b/compose/cli/docker_client.py @@ -1,9 +1,14 @@ +import logging import os import ssl from docker import Client from docker import tls +from ..const import HTTP_TIMEOUT + +log = logging.getLogger(__name__) + def docker_client(): """ @@ -34,5 +39,7 @@ def docker_client(): ca_cert=ca_cert, ) - timeout = int(os.environ.get('DOCKER_CLIENT_TIMEOUT', 60)) - return Client(base_url=base_url, tls=tls_config, version=api_version, timeout=timeout) + if 'DOCKER_CLIENT_TIMEOUT' in os.environ: + log.warn('The DOCKER_CLIENT_TIMEOUT environment variable is deprecated. Please use COMPOSE_HTTP_TIMEOUT instead.') + + return Client(base_url=base_url, tls=tls_config, version=api_version, timeout=HTTP_TIMEOUT) diff --git a/compose/cli/main.py b/compose/cli/main.py index cf971844..175057fb 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -10,11 +10,13 @@ from operator import attrgetter import dockerpty from docker.errors import APIError +from requests.exceptions import ReadTimeout from .. import __version__ from .. import legacy from ..config import parse_environment from ..const import DEFAULT_TIMEOUT +from ..const import HTTP_TIMEOUT from ..progress_stream import StreamOutputError from ..project import ConfigurationError from ..project import NoSuchService @@ -66,6 +68,12 @@ def main(): except NeedsBuildError as e: log.error("Service '%s' needs to be built, but --no-build was passed." % e.service.name) sys.exit(1) + except ReadTimeout as e: + log.error( + "An HTTP request took too long to complete. Retry with --verbose to obtain debug information.\n" + "If you encounter this issue regularly because of slow network conditions, consider setting " + "COMPOSE_HTTP_TIMEOUT to a higher value (current value: %s)." % HTTP_TIMEOUT + ) def setup_logging(): diff --git a/compose/const.py b/compose/const.py index 709c3a10..dbfa56b8 100644 --- a/compose/const.py +++ b/compose/const.py @@ -1,3 +1,4 @@ +import os DEFAULT_TIMEOUT = 10 LABEL_CONTAINER_NUMBER = 'com.docker.compose.container-number' @@ -6,3 +7,4 @@ LABEL_PROJECT = 'com.docker.compose.project' LABEL_SERVICE = 'com.docker.compose.service' LABEL_VERSION = 'com.docker.compose.version' LABEL_CONFIG_HASH = 'com.docker.compose.config-hash' +HTTP_TIMEOUT = int(os.environ.get('COMPOSE_HTTP_TIMEOUT', os.environ.get('DOCKER_CLIENT_TIMEOUT', 60))) diff --git a/docs/reference/overview.md b/docs/reference/overview.md index 7425aa5e..52598737 100644 --- a/docs/reference/overview.md +++ b/docs/reference/overview.md @@ -44,6 +44,11 @@ the `docker` daemon. Configures the path to the `ca.pem`, `cert.pem`, and `key.pem` files used for TLS verification. Defaults to `~/.docker`. +### COMPOSE\_HTTP\_TIMEOUT + +Configures the time (in seconds) a request to the Docker daemon is allowed to hang before Compose considers +it failed. Defaults to 60 seconds. + diff --git a/tests/unit/cli/docker_client_test.py b/tests/unit/cli/docker_client_test.py index 5ccde73a..d497495b 100644 --- a/tests/unit/cli/docker_client_test.py +++ b/tests/unit/cli/docker_client_test.py @@ -16,7 +16,7 @@ class DockerClientTestCase(unittest.TestCase): docker_client.docker_client() def test_docker_client_with_custom_timeout(self): - with mock.patch.dict(os.environ): - os.environ['DOCKER_CLIENT_TIMEOUT'] = timeout = "300" + timeout = 300 + with mock.patch('compose.cli.docker_client.HTTP_TIMEOUT', 300): client = docker_client.docker_client() - self.assertEqual(client.timeout, int(timeout)) + self.assertEqual(client.timeout, int(timeout))