mirror of https://github.com/docker/compose.git
Merge pull request #2407 from dnephin/config_command
New `docker-compose config` command
This commit is contained in:
commit
82dfd465a2
|
@ -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
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
notaservice: oops
|
||||
|
||||
web:
|
||||
image: 'alpine:edge'
|
Loading…
Reference in New Issue