Pārlūkot izejas kodu

Refactor service to add a container object

Ben Firshman 12 gadi atpakaļ
vecāks
revīzija
a5fc880d10
6 mainītis faili ar 169 papildinājumiem un 57 dzēšanām
  1. 2 2
      plum/cli/main.py
  2. 86 0
      plum/container.py
  3. 19 27
      plum/service.py
  4. 36 0
      tests/container_test.py
  5. 4 4
      tests/service_collection_test.py
  6. 22 24
      tests/service_test.py

+ 2 - 2
plum/cli/main.py

@@ -78,7 +78,7 @@ class TopLevelCommand(Command):
         Usage: ps
         """
         for container in self._get_containers(all=False):
-            print get_container_name(container)
+            print container.name
 
     def run(self, options):
         """
@@ -126,4 +126,4 @@ class TopLevelCommand(Command):
         LogPrinter(client=self.client).attach(containers)
 
     def _get_containers(self, all):
-        return [c for s in self.service_collection for c in s.get_containers(all=all)]
+        return [c for s in self.service_collection for c in s.containers(all=all)]

+ 86 - 0
plum/container.py

@@ -0,0 +1,86 @@
+
+
+class Container(object):
+    """
+    Represents a Docker container, constructed from the output of 
+    GET /containers/:id:/json.
+    """
+    def __init__(self, client, dictionary, has_been_inspected=False):
+        self.client = client
+        self.dictionary = dictionary
+        self.has_been_inspected = has_been_inspected
+
+    @classmethod
+    def from_ps(cls, client, dictionary, **kwargs):
+        """
+        Construct a container object from the output of GET /containers/json.
+        """
+        new_dictionary = {
+            'ID': dictionary['Id'],
+            'Image': dictionary['Image'],
+        }
+        for name in dictionary.get('Names', []):
+            if len(name.split('/')) == 2:
+                new_dictionary['Name'] = name
+        return cls(client, new_dictionary, **kwargs)
+
+    @classmethod
+    def from_id(cls, client, id):
+        return cls(client, client.inspect_container(id))
+
+    @classmethod
+    def create(cls, client, **options):
+        response = client.create_container(**options)
+        return cls.from_id(client, response['Id'])
+
+    @property
+    def id(self):
+        return self.dictionary['ID']
+
+    @property
+    def name(self):
+        return self.dictionary['Name']
+
+    @property
+    def environment(self):
+        self.inspect_if_not_inspected()
+        out = {}
+        for var in self.dictionary.get('Config', {}).get('Env', []):
+            k, v = var.split('=', 1)
+            out[k] = v
+        return out
+
+    def start(self, **options):
+        return self.client.start(self.id, **options)
+
+    def stop(self):
+        return self.client.stop(self.id)
+
+    def kill(self):
+        return self.client.kill(self.id)
+
+    def remove(self):
+        return self.client.remove_container(self.id)
+
+    def inspect_if_not_inspected(self):
+        if not self.has_been_inspected:
+            self.inspect()
+
+    def wait(self):
+        return self.client.wait(self.id)
+
+    def logs(self, *args, **kwargs):
+        return self.client.logs(self.id, *args, **kwargs)
+
+    def inspect(self):
+        self.dictionary = self.client.inspect_container(self.id)
+        return self.dictionary
+
+    def links(self):
+        links = []
+        for container in self.client.containers():
+            for name in container['Names']:
+                bits = name.split('/')
+                if len(bits) > 2 and bits[1] == self.name[1:]:
+                    links.append(bits[2])
+        return links

+ 19 - 27
plum/service.py

@@ -1,6 +1,7 @@
 from docker.client import APIError
 import logging
 import re
+from .container import Container
 
 log = logging.getLogger(__name__)
 
@@ -21,28 +22,26 @@ class Service(object):
         self.links = links or []
         self.options = options
 
-    @property
-    def containers(self):
-        return list(self.get_containers(all=True))
-
-    def get_containers(self, all):
+    def containers(self, all=False):
+        l = []
         for container in self.client.containers(all=all):
             name = get_container_name(container)
             if is_valid_name(name) and parse_name(name)[0] == self.name:
-                yield container
+                l.append(Container.from_ps(self.client, container))
+        return l
 
     def start(self):
-        if len(self.containers) == 0:
+        if len(self.containers()) == 0:
             return self.start_container()
 
     def stop(self):
         self.scale(0)
 
     def scale(self, num):
-        while len(self.containers) < num:
+        while len(self.containers()) < num:
             self.start_container()
 
-        while len(self.containers) > num:
+        while len(self.containers()) > num:
             self.stop_container()
 
     def create_container(self, **override_options):
@@ -52,12 +51,12 @@ class Service(object):
         """
         container_options = self._get_container_options(override_options)
         try:
-            return self.client.create_container(**container_options)
+            return Container.create(self.client, **container_options)
         except APIError, e:
             if e.response.status_code == 404 and e.explanation and 'No such image' in e.explanation:
                 log.info('Pulling image %s...' % container_options['image'])
                 self.client.pull(container_options['image'])
-                return self.client.create_container(**container_options)
+                return Container.create(self.client, **container_options)
             raise
 
     def start_container(self, container=None, **override_options):
@@ -71,39 +70,32 @@ class Service(object):
                 port_bindings[int(internal_port)] = int(external_port)
             else:
                 port_bindings[int(port)] = None
-        log.info("Starting %s..." % container['Id'])
-        self.client.start(
-            container['Id'],
+        log.info("Starting %s..." % container.name)
+        container.start(
             links=self._get_links(),
             port_bindings=port_bindings,
         )
         return container
 
     def stop_container(self):
-        container = self.containers[-1]
-        log.info("Stopping and removing %s..." % get_container_name(container))
-        self.client.kill(container)
-        self.client.remove_container(container)
+        container = self.containers()[-1]
+        log.info("Stopping and removing %s..." % container.name)
+        container.kill()
+        container.remove()
 
     def next_container_number(self):
-        numbers = [parse_name(get_container_name(c))[1] for c in self.containers]
+        numbers = [parse_name(c.name)[1] for c in self.containers(all=True)]
 
         if len(numbers) == 0:
             return 1
         else:
             return max(numbers) + 1
 
-    def get_names(self):
-        return [get_container_name(c) for c in self.containers]
-
-    def inspect(self):
-        return [self.client.inspect_container(c['Id']) for c in self.containers]
-
     def _get_links(self):
         links = {}
         for service in self.links:
-            for name in service.get_names():
-                links[name] = name
+            for container in service.containers():
+                links[container.name[1:]] = container.name[1:]
         return links
 
     def _get_container_options(self, override_options):

+ 36 - 0
tests/container_test.py

@@ -0,0 +1,36 @@
+from .testcases import DockerClientTestCase
+from plum.container import Container
+
+class ContainerTest(DockerClientTestCase):
+    def test_from_ps(self):
+        container = Container.from_ps(self.client, {
+            "Id":"abc",
+            "Image":"ubuntu:12.04",
+            "Command":"sleep 300",
+            "Created":1387384730,
+            "Status":"Up 8 seconds",
+            "Ports":None,
+            "SizeRw":0,
+            "SizeRootFs":0,
+            "Names":["/db_1"]
+        }, has_been_inspected=True)
+        self.assertEqual(container.dictionary, {
+            "ID": "abc",
+            "Image":"ubuntu:12.04",
+            "Name": "/db_1",
+        })
+
+    def test_environment(self):
+        container = Container(self.client, {
+            'ID': 'abc',
+            'Config': {
+                'Env': [
+                    'FOO=BAR',
+                    'BAZ=DOGE',
+                ]
+            }
+        }, has_been_inspected=True)
+        self.assertEqual(container.environment, {
+            'FOO': 'BAR',
+            'BAZ': 'DOGE',
+        })

+ 4 - 4
tests/service_collection_test.py

@@ -50,13 +50,13 @@ class ServiceCollectionTest(DockerClientTestCase):
 
         collection.start()
 
-        self.assertEqual(len(collection[0].containers), 1)
-        self.assertEqual(len(collection[1].containers), 1)
+        self.assertEqual(len(collection[0].containers()), 1)
+        self.assertEqual(len(collection[1].containers()), 1)
 
         collection.stop()
 
-        self.assertEqual(len(collection[0].containers), 0)
-        self.assertEqual(len(collection[1].containers), 0)
+        self.assertEqual(len(collection[0].containers()), 0)
+        self.assertEqual(len(collection[1].containers()), 0)
 
 
 

+ 22 - 24
tests/service_test.py

@@ -24,57 +24,57 @@ class ServiceTest(DockerClientTestCase):
 
         foo.start()
 
-        self.assertEqual(len(foo.containers), 1)
-        self.assertEqual(foo.containers[0]['Names'], ['/foo_1'])
-        self.assertEqual(len(bar.containers), 0)
+        self.assertEqual(len(foo.containers()), 1)
+        self.assertEqual(foo.containers()[0].name, '/foo_1')
+        self.assertEqual(len(bar.containers()), 0)
 
         bar.scale(2)
 
-        self.assertEqual(len(foo.containers), 1)
-        self.assertEqual(len(bar.containers), 2)
+        self.assertEqual(len(foo.containers()), 1)
+        self.assertEqual(len(bar.containers()), 2)
 
-        names = [c['Names'] for c in bar.containers]
-        self.assertIn(['/bar_1'], names)
-        self.assertIn(['/bar_2'], names)
+        names = [c.name for c in bar.containers()]
+        self.assertIn('/bar_1', names)
+        self.assertIn('/bar_2', names)
 
     def test_up_scale_down(self):
         service = self.create_service('scaling_test')
-        self.assertEqual(len(service.containers), 0)
+        self.assertEqual(len(service.containers()), 0)
 
         service.start()
-        self.assertEqual(len(service.containers), 1)
+        self.assertEqual(len(service.containers()), 1)
 
         service.start()
-        self.assertEqual(len(service.containers), 1)
+        self.assertEqual(len(service.containers()), 1)
 
         service.scale(2)
-        self.assertEqual(len(service.containers), 2)
+        self.assertEqual(len(service.containers()), 2)
 
         service.scale(1)
-        self.assertEqual(len(service.containers), 1)
+        self.assertEqual(len(service.containers()), 1)
 
         service.stop()
-        self.assertEqual(len(service.containers), 0)
+        self.assertEqual(len(service.containers()), 0)
 
         service.stop()
-        self.assertEqual(len(service.containers), 0)
+        self.assertEqual(len(service.containers()), 0)
 
     def test_start_container_passes_through_options(self):
         db = self.create_service('db')
         db.start_container(environment={'FOO': 'BAR'})
-        self.assertEqual(db.inspect()[0]['Config']['Env'], ['FOO=BAR'])
+        self.assertEqual(db.containers()[0].environment['FOO'], 'BAR')
 
     def test_start_container_inherits_options_from_constructor(self):
         db = self.create_service('db', environment={'FOO': 'BAR'})
         db.start_container()
-        self.assertEqual(db.inspect()[0]['Config']['Env'], ['FOO=BAR'])
+        self.assertEqual(db.containers()[0].environment['FOO'], 'BAR')
 
     def test_start_container_creates_links(self):
         db = self.create_service('db')
         web = self.create_service('web', links=[db])
         db.start_container()
         web.start_container()
-        self.assertIn('/web_1/db_1', db.containers[0]['Names'])
+        self.assertIn('db_1', web.containers()[0].links())
         db.stop()
         web.stop()
 
@@ -85,20 +85,18 @@ class ServiceTest(DockerClientTestCase):
             build='tests/fixtures/simple-dockerfile',
         )
         container = service.start()
-        self.client.wait(container)
-        self.assertIn('success', self.client.logs(container))
+        container.wait()
+        self.assertIn('success', container.logs())
 
     def test_start_container_creates_ports(self):
         service = self.create_service('web', ports=[8000])
-        service.start_container()
-        container = service.inspect()[0]
+        container = service.start_container().inspect()
         self.assertIn('8000/tcp', container['HostConfig']['PortBindings'])
         self.assertNotEqual(container['HostConfig']['PortBindings']['8000/tcp'][0]['HostPort'], '8000')
 
     def test_start_container_creates_fixed_external_ports(self):
         service = self.create_service('web', ports=['8000:8000'])
-        service.start_container()
-        container = service.inspect()[0]
+        container = service.start_container().inspect()
         self.assertIn('8000/tcp', container['HostConfig']['PortBindings'])
         self.assertEqual(container['HostConfig']['PortBindings']['8000/tcp'][0]['HostPort'], '8000')