[virt-tools-list] [virt-bootstrap] [PATCH v6 15/26] Enable UID/GID mapping for qcow2
Cedric Bosdonnat
cbosdonnat at suse.com
Sat Aug 19 12:58:04 UTC 2017
On Thu, 2017-08-17 at 10:39 +0100, Radostin Stoyanov wrote:
> Apply ownership mapping in qcow2 images using libguestfs python
> bindings. To make this solution more general we introduce function
> guestfs_walk() which will return the root file system tree of disk
> image along with UID/GID values.
>
> These changes are applied in additional qcow2 disk image using the
> last layer as backing file. For FileSource this is layer-1.qcow
> with backing file layer-0.qcow2.
> ---
> src/virtBootstrap/sources/docker_source.py | 12 +++++
> src/virtBootstrap/sources/file_source.py | 7 +++
> src/virtBootstrap/utils.py | 74 ++++++++++++++++++++++++++++++
> src/virtBootstrap/virt_bootstrap.py | 2 +
> tests/docker_source.py | 1 -
> tests/file_source.py | 1 -
> 6 files changed, 95 insertions(+), 2 deletions(-)
>
> diff --git a/src/virtBootstrap/sources/docker_source.py b/src/virtBootstrap/sources/docker_source.py
> index 99247ab..ba70a44 100644
> --- a/src/virtBootstrap/sources/docker_source.py
> +++ b/src/virtBootstrap/sources/docker_source.py
> @@ -49,15 +49,22 @@ class DockerSource(object):
> @param uri: Address of source registry
> @param username: Username to access source registry
> @param password: Password to access source registry
> + @param uid_map: Mappings for UID of files in rootfs
> + @param gid_map: Mappings for GID of files in rootfs
> @param fmt: Format used to store image [dir, qcow2]
> @param not_secure: Do not require HTTPS and certificate verification
> @param no_cache: Whether to store downloaded images or not
> @param progress: Instance of the progress module
> +
> + Note: uid_map and gid_map have the format:
> + [[<start>, <target>, <count>], [<start>, <target>, <count>] ...]
> """
>
> self.url = self.gen_valid_uri(kwargs['uri'])
> self.username = kwargs.get('username', None)
> self.password = kwargs.get('password', None)
> + self.uid_map = kwargs.get('uid_map', [])
> + self.gid_map = kwargs.get('gid_map', [])
> self.output_format = kwargs.get('fmt', utils.DEFAULT_OUTPUT_FORMAT)
> self.insecure = kwargs.get('not_secure', False)
> self.no_cache = kwargs.get('no_cache', False)
> @@ -283,6 +290,11 @@ class DockerSource(object):
> )
> img.create_base_layer()
> img.create_backing_chains()
> + if self.uid_map or self.gid_map:
> + logger.info("Mapping UID/GID")
> + utils.map_id_in_image(
> + len(self.layers), dest, self.uid_map, self.gid_map
> + )
>
> else:
> raise Exception("Unknown format:" + self.output_format)
> diff --git a/src/virtBootstrap/sources/file_source.py b/src/virtBootstrap/sources/file_source.py
> index 69f024c..b4b29ce 100644
> --- a/src/virtBootstrap/sources/file_source.py
> +++ b/src/virtBootstrap/sources/file_source.py
> @@ -41,10 +41,14 @@ class FileSource(object):
>
> @param uri: Path to tar archive file.
> @param fmt: Format used to store image [dir, qcow2]
> + @param uid_map: Mappings for UID of files in rootfs
> + @param gid_map: Mappings for GID of files in rootfs
> @param progress: Instance of the progress module
> """
> self.path = kwargs['uri'].path
> self.output_format = kwargs.get('fmt', utils.DEFAULT_OUTPUT_FORMAT)
> + self.uid_map = kwargs.get('uid_map', [])
> + self.gid_map = kwargs.get('gid_map', [])
> self.progress = kwargs['progress'].update_progress
>
> def unpack(self, dest):
> @@ -73,6 +77,9 @@ class FileSource(object):
> progress=self.progress
> )
> img.create_base_layer()
> + if self.uid_map or self.gid_map:
> + logger.info("Mapping UID/GID")
> + utils.map_id_in_image(1, dest, self.uid_map, self.gid_map)
>
> else:
> raise Exception("Unknown format:" + self.output_format)
> diff --git a/src/virtBootstrap/utils.py b/src/virtBootstrap/utils.py
> index 482e4aa..2e0bbf8 100644
> --- a/src/virtBootstrap/utils.py
> +++ b/src/virtBootstrap/utils.py
> @@ -64,6 +64,9 @@ class BuildImage(object):
> @param tar_files: Tarballs to be converted to qcow2 images
> @param dest: Directory where the qcow2 images will be created
> @param progress: Instance of the progress module
> +
> + Note: uid_map and gid_map have the format:
> + [[<start>, <target>, <count>], [<start>, <target>, <count>] ...]
> """
> self.g = guestfs.GuestFS(python_return_dict=True)
> self.layers = layers
> @@ -529,6 +532,77 @@ def map_id(path, map_uid, map_gid):
> os.lchown(file_path, new_uid, new_gid)
>
>
> +def guestfs_walk(rootfs_tree, g, path='/'):
> + """
> + File system walk for guestfs
> + """
> + stat = g.lstat(path)
> + rootfs_tree[path] = {'uid': stat['uid'], 'gid': stat['gid']}
> + for member in g.ls(path):
> + m_path = os.path.join(path, member)
> + if g.is_dir(m_path):
> + guestfs_walk(rootfs_tree, g, m_path)
> + else:
> + stat = g.lstat(m_path)
> + rootfs_tree[m_path] = {'uid': stat['uid'], 'gid': stat['gid']}
> +
> +
> +def apply_mapping_in_image(uid, gid, rootfs_tree, g):
> + """
> + Apply mapping of new ownership
> + """
> + if uid:
> + uid_opts = get_mapping_opts(uid)
> + if gid:
> + gid_opts = get_mapping_opts(gid)
> +
> + for member in rootfs_tree:
> + old_uid = rootfs_tree[member]['uid']
> + old_gid = rootfs_tree[member]['gid']
> +
> + new_uid = get_map_id(old_uid, uid_opts) if uid else -1
> + new_gid = get_map_id(old_gid, gid_opts) if gid else -1
> + if new_uid != -1 or new_gid != -1:
> + g.lchown(new_uid, new_gid, os.path.join('/', member))
> +
> +
> +def map_id_in_image(nlayers, dest, map_uid, map_gid):
> + """
> + Create additional layer in which UID/GID mipping is applied.
> +
> + map_gid and map_uid have the format:
> + [[<start>, <target>, <count>], [<start>, <target>, <count>], ...]
> + """
> +
> + g = guestfs.GuestFS(python_return_dict=True)
> + last_layer = os.path.join(dest, "layer-%d.qcow2" % (nlayers - 1))
> + additional_layer = os.path.join(dest, "layer-%d.qcow2" % nlayers)
> + # Add the last layer as readonly
> + g.add_drive_opts(last_layer, format='qcow2', readonly=True)
> + # Create the additional layer
> + g.disk_create(
> + filename=additional_layer,
> + format='qcow2',
> + size=-1,
> + backingfile=last_layer,
> + backingformat='qcow2'
> + )
> + g.add_drive(additional_layer, format='qcow2')
> + g.launch()
> + g.mount('/dev/sda', '/')
> + rootfs_tree = dict()
> + guestfs_walk(rootfs_tree, g)
> + g.umount('/')
> + g.mount('/dev/sdb', '/')
> +
> + balance_uid_gid_maps(map_uid, map_gid)
> + for uid, gid in zip(map_uid, map_gid):
> + apply_mapping_in_image(uid, gid, rootfs_tree, g)
> +
> + g.umount('/')
> + g.shutdown()
> +
> +
> def balance_uid_gid_maps(uid_map, gid_map):
> """
> Make sure the UID/GID list of mappings have the same lenght.
> diff --git a/src/virtBootstrap/virt_bootstrap.py b/src/virtBootstrap/virt_bootstrap.py
> index f970838..2d81998 100755
> --- a/src/virtBootstrap/virt_bootstrap.py
> +++ b/src/virtBootstrap/virt_bootstrap.py
> @@ -130,6 +130,8 @@ def bootstrap(uri, dest,
> fmt=fmt,
> username=username,
> password=password,
> + uid_map=uid_map,
> + gid_map=gid_map,
> not_secure=not_secure,
> no_cache=no_cache,
> progress=prog).unpack(dest)
> diff --git a/tests/docker_source.py b/tests/docker_source.py
> index ee294c1..93835de 100644
> --- a/tests/docker_source.py
> +++ b/tests/docker_source.py
> @@ -326,7 +326,6 @@ class Qcow2BuildImage(CreateLayers):
> self.main()
>
>
> - at unittest.skip("Need fix for this test to pass")
> class Qcow2OwnershipMapping(Qcow2BuildImage):
> """
> Ensures that UID/GID mapping works correctly for qcow2 conversion.
> diff --git a/tests/file_source.py b/tests/file_source.py
> index 2a0f964..a18b6f4 100644
> --- a/tests/file_source.py
> +++ b/tests/file_source.py
> @@ -72,7 +72,6 @@ class Qcow2BuildImage(BuildTarFiles):
> self.check_qcow2_images(self.get_image_path())
>
>
> - at unittest.skip("Not implemented")
> class Qcow2OwnershipMapping(Qcow2BuildImage):
> """
> Ensures that UID/GID mapping works correctly for qcow2 conversion.
I would add these tests only in this commit rather than skipping them before.
ACK with the changes mentioned above
--
Cedric
More information about the virt-tools-list
mailing list