فهرست منبع

Ensure that the config output by config command never contains python objects.

Signed-off-by: Daniel Nephin <[email protected]>
Daniel Nephin 9 سال پیش
والد
کامیت
1bfbba36b2
5فایلهای تغییر یافته به همراه53 افزوده شده و 20 حذف شده
  1. 2 8
      compose/cli/main.py
  2. 30 0
      compose/config/serialize.py
  3. 7 0
      compose/config/types.py
  4. 9 11
      compose/service.py
  5. 5 1
      tests/acceptance/cli_test.py

+ 2 - 8
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):
         """

+ 30 - 0
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)

+ 7 - 0
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)

+ 9 - 11
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

+ 5 - 1
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'],
                 },
             },
         }