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 <aanand.prasad@gmail.com>
This commit is contained in:
Aanand Prasad 2015-07-29 18:04:19 +01:00
parent 276e369c31
commit 9768872507
2 changed files with 42 additions and 4 deletions

View File

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

View File

@ -221,6 +221,40 @@ class ServiceTest(DockerClientTestCase):
self.assertTrue(path.basename(actual_host_path) == path.basename(host_path), self.assertTrue(path.basename(actual_host_path) == path.basename(host_path),
msg=("Last component differs: %s, %s" % (actual_host_path, 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) @patch.dict(os.environ)
def test_create_container_with_home_and_env_var_in_volume_path(self): def test_create_container_with_home_and_env_var_in_volume_path(self):
os.environ['VOLUME_NAME'] = 'my-volume' os.environ['VOLUME_NAME'] = 'my-volume'