diff --git a/pkg/bib/osinfo/osinfo.go b/pkg/bib/osinfo/osinfo.go index 930d38aa0c..3fabf5bfff 100644 --- a/pkg/bib/osinfo/osinfo.go +++ b/pkg/bib/osinfo/osinfo.go @@ -9,12 +9,15 @@ import ( "strings" "github.com/sirupsen/logrus" + "gopkg.in/yaml.v3" "github.com/osbuild/blueprint/pkg/blueprint" "github.com/osbuild/images/pkg/bib/blueprintload" + "github.com/osbuild/images/pkg/disk" "github.com/osbuild/images/pkg/distro" ) +// XXX: use image-builder instead? const bibPathPrefix = "usr/lib/bootc-image-builder" type OSRelease struct { @@ -37,6 +40,8 @@ type Info struct { SELinuxPolicy string ImageCustomization *blueprint.Customizations KernelInfo *KernelInfo + + PartitionTable *disk.PartitionTable } func validateOSRelease(osrelease map[string]string) error { @@ -125,6 +130,29 @@ func readImageCustomization(root string) (*blueprint.Customizations, error) { return config.Customizations, nil } +type diskYAML struct { + PartitionTable *disk.PartitionTable `json:"partition_table" yaml:"partition_table"` +} + +func readPartitionTable(root string) (*disk.PartitionTable, error) { + p := path.Join(root, bibPathPrefix, "disk.yaml") + var disk diskYAML + f, err := os.Open(p) + if err != nil { + if os.IsNotExist(err) { + return nil, nil + } + return nil, fmt.Errorf("cannot load disk definitions from %q: %w", p, err) + } + defer f.Close() + + if err := yaml.NewDecoder(f).Decode(&disk); err != nil { + return nil, fmt.Errorf("cannot parse disk definitions from %q: %w", p, err) + } + + return disk.PartitionTable, nil +} + func readKernelInfo(root string) (*KernelInfo, error) { modulesDir := path.Join(root, "usr/lib/modules") entries, err := os.ReadDir(modulesDir) @@ -177,6 +205,11 @@ func Load(root string) (*Info, error) { return nil, err } + pt, err := readPartitionTable(root) + if err != nil { + return nil, err + } + kernelInfo, err := readKernelInfo(root) if err != nil { logrus.Debugf("cannot read kernel info: %v", err) @@ -206,5 +239,6 @@ func Load(root string) (*Info, error) { SELinuxPolicy: selinuxPolicy, ImageCustomization: customization, KernelInfo: kernelInfo, + PartitionTable: pt, }, nil } diff --git a/pkg/bib/osinfo/osinfo_test.go b/pkg/bib/osinfo/osinfo_test.go index e2effdcd9d..f022958ac8 100644 --- a/pkg/bib/osinfo/osinfo_test.go +++ b/pkg/bib/osinfo/osinfo_test.go @@ -9,6 +9,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/osbuild/images/pkg/datasizes" + "github.com/osbuild/images/pkg/disk" ) func writeOSRelease(root, id, versionID, name, platformID, variantID, idLike string) error { @@ -212,3 +215,56 @@ func TestLoadInfoKernel(t *testing.T) { }) } } + +var fakePartitionTableYAML = ` +.common: + partitioning: + guids: + - &bios_boot_partition_guid "21686148-6449-6E6F-744E-656564454649" + +partition_table: + type: "gpt" + partitions: + - &bios_boot_partition + size: 1 MiB + uuid: 2866630c-0c7e-469c-bc82-c458e3fd6223 + bootable: true + type: *bios_boot_partition_guid +` + +func createPartitionTable(root, fakePartitionTableYAML string) error { + dst := path.Join(root, "/usr/lib/bootc-image-builder/disk.yaml") + if err := os.MkdirAll(path.Dir(dst), 0755); err != nil { + return err + } + return os.WriteFile(dst, []byte(fakePartitionTableYAML), 0644) +} + +func TestLoadInfoPartitionTableHappy(t *testing.T) { + root := t.TempDir() + require.NoError(t, writeOSRelease(root, "fedora", "40", "Fedora Linux", "fedora", "platform:f40", "coreos")) + require.NoError(t, createPartitionTable(root, fakePartitionTableYAML)) + + info, err := Load(root) + require.NoError(t, err) + assert.Equal(t, &disk.PartitionTable{ + Type: disk.PT_GPT, + Partitions: []disk.Partition{ + { + Bootable: true, + Size: 1 * datasizes.MiB, + Type: "21686148-6449-6E6F-744E-656564454649", + UUID: "2866630c-0c7e-469c-bc82-c458e3fd6223", + }, + }, + }, info.PartitionTable) +} + +func TestLoadInfoPartitionTableSad(t *testing.T) { + root := t.TempDir() + require.NoError(t, writeOSRelease(root, "fedora", "40", "Fedora Linux", "fedora", "platform:f40", "coreos")) + require.NoError(t, createPartitionTable(root, "@invalidYAML")) + + _, err := Load(root) + assert.EqualError(t, err, fmt.Sprintf(`cannot parse disk definitions from "%s/usr/lib/bootc-image-builder/disk.yaml": yaml: found character that cannot start any token`, root)) +} diff --git a/pkg/distro/bootc/export_test.go b/pkg/distro/bootc/export_test.go index 34b63b3424..39d35e581a 100644 --- a/pkg/distro/bootc/export_test.go +++ b/pkg/distro/bootc/export_test.go @@ -16,6 +16,8 @@ var ( UpdateFilesystemSizes = updateFilesystemSizes CreateRand = createRand CalcRequiredDirectorySizes = calcRequiredDirectorySizes + + TestDiskContainers = diskContainers ) func NewTestBootcImageType() *BootcImageType { @@ -40,6 +42,10 @@ func NewTestBootcImageType() *BootcImageType { return imgType } +func (t *BootcImageType) SetSourceInfoPartitionTable(basept *disk.PartitionTable) { + t.arch.distro.sourceInfo.PartitionTable = basept +} + func (t *BootcImageType) GenPartitionTable(customizations *blueprint.Customizations, rootfsMinSize uint64, rng *rand.Rand) (*disk.PartitionTable, error) { return t.genPartitionTable(customizations, rootfsMinSize, rng) } diff --git a/pkg/distro/bootc/image_test.go b/pkg/distro/bootc/image_test.go index 320bb78288..1cc6913c13 100644 --- a/pkg/distro/bootc/image_test.go +++ b/pkg/distro/bootc/image_test.go @@ -8,6 +8,7 @@ import ( "github.com/osbuild/blueprint/pkg/blueprint" "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/datasizes" "github.com/osbuild/images/pkg/disk" "github.com/osbuild/images/pkg/disk/partition" @@ -660,3 +661,21 @@ func TestManifestDirCustomizationsSad(t *testing.T) { _, _, err := imgType.Manifest(bp, distro.ImageOptions{}, nil, common.ToPtr(int64(0))) assert.EqualError(t, err, `the following custom directories are not allowed: ["/dir/not/allowed"]`) } + +func TestGenPartitionTableFromOSInfo(t *testing.T) { + var bp blueprint.Blueprint + imgType := bootc.NewTestBootcImageType() + // pretend a custom partition table is set via the bootc + // container sourceInfo mechanism + newPt := bootc.PartitionTables[arch.Current().String()] + newPt.UUID = "01010101-01011-01011-01011-01010101" + imgType.SetSourceInfoPartitionTable(&newPt) + + // validate that the container uuid is part of the generated + // manifest + mf, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, common.ToPtr(int64(0))) + assert.NoError(t, err) + manifestJson, err := mf.Serialize(nil, bootc.TestDiskContainers, nil, nil) + assert.NoError(t, err) + assert.Contains(t, string(manifestJson), "01010101-01011-01011-01011-01010101") +} diff --git a/pkg/distro/bootc/partition.go b/pkg/distro/bootc/partition.go index 6dbd853a07..8e93add88e 100644 --- a/pkg/distro/bootc/partition.go +++ b/pkg/distro/bootc/partition.go @@ -61,12 +61,28 @@ var ( }) ) +func (t *BootcImageType) basePartitionTable() (*disk.PartitionTable, error) { + // base partition table can come from the container + if t.arch.distro.sourceInfo != nil && t.arch.distro.sourceInfo.PartitionTable != nil { + return t.arch.distro.sourceInfo.PartitionTable, nil + } + // get it from the build-in fallback partition tables + if pt, ok := partitionTables[t.arch.Name()]; ok { + return &pt, nil + } + return nil, fmt.Errorf("cannot find a base partition table for %q", t.Name()) +} + func (t *BootcImageType) genPartitionTable(customizations *blueprint.Customizations, rootfsMinSize uint64, rng *rand.Rand) (*disk.PartitionTable, error) { fsCust := customizations.GetFilesystems() diskCust, err := customizations.GetPartitioning() if err != nil { return nil, fmt.Errorf("error reading disk customizations: %w", err) } + basept, err := t.basePartitionTable() + if err != nil { + return nil, err + } // Embedded disk customization applies if there was no local customization if fsCust == nil && diskCust == nil && t.arch.distro.sourceInfo != nil && t.arch.distro.sourceInfo.ImageCustomization != nil { @@ -85,12 +101,12 @@ func (t *BootcImageType) genPartitionTable(customizations *blueprint.Customizati case fsCust != nil && diskCust != nil: return nil, fmt.Errorf("cannot combine disk and filesystem customizations") case diskCust != nil: - partitionTable, err = t.genPartitionTableDiskCust(diskCust, rootfsMinSize, rng) + partitionTable, err = t.genPartitionTableDiskCust(basept, diskCust, rootfsMinSize, rng) if err != nil { return nil, err } default: - partitionTable, err = t.genPartitionTableFsCust(fsCust, rootfsMinSize, rng) + partitionTable, err = t.genPartitionTableFsCust(basept, fsCust, rootfsMinSize, rng) if err != nil { return nil, err } @@ -110,15 +126,14 @@ func (t *BootcImageType) genPartitionTable(customizations *blueprint.Customizati return partitionTable, nil } -func (t *BootcImageType) genPartitionTableDiskCust(diskCust *blueprint.DiskCustomization, rootfsMinSize uint64, rng *rand.Rand) (*disk.PartitionTable, error) { +func (t *BootcImageType) genPartitionTableDiskCust(basept *disk.PartitionTable, diskCust *blueprint.DiskCustomization, rootfsMinSize uint64, rng *rand.Rand) (*disk.PartitionTable, error) { if err := diskCust.ValidateLayoutConstraints(); err != nil { return nil, fmt.Errorf("cannot use disk customization: %w", err) } diskCust.MinSize = max(diskCust.MinSize, rootfsMinSize) - basept, ok := partitionTables[t.arch.Name()] - if !ok { + if basept == nil { return nil, fmt.Errorf("pipelines: no partition tables defined for %s", t.arch.Name()) } defaultFSType, err := disk.NewFSType(t.arch.distro.defaultFs) @@ -140,9 +155,8 @@ func (t *BootcImageType) genPartitionTableDiskCust(diskCust *blueprint.DiskCusto return disk.NewCustomPartitionTable(diskCust, partOptions, rng) } -func (t *BootcImageType) genPartitionTableFsCust(fsCust []blueprint.FilesystemCustomization, rootfsMinSize uint64, rng *rand.Rand) (*disk.PartitionTable, error) { - basept, ok := partitionTables[t.arch.Name()] - if !ok { +func (t *BootcImageType) genPartitionTableFsCust(basept *disk.PartitionTable, fsCust []blueprint.FilesystemCustomization, rootfsMinSize uint64, rng *rand.Rand) (*disk.PartitionTable, error) { + if basept == nil { return nil, fmt.Errorf("pipelines: no partition tables defined for %s", t.arch.Name()) } @@ -155,7 +169,7 @@ func (t *BootcImageType) genPartitionTableFsCust(fsCust []blueprint.FilesystemCu } fsCustomizations := updateFilesystemSizes(fsCust, rootfsMinSize) - pt, err := disk.NewPartitionTable(&basept, fsCustomizations, DEFAULT_SIZE, partitioningMode, t.arch.arch, nil, rng) + pt, err := disk.NewPartitionTable(basept, fsCustomizations, DEFAULT_SIZE, partitioningMode, t.arch.arch, nil, rng) if err != nil { return nil, err }