Compare commits

..

32 Commits
1.7.0 ... 1.7.1

Author SHA1 Message Date
Joffrey F
6c29830127 Merge pull request #3418 from shin-/bump-1.7.1
Bump 1.7.1
2016-05-04 13:03:11 -07:00
Joffrey F
0a9ab358bf Bump 1.7.1
Signed-off-by: Joffrey F <joffrey@docker.com>
2016-05-04 11:52:57 -07:00
Joffrey F
3c424b709e Properly handle APIError failures in Project.up
Signed-off-by: Joffrey F <joffrey@docker.com>
2016-05-04 11:52:57 -07:00
Joffrey F
47a40d42c7 Require latest docker-py version
Signed-off-by: Joffrey F <joffrey@docker.com>
2016-05-04 11:52:57 -07:00
Aaron Nall
f316b448c2 Add missing log event filter when using docker-compose logs.
Signed-off-by: Aaron Nall <aronahl@hotmail.com>
2016-05-04 11:52:57 -07:00
André R
6bfdde6855 Clarify env-file doc that .env is read from cwd
Closes #3381

Signed-off-by: André R <ar@ez.no>
2016-05-04 11:52:57 -07:00
Joffrey F
2a08d4731e Skip event objects that don't contain a status field
Signed-off-by: Joffrey F <joffrey@docker.com>
2016-05-04 11:52:57 -07:00
Joffrey F
11d8093fc8 Support combination of shorthand flag and equal sign for host option
Signed-off-by: Joffrey F <joffrey@docker.com>
2016-05-04 11:52:57 -07:00
Daniel Nephin
d0b46ca9b2 Upgade pip to latest
Hopefully fixes our builds.

Signed-off-by: Daniel Nephin <dnephin@docker.com>
2016-05-04 11:52:56 -07:00
Joffrey F
b7f9fc4b28 Define WindowsError on non-win32 platforms
Signed-off-by: Joffrey F <joffrey@docker.com>
2016-05-04 11:52:56 -07:00
Ben Firshman
70a605acac Explain the explanation about file versions
This explanation looked like it was part of the error. Added an
extra new line and a bit of copy to explain the explanation.

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2016-05-04 11:52:56 -07:00
Ben Firshman
85b85bc675 Make validation error less robotic
"ERROR: Validation failed in file './docker-compose.yml', reason(s):"

is now:

"ERROR: The Compose file './docker-compose.yml' is invalid because:"

Signed-off-by: Ben Firshman <ben@firshman.co.uk>
2016-05-04 11:52:56 -07:00
Patrice FERLET
b334b6f059 Fix the tests from jenkins
Acceptance tests didn't set "help" command to return "0" EXIT_CODE.

close #3354
related #3263

Signed-off-by: Patrice Ferlet <metal3d@gmail.com>
2016-05-04 11:52:56 -07:00
Joffrey F
0c1c338a02 Force docker-py 1.8.0 or above
Signed-off-by: Joffrey F <joffrey@docker.com>
2016-05-04 11:52:56 -07:00
johnharris85
f655a8af95 Account for aliased links Fix failing tests Signed-off-by: John Harris <john@johnharris.io> 2016-05-04 11:52:56 -07:00
johnharris85
f7cd94d4a9 Adding tests Signed-off-by: John Harris <john@johnharris.io> 2016-05-04 11:52:56 -07:00
johnharris85
e4d2d7ed8a Config now catches undefined service links Fixes issue #2922 Signed-off-by: John Harris <john@johnharris.io> 2016-05-04 11:52:56 -07:00
Daniel Nephin
2a8c2c8ad6 Unit test for skipping network disconnect.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
2016-05-04 11:52:56 -07:00
Daniel Nephin
5852db4d72 Set networking_config when creating a container.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
2016-05-04 11:52:56 -07:00
Daniel Nephin
250a7a530b Only disconnect if we don't already have the short id alias.
Signed-off-by: Daniel Nephin <dnephin@docker.com>
2016-05-04 11:52:56 -07:00
johnharris85
4e8b017283 Fix CLI docstring to reflect Docopt behaviour.
Signed-off-by: John Harris <john@johnharris.io>
2016-05-04 11:52:56 -07:00
Vladimir Lagunov
a86a195c50 Fix #3248: Accidental config_hash change
Signed-off-by: Vladimir Lagunov <lagunov.vladimir@gmail.com>
2016-05-04 11:52:56 -07:00
Tony Witherspoon
3368887a29 Updated StringIO import to support io module
Signed-off-by: Tony Witherspoon <tony.witherspoon@gmail.com>
2016-05-04 11:52:56 -07:00
Tony Witherspoon
e5f1429ce1 Updated cli_test.py to validate against the updated help command conditions
Signed-off-by: Tony Witherspoon <tony.witherspoon@gmail.com>
2016-05-04 11:52:56 -07:00
Tony Witherspoon
65b0e5973b updated cli_test.py to no longer expect raised SystemExit exceptions
Signed-off-by: Tony Witherspoon <tony.witherspoon@gmail.com>
2016-05-04 11:52:56 -07:00
Tony Witherspoon
9cf483e224 Added code to output the top level command options if docker-compose help with no command options provided
Signed-off-by: Tony Witherspoon <tony.witherspoon@gmail.com>
2016-05-04 11:52:56 -07:00
Aanand Prasad
1e164ca802 Fix format of 'restart' option in 'config' output
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2016-05-04 11:52:56 -07:00
Aanand Prasad
a2ded237e4 Fix output of 'config' for v1 files
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2016-05-04 11:52:56 -07:00
Aanand Prasad
8a9ab69a1c Check full error message in test_up_with_net_is_invalid
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2016-05-04 11:52:56 -07:00
Aanand Prasad
9cfbfd55c4 Remove v2_only decorators on config tests
Signed-off-by: Aanand Prasad <aanand.prasad@gmail.com>
2016-05-04 11:52:56 -07:00
Joffrey F
d41e6e00fa Merge remote-tracking branch 'source/release' into bump-1.7.1 2016-05-04 11:49:05 -07:00
Daniel Nephin
e8da6cb631 Merge pull request #3319 from docker/bump-1.7.0
WIP: Bump 1.7.0
2016-04-13 13:20:02 -04:00
28 changed files with 342 additions and 67 deletions

View File

@@ -1,6 +1,47 @@
Change log
==========
1.7.1 (2016-05-04)
-----------------
Bug Fixes
- Fixed a bug where the output of `docker-compose config` for v1 files
would be an invalid configuration file.
- Fixed a bug where `docker-compose config` would not check the validity
of links.
- Fixed an issue where `docker-compose help` would not output a list of
available commands and generic options as expected.
- Fixed an issue where filtering by service when using `docker-compose logs`
would not apply for newly created services.
- Fixed a bug where unchanged services would sometimes be recreated in
in the up phase when using Compose with Python 3.
- Fixed an issue where API errors encountered during the up phase would
not be recognized as a failure state by Compose.
- Fixed a bug where Compose would raise a NameError because of an undefined
exception name on non-Windows platforms.
- Fixed a bug where the wrong version of `docker-py` would sometimes be
installed alongside Compose.
- Fixed a bug where the host value output by `docker-machine config default`
would not be recognized as valid options by the `docker-compose`
command line.
- Fixed an issue where Compose would sometimes exit unexpectedly while
reading events broadcasted by a Swarm cluster.
- Corrected a statement in the docs about the location of the `.env` file,
which is indeed read from the current directory, instead of in the same
location as the Compose file.
1.7.0 (2016-04-13)
------------------

View File

@@ -49,11 +49,11 @@ RUN set -ex; \
# Install pip
RUN set -ex; \
curl -L https://pypi.python.org/packages/source/p/pip/pip-7.0.1.tar.gz | tar -xz; \
cd pip-7.0.1; \
curl -L https://pypi.python.org/packages/source/p/pip/pip-8.1.1.tar.gz | tar -xz; \
cd pip-8.1.1; \
python setup.py install; \
cd ..; \
rm -rf pip-7.0.1
rm -rf pip-8.1.1
# Python3 requires a valid locale
RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && locale-gen

View File

@@ -1,4 +1,4 @@
from __future__ import absolute_import
from __future__ import unicode_literals
__version__ = '1.7.0'
__version__ = '1.7.1'

View File

@@ -21,12 +21,15 @@ log = logging.getLogger(__name__)
def project_from_options(project_dir, options):
environment = Environment.from_env_file(project_dir)
host = options.get('--host')
if host is not None:
host = host.lstrip('=')
return get_project(
project_dir,
get_config_path_from_options(project_dir, options, environment),
project_name=options.get('--project-name'),
verbose=options.get('--verbose'),
host=options.get('--host'),
host=host,
tls_config=tls_config_from_options(options),
environment=environment
)

View File

@@ -24,6 +24,7 @@ from ..const import IS_WINDOWS_PLATFORM
from ..progress_stream import StreamOutputError
from ..project import NoSuchService
from ..project import OneOffFilter
from ..project import ProjectError
from ..service import BuildAction
from ..service import BuildError
from ..service import ConvergenceStrategy
@@ -58,7 +59,7 @@ def main():
except (KeyboardInterrupt, signals.ShutdownException):
log.error("Aborting.")
sys.exit(1)
except (UserError, NoSuchService, ConfigurationError) as e:
except (UserError, NoSuchService, ConfigurationError, ProjectError) as e:
log.error(e.msg)
sys.exit(1)
except BuildError as e:
@@ -142,7 +143,7 @@ class TopLevelCommand(object):
"""Define and run multi-container applications with Docker.
Usage:
docker-compose [-f=<arg>...] [options] [COMMAND] [ARGS...]
docker-compose [-f <arg>...] [options] [COMMAND] [ARGS...]
docker-compose -h|--help
Options:
@@ -361,10 +362,14 @@ class TopLevelCommand(object):
"""
Get help on a command.
Usage: help COMMAND
Usage: help [COMMAND]
"""
handler = get_handler(cls, options['COMMAND'])
raise SystemExit(getdoc(handler))
if options['COMMAND']:
subject = get_handler(cls, options['COMMAND'])
else:
subject = cls
print(getdoc(subject))
def kill(self, options):
"""
@@ -411,7 +416,8 @@ class TopLevelCommand(object):
self.project,
containers,
options['--no-color'],
log_args).run()
log_args,
event_stream=self.project.events(service_names=options['SERVICE'])).run()
def pause(self, options):
"""

View File

@@ -12,6 +12,13 @@ from six.moves import input
import compose
# WindowsError is not defined on non-win32 platforms. Avoid runtime errors by
# defining it as OSError (its parent class) if missing.
try:
WindowsError
except NameError:
WindowsError = OSError
def yesno(prompt, default=None):
"""

View File

@@ -37,6 +37,7 @@ from .validation import validate_against_config_schema
from .validation import validate_config_section
from .validation import validate_depends_on
from .validation import validate_extends_file_path
from .validation import validate_links
from .validation import validate_network_mode
from .validation import validate_service_constraints
from .validation import validate_top_level_object
@@ -580,6 +581,7 @@ def validate_service(service_config, service_names, version):
validate_ulimits(service_config)
validate_network_mode(service_config, service_names)
validate_depends_on(service_config, service_names)
validate_links(service_config, service_names)
if not service_dict.get('image') and has_uppercase(service_name):
raise ConfigurationError(
@@ -726,7 +728,7 @@ class MergeDict(dict):
merged = parse_sequence_func(self.base.get(field, []))
merged.update(parse_sequence_func(self.override.get(field, [])))
self[field] = [item.repr() for item in merged.values()]
self[field] = [item.repr() for item in sorted(merged.values())]
def merge_scalar(self, field):
if self.needs_merge(field):
@@ -928,7 +930,7 @@ def dict_from_path_mappings(path_mappings):
def path_mappings_from_dict(d):
return [join_path_mapping(v) for v in d.items()]
return [join_path_mapping(v) for v in sorted(d.items())]
def split_path_mapping(volume_path):

View File

@@ -3,10 +3,11 @@ from __future__ import unicode_literals
VERSION_EXPLANATION = (
'Either specify a version of "2" (or "2.0") and place your service '
'definitions under the `services` key, or omit the `version` key and place '
'your service definitions at the root of the file to use version 1.\n'
'For more on the Compose file format versions, see '
'You might be seeing this error because you\'re using the wrong Compose '
'file version. Either specify a version of "2" (or "2.0") and place your '
'service definitions under the `services` key, or omit the `version` key '
'and place your service definitions at the root of the file to use '
'version 1.\nFor more on the Compose file format versions, see '
'https://docs.docker.com/compose/compose-file/')

View File

@@ -5,6 +5,8 @@ import six
import yaml
from compose.config import types
from compose.config.config import V1
from compose.config.config import V2_0
def serialize_config_type(dumper, data):
@@ -17,14 +19,36 @@ yaml.SafeDumper.add_representer(types.VolumeSpec, serialize_config_type)
def serialize_config(config):
denormalized_services = [
denormalize_service_dict(service_dict, config.version)
for service_dict in config.services
]
services = {
service_dict.pop('name'): service_dict
for service_dict in denormalized_services
}
output = {
'version': config.version,
'services': {service.pop('name'): service for service in config.services},
'version': V2_0,
'services': services,
'networks': config.networks,
'volumes': config.volumes,
}
return yaml.safe_dump(
output,
default_flow_style=False,
indent=2,
width=80)
def denormalize_service_dict(service_dict, version):
service_dict = service_dict.copy()
if 'restart' in service_dict:
service_dict['restart'] = types.serialize_restart_spec(service_dict['restart'])
if version == V1 and 'network_mode' not in service_dict:
service_dict['network_mode'] = 'bridge'
return service_dict

View File

@@ -7,6 +7,8 @@ from __future__ import unicode_literals
import os
from collections import namedtuple
import six
from compose.config.config import V1
from compose.config.errors import ConfigurationError
from compose.const import IS_WINDOWS_PLATFORM
@@ -89,6 +91,13 @@ def parse_restart_spec(restart_config):
return {'Name': name, 'MaximumRetryCount': int(max_retry_count)}
def serialize_restart_spec(restart_spec):
parts = [restart_spec['Name']]
if restart_spec['MaximumRetryCount']:
parts.append(six.text_type(restart_spec['MaximumRetryCount']))
return ':'.join(parts)
def parse_extra_hosts(extra_hosts_config):
if not extra_hosts_config:
return {}

View File

@@ -171,6 +171,14 @@ def validate_network_mode(service_config, service_names):
"is undefined.".format(s=service_config, dep=dependency))
def validate_links(service_config, service_names):
for link in service_config.config.get('links', []):
if link.split(':')[0] not in service_names:
raise ConfigurationError(
"Service '{s.name}' has a link to service '{link}' which is "
"undefined.".format(s=service_config, link=link))
def validate_depends_on(service_config, service_names):
for dependency in service_config.config.get('depends_on', []):
if dependency not in service_names:
@@ -211,7 +219,7 @@ def handle_error_for_schema_with_id(error, path):
return get_unsupported_config_msg(path, invalid_config_key)
if not error.path:
return '{}\n{}'.format(error.message, VERSION_EXPLANATION)
return '{}\n\n{}'.format(error.message, VERSION_EXPLANATION)
def handle_generic_error(error, path):
@@ -408,6 +416,6 @@ def handle_errors(errors, format_error_func, filename):
error_msg = '\n'.join(format_error_func(error) for error in errors)
raise ConfigurationError(
"Validation failed{file_msg}, reason(s):\n{error_msg}".format(
file_msg=" in file '{}'".format(filename) if filename else "",
"The Compose file{file_msg} is invalid because:\n{error_msg}".format(
file_msg=" '{}'".format(filename) if filename else "",
error_msg=error_msg))

View File

@@ -59,7 +59,7 @@ def parallel_execute(objects, func, get_name, msg, get_deps=None):
if error_to_reraise:
raise error_to_reraise
return results
return results, errors
def _no_deps(x):

View File

@@ -342,7 +342,10 @@ class Project(object):
filters={'label': self.labels()},
decode=True
):
if event['status'] in IMAGE_EVENTS:
# The first part of this condition is a guard against some events
# broadcasted by swarm that don't have a status field.
# See https://github.com/docker/compose/issues/3316
if 'status' not in event or event['status'] in IMAGE_EVENTS:
# We don't receive any image events because labels aren't applied
# to images
continue
@@ -387,13 +390,18 @@ class Project(object):
def get_deps(service):
return {self.get_service(dep) for dep in service.get_dependency_names()}
results = parallel.parallel_execute(
results, errors = parallel.parallel_execute(
services,
do,
operator.attrgetter('name'),
None,
get_deps
)
if errors:
raise ProjectError(
'Encountered errors while bringing up the project.'
)
return [
container
for svc_containers in results
@@ -528,3 +536,7 @@ class NoSuchService(Exception):
def __str__(self):
return self.msg
class ProjectError(Exception):
pass

View File

@@ -453,20 +453,20 @@ class Service(object):
connected_networks = container.get('NetworkSettings.Networks')
for network, netdefs in self.networks.items():
aliases = netdefs.get('aliases', [])
ipv4_address = netdefs.get('ipv4_address', None)
ipv6_address = netdefs.get('ipv6_address', None)
if network in connected_networks:
if short_id_alias_exists(container, network):
continue
self.client.disconnect_container_from_network(
container.id, network)
container.id,
network)
self.client.connect_container_to_network(
container.id, network,
aliases=list(self._get_aliases(container).union(aliases)),
ipv4_address=ipv4_address,
ipv6_address=ipv6_address,
links=self._get_links(False)
)
aliases=self._get_aliases(netdefs, container),
ipv4_address=netdefs.get('ipv4_address', None),
ipv6_address=netdefs.get('ipv6_address', None),
links=self._get_links(False))
def remove_duplicate_containers(self, timeout=DEFAULT_TIMEOUT):
for c in self.duplicate_containers():
@@ -533,11 +533,32 @@ class Service(object):
numbers = [c.number for c in containers]
return 1 if not numbers else max(numbers) + 1
def _get_aliases(self, container):
if container.labels.get(LABEL_ONE_OFF) == "True":
return set()
def _get_aliases(self, network, container=None):
if container and container.labels.get(LABEL_ONE_OFF) == "True":
return []
return {self.name, container.short_id}
return list(
{self.name} |
({container.short_id} if container else set()) |
set(network.get('aliases', ()))
)
def build_default_networking_config(self):
if not self.networks:
return {}
network = self.networks[self.network_mode.id]
endpoint = {
'Aliases': self._get_aliases(network),
'IPAMConfig': {},
}
if network.get('ipv4_address'):
endpoint['IPAMConfig']['IPv4Address'] = network.get('ipv4_address')
if network.get('ipv6_address'):
endpoint['IPAMConfig']['IPv6Address'] = network.get('ipv6_address')
return {"EndpointsConfig": {self.network_mode.id: endpoint}}
def _get_links(self, link_to_self):
links = {}
@@ -633,6 +654,10 @@ class Service(object):
override_options,
one_off=one_off)
networking_config = self.build_default_networking_config()
if networking_config:
container_options['networking_config'] = networking_config
container_options['environment'] = format_environment(
container_options['environment'])
return container_options
@@ -796,6 +821,12 @@ class Service(object):
log.error(six.text_type(e))
def short_id_alias_exists(container, network):
aliases = container.get(
'NetworkSettings.Networks.{net}.Aliases'.format(net=network)) or ()
return container.short_id in aliases
class NetworkMode(object):
"""A `standard` network mode (ex: host, bridge)"""

View File

@@ -13,8 +13,8 @@ weight=10
# Environment file
Compose supports declaring default environment variables in an environment
file named `.env` and placed in the same folder as your
[compose file](compose-file.md).
file named `.env` placed in the folder `docker-compose` command is executed from
*(current working directory)*.
Compose expects each line in an env file to be in `VAR=VAL` format. Lines
beginning with `#` (i.e. comments) are ignored, as are blank lines.

View File

@@ -39,7 +39,7 @@ which the release page specifies, in your terminal.
The following is an example command illustrating the format:
curl -L https://github.com/docker/compose/releases/download/1.7.0/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
curl -L https://github.com/docker/compose/releases/download/1.7.1/docker-compose-`uname -s`-`uname -m` > /usr/local/bin/docker-compose
If you have problems installing with `curl`, see
[Alternative Install Options](#alternative-install-options).
@@ -54,7 +54,7 @@ which the release page specifies, in your terminal.
7. Test the installation.
$ docker-compose --version
docker-compose version: 1.7.0
docker-compose version: 1.7.1
## Alternative install options
@@ -77,7 +77,7 @@ to get started.
Compose can also be run inside a container, from a small bash script wrapper.
To install compose as a container run:
$ curl -L https://github.com/docker/compose/releases/download/1.7.0/run.sh > /usr/local/bin/docker-compose
$ curl -L https://github.com/docker/compose/releases/download/1.7.1/run.sh > /usr/local/bin/docker-compose
$ chmod +x /usr/local/bin/docker-compose
## Master builds

View File

@@ -1,6 +1,6 @@
PyYAML==3.11
cached-property==1.2.0
docker-py==1.8.0
docker-py==1.8.1
dockerpty==0.4.1
docopt==0.6.1
enum34==1.0.4

View File

@@ -15,7 +15,7 @@
set -e
VERSION="1.7.0"
VERSION="1.7.1"
IMAGE="docker/compose:$VERSION"

View File

@@ -34,7 +34,7 @@ install_requires = [
'requests >= 2.6.1, < 2.8',
'texttable >= 0.8.1, < 0.9',
'websocket-client >= 0.32.0, < 1.0',
'docker-py > 1.7.2, < 2',
'docker-py >= 1.8.1, < 2',
'dockerpty >= 0.4.1, < 0.5',
'six >= 1.3.0, < 2',
'jsonschema >= 2.5.1, < 3',

View File

@@ -140,20 +140,23 @@ class CLITestCase(DockerClientTestCase):
def test_help(self):
self.base_dir = 'tests/fixtures/no-composefile'
result = self.dispatch(['help', 'up'], returncode=1)
assert 'Usage: up [options] [SERVICE...]' in result.stderr
result = self.dispatch(['help', 'up'], returncode=0)
assert 'Usage: up [options] [SERVICE...]' in result.stdout
# Prevent tearDown from trying to create a project
self.base_dir = None
# TODO: this shouldn't be v2-dependent
@v2_only()
def test_shorthand_host_opt(self):
self.dispatch(
['-H={0}'.format(os.environ.get('DOCKER_HOST', 'unix://')),
'up', '-d'],
returncode=0
)
def test_config_list_services(self):
self.base_dir = 'tests/fixtures/v2-full'
result = self.dispatch(['config', '--services'])
assert set(result.stdout.rstrip().split('\n')) == {'web', 'other'}
# TODO: this shouldn't be v2-dependent
@v2_only()
def test_config_quiet_with_error(self):
self.base_dir = None
result = self.dispatch([
@@ -162,14 +165,10 @@ class CLITestCase(DockerClientTestCase):
], returncode=1)
assert "'notaservice' must be a mapping" in result.stderr
# TODO: this shouldn't be v2-dependent
@v2_only()
def test_config_quiet(self):
self.base_dir = 'tests/fixtures/v2-full'
assert self.dispatch(['config', '-q']).stdout == ''
# TODO: this shouldn't be v2-dependent
@v2_only()
def test_config_default(self):
self.base_dir = 'tests/fixtures/v2-full'
result = self.dispatch(['config'])
@@ -198,6 +197,58 @@ class CLITestCase(DockerClientTestCase):
}
assert output == expected
def test_config_restart(self):
self.base_dir = 'tests/fixtures/restart'
result = self.dispatch(['config'])
assert yaml.load(result.stdout) == {
'version': '2.0',
'services': {
'never': {
'image': 'busybox',
'restart': 'no',
},
'always': {
'image': 'busybox',
'restart': 'always',
},
'on-failure': {
'image': 'busybox',
'restart': 'on-failure',
},
'on-failure-5': {
'image': 'busybox',
'restart': 'on-failure:5',
},
},
'networks': {},
'volumes': {},
}
def test_config_v1(self):
self.base_dir = 'tests/fixtures/v1-config'
result = self.dispatch(['config'])
assert yaml.load(result.stdout) == {
'version': '2.0',
'services': {
'net': {
'image': 'busybox',
'network_mode': 'bridge',
},
'volume': {
'image': 'busybox',
'volumes': ['/data:rw'],
'network_mode': 'bridge',
},
'app': {
'image': 'busybox',
'volumes_from': ['service:volume:rw'],
'network_mode': 'service:net',
},
},
'networks': {},
'volumes': {},
}
def test_ps(self):
self.project.get_service('simple').create_container()
result = self.dispatch(['ps'])
@@ -683,9 +734,7 @@ class CLITestCase(DockerClientTestCase):
['-f', 'v2-invalid.yml', 'up', '-d'],
returncode=1)
# TODO: fix validation error messages for v2 files
# assert "Unsupported config option for service 'web': 'net'" in exc.exconly()
assert "Unsupported config option" in result.stderr
assert "Unsupported config option for services.bar: 'net'" in result.stderr
def test_up_with_net_v1(self):
self.base_dir = 'tests/fixtures/net-container'

View File

@@ -1,3 +1,5 @@
mydb:
build: '.'
myweb:
build: '.'
extends:

View File

@@ -0,0 +1,14 @@
version: "2"
services:
never:
image: busybox
restart: "no"
always:
image: busybox
restart: always
on-failure:
image: busybox
restart: on-failure
on-failure-5:
image: busybox
restart: "on-failure:5"

View File

@@ -0,0 +1,10 @@
net:
image: busybox
volume:
image: busybox
volumes:
- /data
app:
image: busybox
net: "container:net"
volumes_from: ["volume"]

View File

@@ -19,6 +19,7 @@ from compose.const import LABEL_PROJECT
from compose.const import LABEL_SERVICE
from compose.container import Container
from compose.project import Project
from compose.project import ProjectError
from compose.service import ConvergenceStrategy
from tests.integration.testcases import v2_only
@@ -565,7 +566,11 @@ class ProjectTest(DockerClientTestCase):
'name': 'web',
'image': 'busybox:latest',
'command': 'top',
'networks': {'foo': None, 'bar': None, 'baz': None},
'networks': {
'foo': None,
'bar': None,
'baz': {'aliases': ['extra']},
},
}],
volumes={},
networks={
@@ -581,15 +586,23 @@ class ProjectTest(DockerClientTestCase):
config_data=config_data,
)
project.up()
self.assertEqual(len(project.containers()), 1)
containers = project.containers()
assert len(containers) == 1
container, = containers
for net_name in ['foo', 'bar', 'baz']:
full_net_name = 'composetest_{}'.format(net_name)
network_data = self.client.inspect_network(full_net_name)
self.assertEqual(network_data['Name'], full_net_name)
assert network_data['Name'] == full_net_name
aliases_key = 'NetworkSettings.Networks.{net}.Aliases'
assert 'web' in container.get(aliases_key.format(net='composetest_foo'))
assert 'web' in container.get(aliases_key.format(net='composetest_baz'))
assert 'extra' in container.get(aliases_key.format(net='composetest_baz'))
foo_data = self.client.inspect_network('composetest_foo')
self.assertEqual(foo_data['Driver'], 'bridge')
assert foo_data['Driver'] == 'bridge'
@v2_only()
def test_up_with_ipam_config(self):
@@ -740,7 +753,8 @@ class ProjectTest(DockerClientTestCase):
config_data=config_data,
)
assert len(project.up()) == 0
with self.assertRaises(ProjectError):
project.up()
@v2_only()
def test_project_up_volumes(self):

View File

@@ -5,6 +5,7 @@ from __future__ import unicode_literals
import os
import shutil
import tempfile
from io import StringIO
import docker
import py
@@ -83,10 +84,10 @@ class CLITestCase(unittest.TestCase):
self.assertTrue(project.services)
def test_command_help(self):
with pytest.raises(SystemExit) as exc:
with mock.patch('sys.stdout', new=StringIO()) as fake_stdout:
TopLevelCommand.help({'COMMAND': 'up'})
assert 'Usage: up' in exc.exconly()
assert "Usage: up" in fake_stdout.getvalue()
def test_command_help_nonexistent(self):
with pytest.raises(NoSuchCommand):

View File

@@ -1360,6 +1360,17 @@ class ConfigTest(unittest.TestCase):
config.load(config_details)
assert "Service 'one' depends on service 'three'" in exc.exconly()
def test_linked_service_is_undefined(self):
with self.assertRaises(ConfigurationError):
config.load(
build_config_details({
'version': '2',
'services': {
'web': {'image': 'busybox', 'links': ['db:db']},
},
})
)
def test_load_dockerfile_without_context(self):
config_details = build_config_details({
'version': '2',

View File

@@ -29,7 +29,7 @@ def get_deps(obj):
def test_parallel_execute():
results = parallel_execute(
results, errors = parallel_execute(
objects=[1, 2, 3, 4, 5],
func=lambda x: x * 2,
get_name=six.text_type,
@@ -37,6 +37,7 @@ def test_parallel_execute():
)
assert sorted(results) == [2, 4, 6, 8, 10]
assert errors == {}
def test_parallel_execute_with_deps():

View File

@@ -643,6 +643,35 @@ class ServiceTest(unittest.TestCase):
assert service.image_name == 'testing_foo'
class TestServiceNetwork(object):
def test_connect_container_to_networks_short_aliase_exists(self):
mock_client = mock.create_autospec(docker.Client)
service = Service(
'db',
mock_client,
'myproject',
image='foo',
networks={'project_default': {}})
container = Container(
None,
{
'Id': 'abcdef',
'NetworkSettings': {
'Networks': {
'project_default': {
'Aliases': ['analias', 'abcdef'],
},
},
},
},
True)
service.connect_container_to_networks(container)
assert not mock_client.disconnect_container_from_network.call_count
assert not mock_client.connect_container_to_network.call_count
def sort_by_name(dictionary_list):
return sorted(dictionary_list, key=lambda k: k['name'])