errors.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. import contextlib
  2. import logging
  3. import socket
  4. from distutils.spawn import find_executable
  5. from textwrap import dedent
  6. from docker.errors import APIError
  7. from requests.exceptions import ConnectionError as RequestsConnectionError
  8. from requests.exceptions import ReadTimeout
  9. from requests.exceptions import SSLError
  10. from requests.packages.urllib3.exceptions import ReadTimeoutError
  11. from ..const import API_VERSION_TO_ENGINE_VERSION
  12. from .utils import binarystr_to_unicode
  13. from .utils import is_docker_for_mac_installed
  14. from .utils import is_mac
  15. from .utils import is_ubuntu
  16. from .utils import is_windows
  17. log = logging.getLogger(__name__)
  18. class UserError(Exception):
  19. def __init__(self, msg):
  20. self.msg = dedent(msg).strip()
  21. def __str__(self):
  22. return self.msg
  23. class ConnectionError(Exception):
  24. pass
  25. @contextlib.contextmanager
  26. def handle_connection_errors(client):
  27. try:
  28. yield
  29. except SSLError as e:
  30. log.error('SSL error: %s' % e)
  31. raise ConnectionError()
  32. except RequestsConnectionError as e:
  33. if e.args and isinstance(e.args[0], ReadTimeoutError):
  34. log_timeout_error(client.timeout)
  35. raise ConnectionError()
  36. exit_with_error(get_conn_error_message(client.base_url))
  37. except APIError as e:
  38. log_api_error(e, client.api_version)
  39. raise ConnectionError()
  40. except (ReadTimeout, socket.timeout):
  41. log_timeout_error(client.timeout)
  42. raise ConnectionError()
  43. except Exception as e:
  44. if is_windows():
  45. import pywintypes
  46. if isinstance(e, pywintypes.error):
  47. log_windows_pipe_error(e)
  48. raise ConnectionError()
  49. raise
  50. def log_windows_pipe_error(exc):
  51. if exc.winerror == 2:
  52. log.error("Couldn't connect to Docker daemon. You might need to start Docker for Windows.")
  53. elif exc.winerror == 232: # https://github.com/docker/compose/issues/5005
  54. log.error(
  55. "The current Compose file version is not compatible with your engine version. "
  56. "Please upgrade your Compose file to a more recent version, or set "
  57. "a COMPOSE_API_VERSION in your environment."
  58. )
  59. else:
  60. log.error(
  61. "Windows named pipe error: {} (code: {})".format(
  62. binarystr_to_unicode(exc.strerror), exc.winerror
  63. )
  64. )
  65. def log_timeout_error(timeout):
  66. log.error(
  67. "An HTTP request took too long to complete. Retry with --verbose to "
  68. "obtain debug information.\n"
  69. "If you encounter this issue regularly because of slow network "
  70. "conditions, consider setting COMPOSE_HTTP_TIMEOUT to a higher "
  71. "value (current value: %s)." % timeout)
  72. def log_api_error(e, client_version):
  73. explanation = binarystr_to_unicode(e.explanation)
  74. if 'client is newer than server' not in explanation:
  75. log.error(explanation)
  76. return
  77. version = API_VERSION_TO_ENGINE_VERSION.get(client_version)
  78. if not version:
  79. # They've set a custom API version
  80. log.error(explanation)
  81. return
  82. log.error(
  83. "The Docker Engine version is less than the minimum required by "
  84. "Compose. Your current project requires a Docker Engine of "
  85. "version {version} or greater.".format(version=version)
  86. )
  87. def exit_with_error(msg):
  88. log.error(dedent(msg).strip())
  89. raise ConnectionError()
  90. def get_conn_error_message(url):
  91. try:
  92. if find_executable('docker') is None:
  93. return docker_not_found_msg("Couldn't connect to Docker daemon.")
  94. if is_docker_for_mac_installed():
  95. return conn_error_docker_for_mac
  96. if find_executable('docker-machine') is not None:
  97. return conn_error_docker_machine
  98. except UnicodeDecodeError:
  99. # https://github.com/docker/compose/issues/5442
  100. # Ignore the error and print the generic message instead.
  101. pass
  102. return conn_error_generic.format(url=url)
  103. def docker_not_found_msg(problem):
  104. return "{} You might need to install Docker:\n\n{}".format(
  105. problem, docker_install_url())
  106. def docker_install_url():
  107. if is_mac():
  108. return docker_install_url_mac
  109. elif is_ubuntu():
  110. return docker_install_url_ubuntu
  111. elif is_windows():
  112. return docker_install_url_windows
  113. else:
  114. return docker_install_url_generic
  115. docker_install_url_mac = "https://docs.docker.com/engine/installation/mac/"
  116. docker_install_url_ubuntu = "https://docs.docker.com/engine/installation/ubuntulinux/"
  117. docker_install_url_windows = "https://docs.docker.com/engine/installation/windows/"
  118. docker_install_url_generic = "https://docs.docker.com/engine/installation/"
  119. conn_error_docker_machine = """
  120. Couldn't connect to Docker daemon - you might need to run `docker-machine start default`.
  121. """
  122. conn_error_docker_for_mac = """
  123. Couldn't connect to Docker daemon. You might need to start Docker for Mac.
  124. """
  125. conn_error_generic = """
  126. Couldn't connect to Docker daemon at {url} - is it running?
  127. If it's at a non-standard location, specify the URL with the DOCKER_HOST environment variable.
  128. """