diff --git a/bib/cmd/bootc-image-builder/export_test.go b/bib/cmd/bootc-image-builder/export_test.go index ae62449d..8dcbaef9 100644 --- a/bib/cmd/bootc-image-builder/export_test.go +++ b/bib/cmd/bootc-image-builder/export_test.go @@ -1,16 +1,10 @@ package main var ( - CanChownInPath = canChownInPath - CheckFilesystemCustomizations = checkFilesystemCustomizations - GetDistroAndRunner = getDistroAndRunner - CheckMountpoints = checkMountpoints - PartitionTables = partitionTables - UpdateFilesystemSizes = updateFilesystemSizes - GenPartitionTable = genPartitionTable - CreateRand = createRand - BuildCobraCmdline = buildCobraCmdline - CalcRequiredDirectorySizes = calcRequiredDirectorySizes + CanChownInPath = canChownInPath + GetDistroAndRunner = getDistroAndRunner + CreateRand = createRand + BuildCobraCmdline = buildCobraCmdline ) func MockOsGetuid(new func() int) (restore func()) { diff --git a/bib/cmd/bootc-image-builder/image.go b/bib/cmd/bootc-image-builder/image.go index df670c37..78d3b9d7 100644 --- a/bib/cmd/bootc-image-builder/image.go +++ b/bib/cmd/bootc-image-builder/image.go @@ -2,12 +2,10 @@ package main import ( cryptorand "crypto/rand" - "errors" "fmt" "math" "math/big" "math/rand" - "path/filepath" "slices" "strconv" "strings" @@ -18,15 +16,11 @@ import ( "github.com/osbuild/images/pkg/container" "github.com/osbuild/images/pkg/customizations/anaconda" "github.com/osbuild/images/pkg/customizations/kickstart" - "github.com/osbuild/images/pkg/customizations/users" "github.com/osbuild/images/pkg/disk" - "github.com/osbuild/images/pkg/disk/partition" "github.com/osbuild/images/pkg/image" "github.com/osbuild/images/pkg/manifest" "github.com/osbuild/images/pkg/osbuild" - "github.com/osbuild/images/pkg/pathpolicy" "github.com/osbuild/images/pkg/platform" - "github.com/osbuild/images/pkg/policies" "github.com/osbuild/images/pkg/rpmmd" "github.com/osbuild/images/pkg/runner" "github.com/sirupsen/logrus" @@ -35,9 +29,6 @@ import ( "github.com/osbuild/bootc-image-builder/bib/internal/imagetypes" ) -// TODO: Auto-detect this from container image metadata -const DEFAULT_SIZE = uint64(10 * GibiByte) - type ManifestConfig struct { // OCI image path (without the transport, that is always docker://) Imgref string @@ -51,10 +42,6 @@ type ManifestConfig struct { // CPU architecture of the image Architecture arch.Arch - // The minimum size required for the root fs in order to fit the container - // contents - RootfsMinsize uint64 - // Paths to the directory with the distro definitions DistroDefPaths []string @@ -69,404 +56,6 @@ type ManifestConfig struct { UseLibrepo bool } -func Manifest(c *ManifestConfig) (*manifest.Manifest, error) { - rng := createRand() - - if c.ImageTypes.BuildsISO() { - return manifestForISO(c, rng) - } - return manifestForDiskImage(c, rng) -} - -var ( - // The mountpoint policy for bootc images is more restrictive than the - // ostree mountpoint policy defined in osbuild/images. It only allows / - // (for sizing the root partition) and custom mountpoints under /var but - // not /var itself. - - // Since our policy library doesn't support denying a path while allowing - // its subpaths (only the opposite), we augment the standard policy check - // with a simple search through the custom mountpoints to deny /var - // specifically. - mountpointPolicy = pathpolicy.NewPathPolicies(map[string]pathpolicy.PathPolicy{ - // allow all existing mountpoints (but no subdirs) to support size customizations - "/": {Deny: false, Exact: true}, - "/boot": {Deny: false, Exact: true}, - - // /var is not allowed, but we need to allow any subdirectories that - // are not denied below, so we allow it initially and then check it - // separately (in checkMountpoints()) - "/var": {Deny: false}, - - // /var subdir denials - "/var/home": {Deny: true}, - "/var/lock": {Deny: true}, // symlink to ../run/lock which is on tmpfs - "/var/mail": {Deny: true}, // symlink to spool/mail - "/var/mnt": {Deny: true}, - "/var/roothome": {Deny: true}, - "/var/run": {Deny: true}, // symlink to ../run which is on tmpfs - "/var/srv": {Deny: true}, - "/var/usrlocal": {Deny: true}, - }) - - mountpointMinimalPolicy = pathpolicy.NewPathPolicies(map[string]pathpolicy.PathPolicy{ - // allow all existing mountpoints to support size customizations - "/": {Deny: false, Exact: true}, - "/boot": {Deny: false, Exact: true}, - }) -) - -func checkMountpoints(filesystems []blueprint.FilesystemCustomization, policy *pathpolicy.PathPolicies) error { - errs := []error{} - for _, fs := range filesystems { - if err := policy.Check(fs.Mountpoint); err != nil { - errs = append(errs, err) - } - if fs.Mountpoint == "/var" { - // this error message is consistent with the errors returned by policy.Check() - // TODO: remove trailing space inside the quoted path when the function is fixed in osbuild/images. - errs = append(errs, fmt.Errorf(`path "/var" is not allowed`)) - } - } - if len(errs) > 0 { - return fmt.Errorf("the following errors occurred while validating custom mountpoints:\n%w", errors.Join(errs...)) - } - return nil -} - -func checkFilesystemCustomizations(fsCustomizations []blueprint.FilesystemCustomization, ptmode partition.PartitioningMode) error { - var policy *pathpolicy.PathPolicies - switch ptmode { - case partition.BtrfsPartitioningMode: - // btrfs subvolumes are not supported at build time yet, so we only - // allow / and /boot to be customized when building a btrfs disk (the - // minimal policy) - policy = mountpointMinimalPolicy - default: - policy = mountpointPolicy - } - if err := checkMountpoints(fsCustomizations, policy); err != nil { - return err - } - return nil -} - -// updateFilesystemSizes updates the size of the root filesystem customization -// based on the minRootSize. The new min size whichever is larger between the -// existing size and the minRootSize. If the root filesystem is not already -// configured, a new customization is added. -func updateFilesystemSizes(fsCustomizations []blueprint.FilesystemCustomization, minRootSize uint64) []blueprint.FilesystemCustomization { - updated := make([]blueprint.FilesystemCustomization, len(fsCustomizations), len(fsCustomizations)+1) - hasRoot := false - for idx, fsc := range fsCustomizations { - updated[idx] = fsc - if updated[idx].Mountpoint == "/" { - updated[idx].MinSize = max(updated[idx].MinSize, minRootSize) - hasRoot = true - } - } - - if !hasRoot { - // no root customization found: add it - updated = append(updated, blueprint.FilesystemCustomization{Mountpoint: "/", MinSize: minRootSize}) - } - return updated -} - -// setFSTypes sets the filesystem types for all mountable entities to match the -// selected rootfs type. -// If rootfs is 'btrfs', the function will keep '/boot' to its default. -func setFSTypes(pt *disk.PartitionTable, rootfs string) error { - if rootfs == "" { - return fmt.Errorf("root filesystem type is empty") - } - - return pt.ForEachMountable(func(mnt disk.Mountable, _ []disk.Entity) error { - switch mnt.GetMountpoint() { - case "/boot/efi": - // never change the efi partition's type - return nil - case "/boot": - // change only if we're not doing btrfs - if rootfs == "btrfs" { - return nil - } - fallthrough - default: - switch elem := mnt.(type) { - case *disk.Filesystem: - elem.Type = rootfs - case *disk.BtrfsSubvolume: - // nothing to do - default: - return fmt.Errorf("the mountable disk entity for %q of the base partition table is not an ordinary filesystem but %T", mnt.GetMountpoint(), mnt) - } - return nil - } - }) -} - -func genPartitionTable(c *ManifestConfig, customizations *blueprint.Customizations, 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) - } - - // Embedded disk customization applies if there was no local customization - if fsCust == nil && diskCust == nil && c.SourceInfo != nil && c.SourceInfo.ImageCustomization != nil { - imageCustomizations := c.SourceInfo.ImageCustomization - - fsCust = imageCustomizations.GetFilesystems() - diskCust, err = imageCustomizations.GetPartitioning() - if err != nil { - return nil, fmt.Errorf("error reading disk customizations: %w", err) - } - } - - var partitionTable *disk.PartitionTable - switch { - // XXX: move into images library - case fsCust != nil && diskCust != nil: - return nil, fmt.Errorf("cannot combine disk and filesystem customizations") - case diskCust != nil: - partitionTable, err = genPartitionTableDiskCust(c, diskCust, rng) - if err != nil { - return nil, err - } - default: - partitionTable, err = genPartitionTableFsCust(c, fsCust, rng) - if err != nil { - return nil, err - } - } - - // Ensure ext4 rootfs has fs-verity enabled - rootfs := partitionTable.FindMountable("/") - if rootfs != nil { - switch elem := rootfs.(type) { - case *disk.Filesystem: - if elem.Type == "ext4" { - elem.MkfsOptions = append(elem.MkfsOptions, []disk.MkfsOption{disk.MkfsVerity}...) - } - } - } - - if c.SourceInfo != nil && c.SourceInfo.KernelInfo != nil && c.SourceInfo.KernelInfo.HasAbootImg { - idx := slices.IndexFunc(partitionTable.Partitions, func(part disk.Partition) bool { - // The aboot support in ostree supports both traditional android verified boot and - // ukiboot. For aboot, the partition is labeled "boot_a", as described in - // https://source.android.com/docs/core/ota/ab/ab_implement - // For ukibooot (https://gitlab.com/CentOS/automotive/src/ukiboot) the partition - // either has label ukiboot_a (GPT) or type 0x46 (MBR). - return part.Label == "boot_a" || part.Label == "ukiboot_a" || part.Type == "46" - }) - if idx >= 0 { - sourcePipeline := "build" - if c.BuildSourceInfo != nil { - sourcePipeline = "target" - } - - partitionTable.Partitions[idx].Payload = &disk.Raw{ - SourcePipeline: sourcePipeline, - SourcePath: filepath.Join("/usr/lib/modules/", c.SourceInfo.KernelInfo.Version, "aboot.img"), - } - } - } - - return partitionTable, nil -} - -// calcRequiredDirectorySizes will calculate the minimum sizes for / -// for disk customizations. We need this because with advanced partitioning -// we never grow the rootfs to the size of the disk (unlike the tranditional -// filesystem customizations). -// -// So we need to go over the customizations and ensure the min-size for "/" -// is at least rootfsMinSize. -// -// Note that a custom "/usr" is not supported in image mode so splitting -// rootfsMinSize between / and /usr is not a concern. -func calcRequiredDirectorySizes(distCust *blueprint.DiskCustomization, rootfsMinSize uint64) (map[string]uint64, error) { - // XXX: this has *way* too much low-level knowledge about the - // inner workings of blueprint.DiskCustomizations plus when - // a new type it needs to get added here too, think about - // moving into "images" instead (at least partly) - mounts := map[string]uint64{} - for _, part := range distCust.Partitions { - switch part.Type { - case "", "plain": - mounts[part.Mountpoint] = part.MinSize - case "lvm": - for _, lv := range part.LogicalVolumes { - mounts[lv.Mountpoint] = part.MinSize - } - case "btrfs": - for _, subvol := range part.Subvolumes { - mounts[subvol.Mountpoint] = part.MinSize - } - default: - return nil, fmt.Errorf("unknown disk customization type %q", part.Type) - } - } - // ensure rootfsMinSize is respected - return map[string]uint64{ - "/": max(rootfsMinSize, mounts["/"]), - }, nil -} - -func genPartitionTableDiskCust(c *ManifestConfig, diskCust *blueprint.DiskCustomization, 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, 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 - } - requiredMinSizes, err := calcRequiredDirectorySizes(diskCust, c.RootfsMinsize) - 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, - RequiredMinSizes: requiredMinSizes, - Architecture: c.Architecture, - } - 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) - } - - partitioningMode := partition.RawPartitioningMode - if c.RootFSType == "btrfs" { - partitioningMode = partition.BtrfsPartitioningMode - } - if err := checkFilesystemCustomizations(fsCust, partitioningMode); err != nil { - return nil, err - } - fsCustomizations := updateFilesystemSizes(fsCust, c.RootfsMinsize) - - pt, err := disk.NewPartitionTable(&basept, fsCustomizations, DEFAULT_SIZE, partitioningMode, c.Architecture, nil, rng) - if err != nil { - return nil, err - } - - if err := setFSTypes(pt, c.RootFSType); err != nil { - return nil, fmt.Errorf("error setting root filesystem type: %w", err) - } - return pt, nil -} - -func manifestForDiskImage(c *ManifestConfig, rng *rand.Rand) (*manifest.Manifest, error) { - if c.Imgref == "" { - return nil, fmt.Errorf("pipeline: no base image defined") - } - containerSource := container.SourceSpec{ - Source: c.Imgref, - Name: c.Imgref, - Local: true, - } - buildContainerSource := container.SourceSpec{ - Source: c.BuildImgref, - Name: c.BuildImgref, - Local: true, - } - - var customizations *blueprint.Customizations - if c.Config != nil { - customizations = c.Config.Customizations - } - - platform := &platform.Data{ - Arch: c.Architecture, - UEFIVendor: c.SourceInfo.UEFIVendor, - QCOW2Compat: "1.1", - } - switch c.Architecture { - case arch.ARCH_X86_64: - platform.BIOSPlatform = "i386-pc" - case arch.ARCH_PPC64LE: - platform.BIOSPlatform = "powerpc-ieee1275" - case arch.ARCH_S390X: - platform.ZiplSupport = true - } - // For the bootc-disk image, the filename is the basename and the extension - // is added automatically for each disk format - filename := "disk" - - img := image.NewBootcDiskImage(platform, filename, containerSource, buildContainerSource) - img.OSCustomizations.Users = users.UsersFromBP(customizations.GetUsers()) - img.OSCustomizations.Groups = users.GroupsFromBP(customizations.GetGroups()) - img.OSCustomizations.SELinux = c.SourceInfo.SELinuxPolicy - img.OSCustomizations.BuildSELinux = img.OSCustomizations.SELinux - if c.BuildSourceInfo != nil { - img.OSCustomizations.BuildSELinux = c.BuildSourceInfo.SELinuxPolicy - } - - img.OSCustomizations.KernelOptionsAppend = []string{ - "rw", - // TODO: Drop this as we expect kargs to come from the container image, - // xref https://github.com/CentOS/centos-bootc-layered/blob/main/cloud/usr/lib/bootc/install/05-cloud-kargs.toml - "console=tty0", - "console=ttyS0", - } - - if kopts := customizations.GetKernel(); kopts != nil && kopts.Append != "" { - img.OSCustomizations.KernelOptionsAppend = append(img.OSCustomizations.KernelOptionsAppend, kopts.Append) - } - - pt, err := genPartitionTable(c, customizations, rng) - if err != nil { - return nil, err - } - img.PartitionTable = pt - - // Check Directory/File Customizations are valid - dc := customizations.GetDirectories() - fc := customizations.GetFiles() - if err := blueprint.ValidateDirFileCustomizations(dc, fc); err != nil { - return nil, err - } - if err := blueprint.CheckDirectoryCustomizationsPolicy(dc, policies.OstreeCustomDirectoriesPolicies); err != nil { - return nil, err - } - if err := blueprint.CheckFileCustomizationsPolicy(fc, policies.OstreeCustomFilesPolicies); err != nil { - return nil, err - } - img.OSCustomizations.Files, err = blueprint.FileCustomizationsToFsNodeFiles(fc) - if err != nil { - return nil, err - } - img.OSCustomizations.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(dc) - if err != nil { - return nil, err - } - - mf := manifest.New() - mf.Distro = manifest.DISTRO_FEDORA - runner := &runner.Linux{} - - if err := img.InstantiateManifestFromContainers(&mf, []container.SourceSpec{containerSource}, runner, rng); err != nil { - return nil, err - } - - return &mf, nil -} - func labelForISO(os *osinfo.OSRelease, arch *arch.Arch) string { switch os.ID { case "fedora": diff --git a/bib/cmd/bootc-image-builder/image_test.go b/bib/cmd/bootc-image-builder/image_test.go index acddc18f..a204b181 100644 --- a/bib/cmd/bootc-image-builder/image_test.go +++ b/bib/cmd/bootc-image-builder/image_test.go @@ -7,11 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/osbuild/blueprint/pkg/blueprint" - "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" "github.com/osbuild/images/pkg/manifest" "github.com/osbuild/images/pkg/runner" @@ -61,658 +56,3 @@ func TestGetDistroAndRunner(t *testing.T) { }) } } - -func TestCheckFilesystemCustomizationsValidates(t *testing.T) { - for _, tc := range []struct { - fsCust []blueprint.FilesystemCustomization - ptmode partition.PartitioningMode - expectedErr string - }{ - // happy - { - fsCust: []blueprint.FilesystemCustomization{}, - expectedErr: "", - }, - { - fsCust: []blueprint.FilesystemCustomization{}, - ptmode: partition.BtrfsPartitioningMode, - expectedErr: "", - }, - { - fsCust: []blueprint.FilesystemCustomization{ - {Mountpoint: "/"}, {Mountpoint: "/boot"}, - }, - ptmode: partition.RawPartitioningMode, - expectedErr: "", - }, - { - fsCust: []blueprint.FilesystemCustomization{ - {Mountpoint: "/"}, {Mountpoint: "/boot"}, - }, - ptmode: partition.BtrfsPartitioningMode, - expectedErr: "", - }, - { - fsCust: []blueprint.FilesystemCustomization{ - {Mountpoint: "/"}, - {Mountpoint: "/boot"}, - {Mountpoint: "/var/log"}, - {Mountpoint: "/var/data"}, - }, - expectedErr: "", - }, - // sad - { - fsCust: []blueprint.FilesystemCustomization{ - {Mountpoint: "/"}, - {Mountpoint: "/ostree"}, - }, - ptmode: partition.RawPartitioningMode, - expectedErr: "the following errors occurred while validating custom mountpoints:\npath \"/ostree\" is not allowed", - }, - { - fsCust: []blueprint.FilesystemCustomization{ - {Mountpoint: "/"}, - {Mountpoint: "/var"}, - }, - ptmode: partition.RawPartitioningMode, - expectedErr: "the following errors occurred while validating custom mountpoints:\npath \"/var\" is not allowed", - }, - { - fsCust: []blueprint.FilesystemCustomization{ - {Mountpoint: "/"}, - {Mountpoint: "/var/data"}, - }, - ptmode: partition.BtrfsPartitioningMode, - expectedErr: "the following errors occurred while validating custom mountpoints:\npath \"/var/data\" is not allowed", - }, - { - fsCust: []blueprint.FilesystemCustomization{ - {Mountpoint: "/"}, - {Mountpoint: "/boot/"}, - }, - ptmode: partition.BtrfsPartitioningMode, - expectedErr: "the following errors occurred while validating custom mountpoints:\npath \"/boot/\" must be canonical", - }, - { - fsCust: []blueprint.FilesystemCustomization{ - {Mountpoint: "/"}, - {Mountpoint: "/boot/"}, - {Mountpoint: "/opt"}, - }, - ptmode: partition.BtrfsPartitioningMode, - expectedErr: "the following errors occurred while validating custom mountpoints:\npath \"/boot/\" must be canonical\npath \"/opt\" is not allowed", - }, - } { - if tc.expectedErr == "" { - assert.NoError(t, bib.CheckFilesystemCustomizations(tc.fsCust, tc.ptmode)) - } else { - assert.ErrorContains(t, bib.CheckFilesystemCustomizations(tc.fsCust, tc.ptmode), tc.expectedErr) - } - } -} - -func TestLocalMountpointPolicy(t *testing.T) { - // extended testing of the general mountpoint policy (non-minimal) - type testCase struct { - path string - allowed bool - } - - testCases := []testCase{ - // existing mountpoints / and /boot are fine for sizing - {"/", true}, - {"/boot", true}, - - // root mountpoints are not allowed - {"/data", false}, - {"/opt", false}, - {"/stuff", false}, - {"/usr", false}, - - // /var explicitly is not allowed - {"/var", false}, - - // subdirs of /boot are not allowed - {"/boot/stuff", false}, - {"/boot/loader", false}, - - // /var subdirectories are allowed - {"/var/data", true}, - {"/var/scratch", true}, - {"/var/log", true}, - {"/var/opt", true}, - {"/var/opt/application", true}, - - // but not these - {"/var/home", false}, - {"/var/lock", false}, // symlink to ../run/lock which is on tmpfs - {"/var/mail", false}, // symlink to spool/mail - {"/var/mnt", false}, - {"/var/roothome", false}, - {"/var/run", false}, // symlink to ../run which is on tmpfs - {"/var/srv", false}, - {"/var/usrlocal", false}, - - // nor their subdirs - {"/var/run/subrun", false}, - {"/var/srv/test", false}, - {"/var/home/user", false}, - {"/var/usrlocal/bin", false}, - } - - for _, tc := range testCases { - t.Run(tc.path, func(t *testing.T) { - err := bib.CheckFilesystemCustomizations([]blueprint.FilesystemCustomization{{Mountpoint: tc.path}}, partition.RawPartitioningMode) - if err != nil && tc.allowed { - t.Errorf("expected %s to be allowed, but got error: %v", tc.path, err) - } else if err == nil && !tc.allowed { - t.Errorf("expected %s to be denied, but got no error", tc.path) - } - }) - } -} - -func TestBasePartitionTablesHaveRoot(t *testing.T) { - // make sure that all base partition tables have at least a root partition defined - for arch, pt := range bib.PartitionTables { - rootMountable := pt.FindMountable("/") - if rootMountable == nil { - t.Errorf("partition table %q does not define a root filesystem", arch) - } - _, isFS := rootMountable.(*disk.Filesystem) - if !isFS { - t.Errorf("root mountable for %q is not an ordinary filesystem", arch) - } - } - -} - -func TestUpdateFilesystemSizes(t *testing.T) { - type testCase struct { - customizations []blueprint.FilesystemCustomization - minRootSize uint64 - expected []blueprint.FilesystemCustomization - } - - testCases := map[string]testCase{ - "simple": { - customizations: nil, - minRootSize: 999, - expected: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/", - MinSize: 999, - }, - }, - }, - "container-is-larger": { - customizations: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/", - MinSize: 10, - }, - }, - minRootSize: 999, - expected: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/", - MinSize: 999, - }, - }, - }, - "container-is-smaller": { - customizations: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/", - MinSize: 1000, - }, - }, - minRootSize: 892, - expected: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/", - MinSize: 1000, - }, - }, - }, - "customizations-noroot": { - customizations: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/var/data", - MinSize: 1_000_000, - }, - }, - minRootSize: 9000, - expected: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/var/data", - MinSize: 1_000_000, - }, - { - Mountpoint: "/", - MinSize: 9000, - }, - }, - }, - "customizations-withroot-smallcontainer": { - customizations: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/var/data", - MinSize: 1_000_000, - }, - { - Mountpoint: "/", - MinSize: 2_000_000, - }, - }, - minRootSize: 9000, - expected: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/var/data", - MinSize: 1_000_000, - }, - { - Mountpoint: "/", - MinSize: 2_000_000, - }, - }, - }, - "customizations-withroot-largecontainer": { - customizations: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/var/data", - MinSize: 1_000_000, - }, - { - Mountpoint: "/", - MinSize: 2_000_000, - }, - }, - minRootSize: 9_000_000, - expected: []blueprint.FilesystemCustomization{ - { - Mountpoint: "/var/data", - MinSize: 1_000_000, - }, - { - Mountpoint: "/", - MinSize: 9_000_000, - }, - }, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert.ElementsMatch(t, bib.UpdateFilesystemSizes(tc.customizations, tc.minRootSize), tc.expected) - }) - } - -} - -func findMountableSizeableFor(pt *disk.PartitionTable, needle string) (disk.Mountable, disk.Sizeable) { - var foundMnt disk.Mountable - var foundParent disk.Sizeable - err := pt.ForEachMountable(func(mnt disk.Mountable, path []disk.Entity) error { - if mnt.GetMountpoint() == needle { - foundMnt = mnt - for idx := len(path) - 1; idx >= 0; idx-- { - if sz, ok := path[idx].(disk.Sizeable); ok { - foundParent = sz - break - } - } - } - return nil - }) - if err != nil { - panic(err) - } - return foundMnt, foundParent -} - -func TestGenPartitionTableSetsRootfsForAllFilesystemsXFS(t *testing.T) { - rng := bib.CreateRand() - - cnf := &bib.ManifestConfig{ - Architecture: arch.ARCH_X86_64, - RootFSType: "xfs", - } - cus := &blueprint.Customizations{ - Filesystem: []blueprint.FilesystemCustomization{ - {Mountpoint: "/var/data", MinSize: 2_000_000}, - {Mountpoint: "/var/stuff", MinSize: 10_000_000}, - }, - } - pt, err := bib.GenPartitionTable(cnf, cus, rng) - assert.NoError(t, err) - - for _, mntPoint := range []string{"/", "/boot", "/var/data"} { - mnt, _ := findMountableSizeableFor(pt, mntPoint) - assert.Equal(t, "xfs", mnt.GetFSType()) - } - _, parent := findMountableSizeableFor(pt, "/var/data") - assert.True(t, parent.GetSize() >= 2_000_000) - - _, parent = findMountableSizeableFor(pt, "/var/stuff") - assert.True(t, parent.GetSize() >= 10_000_000) - - // ESP is always vfat - mnt, _ := findMountableSizeableFor(pt, "/boot/efi") - assert.Equal(t, "vfat", mnt.GetFSType()) -} - -func TestGenPartitionTableSetsRootfsForAllFilesystemsBtrfs(t *testing.T) { - rng := bib.CreateRand() - - cnf := &bib.ManifestConfig{ - Architecture: arch.ARCH_X86_64, - RootFSType: "btrfs", - } - cus := &blueprint.Customizations{} - pt, err := bib.GenPartitionTable(cnf, cus, rng) - assert.NoError(t, err) - - mnt, _ := findMountableSizeableFor(pt, "/") - assert.Equal(t, "btrfs", mnt.GetFSType()) - - // btrfs has a default (ext4) /boot - mnt, _ = findMountableSizeableFor(pt, "/boot") - assert.Equal(t, "ext4", mnt.GetFSType()) - - // ESP is always vfat - mnt, _ = findMountableSizeableFor(pt, "/boot/efi") - assert.Equal(t, "vfat", mnt.GetFSType()) -} - -func TestGenPartitionTableDiskCustomizationRunsValidateLayoutConstraints(t *testing.T) { - rng := bib.CreateRand() - - cnf := &bib.ManifestConfig{ - Architecture: arch.ARCH_X86_64, - RootFSType: "xfs", - } - cus := &blueprint.Customizations{ - Disk: &blueprint.DiskCustomization{ - Partitions: []blueprint.PartitionCustomization{ - { - Type: "lvm", - VGCustomization: blueprint.VGCustomization{}, - }, - { - Type: "lvm", - VGCustomization: blueprint.VGCustomization{}, - }, - }, - }, - } - _, err := bib.GenPartitionTable(cnf, cus, rng) - assert.EqualError(t, err, "cannot use disk customization: multiple LVM volume groups are not yet supported") -} - -func TestGenPartitionTableDiskCustomizationUnknownTypesError(t *testing.T) { - cus := &blueprint.Customizations{ - Disk: &blueprint.DiskCustomization{ - Partitions: []blueprint.PartitionCustomization{ - { - Type: "rando", - }, - }, - }, - } - _, err := bib.CalcRequiredDirectorySizes(cus.Disk, 5*datasizes.GiB) - assert.EqualError(t, err, `unknown disk customization type "rando"`) -} - -func TestGenPartitionTableDiskCustomizationSizes(t *testing.T) { - rng := bib.CreateRand() - - for _, tc := range []struct { - name string - rootfsMinSize uint64 - partitions []blueprint.PartitionCustomization - expectedMinRootSize uint64 - }{ - { - "empty disk customizaton, root expands to rootfsMinsize", - 2 * datasizes.GiB, - nil, - 2 * datasizes.GiB, - }, - // plain - { - "plain, no root minsize, expands to rootfsMinSize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - MinSize: 10 * datasizes.GiB, - FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ - Mountpoint: "/var", - FSType: "xfs", - }, - }, - }, - 5 * datasizes.GiB, - }, - { - "plain, small root minsize, expands to rootfsMnSize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - MinSize: 1 * datasizes.GiB, - FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ - Mountpoint: "/", - FSType: "xfs", - }, - }, - }, - 5 * datasizes.GiB, - }, - { - "plain, big root minsize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - MinSize: 10 * datasizes.GiB, - FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ - Mountpoint: "/", - FSType: "xfs", - }, - }, - }, - 10 * datasizes.GiB, - }, - // btrfs - { - "btrfs, no root minsize, expands to rootfsMinSize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - Type: "btrfs", - MinSize: 10 * datasizes.GiB, - BtrfsVolumeCustomization: blueprint.BtrfsVolumeCustomization{ - Subvolumes: []blueprint.BtrfsSubvolumeCustomization{ - { - Mountpoint: "/var", - Name: "varvol", - }, - }, - }, - }, - }, - 5 * datasizes.GiB, - }, - { - "btrfs, small root minsize, expands to rootfsMnSize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - Type: "btrfs", - MinSize: 1 * datasizes.GiB, - BtrfsVolumeCustomization: blueprint.BtrfsVolumeCustomization{ - Subvolumes: []blueprint.BtrfsSubvolumeCustomization{ - { - Mountpoint: "/", - Name: "rootvol", - }, - }, - }, - }, - }, - 5 * datasizes.GiB, - }, - { - "btrfs, big root minsize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - Type: "btrfs", - MinSize: 10 * datasizes.GiB, - BtrfsVolumeCustomization: blueprint.BtrfsVolumeCustomization{ - Subvolumes: []blueprint.BtrfsSubvolumeCustomization{ - { - Mountpoint: "/", - Name: "rootvol", - }, - }, - }, - }, - }, - 10 * datasizes.GiB, - }, - // lvm - { - "lvm, no root minsize, expands to rootfsMinSize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - Type: "lvm", - MinSize: 10 * datasizes.GiB, - VGCustomization: blueprint.VGCustomization{ - LogicalVolumes: []blueprint.LVCustomization{ - { - MinSize: 10 * datasizes.GiB, - FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ - Mountpoint: "/var", - FSType: "xfs", - }, - }, - }, - }, - }, - }, - 5 * datasizes.GiB, - }, - { - "lvm, small root minsize, expands to rootfsMnSize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - Type: "lvm", - MinSize: 1 * datasizes.GiB, - VGCustomization: blueprint.VGCustomization{ - LogicalVolumes: []blueprint.LVCustomization{ - { - MinSize: 1 * datasizes.GiB, - FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ - Mountpoint: "/", - FSType: "xfs", - }, - }, - }, - }, - }, - }, - 5 * datasizes.GiB, - }, - { - "lvm, big root minsize", - 5 * datasizes.GiB, - []blueprint.PartitionCustomization{ - { - Type: "lvm", - MinSize: 10 * datasizes.GiB, - VGCustomization: blueprint.VGCustomization{ - LogicalVolumes: []blueprint.LVCustomization{ - { - MinSize: 10 * datasizes.GiB, - FilesystemTypedCustomization: blueprint.FilesystemTypedCustomization{ - Mountpoint: "/", - FSType: "xfs", - }, - }, - }, - }, - }, - }, - 10 * datasizes.GiB, - }, - } { - t.Run(tc.name, func(t *testing.T) { - cnf := &bib.ManifestConfig{ - Architecture: arch.ARCH_X86_64, - RootFSType: "xfs", - RootfsMinsize: tc.rootfsMinSize, - } - cus := &blueprint.Customizations{ - Disk: &blueprint.DiskCustomization{ - Partitions: tc.partitions, - }, - } - pt, err := bib.GenPartitionTable(cnf, cus, rng) - assert.NoError(t, err) - - var rootSize uint64 - err = pt.ForEachMountable(func(mnt disk.Mountable, path []disk.Entity) error { - if mnt.GetMountpoint() == "/" { - for idx := len(path) - 1; idx >= 0; idx-- { - if parent, ok := path[idx].(disk.Sizeable); ok { - rootSize = parent.GetSize() - break - } - } - } - return nil - }) - assert.NoError(t, err) - // expected size is within a reasonable limit - assert.True(t, rootSize >= tc.expectedMinRootSize && rootSize < tc.expectedMinRootSize+5*datasizes.MiB) - }) - } -} - -func TestManifestFilecustomizationsSad(t *testing.T) { - config := getBaseConfig() - config.ImageTypes = []string{"qcow2"} - config.Config = &blueprint.Blueprint{ - Customizations: &blueprint.Customizations{ - Files: []blueprint.FileCustomization{ - { - Path: "/not/allowed", - Data: "some-data", - }, - }, - }, - } - - _, err := bib.Manifest(config) - assert.EqualError(t, err, `the following custom files are not allowed: ["/not/allowed"]`) -} - -func TestManifestDirCustomizationsSad(t *testing.T) { - config := getBaseConfig() - config.ImageTypes = []string{"qcow2"} - config.Config = &blueprint.Blueprint{ - Customizations: &blueprint.Customizations{ - Directories: []blueprint.DirectoryCustomization{ - { - Path: "/dir/not/allowed", - }, - }, - }, - } - - _, err := bib.Manifest(config) - assert.EqualError(t, err, `the following custom directories are not allowed: ["/dir/not/allowed"]`) -} diff --git a/bib/cmd/bootc-image-builder/main.go b/bib/cmd/bootc-image-builder/main.go index db1ee508..2e299300 100644 --- a/bib/cmd/bootc-image-builder/main.go +++ b/bib/cmd/bootc-image-builder/main.go @@ -1,10 +1,12 @@ package main import ( + "bytes" "encoding/json" "errors" "fmt" "io" + "io/fs" "log" "os" "os/exec" @@ -18,15 +20,19 @@ import ( "github.com/spf13/pflag" "golang.org/x/exp/slices" + repos "github.com/osbuild/images/data/repositories" "github.com/osbuild/images/pkg/arch" "github.com/osbuild/images/pkg/bib/blueprintload" "github.com/osbuild/images/pkg/cloud" "github.com/osbuild/images/pkg/cloud/awscloud" "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/distro/bootc" "github.com/osbuild/images/pkg/dnfjson" "github.com/osbuild/images/pkg/experimentalflags" "github.com/osbuild/images/pkg/manifest" + "github.com/osbuild/images/pkg/manifestgen" "github.com/osbuild/images/pkg/osbuild" + "github.com/osbuild/images/pkg/reporegistry" "github.com/osbuild/images/pkg/rpmmd" "github.com/osbuild/bootc-image-builder/bib/internal/imagetypes" @@ -35,15 +41,6 @@ import ( "github.com/osbuild/image-builder-cli/pkg/progress" "github.com/osbuild/image-builder-cli/pkg/setup" - "github.com/osbuild/image-builder-cli/pkg/util" -) - -const ( - // As a baseline heuristic we double the size of - // the input container to support in-place updates. - // This is planned to be more configurable in the - // future. - containerSizeToDiskSizeMultiplier = 2 ) // all possible locations for the bib's distro definitions @@ -96,23 +93,9 @@ func inContainerOrUnknown() bool { return err == nil } -// getContainerSize returns the size of an already pulled container image in bytes -func getContainerSize(imgref string) (uint64, error) { - output, err := exec.Command("podman", "image", "inspect", imgref, "--format", "{{.Size}}").Output() - if err != nil { - return 0, fmt.Errorf("failed inspect image: %w", util.OutputErr(err)) - } - size, err := strconv.ParseUint(strings.TrimSpace(string(output)), 10, 64) - if err != nil { - return 0, fmt.Errorf("cannot parse image size: %w", err) - } - - logrus.Debugf("container size: %v", size) - return size, nil -} - func makeManifest(c *ManifestConfig, solver *dnfjson.Solver, cacheRoot string) (manifest.OSBuildManifest, map[string][]rpmmd.RepoConfig, error) { - mani, err := Manifest(c) + rng := createRand() + mani, err := manifestForISO(c, rng) if err != nil { return nil, nil, fmt.Errorf("cannot get manifest: %w", err) } @@ -245,23 +228,67 @@ func manifestFromCobra(cmd *cobra.Command, args []string, pbar progress.Progress if err != nil { return nil, nil, fmt.Errorf("cannot detect build types %v: %w", imgTypes, err) } - config, err := blueprintload.LoadWithFallback(userConfigFile) if err != nil { return nil, nil, fmt.Errorf("cannot read config: %w", err) } - pbar.SetPulseMsgf("Manifest generation step") - pbar.Start() - if err := setup.ValidateHasContainerTags(imgref); err != nil { return nil, nil, err } - cntSize, err := getContainerSize(imgref) - if err != nil { - return nil, nil, fmt.Errorf("cannot get container size: %w", err) + pbar.SetPulseMsgf("Manifest generation step") + pbar.Start() + + // For now shortcut here and build ding "images" for anything + // that is not the iso + if !imageTypes.BuildsISO() { + distro, err := bootc.NewBootcDistro(imgref) + if err != nil { + return nil, nil, err + } + if err := distro.SetBuildContainer(buildImgref); err != nil { + return nil, nil, err + } + if err := distro.SetDefaultFs(rootFs); err != nil { + return nil, nil, err + } + // XXX: consider target-arch + archi, err := distro.GetArch(cntArch.String()) + if err != nil { + return nil, nil, err + } + // XXX: how to generate for all image types + imgType, err := archi.GetImageType(imgTypes[0]) + if err != nil { + return nil, nil, err + } + + var buf bytes.Buffer + repos, err := reporegistry.New(nil, []fs.FS{repos.FS}) + if err != nil { + return nil, nil, err + } + mg, err := manifestgen.New(repos, &manifestgen.Options{ + Output: &buf, + // XXX: hack to skip repo loading for the bootc image. + // We need to add a SkipRepositories or similar to + // manifestgen instead to make this clean + OverrideRepos: []rpmmd.RepoConfig{ + { + BaseURLs: []string{"https://example.com/not-used"}, + }, + }, + }) + if err != nil { + return nil, nil, err + } + if err := mg.Generate(config, distro, imgType, archi, nil); err != nil { + return nil, nil, err + } + return buf.Bytes(), nil, nil } + container, err := podman_container.New(imgref) if err != nil { return nil, nil, err @@ -334,7 +361,6 @@ func manifestFromCobra(cmd *cobra.Command, args []string, pbar progress.Progress ImageTypes: imageTypes, Imgref: imgref, BuildImgref: buildImgref, - RootfsMinsize: cntSize * containerSizeToDiskSizeMultiplier, DistroDefPaths: distroDefPaths, SourceInfo: sourceinfo, BuildSourceInfo: buildSourceinfo, diff --git a/bib/cmd/bootc-image-builder/main_test.go b/bib/cmd/bootc-image-builder/main_test.go index 21bbd940..9ee0b933 100644 --- a/bib/cmd/bootc-image-builder/main_test.go +++ b/bib/cmd/bootc-image-builder/main_test.go @@ -1,8 +1,6 @@ package main_test import ( - "encoding/json" - "errors" "fmt" "os" "strings" @@ -14,16 +12,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/osbuild/blueprint/pkg/blueprint" - "github.com/osbuild/images/pkg/arch" - "github.com/osbuild/images/pkg/bib/osinfo" - "github.com/osbuild/images/pkg/container" - "github.com/osbuild/images/pkg/dnfjson" - "github.com/osbuild/images/pkg/manifest" - "github.com/osbuild/images/pkg/rpmmd" - main "github.com/osbuild/bootc-image-builder/bib/cmd/bootc-image-builder" - "github.com/osbuild/bootc-image-builder/bib/internal/imagetypes" ) func TestCanChownInPathHappy(t *testing.T) { @@ -60,467 +49,6 @@ func TestCanChownInPathCannotChange(t *testing.T) { assert.Equal(t, canChown, false) } -type manifestTestCase struct { - config *main.ManifestConfig - imageTypes imagetypes.ImageTypes - depsolved map[string]dnfjson.DepsolveResult - containers map[string][]container.Spec - expStages map[string][]string - notExpectedStages map[string][]string - err interface{} -} - -func getBaseConfig() *main.ManifestConfig { - return &main.ManifestConfig{ - Architecture: arch.ARCH_X86_64, - Imgref: "testempty", - SourceInfo: &osinfo.Info{ - OSRelease: osinfo.OSRelease{ - ID: "fedora", - VersionID: "40", - Name: "Fedora Linux", - PlatformID: "platform:f40", - }, - UEFIVendor: "fedora", - }, - - // We need the real path here, because we are creating real manifests - DistroDefPaths: []string{"../../data/defs"}, - - // RootFSType is required to create a Manifest - RootFSType: "ext4", - } -} - -func getUserConfig() *main.ManifestConfig { - // add a user - pass := "super-secret-password-42" - key := "ssh-ed25519 AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" - return &main.ManifestConfig{ - Architecture: arch.ARCH_X86_64, - Imgref: "testuser", - Config: &blueprint.Blueprint{ - Customizations: &blueprint.Customizations{ - User: []blueprint.UserCustomization{ - { - Name: "tester", - Password: &pass, - Key: &key, - }, - }, - }, - }, - SourceInfo: &osinfo.Info{ - OSRelease: osinfo.OSRelease{ - ID: "fedora", - VersionID: "40", - Name: "Fedora Linux", - PlatformID: "platform:f40", - }, - UEFIVendor: "fedora", - }, - - // We need the real path here, because we are creating real manifests - DistroDefPaths: []string{"../../data/defs"}, - - // RootFSType is required to create a Manifest - RootFSType: "ext4", - } -} - -func TestManifestGenerationEmptyConfig(t *testing.T) { - baseConfig := getBaseConfig() - testCases := map[string]manifestTestCase{ - "ami-base": { - config: baseConfig, - imageTypes: []string{"ami"}, - }, - "raw-base": { - config: baseConfig, - imageTypes: []string{"raw"}, - }, - "qcow2-base": { - config: baseConfig, - imageTypes: []string{"qcow2"}, - }, - "iso-base": { - config: baseConfig, - imageTypes: []string{"iso"}, - }, - "empty-config": { - config: &main.ManifestConfig{}, - imageTypes: []string{"qcow2"}, - err: errors.New("pipeline: no base image defined"), - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - config := main.ManifestConfig(*tc.config) - config.ImageTypes = tc.imageTypes - _, err := main.Manifest(&config) - assert.Equal(t, err, tc.err) - }) - } -} - -func TestManifestGenerationUserConfig(t *testing.T) { - userConfig := getUserConfig() - testCases := map[string]manifestTestCase{ - "ami-user": { - config: userConfig, - imageTypes: []string{"ami"}, - }, - "raw-user": { - config: userConfig, - imageTypes: []string{"raw"}, - }, - "qcow2-user": { - config: userConfig, - imageTypes: []string{"qcow2"}, - }, - "iso-user": { - config: userConfig, - imageTypes: []string{"iso"}, - }, - } - - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - config := main.ManifestConfig(*tc.config) - config.ImageTypes = tc.imageTypes - _, err := main.Manifest(&config) - assert.NoError(t, err) - }) - } -} - -// Disk images require a container for the build/image pipelines -var containerSpec = container.Spec{ - Source: "test-container", - Digest: "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", - ImageID: "sha256:1111111111111111111111111111111111111111111111111111111111111111", -} - -// diskContainers can be passed to Serialize() to get a minimal disk image -var diskContainers = map[string][]container.Spec{ - "build": { - containerSpec, - }, - "image": { - containerSpec, - }, - "target": { - containerSpec, - }, -} - -// TODO: this tests at this layer is not ideal, it has too much knowledge -// over the implementation details of the "images" library and how an -// image.NewBootcDiskImage() works (i.e. what the pipeline names are and -// what key piplines to expect). These details should be tested in "images" -// and here we would just check (somehow) that image.NewBootcDiskImage() -// (or image.NewAnacondaContainerInstaller()) is called and the right -// customizations are passed. The existing layout makes this hard so this -// is fine for now but would be nice to revisit this. -func TestManifestSerialization(t *testing.T) { - // Tests that the manifest is generated without error and is serialized - // with expected key stages. - - // ISOs require a container for the bootiso-tree, build packages, and packages for the anaconda-tree (with a kernel). - var isoContainers = map[string][]container.Spec{ - "bootiso-tree": { - containerSpec, - }, - } - isoPackages := map[string]dnfjson.DepsolveResult{ - "build": { - Packages: []rpmmd.PackageSpec{ - { - Name: "package", - Version: "113", - Checksum: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - }, - }, - }, - "anaconda-tree": { - Packages: []rpmmd.PackageSpec{ - { - Name: "kernel", - Version: "10.11", - Checksum: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", - }, - { - Name: "package", - Version: "113", - Checksum: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - }, - }, - }, - } - - pkgsNoBuild := map[string]dnfjson.DepsolveResult{ - "anaconda-tree": { - Packages: []rpmmd.PackageSpec{ - - { - Name: "kernel", - Version: "10.11", - Checksum: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", - }, - { - Name: "package", - Version: "113", - Checksum: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", - }, - }, - }, - } - - baseConfig := getBaseConfig() - userConfig := getUserConfig() - testCases := map[string]manifestTestCase{ - "ami-base": { - config: baseConfig, - imageTypes: []string{"ami"}, - containers: diskContainers, - expStages: map[string][]string{ - "build": {"org.osbuild.container-deploy"}, - "image": { - "org.osbuild.bootc.install-to-filesystem", - }, - }, - notExpectedStages: map[string][]string{ - "build": {"org.osbuild.rpm"}, - "image": { - "org.osbuild.users", - }, - }, - }, - "raw-base": { - config: baseConfig, - imageTypes: []string{"raw"}, - containers: diskContainers, - expStages: map[string][]string{ - "build": {"org.osbuild.container-deploy"}, - "image": { - "org.osbuild.bootc.install-to-filesystem", - }, - }, - notExpectedStages: map[string][]string{ - "build": {"org.osbuild.rpm"}, - "image": { - "org.osbuild.users", - }, - }, - }, - "qcow2-base": { - config: baseConfig, - imageTypes: []string{"qcow2"}, - containers: diskContainers, - expStages: map[string][]string{ - "build": {"org.osbuild.container-deploy"}, - "image": { - "org.osbuild.bootc.install-to-filesystem", - }, - }, - notExpectedStages: map[string][]string{ - "build": {"org.osbuild.rpm"}, - "image": { - "org.osbuild.users", - }, - }, - }, - "ami-user": { - config: userConfig, - imageTypes: []string{"ami"}, - containers: diskContainers, - expStages: map[string][]string{ - "build": {"org.osbuild.container-deploy"}, - "image": { - "org.osbuild.users", - "org.osbuild.bootc.install-to-filesystem", - }, - }, - notExpectedStages: map[string][]string{ - "build": {"org.osbuild.rpm"}, - }, - }, - "raw-user": { - config: userConfig, - imageTypes: []string{"raw"}, - containers: diskContainers, - expStages: map[string][]string{ - "build": {"org.osbuild.container-deploy"}, - "image": { - "org.osbuild.users", // user creation stage when we add users - "org.osbuild.bootc.install-to-filesystem", - }, - }, - notExpectedStages: map[string][]string{ - "build": {"org.osbuild.rpm"}, - }, - }, - "qcow2-user": { - config: userConfig, - imageTypes: []string{"qcow2"}, - containers: diskContainers, - expStages: map[string][]string{ - "build": {"org.osbuild.container-deploy"}, - "image": { - "org.osbuild.users", // user creation stage when we add users - "org.osbuild.bootc.install-to-filesystem", - }, - }, - notExpectedStages: map[string][]string{ - "build": {"org.osbuild.rpm"}, - }, - }, - "iso-user": { - config: userConfig, - imageTypes: []string{"iso"}, - containers: isoContainers, - depsolved: isoPackages, - expStages: map[string][]string{ - "build": {"org.osbuild.rpm"}, - "bootiso-tree": {"org.osbuild.skopeo"}, // adds the container to the ISO tree - }, - }, - "iso-nobuildpkg": { - config: userConfig, - imageTypes: []string{"iso"}, - containers: isoContainers, - depsolved: pkgsNoBuild, - err: "serialization not started", - }, - "iso-nocontainer": { - config: userConfig, - imageTypes: []string{"iso"}, - depsolved: isoPackages, - err: "missing ostree, container, or ospipeline parameters in ISO tree pipeline", - }, - "ami-nocontainer": { - config: userConfig, - imageTypes: []string{"ami"}, - // errors come from BuildrootFromContainer() - // TODO: think about better error and testing here (not the ideal layer or err msg) - err: "serialization not started", - }, - "raw-nocontainer": { - config: userConfig, - imageTypes: []string{"raw"}, - // errors come from BuildrootFromContainer() - // TODO: think about better error and testing here (not the ideal layer or err msg) - err: "serialization not started", - }, - "qcow2-nocontainer": { - config: userConfig, - imageTypes: []string{"qcow2"}, - // errors come from BuildrootFromContainer() - // TODO: think about better error and testing here (not the ideal layer or err msg) - err: "serialization not started", - }, - } - - // Use an empty config: only the imgref is required - for name, tc := range testCases { - t.Run(name, func(t *testing.T) { - assert := assert.New(t) - config := main.ManifestConfig(*tc.config) - config.ImageTypes = tc.imageTypes - mf, err := main.Manifest(&config) - assert.NoError(err) // this isn't the error we're testing for - - if tc.err != nil { - assert.PanicsWithValue(tc.err, func() { - _, err := mf.Serialize(tc.depsolved, tc.containers, nil, nil) - assert.NoError(err) - }) - } else { - manifestJson, err := mf.Serialize(tc.depsolved, tc.containers, nil, nil) - assert.NoError(err) - assert.NoError(checkStages(manifestJson, tc.expStages, tc.notExpectedStages)) - } - }) - } - - { - // this one panics with a typed error and needs to be tested separately from the above (PanicsWithError()) - t.Run("iso-nopkgs", func(t *testing.T) { - assert := assert.New(t) - config := main.ManifestConfig(*userConfig) - config.ImageTypes, _ = imagetypes.New("iso") - manifest, err := main.Manifest(&config) - assert.NoError(err) // this isn't the error we're testing for - - expError := "package \"kernel\" not found in the PackageSpec list" - assert.PanicsWithError(expError, func() { - _, err := manifest.Serialize(nil, isoContainers, nil, nil) - assert.NoError(err) - }) - }) - } -} - -// simplified representation of a manifest -type testManifest struct { - Pipelines []pipeline `json:"pipelines"` -} -type pipeline struct { - Name string `json:"name"` - Stages []stage `json:"stages"` -} -type stage struct { - Type string `json:"type"` -} - -func checkStages(serialized manifest.OSBuildManifest, pipelineStages map[string][]string, missingStages map[string][]string) error { - mf := &testManifest{} - if err := json.Unmarshal(serialized, mf); err != nil { - return err - } - pipelineMap := map[string]pipeline{} - for _, pl := range mf.Pipelines { - pipelineMap[pl.Name] = pl - } - - for plname, stages := range pipelineStages { - pl, found := pipelineMap[plname] - if !found { - return fmt.Errorf("pipeline %q not found", plname) - } - - stageMap := map[string]bool{} - for _, stage := range pl.Stages { - stageMap[stage.Type] = true - } - for _, stage := range stages { - if _, found := stageMap[stage]; !found { - return fmt.Errorf("pipeline %q - stage %q - not found", plname, stage) - } - } - } - - for plname, stages := range missingStages { - pl, found := pipelineMap[plname] - if !found { - return fmt.Errorf("pipeline %q not found", plname) - } - - stageMap := map[string]bool{} - for _, stage := range pl.Stages { - stageMap[stage.Type] = true - } - for _, stage := range stages { - if _, found := stageMap[stage]; found { - return fmt.Errorf("pipeline %q - stage %q - found (but should not be)", plname, stage) - } - } - } - - return nil -} - func mockOsArgs(new []string) (restore func()) { saved := os.Args os.Args = append([]string{"argv0"}, new...) diff --git a/bib/cmd/bootc-image-builder/partition_tables.go b/bib/cmd/bootc-image-builder/partition_tables.go deleted file mode 100644 index 9ce5468c..00000000 --- a/bib/cmd/bootc-image-builder/partition_tables.go +++ /dev/null @@ -1,139 +0,0 @@ -package main - -import ( - "github.com/osbuild/images/pkg/arch" - "github.com/osbuild/images/pkg/disk" - "github.com/osbuild/images/pkg/distro" -) - -const ( - MebiByte = 1024 * 1024 // MiB - GibiByte = 1024 * 1024 * 1024 // GiB - // BootOptions defines the mountpoint options for /boot - // See https://github.com/containers/bootc/pull/341 for the rationale for - // using `ro` by default. Briefly it protects against corruption - // by non-ostree aware tools. - BootOptions = "ro" - // And we default to `ro` for the rootfs too, because we assume the input - // container image is using composefs. For more info, see - // https://github.com/containers/bootc/pull/417 and - // https://github.com/ostreedev/ostree/issues/3193 - RootOptions = "ro" -) - -// diskUuidOfUnknownOrigin is used by default for disk images, -// picked by someone in the past for unknown reasons. More in -// e.g. https://github.com/osbuild/bootc-image-builder/pull/568 and -// https://github.com/osbuild/images/pull/823 -const diskUuidOfUnknownOrigin = "D209C89E-EA5E-4FBD-B161-B461CCE297E0" - -// efiPartition defines the default ESP. See also -// https://en.wikipedia.org/wiki/EFI_system_partition -var efiPartition = disk.Partition{ - Size: 501 * MebiByte, - Type: disk.EFISystemPartitionGUID, - UUID: disk.EFISystemPartitionUUID, - Payload: &disk.Filesystem{ - Type: "vfat", - UUID: disk.EFIFilesystemUUID, - Mountpoint: "/boot/efi", - Label: "EFI-SYSTEM", - FSTabOptions: "umask=0077,shortname=winnt", - FSTabFreq: 0, - FSTabPassNo: 2, - }, -} - -// bootPartition defines a distinct filesystem for /boot -// which is needed for e.g. LVM or LUKS when using GRUB -// (which this project doesn't support today...) -// See also https://github.com/containers/bootc/pull/529/commits/e5548d8765079171e6ed39a3ab0479bc8681a1c9 -var bootPartition = disk.Partition{ - Size: 1 * GibiByte, - Type: disk.FilesystemDataGUID, - UUID: disk.DataPartitionUUID, - Payload: &disk.Filesystem{ - Type: "ext4", - Mountpoint: "/boot", - Label: "boot", - FSTabOptions: BootOptions, - FSTabFreq: 1, - FSTabPassNo: 2, - }, -} - -// rootPartition holds the root filesystem; however note -// that while the type here defines "ext4" because the data -// type requires something there, in practice we pull -// the rootfs type from the container image by default. -// See https://containers.github.io/bootc/bootc-install.html -var rootPartition = disk.Partition{ - Size: 2 * GibiByte, - Type: disk.FilesystemDataGUID, - UUID: disk.RootPartitionUUID, - Payload: &disk.Filesystem{ - Type: "ext4", - Label: "root", - Mountpoint: "/", - FSTabOptions: RootOptions, - FSTabFreq: 1, - FSTabPassNo: 1, - }, -} - -var partitionTables = distro.BasePartitionTableMap{ - arch.ARCH_X86_64.String(): disk.PartitionTable{ - UUID: diskUuidOfUnknownOrigin, - Type: disk.PT_GPT, - Partitions: []disk.Partition{ - { - Size: 1 * MebiByte, - Bootable: true, - Type: disk.BIOSBootPartitionGUID, - UUID: disk.BIOSBootPartitionUUID, - }, - efiPartition, - bootPartition, - rootPartition, - }, - }, - arch.ARCH_AARCH64.String(): disk.PartitionTable{ - UUID: diskUuidOfUnknownOrigin, - Type: disk.PT_GPT, - Partitions: []disk.Partition{ - efiPartition, - bootPartition, - rootPartition, - }, - }, - arch.ARCH_S390X.String(): disk.PartitionTable{ - UUID: diskUuidOfUnknownOrigin, - Type: disk.PT_GPT, - Partitions: []disk.Partition{ - bootPartition, - rootPartition, - }, - }, - arch.ARCH_PPC64LE.String(): disk.PartitionTable{ - UUID: diskUuidOfUnknownOrigin, - Type: disk.PT_GPT, - Partitions: []disk.Partition{ - { - Size: 4 * MebiByte, - Type: disk.PRePartitionGUID, - Bootable: true, - }, - bootPartition, - rootPartition, - }, - }, - arch.ARCH_RISCV64.String(): disk.PartitionTable{ - UUID: diskUuidOfUnknownOrigin, - Type: disk.PT_GPT, - Partitions: []disk.Partition{ - efiPartition, - bootPartition, - rootPartition, - }, - }, -} diff --git a/bib/go.mod b/bib/go.mod index 07768124..0772470c 100644 --- a/bib/go.mod +++ b/bib/go.mod @@ -7,7 +7,7 @@ require ( github.com/hashicorp/go-version v1.7.0 github.com/osbuild/blueprint v1.13.0 github.com/osbuild/image-builder-cli v0.0.0-20250331194259-63bb56e12db3 - github.com/osbuild/images v0.183.0 + github.com/osbuild/images v0.186.0 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.9.1 github.com/spf13/pflag v1.0.7 @@ -23,26 +23,26 @@ require ( github.com/Microsoft/hcsshim v0.13.0 // indirect github.com/VividCortex/ewma v1.2.0 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect - github.com/aws/aws-sdk-go-v2 v1.38.1 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 // indirect - github.com/aws/aws-sdk-go-v2/config v1.31.2 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.18.6 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 // indirect - github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.0 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 // indirect + github.com/aws/aws-sdk-go-v2 v1.38.3 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 // indirect + github.com/aws/aws-sdk-go-v2/config v1.31.6 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.18.10 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 // indirect + github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 // indirect github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4 // indirect - github.com/aws/aws-sdk-go-v2/service/ec2 v1.245.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4 // indirect - github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 // indirect - github.com/aws/smithy-go v1.22.5 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 // indirect + github.com/aws/aws-sdk-go-v2/service/ec2 v1.249.0 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 // indirect + github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 // indirect + github.com/aws/smithy-go v1.23.0 // indirect github.com/containerd/cgroups/v3 v3.0.5 // indirect github.com/containerd/errdefs v1.0.0 // indirect github.com/containerd/errdefs/pkg v0.3.0 // indirect diff --git a/bib/go.sum b/bib/go.sum index 4b8d1722..c17e1d9d 100644 --- a/bib/go.sum +++ b/bib/go.sum @@ -14,46 +14,46 @@ github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1o github.com/VividCortex/ewma v1.2.0/go.mod h1:nz4BbCtbLyFDeC9SUHbtcT5644juEuWfUAUnGx7j5l4= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= -github.com/aws/aws-sdk-go-v2 v1.38.1 h1:j7sc33amE74Rz0M/PoCpsZQ6OunLqys/m5antM0J+Z8= -github.com/aws/aws-sdk-go-v2 v1.38.1/go.mod h1:9Q0OoGQoboYIAJyslFyF1f5K1Ryddop8gqMhWx/n4Wg= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0 h1:6GMWV6CNpA/6fbFHnoAjrv4+LGfyTqZz2LtCHnspgDg= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.0/go.mod h1:/mXlTIVG9jbxkqDnr5UQNQxW1HRYxeGklkM9vAFeabg= -github.com/aws/aws-sdk-go-v2/config v1.31.2 h1:NOaSZpVGEH2Np/c1toSeW0jooNl+9ALmsUTZ8YvkJR0= -github.com/aws/aws-sdk-go-v2/config v1.31.2/go.mod h1:17ft42Yb2lF6OigqSYiDAiUcX4RIkEMY6XxEMJsrAes= -github.com/aws/aws-sdk-go-v2/credentials v1.18.6 h1:AmmvNEYrru7sYNJnp3pf57lGbiarX4T9qU/6AZ9SucU= -github.com/aws/aws-sdk-go-v2/credentials v1.18.6/go.mod h1:/jdQkh1iVPa01xndfECInp1v1Wnp70v3K4MvtlLGVEc= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4 h1:lpdMwTzmuDLkgW7086jE94HweHCqG+uOJwHf3LZs7T0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.4/go.mod h1:9xzb8/SV62W6gHQGC/8rrvgNXU6ZoYM3sAIJCIrXJxY= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.0 h1:2FFgK3oFA8PTNBjprLFfcmkgg7U9YuSimBvR64RUmiA= -github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.0/go.mod h1:xdxj6nC1aU/jAO80RIlIj3fU40MOSqutEA9N2XFct04= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4 h1:IdCLsiiIj5YJ3AFevsewURCPV+YWUlOW8JiPhoAy8vg= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.4/go.mod h1:l4bdfCD7XyyZA9BolKBo1eLqgaJxl0/x91PL4Yqe0ao= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4 h1:j7vjtr1YIssWQOMeOWRbh3z8g2oY/xPjnZH2gLY4sGw= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.4/go.mod h1:yDmJgqOiH4EA8Hndnv4KwAo8jCGTSnM5ASG1nBI+toA= +github.com/aws/aws-sdk-go-v2 v1.38.3 h1:B6cV4oxnMs45fql4yRH+/Po/YU+597zgWqvDpYMturk= +github.com/aws/aws-sdk-go-v2 v1.38.3/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00= +github.com/aws/aws-sdk-go-v2/config v1.31.6 h1:a1t8fXY4GT4xjyJExz4knbuoxSCacB5hT/WgtfPyLjo= +github.com/aws/aws-sdk-go-v2/config v1.31.6/go.mod h1:5ByscNi7R+ztvOGzeUaIu49vkMk2soq5NaH5PYe33MQ= +github.com/aws/aws-sdk-go-v2/credentials v1.18.10 h1:xdJnXCouCx8Y0NncgoptztUocIYLKeQxrCgN6x9sdhg= +github.com/aws/aws-sdk-go-v2/credentials v1.18.10/go.mod h1:7tQk08ntj914F/5i9jC4+2HQTAuJirq7m1vZVIhEkWs= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6 h1:wbjnrrMnKew78/juW7I2BtKQwa1qlf6EjQgS69uYY14= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.6/go.mod h1:AtiqqNrDioJXuUgz3+3T0mBWN7Hro2n9wll2zRUc0ww= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.4 h1:BTl+TXrpnrpPWb/J3527GsJ/lMkn7z3GO12j6OlsbRg= +github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.4/go.mod h1:cG2tenc/fscpChiZE29a2crG9uo2t6nQGflFllFL8M8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6 h1:uF68eJA6+S9iVr9WgX1NaRGyQ/6MdIyc4JNUo6TN1FA= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.6/go.mod h1:qlPeVZCGPiobx8wb1ft0GHT5l+dc6ldnwInDFaMvC7Y= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6 h1:pa1DEC6JoI0zduhZePp3zmhWvk/xxm4NB8Hy/Tlsgos= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.6/go.mod h1:gxEjPebnhWGJoaDdtDkA0JX46VRg1wcTHYe63OfX5pE= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 h1:bIqFDwgGXXN1Kpp99pDOdKMTTb5d2KyU5X/BZxjOkRo= github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3/go.mod h1:H5O/EsxDWyU+LP/V8i5sm8cxoZgc2fdNR9bxlOFrQTo= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4 h1:BE/MNQ86yzTINrfxPPFS86QCBNQeLKY2A0KhDh47+wI= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.4/go.mod h1:SPBBhkJxjcrzJBc+qY85e83MQ2q3qdra8fghhkkyrJg= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.245.2 h1:P94OfRObDwjklbvdJTGuRZXeGYF7Bv5NNUo+I628kKQ= -github.com/aws/aws-sdk-go-v2/service/ec2 v1.245.2/go.mod h1:D8Wb993SJuFQ10Lp95Vod8VTpYjJz4v0LeW4rEI471c= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0 h1:6+lZi2JeGKtCraAj1rpoZfKqnQ9SptseRZioejfUOLM= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.0/go.mod h1:eb3gfbVIxIoGgJsi9pGne19dhCBpK6opTYpQqAmdy44= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4 h1:Beh9oVgtQnBgR4sKKzkUBRQpf1GnL4wt0l4s8h2VCJ0= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.4/go.mod h1:b17At0o8inygF+c6FOD3rNyYZufPw62o9XJbSfQPgbo= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4 h1:ueB2Te0NacDMnaC+68za9jLwkjzxGWm0KB5HTUHjLTI= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.4/go.mod h1:nLEfLnVMmLvyIG58/6gsSA03F1voKGaCfHV7+lR8S7s= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4 h1:HVSeukL40rHclNcUqVcBwE1YoZhOkoLeBfhUqR3tjIU= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.4/go.mod h1:DnbBOv4FlIXHj2/xmrUQYtawRFC9L9ZmQPz+DBc6X5I= -github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1 h1:2n6Pd67eJwAb/5KCX62/8RTU0aFAAW7V5XIGSghiHrw= -github.com/aws/aws-sdk-go-v2/service/s3 v1.87.1/go.mod h1:w5PC+6GHLkvMJKasYGVloB3TduOtROEMqm15HSuIbw4= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.2 h1:ve9dYBB8CfJGTFqcQ3ZLAAb/KXWgYlgu/2R2TZL2Ko0= -github.com/aws/aws-sdk-go-v2/service/sso v1.28.2/go.mod h1:n9bTZFZcBa9hGGqVz3i/a6+NG0zmZgtkB9qVVFDqPA8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2 h1:pd9G9HQaM6UZAZh19pYOkpKSQkyQQ9ftnl/LttQOcGI= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.33.2/go.mod h1:eknndR9rU8UpE/OmFpqU78V1EcXPKFTTm5l/buZYgvM= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.0 h1:iV1Ko4Em/lkJIsoKyGfc0nQySi+v0Udxr6Igq+y9JZc= -github.com/aws/aws-sdk-go-v2/service/sts v1.38.0/go.mod h1:bEPcjW7IbolPfK67G1nilqWyoxYMSPrDiIQ3RdIdKgo= -github.com/aws/smithy-go v1.22.5 h1:P9ATCXPMb2mPjYBgueqJNCA5S9UfktsW0tTxi+a7eqw= -github.com/aws/smithy-go v1.22.5/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6 h1:R0tNFJqfjHL3900cqhXuwQ+1K4G0xc9Yf8EDbFXCKEw= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.6/go.mod h1:y/7sDdu+aJvPtGXr4xYosdpq9a6T9Z0jkXfugmti0rI= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.249.0 h1:1wn3h1PKTKQ9tg7bzfm4x1iqKYsLY2qfmV4SsDmakkI= +github.com/aws/aws-sdk-go-v2/service/ec2 v1.249.0/go.mod h1:SmMqzfS4HVsOD58lwLZ79oxF58f8zVe5YdK3o+/o1Ck= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6 h1:hncKj/4gR+TPauZgTAsxOxNcvBayhUlYZ6LO/BYiQ30= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.6/go.mod h1:OiIh45tp6HdJDDJGnja0mw8ihQGz3VGrUflLqSL0SmM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6 h1:LHS1YAIJXJ4K9zS+1d/xa9JAA9sL2QyXIQCQFQW/X08= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.6/go.mod h1:c9PCiTEuh0wQID5/KqA32J+HAgZxN9tOGXKCiYJjTZI= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6 h1:nEXUSAwyUfLTgnc9cxlDWy637qsq4UWwp3sNAfl0Z3Y= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.6/go.mod h1:HGzIULx4Ge3Do2V0FaiYKcyKzOqwrhUZgCI77NisswQ= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3 h1:ETkfWcXP2KNPLecaDa++5bsQhCRa5M5sLUJa5DWYIIg= +github.com/aws/aws-sdk-go-v2/service/s3 v1.87.3/go.mod h1:+/3ZTqoYb3Ur7DObD00tarKMLMuKg8iqz5CHEanqTnw= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.1 h1:8OLZnVJPvjnrxEwHFg9hVUof/P4sibH+Ea4KKuqAGSg= +github.com/aws/aws-sdk-go-v2/service/sso v1.29.1/go.mod h1:27M3BpVi0C02UiQh1w9nsBEit6pLhlaH3NHna6WUbDE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2 h1:gKWSTnqudpo8dAxqBqZnDoDWCiEh/40FziUjr/mo6uA= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.34.2/go.mod h1:x7+rkNmRoEN1U13A6JE2fXne9EWyJy54o3n6d4mGaXQ= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.2 h1:YZPjhyaGzhDQEvsffDEcpycq49nl7fiGcfJTIo8BszI= +github.com/aws/aws-sdk-go-v2/service/sts v1.38.2/go.mod h1:2dIN8qhQfv37BdUYGgEC8Q3tteM3zFxTI1MLO2O3J3c= +github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE= +github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= @@ -245,8 +245,8 @@ github.com/osbuild/blueprint v1.13.0 h1:blo22+S2ZX5bBmjGcRveoTUrV4Ms7kLfKyb32Wyu github.com/osbuild/blueprint v1.13.0/go.mod h1:HPlJzkEl7q5g8hzaGksUk7ifFAy9QFw9LmzhuFOAVm4= github.com/osbuild/image-builder-cli v0.0.0-20250331194259-63bb56e12db3 h1:M3yYunKH4quwJLQrnFo7dEwCTKorafNC+AUqAo7m5Yo= github.com/osbuild/image-builder-cli v0.0.0-20250331194259-63bb56e12db3/go.mod h1:0sEmiQiMo1ChSuOoeONN0RmsoZbQEvj2mlO2448gC5w= -github.com/osbuild/images v0.183.0 h1:OGdtSKvZ8NL7ZnTp0Ud/BF8VhgfBtr50SedTn7Yp+Io= -github.com/osbuild/images v0.183.0/go.mod h1:qbGjthiOmiZr1xCJEYMHv5oPNXXcxkJyvj7dky4/ibw= +github.com/osbuild/images v0.186.0 h1:7dG7hwprbvHiOfvE3LYLzN3GUETsImNaPoVjRssi0O0= +github.com/osbuild/images v0.186.0/go.mod h1:mRp0NKABLeJxFFuUKzWKw+qL5JktftMtn0hALDyxqHM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/test/test_build_disk.py b/test/test_build_disk.py index eaf2138c..497c592f 100644 --- a/test/test_build_disk.py +++ b/test/test_build_disk.py @@ -379,6 +379,11 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload, gpg_ } testutil.maybe_create_filesystem_customizations(cfg, tc) testutil.maybe_create_disk_customizations(cfg, tc) + # if we build an iso we cannot have the "home" customization for + # user root or images will panic(), c.f. + # https://github.com/osbuild/images/pull/1806 + if not image_types[0] in DISK_IMAGE_TYPES: + del cfg["customizations"]["user"][0]["home"] config_json_path = output_path / "config.json" config_json_path.write_text(json.dumps(cfg), encoding="utf-8") @@ -416,6 +421,7 @@ def build_images(shared_tmpdir, build_container, request, force_aws_upload, gpg_ if image_types[0] in DISK_IMAGE_TYPES: types_arg = [f"--type={it}" for it in DISK_IMAGE_TYPES] else: + # building an iso types_arg = [f"--type={image_types[0]}"] # run container to deploy an image into a bootable disk and upload to a cloud service if applicable diff --git a/test/test_manifest.py b/test/test_manifest.py index 347c64fe..5cd578ae 100644 --- a/test/test_manifest.py +++ b/test/test_manifest.py @@ -153,7 +153,8 @@ def test_manifest_cross_arch_check(tmp_path, build_container): "manifest", "--target-arch=aarch64", f"localhost/{container_tag}" ], check=True, capture_output=True, encoding="utf8") - assert 'image found is for unexpected architecture "x86_64"' in exc.value.stderr + assert 'cannot generate manifest: requested container architecture '\ + 'does not match resolved container: "x86_64" !=' in exc.value.stderr def find_rootfs_type_from(manifest_str): @@ -952,45 +953,61 @@ def test_manifest_image_customize_disk(tmp_path, build_container): assert sfdisk_options["partitions"][2]["size"] == 3 * 1024 * 1024 * 1024 / 512 -def test_manifest_image_aboot(tmp_path, build_container): +def test_manifest_image_disk_yaml(tmp_path, build_container): # no need to parameterize this test, overrides behaves same for all containers container_ref = "quay.io/centos-bootc/centos-bootc:stream9" testutil.pull_container(container_ref) - cfg = { - "blueprint": { - "customizations": { - "disk": { - "partitions": [ - { - "part_label": "ukiboot_a", - "part_uuid": "DF331E4D-BE00-463F-B4A7-8B43E18FB53A", - "fs_type": "none", - "minsize": "1 GiB", - }, - { - "part_label": "ukiboot_b", - "part_uuid": "DF331E4D-BE00-463F-B4A7-8B43E18FB53A", - "fs_type": "none", - "minsize": "1 GiB", - }, - { - "part_label": "ukibootctl", - "part_uuid": "FEFD9070-346F-4C9A-85E6-17F07F922773", - "fs_type": "none", - "minsize": "1 GiB", - }, - ], - }, - }, - }, - } + disk_yaml = textwrap.dedent("""--- + #enabled once https://github.com/osbuild/images/pull/1834 is in + #mount_configuration: none + partition_table: + size: '8589934592' + partitions: + - bootable: true + size: 1 MiB + type: 21686148-6449-6E6F-744E-656564454649 + uuid: fac7f1fb-3e8d-4137-a512-961de09a5549 + - bootable: false + label: efi + payload: + label: ESP + mountpoint: /boot/efi + type: vfat + payload_type: filesystem + size: '104857600' + type: c12a7328-f81f-11d2-ba4b-00a0c93ec93b + uuid: 68b2905b-df3e-4fb3-80fa-49d1e773aa33 + - label: ukiboot_a + size: '134217728' + type: df331e4d-be00-463f-b4a7-8b43e18fb53a + uuid: CD3B4BE3-0139-4A63-8060-658554C7273B + payload_type: raw + payload: + source_path: /usr/lib/modules/5.0-x86_64/aboot.img + - label: ukiboot_b + size: '134217728' + type: df331e4d-be00-463f-b4a7-8b43e18fb53a + uuid: E4D4DA50-7050-41AE-A5F9-DEF12B94DFB5 + - label: ukibootctl + size: '1048576' + type: fefd9070-346f-4c9a-85e6-17f07f922773 + uuid: 5A6F3ADE-EEB0-11EF-A838-E89C256C3906 + - label: root + payload: + label: root + mountpoint: / + type: ext4 + payload_type: filesystem + type: b921b045-1df0-41c3-af44-4c6f280d3fae + uuid: 6264d520-3fb9-423f-8ab8-7a0a8e3d3562 + """) - config_json_path = tmp_path / "config.json" - config_json_path.write_text(json.dumps(cfg), encoding="utf-8") + disk_yaml_path = tmp_path / "disk.yaml" + disk_yaml_path.write_text(disk_yaml, encoding="utf-8") - testdata_path = tmp_path / "testdata" - testdata_path.write_text("some test data", encoding="utf-8") + testdata_path = tmp_path / "fake-aboot.img" + testdata_path.write_text("fake aboot.img content", encoding="utf-8") # Create derived container with the custom partitioning with an aboot # partition and a kernel module dir with an aboot.img file @@ -998,11 +1015,10 @@ def test_manifest_image_aboot(tmp_path, build_container): cntf_path.write_text(textwrap.dedent(f"""\n FROM {container_ref} RUN mkdir -p -m 0755 /usr/lib/bootc-image-builder - COPY config.json /usr/lib/bootc-image-builder/ - RUN rm -rf /usr/lib/modules/* + COPY disk.yaml /usr/lib/bootc-image-builder/ + # add a preditable aboot.img for the write-device tes RUN mkdir -p -m 0755 /usr/lib/modules/5.0-x86_64/ - COPY testdata /usr/lib/modules/5.0-x86_64/vmlinuz - COPY testdata /usr/lib/modules/5.0-x86_64/aboot.img + COPY fake-aboot.img /usr/lib/modules/5.0-x86_64/aboot.img """), encoding="utf8") print(f"building filesystem customize container from {container_ref}")