Browse Source

Prevent `docker-compose scale` to be used with a v2.2 config file

Signed-off-by: Joffrey F <[email protected]>
Joffrey F 8 years ago
parent
commit
ef40e3c6b9

+ 7 - 0
compose/cli/main.py

@@ -26,6 +26,7 @@ from ..config import resolve_build_args
 from ..config.environment import Environment
 from ..config.serialize import serialize_config
 from ..config.types import VolumeSpec
+from ..const import COMPOSEFILE_V2_2 as V2_2
 from ..const import IS_WINDOWS_PLATFORM
 from ..errors import StreamParseError
 from ..progress_stream import StreamOutputError
@@ -771,6 +772,12 @@ class TopLevelCommand(object):
         """
         timeout = timeout_from_opts(options)
 
+        if self.project.config_version == V2_2:
+            raise UserError(
+                'The scale command is incompatible with the v2.2 format. '
+                'Use the up command with the --scale flag instead.'
+            )
+
         for service_name, num in parse_scale_args(options['SERVICE=NUM']).items():
             self.project.get_service(service_name).scale(num, timeout=timeout)
 

+ 3 - 2
compose/project.py

@@ -57,12 +57,13 @@ class Project(object):
     """
     A collection of services.
     """
-    def __init__(self, name, services, client, networks=None, volumes=None):
+    def __init__(self, name, services, client, networks=None, volumes=None, config_version=None):
         self.name = name
         self.services = services
         self.client = client
         self.volumes = volumes or ProjectVolumes({})
         self.networks = networks or ProjectNetworks({}, False)
+        self.config_version = config_version
 
     def labels(self, one_off=OneOffFilter.exclude):
         labels = ['{0}={1}'.format(LABEL_PROJECT, self.name)]
@@ -82,7 +83,7 @@ class Project(object):
             networks,
             use_networking)
         volumes = ProjectVolumes.from_config(name, config_data, client)
-        project = cls(name, [], client, project_networks, volumes)
+        project = cls(name, [], client, project_networks, volumes, config_data.version)
 
         for service_dict in config_data.services:
             service_dict = dict(service_dict)

+ 7 - 6
compose/service.py

@@ -383,8 +383,8 @@ class Service(object):
                 lambda n: self.get_container_name(n),
                 "Creating"
             )
-            if errors:
-                raise OperationFailedError(errors.values()[0])
+            for error in errors.values():
+                raise OperationFailedError(error)
 
             return containers
 
@@ -404,8 +404,9 @@ class Service(object):
                 lambda c: c.name,
                 "Recreating"
             )
-            if errors:
-                raise OperationFailedError(errors.values()[0])
+            for error in errors.values():
+                raise OperationFailedError(error)
+
             if len(containers) < scale:
                 containers.extend(self._execute_convergence_create(
                     scale - len(containers), detached, start
@@ -424,8 +425,8 @@ class Service(object):
                     "Starting"
                 )
 
-                if errors:
-                    raise OperationFailedError(errors.values()[0])
+                for error in errors.values():
+                    raise OperationFailedError(error)
 
             if len(containers) < scale:
                 containers.extend(self._execute_convergence_create(

+ 31 - 5
tests/acceptance/cli_test.py

@@ -1866,9 +1866,27 @@ class CLITestCase(DockerClientTestCase):
         self.assertEqual(len(project.get_service('simple').containers()), 0)
         self.assertEqual(len(project.get_service('another').containers()), 0)
 
-    def test_up_scale(self):
+    def test_scale_v2_2(self):
+        self.base_dir = 'tests/fixtures/scale'
+        result = self.dispatch(['scale', 'web=1'], returncode=1)
+        assert 'incompatible with the v2.2 format' in result.stderr
+
+    def test_up_scale_scale_up(self):
+        self.base_dir = 'tests/fixtures/scale'
+        project = self.project
+
+        self.dispatch(['up', '-d'])
+        assert len(project.get_service('web').containers()) == 2
+        assert len(project.get_service('db').containers()) == 1
+
+        self.dispatch(['up', '-d', '--scale', 'web=3'])
+        assert len(project.get_service('web').containers()) == 3
+        assert len(project.get_service('db').containers()) == 1
+
+    def test_up_scale_scale_down(self):
         self.base_dir = 'tests/fixtures/scale'
         project = self.project
+
         self.dispatch(['up', '-d'])
         assert len(project.get_service('web').containers()) == 2
         assert len(project.get_service('db').containers()) == 1
@@ -1877,13 +1895,21 @@ class CLITestCase(DockerClientTestCase):
         assert len(project.get_service('web').containers()) == 1
         assert len(project.get_service('db').containers()) == 1
 
-        self.dispatch(['up', '-d', '--scale', 'web=3'])
+    def test_up_scale_reset(self):
+        self.base_dir = 'tests/fixtures/scale'
+        project = self.project
+
+        self.dispatch(['up', '-d', '--scale', 'web=3', '--scale', 'db=3'])
         assert len(project.get_service('web').containers()) == 3
+        assert len(project.get_service('db').containers()) == 3
+
+        self.dispatch(['up', '-d'])
+        assert len(project.get_service('web').containers()) == 2
         assert len(project.get_service('db').containers()) == 1
 
-        self.dispatch(['up', '-d', '--scale', 'web=1', '--scale', 'db=2'])
-        assert len(project.get_service('web').containers()) == 1
-        assert len(project.get_service('db').containers()) == 2
+    def test_up_scale_to_zero(self):
+        self.base_dir = 'tests/fixtures/scale'
+        project = self.project
 
         self.dispatch(['up', '-d'])
         assert len(project.get_service('web').containers()) == 2

+ 3 - 3
tests/integration/project_test.py

@@ -565,12 +565,12 @@ class ProjectTest(DockerClientTestCase):
         self.assertEqual(len(service.containers()), 3)
         project.up()
         service = project.get_service('web')
-        self.assertEqual(len(service.containers()), 3)
+        self.assertEqual(len(service.containers()), 1)
         service.scale(1)
         self.assertEqual(len(service.containers()), 1)
-        project.up()
+        project.up(scale_override={'web': 3})
         service = project.get_service('web')
-        self.assertEqual(len(service.containers()), 1)
+        self.assertEqual(len(service.containers()), 3)
         # does scale=0 ,makes any sense? after recreating at least 1 container is running
         service.scale(0)
         project.up()

+ 10 - 8
tests/integration/service_test.py

@@ -26,6 +26,7 @@ from compose.const import LABEL_PROJECT
 from compose.const import LABEL_SERVICE
 from compose.const import LABEL_VERSION
 from compose.container import Container
+from compose.errors import OperationFailedError
 from compose.project import OneOffFilter
 from compose.service import ConvergencePlan
 from compose.service import ConvergenceStrategy
@@ -777,15 +778,15 @@ class ServiceTest(DockerClientTestCase):
                 message="testing",
                 response={},
                 explanation="Boom")):
-
             with mock.patch('sys.stderr', new_callable=StringIO) as mock_stderr:
-                service.scale(3)
+                with pytest.raises(OperationFailedError):
+                    service.scale(3)
 
-        self.assertEqual(len(service.containers()), 1)
-        self.assertTrue(service.containers()[0].is_running)
-        self.assertIn(
-            "ERROR: for composetest_web_2  Cannot create container for service web: Boom",
-            mock_stderr.getvalue()
+        assert len(service.containers()) == 1
+        assert service.containers()[0].is_running
+        assert (
+            "ERROR: for composetest_web_2  Cannot create container for service"
+            " web: Boom" in mock_stderr.getvalue()
         )
 
     def test_scale_with_unexpected_exception(self):
@@ -837,7 +838,8 @@ class ServiceTest(DockerClientTestCase):
         service = self.create_service('app', container_name='custom-container')
         self.assertEqual(service.custom_container_name, 'custom-container')
 
-        service.scale(3)
+        with pytest.raises(OperationFailedError):
+            service.scale(3)
 
         captured_output = mock_log.warn.call_args[0][0]