Merge pull request #2407 from dnephin/config_command

New `docker-compose config` command
This commit is contained in:
Aanand Prasad 2015-12-08 15:16:48 +00:00
commit 82dfd465a2
4 changed files with 82 additions and 10 deletions

View File

@ -46,7 +46,7 @@ def friendly_error_message():
def project_from_options(base_dir, options):
return get_project(
base_dir,
get_config_path(options.get('--file')),
get_config_path_from_options(options),
project_name=options.get('--project-name'),
verbose=options.get('--verbose'),
use_networking=options.get('--x-networking'),
@ -54,7 +54,8 @@ def project_from_options(base_dir, options):
)
def get_config_path(file_option):
def get_config_path_from_options(options):
file_option = options.get('--file')
if file_option:
return file_option

View File

@ -8,10 +8,12 @@ import sys
from inspect import getdoc
from operator import attrgetter
import yaml
from docker.errors import APIError
from requests.exceptions import ReadTimeout
from .. import __version__
from ..config import config
from ..config import ConfigurationError
from ..config import parse_environment
from ..const import DEFAULT_TIMEOUT
@ -23,6 +25,7 @@ from ..service import BuildError
from ..service import ConvergenceStrategy
from ..service import NeedsBuildError
from .command import friendly_error_message
from .command import get_config_path_from_options
from .command import project_from_options
from .docopt_command import DocoptCommand
from .docopt_command import NoSuchCommand
@ -126,6 +129,7 @@ class TopLevelCommand(DocoptCommand):
Commands:
build Build or rebuild services
config Validate and view the compose file
help Get help on a command
kill Kill containers
logs View output from containers
@ -158,6 +162,10 @@ class TopLevelCommand(DocoptCommand):
handler(None, command_options)
return
if options['COMMAND'] == 'config':
handler(options, command_options)
return
project = project_from_options(self.base_dir, options)
with friendly_error_message():
handler(project, command_options)
@ -183,6 +191,36 @@ class TopLevelCommand(DocoptCommand):
pull=bool(options.get('--pull', False)),
force_rm=bool(options.get('--force-rm', False)))
def config(self, config_options, options):
"""
Validate and view the compose file.
Usage: config [options]
Options:
-q, --quiet Only validate the configuration, don't print
anything.
--services Print the service names, one per line.
"""
config_path = get_config_path_from_options(config_options)
compose_config = config.load(config.find(self.base_dir, config_path))
if options['--quiet']:
return
if options['--services']:
print('\n'.join(service['name'] for service in compose_config))
return
compose_config = dict(
(service.pop('name'), service) for service in compose_config)
print(yaml.dump(
compose_config,
default_flow_style=False,
indent=2,
width=80))
def help(self, project, options):
"""
Get help on a command.

View File

@ -7,6 +7,7 @@ import subprocess
import time
from collections import namedtuple
from operator import attrgetter
from textwrap import dedent
from docker import errors
@ -90,10 +91,11 @@ class CLITestCase(DockerClientTestCase):
self.base_dir = 'tests/fixtures/simple-composefile'
def tearDown(self):
self.project.kill()
self.project.remove_stopped()
for container in self.project.containers(stopped=True, one_off=True):
container.remove(force=True)
if self.base_dir:
self.project.kill()
self.project.remove_stopped()
for container in self.project.containers(stopped=True, one_off=True):
container.remove(force=True)
super(CLITestCase, self).tearDown()
@property
@ -109,13 +111,39 @@ class CLITestCase(DockerClientTestCase):
return wait_on_process(proc, returncode=returncode)
def test_help(self):
old_base_dir = self.base_dir
self.base_dir = 'tests/fixtures/no-composefile'
result = self.dispatch(['help', 'up'], returncode=1)
assert 'Usage: up [options] [SERVICE...]' in result.stderr
# self.project.kill() fails during teardown
# unless there is a composefile.
self.base_dir = old_base_dir
# Prevent tearDown from trying to create a project
self.base_dir = None
def test_config_list_services(self):
result = self.dispatch(['config', '--services'])
assert set(result.stdout.rstrip().split('\n')) == {'simple', 'another'}
def test_config_quiet_with_error(self):
self.base_dir = None
result = self.dispatch([
'-f', 'tests/fixtures/invalid-composefile/invalid.yml',
'config', '-q'
], returncode=1)
assert "'notaservice' doesn't have any configuration" in result.stderr
def test_config_quiet(self):
assert self.dispatch(['config', '-q']).stdout == ''
def test_config_default(self):
result = self.dispatch(['config'])
assert dedent("""
simple:
command: top
image: busybox:latest
""").lstrip() in result.stdout
assert dedent("""
another:
command: top
image: busybox:latest
""").lstrip() in result.stdout
def test_ps(self):
self.project.get_service('simple').create_container()

View File

@ -0,0 +1,5 @@
notaservice: oops
web:
image: 'alpine:edge'