iso-wrapper: Add utility for mounting images

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ář <lsedlar@redhat.com>
This commit is contained in:
Lubomír Sedlář 2017-02-20 10:34:55 +01:00
parent 05a666fb3b
commit 306f7e69b0
3 changed files with 49 additions and 15 deletions

View File

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

View File

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

View File

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