mirror of https://github.com/docker/compose.git
commit
5a0ef19ee0
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -1,6 +1,29 @@
|
|||
Change log
|
||||
==========
|
||||
|
||||
1.10.1 (2017-02-01)
|
||||
------------------
|
||||
|
||||
### Bugfixes
|
||||
|
||||
- Fixed an issue where presence of older versions of the docker-py
|
||||
package would cause unexpected crashes while running Compose
|
||||
|
||||
- Fixed an issue where healthcheck dependencies would be lost when
|
||||
using multiple compose files for a project
|
||||
|
||||
- Fixed a few issues that made the output of the `config` command
|
||||
invalid
|
||||
|
||||
- Fixed an issue where adding volume labels to v3 Compose files would
|
||||
result in an error
|
||||
|
||||
- Fixed an issue on Windows where build context paths containing unicode
|
||||
characters were being improperly encoded
|
||||
|
||||
- Fixed a bug where Compose would occasionally crash while streaming logs
|
||||
when containers would stop or restart
|
||||
|
||||
1.10.0 (2017-01-18)
|
||||
-------------------
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__version__ = '1.10.0'
|
||||
__version__ = '1.10.1'
|
||||
|
|
|
@ -14,6 +14,30 @@ from distutils.spawn import find_executable
|
|||
from inspect import getdoc
|
||||
from operator import attrgetter
|
||||
|
||||
|
||||
# Attempt to detect https://github.com/docker/compose/issues/4344
|
||||
try:
|
||||
# A regular import statement causes PyInstaller to freak out while
|
||||
# trying to load pip. This way it is simply ignored.
|
||||
pip = __import__('pip')
|
||||
pip_packages = pip.get_installed_distributions()
|
||||
if 'docker-py' in [pkg.project_name for pkg in pip_packages]:
|
||||
from .colors import red
|
||||
print(
|
||||
red('ERROR:'),
|
||||
"Dependency conflict: an older version of the 'docker-py' package "
|
||||
"is polluting the namespace. "
|
||||
"Run the following command to remedy the issue:\n"
|
||||
"pip uninstall docker docker-py; pip install docker",
|
||||
file=sys.stderr
|
||||
)
|
||||
sys.exit(1)
|
||||
except ImportError:
|
||||
# pip is not available, which indicates it's probably the binary
|
||||
# distribution of Compose which is not affected
|
||||
pass
|
||||
|
||||
|
||||
from . import errors
|
||||
from . import signals
|
||||
from .. import __version__
|
||||
|
|
|
@ -818,6 +818,7 @@ def merge_service_dicts(base, override, version):
|
|||
md.merge_mapping('ulimits', parse_ulimits)
|
||||
md.merge_mapping('networks', parse_networks)
|
||||
md.merge_mapping('sysctls', parse_sysctls)
|
||||
md.merge_mapping('depends_on', parse_depends_on)
|
||||
md.merge_sequence('links', ServiceLink.parse)
|
||||
|
||||
for field in ['volumes', 'devices']:
|
||||
|
@ -825,7 +826,7 @@ def merge_service_dicts(base, override, version):
|
|||
|
||||
for field in [
|
||||
'ports', 'cap_add', 'cap_drop', 'expose', 'external_links',
|
||||
'security_opt', 'volumes_from', 'depends_on',
|
||||
'security_opt', 'volumes_from',
|
||||
]:
|
||||
md.merge_field(field, merge_unique_items_lists, default=[])
|
||||
|
||||
|
@ -920,6 +921,9 @@ parse_environment = functools.partial(parse_dict_or_list, split_env, 'environmen
|
|||
parse_labels = functools.partial(parse_dict_or_list, split_kv, 'labels')
|
||||
parse_networks = functools.partial(parse_dict_or_list, lambda k: (k, None), 'networks')
|
||||
parse_sysctls = functools.partial(parse_dict_or_list, split_kv, 'sysctls')
|
||||
parse_depends_on = functools.partial(
|
||||
parse_dict_or_list, lambda k: (k, {'condition': 'service_started'}), 'depends_on'
|
||||
)
|
||||
|
||||
|
||||
def parse_ulimits(ulimits):
|
||||
|
|
|
@ -308,6 +308,7 @@
|
|||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"internal": {"type": "boolean"},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
|
@ -328,10 +329,11 @@
|
|||
"type": ["boolean", "object"],
|
||||
"properties": {
|
||||
"name": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"}
|
||||
},
|
||||
"labels": {"$ref": "#/definitions/list_or_dict"},
|
||||
"additionalProperties": false
|
||||
},
|
||||
|
||||
|
|
|
@ -52,6 +52,25 @@ def serialize_config(config):
|
|||
width=80)
|
||||
|
||||
|
||||
def serialize_ns_time_value(value):
|
||||
result = (value, 'ns')
|
||||
table = [
|
||||
(1000., 'us'),
|
||||
(1000., 'ms'),
|
||||
(1000., 's'),
|
||||
(60., 'm'),
|
||||
(60., 'h')
|
||||
]
|
||||
for stage in table:
|
||||
tmp = value / stage[0]
|
||||
if tmp == int(value / stage[0]):
|
||||
value = tmp
|
||||
result = (int(value), stage[1])
|
||||
else:
|
||||
break
|
||||
return '{0}{1}'.format(*result)
|
||||
|
||||
|
||||
def denormalize_service_dict(service_dict, version):
|
||||
service_dict = service_dict.copy()
|
||||
|
||||
|
@ -68,4 +87,14 @@ def denormalize_service_dict(service_dict, version):
|
|||
svc for svc in service_dict['depends_on'].keys()
|
||||
])
|
||||
|
||||
if 'healthcheck' in service_dict:
|
||||
if 'interval' in service_dict['healthcheck']:
|
||||
service_dict['healthcheck']['interval'] = serialize_ns_time_value(
|
||||
service_dict['healthcheck']['interval']
|
||||
)
|
||||
if 'timeout' in service_dict['healthcheck']:
|
||||
service_dict['healthcheck']['timeout'] = serialize_ns_time_value(
|
||||
service_dict['healthcheck']['timeout']
|
||||
)
|
||||
|
||||
return service_dict
|
||||
|
|
|
@ -22,6 +22,7 @@ from .config import DOCKER_CONFIG_KEYS
|
|||
from .config import merge_environment
|
||||
from .config.types import VolumeSpec
|
||||
from .const import DEFAULT_TIMEOUT
|
||||
from .const import IS_WINDOWS_PLATFORM
|
||||
from .const import LABEL_CONFIG_HASH
|
||||
from .const import LABEL_CONTAINER_NUMBER
|
||||
from .const import LABEL_ONE_OFF
|
||||
|
@ -769,9 +770,9 @@ class Service(object):
|
|||
|
||||
build_opts = self.options.get('build', {})
|
||||
path = build_opts.get('context')
|
||||
# python2 os.path() doesn't support unicode, so we need to encode it to
|
||||
# a byte string
|
||||
if not six.PY3:
|
||||
# python2 os.stat() doesn't support unicode on some UNIX, so we
|
||||
# encode it to a bytestring to be safe
|
||||
if not six.PY3 and not IS_WINDOWS_PLATFORM:
|
||||
path = path.encode('utf8')
|
||||
|
||||
build_output = self.client.build(
|
||||
|
|
|
@ -2,7 +2,7 @@ PyYAML==3.11
|
|||
backports.ssl-match-hostname==3.5.0.1; python_version < '3'
|
||||
cached-property==1.2.0
|
||||
colorama==0.3.7
|
||||
docker==2.0.1
|
||||
docker==2.0.2
|
||||
dockerpty==0.4.1
|
||||
docopt==0.6.1
|
||||
enum34==1.0.4; python_version < '3.4'
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
set -e
|
||||
|
||||
VERSION="1.10.0"
|
||||
VERSION="1.10.1"
|
||||
IMAGE="docker/compose:$VERSION"
|
||||
|
||||
|
||||
|
|
2
setup.py
2
setup.py
|
@ -37,7 +37,7 @@ install_requires = [
|
|||
'requests >= 2.6.1, != 2.11.0, < 2.12',
|
||||
'texttable >= 0.8.1, < 0.9',
|
||||
'websocket-client >= 0.32.0, < 1.0',
|
||||
'docker >= 2.0.1, < 3.0',
|
||||
'docker >= 2.0.2, < 3.0',
|
||||
'dockerpty >= 0.4.1, < 0.5',
|
||||
'six >= 1.3.0, < 2',
|
||||
'jsonschema >= 2.5.1, < 3',
|
||||
|
|
|
@ -295,7 +295,13 @@ class CLITestCase(DockerClientTestCase):
|
|||
assert yaml.load(result.stdout) == {
|
||||
'version': '3.0',
|
||||
'networks': {},
|
||||
'volumes': {},
|
||||
'volumes': {
|
||||
'foobar': {
|
||||
'labels': {
|
||||
'com.docker.compose.test': 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
'services': {
|
||||
'web': {
|
||||
'image': 'busybox',
|
||||
|
@ -333,8 +339,8 @@ class CLITestCase(DockerClientTestCase):
|
|||
|
||||
'healthcheck': {
|
||||
'test': 'cat /etc/passwd',
|
||||
'interval': 10000000000,
|
||||
'timeout': 1000000000,
|
||||
'interval': '10s',
|
||||
'timeout': '1s',
|
||||
'retries': 5,
|
||||
},
|
||||
|
||||
|
|
|
@ -35,3 +35,7 @@ services:
|
|||
retries: 5
|
||||
|
||||
stop_grace_period: 20s
|
||||
volumes:
|
||||
foobar:
|
||||
labels:
|
||||
com.docker.compose.test: 'true'
|
||||
|
|
|
@ -13,6 +13,7 @@ from compose.config.config import resolve_environment
|
|||
from compose.config.config import V1
|
||||
from compose.config.config import V2_0
|
||||
from compose.config.config import V2_1
|
||||
from compose.config.config import V3_0
|
||||
from compose.config.environment import Environment
|
||||
from compose.const import API_VERSIONS
|
||||
from compose.const import LABEL_PROJECT
|
||||
|
@ -36,13 +37,15 @@ def get_links(container):
|
|||
|
||||
def engine_max_version():
|
||||
if 'DOCKER_VERSION' not in os.environ:
|
||||
return V2_1
|
||||
return V3_0
|
||||
version = os.environ['DOCKER_VERSION'].partition('-')[0]
|
||||
if version_lt(version, '1.10'):
|
||||
return V1
|
||||
elif version_lt(version, '1.12'):
|
||||
return V2_0
|
||||
return V2_1
|
||||
elif version_lt(version, '1.13'):
|
||||
return V2_1
|
||||
return V3_0
|
||||
|
||||
|
||||
def build_version_required_decorator(ignored_versions):
|
||||
|
|
|
@ -23,6 +23,7 @@ from compose.config.environment import Environment
|
|||
from compose.config.errors import ConfigurationError
|
||||
from compose.config.errors import VERSION_EXPLANATION
|
||||
from compose.config.serialize import denormalize_service_dict
|
||||
from compose.config.serialize import serialize_ns_time_value
|
||||
from compose.config.types import VolumeSpec
|
||||
from compose.const import IS_WINDOWS_PLATFORM
|
||||
from compose.utils import nanoseconds_from_time_seconds
|
||||
|
@ -1713,6 +1714,40 @@ class ConfigTest(unittest.TestCase):
|
|||
}
|
||||
}
|
||||
|
||||
def test_merge_depends_on_no_override(self):
|
||||
base = {
|
||||
'image': 'busybox',
|
||||
'depends_on': {
|
||||
'app1': {'condition': 'service_started'},
|
||||
'app2': {'condition': 'service_healthy'}
|
||||
}
|
||||
}
|
||||
override = {}
|
||||
actual = config.merge_service_dicts(base, override, V2_1)
|
||||
assert actual == base
|
||||
|
||||
def test_merge_depends_on_mixed_syntax(self):
|
||||
base = {
|
||||
'image': 'busybox',
|
||||
'depends_on': {
|
||||
'app1': {'condition': 'service_started'},
|
||||
'app2': {'condition': 'service_healthy'}
|
||||
}
|
||||
}
|
||||
override = {
|
||||
'depends_on': ['app3']
|
||||
}
|
||||
|
||||
actual = config.merge_service_dicts(base, override, V2_1)
|
||||
assert actual == {
|
||||
'image': 'busybox',
|
||||
'depends_on': {
|
||||
'app1': {'condition': 'service_started'},
|
||||
'app2': {'condition': 'service_healthy'},
|
||||
'app3': {'condition': 'service_started'}
|
||||
}
|
||||
}
|
||||
|
||||
def test_external_volume_config(self):
|
||||
config_details = build_config_details({
|
||||
'version': '2',
|
||||
|
@ -3300,3 +3335,38 @@ class SerializeTest(unittest.TestCase):
|
|||
}
|
||||
|
||||
assert denormalize_service_dict(service_dict, V2_1) == service_dict
|
||||
|
||||
def test_serialize_time(self):
|
||||
data = {
|
||||
9: '9ns',
|
||||
9000: '9us',
|
||||
9000000: '9ms',
|
||||
90000000: '90ms',
|
||||
900000000: '900ms',
|
||||
999999999: '999999999ns',
|
||||
1000000000: '1s',
|
||||
60000000000: '1m',
|
||||
60000000001: '60000000001ns',
|
||||
9000000000000: '150m',
|
||||
90000000000000: '25h',
|
||||
}
|
||||
|
||||
for k, v in data.items():
|
||||
assert serialize_ns_time_value(k) == v
|
||||
|
||||
def test_denormalize_healthcheck(self):
|
||||
service_dict = {
|
||||
'image': 'test',
|
||||
'healthcheck': {
|
||||
'test': 'exit 1',
|
||||
'interval': '1m40s',
|
||||
'timeout': '30s',
|
||||
'retries': 5
|
||||
}
|
||||
}
|
||||
processed_service = config.process_service(config.ServiceConfig(
|
||||
'.', 'test', 'test', service_dict
|
||||
))
|
||||
denormalized_service = denormalize_service_dict(processed_service, V2_1)
|
||||
assert denormalized_service['healthcheck']['interval'] == '100s'
|
||||
assert denormalized_service['healthcheck']['timeout'] == '30s'
|
||||
|
|
Loading…
Reference in New Issue