mirror of https://github.com/docker/compose.git
Do not normalize volume paths on Windows by default
Add environment variable to enable normalization if needed. Do not normalize internal paths Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
f60c60205b
commit
efb09af271
|
@ -651,7 +651,10 @@ def finalize_service(service_config, service_names, version, environment):
|
||||||
|
|
||||||
if 'volumes' in service_dict:
|
if 'volumes' in service_dict:
|
||||||
service_dict['volumes'] = [
|
service_dict['volumes'] = [
|
||||||
VolumeSpec.parse(v) for v in service_dict['volumes']]
|
VolumeSpec.parse(
|
||||||
|
v, environment.get('COMPOSE_CONVERT_WINDOWS_PATHS')
|
||||||
|
) for v in service_dict['volumes']
|
||||||
|
]
|
||||||
|
|
||||||
if 'net' in service_dict:
|
if 'net' in service_dict:
|
||||||
network_mode = service_dict.pop('net')
|
network_mode = service_dict.pop('net')
|
||||||
|
|
|
@ -5,6 +5,7 @@ from __future__ import absolute_import
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
@ -14,6 +15,8 @@ from compose.config.errors import ConfigurationError
|
||||||
from compose.const import IS_WINDOWS_PLATFORM
|
from compose.const import IS_WINDOWS_PLATFORM
|
||||||
from compose.utils import splitdrive
|
from compose.utils import splitdrive
|
||||||
|
|
||||||
|
win32_root_path_pattern = re.compile(r'^[A-Za-z]\:\\.*')
|
||||||
|
|
||||||
|
|
||||||
class VolumeFromSpec(namedtuple('_VolumeFromSpec', 'source mode type')):
|
class VolumeFromSpec(namedtuple('_VolumeFromSpec', 'source mode type')):
|
||||||
|
|
||||||
|
@ -154,7 +157,7 @@ class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')):
|
||||||
return cls(external, internal, mode)
|
return cls(external, internal, mode)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _parse_win32(cls, volume_config):
|
def _parse_win32(cls, volume_config, normalize):
|
||||||
# relative paths in windows expand to include the drive, eg C:\
|
# relative paths in windows expand to include the drive, eg C:\
|
||||||
# so we join the first 2 parts back together to count as one
|
# so we join the first 2 parts back together to count as one
|
||||||
mode = 'rw'
|
mode = 'rw'
|
||||||
|
@ -168,13 +171,13 @@ class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')):
|
||||||
|
|
||||||
parts = separate_next_section(volume_config)
|
parts = separate_next_section(volume_config)
|
||||||
if len(parts) == 1:
|
if len(parts) == 1:
|
||||||
internal = normalize_path_for_engine(os.path.normpath(parts[0]))
|
internal = parts[0]
|
||||||
external = None
|
external = None
|
||||||
else:
|
else:
|
||||||
external = parts[0]
|
external = parts[0]
|
||||||
parts = separate_next_section(parts[1])
|
parts = separate_next_section(parts[1])
|
||||||
external = normalize_path_for_engine(os.path.normpath(external))
|
external = os.path.normpath(external)
|
||||||
internal = normalize_path_for_engine(os.path.normpath(parts[0]))
|
internal = parts[0]
|
||||||
if len(parts) > 1:
|
if len(parts) > 1:
|
||||||
if ':' in parts[1]:
|
if ':' in parts[1]:
|
||||||
raise ConfigurationError(
|
raise ConfigurationError(
|
||||||
|
@ -183,15 +186,18 @@ class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')):
|
||||||
)
|
)
|
||||||
mode = parts[1]
|
mode = parts[1]
|
||||||
|
|
||||||
|
if normalize:
|
||||||
|
external = normalize_path_for_engine(external) if external else None
|
||||||
|
|
||||||
return cls(external, internal, mode)
|
return cls(external, internal, mode)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, volume_config):
|
def parse(cls, volume_config, normalize=False):
|
||||||
"""Parse a volume_config path and split it into external:internal[:mode]
|
"""Parse a volume_config path and split it into external:internal[:mode]
|
||||||
parts to be returned as a valid VolumeSpec.
|
parts to be returned as a valid VolumeSpec.
|
||||||
"""
|
"""
|
||||||
if IS_WINDOWS_PLATFORM:
|
if IS_WINDOWS_PLATFORM:
|
||||||
return cls._parse_win32(volume_config)
|
return cls._parse_win32(volume_config, normalize)
|
||||||
else:
|
else:
|
||||||
return cls._parse_unix(volume_config)
|
return cls._parse_unix(volume_config)
|
||||||
|
|
||||||
|
@ -201,7 +207,14 @@ class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_named_volume(self):
|
def is_named_volume(self):
|
||||||
return self.external and not self.external.startswith(('.', '/', '~'))
|
res = self.external and not self.external.startswith(('.', '/', '~'))
|
||||||
|
if not IS_WINDOWS_PLATFORM:
|
||||||
|
return res
|
||||||
|
|
||||||
|
return (
|
||||||
|
res and not self.external.startswith('\\') and
|
||||||
|
not win32_root_path_pattern.match(self.external)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ServiceLink(namedtuple('_ServiceLink', 'target alias')):
|
class ServiceLink(namedtuple('_ServiceLink', 'target alias')):
|
||||||
|
|
|
@ -63,35 +63,67 @@ class TestVolumeSpec(object):
|
||||||
VolumeSpec.parse('one:two:three:four')
|
VolumeSpec.parse('one:two:three:four')
|
||||||
assert 'has incorrect format' in exc.exconly()
|
assert 'has incorrect format' in exc.exconly()
|
||||||
|
|
||||||
def test_parse_volume_windows_absolute_path(self):
|
def test_parse_volume_windows_absolute_path_normalized(self):
|
||||||
windows_path = "c:\\Users\\me\\Documents\\shiny\\config:\\opt\\shiny\\config:ro"
|
windows_path = "c:\\Users\\me\\Documents\\shiny\\config:/opt/shiny/config:ro"
|
||||||
assert VolumeSpec._parse_win32(windows_path) == (
|
assert VolumeSpec._parse_win32(windows_path, True) == (
|
||||||
"/c/Users/me/Documents/shiny/config",
|
"/c/Users/me/Documents/shiny/config",
|
||||||
"/opt/shiny/config",
|
"/opt/shiny/config",
|
||||||
"ro"
|
"ro"
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parse_volume_windows_internal_path(self):
|
def test_parse_volume_windows_absolute_path_native(self):
|
||||||
|
windows_path = "c:\\Users\\me\\Documents\\shiny\\config:/opt/shiny/config:ro"
|
||||||
|
assert VolumeSpec._parse_win32(windows_path, False) == (
|
||||||
|
"c:\\Users\\me\\Documents\\shiny\\config",
|
||||||
|
"/opt/shiny/config",
|
||||||
|
"ro"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parse_volume_windows_internal_path_normalized(self):
|
||||||
windows_path = 'C:\\Users\\reimu\\scarlet:C:\\scarlet\\app:ro'
|
windows_path = 'C:\\Users\\reimu\\scarlet:C:\\scarlet\\app:ro'
|
||||||
assert VolumeSpec._parse_win32(windows_path) == (
|
assert VolumeSpec._parse_win32(windows_path, True) == (
|
||||||
'/c/Users/reimu/scarlet',
|
'/c/Users/reimu/scarlet',
|
||||||
'/c/scarlet/app',
|
'C:\\scarlet\\app',
|
||||||
'ro'
|
'ro'
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parse_volume_windows_just_drives(self):
|
def test_parse_volume_windows_internal_path_native(self):
|
||||||
|
windows_path = 'C:\\Users\\reimu\\scarlet:C:\\scarlet\\app:ro'
|
||||||
|
assert VolumeSpec._parse_win32(windows_path, False) == (
|
||||||
|
'C:\\Users\\reimu\\scarlet',
|
||||||
|
'C:\\scarlet\\app',
|
||||||
|
'ro'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parse_volume_windows_just_drives_normalized(self):
|
||||||
windows_path = 'E:\\:C:\\:ro'
|
windows_path = 'E:\\:C:\\:ro'
|
||||||
assert VolumeSpec._parse_win32(windows_path) == (
|
assert VolumeSpec._parse_win32(windows_path, True) == (
|
||||||
'/e/',
|
'/e/',
|
||||||
'/c/',
|
'C:\\',
|
||||||
'ro'
|
'ro'
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_parse_volume_windows_mixed_notations(self):
|
def test_parse_volume_windows_just_drives_native(self):
|
||||||
windows_path = '/c/Foo:C:\\bar'
|
windows_path = 'E:\\:C:\\:ro'
|
||||||
assert VolumeSpec._parse_win32(windows_path) == (
|
assert VolumeSpec._parse_win32(windows_path, False) == (
|
||||||
|
'E:\\',
|
||||||
|
'C:\\',
|
||||||
|
'ro'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parse_volume_windows_mixed_notations_normalized(self):
|
||||||
|
windows_path = 'C:\\Foo:/root/foo'
|
||||||
|
assert VolumeSpec._parse_win32(windows_path, True) == (
|
||||||
'/c/Foo',
|
'/c/Foo',
|
||||||
'/c/bar',
|
'/root/foo',
|
||||||
|
'rw'
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_parse_volume_windows_mixed_notations_native(self):
|
||||||
|
windows_path = 'C:\\Foo:/root/foo'
|
||||||
|
assert VolumeSpec._parse_win32(windows_path, False) == (
|
||||||
|
'C:\\Foo',
|
||||||
|
'/root/foo',
|
||||||
'rw'
|
'rw'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -786,7 +786,7 @@ class ServiceVolumesTest(unittest.TestCase):
|
||||||
self.mock_client = mock.create_autospec(docker.Client)
|
self.mock_client = mock.create_autospec(docker.Client)
|
||||||
|
|
||||||
def test_build_volume_binding(self):
|
def test_build_volume_binding(self):
|
||||||
binding = build_volume_binding(VolumeSpec.parse('/outside:/inside'))
|
binding = build_volume_binding(VolumeSpec.parse('/outside:/inside', True))
|
||||||
assert binding == ('/inside', '/outside:/inside:rw')
|
assert binding == ('/inside', '/outside:/inside:rw')
|
||||||
|
|
||||||
def test_get_container_data_volumes(self):
|
def test_get_container_data_volumes(self):
|
||||||
|
@ -845,10 +845,10 @@ class ServiceVolumesTest(unittest.TestCase):
|
||||||
|
|
||||||
def test_merge_volume_bindings(self):
|
def test_merge_volume_bindings(self):
|
||||||
options = [
|
options = [
|
||||||
VolumeSpec.parse('/host/volume:/host/volume:ro'),
|
VolumeSpec.parse('/host/volume:/host/volume:ro', True),
|
||||||
VolumeSpec.parse('/host/rw/volume:/host/rw/volume'),
|
VolumeSpec.parse('/host/rw/volume:/host/rw/volume', True),
|
||||||
VolumeSpec.parse('/new/volume'),
|
VolumeSpec.parse('/new/volume', True),
|
||||||
VolumeSpec.parse('/existing/volume'),
|
VolumeSpec.parse('/existing/volume', True),
|
||||||
]
|
]
|
||||||
|
|
||||||
self.mock_client.inspect_image.return_value = {
|
self.mock_client.inspect_image.return_value = {
|
||||||
|
@ -882,8 +882,8 @@ class ServiceVolumesTest(unittest.TestCase):
|
||||||
'web',
|
'web',
|
||||||
image='busybox',
|
image='busybox',
|
||||||
volumes=[
|
volumes=[
|
||||||
VolumeSpec.parse('/host/path:/data1'),
|
VolumeSpec.parse('/host/path:/data1', True),
|
||||||
VolumeSpec.parse('/host/path:/data2'),
|
VolumeSpec.parse('/host/path:/data2', True),
|
||||||
],
|
],
|
||||||
client=self.mock_client,
|
client=self.mock_client,
|
||||||
)
|
)
|
||||||
|
@ -1007,7 +1007,7 @@ class ServiceVolumesTest(unittest.TestCase):
|
||||||
'web',
|
'web',
|
||||||
client=self.mock_client,
|
client=self.mock_client,
|
||||||
image='busybox',
|
image='busybox',
|
||||||
volumes=[VolumeSpec.parse(volume)],
|
volumes=[VolumeSpec.parse(volume, True)],
|
||||||
).create_container()
|
).create_container()
|
||||||
|
|
||||||
assert self.mock_client.create_container.call_count == 1
|
assert self.mock_client.create_container.call_count == 1
|
||||||
|
|
Loading…
Reference in New Issue