[virt-tools-list] [virt-bootstrap] [PATCH v6 15/26] Enable UID/GID mapping for qcow2

Radostin Stoyanov rstoyanov1 at gmail.com
Thu Aug 17 09:39:53 UTC 2017


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.
-- 
2.13.5




More information about the virt-tools-list mailing list