Extend and update tests. --- src/virtBootstrap/sources/docker_source.py | 11 ++++++- src/virtBootstrap/sources/file_source.py | 8 ++++- src/virtBootstrap/utils.py | 39 +++++++++++++++++++++++++ src/virtBootstrap/virt_bootstrap.py | 2 ++ tests/test_build_qcow2_image.py | 47 ++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 2 deletions(-) diff --git a/src/virtBootstrap/sources/docker_source.py b/src/virtBootstrap/sources/docker_source.py index 9d7c187..45e6c1d 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', None) + self.gid_map = kwargs.get('gid_map', None) 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) @@ -270,7 +277,9 @@ class DockerSource(object): utils.Build_QCOW2_Image( tar_files=self.tar_files, dest=dest, - progress=self.progress + progress=self.progress, + uid_map=self.uid_map, + gid_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 3bfccf2..70b91db 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', None) + self.gid_map = kwargs.get('gid_map', None) self.progress = kwargs['progress'].update_progress def unpack(self, dest): @@ -66,7 +70,9 @@ class FileSource(object): utils.Build_QCOW2_Image( tar_files=[self.path], dest=dest, - progress=self.progress + progress=self.progress, + uid_map=self.uid_map, + gid_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 7be4d32..8304a2b 100644 --- a/src/virtBootstrap/utils.py +++ b/src/virtBootstrap/utils.py @@ -29,6 +29,7 @@ import json import os import subprocess import sys +import tarfile import tempfile import logging import re @@ -62,8 +63,14 @@ class Build_QCOW2_Image(object): Initialize guestfs @param tar_files: List of tar files from which to create rootfs + @param uid_map: Mappings for UID of files in rootfs + @param gid_map: Mappings for GID of files in rootfs @param dest: Destination directory where qcow2 images will be stored @param progress: Instance of the progress module + + Note: uid_map and gid_map have the format: + [[<start>, <target>, <count>], [<start>, <target>, <count>] ...] + """ self.tar_files = kwargs['tar_files'] if not isinstance(self.tar_files, list): @@ -71,6 +78,9 @@ class Build_QCOW2_Image(object): 'tar_files must be list not %s' % type(self.tar_files) ) self.progress = kwargs['progress'] + self.uid_map = kwargs.get('uid_map', None) + self.gid_map = kwargs.get('gid_map', None) + self.fmt = 'qcow2' self.qcow2_files = [os.path.join(kwargs['dest'], 'layer-%s.qcow2' % i) for i in range(len(self.tar_files))] @@ -105,6 +115,14 @@ class Build_QCOW2_Image(object): # from tar file. self.g.tar_in(tar_file, '/', get_compression_type(tar_file), xattrs=True, selinux=True, acls=True) + + # UID/GID Mapping + if self.uid_map or self.gid_map: + tar_members = tarfile.open(tar_file).getmembers() + balance_uid_gid_maps(self.uid_map, self.gid_map) + for uid, gid in zip(self.uid_map, self.gid_map): + self.map_id(tar_members, uid, gid) + # Shutdown guestfs instance to avoid hot-plugging of devices. self.g.umount('/') @@ -145,6 +163,27 @@ class Build_QCOW2_Image(object): logger=logger) self.tar_in(tar_file, devices[i]) + def map_id(self, tar_members, map_uid, map_gid): + """ + Remapping ownership of all files inside image. + + map_gid and map_uid: Contain integers in a list with format: + [<start>, <target>, <count>] + """ + if map_uid: + uid_opts = get_mapping_opts(map_uid) + if map_gid: + gid_opts = get_mapping_opts(map_gid) + + for member in tar_members: + old_uid = member.uid + old_gid = member.gid + + new_uid = get_map_id(old_uid, uid_opts) if map_uid else -1 + new_gid = get_map_id(old_gid, gid_opts) if map_gid else -1 + if new_uid != -1 or new_gid != -1: + self.g.lchown(new_uid, new_gid, os.path.join('/', member.name)) + def get_compression_type(tar_file): """ diff --git a/src/virtBootstrap/virt_bootstrap.py b/src/virtBootstrap/virt_bootstrap.py index 0c1626c..6c1e944 100755 --- a/src/virtBootstrap/virt_bootstrap.py +++ b/src/virtBootstrap/virt_bootstrap.py @@ -124,6 +124,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/test_build_qcow2_image.py b/tests/test_build_qcow2_image.py index 782761e..5ed0ade 100644 --- a/tests/test_build_qcow2_image.py +++ b/tests/test_build_qcow2_image.py @@ -229,3 +229,50 @@ class TestBuild_Image(unittest.TestCase): progress_cb=mock.Mock() ) self.check_qcow2_images(images) + + +class TestIDMapping(TestBuild_Image): + """ + Ensures that UID/GID mapping for qcow2 images work as expected. + """ + + def set_ownership_mapping(self, uid_map, gid_map): + """ + Update the values of UID/GID in ROOTFS_TREE to match the ownership + mapping and thus can be used from check_members() + """ + for user in ROOTFS_TREE: + user_uid = ROOTFS_TREE[user]['uid'] + user_gid = ROOTFS_TREE[user]['gid'] + + for start, tartget, rng in uid_map: + if user_uid >= start and user_uid <= start + rng: + diff = ROOTFS_TREE[user]['uid'] - start + ROOTFS_TREE[user]['uid'] = tartget + diff + + for start, tartget, rng in gid_map: + if user_gid >= start and user_gid <= start + rng: + diff = ROOTFS_TREE[user]['gid'] - start + ROOTFS_TREE[user]['gid'] = tartget + diff + + def runTest(self): + """ + Create qcow2 image from each dummy tarfile using FileSource + and apply UID/GID mappings. + """ + images = [] + idmap = [[1000, 2000, 10], [0, 1000, 10], [500, 500, 10]] + for archive in TAR_FILES: + dest = os.path.join(IMAGES_DIR, archive.split('.')[0]) + images.append(os.path.join(dest, "layer-0.qcow2")) + uri = os.path.join(TAR_DIR, archive) + virt_bootstrap.bootstrap( + uri=uri, + dest=dest, + fmt='qcow2', + progress_cb=mock.Mock(), + uid_map=idmap, + gid_map=idmap + ) + self.set_ownership_mapping(idmap, idmap) + self.check_qcow2_images(images) -- 2.13.4 _______________________________________________ virt-tools-list mailing list virt-tools-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/virt-tools-list