mirror of
https://github.com/docker/compose.git
synced 2025-07-01 19:04:34 +02:00
commit
01cd2688bb
@ -42,7 +42,7 @@ Change log
|
|||||||
- Values interpolated from the environment will now be converted to the
|
- Values interpolated from the environment will now be converted to the
|
||||||
proper type when used in non-string fields.
|
proper type when used in non-string fields.
|
||||||
|
|
||||||
- Added support for `--labels` in `docker-compose run`
|
- Added support for `--label` in `docker-compose run`
|
||||||
|
|
||||||
- Added support for `--timeout` in `docker-compose down`
|
- Added support for `--timeout` in `docker-compose down`
|
||||||
|
|
||||||
@ -71,6 +71,8 @@ Change log
|
|||||||
- Fixed a bug where missing secret files would generate an empty directory
|
- Fixed a bug where missing secret files would generate an empty directory
|
||||||
in their place
|
in their place
|
||||||
|
|
||||||
|
- Fixed character encoding issues in the CLI's error handlers
|
||||||
|
|
||||||
- Added validation for the `test` field in healthchecks
|
- Added validation for the `test` field in healthchecks
|
||||||
|
|
||||||
- Added validation for the `subnet` field in IPAM configurations
|
- Added validation for the `subnet` field in IPAM configurations
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
__version__ = '1.18.0-rc1'
|
__version__ = '1.18.0-rc2'
|
||||||
|
@ -7,7 +7,6 @@ import socket
|
|||||||
from distutils.spawn import find_executable
|
from distutils.spawn import find_executable
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import six
|
|
||||||
from docker.errors import APIError
|
from docker.errors import APIError
|
||||||
from requests.exceptions import ConnectionError as RequestsConnectionError
|
from requests.exceptions import ConnectionError as RequestsConnectionError
|
||||||
from requests.exceptions import ReadTimeout
|
from requests.exceptions import ReadTimeout
|
||||||
@ -15,6 +14,7 @@ from requests.exceptions import SSLError
|
|||||||
from requests.packages.urllib3.exceptions import ReadTimeoutError
|
from requests.packages.urllib3.exceptions import ReadTimeoutError
|
||||||
|
|
||||||
from ..const import API_VERSION_TO_ENGINE_VERSION
|
from ..const import API_VERSION_TO_ENGINE_VERSION
|
||||||
|
from .utils import binarystr_to_unicode
|
||||||
from .utils import is_docker_for_mac_installed
|
from .utils import is_docker_for_mac_installed
|
||||||
from .utils import is_mac
|
from .utils import is_mac
|
||||||
from .utils import is_ubuntu
|
from .utils import is_ubuntu
|
||||||
@ -75,7 +75,9 @@ def log_windows_pipe_error(exc):
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log.error(
|
log.error(
|
||||||
"Windows named pipe error: {} (code: {})".format(exc.strerror, exc.winerror)
|
"Windows named pipe error: {} (code: {})".format(
|
||||||
|
binarystr_to_unicode(exc.strerror), exc.winerror
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -89,9 +91,7 @@ def log_timeout_error(timeout):
|
|||||||
|
|
||||||
|
|
||||||
def log_api_error(e, client_version):
|
def log_api_error(e, client_version):
|
||||||
explanation = e.explanation
|
explanation = binarystr_to_unicode(e.explanation)
|
||||||
if isinstance(explanation, six.binary_type):
|
|
||||||
explanation = explanation.decode('utf-8')
|
|
||||||
|
|
||||||
if 'client is newer than server' not in explanation:
|
if 'client is newer than server' not in explanation:
|
||||||
log.error(explanation)
|
log.error(explanation)
|
||||||
@ -106,7 +106,8 @@ def log_api_error(e, client_version):
|
|||||||
log.error(
|
log.error(
|
||||||
"The Docker Engine version is less than the minimum required by "
|
"The Docker Engine version is less than the minimum required by "
|
||||||
"Compose. Your current project requires a Docker Engine of "
|
"Compose. Your current project requires a Docker Engine of "
|
||||||
"version {version} or greater.".format(version=version))
|
"version {version} or greater.".format(version=version)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def exit_with_error(msg):
|
def exit_with_error(msg):
|
||||||
@ -115,12 +116,17 @@ def exit_with_error(msg):
|
|||||||
|
|
||||||
|
|
||||||
def get_conn_error_message(url):
|
def get_conn_error_message(url):
|
||||||
|
try:
|
||||||
if find_executable('docker') is None:
|
if find_executable('docker') is None:
|
||||||
return docker_not_found_msg("Couldn't connect to Docker daemon.")
|
return docker_not_found_msg("Couldn't connect to Docker daemon.")
|
||||||
if is_docker_for_mac_installed():
|
if is_docker_for_mac_installed():
|
||||||
return conn_error_docker_for_mac
|
return conn_error_docker_for_mac
|
||||||
if find_executable('docker-machine') is not None:
|
if find_executable('docker-machine') is not None:
|
||||||
return conn_error_docker_machine
|
return conn_error_docker_machine
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# https://github.com/docker/compose/issues/5442
|
||||||
|
# Ignore the error and print the generic message instead.
|
||||||
|
pass
|
||||||
return conn_error_generic.format(url=url)
|
return conn_error_generic.format(url=url)
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import subprocess
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
import docker
|
import docker
|
||||||
|
import six
|
||||||
|
|
||||||
import compose
|
import compose
|
||||||
from ..const import IS_WINDOWS_PLATFORM
|
from ..const import IS_WINDOWS_PLATFORM
|
||||||
@ -148,3 +149,15 @@ def human_readable_file_size(size):
|
|||||||
size / float(1 << (order * 10)),
|
size / float(1 << (order * 10)),
|
||||||
suffixes[order]
|
suffixes[order]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def binarystr_to_unicode(s):
|
||||||
|
if not isinstance(s, six.binary_type):
|
||||||
|
return s
|
||||||
|
|
||||||
|
if IS_WINDOWS_PLATFORM:
|
||||||
|
try:
|
||||||
|
return s.decode('windows-1250')
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
pass
|
||||||
|
return s.decode('utf-8', 'replace')
|
||||||
|
@ -1153,7 +1153,7 @@ def resolve_volume_paths(working_dir, service_dict):
|
|||||||
|
|
||||||
def resolve_volume_path(working_dir, volume):
|
def resolve_volume_path(working_dir, volume):
|
||||||
if isinstance(volume, dict):
|
if isinstance(volume, dict):
|
||||||
if volume.get('source', '').startswith('.') and volume['type'] == 'mount':
|
if volume.get('source', '').startswith('.') and volume['type'] == 'bind':
|
||||||
volume['source'] = expand_path(working_dir, volume['source'])
|
volume['source'] = expand_path(working_dir, volume['source'])
|
||||||
return volume
|
return volume
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
VERSION="1.18.0-rc1"
|
VERSION="1.18.0-rc2"
|
||||||
IMAGE="docker/compose:$VERSION"
|
IMAGE="docker/compose:$VERSION"
|
||||||
|
|
||||||
|
|
||||||
|
@ -86,3 +86,13 @@ class TestHandleConnectionErrors(object):
|
|||||||
|
|
||||||
_, args, _ = mock_logging.error.mock_calls[0]
|
_, args, _ = mock_logging.error.mock_calls[0]
|
||||||
assert "Windows named pipe error: The pipe is busy. (code: 231)" == args[0]
|
assert "Windows named pipe error: The pipe is busy. (code: 231)" == args[0]
|
||||||
|
|
||||||
|
@pytest.mark.skipif(not IS_WINDOWS_PLATFORM, reason='Needs pywin32')
|
||||||
|
def test_windows_pipe_error_encoding_issue(self, mock_logging):
|
||||||
|
import pywintypes
|
||||||
|
with pytest.raises(errors.ConnectionError):
|
||||||
|
with handle_connection_errors(mock.Mock(api_version='1.22')):
|
||||||
|
raise pywintypes.error(9999, 'WriteFile', 'I use weird characters \xe9')
|
||||||
|
|
||||||
|
_, args, _ = mock_logging.error.mock_calls[0]
|
||||||
|
assert 'Windows named pipe error: I use weird characters \xe9 (code: 9999)' == args[0]
|
||||||
|
@ -1304,6 +1304,29 @@ class ConfigTest(unittest.TestCase):
|
|||||||
assert npipe_mount.target == '/named_pipe'
|
assert npipe_mount.target == '/named_pipe'
|
||||||
assert not npipe_mount.is_named_volume
|
assert not npipe_mount.is_named_volume
|
||||||
|
|
||||||
|
def test_load_bind_mount_relative_path(self):
|
||||||
|
expected_source = 'C:\\tmp\\web' if IS_WINDOWS_PLATFORM else '/tmp/web'
|
||||||
|
base_file = config.ConfigFile(
|
||||||
|
'base.yaml', {
|
||||||
|
'version': '3.4',
|
||||||
|
'services': {
|
||||||
|
'web': {
|
||||||
|
'image': 'busybox:latest',
|
||||||
|
'volumes': [
|
||||||
|
{'type': 'bind', 'source': './web', 'target': '/web'},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
details = config.ConfigDetails('/tmp', [base_file])
|
||||||
|
config_data = config.load(details)
|
||||||
|
mount = config_data.services[0].get('volumes')[0]
|
||||||
|
assert mount.target == '/web'
|
||||||
|
assert mount.type == 'bind'
|
||||||
|
assert mount.source == expected_source
|
||||||
|
|
||||||
def test_config_valid_service_names(self):
|
def test_config_valid_service_names(self):
|
||||||
for valid_name in ['_', '-', '.__.', '_what-up.', 'what_.up----', 'whatup']:
|
for valid_name in ['_', '-', '.__.', '_what-up.', 'what_.up----', 'whatup']:
|
||||||
services = config.load(
|
services = config.load(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user