This commit is contained in:
Jan Čermák 2025-01-23 10:19:52 +00:00 committed by GitHub
commit 534ad571d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 91 additions and 6 deletions

View File

@ -235,6 +235,9 @@ class Mount(dict):
``default```, ``consistent``, ``cached``, ``delegated``.
propagation (string): A propagation mode with the value ``[r]private``,
``[r]shared``, or ``[r]slave``. Only valid for the ``bind`` type.
recursive (string): Bind mount recursive mode, one of ``enabled``,
``disabled``, ``writable``, or ``readonly``. Only valid for the
``bind`` type.
no_copy (bool): False if the volume should be populated with the data
from the target. Default: ``False``. Only valid for the ``volume``
type.
@ -247,9 +250,9 @@ class Mount(dict):
"""
def __init__(self, target, source, type='volume', read_only=False,
consistency=None, propagation=None, no_copy=False,
labels=None, driver_config=None, tmpfs_size=None,
tmpfs_mode=None):
consistency=None, propagation=None, recursive=None,
no_copy=False, labels=None, driver_config=None,
tmpfs_size=None, tmpfs_mode=None):
self['Target'] = target
self['Source'] = source
if type not in ('bind', 'volume', 'tmpfs', 'npipe'):
@ -267,6 +270,21 @@ class Mount(dict):
self['BindOptions'] = {
'Propagation': propagation
}
if recursive is not None:
bind_options = self.setdefault('BindOptions', {})
if recursive == "enabled":
pass # noop - default
elif recursive == "disabled":
bind_options['NonRecursive'] = True
elif recursive == "writable":
bind_options['ReadOnlyNonRecursive'] = True
elif recursive == "readonly":
bind_options['ReadOnlyForceRecursive'] = True
else:
raise errors.InvalidArgument(
'Invalid recursive bind option, must be one of '
'"enabled", "disabled", "writable", or "readonly".'
)
if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode]):
raise errors.InvalidArgument(
'Incompatible options have been provided for the bind '
@ -282,7 +300,7 @@ class Mount(dict):
volume_opts['DriverConfig'] = driver_config
if volume_opts:
self['VolumeOptions'] = volume_opts
if any([propagation, tmpfs_size, tmpfs_mode]):
if any([propagation, recursive, tmpfs_size, tmpfs_mode]):
raise errors.InvalidArgument(
'Incompatible options have been provided for the volume '
'type mount.'
@ -299,7 +317,7 @@ class Mount(dict):
tmpfs_opts['SizeBytes'] = parse_bytes(tmpfs_size)
if tmpfs_opts:
self['TmpfsOptions'] = tmpfs_opts
if any([propagation, labels, driver_config, no_copy]):
if any([propagation, recursive, labels, driver_config, no_copy]):
raise errors.InvalidArgument(
'Incompatible options have been provided for the tmpfs '
'type mount.'

View File

@ -598,6 +598,60 @@ class VolumeBindTest(BaseAPIIntegrationTest):
inspect_data = self.client.inspect_container(container)
self.check_container_data(inspect_data, False)
@requires_api_version('1.41')
def test_create_with_mounts_recursive_disabled(self):
mount = docker.types.Mount(
type="bind", source=self.mount_origin, target=self.mount_dest,
read_only=True, recursive="disabled"
)
host_config = self.client.create_host_config(mounts=[mount])
container = self.run_container(
TEST_IMG, ['ls', self.mount_dest],
host_config=host_config
)
assert container
logs = self.client.logs(container).decode('utf-8')
assert self.filename in logs
inspect_data = self.client.inspect_container(container)
self.check_container_data(inspect_data, False,
bind_options_field="NonRecursive")
@requires_api_version('1.44')
def test_create_with_mounts_recursive_writable(self):
mount = docker.types.Mount(
type="bind", source=self.mount_origin, target=self.mount_dest,
read_only=True, recursive="writable"
)
host_config = self.client.create_host_config(mounts=[mount])
container = self.run_container(
TEST_IMG, ['ls', self.mount_dest],
host_config=host_config
)
assert container
logs = self.client.logs(container).decode('utf-8')
assert self.filename in logs
inspect_data = self.client.inspect_container(container)
self.check_container_data(inspect_data, False,
bind_options_field="ReadOnlyNonRecursive")
@requires_api_version('1.44')
def test_create_with_mounts_recursive_ro(self):
mount = docker.types.Mount(
type="bind", source=self.mount_origin, target=self.mount_dest,
read_only=True, recursive="readonly"
)
host_config = self.client.create_host_config(mounts=[mount])
container = self.run_container(
TEST_IMG, ['ls', self.mount_dest],
host_config=host_config
)
assert container
logs = self.client.logs(container).decode('utf-8')
assert self.filename in logs
inspect_data = self.client.inspect_container(container)
self.check_container_data(inspect_data, False,
bind_options_field="ReadOnlyForceRecursive")
@requires_api_version('1.30')
def test_create_with_volume_mount(self):
mount = docker.types.Mount(
@ -620,7 +674,8 @@ class VolumeBindTest(BaseAPIIntegrationTest):
assert mount['Source'] == mount_data['Name']
assert mount_data['RW'] is True
def check_container_data(self, inspect_data, rw, propagation='rprivate'):
def check_container_data(self, inspect_data, rw, propagation='rprivate',
bind_options_field=None):
assert 'Mounts' in inspect_data
filtered = list(filter(
lambda x: x['Destination'] == self.mount_dest,
@ -631,6 +686,18 @@ class VolumeBindTest(BaseAPIIntegrationTest):
assert mount_data['Source'] == self.mount_origin
assert mount_data['RW'] == rw
assert mount_data['Propagation'] == propagation
if bind_options_field:
assert 'Mounts' in inspect_data['HostConfig']
mounts = [
x for x in inspect_data['HostConfig']['Mounts']
if x['Target'] == self.mount_dest
]
assert len(mounts) == 1
mount = mounts[0]
assert 'BindOptions' in mount
bind_options = mount['BindOptions']
assert bind_options_field in bind_options
assert bind_options[bind_options_field] is True
def run_with_volume(self, ro, *args, **kwargs):
return self.run_container(