[virt-tools-list] [virt-bootstrap] [PATCH v7 15/26] Enable UID/GID mapping for qcow2
Radostin Stoyanov
rstoyanov1 at gmail.com
Sat Aug 26 20:42:04 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.qcow2
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 | 4 +-
tests/docker_source.py | 23 ++++++++++
tests/file_source.py | 10 ++++
6 files changed, 129 insertions(+), 1 deletion(-)
diff --git a/src/virtBootstrap/sources/docker_source.py b/src/virtBootstrap/sources/docker_source.py
index a6ea3e6..a2fc8b9 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)
@@ -280,6 +287,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 5fb5d8c..a1b648e 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
@@ -532,6 +535,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 length.
diff --git a/src/virtBootstrap/virt_bootstrap.py b/src/virtBootstrap/virt_bootstrap.py
index f970838..f0abac4 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)
@@ -138,7 +140,7 @@ def bootstrap(uri, dest,
logger.info("Setting password of the root account")
utils.set_root_password(fmt, dest, root_password)
- if fmt == "dir" and uid_map or gid_map:
+ if fmt == "dir" and (uid_map or gid_map):
logger.info("Mapping UID/GID")
utils.mapping_uid_gid(dest, uid_map, gid_map)
diff --git a/tests/docker_source.py b/tests/docker_source.py
index 9dc25d9..eeea379 100644
--- a/tests/docker_source.py
+++ b/tests/docker_source.py
@@ -263,6 +263,29 @@ class TestQcow2DockerSource(Qcow2ImageAccessor):
g.umount('/')
g.shutdown()
+ def test_qcow2_ownership_mapping(self):
+ """
+ Ensures that UID/GID mapping works correctly for qcow2 conversion.
+ """
+ self.uid_map = [[1000, 2000, 10], [0, 1000, 10], [500, 500, 10]]
+ self.gid_map = [[1000, 2000, 10], [0, 1000, 10], [500, 500, 10]]
+ layers_rootfs = self.call_bootstrap()
+
+ g = guestfs.GuestFS(python_return_dict=True)
+ g.add_drive_opts(
+ self.get_image_path(len(layers_rootfs)),
+ readonly=True
+ )
+
+ g.launch()
+ for rootfs in layers_rootfs[::-1]:
+ self.rootfs_tree = rootfs
+ self.apply_mapping()
+ g.mount('/dev/sda', '/')
+ self.check_image(g)
+ g.umount('/')
+ g.shutdown()
+
class TestDockerSource(unittest.TestCase):
"""
diff --git a/tests/file_source.py b/tests/file_source.py
index 391ca48..9ffd4e3 100644
--- a/tests/file_source.py
+++ b/tests/file_source.py
@@ -105,3 +105,13 @@ class TestQcow2FileSource(Qcow2ImageAccessor):
"""
self.call_bootstrap()
self.check_qcow2_images(self.get_image_path())
+
+ def test_qcow2_ownership_mapping(self):
+ """
+ Ensures that UID/GID mapping works correctly for qcow2 conversion.
+ """
+ self.uid_map = [[1000, 2000, 10], [0, 1000, 10], [500, 500, 10]]
+ self.gid_map = [[1000, 2000, 10], [0, 1000, 10], [500, 500, 10]]
+ self.call_bootstrap()
+ self.apply_mapping()
+ self.check_qcow2_images(self.get_image_path(1))
--
2.13.5
More information about the virt-tools-list
mailing list