stages/grub2.inst: grub2-mkimage in tmpdir

Use a temporary directory for the output of grub2-mkimage.

This makes the stage clean up the grub2-core.img from the build root
after its done.  It also has the nice side-effect that unit tests that
call the stage are independent.  Previously, a bug in the stage *might*
have been missed if a certain configuration of the stage was not
creating the grub2-core.img.  One unit test could create an image at the fixed path
(/var/tmp/grub2-core.img) and then another one could call the stage with
the buggy configuration but the `shutil.copyfile()` call at the end of
the stage would succeed because it would find the image from the
previous stage run.

To accommodate for this change, the unit test with the mocked run call
is adjusted to intercept the random tmp output path and use it to create
a fake file for the stage to succeed.
This commit is contained in:
Achilleas Koutsou 2025-03-05 17:36:34 +01:00 committed by Simon de Vlieger
parent dc24a131e9
commit f2ab07cf85
2 changed files with 29 additions and 11 deletions

View file

@ -4,6 +4,7 @@ import shutil
import struct
import subprocess
import sys
import tempfile
from typing import BinaryIO, Dict
import osbuild.api
@ -70,12 +71,11 @@ def write_core_image(core_f, image_f, location, sector_size):
shutil.copyfileobj(core_f, image_f)
def core_mkimage(platform: str, prefix: str, options: Dict):
def core_mkimage(platform: str, prefix: str, options: Dict, core_dir: str):
pt_label = options["partlabel"]
fs_type = options["filesystem"]
os.makedirs("/var/tmp/", exist_ok=True)
core_path = "/var/tmp/grub2-core.img"
core_path = os.path.join(core_dir, "grub2-core.img")
# Create the level-2 & 3 stages of the bootloader, aka the core
# it consists of the kernel plus the core modules required to
@ -174,14 +174,16 @@ def main(tree, options):
else:
prefix = options["prefix"]["path"]
print(f"prefix: {prefix}")
core_path = core_mkimage(platform, prefix, options["core"])
location = options.get("location")
if location:
patch_core(location, core_path, image, sector_size, platform)
else:
# If location isn't set, use the image file as-is instead of with the MBR
shutil.copyfile(core_path, image)
with tempfile.TemporaryDirectory() as core_tmpdir:
core_path = core_mkimage(platform, prefix, options["core"], core_tmpdir)
location = options.get("location")
if location:
patch_core(location, core_path, image, sector_size, platform)
else:
# If location isn't set, use the image file as-is instead of with the MBR
shutil.copyfile(core_path, image)
return 0

View file

@ -89,14 +89,30 @@ def test_grub2_partition_mocked(mocked_run, tmp_path, stage_module):
"path": "/boot/",
},
}
output_path = ""
def mock_side_effect(args, **_):
"""
Intercept the args to subprocess.run() to leak the random output path and create the file that's expected by the
shutil.copy() in the stage's main().
"""
nonlocal output_path
output_path = args[args.index("--output") + 1]
with open(output_path, "wb") as fake_image_file:
fake_image_file.write(b"I am a grub core image")
mocked_run.side_effect = mock_side_effect
stage_module.main(tmp_path, options)
assert os.path.basename(output_path) == "grub2-core.img"
assert mocked_run.call_args_list == [
call(["grub2-mkimage", "--verbose",
"--directory", "/usr/lib/grub/some-platform",
"--prefix", "(,gpt1)/boot/",
"--format", "some-platform",
"--compression", "auto",
"--output", "/var/tmp/grub2-core.img",
"--output", output_path,
"part_gpt", "ext2"], check=True),
]