From c80770da3bd382b3f70b19599ccc9db4ae25511f Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Tue, 23 Sep 2025 09:09:22 +0200 Subject: [PATCH 1/3] many: allow `bootc` `image-builder >= 34` contains experimental support for building a subset of image types from bootable containers. Let's allow passing these options along through the plugin. Using this option will mean that the builder needs access to where the bootable container reference is to be pulled from. It also implies that `podman` is installed in the build root. Signed-off-by: Simon de Vlieger --- plugin/builder/image_builder.py | 42 +++++++++++++++++++ plugin/cli/image_builder.py | 35 ++++++++++++++++ plugin/hub/image_builder.py | 15 +++++++ test/unit/test_builder.py | 74 +++++++++++++++++++++++++++++++++ 4 files changed, 166 insertions(+) diff --git a/plugin/builder/image_builder.py b/plugin/builder/image_builder.py index bc59c40..c5a9e40 100644 --- a/plugin/builder/image_builder.py +++ b/plugin/builder/image_builder.py @@ -443,6 +443,48 @@ def handler( if ostree_parent: cmd.extend(["--ostree-parent", ostree_parent]) + # If bootc information is available pass it on to the command + bootc = self.opts.get("bootc") + if bootc: + # The general ref is the 'payload' container for the image type + bootc_ref = bootc.get("ref") + if bootc_ref: + cmd.extend(["--bootc-ref", bootc_ref]) + + # We need to pull the containers into local storage, this + # requires the podman executable to be available in our buildroot + exit_code = broot.mock( + ["--cwd", broot.tmpdir(within=True), "--chroot", "--", + "podman", "pull", bootc_ref] + ) + + if exit_code != 0: + raise koji.GenericError("`podman` failed to pull container") + + # Normally the 'payload' container is used as the build root in + # image-builder. This can be overridden with a separate container + # reference. + bootc_build_ref = bootc.get("build-ref") + if bootc_build_ref: + cmd.extend(["--bootc-build-ref", bootc_build_ref]) + + # Same thing for the build container, it needs to be pulled before + # executing `image-builder`. + exit_code = broot.mock( + ["--cwd", broot.tmpdir(within=True), "--chroot", "--", + "podman", "pull", bootc_build_ref] + ) + + if exit_code != 0: + raise koji.GenericError("`podman` failed to pull container") + + # image-builder tries to determine the root filesystem to use based + # on container metadata and/or contents, for some containers this isnt' + # available so there's an option to set it explicitly + bootc_default_fs = bootc.get("default-fs") + if bootc_default_fs: + cmd.extend(["--bootc-defaultfs", bootc_default_fs]) + # If a seed is set, set it seed = opts.get("seed", None) if seed is not None: diff --git a/plugin/cli/image_builder.py b/plugin/cli/image_builder.py index c6e0645..795eebb 100644 --- a/plugin/cli/image_builder.py +++ b/plugin/cli/image_builder.py @@ -59,6 +59,27 @@ def handle_image_builder_build(gopts, session, args): help="URL to the OSTree repo for OSTree commit image types", ) + parser.add_option( + "--bootc-ref", + type=str, + dest="bootc_ref", + help="Ref to the bootable container for bootc image types", + ) + + parser.add_option( + "--bootc-build-ref", + type=str, + dest="bootc_build_ref", + help="Ref to the bootable container build root container for bootc image types", + ) + + parser.add_option( + "--bootc-default-fs", + type=str, + dest="bootc_default_fs", + help="Set the default root filesystem to use when not defined in the container", + ) + parser.add_option( "--release", help="Override release of the output, otherwise determined based on 'target' and 'name'", @@ -107,6 +128,17 @@ def handle_image_builder_build(gopts, session, args): if opts.ostree_url: ostree["url"] = opts.ostree_url + bootc = {} + + if opts.bootc_ref: + bootc["ref"] = opts.bootc_ref + + if opts.bootc_build_ref: + bootc["build-ref"] = opts.bootc_build_ref + + if opts.bootc_default_fs: + bootc["default-fs"] = opts.bootc_default_fs + task_opts = { "scratch": opts.scratch, } @@ -114,6 +146,9 @@ def handle_image_builder_build(gopts, session, args): if ostree: task_opts["ostree"] = ostree + if bootc: + task_opts["bootc"] = bootc + if opts.repo: task_opts["repos"] = opts.repo diff --git a/plugin/hub/image_builder.py b/plugin/hub/image_builder.py index 544321e..73fd07d 100644 --- a/plugin/hub/image_builder.py +++ b/plugin/hub/image_builder.py @@ -47,6 +47,16 @@ "url": {"type": "string"}, }, }, + "bootc": { + "title": "bootc specific options", + "type": "object", + "additionalProperties": False, + "properties": { + "ref": {"type": "string"}, + "build-ref": {"type": "string"}, + "default-fs": {"type": "string"}, + }, + }, "options": { "title": "Optional arguments", "type": "object", @@ -75,6 +85,11 @@ "$ref": "#/definitions/ostree", "descriptions": "Additional ostree options", }, + "bootc": { + "type": "object", + "$ref": "#/definitions/bootc", + "descriptions": "Additional bootc options", + }, "blueprint": { "type": "object", "description": "Blueprint", diff --git a/test/unit/test_builder.py b/test/unit/test_builder.py index 4accda4..f0c2ea7 100644 --- a/test/unit/test_builder.py +++ b/test/unit/test_builder.py @@ -306,4 +306,78 @@ def test_build_arch_task_seed(koji_mock_kojid): ], ] +def test_build_arch_task_bootc(koji_mock_kojid): + import plugin.builder.image_builder as builder + + t = builder.ImageBuilderBuildArchTask() + + t.id = None + t.session = None + t.options = MockOptions(topurl="/") + t.workdir = None + t.handler( + "Fedora-bootc", + "42", + "1", + "x86_64", + ["qcow2"], + {"build_tag": "f42-build", "build_tag_name": "f42-build"}, + {"extra": {"mock.new_chroot": 0}}, + {"id": 1}, + { + "bootc": { + "ref": "quay.io/centos-bootc/centos-bootc:stream9", + "build-ref": "quay.io/centos-bootc/centos-bootc:stream10", + "default-fs": "ext4", + } + }, + ) + + assert koji_mock_kojid.buildroot.mock_calls == [ + [ + "--cwd", + str(koji_mock_kojid.buildroot._tmpdir), + "--chroot", + "--", + "podman", + "pull", + "quay.io/centos-bootc/centos-bootc:stream9", + ], + [ + "--cwd", + str(koji_mock_kojid.buildroot._tmpdir), + "--chroot", + "--", + "podman", + "pull", + "quay.io/centos-bootc/centos-bootc:stream10", + ], + [ + "--cwd", + str(koji_mock_kojid.buildroot._tmpdir), + "--chroot", + "--", + "sh", + str(koji_mock_kojid.buildroot._tmpdir) + "/mock-wrap", + "image-builder", + "-v", + "build", + "--use-librepo=false", + "--force-repo", + "//repos/f42-build/1/$arch", + "--with-sbom", + "--with-manifest", + "--bootc-ref", + "quay.io/centos-bootc/centos-bootc:stream9", + "--bootc-build-ref", + "quay.io/centos-bootc/centos-bootc:stream10", + "--bootc-defaultfs", + "ext4", + "--output-dir", + "/builddir/output", + "--output-name", + "Fedora-bootc-42-1.x86_64", + "qcow2", + ], + ] From 99d2a942944a3a70a9f01815a5e02c20aeba06de Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Fri, 27 Mar 2026 07:12:51 +0100 Subject: [PATCH 2/3] many/bootc: `installer-payload-ref` For some bootc image types (installers) it is potentially possible to set a payload reference that is embedded onto the ISO. Let's map that into the plugin as well. Signed-off-by: Simon de Vlieger --- plugin/builder/image_builder.py | 16 ++++++++++++++++ plugin/cli/image_builder.py | 10 ++++++++++ plugin/hub/image_builder.py | 1 + 3 files changed, 27 insertions(+) diff --git a/plugin/builder/image_builder.py b/plugin/builder/image_builder.py index c5a9e40..4608ab7 100644 --- a/plugin/builder/image_builder.py +++ b/plugin/builder/image_builder.py @@ -478,6 +478,22 @@ def handler( if exit_code != 0: raise koji.GenericError("`podman` failed to pull container") + # For installers and additional payload container might need to be + # embedded, let's pull that too. + bootc_installer_payload_ref = bootc.get("installer-payload-ref") + if bootc_installer_payload_ref: + cmd.extend(["--bootc-installer-payload-ref", bootc_installer_payload_ref]) + + # Same thing for the installer-payload container, it needs to be pulled before + # executing `image-builder`. + exit_code = broot.mock( + ["--cwd", broot.tmpdir(within=True), "--chroot", "--", + "podman", "pull", bootc_installer_payload_ref] + ) + + if exit_code != 0: + raise koji.GenericError("`podman` failed to pull container") + # image-builder tries to determine the root filesystem to use based # on container metadata and/or contents, for some containers this isnt' # available so there's an option to set it explicitly diff --git a/plugin/cli/image_builder.py b/plugin/cli/image_builder.py index 795eebb..20a3a49 100644 --- a/plugin/cli/image_builder.py +++ b/plugin/cli/image_builder.py @@ -73,6 +73,13 @@ def handle_image_builder_build(gopts, session, args): help="Ref to the bootable container build root container for bootc image types", ) + parser.add_option( + "--bootc-installer-payload-ref", + type=str, + dest="bootc_installer_payload_ref", + help="Ref to the bootable container installer payload container for bootc image types", + ) + parser.add_option( "--bootc-default-fs", type=str, @@ -136,6 +143,9 @@ def handle_image_builder_build(gopts, session, args): if opts.bootc_build_ref: bootc["build-ref"] = opts.bootc_build_ref + if opts.bootc_installer_payload_ref: + bootc["build-ref"] = opts.bootc_installer_payload_ref + if opts.bootc_default_fs: bootc["default-fs"] = opts.bootc_default_fs diff --git a/plugin/hub/image_builder.py b/plugin/hub/image_builder.py index 73fd07d..2efe2ce 100644 --- a/plugin/hub/image_builder.py +++ b/plugin/hub/image_builder.py @@ -54,6 +54,7 @@ "properties": { "ref": {"type": "string"}, "build-ref": {"type": "string"}, + "installer-payload-ref": {"type": "string"}, "default-fs": {"type": "string"}, }, }, From 2c7a4c75b2559996b56098efbc63a3cdad3db97b Mon Sep 17 00:00:00 2001 From: Simon de Vlieger Date: Fri, 27 Mar 2026 07:15:24 +0100 Subject: [PATCH 3/3] builder: simplify bootc ref pulling Use a loop instead of each one separately. Signed-off-by: Simon de Vlieger --- plugin/builder/image_builder.py | 63 +++++++++------------------------ 1 file changed, 17 insertions(+), 46 deletions(-) diff --git a/plugin/builder/image_builder.py b/plugin/builder/image_builder.py index 4608ab7..f88aef1 100644 --- a/plugin/builder/image_builder.py +++ b/plugin/builder/image_builder.py @@ -446,53 +446,24 @@ def handler( # If bootc information is available pass it on to the command bootc = self.opts.get("bootc") if bootc: - # The general ref is the 'payload' container for the image type - bootc_ref = bootc.get("ref") - if bootc_ref: - cmd.extend(["--bootc-ref", bootc_ref]) - - # We need to pull the containers into local storage, this - # requires the podman executable to be available in our buildroot - exit_code = broot.mock( - ["--cwd", broot.tmpdir(within=True), "--chroot", "--", - "podman", "pull", bootc_ref] - ) - - if exit_code != 0: - raise koji.GenericError("`podman` failed to pull container") - - # Normally the 'payload' container is used as the build root in - # image-builder. This can be overridden with a separate container - # reference. - bootc_build_ref = bootc.get("build-ref") - if bootc_build_ref: - cmd.extend(["--bootc-build-ref", bootc_build_ref]) - - # Same thing for the build container, it needs to be pulled before - # executing `image-builder`. - exit_code = broot.mock( - ["--cwd", broot.tmpdir(within=True), "--chroot", "--", - "podman", "pull", bootc_build_ref] - ) - - if exit_code != 0: - raise koji.GenericError("`podman` failed to pull container") - - # For installers and additional payload container might need to be - # embedded, let's pull that too. - bootc_installer_payload_ref = bootc.get("installer-payload-ref") - if bootc_installer_payload_ref: - cmd.extend(["--bootc-installer-payload-ref", bootc_installer_payload_ref]) - - # Same thing for the installer-payload container, it needs to be pulled before - # executing `image-builder`. - exit_code = broot.mock( - ["--cwd", broot.tmpdir(within=True), "--chroot", "--", - "podman", "pull", bootc_installer_payload_ref] - ) + # Various containers can be used during the build. `ref` is the contents of + # the main artifact. `build-ref` is a custom buildroot. `installer-payload-ref` + # is applicable to (some) installer image types that contain an embedded + # container. + for ref in ["ref", "build-ref", "installer-payload-ref"]: + bootc_ref = bootc.get(ref) + if bootc_ref: + cmd.extend(["--bootc-ref", bootc_ref]) + + # We need to pull the container into local storage, this + # requires the podman executable to be available in our buildroot + exit_code = broot.mock( + ["--cwd", broot.tmpdir(within=True), "--chroot", "--", + "podman", "pull", bootc_ref] + ) - if exit_code != 0: - raise koji.GenericError("`podman` failed to pull container") + if exit_code != 0: + raise koji.GenericError(f"`podman` failed to pull container {ref}: {bootc_ref}") # image-builder tries to determine the root filesystem to use based # on container metadata and/or contents, for some containers this isnt'