From 820769e23cc4b822fcf53a3d17b5f544444c9b26 Mon Sep 17 00:00:00 2001 From: Khushiyant Date: Tue, 18 Mar 2025 22:56:15 +0530 Subject: [PATCH] feat(docker/api/container): add support for subpath in volume_opts TESTED: Yes, added unit tests to verify subpath functionality Signed-off-by: Khushiyant --- docker/types/services.py | 7 +++- tests/integration/api_container_test.py | 50 +++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/docker/types/services.py b/docker/types/services.py index 821115411..69c0c498e 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -242,6 +242,7 @@ class Mount(dict): for the ``volume`` type. driver_config (DriverConfig): Volume driver configuration. Only valid for the ``volume`` type. + subpath (str): Path inside a volume to mount instead of the volume root. tmpfs_size (int or string): The size for the tmpfs mount in bytes. tmpfs_mode (int): The permission mode for the tmpfs mount. """ @@ -249,7 +250,7 @@ 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): + tmpfs_mode=None, subpath=None): self['Target'] = target self['Source'] = source if type not in ('bind', 'volume', 'tmpfs', 'npipe'): @@ -267,7 +268,7 @@ def __init__(self, target, source, type='volume', read_only=False, self['BindOptions'] = { 'Propagation': propagation } - if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode]): + if any([labels, driver_config, no_copy, tmpfs_size, tmpfs_mode, subpath]): raise errors.InvalidArgument( 'Incompatible options have been provided for the bind ' 'type mount.' @@ -280,6 +281,8 @@ def __init__(self, target, source, type='volume', read_only=False, volume_opts['Labels'] = labels if driver_config: volume_opts['DriverConfig'] = driver_config + if subpath: + volume_opts['Subpath'] = subpath if volume_opts: self['VolumeOptions'] = volume_opts if any([propagation, tmpfs_size, tmpfs_mode]): diff --git a/tests/integration/api_container_test.py b/tests/integration/api_container_test.py index 0215e14c2..21c2f3579 100644 --- a/tests/integration/api_container_test.py +++ b/tests/integration/api_container_test.py @@ -620,6 +620,56 @@ def test_create_with_volume_mount(self): assert mount['Source'] == mount_data['Name'] assert mount_data['RW'] is True + @requires_api_version('1.45') + def test_create_with_subpath_volume_mount(self): + source_volume = helpers.random_name() + self.client.create_volume(name=source_volume) + + setup_container = None + test_container = None + + + # Create a file structure in the volume to test with + setup_container = self.client.create_container( + TEST_IMG, + [ + "sh", + "-c", + 'mkdir -p /vol/subdir && echo "test content" > /vol/subdir/testfile.txt', + ], + host_config=self.client.create_host_config( + binds=[f"{source_volume}:/vol"] + ), + ) + self.client.start(setup_container) + self.client.wait(setup_container) + + # Now test with subpath + mount = docker.types.Mount( + type="volume", + source=source_volume, + target=self.mount_dest, + read_only=True, + subpath="subdir", + ) + + + host_config = self.client.create_host_config(mounts=[mount]) + test_container = self.client.create_container( + TEST_IMG, + ["cat", os.path.join(self.mount_dest, "testfile.txt")], + host_config=host_config, + ) + + self.client.start(test_container) + self.client.wait(test_container) # Wait for container to finish + output = self.client.logs(test_container).decode("utf-8").strip() + + # If the subpath feature is working, we should be able to see the content + # of the file in the subdir + assert output == "test content" + + def check_container_data(self, inspect_data, rw, propagation='rprivate'): assert 'Mounts' in inspect_data filtered = list(filter(