Skip to content

Commit

Permalink
Merge dfed245 into fa90e4e
Browse files Browse the repository at this point in the history
  • Loading branch information
shin- committed Feb 17, 2017
2 parents fa90e4e + dfed245 commit f963d76
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 34 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,27 @@
Change log
==========

1.11.2 (2017-02-17)
-------------------

### Bugfixes

- Fixed a bug that was preventing secrets configuration from being
loaded properly

- Fixed a bug where the `docker-compose config` command would fail
if the config file contained secrets definitions

- Fixed an issue where Compose on some linux distributions would
pick up and load an outdated version of the requests library

- Fixed an issue where socket-type files inside a build folder
would cause `docker-compose` to crash when trying to build that
service

- Fixed an issue where recursive wildcard patterns `**` were not being
recognized in `.dockerignore` files.

1.11.1 (2017-02-09)
-------------------

Expand Down
2 changes: 1 addition & 1 deletion compose/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals

__version__ = '1.11.1'
__version__ = '1.11.2'
37 changes: 37 additions & 0 deletions compose/cli/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import subprocess
import sys

# Attempt to detect https://github.com/docker/compose/issues/4344
try:
# We don't try importing pip because it messes with package imports
# on some Linux distros (Ubuntu, Fedora)
# https://github.com/docker/compose/issues/4425
# https://github.com/docker/compose/issues/4481
# https://github.com/pypa/pip/blob/master/pip/_vendor/__init__.py
s_cmd = subprocess.Popen(
['pip', 'freeze'], stderr=subprocess.PIPE, stdout=subprocess.PIPE
)
packages = s_cmd.communicate()[0].splitlines()
dockerpy_installed = len(
list(filter(lambda p: p.startswith(b'docker-py=='), packages))
) > 0
if dockerpy_installed:
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 OSError:
# pip command is not available, which indicates it's probably the binary
# distribution of Compose which is not affected
pass
24 changes: 0 additions & 24 deletions compose/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,30 +14,6 @@
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__
Expand Down
5 changes: 5 additions & 0 deletions compose/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,11 @@ def finalize_service(service_config, service_names, version, environment):
if 'restart' in service_dict:
service_dict['restart'] = parse_restart_spec(service_dict['restart'])

if 'secrets' in service_dict:
service_dict['secrets'] = [
types.ServiceSecret.parse(s) for s in service_dict['secrets']
]

normalize_build(service_dict, service_config.working_dir, environment)

service_dict['name'] = service_config.name
Expand Down
3 changes: 3 additions & 0 deletions compose/config/serialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,4 +102,7 @@ def denormalize_service_dict(service_dict, version):
service_dict['healthcheck']['timeout']
)

if 'secrets' in service_dict:
service_dict['secrets'] = map(lambda s: s.repr(), service_dict['secrets'])

return service_dict
5 changes: 5 additions & 0 deletions compose/config/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,8 @@ def parse(cls, spec):
@property
def merge_field(self):
return self.source

def repr(self):
return dict(
[(k, v) for k, v in self._asdict().items() if v is not None]
)
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.2
docker==2.1.0
dockerpty==0.4.1
docopt==0.6.1
enum34==1.0.4; python_version < '3.4'
Expand Down
2 changes: 1 addition & 1 deletion script/run/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

set -e

VERSION="1.11.1"
VERSION="1.11.2"
IMAGE="docker/compose:$VERSION"


Expand Down
12 changes: 5 additions & 7 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals

import codecs
import logging
import os
import re
import sys
Expand Down Expand Up @@ -37,7 +37,7 @@ def find_version(*file_paths):
'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.2, < 3.0',
'docker >= 2.1.0, < 3.0',
'dockerpty >= 0.4.1, < 0.5',
'six >= 1.3.0, < 2',
'jsonschema >= 2.5.1, < 3',
Expand All @@ -64,11 +64,9 @@ def find_version(*file_paths):
for key, value in extras_require.items():
if key.startswith(':') and pkg_resources.evaluate_marker(key[1:]):
install_requires.extend(value)
except Exception:
logging.getLogger(__name__).exception(
'Failed to compute platform dependencies. All dependencies will be '
'installed as a result.'
)
except Exception as e:
print("Failed to compute platform dependencies: {}. ".format(e) +
"All dependencies will be installed as a result.", file=sys.stderr)
for key, value in extras_require.items():
if key.startswith(':'):
install_requires.extend(value)
Expand Down
143 changes: 143 additions & 0 deletions tests/unit/config/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

from ...helpers import build_config_details
from compose.config import config
from compose.config import types
from compose.config.config import resolve_build_args
from compose.config.config import resolve_environment
from compose.config.config import V1
Expand Down Expand Up @@ -53,6 +54,10 @@ def service_sort(services):
return sorted(services, key=itemgetter('name'))


def secret_sort(secrets):
return sorted(secrets, key=itemgetter('source'))


class ConfigTest(unittest.TestCase):
def test_load(self):
service_dicts = config.load(
Expand Down Expand Up @@ -1770,6 +1775,38 @@ def test_merge_pid(self):
'labels': {'com.docker.compose.test': 'yes'}
}

def test_merge_different_secrets(self):
base = {
'image': 'busybox',
'secrets': [
{'source': 'src.txt'}
]
}
override = {'secrets': ['other-src.txt']}

actual = config.merge_service_dicts(base, override, V3_1)
assert secret_sort(actual['secrets']) == secret_sort([
{'source': 'src.txt'},
{'source': 'other-src.txt'}
])

def test_merge_secrets_override(self):
base = {
'image': 'busybox',
'secrets': ['src.txt'],
}
override = {
'secrets': [
{
'source': 'src.txt',
'target': 'data.txt',
'mode': 0o400
}
]
}
actual = config.merge_service_dicts(base, override, V3_1)
assert actual['secrets'] == override['secrets']

def test_external_volume_config(self):
config_details = build_config_details({
'version': '2',
Expand Down Expand Up @@ -1849,6 +1886,91 @@ def test_load_dockerfile_without_context(self):
config.load(config_details)
assert 'has neither an image nor a build context' in exc.exconly()

def test_load_secrets(self):
base_file = config.ConfigFile(
'base.yaml',
{
'version': '3.1',
'services': {
'web': {
'image': 'example/web',
'secrets': [
'one',
{
'source': 'source',
'target': 'target',
'uid': '100',
'gid': '200',
'mode': 0o777,
},
],
},
},
'secrets': {
'one': {'file': 'secret.txt'},
},
})
details = config.ConfigDetails('.', [base_file])
service_dicts = config.load(details).services
expected = [
{
'name': 'web',
'image': 'example/web',
'secrets': [
types.ServiceSecret('one', None, None, None, None),
types.ServiceSecret('source', 'target', '100', '200', 0o777),
],
},
]
assert service_sort(service_dicts) == service_sort(expected)

def test_load_secrets_multi_file(self):
base_file = config.ConfigFile(
'base.yaml',
{
'version': '3.1',
'services': {
'web': {
'image': 'example/web',
'secrets': ['one'],
},
},
'secrets': {
'one': {'file': 'secret.txt'},
},
})
override_file = config.ConfigFile(
'base.yaml',
{
'version': '3.1',
'services': {
'web': {
'secrets': [
{
'source': 'source',
'target': 'target',
'uid': '100',
'gid': '200',
'mode': 0o777,
},
],
},
},
})
details = config.ConfigDetails('.', [base_file, override_file])
service_dicts = config.load(details).services
expected = [
{
'name': 'web',
'image': 'example/web',
'secrets': [
types.ServiceSecret('one', None, None, None, None),
types.ServiceSecret('source', 'target', '100', '200', 0o777),
],
},
]
assert service_sort(service_dicts) == service_sort(expected)


class NetworkModeTest(unittest.TestCase):
def test_network_mode_standard(self):
Expand Down Expand Up @@ -3405,3 +3527,24 @@ def test_denormalize_healthcheck(self):
denormalized_service = denormalize_service_dict(processed_service, V2_1)
assert denormalized_service['healthcheck']['interval'] == '100s'
assert denormalized_service['healthcheck']['timeout'] == '30s'

def test_denormalize_secrets(self):
service_dict = {
'name': 'web',
'image': 'example/web',
'secrets': [
types.ServiceSecret('one', None, None, None, None),
types.ServiceSecret('source', 'target', '100', '200', 0o777),
],
}
denormalized_service = denormalize_service_dict(service_dict, V3_1)
assert secret_sort(denormalized_service['secrets']) == secret_sort([
{'source': 'one'},
{
'source': 'source',
'target': 'target',
'uid': '100',
'gid': '200',
'mode': 0o777,
},
])

0 comments on commit f963d76

Please sign in to comment.