diff --git a/compose/config/config.py b/compose/config/config.py index 4d3f5fae..73516a21 100644 --- a/compose/config/config.py +++ b/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: diff --git a/docs/yml.md b/docs/yml.md index 18551bf2..6ac1ce62 100644 --- a/docs/yml.md +++ b/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`. diff --git a/tests/unit/config_test.py b/tests/unit/config_test.py index 2cd3e005..75a88dfe 100644 --- a/tests/unit/config_test.py +++ b/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'],