Browse Source

up, start, stop, kill and rm all accept a list of services

Aanand Prasad 12 years ago
parent
commit
d3346fa174
3 changed files with 97 additions and 34 deletions
  1. 15 13
      plum/cli/main.py
  2. 42 12
      plum/project.py
  3. 40 9
      tests/project_test.py

+ 15 - 13
plum/cli/main.py

@@ -5,6 +5,7 @@ import re
 from inspect import getdoc
 
 from .. import __version__
+from ..project import NoSuchService
 from .command import Command
 from .formatter import Formatter
 from .log_printer import LogPrinter
@@ -37,6 +38,9 @@ def main():
     except UserError, e:
         log.error(e.msg)
         exit(1)
+    except NoSuchService, e:
+        log.error(e.msg)
+        exit(1)
     except NoSuchCommand, e:
         log.error("No such command: %s", e.command)
         log.error("")
@@ -121,8 +125,6 @@ class TopLevelCommand(Command):
             -d    Detached mode: Run container in the background, print new container name
         """
         service = self.project.get_service(options['SERVICE'])
-        if service is None:
-            raise UserError("No such service: %s" % options['SERVICE'])
         container_options = {
             'command': [options['COMMAND']] + options['ARGS'],
             'tty': not options['-d'],
@@ -146,17 +148,17 @@ class TopLevelCommand(Command):
         """
         Create and start containers
 
-        Usage: up [options]
+        Usage: up [options] [SERVICE...]
 
         Options:
             -d    Detached mode: Run containers in the background, print new container names
         """
         detached = options['-d']
 
-        unstarted = self.project.create_containers()
+        unstarted = self.project.create_containers(service_names=options['SERVICE'])
 
         if not detached:
-            to_attach = self.project.containers() + [c for (s, c) in unstarted]
+            to_attach = self.project.containers(service_names=options['SERVICE']) + [c for (s, c) in unstarted]
             print "Attaching to", list_containers(to_attach)
             log_printer = LogPrinter(to_attach, attach_params={'logs': True})
 
@@ -176,33 +178,33 @@ class TopLevelCommand(Command):
         """
         Start all services
 
-        Usage: start
+        Usage: start [SERVICE...]
         """
-        self.project.start()
+        self.project.start(service_names=options['SERVICE'])
 
     def stop(self, options):
         """
         Stop all services
 
-        Usage: stop
+        Usage: stop [SERVICE...]
         """
-        self.project.stop()
+        self.project.stop(service_names=options['SERVICE'])
 
     def kill(self, options):
         """
         Kill all containers
 
-        Usage: kill
+        Usage: kill [SERVICE...]
         """
-        self.project.kill()
+        self.project.kill(service_names=options['SERVICE'])
 
     def rm(self, options):
         """
         Remove all stopped containers
 
-        Usage: rm
+        Usage: rm [SERVICE...]
         """
-        self.project.remove_stopped()
+        self.project.remove_stopped(service_names=options['SERVICE'])
 
     def logs(self, options):
         """

+ 42 - 12
plum/project.py

@@ -46,17 +46,40 @@ class Project(object):
         return cls.from_dicts(name, dicts, client)
 
     def get_service(self, name):
+        """
+        Retrieve a service by name. Raises NoSuchService
+        if the named service does not exist.
+        """
         for service in self.services:
             if service.name == name:
                 return service
 
-    def create_containers(self):
+        raise NoSuchService(name)
+
+    def get_services(self, service_names=None):
+        """
+        Returns a list of this project's services filtered
+        by the provided list of names, or all services if
+        service_names is None or [].
+
+        Preserves the original order of self.services.
+
+        Raises NoSuchService if any of the named services
+        do not exist.
+        """
+        if service_names is None or len(service_names) == 0:
+            return self.services
+        else:
+            unsorted = [self.get_service(name) for name in service_names]
+            return [s for s in self.services if s in unsorted]
+
+    def create_containers(self, service_names=None):
         """
         Returns a list of (service, container) tuples,
         one for each service with no running containers.
         """
         containers = []
-        for service in self.services:
+        for service in self.get_services(service_names):
             if len(service.containers()) == 0:
                 containers.append((service, service.create_container()))
         return containers
@@ -66,27 +89,34 @@ class Project(object):
             container.kill()
             container.remove()
 
-    def start(self, **options):
-        for service in self.services:
+    def start(self, service_names=None, **options):
+        for service in self.get_services(service_names):
             service.start(**options)
 
-    def stop(self, **options):
-        for service in self.services:
+    def stop(self, service_names=None, **options):
+        for service in self.get_services(service_names):
             service.stop(**options)
 
-    def kill(self, **options):
-        for service in self.services:
+    def kill(self, service_names=None, **options):
+        for service in self.get_services(service_names):
             service.kill(**options)
 
-    def remove_stopped(self, **options):
-        for service in self.services:
+    def remove_stopped(self, service_names=None, **options):
+        for service in self.get_services(service_names):
             service.remove_stopped(**options)
 
-    def containers(self, *args, **kwargs):
+    def containers(self, service_names=None, *args, **kwargs):
         l = []
-        for service in self.services:
+        for service in self.get_services(service_names):
             for container in service.containers(*args, **kwargs):
                 l.append(container)
         return l
 
 
+class NoSuchService(Exception):
+    def __init__(self, name):
+        self.name = name
+        self.msg = "No such service: %s" % self.name
+
+    def __str__(self):
+        return self.msg

+ 40 - 9
tests/project_test.py

@@ -42,11 +42,30 @@ class ProjectTest(DockerClientTestCase):
         project = Project('test', [web], self.client)
         self.assertEqual(project.get_service('web'), web)
 
-    def test_up(self):
+    def test_create_containers(self):
         web = self.create_service('web')
         db = self.create_service('db')
         project = Project('test', [web, db], self.client)
 
+        unstarted = project.create_containers(service_names=['web'])
+        self.assertEqual(len(unstarted), 1)
+        self.assertEqual(unstarted[0][0], web)
+        self.assertEqual(len(web.containers(stopped=True)), 1)
+        self.assertEqual(len(db.containers(stopped=True)), 0)
+
+        unstarted = project.create_containers()
+        self.assertEqual(len(unstarted), 2)
+        self.assertEqual(unstarted[0][0], web)
+        self.assertEqual(unstarted[1][0], db)
+        self.assertEqual(len(web.containers(stopped=True)), 2)
+        self.assertEqual(len(db.containers(stopped=True)), 1)
+
+    def test_up(self):
+        web = self.create_service('web')
+        db = self.create_service('db')
+        other = self.create_service('other')
+        project = Project('test', [web, db, other], self.client)
+
         web.create_container()
 
         self.assertEqual(len(web.containers()), 0)
@@ -54,7 +73,7 @@ class ProjectTest(DockerClientTestCase):
         self.assertEqual(len(web.containers(stopped=True)), 1)
         self.assertEqual(len(db.containers(stopped=True)), 0)
 
-        unstarted = project.create_containers()
+        unstarted = project.create_containers(service_names=['web', 'db'])
         self.assertEqual(len(unstarted), 2)
         self.assertEqual(unstarted[0][0], web)
         self.assertEqual(unstarted[1][0], db)
@@ -71,7 +90,7 @@ class ProjectTest(DockerClientTestCase):
         self.assertEqual(len(web.containers(stopped=True)), 1)
         self.assertEqual(len(db.containers(stopped=True)), 0)
 
-    def test_start_stop(self):
+    def test_start_stop_kill_remove(self):
         web = self.create_service('web')
         db = self.create_service('db')
         project = Project('test', [web, db], self.client)
@@ -81,13 +100,25 @@ class ProjectTest(DockerClientTestCase):
         self.assertEqual(len(web.containers()), 0)
         self.assertEqual(len(db.containers()), 0)
 
-        web.create_container()
+        web_container_1 = web.create_container()
+        web_container_2 = web.create_container()
+        db_container = db.create_container()
+
+        project.start(service_names=['web'])
+        self.assertEqual(set(c.name for c in project.containers()), set([web_container_1.name, web_container_2.name]))
+
         project.start()
+        self.assertEqual(set(c.name for c in project.containers()), set([web_container_1.name, web_container_2.name, db_container.name]))
 
-        self.assertEqual(len(web.containers()), 1)
-        self.assertEqual(len(db.containers()), 0)
+        project.stop(service_names=['web'], timeout=1)
+        self.assertEqual(set(c.name for c in project.containers()), set([db_container.name]))
 
-        project.stop(timeout=1)
+        project.kill(service_names=['db'])
+        self.assertEqual(len(project.containers()), 0)
+        self.assertEqual(len(project.containers(stopped=True)), 3)
 
-        self.assertEqual(len(web.containers()), 0)
-        self.assertEqual(len(db.containers()), 0)
+        project.remove_stopped(service_names=['web'])
+        self.assertEqual(len(project.containers(stopped=True)), 1)
+
+        project.remove_stopped()
+        self.assertEqual(len(project.containers(stopped=True)), 0)