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

Signed-off-by: Daniel Nephin <dnephin@docker.com>
This commit is contained in:
Daniel Nephin 2016-01-14 17:30:48 -05:00
parent 3021ee12fe
commit 1bfbba36b2
5 changed files with 53 additions and 20 deletions

View File

@ -9,7 +9,6 @@ import sys
from inspect import getdoc from inspect import getdoc
from operator import attrgetter from operator import attrgetter
import yaml
from docker.errors import APIError from docker.errors import APIError
from requests.exceptions import ReadTimeout from requests.exceptions import ReadTimeout
@ -18,6 +17,7 @@ from .. import __version__
from ..config import config from ..config import config
from ..config import ConfigurationError from ..config import ConfigurationError
from ..config import parse_environment from ..config import parse_environment
from ..config.serialize import serialize_config
from ..const import DEFAULT_TIMEOUT from ..const import DEFAULT_TIMEOUT
from ..const import HTTP_TIMEOUT from ..const import HTTP_TIMEOUT
from ..const import IS_WINDOWS_PLATFORM from ..const import IS_WINDOWS_PLATFORM
@ -215,13 +215,7 @@ class TopLevelCommand(DocoptCommand):
print('\n'.join(service['name'] for service in compose_config.services)) print('\n'.join(service['name'] for service in compose_config.services))
return return
compose_config = dict( print(serialize_config(compose_config))
(service.pop('name'), service) for service in compose_config.services)
print(yaml.safe_dump(
compose_config,
default_flow_style=False,
indent=2,
width=80))
def create(self, project, options): def create(self, project, options):
""" """

View File

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

View File

@ -67,6 +67,9 @@ class VolumeFromSpec(namedtuple('_VolumeFromSpec', 'source mode type')):
return cls(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): def parse_restart_spec(restart_config):
if not restart_config: if not restart_config:
@ -156,3 +159,7 @@ class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')):
mode = parts[2] mode = parts[2]
return cls(external, internal, mode) 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)

View File

@ -460,7 +460,8 @@ class Service(object):
'links': self.get_link_names(), 'links': self.get_link_names(),
'net': self.net.id, 'net': self.net.id,
'volumes_from': [ '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 return links
def _get_volumes_from(self): def _get_volumes_from(self):
volumes_from = [] return [build_volume_from(spec) for spec in 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
def _get_container_create_options( def _get_container_create_options(
self, self,
@ -927,7 +923,7 @@ def warn_on_masked_volume(volumes_option, container_volumes, service):
def build_volume_binding(volume_spec): 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): 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): if isinstance(volume_from_spec.source, Service):
containers = volume_from_spec.source.containers(stopped=True) containers = volume_from_spec.source.containers(stopped=True)
if not containers: 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] 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): 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 # Labels

View File

@ -177,12 +177,16 @@ class CLITestCase(DockerClientTestCase):
'networks': {'front': {}}, 'networks': {'front': {}},
'services': { 'services': {
'web': { 'web': {
'build': os.path.abspath(self.base_dir), 'build': {
'context': os.path.abspath(self.base_dir),
},
'networks': ['front', 'default'], 'networks': ['front', 'default'],
'volumes_from': ['service:other:rw'],
}, },
'other': { 'other': {
'image': 'busybox:latest', 'image': 'busybox:latest',
'command': 'top', 'command': 'top',
'volumes': ['/data:rw'],
}, },
}, },
} }