Repository: osbuild/osbuild
Pull request #2058 (2025-03-31 14:16:49)

[RHEL-9.6.0] Backport https://github.com/osbuild/osbuild/pull/2038 (RHEL-85560)
Backport https://github.com/osbuild/osbuild/pull/2038 to `rhel-9.6.0` branch.


Patch:
diff --git a/stages/org.osbuild.grub2.inst b/stages/org.osbuild.grub2.inst
index ecc9d2a8..4f4bf6ef 100755
--- a/stages/org.osbuild.grub2.inst
+++ b/stages/org.osbuild.grub2.inst
@@ -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
@@ -86,12 +86,11 @@ def core_mkimage(platform: str, prefix: str, options: Dict):
     # read the partition and its filesystem containing said modules
     # and the grub configuration [NB: efi systems work differently]
 
+    gformat = platform
     if platform == "i386-pc":
         modules = ["biosdisk"]
-        gformat = "i386-pc"
     else:
         modules = []
-        gformat = "i386-pc"
 
     if pt_label in ["dos", "mbr"]:
         modules += ["part_msdos"]
@@ -175,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
 


Test patch:
diff --git a/stages/test/test_grub2_inst.py b/stages/test/test_grub2_inst.py
index 5677112c..a49229e1 100644
--- a/stages/test/test_grub2_inst.py
+++ b/stages/test/test_grub2_inst.py
@@ -1,6 +1,7 @@
 #!/usr/bin/python3
 import os
 import subprocess
+from unittest.mock import call, patch
 
 import pytest
 
@@ -68,3 +69,50 @@ def test_grub2_iso9660(tmp_path, stage_module):
     }
     stage_module.main(treedir, options)
     assert os.path.exists(treedir / "eltorito.img")
+
+
+@patch("subprocess.run")
+def test_grub2_partition_mocked(mocked_run, tmp_path, stage_module):
+    options = {
+        "filename": "disk.img",
+        "platform": "some-platform",
+        "location": 0,
+        "core": {
+            "type": "mkimage",
+            "partlabel": "gpt",
+            "filesystem": "ext4",
+        },
+        "prefix": {
+            "type": "partition",
+            "partlabel": "gpt",
+            "number": 0,
+            "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", output_path,
+              "part_gpt", "ext2"], check=True),
+    ]