소스 검색

Implement --filter flag for docker-compose config --services.

Fix #1498
Signed-off-by: Svyatoslav Ilinskiy <[email protected]>
Svyatoslav Ilinskiy 8 년 전
부모
커밋
21e312e402
3개의 변경된 파일91개의 추가작업 그리고 2개의 파일을 삭제
  1. 59 2
      compose/cli/main.py
  2. 26 0
      tests/acceptance/cli_test.py
  3. 6 0
      tests/fixtures/config-services-filter/docker-compose.yml

+ 59 - 2
compose/cli/main.py

@@ -287,7 +287,7 @@ class TopLevelCommand(object):
         """
         Validate and view the Compose file.
 
-        Usage: config [options]
+        Usage: config [options] [-f KEY=VAL...]
 
         Options:
             --resolve-image-digests  Pin image tags to digests.
@@ -295,6 +295,7 @@ class TopLevelCommand(object):
                                      anything.
             --services               Print the service names, one per line.
             --volumes                Print the volume names, one per line.
+            -f, --filter KEY=VAL     Filter containers by a property (can be used multiple times)
 
         """
 
@@ -309,7 +310,15 @@ class TopLevelCommand(object):
             return
 
         if options['--services']:
-            print('\n'.join(service['name'] for service in compose_config.services))
+            filters = build_filters(options.get('--filter'))
+            if filters:
+                if not self.project:
+                    self.project = project_from_options('.', config_options)
+                services = filter_services(filters, self.project.services, self.project)
+            else:
+                services = [service['name'] for service in compose_config.services]
+
+            print('\n'.join(services))
             return
 
         if options['--volumes']:
@@ -1312,3 +1321,51 @@ def build_exec_command(options, container_id, command):
     args += [container_id]
     args += command
     return args
+
+
+def has_container_with_state(containers, state):
+    for container in containers:
+        states = {
+            'running': container.is_running,
+            'stopped': not container.is_running,
+            'paused': container.is_paused,
+        }
+        if state not in states:
+            raise UserError("Invalid state: %s" % state)
+        if states[state]:
+            return True
+    return False
+
+
+def filter_services(filters, services, project):
+    def should_include(service):
+        for f in filters:
+            if f == 'status':
+                containers = project.containers([service.name], stopped=True)
+                for status in filters[f]:
+                    if not has_container_with_state(containers, status):
+                        return False
+            elif f == 'option':
+                for option in filters[f]:
+                    if option == 'image' or option == 'build':
+                        if option not in service.options:
+                            return False
+                    else:
+                        raise UserError("Invalid option: %s" % option)
+            else:
+                raise UserError("Invalid filter: %s" % f)
+        return True
+
+    return [s.name for s in services if should_include(s)]
+
+
+def build_filters(args):
+    filters = {}
+    for arg in args:
+        if '=' not in arg:
+            raise UserError("Arguments to --filter should be in form KEY=VAL")
+        key, val = arg.split('=', 1)
+        if key not in filters:
+            filters[key] = []
+        filters[key].append(val)
+    return filters

+ 26 - 0
tests/acceptance/cli_test.py

@@ -440,6 +440,32 @@ class CLITestCase(DockerClientTestCase):
             },
         }
 
+    def test_config_services_filter_option(self):
+        self.base_dir = 'tests/fixtures/config-services-filter'
+        image = self.dispatch(['config', '--services', '--filter', 'option=image'])
+        build = self.dispatch(['config', '--services', '--filter', 'option=build'])
+
+        self.assertIn('with_build', build.stdout)
+        self.assertNotIn('with_build', image.stdout)
+        self.assertIn('with_image', image.stdout)
+        self.assertNotIn('with_image', build.stdout)
+
+    def test_config_services_filter_status(self):
+        self.base_dir = 'tests/fixtures/config-services-filter'
+        self.dispatch(['up', '-d'])
+        self.dispatch(['pause', 'with_image'])
+        paused = self.dispatch(['config', '--services', '--filter', 'status=paused'])
+        stopped = self.dispatch(['config', '--services', '--filter', 'status=stopped'])
+        running = self.dispatch(['config', '--services', '--filter', 'status=running',
+                                 '--filter', 'option=build'])
+
+        self.assertNotIn('with_build', stopped.stdout)
+        self.assertNotIn('with_image', stopped.stdout)
+        self.assertNotIn('with_build', paused.stdout)
+        self.assertIn('with_image', paused.stdout)
+        self.assertIn('with_build', running.stdout)
+        self.assertNotIn('with_image', running.stdout)
+
     def test_ps(self):
         self.project.get_service('simple').create_container()
         result = self.dispatch(['ps'])

+ 6 - 0
tests/fixtures/config-services-filter/docker-compose.yml

@@ -0,0 +1,6 @@
+with_image:
+  image: busybox:latest
+  command: top
+with_build:
+  build: ../build-ctx/
+  command: top