688 lines
22 KiB
Go
688 lines
22 KiB
Go
package main
|
|
|
|
import (
|
|
cryptorand "crypto/rand"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"math/big"
|
|
"math/rand"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/osbuild/images/pkg/arch"
|
|
"github.com/osbuild/images/pkg/bib/osinfo"
|
|
"github.com/osbuild/images/pkg/blueprint"
|
|
"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/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/runner"
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"github.com/particle-os/debian-bootc-image-builder/bib/internal/distrodef"
|
|
"github.com/particle-os/debian-bootc-image-builder/bib/internal/imagetypes"
|
|
"github.com/particle-os/debian-bootc-image-builder/bib/internal/debian-patch"
|
|
)
|
|
|
|
// 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
|
|
BuildImgref string
|
|
|
|
ImageTypes imagetypes.ImageTypes
|
|
|
|
// Build config
|
|
Config *blueprint.Blueprint
|
|
|
|
// 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
|
|
|
|
// Extracted information about the source container image
|
|
SourceInfo *osinfo.Info
|
|
BuildSourceInfo *osinfo.Info
|
|
|
|
// RootFSType specifies the filesystem type for the root partition
|
|
RootFSType string
|
|
|
|
// use librepo ad the rpm downlaod backend
|
|
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 disk.PartitioningMode) error {
|
|
var policy *pathpolicy.PathPolicies
|
|
switch ptmode {
|
|
case disk.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}...)
|
|
}
|
|
}
|
|
}
|
|
|
|
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 := disk.RawPartitioningMode
|
|
if c.RootFSType == "btrfs" {
|
|
partitioningMode = disk.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")
|
|
}
|
|
|
|
// Add Debian-specific pre-validation
|
|
if err := debianpatch.PreValidateImage(c.Imgref); err != nil {
|
|
return nil, fmt.Errorf("debian pre-validation failed: %w", err)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
img := image.NewBootcDiskImage(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",
|
|
}
|
|
|
|
switch c.Architecture {
|
|
case arch.ARCH_X86_64:
|
|
img.Platform = &platform.X86{
|
|
BasePlatform: platform.BasePlatform{},
|
|
BIOS: true,
|
|
}
|
|
case arch.ARCH_AARCH64:
|
|
img.Platform = &platform.Aarch64{
|
|
UEFIVendor: "debian",
|
|
BasePlatform: platform.BasePlatform{
|
|
QCOW2Compat: "1.1",
|
|
},
|
|
}
|
|
case arch.ARCH_S390X:
|
|
img.Platform = &platform.S390X{
|
|
BasePlatform: platform.BasePlatform{
|
|
QCOW2Compat: "1.1",
|
|
},
|
|
Zipl: true,
|
|
}
|
|
case arch.ARCH_PPC64LE:
|
|
img.Platform = &platform.PPC64LE{
|
|
BasePlatform: platform.BasePlatform{
|
|
QCOW2Compat: "1.1",
|
|
},
|
|
BIOS: true,
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
// For the bootc-disk image, the filename is the basename and the extension
|
|
// is added automatically for each disk format
|
|
img.Filename = "disk"
|
|
|
|
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 "debian":
|
|
return fmt.Sprintf("Debian-%s-%s", os.VersionID, arch)
|
|
default:
|
|
return fmt.Sprintf("Container-Installer-%s", arch)
|
|
}
|
|
}
|
|
|
|
func needsRHELLoraxTemplates(si osinfo.OSRelease) bool {
|
|
// This function is Red Hat specific and not needed for Debian
|
|
// Always return false since we don't use RHEL Lorax templates
|
|
return false
|
|
}
|
|
|
|
func manifestForISO(c *ManifestConfig, rng *rand.Rand) (*manifest.Manifest, error) {
|
|
if c.Imgref == "" {
|
|
return nil, fmt.Errorf("pipeline: no base image defined")
|
|
}
|
|
|
|
imageDef, err := distrodef.LoadImageDef(c.DistroDefPaths, c.SourceInfo.OSRelease.ID, c.SourceInfo.OSRelease.VersionID, "anaconda-iso")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
containerSource := container.SourceSpec{
|
|
Source: c.Imgref,
|
|
Name: c.Imgref,
|
|
Local: true,
|
|
}
|
|
|
|
// The ref is not needed and will be removed from the ctor later
|
|
// in time
|
|
img := image.NewAnacondaContainerInstaller(containerSource, "")
|
|
img.ContainerRemoveSignatures = true
|
|
img.RootfsCompression = "zstd"
|
|
|
|
img.Product = c.SourceInfo.OSRelease.Name
|
|
img.OSVersion = c.SourceInfo.OSRelease.VersionID
|
|
|
|
img.ExtraBasePackages = debianpatch.DebianPackageSet{
|
|
Include: imageDef.Packages,
|
|
}
|
|
|
|
img.ISOLabel = labelForISO(&c.SourceInfo.OSRelease, &c.Architecture)
|
|
|
|
var customizations *blueprint.Customizations
|
|
if c.Config != nil {
|
|
customizations = c.Config.Customizations
|
|
}
|
|
img.FIPS = customizations.GetFIPS()
|
|
img.Kickstart, err = kickstart.New(customizations)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
img.Kickstart.Path = osbuild.KickstartPathOSBuild
|
|
if kopts := customizations.GetKernel(); kopts != nil && kopts.Append != "" {
|
|
img.Kickstart.KernelOptionsAppend = append(img.Kickstart.KernelOptionsAppend, kopts.Append)
|
|
}
|
|
img.Kickstart.NetworkOnBoot = true
|
|
|
|
instCust, err := customizations.GetInstaller()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if instCust != nil && instCust.Modules != nil {
|
|
img.AdditionalAnacondaModules = append(img.AdditionalAnacondaModules, instCust.Modules.Enable...)
|
|
img.DisabledAnacondaModules = append(img.DisabledAnacondaModules, instCust.Modules.Disable...)
|
|
}
|
|
img.AdditionalAnacondaModules = append(img.AdditionalAnacondaModules,
|
|
anaconda.ModuleUsers,
|
|
anaconda.ModuleServices,
|
|
anaconda.ModuleSecurity,
|
|
)
|
|
|
|
img.Kickstart.OSTree = &kickstart.OSTree{
|
|
OSName: "default",
|
|
}
|
|
img.UseRHELLoraxTemplates = needsRHELLoraxTemplates(c.SourceInfo.OSRelease)
|
|
|
|
switch c.Architecture {
|
|
case arch.ARCH_X86_64:
|
|
img.Platform = &platform.X86{
|
|
BasePlatform: platform.BasePlatform{
|
|
ImageFormat: platform.FORMAT_ISO,
|
|
},
|
|
BIOS: true,
|
|
UEFIVendor: c.SourceInfo.UEFIVendor,
|
|
}
|
|
img.ISOBoot = manifest.Grub2ISOBoot
|
|
case arch.ARCH_AARCH64:
|
|
// aarch64 always uses UEFI, so let's enforce the vendor
|
|
if c.SourceInfo.UEFIVendor == "" {
|
|
return nil, fmt.Errorf("UEFI vendor must be set for aarch64 ISO")
|
|
}
|
|
img.Platform = &platform.Aarch64{
|
|
BasePlatform: platform.BasePlatform{
|
|
ImageFormat: platform.FORMAT_ISO,
|
|
},
|
|
UEFIVendor: c.SourceInfo.UEFIVendor,
|
|
}
|
|
case arch.ARCH_S390X:
|
|
img.Platform = &platform.S390X{
|
|
Zipl: true,
|
|
BasePlatform: platform.BasePlatform{
|
|
ImageFormat: platform.FORMAT_ISO,
|
|
},
|
|
}
|
|
case arch.ARCH_PPC64LE:
|
|
img.Platform = &platform.PPC64LE{
|
|
BIOS: true,
|
|
BasePlatform: platform.BasePlatform{
|
|
ImageFormat: platform.FORMAT_ISO,
|
|
},
|
|
}
|
|
default:
|
|
return nil, fmt.Errorf("unsupported architecture %v", c.Architecture)
|
|
}
|
|
// see https://github.com/osbuild/bootc-image-builder/issues/733
|
|
img.RootfsType = manifest.SquashfsRootfs
|
|
img.Filename = "install.iso"
|
|
|
|
installRootfsType, err := disk.NewFSType(c.RootFSType)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
img.InstallRootfsType = installRootfsType
|
|
|
|
mf := manifest.New()
|
|
|
|
foundDistro, foundRunner, err := getDistroAndRunner(c.SourceInfo.OSRelease)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to infer distro and runner: %w", err)
|
|
}
|
|
mf.Distro = foundDistro
|
|
|
|
_, err = img.InstantiateManifest(&mf, nil, foundRunner, rng)
|
|
return &mf, err
|
|
}
|
|
|
|
func getDistroAndRunner(osRelease osinfo.OSRelease) (manifest.Distro, runner.Runner, error) {
|
|
switch osRelease.ID {
|
|
case "fedora":
|
|
version, err := strconv.ParseUint(osRelease.VersionID, 10, 64)
|
|
if err != nil {
|
|
return manifest.DISTRO_NULL, nil, fmt.Errorf("cannot parse Fedora version (%s): %w", osRelease.VersionID, err)
|
|
}
|
|
|
|
return manifest.DISTRO_FEDORA, &runner.Fedora{
|
|
Version: version,
|
|
}, nil
|
|
case "centos":
|
|
version, err := strconv.ParseUint(osRelease.VersionID, 10, 64)
|
|
if err != nil {
|
|
return manifest.DISTRO_NULL, nil, fmt.Errorf("cannot parse CentOS version (%s): %w", osRelease.VersionID, err)
|
|
}
|
|
r := &runner.CentOS{
|
|
Version: version,
|
|
}
|
|
switch version {
|
|
case 9:
|
|
return manifest.DISTRO_EL9, r, nil
|
|
case 10:
|
|
return manifest.DISTRO_EL10, r, nil
|
|
default:
|
|
logrus.Warnf("Unknown CentOS version %d, using default distro for manifest generation", version)
|
|
return manifest.DISTRO_NULL, r, nil
|
|
}
|
|
|
|
case "rhel":
|
|
versionParts := strings.Split(osRelease.VersionID, ".")
|
|
if len(versionParts) != 2 {
|
|
return manifest.DISTRO_NULL, nil, fmt.Errorf("invalid RHEL version format: %s", osRelease.VersionID)
|
|
}
|
|
major, err := strconv.ParseUint(versionParts[0], 10, 64)
|
|
if err != nil {
|
|
return manifest.DISTRO_NULL, nil, fmt.Errorf("cannot parse RHEL major version (%s): %w", versionParts[0], err)
|
|
}
|
|
minor, err := strconv.ParseUint(versionParts[1], 10, 64)
|
|
if err != nil {
|
|
return manifest.DISTRO_NULL, nil, fmt.Errorf("cannot parse RHEL minor version (%s): %w", versionParts[1], err)
|
|
}
|
|
r := &runner.RHEL{
|
|
Major: major,
|
|
Minor: minor,
|
|
}
|
|
switch major {
|
|
case 9:
|
|
return manifest.DISTRO_EL9, r, nil
|
|
case 10:
|
|
return manifest.DISTRO_EL10, r, nil
|
|
default:
|
|
logrus.Warnf("Unknown RHEL version %d, using default distro for manifest generation", major)
|
|
return manifest.DISTRO_NULL, r, nil
|
|
}
|
|
case "debian":
|
|
version, err := strconv.ParseUint(osRelease.VersionID, 10, 64)
|
|
if err != nil {
|
|
return manifest.DISTRO_NULL, nil, fmt.Errorf("cannot parse Debian version (%s): %w", osRelease.VersionID, err)
|
|
}
|
|
|
|
// For Debian, we'll use DISTRO_NULL since there's no specific Debian distro constant
|
|
// but we'll use the Linux runner which should work for Debian
|
|
logrus.Infof("Detected Debian version %d, using Linux runner", version)
|
|
return manifest.DISTRO_NULL, &runner.Linux{}, nil
|
|
}
|
|
|
|
logrus.Warnf("Unknown distro %s, using default runner", osRelease.ID)
|
|
return manifest.DISTRO_NULL, &runner.Linux{}, nil
|
|
}
|
|
|
|
func createRand() *rand.Rand {
|
|
seed, err := cryptorand.Int(cryptorand.Reader, big.NewInt(math.MaxInt64))
|
|
if err != nil {
|
|
panic("Cannot generate an RNG seed.")
|
|
}
|
|
|
|
// math/rand is good enough in this case
|
|
/* #nosec G404 */
|
|
return rand.New(rand.NewSource(seed.Int64()))
|
|
}
|