diff --git a/stages/org.osbuild.bootupd b/stages/org.osbuild.bootupd index 22da890d..b4d96828 100755 --- a/stages/org.osbuild.bootupd +++ b/stages/org.osbuild.bootupd @@ -9,6 +9,7 @@ and GRUB for BIOS firmware on x86_64. The project is deployed in Fedora CoreOS and derivatives """ +import contextlib import os import subprocess import sys @@ -67,6 +68,19 @@ SCHEMA_2 = r""" """ +@contextlib.contextmanager +def bind_mounts(sources, dest_root): + for src in sources: + dst = os.path.join(dest_root, src.lstrip("/")) + subprocess.run(["mount", "--rbind", src, dst], check=True) + try: + yield + finally: + for src in sources: + dst = os.path.join(dest_root, src.lstrip("/")) + subprocess.run(["umount", "--recursive", dst], check=False) + + def main(args, options): deployment = options.get("deployment", None) static_configs = options.get("static-configs", False) @@ -101,22 +115,11 @@ def main(args, options): # want to make sure the version used matches the target and not # risk any inconsistencies with the build root). Let's set up # and chroot to run the bootupctl command from the target. - submounts = ['dev', 'proc', 'sys', 'run', 'var', 'tmp'] - for mnt in submounts: - subprocess.run(['mount', '--rbind', - os.path.join("/", mnt), - os.path.join(root, mnt)], - check=True) - try: + with bind_mounts(['/dev', '/proc', '/sys', '/run', '/var', '/tmp'], root): cmd = ['chroot', root, '/usr/bin/bootupctl', 'backend', 'install'] cmd.extend(bootupd_args) cmd.append(mounts) subprocess.run(cmd, check=True) - finally: - for mnt in submounts: - subprocess.run(['umount', '--recursive', - os.path.join(root, mnt)], - check=False) return 0 diff --git a/stages/test/test_bootupd.py b/stages/test/test_bootupd.py index d7d2423e..e35a1d84 100644 --- a/stages/test/test_bootupd.py +++ b/stages/test/test_bootupd.py @@ -1,39 +1,42 @@ #!/usr/bin/python3 import os.path -import subprocess -from unittest import mock +from unittest.mock import call, patch import pytest import osbuild.meta -from osbuild.testutil import has_executable, make_fake_input_tree from osbuild.testutil.imports import import_module_from_path @pytest.mark.parametrize("test_data,expected_err", [ # bad - ({"deployment": "totally"}, "'totally' is not of type 'object'"), + ({"deployment": "must-be-object"}, "'must-be-object' is not of type 'object'"), ({"deployment": {"osname": "some-os"}}, "'ref' is a required property"), ({"deployment": {"ref": "some-ref"}}, "'osname' is a required property"), - ({"deployment": {"osname": "some-os", "ref": "some-ref", "serial": "yo"}}, "'yo' is not of type 'number'"), + ({"deployment": {"osname": "some-os", "ref": "some-ref", "serial": "must-be-number"}}, + "'must-be-number' is not of type 'number'"), ({"random": "property"}, "Additional properties are not allowed"), ({"bios": {}}, "'device' is a required property"), - ({"bios": "yes"}, "'yes' is not of type 'object'"), + ({"bios": "must-be-object"}, "'must-be-object' is not of type 'object'"), # good ({}, ""), - ({"deployment": { - "osname": "some-os", - "ref": "some-ref", - "serial": 1, - }, - "static-configs": True, - "bios": { - "device": "/dev/sda", - }}, "") + ({ + "deployment": + { + "osname": "some-os", + "ref": "some-ref", + "serial": 1, + }, + "static-configs": True, + "bios": + { + "device": "/dev/sda", + }, + }, "") ]) -def test_schema_validation_bootupd(test_data, expected_err): +def test_bootupd_schema_validation(test_data, expected_err): name = "org.osbuild.bootupd" root = os.path.join(os.path.dirname(__file__), "../..") mod_info = osbuild.meta.ModuleInfo.load(root, "Stage", name) @@ -54,3 +57,33 @@ def test_schema_validation_bootupd(test_data, expected_err): assert len(res.errors) == 1, [e.as_dict() for e in res.errors] err_msgs = [e.as_dict()["message"] for e in res.errors] assert expected_err in err_msgs[0] + + +@patch("subprocess.run") +def test_bootupd_defaults(mocked_run): + stage_path = os.path.join(os.path.dirname(__file__), "../org.osbuild.bootupd") + stage = import_module_from_path("bootupd_stage", stage_path) + + args = { + "paths": { + "mounts": "/run/osbuild/mounts", + }, + } + options = {} + stage.main(args, options) + + assert mocked_run.call_args_list == [ + call(["mount", "--rbind", "/dev", "/run/osbuild/mounts/dev"], check=True), + call(["mount", "--rbind", "/proc", "/run/osbuild/mounts/proc"], check=True), + call(["mount", "--rbind", "/sys", "/run/osbuild/mounts/sys"], check=True), + call(["mount", "--rbind", "/run", "/run/osbuild/mounts/run"], check=True), + call(["mount", "--rbind", "/var", "/run/osbuild/mounts/var"], check=True), + call(["mount", "--rbind", "/tmp", "/run/osbuild/mounts/tmp"], check=True), + call(["chroot", "/run/osbuild/mounts", "/usr/bin/bootupctl", "backend", "install", "/run/osbuild/mounts"], check=True), + call(["umount", "--recursive", "/run/osbuild/mounts/dev"], check=False), + call(["umount", "--recursive", "/run/osbuild/mounts/proc"], check=False), + call(["umount", "--recursive", "/run/osbuild/mounts/sys"], check=False), + call(["umount", "--recursive", "/run/osbuild/mounts/run"], check=False), + call(["umount", "--recursive", "/run/osbuild/mounts/var"], check=False), + call(["umount", "--recursive", "/run/osbuild/mounts/tmp"], check=False), + ]