diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 49ed5d04cd..7a8ebabe3c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,8 @@ stages: - build - ostree-gen - ostree-build + - bootc-gen + - bootc-build - verify - finish @@ -2030,6 +2032,56 @@ fail: needs: - "generate-ostree-build-config: [rhel-9.9, x86_64]" +"generate-bootc-config: [centos-10, aarch64]": + stage: bootc-gen + extends: .terraform + variables: + RUNNER: aws/fedora-44-aarch64 + INTERNAL_NETWORK: "false" + script: + - sudo -E ./test/scripts/setup-osbuild-repo + - sudo ./test/scripts/install-dependencies + - ./test/scripts/generate-bootc-config --bootc-source centos-10 --arch aarch64 boot-config.yml + artifacts: + paths: + - boot-config.yml + + +"generate-bootc-config: [centos-10, x86_64]": + stage: bootc-gen + extends: .terraform + variables: + RUNNER: aws/fedora-44-x86_64 + INTERNAL_NETWORK: "false" + script: + - sudo -E ./test/scripts/setup-osbuild-repo + - sudo ./test/scripts/install-dependencies + - ./test/scripts/generate-bootc-config --bootc-source centos-10 --arch x86_64 boot-config.yml + artifacts: + paths: + - boot-config.yml + +"bootc-build-trigger: [centos-10, aarch64]": + stage: bootc-build + trigger: + include: + - artifact: boot-config.yml + job: "generate-bootc-config: [centos-10, aarch64]" + strategy: depend + needs: + - "generate-bootc-config: [centos-10, aarch64]" + + +"bootc-build-trigger: [centos-10, x86_64]": + stage: bootc-build + trigger: + include: + - artifact: boot-config.yml + job: "generate-bootc-config: [centos-10, x86_64]" + strategy: depend + needs: + - "generate-bootc-config: [centos-10, x86_64]" + "generate-manifests: [centos-10, ppc64le]": stage: gen extends: .terraform diff --git a/cmd/gen-manifests/main.go b/cmd/gen-manifests/main.go index 942dca3d93..93ad8c93c1 100644 --- a/cmd/gen-manifests/main.go +++ b/cmd/gen-manifests/main.go @@ -536,7 +536,12 @@ func main() { fmt.Fprintln(os.Stderr, "Collecting jobs") - distros, invalidDistros := distros.ResolveArgValues(testedRepoRegistry.ListDistros()) + // With --bootc-refs and no --distros, skip loop to get repositories + repoDistros := testedRepoRegistry.ListDistros() + if len(distros) == 0 && len(bootcRefs) > 0 { + repoDistros = nil + } + distros, invalidDistros := distros.ResolveArgValues(repoDistros) if len(invalidDistros) > 0 { fmt.Fprintf(os.Stderr, "WARNING: invalid distro names: [%s]\n", strings.Join(invalidDistros, ",")) } @@ -639,11 +644,13 @@ func main() { } } + arches, _ := arches.ResolveArgValues(distribution.ListArches()) for _, archName := range arches { archi, err := distribution.GetArch(archName) if err != nil { panic(err) } + imgTypes, _ := imgTypes.ResolveArgValues(archi.ListImageTypes()) for _, imgTypeName := range imgTypes { imgType, err := archi.GetImageType(imgTypeName) if err != nil { diff --git a/test/configs/bootc-remote-with-payload.json b/test/configs/bootc-remote-with-payload.json index 829f6964a9..16aae2c257 100644 --- a/test/configs/bootc-remote-with-payload.json +++ b/test/configs/bootc-remote-with-payload.json @@ -7,7 +7,7 @@ }, "options": { "bootc": { - "installer_payload_ref": "payload-container-fake-ref", + "installer_payload_ref": "quay.io/centos-bootc/centos-bootc:stream10", "use_remote_container_source": true } } diff --git a/test/configs/bootc-with-payload.json b/test/configs/bootc-with-payload.json index 3f2519b90d..eabc3e4eb2 100644 --- a/test/configs/bootc-with-payload.json +++ b/test/configs/bootc-with-payload.json @@ -7,7 +7,7 @@ }, "options": { "bootc": { - "installer_payload_ref": "payload-container-fake-ref" + "installer_payload_ref": "quay.io/centos-bootc/centos-bootc:stream10" } } } diff --git a/test/data/bootcrefs/centos-10.json b/test/data/bootcrefs/centos-10.json new file mode 100644 index 0000000000..7af1daad7a --- /dev/null +++ b/test/data/bootcrefs/centos-10.json @@ -0,0 +1,14 @@ +{ + "aarch64": { + "ref": "quay.io/centos-bootc/centos-bootc:stream10" + }, + "ppc64le": { + "ref": "quay.io/centos-bootc/centos-bootc:stream10" + }, + "s390x": { + "ref": "quay.io/centos-bootc/centos-bootc:stream10" + }, + "x86_64": { + "ref": "quay.io/centos-bootc/centos-bootc:stream10" + } +} diff --git a/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_generic_iso-bootc_remote_with_payload b/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_generic_iso-bootc_remote_with_payload index bdf605b441..da95792e29 100644 --- a/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_generic_iso-bootc_remote_with_payload +++ b/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_generic_iso-bootc_remote_with_payload @@ -1 +1 @@ -86da72d87b728c80df7913e48cb43b91304d37bdacd65a13b02988f973a61537 +cef10b14ef026c90582f80300a29b15ec7ad02de53f2dcd6e00b8502ce8e758f diff --git a/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_generic_iso-bootc_with_payload b/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_generic_iso-bootc_with_payload index a60a6a414b..d234159c76 100644 --- a/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_generic_iso-bootc_with_payload +++ b/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_generic_iso-bootc_with_payload @@ -1 +1 @@ -a3b1cd6a627ebcea95107f3b9722085e47822d47ef3cd16626005c56e742c035 +df81b2a62016713844168497eaa793a86b72735c92eaa7a9b43eb3ebcb00a41e diff --git a/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_installer-bootc_remote_with_payload b/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_installer-bootc_remote_with_payload index 9295a87d89..41389ee8ef 100644 --- a/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_installer-bootc_remote_with_payload +++ b/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_installer-bootc_remote_with_payload @@ -1 +1 @@ -c1f3cba58ad0f4c4db26116a08178c4dec77710c364f09ce263d3481022d254a +301be914a73e0e50c54787671be3b9bb057791c143ce2c8fd4a35a1f3c0a324c diff --git a/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_installer-bootc_with_payload b/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_installer-bootc_with_payload index 1377073286..b45bb24048 100644 --- a/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_installer-bootc_with_payload +++ b/test/data/manifest-checksums/bootc_test_os_1-aarch64-bootc_installer-bootc_with_payload @@ -1 +1 @@ -2df8b35541e90cc3247f453d964cb4b010d6d1c803092bb88d07b6e0cf490d09 +c84eaeeddc762f1e9c49a0e82c3d4663e226d1513c9050bc4ab92ab8a8d10c1c diff --git a/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_generic_iso-bootc_remote_with_payload b/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_generic_iso-bootc_remote_with_payload index fdef5a0984..6ac01a446f 100644 --- a/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_generic_iso-bootc_remote_with_payload +++ b/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_generic_iso-bootc_remote_with_payload @@ -1 +1 @@ -0fa375e73b1872388e9cc1e0585f31796b9bb08b1805a7f53dfaba7d3c3b6d6a +d3c292ecc292bc9155a71c0ab8a144f270e7ea2ab5492280b051c9597e6260e5 diff --git a/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_generic_iso-bootc_with_payload b/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_generic_iso-bootc_with_payload index 1fd5580995..9dc5cca90b 100644 --- a/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_generic_iso-bootc_with_payload +++ b/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_generic_iso-bootc_with_payload @@ -1 +1 @@ -5064e0ceca39e60554b6edb1687089a58ac7a732b535a7da3cdec2df91ea14de +33dc409ed99bc90ac2f5952b318ee3c05273d291e8601300b69cc5c1c30b2f01 diff --git a/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_installer-bootc_remote_with_payload b/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_installer-bootc_remote_with_payload index 58fc0e7b90..cfa3e750d5 100644 --- a/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_installer-bootc_remote_with_payload +++ b/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_installer-bootc_remote_with_payload @@ -1 +1 @@ -6591c49b039799e422ef065c1720e6d2d9b40115d9acdc5ec5e9a121961b85d9 +89d82a0734068ec148713fb7098bf1f20fae41eb649a0f3380a5fe4270c03244 diff --git a/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_installer-bootc_with_payload b/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_installer-bootc_with_payload index d6039abc32..69147fe2d2 100644 --- a/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_installer-bootc_with_payload +++ b/test/data/manifest-checksums/bootc_test_os_1-x86_64-bootc_installer-bootc_with_payload @@ -1 +1 @@ -44d8051c0d48d63847da74e98211885c4195f2c41f9812eaf751e174a6772bb8 +268b552e56217f8a828b6e4632366e21a8161cf43b747fc511b26b24b79d46ca diff --git a/test/scripts/generate-bootc-config b/test/scripts/generate-bootc-config new file mode 100755 index 0000000000..0d9533df54 --- /dev/null +++ b/test/scripts/generate-bootc-config @@ -0,0 +1,157 @@ +#!/usr/bin/env python3 +# +# Generates gitlab child pipelines for building bootc images. +# Each pipeline should build a single image configuration test. +import os +from tempfile import TemporaryDirectory + +import imgtestlib as testlib + +BOOTC_CONFIGS = {"bootc-empty", "bootc-with-payload"} + +BOOTC_IMAGE_TYPES = [ + "qcow2", + "ami", + "raw", + "gce", + "vhd", + "vmdk", + "ova", + "anaconda-iso", + "bootc-generic-iso", + "bootc-installer", + "pxe-tar-xz", +] + +# TODO: Fix these and remove the skips; they break gen-manifests today: +# * anaconda-iso โ€” depsolve fails: InvalidRequest: No 'repos' or 'root_dir' +# * pxe-tar-xz โ€” initramfs requires ostree, dmsquash-live and livenet modules +BOOTC_SKIP_IMAGE_TYPES = { + "anaconda-iso", + "pxe-tar-xz", +} + +JOB_TEMPLATE = """ +build/{distro}/{arch}/{image_type}/{config_name}: + stage: test + script: + - sudo -E ./test/scripts/setup-osbuild-repo + - sudo ./test/scripts/install-dependencies + - podman pull {bootc_ref} + - ./test/scripts/build-image "{distro}" "{image_type}" "{config}" + - ./test/scripts/boot-image "{image_path}" "{config}" + - sudo -E ./test/scripts/upload-results "{distro}" "{image_type}" "{config}" + extends: .terraform + tags: + - {tag} + variables: + RUNNER: {runner}-{arch} + INTERNAL_NETWORK: "{internal}" +""" + + +def should_skip(config_name, image_type): + return image_type in BOOTC_SKIP_IMAGE_TYPES or config_name not in BOOTC_CONFIGS + + +@testlib.gitlab.log_section("Generating manifests") +def generate_manifests(outputdir, arch, bootc_source, bootc_installer_ref=None): + """ + Generate bootc manifests using the default config list and return a + dictionary mapping each manifest file to the manifest data and its ID. + """ + bootc_entry = testlib.bootcsource.resolve_bootc_source(bootc_source, arch) + bootc_ref = bootc_entry["ref"] + bootc_refspec = bootc_ref + if build_ref := bootc_entry.get("build_ref"): + bootc_refspec = f"{bootc_ref}#{build_ref}" + + # TODO: fix broken image types + # don't ask gen-manifests for them in the meantime + images = [t for t in BOOTC_IMAGE_TYPES if t not in BOOTC_SKIP_IMAGE_TYPES] + + print(f"๐Ÿ—’๏ธ Generating bootc manifests using the default config list for {bootc_source} [{arch}]") + # Omit --distros: with --bootc-refs, gen-manifests skips the RPM/repo loop. + err = testlib.core.gen_manifests(outputdir, arches=[arch], + images=images, + bootc_refs=[bootc_refspec], + bootc_installer_ref=bootc_installer_ref, + skip_no_config=True) + + # print stderr in case there were errors or warnings about skipped configurations + # but filter out the annoying ones + stderr = err.decode().splitlines() + for line in stderr: + if "No match for group package" in line: + continue + if "Failed to load consumer certs" in line: + continue + print(line) + + print("โœ… Manifest generation done!\n") + return testlib.core.read_manifests(outputdir) + + +def generate_configs(build_requests, pipeline_file, bootc_source, bootc_ref): + print(f"๐Ÿงช Generating dynamic pipelines for {len(build_requests)} builds") + expected_distro = f"bootc-{bootc_source}" + for build in build_requests: + distro = build["distro"] + arch = build["arch"] + image_type = build["image-type"] + config = build["config"] + config_name = config["name"] + + if distro != expected_distro: + print(f"๐Ÿฆ˜ Skipping {distro}/{arch}/{image_type}/{config_name}: " + f"expected distro {expected_distro} for bootc source {bootc_source}") + continue + + if should_skip(config_name, image_type): + print(f"๐Ÿฆ˜ Skipping {distro}/{arch}/{image_type}/{config_name}") + continue + + build_name = testlib.build.gen_build_name(distro, arch, image_type, config_name) + image_path = f"./build/{build_name}" + + runner = testlib.testenv.get_ci_runner_for(distro, arch, image_type) + tag = testlib.core.get_tag_for(runner) + + config_path = os.path.join(testlib.core.CONFIGS_PATH, config_name + ".json") + pipeline_file.write(JOB_TEMPLATE.format(distro=distro, arch=arch, image_type=image_type, + runner=runner, tag=tag, + config_name=config_name, config=config_path, + internal="true" if "rhel" in distro else "false", + image_path=image_path, bootc_ref=bootc_ref)) + print("โœ… DONE!") + + +def main(): + parser = testlib.core.bootc_clargs() + args = parser.parse_args() + + arch = args.arch + bootc_source = args.bootc_source + bootc_installer_ref = args.bootc_installer_ref + + testlib.core.check_config_names() + + bootc_ref = testlib.bootcsource.resolve_bootc_source(bootc_source, arch)["ref"] + + with TemporaryDirectory() as manifest_dir: + manifests = generate_manifests(manifest_dir, arch, bootc_source, + bootc_installer_ref=bootc_installer_ref) + build_requests = testlib.core.filter_builds(manifests, arch=arch) + + with open(args.config, "w", encoding="utf-8") as config_file: + if len(build_requests) == 0: + print("โšซ No manifest changes detected. Generating null config.") + config_file.write(testlib.core.NULL_CONFIG) + return + + config_file.write(testlib.core.BASE_CONFIG) + generate_configs(build_requests, config_file, bootc_source, bootc_ref) + + +if __name__ == "__main__": + main() diff --git a/test/scripts/generate-gitlab-ci b/test/scripts/generate-gitlab-ci index 24dbe0ad4f..52452a45c9 100755 --- a/test/scripts/generate-gitlab-ci +++ b/test/scripts/generate-gitlab-ci @@ -10,6 +10,7 @@ ARCHITECTURES = ["x86_64", "aarch64"] MANIFEST_ONLY_ARCHES = ["ppc64le", "s390x"] RUNNER = testlib.testenv.get_common_ci_runner() OSTREE_DISTROS = ["fedora", "rhel-8", "rhel-9", "centos-9"] +BOOTC_SOURCES = ["centos-10"] BASE_CONFIG = """--- @@ -19,6 +20,8 @@ stages: - build - ostree-gen - ostree-build + - bootc-gen + - bootc-build - verify - finish @@ -113,6 +116,34 @@ OSTREE_TRIGGER_TEMPLATE = """ - "generate-ostree-build-config: [{distro}, {arch}]" """ +BOOTC_GEN_TEMPLATE = """ +"generate-bootc-config: [{bootc_source}, {arch}]": + stage: bootc-gen + extends: .terraform + variables: + RUNNER: {runner}-{arch} + INTERNAL_NETWORK: "false" + script: + - sudo -E ./test/scripts/setup-osbuild-repo + - sudo ./test/scripts/install-dependencies + - ./test/scripts/generate-bootc-config --bootc-source {bootc_source} --arch {arch} boot-config.yml + artifacts: + paths: + - boot-config.yml +""" + +BOOTC_TRIGGER_TEMPLATE = """ +"bootc-build-trigger: [{bootc_source}, {arch}]": + stage: bootc-build + trigger: + include: + - artifact: boot-config.yml + job: "generate-bootc-config: [{bootc_source}, {arch}]" + strategy: depend + needs: + - "generate-bootc-config: [{bootc_source}, {arch}]" +""" + MANIFEST_GEN_TEMPLATE = """ "generate-manifests: [{distro}, {arch}]": @@ -179,6 +210,8 @@ def main(): trigger_stage = [] ostree_gen_stage = [] ostree_trigger_stage = [] + bootc_gen_stage = [] + bootc_trigger_stage = [] for img in sort_configs(images): combo = (img["distro"], img["arch"]) if combo in combos: @@ -235,6 +268,24 @@ def main(): test_file_path=test_file_path, )) + for bootc_source in BOOTC_SOURCES: + # Build/boot only arches that are both in the bootcref and supported for + # full image CI (skip manifest-only arches like ppc64le/s390x). + bootc_arches = [ + arch for arch in testlib.bootcsource.list_bootc_source_arches(bootc_source) + if arch in ARCHITECTURES + ] + for arch in bootc_arches: + bootc_gen_stage.append(BOOTC_GEN_TEMPLATE.format( + bootc_source=bootc_source, + arch=arch, + runner=RUNNER, + )) + bootc_trigger_stage.append(BOOTC_TRIGGER_TEMPLATE.format( + bootc_source=bootc_source, + arch=arch, + )) + with open(config_path, "w", encoding="utf-8") as config_file: config_file.write(BASE_CONFIG.format(runner=RUNNER)) config_file.write(testlib.core.BASE_CONFIG) @@ -242,6 +293,8 @@ def main(): config_file.write("\n".join(trigger_stage)) config_file.write("\n".join(ostree_gen_stage)) config_file.write("\n".join(ostree_trigger_stage)) + config_file.write("\n".join(bootc_gen_stage)) + config_file.write("\n".join(bootc_trigger_stage)) config_file.write("\n".join(man_gen_stage)) diff --git a/test/scripts/imgtestlib/__init__.py b/test/scripts/imgtestlib/__init__.py index 5ad4b175b5..6f8f1a0b3a 100644 --- a/test/scripts/imgtestlib/__init__.py +++ b/test/scripts/imgtestlib/__init__.py @@ -1 +1 @@ -from . import boot, build, cache, core, gitlab, run, testenv, vm +from . import boot, bootcsource, build, cache, core, gitlab, run, testenv, vm diff --git a/test/scripts/imgtestlib/boot.py b/test/scripts/imgtestlib/boot.py index db12c976a8..0c6077a6fc 100644 --- a/test/scripts/imgtestlib/boot.py +++ b/test/scripts/imgtestlib/boot.py @@ -466,6 +466,10 @@ def cmd_boot_aws(distro, arch, image_type, image_name, privkey, pubkey, image_pa # for image-builder if distro == "rhel-7.9" and image_type == "ec2": boot_mode = "legacy-bios" + # bootc images aren't defined as distros so we can't request the boot mode and we + # have no inspection capabilities, try to boot as hybrid + elif distro.startswith("bootc"): + boot_mode = "uefi-preferred" else: # otherwise ask for the boot mode boot_mode = get_boot_mode(distro, arch, image_type) diff --git a/test/scripts/imgtestlib/bootcsource.py b/test/scripts/imgtestlib/bootcsource.py new file mode 100644 index 0000000000..d355363b17 --- /dev/null +++ b/test/scripts/imgtestlib/bootcsource.py @@ -0,0 +1,32 @@ +import json +import os + +BOOTCREFS_PATH = "./test/data/bootcrefs" + + +def load_bootc_source(source_name): + path = os.path.join(BOOTCREFS_PATH, source_name + ".json") + with open(path, encoding="utf-8") as source_file: + return json.load(source_file) + + +def list_bootc_source_arches(source_name): + return sorted(load_bootc_source(source_name).keys()) + + +def resolve_bootc_source(source_name, arch): + data = load_bootc_source(source_name) + + try: + entry = data[arch] + except KeyError as exc: + raise KeyError(f"bootc source {source_name} does not define arch {arch}") from exc + + if not isinstance(entry, dict): + raise TypeError(f"bootc source {source_name} entry for arch {arch} must be an object") + + ref = entry.get("ref") + if not isinstance(ref, str) or not ref: + raise ValueError(f"bootc source {source_name} entry for arch {arch} must define a non-empty 'ref' string") + + return entry diff --git a/test/scripts/imgtestlib/build.py b/test/scripts/imgtestlib/build.py index 02a2fba466..814d91ec9d 100644 --- a/test/scripts/imgtestlib/build.py +++ b/test/scripts/imgtestlib/build.py @@ -3,12 +3,22 @@ import tempfile from typing import Dict, List +from .bootcsource import resolve_bootc_source from .gitlab import log_section from .run import runcmd, runcmd_nc from .testenv import get_host_distro, get_osbuild_commit, rng_seed_env -def config_to_cli_args(config: dict) -> List[str]: +def resolve_bootc_options(config: dict, distro: str, arch: str) -> dict: + bootc = dict(config.get("options", {}).get("bootc", {})) + if not distro.startswith("bootc-"): + return bootc + + source_data = resolve_bootc_source(distro.removeprefix("bootc-"), arch) + return {**source_data, **bootc} + + +def config_to_cli_args(config: dict, bootc: dict) -> List[str]: args: List[str] = [] blueprint = config.get("blueprint", {}) @@ -25,7 +35,10 @@ def config_to_cli_args(config: dict) -> List[str]: if parent := ostree.get("parent"): args.append(f"--ostree-parent={parent}") - bootc = options.get("bootc", {}) + if ref := bootc.get("ref"): + args.append(f"--bootc-ref={ref}") + if build_ref := bootc.get("build_ref"): + args.append(f"--bootc-build-ref={build_ref}") if payload_ref := bootc.get("installer_payload_ref"): args.append(f"--bootc-installer-payload-ref={payload_ref}") if bootc.get("use_remote_container_source"): @@ -47,6 +60,7 @@ def build_image(distro, arch, image_type, config_path): config = json.load(config_file) config_name = config["name"] + bootc = resolve_bootc_options(config, distro, arch) build_name = gen_build_name(distro, arch, image_type, config_name) build_dir = os.path.join("build", build_name) @@ -59,7 +73,6 @@ def build_image(distro, arch, image_type, config_path): seed = rng_seed_env()["OSBUILD_TESTING_RNG_SEED"] cmd = [ "sudo", "-E", "./bin/image-builder", "build", image_type, - "--distro", distro, "--arch", arch, "--force-repo-dir", "test/data/repositories", "--output-dir", build_dir, @@ -68,7 +81,10 @@ def build_image(distro, arch, image_type, config_path): "--ignore-warnings", "--seed", str(seed), ] - cmd.extend(config_to_cli_args(config)) + # bootc builds derive the distro from the container and reject --distro. + if not bootc.get("ref"): + cmd.extend(["--distro", distro]) + cmd.extend(config_to_cli_args(config, bootc)) runcmd_nc(cmd) print("โœ… Build finished!!") diff --git a/test/scripts/imgtestlib/core.py b/test/scripts/imgtestlib/core.py index 931c8d13e4..649585fb3c 100644 --- a/test/scripts/imgtestlib/core.py +++ b/test/scripts/imgtestlib/core.py @@ -101,7 +101,8 @@ def check_config_names(): def gen_manifests(outputdir, config_list=None, distros=None, arches=None, images=None, - commits=False, flatpaks=False, skip_no_config=False): + commits=False, flatpaks=False, skip_no_config=False, bootc_refs=None, + bootc_remote=False, bootc_installer_ref=None): # pylint: disable=too-many-arguments,too-many-positional-arguments cmd = ["go", "run", "./cmd/gen-manifests", "--cache", os.path.join(TEST_CACHE_ROOT, "rpmmd"), @@ -115,12 +116,21 @@ def gen_manifests(outputdir, config_list=None, distros=None, arches=None, images cmd.extend(["--arches", ",".join(arches)]) if images: cmd.extend(["--types", ",".join(images)]) + if bootc_refs: + cmd.extend(["--bootc-refs", ",".join(bootc_refs)]) + if bootc_remote: + cmd.append("--bootc-remote") + if bootc_installer_ref: + cmd.extend(["--bootc-installer-ref", bootc_installer_ref]) if commits: cmd.append("--commits") if flatpaks: cmd.append("--flatpaks") if skip_no_config: cmd.append("--skip-noconfig") + # Bootc needs rootful "podman mount". + if bootc_refs and os.geteuid() != 0: + cmd = ["sudo", *cmd] env = rng_seed_env() env["GOPROXY"] = "https://proxy.golang.org,direct" print("โŒจ๏ธ" + " ".join(cmd) + " ENV: " + str(env)) @@ -274,6 +284,19 @@ def clargs(): return parser +def bootc_clargs(): + default_arch = os.uname().machine + parser = argparse.ArgumentParser() + parser.add_argument("config", type=str, help="path to write config") + parser.add_argument("--bootc-source", type=str, required=True, + help="bootc source name (test/data/bootcrefs/.json)") + parser.add_argument("--arch", type=str, default=default_arch, + help="architecture to generate configs for (defaults to host architecture)") + parser.add_argument("--bootc-installer-ref", type=str, default=None, + help="installer payload container ref for manifest generation") + return parser + + def is_manifest_list(data): """Inspect a manifest determine if it's a multi-image manifest-list.""" media_type = data.get("mediaType") diff --git a/test/scripts/test_imgtestlib.py b/test/scripts/test_imgtestlib.py index 114e542938..dac82cbdc1 100644 --- a/test/scripts/test_imgtestlib.py +++ b/test/scripts/test_imgtestlib.py @@ -35,6 +35,94 @@ def test_runcmd_env(): assert stderr == b"" +def test_config_to_cli_args_bootc(): + config = { + "name": "bootc-empty", + "blueprint": {}, + "options": { + "bootc": { + "ref": "quay.io/example/bootc:latest", + "build_ref": "quay.io/example/toolbox:latest", + "installer_payload_ref": "quay.io/example/payload:latest", + "use_remote_container_source": True, + }, + }, + } + + bootc = testlib.build.resolve_bootc_options(config, "centos-10", "x86_64") + args = testlib.build.config_to_cli_args(config, bootc) + + assert any(arg.startswith("--blueprint=") for arg in args) + assert "--bootc-ref=quay.io/example/bootc:latest" in args + assert "--bootc-build-ref=quay.io/example/toolbox:latest" in args + assert "--bootc-installer-payload-ref=quay.io/example/payload:latest" in args + assert "--bootc-pull-container" in args + + +def test_resolve_bootc_options_from_bootcrefs(): + config = { + "name": "bootc-empty", + "blueprint": {}, + "options": { + "bootc": { + "use_remote_container_source": True, + }, + }, + } + + bootc = testlib.build.resolve_bootc_options(config, "bootc-centos-10", "x86_64") + + assert bootc["ref"] == "quay.io/centos-bootc/centos-bootc:stream10" + assert bootc["use_remote_container_source"] is True + + +def test_resolve_bootc_options_prefers_config_over_bootcrefs(): + config = { + "name": "bootc-empty", + "blueprint": {}, + "options": { + "bootc": { + "ref": "quay.io/example/bootc:latest", + }, + }, + } + + bootc = testlib.build.resolve_bootc_options(config, "bootc-centos-10", "x86_64") + + assert bootc["ref"] == "quay.io/example/bootc:latest" + + +def test_resolve_bootc_source(): + entry = testlib.bootcsource.resolve_bootc_source("centos-10", "x86_64") + assert entry["ref"] == "quay.io/centos-bootc/centos-bootc:stream10" + + +def test_list_bootc_source_arches(): + arches = testlib.bootcsource.list_bootc_source_arches("centos-10") + assert arches == ["aarch64", "ppc64le", "s390x", "x86_64"] + + +def test_resolve_bootc_source_rejects_string_entry(tmp_path, monkeypatch): + source_dir = tmp_path / "bootcrefs" + source_dir.mkdir() + (source_dir / "bad.json").write_text('{"x86_64": "quay.io/example/bootc:latest"}', encoding="utf-8") + monkeypatch.setattr(testlib.bootcsource, "BOOTCREFS_PATH", os.fspath(source_dir)) + + with pytest.raises(TypeError, match="must be an object"): + testlib.bootcsource.resolve_bootc_source("bad", "x86_64") + + +def test_resolve_bootc_source_requires_ref(tmp_path, monkeypatch): + source_dir = tmp_path / "bootcrefs" + source_dir.mkdir() + (source_dir / "bad.json").write_text('{"x86_64": {"build_ref": "quay.io/example/toolbox:latest"}}', + encoding="utf-8") + monkeypatch.setattr(testlib.bootcsource, "BOOTCREFS_PATH", os.fspath(source_dir)) + + with pytest.raises(ValueError, match="must define a non-empty 'ref' string"): + testlib.bootcsource.resolve_bootc_source("bad", "x86_64") + + def test_read_seed(): # check that it's read without error - no need to test the value itself seed_env = testlib.testenv.rng_seed_env()