فهرست منبع

Merge pull request #1833 from aanand/deprecate-relative-volumes-without-dot

Show a warning when a relative path is specified without "./"
Mazz Mosley 10 سال پیش
والد
کامیت
52733f6996
3فایلهای تغییر یافته به همراه73 افزوده شده و 6 حذف شده
  1. 25 4
      compose/config/config.py
  2. 3 2
      docs/yml.md
  3. 45 0
      tests/unit/config_test.py

+ 25 - 4
compose/config/config.py

@@ -90,6 +90,13 @@ SUPPORTED_FILENAMES = [
 ]
 
 
+PATH_START_CHARS = [
+    '/',
+    '.',
+    '~',
+]
+
+
 log = logging.getLogger(__name__)
 
 
@@ -260,7 +267,7 @@ def process_container_options(service_dict, working_dir=None):
         raise ConfigurationError("Invalid 'memswap_limit' configuration for %s service: when defining 'memswap_limit' you must set 'mem_limit' as well" % service_dict['name'])
 
     if 'volumes' in service_dict and service_dict.get('volume_driver') is None:
-        service_dict['volumes'] = resolve_volume_paths(service_dict['volumes'], working_dir=working_dir)
+        service_dict['volumes'] = resolve_volume_paths(service_dict, working_dir=working_dir)
 
     if 'build' in service_dict:
         service_dict['build'] = resolve_build_path(service_dict['build'], working_dir=working_dir)
@@ -421,17 +428,31 @@ def env_vars_from_file(filename):
     return env
 
 
-def resolve_volume_paths(volumes, working_dir=None):
+def resolve_volume_paths(service_dict, working_dir=None):
     if working_dir is None:
         raise Exception("No working_dir passed to resolve_volume_paths()")
 
-    return [resolve_volume_path(v, working_dir) for v in volumes]
+    return [
+        resolve_volume_path(v, working_dir, service_dict['name'])
+        for v in service_dict['volumes']
+    ]
 
 
-def resolve_volume_path(volume, working_dir):
+def resolve_volume_path(volume, working_dir, service_name):
     container_path, host_path = split_path_mapping(volume)
     container_path = os.path.expanduser(container_path)
+
     if host_path is not None:
+        if not any(host_path.startswith(c) for c in PATH_START_CHARS):
+            log.warn(
+                'Warning: the mapping "{0}" in the volumes config for '
+                'service "{1}" is ambiguous. In a future version of Docker, '
+                'it will designate a "named" volume '
+                '(see https://github.com/docker/docker/pull/14242). '
+                'To prevent unexpected behaviour, change it to "./{0}"'
+                .format(volume, service_name)
+            )
+
         host_path = os.path.expanduser(host_path)
         return "%s:%s" % (expand_path(working_dir, host_path), container_path)
     else:

+ 3 - 2
docs/yml.md

@@ -135,11 +135,12 @@ Mount paths as volumes, optionally specifying a path on the host machine
 
     volumes:
      - /var/lib/mysql
-     - cache/:/tmp/cache
+     - ./cache:/tmp/cache
      - ~/configs:/etc/configs/:ro
 
 You can mount a relative path on the host, which will expand relative to
-the directory of the Compose configuration file being used.
+the directory of the Compose configuration file being used. Relative paths
+should always begin with `.` or `..`.
 
 > Note: No path expansion will be done if you have also specified a
 > `volume_driver`.

+ 45 - 0
tests/unit/config_test.py

@@ -117,6 +117,51 @@ class InterpolationTest(unittest.TestCase):
         d = make_service_dict('foo', {'volumes': ['~:/container/path']}, working_dir='.')
         self.assertEqual(d['volumes'], ['/home/user:/container/path'])
 
+    @mock.patch.dict(os.environ)
+    def test_volume_binding_with_local_dir_name_raises_warning(self):
+        def make_dict(**config):
+            make_service_dict('foo', config, working_dir='.')
+
+        with mock.patch('compose.config.config.log.warn') as warn:
+            make_dict(volumes=['/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['/data:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['.:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['..:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['./data:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['../data:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['.profile:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['~:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['~/data:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['~tmp:/container/path'])
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['data:/container/path'], volume_driver='mydriver')
+            self.assertEqual(0, warn.call_count)
+
+            make_dict(volumes=['data:/container/path'])
+            self.assertEqual(1, warn.call_count)
+            warning = warn.call_args[0][0]
+            self.assertIn('"data:/container/path"', warning)
+            self.assertIn('"./data:/container/path"', warning)
+
     def test_named_volume_with_driver_does_not_expand(self):
         d = make_service_dict('foo', {
             'volumes': ['namedvolume:/data'],