Merge pull request #2662 from dnephin/fix_config_printing_unicode

Fix config printing unicode objects
This commit is contained in:
Daniel Nephin 2016-01-14 19:26:39 -05:00
commit 2a2eb81215
10 changed files with 120 additions and 53 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.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

@ -65,7 +65,10 @@ class Network(object):
) )
def remove(self): def remove(self):
# TODO: don't remove external networks if self.external_name:
log.info("Network %s is external, skipping", self.full_name)
return
log.info("Removing network {}".format(self.full_name)) log.info("Removing network {}".format(self.full_name))
self.client.remove_network(self.full_name) self.client.remove_network(self.full_name)

View File

@ -275,7 +275,7 @@ class Project(object):
def down(self, remove_image_type, include_volumes): def down(self, remove_image_type, include_volumes):
self.stop() self.stop()
self.remove_stopped(v=include_volumes) self.remove_stopped(v=include_volumes)
self.remove_default_network() self.remove_networks()
if include_volumes: if include_volumes:
self.remove_volumes() self.remove_volumes()
@ -286,11 +286,11 @@ class Project(object):
for service in self.get_services(): for service in self.get_services():
service.remove_image(remove_image_type) service.remove_image(remove_image_type)
def remove_default_network(self): def remove_networks(self):
if not self.use_networking: if not self.use_networking:
return return
if self.uses_default_network(): for network in self.networks:
self.default_network.remove() network.remove()
def remove_volumes(self): def remove_volumes(self):
for volume in self.volumes: for volume in self.volumes:

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

@ -10,8 +10,8 @@ import subprocess
import time import time
from collections import namedtuple from collections import namedtuple
from operator import attrgetter from operator import attrgetter
from textwrap import dedent
import yaml
from docker import errors from docker import errors
from .. import mock from .. import mock
@ -148,8 +148,9 @@ class CLITestCase(DockerClientTestCase):
self.base_dir = None self.base_dir = None
def test_config_list_services(self): def test_config_list_services(self):
self.base_dir = 'tests/fixtures/v2-full'
result = self.dispatch(['config', '--services']) result = self.dispatch(['config', '--services'])
assert set(result.stdout.rstrip().split('\n')) == {'simple', 'another'} assert set(result.stdout.rstrip().split('\n')) == {'web', 'other'}
def test_config_quiet_with_error(self): def test_config_quiet_with_error(self):
self.base_dir = None self.base_dir = None
@ -160,20 +161,36 @@ class CLITestCase(DockerClientTestCase):
assert "'notaservice' doesn't have any configuration" in result.stderr assert "'notaservice' doesn't have any configuration" in result.stderr
def test_config_quiet(self): def test_config_quiet(self):
self.base_dir = 'tests/fixtures/v2-full'
assert self.dispatch(['config', '-q']).stdout == '' assert self.dispatch(['config', '-q']).stdout == ''
def test_config_default(self): def test_config_default(self):
self.base_dir = 'tests/fixtures/v2-full'
result = self.dispatch(['config']) result = self.dispatch(['config'])
assert dedent(""" # assert there are no python objects encoded in the output
simple: assert '!!' not in result.stdout
command: top
image: busybox:latest output = yaml.load(result.stdout)
""").lstrip() in result.stdout expected = {
assert dedent(""" 'version': 2,
another: 'volumes': {'data': {'driver': 'local'}},
command: top 'networks': {'front': {}},
image: busybox:latest 'services': {
""").lstrip() in result.stdout 'web': {
'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'],
},
},
}
assert output == expected
def test_ps(self): def test_ps(self):
self.project.get_service('simple').create_container() self.project.get_service('simple').create_container()
@ -340,16 +357,20 @@ class CLITestCase(DockerClientTestCase):
assert '--rmi flag must be' in result.stderr assert '--rmi flag must be' in result.stderr
def test_down(self): def test_down(self):
self.base_dir = 'tests/fixtures/shutdown' self.base_dir = 'tests/fixtures/v2-full'
self.dispatch(['up', '-d']) self.dispatch(['up', '-d'])
wait_on_condition(ContainerCountCondition(self.project, 1)) wait_on_condition(ContainerCountCondition(self.project, 2))
result = self.dispatch(['down', '--rmi=local', '--volumes']) result = self.dispatch(['down', '--rmi=local', '--volumes'])
assert 'Stopping shutdown_web_1' in result.stderr assert 'Stopping v2full_web_1' in result.stderr
assert 'Removing shutdown_web_1' in result.stderr assert 'Stopping v2full_other_1' in result.stderr
assert 'Removing volume shutdown_data' in result.stderr assert 'Removing v2full_web_1' in result.stderr
assert 'Removing image shutdown_web' in result.stderr assert 'Removing v2full_other_1' in result.stderr
assert 'Removing network shutdown_default' in result.stderr assert 'Removing volume v2full_data' in result.stderr
assert 'Removing image v2full_web' in result.stderr
assert 'Removing image busybox' not in result.stderr
assert 'Removing network v2full_default' in result.stderr
assert 'Removing network v2full_front' in result.stderr
def test_up_detached(self): def test_up_detached(self):
self.dispatch(['up', '-d']) self.dispatch(['up', '-d'])

View File

@ -1,10 +0,0 @@
version: 2
volumes:
data:
driver: local
services:
web:
build: .

View File

@ -0,0 +1,24 @@
version: 2
volumes:
data:
driver: local
networks:
front: {}
services:
web:
build: .
networks:
- front
- default
volumes_from:
- other
other:
image: busybox:latest
command: top
volumes:
- /data