From 415fa6c59bdab6a79e7a01429bcb3898cdfab92b Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 29 Nov 2017 12:21:56 -0800 Subject: [PATCH] Use mounts for secrets instead of volumes Signed-off-by: Joffrey F --- compose/config/types.py | 42 ++++++++++++++++++++++++++++++++++++++ compose/service.py | 27 ++++++++++++++++++++---- tests/unit/service_test.py | 12 +++++------ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/compose/config/types.py b/compose/config/types.py index c410343b8..548f2c1cd 100644 --- a/compose/config/types.py +++ b/compose/config/types.py @@ -133,6 +133,48 @@ def normalize_path_for_engine(path): return path.replace('\\', '/') +class MountSpec(object): + options_map = { + 'volume': { + 'nocopy': 'no_copy' + }, + 'bind': { + 'propagation': 'propagation' + } + } + _fields = ['type', 'source', 'target', 'read_only', 'consistency'] + + def __init__(self, type, source=None, target=None, read_only=None, consistency=None, **kwargs): + self.type = type + self.source = source + self.target = target + self.read_only = read_only + self.consistency = consistency + self.options = None + if self.type in kwargs: + self.options = kwargs[self.type] + + def as_volume_spec(self): + mode = 'ro' if self.read_only else 'rw' + return VolumeSpec(external=self.source, internal=self.target, mode=mode) + + def legacy_repr(self): + return self.as_volume_spec().repr() + + def repr(self): + res = {} + for field in self._fields: + if getattr(self, field, None): + res[field] = getattr(self, field) + if self.options: + res[self.type] = self.options + return res + + @property + def is_named_volume(self): + return self.type == 'volume' and self.source + + class VolumeSpec(namedtuple('_VolumeSpec', 'external internal mode')): @classmethod diff --git a/compose/service.py b/compose/service.py index b696fd664..07db3ac5f 100644 --- a/compose/service.py +++ b/compose/service.py @@ -14,6 +14,7 @@ from docker.errors import APIError from docker.errors import ImageNotFound from docker.errors import NotFound from docker.types import LogConfig +from docker.types import Mount from docker.utils import version_gte from docker.utils import version_lt from docker.utils.ports import build_port_bindings @@ -27,6 +28,7 @@ from .config import DOCKER_CONFIG_KEYS from .config import merge_environment from .config import merge_labels from .config.errors import DependencyError +from .config.types import MountSpec from .config.types import ServicePort from .config.types import VolumeSpec from .const import DEFAULT_TIMEOUT @@ -795,9 +797,13 @@ class Service(object): secret_volumes = self.get_secret_volumes() if secret_volumes: - override_options['binds'].extend(v.repr() for v in secret_volumes) - container_options['volumes'].update( - (v.internal, {}) for v in secret_volumes) + if version_lt(self.client.api_version, '1.30'): + override_options['binds'].extend(v.legacy_repr() for v in secret_volumes) + container_options['volumes'].update( + (v.target, {}) for v in secret_volumes + ) + else: + override_options['mounts'] = [build_mount(v) for v in secret_volumes] container_options['image'] = self.image_name @@ -891,6 +897,7 @@ class Service(object): device_read_iops=blkio_config.get('device_read_iops'), device_write_bps=blkio_config.get('device_write_bps'), device_write_iops=blkio_config.get('device_write_iops'), + mounts=options.get('mounts'), ) def get_secret_volumes(self): @@ -901,7 +908,7 @@ class Service(object): elif not os.path.isabs(target): target = '{}/{}'.format(const.SECRETS_PATH, target) - return VolumeSpec(secret['file'], target, 'ro') + return MountSpec('bind', secret['file'], target, read_only=True) return [build_spec(secret) for secret in self.secrets] @@ -1346,6 +1353,18 @@ def build_volume_from(volume_from_spec): return "{}:{}".format(volume_from_spec.source.id, volume_from_spec.mode) +def build_mount(mount_spec): + kwargs = {} + if mount_spec.options: + for option, sdk_name in mount_spec.options_map[mount_spec.type].items(): + if option in mount_spec.options: + kwargs[sdk_name] = mount_spec.options[option] + + return Mount( + type=mount_spec.type, target=mount_spec.target, source=mount_spec.source, + read_only=mount_spec.read_only, consistency=mount_spec.consistency, **kwargs + ) + # Labels diff --git a/tests/unit/service_test.py b/tests/unit/service_test.py index 8e8f60203..87c86a731 100644 --- a/tests/unit/service_test.py +++ b/tests/unit/service_test.py @@ -1133,8 +1133,8 @@ class ServiceSecretTest(unittest.TestCase): ) volumes = service.get_secret_volumes() - assert volumes[0].external == secret1['file'] - assert volumes[0].internal == '{}/{}'.format(SECRETS_PATH, secret1['secret'].target) + assert volumes[0].source == secret1['file'] + assert volumes[0].target == '{}/{}'.format(SECRETS_PATH, secret1['secret'].target) def test_get_secret_volumes_abspath(self): secret1 = { @@ -1149,8 +1149,8 @@ class ServiceSecretTest(unittest.TestCase): ) volumes = service.get_secret_volumes() - assert volumes[0].external == secret1['file'] - assert volumes[0].internal == secret1['secret'].target + assert volumes[0].source == secret1['file'] + assert volumes[0].target == secret1['secret'].target def test_get_secret_volumes_no_target(self): secret1 = { @@ -1165,5 +1165,5 @@ class ServiceSecretTest(unittest.TestCase): ) volumes = service.get_secret_volumes() - assert volumes[0].external == secret1['file'] - assert volumes[0].internal == '{}/{}'.format(SECRETS_PATH, secret1['secret'].source) + assert volumes[0].source == secret1['file'] + assert volumes[0].target == '{}/{}'.format(SECRETS_PATH, secret1['secret'].source)