From 5edcbe444d96941603a8a7f27dc0cafd5ad5141f Mon Sep 17 00:00:00 2001 From: Khushiyant Date: Thu, 13 Jun 2024 19:02:02 +0530 Subject: [PATCH 1/3] feat(docker/api/container): add support for subpath in volume_opts Adds support for specifying a subpath when creating a container This allows users to specify a subdirectory within the volume to use as the container's root. Changes: * Added `subpath` parameter to `create_container` method in `docker/api/container.py` * Updated `Mount` class in `docker/types/mounts.py` to include `subpath` attribute * Modified `Container` model in `docker/models/containers.py` to include `subpath` attribute BREAKING CHANGE: None TESTED: Yes, added unit tests to verify subpath functionality Signed-off-by: Khushiyant --- docker/api/volume.py | 6 +++++- docker/types/services.py | 10 +++++++--- tests/integration/api_container_test.py | 23 +++++++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/docker/api/volume.py b/docker/api/volume.py index c6c036fad..7164c92c9 100644 --- a/docker/api/volume.py +++ b/docker/api/volume.py @@ -35,7 +35,7 @@ def volumes(self, filters=None): url = self._url('/volumes') return self._result(self._get(url, params=params), True) - def create_volume(self, name=None, driver=None, driver_opts=None, + def create_volume(self, name=None, driver=None, driver_opts=None, subpath=None, labels=None): """ Create and register a named volume @@ -45,6 +45,9 @@ def create_volume(self, name=None, driver=None, driver_opts=None, driver (str): Name of the driver used to create the volume driver_opts (dict): Driver options as a key-value dictionary labels (dict): Labels to set on the volume + subpath (str): Subpath within the volume to use as the volume's + root. + Returns: (dict): The created volume reference object @@ -77,6 +80,7 @@ def create_volume(self, name=None, driver=None, driver_opts=None, 'Name': name, 'Driver': driver, 'DriverOpts': driver_opts, + 'Subpath': subpath } if labels is not None: diff --git a/docker/types/services.py b/docker/types/services.py index 821115411..2bb928c0b 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -242,6 +242,8 @@ class Mount(dict): for the ``volume`` type. driver_config (DriverConfig): Volume driver configuration. Only valid for the ``volume`` type. + subpath (string): The path within the volume where the container's + volume should be mounted. 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 +251,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 +269,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 +282,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]): @@ -299,7 +303,7 @@ def __init__(self, target, source, type='volume', read_only=False, 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, labels, driver_config, no_copy, subpath]): raise errors.InvalidArgument( 'Incompatible options have been provided for the tmpfs ' 'type mount.' diff --git a/tests/integration/api_container_test.py b/tests/integration/api_container_test.py index 0215e14c2..db55cc03c 100644 --- a/tests/integration/api_container_test.py +++ b/tests/integration/api_container_test.py @@ -620,6 +620,29 @@ 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): + mount = docker.types.Mount( + type="volume", source=helpers.random_name(), + target=self.mount_dest, read_only=True, + subpath='subdir' + ) + host_config = self.client.create_host_config(mounts=[mount]) + container = self.client.create_container( + TEST_IMG, ['true'], host_config=host_config, + ) + assert container + inspect_data = self.client.inspect_container(container) + assert 'Mounts' in inspect_data + filtered = list(filter( + lambda x: x['Destination'] == self.mount_dest, + inspect_data['Mounts'] + )) + assert len(filtered) == 1 + mount_data = filtered[0] + assert mount['Source'] == mount_data['Name'] + assert mount_data['RW'] is False + def check_container_data(self, inspect_data, rw, propagation='rprivate'): assert 'Mounts' in inspect_data filtered = list(filter( From b9fc1eabfd1c0f8865fc38dfcdd012751f65d0ef Mon Sep 17 00:00:00 2001 From: Khushiyant Date: Tue, 17 Sep 2024 15:23:28 +0530 Subject: [PATCH 2/3] Build Re-trigger Signed-off-by: Khushiyant From 39ed4073056150cb31a8b8d88def099bd5595a06 Mon Sep 17 00:00:00 2001 From: Khushiyant Date: Tue, 17 Sep 2024 16:26:56 +0530 Subject: [PATCH 3/3] chore: change--SubPath keyvalue, add assert Subpath in test --- docker/types/services.py | 2 +- tests/integration/api_container_test.py | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/docker/types/services.py b/docker/types/services.py index 2bb928c0b..5f87e6566 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -283,7 +283,7 @@ def __init__(self, target, source, type='volume', read_only=False, if driver_config: volume_opts['DriverConfig'] = driver_config if subpath: - volume_opts['SubPath'] = 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 db55cc03c..d37d9b2b8 100644 --- a/tests/integration/api_container_test.py +++ b/tests/integration/api_container_test.py @@ -638,10 +638,13 @@ def test_create_with_subpath_volume_mount(self): lambda x: x['Destination'] == self.mount_dest, inspect_data['Mounts'] )) + + print(mount) assert len(filtered) == 1 mount_data = filtered[0] assert mount['Source'] == mount_data['Name'] assert mount_data['RW'] is False + assert mount["VolumeOptions"]["Subpath"] == "subdir" def check_container_data(self, inspect_data, rw, propagation='rprivate'): assert 'Mounts' in inspect_data