diff --git a/bib/cmd/bootc-image-builder/image.go b/bib/cmd/bootc-image-builder/image.go index 2ea393f8..011348b2 100644 --- a/bib/cmd/bootc-image-builder/image.go +++ b/bib/cmd/bootc-image-builder/image.go @@ -202,6 +202,47 @@ func setFSTypes(pt *disk.PartitionTable, rootfs string) error { } func genPartitionTable(c *ManifestConfig, customizations *blueprint.Customizations, rng *rand.Rand) (*disk.PartitionTable, error) { + fsCust := customizations.GetFilesystems() + diskCust := customizations.GetPartitioning() + // XXX: remove once https://github.com/osbuild/images/pull/1054 is + // merged, then validation is part of GetPartitioning + err := diskCust.Validate() + if err != nil { + return nil, fmt.Errorf("error reading disk customizations: %w", err) + } + switch { + // XXX: move into images library + case fsCust != nil && diskCust != nil: + return nil, fmt.Errorf("cannot combine disk and filesystem customizations") + case diskCust != nil: + return genPartitionTableDiskCust(c, diskCust, rng) + default: + return genPartitionTableFsCust(c, fsCust, rng) + } +} + +func genPartitionTableDiskCust(c *ManifestConfig, diskCust *blueprint.DiskCustomization, rng *rand.Rand) (*disk.PartitionTable, error) { + diskCust.MinSize = max(diskCust.MinSize, c.RootfsMinsize) + + basept, ok := partitionTables[c.Architecture.String()] + if !ok { + return nil, fmt.Errorf("pipelines: no partition tables defined for %s", c.Architecture) + } + defaultFSType, err := disk.NewFSType(c.RootFSType) + if err != nil { + return nil, err + } + + partOptions := &disk.CustomPartitionTableOptions{ + PartitionTableType: basept.Type, + // XXX: not setting/defaults will fail to boot with btrfs/lvm + BootMode: platform.BOOT_HYBRID, + DefaultFSType: defaultFSType, + } + return disk.NewCustomPartitionTable(diskCust, partOptions, rng) +} + +func genPartitionTableFsCust(c *ManifestConfig, fsCust []blueprint.FilesystemCustomization, rng *rand.Rand) (*disk.PartitionTable, error) { basept, ok := partitionTables[c.Architecture.String()] if !ok { return nil, fmt.Errorf("pipelines: no partition tables defined for %s", c.Architecture) @@ -211,10 +252,10 @@ func genPartitionTable(c *ManifestConfig, customizations *blueprint.Customizatio if c.RootFSType == "btrfs" { partitioningMode = disk.BtrfsPartitioningMode } - if err := checkFilesystemCustomizations(customizations.GetFilesystems(), partitioningMode); err != nil { + if err := checkFilesystemCustomizations(fsCust, partitioningMode); err != nil { return nil, err } - fsCustomizations := updateFilesystemSizes(customizations.GetFilesystems(), c.RootfsMinsize) + fsCustomizations := updateFilesystemSizes(fsCust, c.RootfsMinsize) pt, err := disk.NewPartitionTable(&basept, fsCustomizations, DEFAULT_SIZE, partitioningMode, nil, rng) if err != nil { diff --git a/package-requires.txt b/package-requires.txt index 5d0081b3..e6c92fa6 100644 --- a/package-requires.txt +++ b/package-requires.txt @@ -2,7 +2,7 @@ # from the Containerfile by default, using leading '#' as comments. # This project uses osbuild -osbuild osbuild-ostree osbuild-depsolve-dnf +osbuild osbuild-ostree osbuild-depsolve-dnf osbuild-lvm2 # We mount container images internally podman diff --git a/test/test_manifest.py b/test/test_manifest.py index f28d005b..e6c0de60 100644 --- a/test/test_manifest.py +++ b/test/test_manifest.py @@ -550,3 +550,84 @@ def test_manifest_fips_customization(tmp_path, build_container): ], text=True) st = find_grub2_iso_stage_from(output) assert "fips=1" in st["options"]["kernel"]["opts"] + + + +def find_bootc_install_to_fs_stage_from(manifest_str): + manifest = json.loads(manifest_str) + for pipeline in manifest["pipelines"]: + # the fstab stage in cross-arch manifests is in the "ostree-deployment" pipeline + if pipeline["name"] == "image": + for st in pipeline["stages"]: + if st["type"] == "org.osbuild.bootc.install-to-filesystem": + return st + raise ValueError(f"cannot find bootc.install-to-filesystem stage in manifest:\n{manifest_str}") + + +def test_manifest_disk_customization_lvm(tmp_path, build_container): + container_ref = "quay.io/centos-bootc/centos-bootc:stream9" + + config = { + "customizations": { + "disk": { + "partitions": [ + { + "type": "lvm", + "logical_volumes": [ + { + "fs_type": "ext4", + "mountpoint": "/", + } + ] + } + ] + } + } + } + config_path = tmp_path / "config.json" + with config_path.open("w") as config_file: + json.dump(config, config_file) + + output = subprocess.check_output([ + *testutil.podman_run_common, + "-v", f"{config_path}:/config.json:ro", + build_container, + "manifest", f"{container_ref}", + ]) + st = find_bootc_install_to_fs_stage_from(output) + assert st["devices"]["rootlv"]["type"] == "org.osbuild.lvm2.lv" + + +def test_manifest_disk_customization_btrfs(tmp_path, build_container): + container_ref = "quay.io/centos-bootc/centos-bootc:stream9" + + config = { + "customizations": { + "disk": { + "partitions": [ + { + "type": "btrfs", + "subvolumes": [ + { + "name": "root", + "mountpoint": "/", + } + ] + } + ] + } + } + } + config_path = tmp_path / "config.json" + with config_path.open("w") as config_file: + json.dump(config, config_file) + + output = subprocess.check_output([ + *testutil.podman_run_common, + "-v", f"{config_path}:/config.json:ro", + build_container, + "manifest", f"{container_ref}", + ]) + st = find_bootc_install_to_fs_stage_from(output) + assert st["mounts"][0]["type"] == "org.osbuild.btrfs" + assert st["mounts"][0]["target"] == "/" diff --git a/test/testcases.py b/test/testcases.py index 3adfa768..0cf51e1d 100644 --- a/test/testcases.py +++ b/test/testcases.py @@ -25,6 +25,8 @@ class TestCase: rootfs: str = "" # Sign the container_ref and use the new signed image instead of the original one sign: bool = False + # use special partition_mode like "lvm" + partition_mode: str = "" def bib_rootfs_args(self): if self.rootfs: @@ -82,11 +84,17 @@ def gen_testcases(what): # pylint: disable=too-many-return-statements TestCaseCentos(image="anaconda-iso"), ] if what == "qemu-boot": + # test partition defaults with qcow2 test_cases = [ - klass(image=img) + klass(image="qcow2") for klass in (TestCaseCentos, TestCaseFedora) - for img in ("raw", "qcow2") ] + # and custom with raw (this is arbitrary, we could do it the + # other way around too + test_cases.append( + TestCaseCentos(image="raw", partition_mode="lvm")) + test_cases.append( + TestCaseFedora(image="raw", partition_mode="btrfs")) # do a cross arch test too if platform.machine() == "x86_64": # TODO: re-enable once