소스 검색

Add docker-compose config subcommand.

Signed-off-by: Daniel Nephin <[email protected]>
Daniel Nephin 10 년 전
부모
커밋
437f3f8adb
4개의 변경된 파일82개의 추가작업 그리고 10개의 파일을 삭제
  1. 3 2
      compose/cli/command.py
  2. 38 0
      compose/cli/main.py
  3. 36 8
      tests/acceptance/cli_test.py
  4. 5 0
      tests/fixtures/invalid-composefile/invalid.yml

+ 3 - 2
compose/cli/command.py

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

+ 38 - 0
compose/cli/main.py

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

+ 36 - 8
tests/acceptance/cli_test.py

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

+ 5 - 0
tests/fixtures/invalid-composefile/invalid.yml

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