Repository: osbuild/osbuild
Pull request #1529 (2024-01-05 11:47:01)

stages(grub2): allow pulling efi binaries from alternative efi roots
The EFI binaries are currently pulled from a hardcoded path in the buildroot. When moving to containers as buildroots this will no longer work as they have an alternative layout. This is an easy "fix" - make the location of the `EFI/` directory configurable.

This allows us set `efi_root` to `/usr/lib/bootupd/updates/EFI/` and keep our existing `bootc-image-builder` workflow.

Note that this may actually not be the desired solution and instead we want the new `bootupd`: https://github.com/osbuild/osbuild/pull/1519

mvo5: After talking to @achilleas-k it was agreed that this is a genuinely useful feature even if we switch to bootupd shortly for the bootc-image-builder.

Patch:
diff --git a/stages/org.osbuild.container-deploy b/stages/org.osbuild.container-deploy
index c9546864..466c9b9b 100755
--- a/stages/org.osbuild.container-deploy
+++ b/stages/org.osbuild.container-deploy
@@ -5,6 +5,7 @@ Deploy a container.
 Buildhost commands used: podman skopeo
 """
 import contextlib
+import os
 import random
 import string
 import subprocess
@@ -54,6 +55,8 @@ def main(inputs, output):
     assert len(images) == 1
     image = list(images.values())[0]
 
+    # skopeo needs /var/tmp but the bwrap env is minimal and may not have it
+    os.makedirs("/var/tmp", mode=0o1777, exist_ok=True)
     # We cannot use a tmpdir as storage here because of
     # https://github.com/containers/storage/issues/1779 so instead
     # just pick a random suffix. This runs inside bwrap which gives a
diff --git a/stages/org.osbuild.grub2 b/stages/org.osbuild.grub2
index 93ccbd35..72e6e1a0 100755
--- a/stages/org.osbuild.grub2
+++ b/stages/org.osbuild.grub2
@@ -152,6 +152,11 @@ SCHEMA = """
          "examples": ["fedora"],
          "pattern": "^(.+)$"
       },
+      "efi_src_dir": {
+        "type": "string",
+         "description": "The source path to use for the EFI/ binaries when installing is set to True",
+         "default": "/boot/efi/EFI"
+      },
       "install": {
         "description": "Install EFI binaries and data from the build root",
         "type": "boolean",
@@ -347,10 +352,10 @@ def copy_font(tree):
     shutil.copy2("/usr/share/grub/unicode.pf2", f"{tree}/boot/grub2/fonts/")
 
 
-def copy_efi_data(tree, vendor):
+def copy_efi_data(tree, efi_src_dir, vendor):
     """Copy the EFI binaries & data into /boot/efi"""
     for d in ['BOOT', vendor]:
-        source = f"/boot/efi/EFI/{d}"
+        source = f"{efi_src_dir}/{d}"
         target = f"{tree}/boot/efi/EFI/{d}/"
         shutil.copytree(source, target,
                         symlinks=False)
@@ -573,13 +578,14 @@ def main(tree, options):
         # - grub.cfg: needs to be generated, either the canonical one
         #     or a shim one that redirects to the canonical one in
         #     /boot/grub2 in case of hybrid boot (see above)
+        efi_src_dir = uefi.get("efi_src_dir", "/boot/efi/EFI")
         vendor = uefi["vendor"]
         unified = uefi.get("unified", False)
 
         # EFI binaries and accompanying data can be installed from
         # the build root instead of using an rpm package
         if uefi.get('install', False):
-            copy_efi_data(tree, vendor)
+            copy_efi_data(tree, efi_src_dir, vendor)
 
         grubcfg = f"boot/efi/EFI/{vendor}/grub.cfg"
         if hybrid or unified:


Test patch:
diff --git a/stages/test/test_container_deploy.py b/stages/test/test_container_deploy.py
index b1afae8e..6ccfed32 100644
--- a/stages/test/test_container_deploy.py
+++ b/stages/test/test_container_deploy.py
@@ -5,6 +5,7 @@ import os.path
 import random
 import string
 import subprocess
+from unittest.mock import call, patch
 
 import pytest
 
@@ -68,7 +69,10 @@ def test_container_deploy_integration(tmp_path):
     }
     output_dir = tmp_path / "output"
 
-    stage.main(inputs, output_dir)
+    with patch("os.makedirs", wraps=os.makedirs) as mocked_makedirs:
+        stage.main(inputs, output_dir)
 
     assert output_dir.exists()
     assert (output_dir / "file1").read_bytes() == b"file1 from final layer"
+
+    assert mocked_makedirs.call_args_list == [call("/var/tmp", mode=0o1777, exist_ok=True)]
diff --git a/stages/test/test_grub2.py b/stages/test/test_grub2.py
new file mode 100644
index 00000000..b95cbd46
--- /dev/null
+++ b/stages/test/test_grub2.py
@@ -0,0 +1,32 @@
+#!/usr/bin/python3
+
+import os.path
+
+from osbuild.testutil import make_fake_tree
+from osbuild.testutil.imports import import_module_from_path
+
+
+def test_grub2_copy_efi_data(tmp_path):
+    stage_path = os.path.join(os.path.dirname(__file__), "../org.osbuild.grub2")
+    stage = import_module_from_path("stage", stage_path)
+
+    fake_efi_src_dir = tmp_path / "fake-efi/EFI"
+    make_fake_tree(fake_efi_src_dir, {
+        "fedora/a.shim": "fake shim",
+        "BOOT/BOOTX64.EFI": "I'm not real",
+    })
+
+    test_options = {
+        "rootfs": {
+            "label": "my-rootfs",
+        },
+        "uefi": {
+            "install": True,
+            "efi_src_dir": fake_efi_src_dir,
+            "vendor": "fedora",
+        },
+    }
+    fake_tree = tmp_path / "tree"
+    stage.main(fake_tree, test_options)
+    assert (fake_tree / "boot/efi/EFI/fedora/a.shim").exists()
+    assert (fake_tree / "boot/efi/EFI/BOOT/BOOTX64.EFI").exists()