Browse Source

Add an acceptance test and docs for the down subcommand

Signed-off-by: Daniel Nephin <[email protected]>
Daniel Nephin 9 years ago
parent
commit
c64af0a459

+ 1 - 1
compose/cli/main.py

@@ -247,7 +247,7 @@ class TopLevelCommand(DocoptCommand):
     def down(self, project, options):
         """
         Stop containers and remove containers, networks, volumes, and images
-        created by `up`.
+        created by `up`. Only containers and networks are removed by default.
 
         Usage: down [options]
 

+ 1 - 1
compose/parallel.py

@@ -24,7 +24,7 @@ def parallel_execute(objects, func, index_func, msg):
     object we give it.
     """
     objects = list(objects)
-    stream = get_output_stream(sys.stdout)
+    stream = get_output_stream(sys.stderr)
     writer = ParallelStreamWriter(stream, msg)
 
     for obj in objects:

+ 2 - 1
compose/project.py

@@ -272,7 +272,7 @@ class Project(object):
 
     def down(self, remove_image_type, include_volumes):
         self.stop()
-        self.remove_stopped()
+        self.remove_stopped(v=include_volumes)
         self.remove_network()
 
         if include_volumes:
@@ -441,6 +441,7 @@ class Project(object):
             return
         network = self.get_network()
         if network:
+            log.info("Removing network %s", self.default_network_name)
             self.client.remove_network(network['Id'])
 
     def uses_default_network(self):

+ 1 - 0
compose/service.py

@@ -686,6 +686,7 @@ class Service(object):
         if image_type == ImageType.local and self.options.get('image'):
             return False
 
+        log.info("Removing image %s", self.image_name)
         try:
             self.client.remove_image(self.image_name)
             return True

+ 9 - 0
compose/volume.py

@@ -1,9 +1,14 @@
 from __future__ import absolute_import
 from __future__ import unicode_literals
 
+import logging
+
 from docker.errors import NotFound
 
 
+log = logging.getLogger(__name__)
+
+
 class Volume(object):
     def __init__(self, client, project, name, driver=None, driver_opts=None,
                  external_name=None):
@@ -20,6 +25,10 @@ class Volume(object):
         )
 
     def remove(self):
+        if self.external:
+            log.info("Volume %s is external, skipping", self.full_name)
+            return
+        log.info("Removing volume %s", self.full_name)
         return self.client.remove_volume(self.full_name)
 
     def inspect(self):

+ 1 - 2
docs/index.md

@@ -154,8 +154,7 @@ environments in just a few commands:
 
     $ docker-compose up -d
     $ ./run_tests
-    $ docker-compose stop
-    $ docker-compose rm -f
+    $ docker-compose down
 
 ### Single host deployments
 

+ 26 - 0
docs/reference/down.md

@@ -0,0 +1,26 @@
+<!--[metadata]>
++++
+title = "down"
+description = "down"
+keywords = ["fig, composition, compose, docker, orchestration, cli,  down"]
+[menu.main]
+identifier="build.compose"
+parent = "smn_compose_cli"
++++
+<![end-metadata]-->
+
+# down
+
+```
+Stop containers and remove containers, networks, volumes, and images
+created by `up`. Only containers and networks are removed by default.
+
+Usage: down [options]
+
+Options:
+    --rmi type      Remove images, type may be one of: 'all' to remove
+                    all images, or 'local' to remove only images that
+                    don't have an custom name set by the `image` field
+    -v, --volumes   Remove data volumes
+
+```

+ 5 - 0
docs/reference/index.md

@@ -14,10 +14,14 @@ parent = "smn_compose_ref"
 The following pages describe the usage information for the [docker-compose](docker-compose.md) subcommands. You can also see this information by running `docker-compose [SUBCOMMAND] --help` from the command line.
 
 * [build](build.md)
+* [config](config.md)
+* [create](create.md)
+* [down](down.md)
 * [events](events.md)
 * [help](help.md)
 * [kill](kill.md)
 * [logs](logs.md)
+* [pause](pause.md)
 * [port](port.md)
 * [ps](ps.md)
 * [pull](pull.md)
@@ -27,6 +31,7 @@ The following pages describe the usage information for the [docker-compose](dock
 * [scale](scale.md)
 * [start](start.md)
 * [stop](stop.md)
+* [unpause](unpause.md)
 * [up](up.md)
 
 ## Where to go next

+ 10 - 2
tests/acceptance/cli_test.py

@@ -319,8 +319,16 @@ class CLITestCase(DockerClientTestCase):
         assert '--rmi flag must be' in result.stderr
 
     def test_down(self):
-        result = self.dispatch(['down'])
-        # TODO:
+        self.base_dir = 'tests/fixtures/shutdown'
+        self.dispatch(['up', '-d'])
+        wait_on_condition(ContainerCountCondition(self.project, 1))
+
+        result = self.dispatch(['down', '--rmi=local', '--volumes'])
+        assert 'Stopping shutdown_web_1' in result.stderr
+        assert 'Removing shutdown_web_1' in result.stderr
+        assert 'Removing volume shutdown_data' in result.stderr
+        assert 'Removing image shutdown_web' in result.stderr
+        assert 'Removing network shutdown_default' in result.stderr
 
     def test_up_detached(self):
         self.dispatch(['up', '-d'])

+ 4 - 0
tests/fixtures/shutdown/Dockerfile

@@ -0,0 +1,4 @@
+
+FROM busybox:latest
+RUN  echo something
+CMD  top

+ 10 - 0
tests/fixtures/shutdown/docker-compose.yml

@@ -0,0 +1,10 @@
+
+version: 2
+
+volumes:
+  data:
+    driver: local
+
+services:
+  web:
+    build: .

+ 6 - 6
tests/integration/service_test.py

@@ -616,13 +616,13 @@ class ServiceTest(DockerClientTestCase):
         service.create_container(number=next_number)
         service.create_container(number=next_number + 1)
 
-        with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+        with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr:
             service.scale(2)
         for container in service.containers():
             self.assertTrue(container.is_running)
             self.assertTrue(container.number in valid_numbers)
 
-        captured_output = mock_stdout.getvalue()
+        captured_output = mock_stderr.getvalue()
         self.assertNotIn('Creating', captured_output)
         self.assertIn('Starting', captured_output)
 
@@ -639,14 +639,14 @@ class ServiceTest(DockerClientTestCase):
         for container in service.containers():
             self.assertFalse(container.is_running)
 
-        with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+        with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr:
             service.scale(2)
 
         self.assertEqual(len(service.containers()), 2)
         for container in service.containers():
             self.assertTrue(container.is_running)
 
-        captured_output = mock_stdout.getvalue()
+        captured_output = mock_stderr.getvalue()
         self.assertIn('Creating', captured_output)
         self.assertIn('Starting', captured_output)
 
@@ -665,12 +665,12 @@ class ServiceTest(DockerClientTestCase):
                 response={},
                 explanation="Boom")):
 
-            with mock.patch('sys.stdout', new_callable=StringIO) as mock_stdout:
+            with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr:
                 service.scale(3)
 
         self.assertEqual(len(service.containers()), 1)
         self.assertTrue(service.containers()[0].is_running)
-        self.assertIn("ERROR: for 2  Boom", mock_stdout.getvalue())
+        self.assertIn("ERROR: for 2  Boom", mock_stderr.getvalue())
 
     def test_scale_with_unexpected_exception(self):
         """Test that when scaling if the API returns an error, that is not of type

+ 26 - 0
tests/unit/volume_test.py

@@ -0,0 +1,26 @@
+from __future__ import absolute_import
+from __future__ import unicode_literals
+
+import docker
+import pytest
+
+from compose import volume
+from tests import mock
+
+
[email protected]
+def mock_client():
+    return mock.create_autospec(docker.Client)
+
+
+class TestVolume(object):
+
+    def test_remove_local_volume(self, mock_client):
+        vol = volume.Volume(mock_client, 'foo', 'project')
+        vol.remove()
+        mock_client.remove_volume.assert_called_once_with('foo_project')
+
+    def test_remove_external_volume(self, mock_client):
+        vol = volume.Volume(mock_client, 'foo', 'project', external_name='data')
+        vol.remove()
+        assert not mock_client.remove_volume.called