[virt-tools-list] [virt-bootstrap] [PATCH v5 08/13] Enable UID/GID mapping for qcow2

Radostin Stoyanov rstoyanov1 at gmail.com
Fri Aug 4 14:30:44 UTC 2017


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




More information about the virt-tools-list mailing list