Compare commits

...

30 commits

Author SHA1 Message Date
Joffrey F
aa8fb8f708 Merge pull request #4499 from xulike666/function-name-modification
function-name-modification for tests/*
2017-02-16 11:59:36 -08:00
Joffrey F
b49f42f9b7 Merge pull request #4496 from xulike666/quick-easy-typo-fix
fix a typo in script/release/utils.sh
2017-02-16 11:59:17 -08:00
Joffrey F
afd3bcfbf4 Merge pull request #4484 from shin-/4425-sys-path-fix
Reinitialize sys.path after it's been potentially modified by pip
2017-02-16 11:39:54 -08:00
Aaron.L.Xu
d20e3f3342 function-name-modification for tests/*
Signed-off-by: Aaron.L.Xu <likexu@harmonycloud.cn>
2017-02-16 15:25:20 +08:00
Aaron.L.Xu
27297fd1af fix a typo in script/release/utils.sh
Signed-off-by: Aaron.L.Xu <likexu@harmonycloud.cn>
2017-02-16 11:14:49 +08:00
Joffrey F
66f4a795a2 Don't import pip inside Compose
Signed-off-by: Joffrey F <joffrey@docker.com>
2017-02-15 16:31:50 -08:00
Joffrey F
40c26ca676 Merge pull request #4480 from shin-/4479-merge-secrets
Fix `config` command output with service.secrets section
2017-02-14 17:08:18 -08:00
Joffrey F
abce83ef25 Fix config command output with service.secrets section
Signed-off-by: Joffrey F <joffrey@docker.com>
2017-02-13 16:51:50 -08:00
Joffrey F
bb5d7b2433 Merge pull request #4473 from Vehsamrak/patch-1
Compose file reference link fix in README.md
2017-02-13 13:03:39 -08:00
Petr Karmashev
252699c1d1 Compose file reference link fix in README.md
Signed-off-by: Petr Karmashev <smonkl@bk.ru>
2017-02-12 02:10:34 +03:00
Joffrey F
ad0e6d219b Merge pull request #4469 from dnephin/fix_secrets_config
Fixes secrets config loading
2017-02-10 18:39:53 -08:00
Daniel Nephin
dc5b3f3b3e Fix secrets config.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
2017-02-10 17:11:24 -05:00
Joffrey F
1fe2443735 Merge pull request #4438 from fate-grand-order/master
fix typo in CHANGELOG.md
2017-02-09 16:35:35 -08:00
Joffrey F
27e0f31275 Merge pull request #4443 from xulike666/fix-accessibility
Referencing the right segment of code in compose/bundle.py
2017-02-09 16:34:48 -08:00
Joffrey F
2f13201b9e Merge pull request #4453 from kevinetc123/patch-typo
fix typo in project.py
2017-02-09 16:33:07 -08:00
Joffrey F
276db7231a Merge pull request #4455 from dnephin/fix_3.1
Fix version 3.1
2017-02-09 12:38:29 -08:00
Daniel Nephin
c092fa37de Fix version 3.1
Signed-off-by: Daniel Nephin <dnephin@docker.com>
2017-02-09 12:39:57 -05:00
kevinetc123
47e4442722 fix typo in project.py
Signed-off-by: kevinetc123 <kaiwentan@harmonycloud.cn>
2017-02-09 19:10:26 +08:00
Joffrey F
b306e843d3 Merge pull request #4448 from shin-/1.11-release
1.11 release-master realign
2017-02-08 13:54:12 -08:00
Joffrey F
fc7b74d7f9 Bump to next dev version
Signed-off-by: Joffrey F <joffrey@docker.com>
2017-02-08 13:47:08 -08:00
Joffrey F
2cd6cb9a47 Bump 1.10.1
Signed-off-by: Joffrey F <joffrey@docker.com>
2017-02-08 13:45:25 -08:00
Joffrey F
01d1895a35 Bump 1.11.0
Signed-off-by: Joffrey F <joffrey@docker.com>
2017-02-08 13:44:40 -08:00
Joffrey F
979a0d53f7 Bump 1.11.0-rc1
Signed-off-by: Joffrey F <joffrey@docker.com>
2017-02-08 13:44:31 -08:00
Aaron.L.Xu
f083526829 referencing right segment of code
Signed-off-by: Aaron.L.Xu <likexu@harmonycloud.cn>
2017-02-08 18:50:26 +08:00
fate-grand-order
b392b6e12e fix typo in CHANGELOG.md
Signed-off-by: fate-grand-order <chenjg@harmonycloud.cn>
2017-02-07 15:59:34 +08:00
Joffrey F
165eb9c91a Merge pull request #3558 from shin-/1135-pyinstaller-update
Use newer version of PyInstaller to fix prelinking issues
2017-02-06 15:00:39 -08:00
Joffrey F
1636985a7a Merge pull request #4426 from shin-/4392-context-mgr
Close the open file handle using context manager
2017-02-06 13:30:49 -08:00
Kevin Jing Qiu
a3a9d8944a Close the open file handle using context manager
Signed-off-by: Kevin Jing Qiu <kevin.qiu@points.com>
2017-02-03 14:50:40 -08:00
Joffrey F
84774cacd2 Upgrade python and pip versions in Dockerfile
Add libbz2 dependency

Signed-off-by: Joffrey F <joffrey@docker.com>
2017-02-01 14:12:41 -08:00
Joffrey F
22249add84 Use newer version of PyInstaller to fix prelinking issues
Signed-off-by: Joffrey F <joffrey@docker.com>
2017-01-30 13:52:27 -08:00
20 changed files with 287 additions and 72 deletions

View file

@ -1,6 +1,53 @@
Change log Change log
========== ==========
1.11.0 (2017-02-08)
-------------------
### New Features
#### Compose file version 3.1
- Introduced version 3.1 of the `docker-compose.yml` specification. This
version requires Docker Engine 1.13.0 or above. It introduces support
for secrets. See the documentation for more information
#### Compose file version 2.0 and up
- Introduced the `docker-compose top` command that displays processes running
for the different services managed by Compose.
### Bugfixes
- Fixed a bug where extending a service defining a healthcheck dictionary
would cause `docker-compose` to error out.
- Fixed an issue where the `pid` entry in a service definition was being
ignored when using multiple Compose files.
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.10.0 (2017-01-18)
------------------- -------------------
@ -573,7 +620,7 @@ Bug Fixes:
if at least one container is using the network. if at least one container is using the network.
- When printings logs during `up` or `logs`, flush the output buffer after - When printings logs during `up` or `logs`, flush the output buffer after
each line to prevent buffering issues from hideing logs. each line to prevent buffering issues from hiding logs.
- Recreate a container if one of its dependencies is being created. - Recreate a container if one of its dependencies is being created.
Previously a container was only recreated if it's dependencies already Previously a container was only recreated if it's dependencies already

View file

@ -13,6 +13,7 @@ RUN set -ex; \
ca-certificates \ ca-certificates \
curl \ curl \
libsqlite3-dev \ libsqlite3-dev \
libbz2-dev \
; \ ; \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/*
@ -20,40 +21,32 @@ RUN curl https://get.docker.com/builds/Linux/x86_64/docker-1.8.3 \
-o /usr/local/bin/docker && \ -o /usr/local/bin/docker && \
chmod +x /usr/local/bin/docker chmod +x /usr/local/bin/docker
# Build Python 2.7.9 from source # Build Python 2.7.13 from source
RUN set -ex; \ RUN set -ex; \
curl -L https://www.python.org/ftp/python/2.7.9/Python-2.7.9.tgz | tar -xz; \ curl -L https://www.python.org/ftp/python/2.7.13/Python-2.7.13.tgz | tar -xz; \
cd Python-2.7.9; \ cd Python-2.7.13; \
./configure --enable-shared; \ ./configure --enable-shared; \
make; \ make; \
make install; \ make install; \
cd ..; \ cd ..; \
rm -rf /Python-2.7.9 rm -rf /Python-2.7.13
# Build python 3.4 from source # Build python 3.4 from source
RUN set -ex; \ RUN set -ex; \
curl -L https://www.python.org/ftp/python/3.4.3/Python-3.4.3.tgz | tar -xz; \ curl -L https://www.python.org/ftp/python/3.4.6/Python-3.4.6.tgz | tar -xz; \
cd Python-3.4.3; \ cd Python-3.4.6; \
./configure --enable-shared; \ ./configure --enable-shared; \
make; \ make; \
make install; \ make install; \
cd ..; \ cd ..; \
rm -rf /Python-3.4.3 rm -rf /Python-3.4.6
# Make libpython findable # Make libpython findable
ENV LD_LIBRARY_PATH /usr/local/lib ENV LD_LIBRARY_PATH /usr/local/lib
# Install setuptools
RUN set -ex; \
curl -L https://bootstrap.pypa.io/ez_setup.py | python
# Install pip # Install pip
RUN set -ex; \ RUN set -ex; \
curl -L https://pypi.python.org/packages/source/p/pip/pip-8.1.1.tar.gz | tar -xz; \ curl -L https://bootstrap.pypa.io/get-pip.py | python
cd pip-8.1.1; \
python setup.py install; \
cd ..; \
rm -rf pip-8.1.1
# Python3 requires a valid locale # Python3 requires a valid locale
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen

View file

@ -35,7 +35,7 @@ A `docker-compose.yml` looks like this:
image: redis image: redis
For more information about the Compose file, see the For more information about the Compose file, see the
[Compose file reference](https://github.com/docker/docker.github.io/blob/master/compose/compose-file.md) [Compose file reference](https://github.com/docker/docker.github.io/blob/master/compose/compose-file/compose-versioning.md)
Compose has commands for managing the whole lifecycle of your application: Compose has commands for managing the whole lifecycle of your application:

View file

@ -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.11.0dev' __version__ = '1.12.0dev'

View file

@ -202,7 +202,7 @@ def convert_service_to_bundle(name, service_dict, image_digest):
return container_config return container_config
# See https://github.com/docker/swarmkit/blob//agent/exec/container/container.go#L95 # See https://github.com/docker/swarmkit/blob/agent/exec/container/container.go#L95
def set_command_and_args(config, entrypoint, command): def set_command_and_args(config, entrypoint, command):
if isinstance(entrypoint, six.string_types): if isinstance(entrypoint, six.string_types):
entrypoint = split_command(entrypoint) entrypoint = split_command(entrypoint)

View file

@ -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

View file

@ -14,30 +14,6 @@ from distutils.spawn import find_executable
from inspect import getdoc from inspect import getdoc
from operator import attrgetter 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 errors
from . import signals from . import signals
from .. import __version__ from .. import __version__

View file

@ -186,11 +186,6 @@ class ConfigFile(namedtuple('_ConfigFile', 'filename config')):
if version == '3': if version == '3':
version = V3_0 version = V3_0
if version not in (V2_0, V2_1, V3_0):
raise ConfigurationError(
'Version in "{}" is unsupported. {}'
.format(self.filename, VERSION_EXPLANATION))
return version return version
def get_service(self, name): def get_service(self, name):
@ -479,7 +474,7 @@ def process_config_file(config_file, environment, service_name=None):
'service', 'service',
environment) environment)
if config_file.version in (V2_0, V2_1, V3_0): if config_file.version in (V2_0, V2_1, V3_0, V3_1):
processed_config = dict(config_file.config) processed_config = dict(config_file.config)
processed_config['services'] = services processed_config['services'] = services
processed_config['volumes'] = interpolate_config_section( processed_config['volumes'] = interpolate_config_section(
@ -495,7 +490,9 @@ def process_config_file(config_file, environment, service_name=None):
elif config_file.version == V1: elif config_file.version == V1:
processed_config = services processed_config = services
else: else:
raise Exception("Unsupported version: {}".format(repr(config_file.version))) raise ConfigurationError(
'Version in "{}" is unsupported. {}'
.format(config_file.filename, VERSION_EXPLANATION))
config_file = config_file._replace(config=processed_config) config_file = config_file._replace(config=processed_config)
validate_against_config_schema(config_file) validate_against_config_schema(config_file)
@ -766,6 +763,11 @@ def finalize_service(service_config, service_names, version, environment):
if 'restart' in service_dict: if 'restart' in service_dict:
service_dict['restart'] = parse_restart_spec(service_dict['restart']) 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) normalize_build(service_dict, service_config.working_dir, environment)
service_dict['name'] = service_config.name service_dict['name'] = service_config.name

View file

@ -2,6 +2,7 @@ from __future__ import absolute_import
from __future__ import unicode_literals from __future__ import unicode_literals
import codecs import codecs
import contextlib
import logging import logging
import os import os
@ -31,11 +32,12 @@ def env_vars_from_file(filename):
elif not os.path.isfile(filename): elif not os.path.isfile(filename):
raise ConfigurationError("%s is not a file." % (filename)) raise ConfigurationError("%s is not a file." % (filename))
env = {} env = {}
for line in codecs.open(filename, 'r', 'utf-8'): with contextlib.closing(codecs.open(filename, 'r', 'utf-8')) as fileobj:
line = line.strip() for line in fileobj:
if line and not line.startswith('#'): line = line.strip()
k, v = split_env(line) if line and not line.startswith('#'):
env[k] = v k, v = split_env(line)
env[k] = v
return env return env

View file

@ -102,4 +102,7 @@ def denormalize_service_dict(service_dict, version):
service_dict['healthcheck']['timeout'] service_dict['healthcheck']['timeout']
) )
if 'secrets' in service_dict:
service_dict['secrets'] = map(lambda s: s.repr(), service_dict['secrets'])
return service_dict return service_dict

View file

@ -253,3 +253,8 @@ class ServiceSecret(namedtuple('_ServiceSecret', 'source target uid gid mode')):
@property @property
def merge_field(self): def merge_field(self):
return self.source return self.source
def repr(self):
return dict(
[(k, v) for k, v in self._asdict().items() if v is not None]
)

View file

@ -365,7 +365,7 @@ class Project(object):
# TODO: get labels from the API v1.22 , see github issue 2618 # TODO: get labels from the API v1.22 , see github issue 2618
try: try:
# this can fail if the conatiner has been removed # this can fail if the container has been removed
container = Container.from_id(self.client, event['id']) container = Container.from_id(self.client, event['id'])
except APIError: except APIError:
continue continue

View file

@ -37,6 +37,11 @@ exe = EXE(pyz,
'compose/config/config_schema_v3.0.json', 'compose/config/config_schema_v3.0.json',
'DATA' 'DATA'
), ),
(
'compose/config/config_schema_v3.1.json',
'compose/config/config_schema_v3.1.json',
'DATA'
),
( (
'compose/GITSHA', 'compose/GITSHA',
'compose/GITSHA', 'compose/GITSHA',

View file

@ -1 +1 @@
pyinstaller==3.1.1 pyinstaller==3.2.1

View file

@ -1,6 +1,6 @@
#!/bin/bash #!/bin/bash
# #
# Util functions for release scritps # Util functions for release scripts
# #
set -e set -e

View file

@ -15,7 +15,7 @@
set -e set -e
VERSION="1.8.0" VERSION="1.12.0dev"
IMAGE="docker/compose:$VERSION" IMAGE="docker/compose:$VERSION"

View file

@ -1,10 +1,10 @@
#!/usr/bin/env python #!/usr/bin/env python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from __future__ import absolute_import from __future__ import absolute_import
from __future__ import print_function
from __future__ import unicode_literals from __future__ import unicode_literals
import codecs import codecs
import logging
import os import os
import re import re
import sys import sys
@ -64,11 +64,9 @@ try:
for key, value in extras_require.items(): for key, value in extras_require.items():
if key.startswith(':') and pkg_resources.evaluate_marker(key[1:]): if key.startswith(':') and pkg_resources.evaluate_marker(key[1:]):
install_requires.extend(value) install_requires.extend(value)
except Exception: except Exception as e:
logging.getLogger(__name__).exception( print("Failed to compute platform dependencies: {}. ".format(e) +
'Failed to compute platform dependencies. All dependencies will be ' "All dependencies will be installed as a result.", file=sys.stderr)
'installed as a result.'
)
for key, value in extras_require.items(): for key, value in extras_require.items():
if key.startswith(':'): if key.startswith(':'):
install_requires.extend(value) install_requires.extend(value)

View file

@ -1234,7 +1234,7 @@ class CLITestCase(DockerClientTestCase):
container = service.containers(stopped=True, one_off=OneOffFilter.only)[0] container = service.containers(stopped=True, one_off=OneOffFilter.only)[0]
self.assertEqual(user, container.get('Config.User')) self.assertEqual(user, container.get('Config.User'))
def test_run_service_with_environement_overridden(self): def test_run_service_with_environment_overridden(self):
name = 'service' name = 'service'
self.base_dir = 'tests/fixtures/environment-composefile' self.base_dir = 'tests/fixtures/environment-composefile'
self.dispatch([ self.dispatch([
@ -1246,9 +1246,9 @@ class CLITestCase(DockerClientTestCase):
]) ])
service = self.project.get_service(name) service = self.project.get_service(name)
container = service.containers(stopped=True, one_off=OneOffFilter.only)[0] container = service.containers(stopped=True, one_off=OneOffFilter.only)[0]
# env overriden # env overridden
self.assertEqual('notbar', container.environment['foo']) self.assertEqual('notbar', container.environment['foo'])
# keep environement from yaml # keep environment from yaml
self.assertEqual('world', container.environment['hello']) self.assertEqual('world', container.environment['hello'])
# added option from command line # added option from command line
self.assertEqual('beta', container.environment['alpha']) self.assertEqual('beta', container.environment['alpha'])
@ -1293,7 +1293,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(port_range[0], "0.0.0.0:49153") self.assertEqual(port_range[0], "0.0.0.0:49153")
self.assertEqual(port_range[1], "0.0.0.0:49154") self.assertEqual(port_range[1], "0.0.0.0:49154")
def test_run_service_with_explicitly_maped_ports(self): def test_run_service_with_explicitly_mapped_ports(self):
# create one off container # create one off container
self.base_dir = 'tests/fixtures/ports-composefile' self.base_dir = 'tests/fixtures/ports-composefile'
self.dispatch(['run', '-d', '-p', '30000:3000', '--publish', '30001:3001', 'simple']) self.dispatch(['run', '-d', '-p', '30000:3000', '--publish', '30001:3001', 'simple'])
@ -1310,7 +1310,7 @@ class CLITestCase(DockerClientTestCase):
self.assertEqual(port_short, "0.0.0.0:30000") self.assertEqual(port_short, "0.0.0.0:30000")
self.assertEqual(port_full, "0.0.0.0:30001") self.assertEqual(port_full, "0.0.0.0:30001")
def test_run_service_with_explicitly_maped_ip_ports(self): def test_run_service_with_explicitly_mapped_ip_ports(self):
# create one off container # create one off container
self.base_dir = 'tests/fixtures/ports-composefile' self.base_dir = 'tests/fixtures/ports-composefile'
self.dispatch([ self.dispatch([

View file

@ -184,7 +184,7 @@ class CLITestCase(unittest.TestCase):
mock_client.create_host_config.call_args[1].get('restart_policy') mock_client.create_host_config.call_args[1].get('restart_policy')
) )
def test_command_manula_and_service_ports_together(self): def test_command_manual_and_service_ports_together(self):
project = Project.from_config( project = Project.from_config(
name='composetest', name='composetest',
client=None, client=None,

View file

@ -13,12 +13,14 @@ import pytest
from ...helpers import build_config_details from ...helpers import build_config_details
from compose.config import config from compose.config import config
from compose.config import types
from compose.config.config import resolve_build_args from compose.config.config import resolve_build_args
from compose.config.config import resolve_environment from compose.config.config import resolve_environment
from compose.config.config import V1 from compose.config.config import V1
from compose.config.config import V2_0 from compose.config.config import V2_0
from compose.config.config import V2_1 from compose.config.config import V2_1
from compose.config.config import V3_0 from compose.config.config import V3_0
from compose.config.config import V3_1
from compose.config.environment import Environment from compose.config.environment import Environment
from compose.config.errors import ConfigurationError from compose.config.errors import ConfigurationError
from compose.config.errors import VERSION_EXPLANATION from compose.config.errors import VERSION_EXPLANATION
@ -52,6 +54,10 @@ def service_sort(services):
return sorted(services, key=itemgetter('name')) return sorted(services, key=itemgetter('name'))
def secret_sort(secrets):
return sorted(secrets, key=itemgetter('source'))
class ConfigTest(unittest.TestCase): class ConfigTest(unittest.TestCase):
def test_load(self): def test_load(self):
service_dicts = config.load( service_dicts = config.load(
@ -168,6 +174,9 @@ class ConfigTest(unittest.TestCase):
cfg = config.load(build_config_details({'version': version})) cfg = config.load(build_config_details({'version': version}))
assert cfg.version == V3_0 assert cfg.version == V3_0
cfg = config.load(build_config_details({'version': '3.1'}))
assert cfg.version == V3_1
def test_v1_file_version(self): def test_v1_file_version(self):
cfg = config.load(build_config_details({'web': {'image': 'busybox'}})) cfg = config.load(build_config_details({'web': {'image': 'busybox'}}))
assert cfg.version == V1 assert cfg.version == V1
@ -1766,6 +1775,38 @@ class ConfigTest(unittest.TestCase):
'labels': {'com.docker.compose.test': 'yes'} '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): def test_external_volume_config(self):
config_details = build_config_details({ config_details = build_config_details({
'version': '2', 'version': '2',
@ -1845,6 +1886,91 @@ class ConfigTest(unittest.TestCase):
config.load(config_details) config.load(config_details)
assert 'has neither an image nor a build context' in exc.exconly() 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): class NetworkModeTest(unittest.TestCase):
def test_network_mode_standard(self): def test_network_mode_standard(self):
@ -3401,3 +3527,24 @@ class SerializeTest(unittest.TestCase):
denormalized_service = denormalize_service_dict(processed_service, V2_1) denormalized_service = denormalize_service_dict(processed_service, V2_1)
assert denormalized_service['healthcheck']['interval'] == '100s' assert denormalized_service['healthcheck']['interval'] == '100s'
assert denormalized_service['healthcheck']['timeout'] == '30s' 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,
},
])