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))