فهرست منبع

Add up flag `--renew-anon-volumes` (shorthand -V)
to avoid reusing the previous container's data

Signed-off-by: Joffrey F <[email protected]>

Joffrey F 7 سال پیش
والد
کامیت
b07091ac5f
4فایلهای تغییر یافته به همراه101 افزوده شده و 12 حذف شده
  1. 11 4
      compose/cli/main.py
  2. 4 2
      compose/project.py
  3. 9 6
      compose/service.py
  4. 77 0
      tests/integration/service_test.py

+ 11 - 4
compose/cli/main.py

@@ -936,7 +936,7 @@ class TopLevelCommand(object):
             --always-recreate-deps     Recreate dependent containers.
                                        Incompatible with --no-recreate.
             --no-recreate              If containers already exist, don't recreate
-                                       them. Incompatible with --force-recreate.
+                                       them. Incompatible with --force-recreate and -V.
             --no-build                 Don't build an image, even if it's missing.
             --no-start                 Don't start the services after creating them.
             --build                    Build images before starting containers.
@@ -945,8 +945,10 @@ class TopLevelCommand(object):
             -t, --timeout TIMEOUT      Use this timeout in seconds for container
                                        shutdown when attached or when containers are
                                        already running. (default: 10)
-            --remove-orphans           Remove containers for services not
-                                       defined in the Compose file
+            -V, --renew-anon-volumes   Recreate anonymous volumes instead of retrieving
+                                       data from the previous containers.
+            --remove-orphans           Remove containers for services not defined
+                                       in the Compose file.
             --exit-code-from SERVICE   Return the exit code of the selected service
                                        container. Implies --abort-on-container-exit.
             --scale SERVICE=NUM        Scale SERVICE to NUM instances. Overrides the
@@ -992,6 +994,7 @@ class TopLevelCommand(object):
                     start=not no_start,
                     always_recreate_deps=always_recreate_deps,
                     reset_container_image=rebuild,
+                    renew_anonymous_volumes=options.get('--renew-anon-volumes')
                 )
 
             try:
@@ -1083,10 +1086,14 @@ def compute_exit_code(exit_value_from, attached_containers, cascade_starter, all
 def convergence_strategy_from_opts(options):
     no_recreate = options['--no-recreate']
     force_recreate = options['--force-recreate']
+    renew_anonymous_volumes = options.get('--renew-anon-volumes')
     if force_recreate and no_recreate:
         raise UserError("--force-recreate and --no-recreate cannot be combined.")
 
-    if force_recreate:
+    if no_recreate and renew_anonymous_volumes:
+        raise UserError('--no-recreate and --renew-anon-volumes cannot be combined.')
+
+    if force_recreate or renew_anonymous_volumes:
         return ConvergenceStrategy.always
 
     if no_recreate:

+ 4 - 2
compose/project.py

@@ -445,7 +445,8 @@ class Project(object):
            rescale=True,
            start=True,
            always_recreate_deps=False,
-           reset_container_image=False):
+           reset_container_image=False,
+           renew_anonymous_volumes=False):
 
         self.initialize()
         if not ignore_orphans:
@@ -474,7 +475,8 @@ class Project(object):
                 rescale=rescale,
                 start=start,
                 project_services=scaled_services,
-                reset_container_image=reset_container_image
+                reset_container_image=reset_container_image,
+                renew_anonymous_volumes=renew_anonymous_volumes,
             )
 
         def get_deps(service):

+ 9 - 6
compose/service.py

@@ -409,7 +409,8 @@ class Service(object):
 
             return containers
 
-    def _execute_convergence_recreate(self, containers, scale, timeout, detached, start):
+    def _execute_convergence_recreate(self, containers, scale, timeout, detached, start,
+                                      renew_anonymous_volumes):
             if scale is not None and len(containers) > scale:
                 self._downscale(containers[scale:], timeout)
                 containers = containers[:scale]
@@ -417,7 +418,7 @@ class Service(object):
             def recreate(container):
                 return self.recreate_container(
                     container, timeout=timeout, attach_logs=not detached,
-                    start_new_container=start
+                    start_new_container=start, renew_anonymous_volumes=renew_anonymous_volumes
                 )
             containers, errors = parallel_execute(
                 containers,
@@ -470,7 +471,7 @@ class Service(object):
     def execute_convergence_plan(self, plan, timeout=None, detached=False,
                                  start=True, scale_override=None,
                                  rescale=True, project_services=None,
-                                 reset_container_image=False):
+                                 reset_container_image=False, renew_anonymous_volumes=False):
         (action, containers) = plan
         scale = scale_override if scale_override is not None else self.scale_num
         containers = sorted(containers, key=attrgetter('number'))
@@ -495,7 +496,8 @@ class Service(object):
                 for c in containers:
                     c.reset_image(img_id)
             return self._execute_convergence_recreate(
-                containers, scale, timeout, detached, start
+                containers, scale, timeout, detached, start,
+                renew_anonymous_volumes,
             )
 
         if action == 'start':
@@ -515,7 +517,8 @@ class Service(object):
 
         raise Exception("Invalid action: {}".format(action))
 
-    def recreate_container(self, container, timeout=None, attach_logs=False, start_new_container=True):
+    def recreate_container(self, container, timeout=None, attach_logs=False, start_new_container=True,
+                           renew_anonymous_volumes=False):
         """Recreate a container.
 
         The original container is renamed to a temporary name so that data
@@ -526,7 +529,7 @@ class Service(object):
         container.stop(timeout=self.stop_timeout(timeout))
         container.rename_to_tmp_name()
         new_container = self.create_container(
-            previous_container=container,
+            previous_container=container if not renew_anonymous_volumes else None,
             number=container.labels.get(LABEL_CONTAINER_NUMBER),
             quiet=True,
         )

+ 77 - 0
tests/integration/service_test.py

@@ -589,6 +589,25 @@ class ServiceTest(DockerClientTestCase):
         assert [mount['Destination'] for mount in new_container.get('Mounts')] == ['/data']
         assert new_container.get_mount('/data')['Source'] == volume_path
 
+    def test_execute_convergence_plan_with_image_declared_volume_renew(self):
+        service = Service(
+            project='composetest',
+            name='db',
+            client=self.client,
+            build={'context': 'tests/fixtures/dockerfile-with-volume'},
+        )
+
+        old_container = create_and_start_container(service)
+        assert [mount['Destination'] for mount in old_container.get('Mounts')] == ['/data']
+        volume_path = old_container.get_mount('/data')['Source']
+
+        new_container, = service.execute_convergence_plan(
+            ConvergencePlan('recreate', [old_container]), renew_anonymous_volumes=True
+        )
+
+        assert [mount['Destination'] for mount in new_container.get('Mounts')] == ['/data']
+        assert new_container.get_mount('/data')['Source'] != volume_path
+
     def test_execute_convergence_plan_when_image_volume_masks_config(self):
         service = self.create_service(
             'db',
@@ -637,6 +656,64 @@ class ServiceTest(DockerClientTestCase):
         )
         assert new_container.get_mount('/data')['Source'] != host_path
 
+    def test_execute_convergence_plan_anonymous_volume_renew(self):
+        service = self.create_service(
+            'db',
+            image='busybox',
+            volumes=[VolumeSpec(None, '/data', 'rw')])
+
+        old_container = create_and_start_container(service)
+        assert (
+            [mount['Destination'] for mount in old_container.get('Mounts')] ==
+            ['/data']
+        )
+        volume_path = old_container.get_mount('/data')['Source']
+
+        new_container, = service.execute_convergence_plan(
+            ConvergencePlan('recreate', [old_container]),
+            renew_anonymous_volumes=True
+        )
+
+        assert (
+            [mount['Destination'] for mount in new_container.get('Mounts')] ==
+            ['/data']
+        )
+        assert new_container.get_mount('/data')['Source'] != volume_path
+
+    def test_execute_convergence_plan_anonymous_volume_recreate_then_renew(self):
+        service = self.create_service(
+            'db',
+            image='busybox',
+            volumes=[VolumeSpec(None, '/data', 'rw')])
+
+        old_container = create_and_start_container(service)
+        assert (
+            [mount['Destination'] for mount in old_container.get('Mounts')] ==
+            ['/data']
+        )
+        volume_path = old_container.get_mount('/data')['Source']
+
+        mid_container, = service.execute_convergence_plan(
+            ConvergencePlan('recreate', [old_container]),
+        )
+
+        assert (
+            [mount['Destination'] for mount in mid_container.get('Mounts')] ==
+            ['/data']
+        )
+        assert mid_container.get_mount('/data')['Source'] == volume_path
+
+        new_container, = service.execute_convergence_plan(
+            ConvergencePlan('recreate', [mid_container]),
+            renew_anonymous_volumes=True
+        )
+
+        assert (
+            [mount['Destination'] for mount in new_container.get('Mounts')] ==
+            ['/data']
+        )
+        assert new_container.get_mount('/data')['Source'] != volume_path
+
     def test_execute_convergence_plan_without_start(self):
         service = self.create_service(
             'db',