diff --git a/compose/cli/main.py b/compose/cli/main.py index 661c91f2a..4be8536f4 100644 --- a/compose/cli/main.py +++ b/compose/cli/main.py @@ -9,7 +9,6 @@ import sys from inspect import getdoc from operator import attrgetter -import yaml from docker.errors import APIError from requests.exceptions import ReadTimeout @@ -18,6 +17,7 @@ from .. import __version__ from ..config import config from ..config import ConfigurationError from ..config import parse_environment +from ..config.serialize import serialize_config from ..const import DEFAULT_TIMEOUT from ..const import HTTP_TIMEOUT from ..const import IS_WINDOWS_PLATFORM @@ -215,13 +215,7 @@ class TopLevelCommand(DocoptCommand): print('\n'.join(service['name'] for service in compose_config.services)) return - compose_config = dict( - (service.pop('name'), service) for service in compose_config.services) - print(yaml.safe_dump( - compose_config, - default_flow_style=False, - indent=2, - width=80)) + print(serialize_config(compose_config)) def create(self, project, options): """ diff --git a/compose/config/serialize.py b/compose/config/serialize.py new file mode 100644 index 000000000..06e0a027b --- /dev/null +++ b/compose/config/serialize.py @@ -0,0 +1,30 @@ +from __future__ import absolute_import +from __future__ import unicode_literals + +import six +import yaml + +from compose.config import types + + +def serialize_config_type(dumper, data): + representer = dumper.represent_str if six.PY3 else dumper.represent_unicode + return representer(data.repr()) + + +yaml.SafeDumper.add_representer(types.VolumeFromSpec, serialize_config_type) +yaml.SafeDumper.add_representer(types.VolumeSpec, serialize_config_type) + + +def serialize_config(config): + output = { + 'version': config.version, + 'services': {service.pop('name'): service for service in config.services}, + 'networks': config.networks, + 'volumes': config.volumes, + } + return yaml.safe_dump( + output, + default_flow_style=False, + indent=2, + width=80) diff --git a/compose/config/types.py b/compose/config/types.py index c0adca6c7..b872cba91 100644 --- a/compose/config/types.py +++ b/compose/config/types.py @@ -67,6 +67,9 @@ class VolumeFromSpec(namedtuple('_VolumeFromSpec', 'source mode type')): return cls(source, mode, type) + def repr(self): + return '{v.type}:{v.source}:{v.mode}'.format(v=self) + def parse_restart_spec(restart_config): if not restart_config: @@ -156,3 +159,7 @@ class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')): mode = parts[2] return cls(external, internal, mode) + + def repr(self): + external = self.external + ':' if self.external else '' + return '{ext}{v.internal}:{v.mode}'.format(ext=external, v=self) diff --git a/compose/service.py b/compose/service.py index c91c3a58c..0866b83bb 100644 --- a/compose/service.py +++ b/compose/service.py @@ -460,7 +460,8 @@ class Service(object): 'links': self.get_link_names(), 'net': self.net.id, 'volumes_from': [ - (v.source.name, v.mode) for v in self.volumes_from if isinstance(v.source, Service) + (v.source.name, v.mode) + for v in self.volumes_from if isinstance(v.source, Service) ], } @@ -519,12 +520,7 @@ class Service(object): return links def _get_volumes_from(self): - volumes_from = [] - for volume_from_spec in self.volumes_from: - volumes = build_volume_from(volume_from_spec) - volumes_from.extend(volumes) - - return volumes_from + return [build_volume_from(spec) for spec in self.volumes_from] def _get_container_create_options( self, @@ -927,7 +923,7 @@ def warn_on_masked_volume(volumes_option, container_volumes, service): def build_volume_binding(volume_spec): - return volume_spec.internal, "{}:{}:{}".format(*volume_spec) + return volume_spec.internal, volume_spec.repr() def build_volume_from(volume_from_spec): @@ -938,12 +934,14 @@ def build_volume_from(volume_from_spec): if isinstance(volume_from_spec.source, Service): containers = volume_from_spec.source.containers(stopped=True) if not containers: - return ["{}:{}".format(volume_from_spec.source.create_container().id, volume_from_spec.mode)] + return "{}:{}".format( + volume_from_spec.source.create_container().id, + volume_from_spec.mode) container = containers[0] - return ["{}:{}".format(container.id, volume_from_spec.mode)] + return "{}:{}".format(container.id, volume_from_spec.mode) elif isinstance(volume_from_spec.source, Container): - return ["{}:{}".format(volume_from_spec.source.id, volume_from_spec.mode)] + return "{}:{}".format(volume_from_spec.source.id, volume_from_spec.mode) # Labels diff --git a/tests/acceptance/cli_test.py b/tests/acceptance/cli_test.py index d93881997..d910473a8 100644 --- a/tests/acceptance/cli_test.py +++ b/tests/acceptance/cli_test.py @@ -177,12 +177,16 @@ class CLITestCase(DockerClientTestCase): 'networks': {'front': {}}, 'services': { 'web': { - 'build': os.path.abspath(self.base_dir), + 'build': { + 'context': os.path.abspath(self.base_dir), + }, 'networks': ['front', 'default'], + 'volumes_from': ['service:other:rw'], }, 'other': { 'image': 'busybox:latest', 'command': 'top', + 'volumes': ['/data:rw'], }, }, }