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

Cedric Bosdonnat cbosdonnat at suse.com
Tue Aug 29 07:42:26 UTC 2017


On Sat, 2017-08-26 at 21:42 +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.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))

ACK
--
Cedric




More information about the virt-tools-list mailing list