From 306f7e69b08ca378a0bc300049a9ee78b708c8ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lubom=C3=ADr=20Sedl=C3=A1=C5=99?= Date: Mon, 20 Feb 2017 10:34:55 +0100 Subject: [PATCH] iso-wrapper: Add utility for mounting images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch refactors logic for creating a temporary mount point, mounting an image, running arbitrary code on it, unmounting the image and removing the mount point. It immediately uses it in the buildinstall phase. Similar mounting is present in product_img phase as well, but due to different usage pattern it's not changed yet. Signed-off-by: Lubomír Sedlář --- pungi/phases/buildinstall.py | 23 ++++++++--------------- pungi/wrappers/iso.py | 19 +++++++++++++++++++ tests/test_iso_wrapper.py | 22 ++++++++++++++++++++++ 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/pungi/phases/buildinstall.py b/pungi/phases/buildinstall.py index 709c6bf7..50d690c7 100644 --- a/pungi/phases/buildinstall.py +++ b/pungi/phases/buildinstall.py @@ -27,7 +27,7 @@ from productmd.images import Image from pungi.arch import get_valid_arches from pungi.util import get_buildroot_rpms, get_volid, get_arch_variant_data -from pungi.util import get_file_size, get_mtime, failable, makedirs, fusermount +from pungi.util import get_file_size, get_mtime, failable, makedirs from pungi.wrappers.lorax import LoraxWrapper from pungi.wrappers.kojiwrapper import KojiWrapper from pungi.wrappers import iso @@ -267,22 +267,15 @@ def tweak_buildinstall(compose, src, dst, arch, variant, label, volid, kickstart for image in images: if not os.path.isfile(image): continue - mount_tmp_dir = compose.mkdtemp(prefix="tweak_buildinstall") - # use guestmount to mount the image, which doesn't require root privileges - # LIBGUESTFS_BACKEND=direct: running qemu directly without libvirt - cmd = ["LIBGUESTFS_BACKEND=direct", "guestmount", "-a", image, "-m", "/dev/sda", mount_tmp_dir] - run(cmd) - for config in BOOT_CONFIGS: - config_path = os.path.join(tmp_dir, config) - config_in_image = os.path.join(mount_tmp_dir, config) + with iso.mount(image, logger=compose._logger) as mount_tmp_dir: + for config in BOOT_CONFIGS: + config_path = os.path.join(tmp_dir, config) + config_in_image = os.path.join(mount_tmp_dir, config) - if os.path.isfile(config_in_image): - cmd = ["cp", "-v", "--remove-destination", config_path, config_in_image] - run(cmd) - - fusermount(mount_tmp_dir) - shutil.rmtree(mount_tmp_dir) + if os.path.isfile(config_in_image): + cmd = ["cp", "-v", "--remove-destination", config_path, config_in_image] + run(cmd) # HACK: make buildinstall files world readable run("chmod -R a+rX %s" % pipes.quote(tmp_dir)) diff --git a/pungi/wrappers/iso.py b/pungi/wrappers/iso.py index 9b1a585b..2ee9c79d 100644 --- a/pungi/wrappers/iso.py +++ b/pungi/wrappers/iso.py @@ -18,8 +18,10 @@ import os import sys import pipes from fnmatch import fnmatch +import contextlib from kobo.shortcuts import force_list, relative_path, run +from pungi import util # HACK: define cmp in python3 @@ -392,3 +394,20 @@ def cmp_graft_points(x, y): return 1 return cmp(x, y) + + +@contextlib.contextmanager +def mount(image, logger=None): + """Mount an image and make sure it's unmounted. + + The yielded path will only be valid in the with block and is removed once + the image is unmounted. + """ + # use guestmount to mount the image, which doesn't require root privileges + # LIBGUESTFS_BACKEND=direct: running qemu directly without libvirt + with util.temp_dir(prefix='iso-mount-') as mount_dir: + run(["LIBGUESTFS_BACKEND=direct", "guestmount", "-a", image, "-m", "/dev/sda", mount_dir]) + try: + yield mount_dir + finally: + util.fusermount(mount_dir, logger=logger) diff --git a/tests/test_iso_wrapper.py b/tests/test_iso_wrapper.py index 6c2dfd20..18d15623 100644 --- a/tests/test_iso_wrapper.py +++ b/tests/test_iso_wrapper.py @@ -37,3 +37,25 @@ class TestIsoUtils(unittest.TestCase): self.assertEqual(mock_run.call_args_list, [mock.call(['/usr/bin/checkisomd5', '--md5sumonly', 'dummy.iso'])]) self.assertGreater(len(logger.mock_calls), 0) + + @mock.patch('pungi.util.run_unmount_cmd') + @mock.patch('pungi.wrappers.iso.run') + def test_mount_iso(self, mock_run, mock_unmount): + with iso.mount('dummy') as temp_dir: + self.assertTrue(os.path.isdir(temp_dir)) + self.assertEqual(len(mock_run.call_args_list), 1) + self.assertEqual(len(mock_unmount.call_args_list), 1) + self.assertFalse(os.path.isdir(temp_dir)) + + @mock.patch('pungi.util.run_unmount_cmd') + @mock.patch('pungi.wrappers.iso.run') + def test_mount_iso_always_unmounts(self, mock_run, mock_unmount): + try: + with iso.mount('dummy') as temp_dir: + self.assertTrue(os.path.isdir(temp_dir)) + raise RuntimeError() + except RuntimeError: + pass + self.assertEqual(len(mock_run.call_args_list), 1) + self.assertEqual(len(mock_unmount.call_args_list), 1) + self.assertFalse(os.path.isdir(temp_dir))