浏览代码

Fix "Duplicate volume mount" error when config has trailing slashes

When an image declares a volume such as `/var/lib/mysql`, and a Compose
file has a line like `./data:/var/lib/mysql/` (note the trailing slash),
Compose creates duplicate volume binds when *recreating* the container.
(The first container is created without a hitch, but contains multiple
entries in its "Volumes" config.)

Fixed by normalizing all paths in volumes config.

Signed-off-by: Aanand Prasad <[email protected]>
Aanand Prasad 10 年之前
父节点
当前提交
9768872507
共有 2 个文件被更改,包括 42 次插入4 次删除
  1. 8 4
      compose/service.py
  2. 34 0
      tests/integration/service_test.py

+ 8 - 4
compose/service.py

@@ -3,6 +3,7 @@ from __future__ import absolute_import
 from collections import namedtuple
 import logging
 import re
+import os
 import sys
 from operator import attrgetter
 
@@ -848,12 +849,15 @@ def parse_volume_spec(volume_config):
                           "external:internal[:mode]" % volume_config)
 
     if len(parts) == 1:
-        return VolumeSpec(None, parts[0], 'rw')
+        external = None
+        internal = os.path.normpath(parts[0])
+    else:
+        external = os.path.normpath(parts[0])
+        internal = os.path.normpath(parts[1])
 
-    if len(parts) == 2:
-        parts.append('rw')
+    mode = parts[2] if len(parts) == 3 else 'rw'
 
-    return VolumeSpec(*parts)
+    return VolumeSpec(external, internal, mode)
 
 
 # Ports

+ 34 - 0
tests/integration/service_test.py

@@ -221,6 +221,40 @@ class ServiceTest(DockerClientTestCase):
         self.assertTrue(path.basename(actual_host_path) == path.basename(host_path),
                         msg=("Last component differs: %s, %s" % (actual_host_path, host_path)))
 
+    def test_duplicate_volume_trailing_slash(self):
+        """
+        When an image specifies a volume, and the Compose file specifies a host path
+        but adds a trailing slash, make sure that we don't create duplicate binds.
+        """
+        host_path = '/tmp/data'
+        container_path = '/data'
+        volumes = ['{}:{}/'.format(host_path, container_path)]
+
+        tmp_container = self.client.create_container(
+            'busybox', 'true',
+            volumes={container_path: {}},
+            labels={'com.docker.compose.test_image': 'true'},
+        )
+        image = self.client.commit(tmp_container)['Id']
+
+        service = self.create_service('db', image=image, volumes=volumes)
+        old_container = create_and_start_container(service)
+
+        self.assertEqual(
+            old_container.get('Config.Volumes'),
+            {container_path: {}},
+        )
+
+        service = self.create_service('db', image=image, volumes=volumes)
+        new_container = service.recreate_container(old_container)
+
+        self.assertEqual(
+            new_container.get('Config.Volumes'),
+            {container_path: {}},
+        )
+
+        self.assertEqual(service.containers(stopped=False), [new_container])
+
     @patch.dict(os.environ)
     def test_create_container_with_home_and_env_var_in_volume_path(self):
         os.environ['VOLUME_NAME'] = 'my-volume'