Update osbuild/images to v0.105.0

This brings a lot of new stuff, among others:
 - Internal image types for RHEL-10 (COMPOSER-2357)
 - Support for Swap partitions in DiskCustomizations (COMPOSER-2400)
 - Support for new partitioning customizations for CS and RHEL
 - Fix for RHEL 8 and 9 AMI boot mode (RHEL-69628)

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
Tomáš Hozza 2024-12-06 11:20:10 +01:00 committed by Ondřej Budai
parent 73f3aa22a2
commit fdff0a903e
36 changed files with 1527 additions and 524 deletions

2
go.mod
View file

@ -46,7 +46,7 @@ require (
github.com/labstack/gommon v0.4.2
github.com/openshift-online/ocm-sdk-go v0.1.438
github.com/oracle/oci-go-sdk/v54 v54.0.0
github.com/osbuild/images v0.102.0
github.com/osbuild/images v0.105.0
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d
github.com/osbuild/pulp-client v0.1.0
github.com/prometheus/client_golang v1.20.2

4
go.sum
View file

@ -534,8 +534,8 @@ github.com/openshift-online/ocm-sdk-go v0.1.438 h1:tsLCCUzbLCTL4RZG02y9RuopmGCXp
github.com/openshift-online/ocm-sdk-go v0.1.438/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y=
github.com/oracle/oci-go-sdk/v54 v54.0.0 h1:CDLjeSejv2aDpElAJrhKpi6zvT/zhZCZuXchUUZ+LS4=
github.com/oracle/oci-go-sdk/v54 v54.0.0/go.mod h1:+t+yvcFGVp+3ZnztnyxqXfQDsMlq8U25faBLa+mqCMc=
github.com/osbuild/images v0.102.0 h1:RQuxZM2w/afCa+Q8mrEG9S60Zbi4j9aSFoFUKFo/Tkk=
github.com/osbuild/images v0.102.0/go.mod h1:4bNmMQOVadIKVC1q8zsLO8tdEQFH90zIp+MQBQUnCiE=
github.com/osbuild/images v0.105.0 h1:KVFKmBhxDzpdZuzLfM84TpfNP40feC5DjRKn+OJcOZ8=
github.com/osbuild/images v0.105.0/go.mod h1:4bNmMQOVadIKVC1q8zsLO8tdEQFH90zIp+MQBQUnCiE=
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d h1:r9BFPDv0uuA9k1947Jybcxs36c/pTywWS1gjeizvtcQ=
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d/go.mod h1:zR1iu/hOuf+OQNJlk70tju9IqzzM4ycq0ectkFBm94U=
github.com/osbuild/pulp-client v0.1.0 h1:L0C4ezBJGTamN3BKdv+rKLuq/WxXJbsFwz/Hj7aEmJ8=

View file

@ -1,5 +0,0 @@
package blueprint
type CACustomization struct {
PEMCerts []string `json:"pem_certs,omitempty" toml:"pem_certs,omitempty"`
}

View file

@ -33,7 +33,7 @@ type Customizations struct {
Installer *InstallerCustomization `json:"installer,omitempty" toml:"installer,omitempty"`
RPM *RPMCustomization `json:"rpm,omitempty" toml:"rpm,omitempty"`
RHSM *RHSMCustomization `json:"rhsm,omitempty" toml:"rhsm,omitempty"`
CACerts *CACustomization `json:"cacerts,omitempty" toml:"ca,omitempty"`
CACerts *CACustomization `json:"cacerts,omitempty" toml:"cacerts,omitempty"`
}
type IgnitionCustomization struct {
@ -144,6 +144,10 @@ type ContainerStorageCustomization struct {
StoragePath *string `json:"destination-path,omitempty" toml:"destination-path,omitempty"`
}
type CACustomization struct {
PEMCerts []string `json:"pem_certs,omitempty" toml:"pem_certs,omitempty"`
}
type CustomizationError struct {
Message string
}
@ -441,16 +445,14 @@ func (c *Customizations) GetRHSM() *RHSMCustomization {
}
func (c *Customizations) checkCACerts() error {
if c == nil {
if c == nil || c.CACerts == nil {
return nil
}
if c.CACerts != nil {
for _, bundle := range c.CACerts.PEMCerts {
_, err := cert.ParseCerts(bundle)
if err != nil {
return err
}
for _, bundle := range c.CACerts.PEMCerts {
_, err := cert.ParseCerts(bundle)
if err != nil {
return err
}
}

View file

@ -75,6 +75,9 @@ type PartitionCustomization struct {
// - Does not define a size. The size is defined by its container: a
// partition ([PartitionCustomization]) or LVM logical volume
// ([LVCustomization]).
//
// Setting the FSType to "swap" creates a swap area (and the Mountpoint must be
// empty).
type FilesystemTypedCustomization struct {
Mountpoint string `json:"mountpoint" toml:"mountpoint"`
Label string `json:"label,omitempty" toml:"label,omitempty"`
@ -332,6 +335,7 @@ func (v *PartitionCustomization) UnmarshalTOML(data any) error {
// - Plain filesystem types are valid for the partition type
// - All non-empty properties are valid for the partition type (e.g.
// LogicalVolumes is empty when the type is "plain" or "btrfs")
// - Filesystems with FSType set to "swap" do not specify a mountpoint.
//
// Note that in *addition* consumers should also call
// ValidateLayoutConstraints() to validate that the policy for disk
@ -450,6 +454,14 @@ var validPlainFSTypes = []string{
}
func (p *PartitionCustomization) validatePlain(mountpoints map[string]bool) error {
if p.FSType == "swap" {
// make sure the mountpoint is empty and return
if p.Mountpoint != "" {
return fmt.Errorf("mountpoint for swap partition must be empty (got %q)", p.Mountpoint)
}
return nil
}
if err := validateMountpoint(p.Mountpoint); err != nil {
return err
}
@ -490,6 +502,13 @@ func (p *PartitionCustomization) validateLVM(mountpoints, vgnames map[string]boo
}
lvnames[lv.Name] = true
if lv.FSType == "swap" {
// make sure the mountpoint is empty and return
if lv.Mountpoint != "" {
return fmt.Errorf("mountpoint for swap logical volume with name %q in volume group %q must be empty", lv.Name, p.Name)
}
return nil
}
if err := validateMountpoint(lv.Mountpoint); err != nil {
return fmt.Errorf("invalid logical volume customization: %w", err)
}
@ -560,7 +579,9 @@ func CheckDiskMountpointsPolicy(partitioning *DiskCustomization, mountpointAllow
mountpoints = append(mountpoints, part.Mountpoint)
}
for _, lv := range part.LogicalVolumes {
mountpoints = append(mountpoints, lv.Mountpoint)
if lv.Mountpoint != "" {
mountpoints = append(mountpoints, lv.Mountpoint)
}
}
for _, subvol := range part.Subvolumes {
mountpoints = append(mountpoints, subvol.Mountpoint)

View file

@ -155,6 +155,10 @@ func (bs *BtrfsSubvolume) GetMountpoint() string {
return bs.Mountpoint
}
func (bs *BtrfsSubvolume) GetFSFile() string {
return bs.GetMountpoint()
}
func (bs *BtrfsSubvolume) GetFSType() string {
return "btrfs"
}

View file

@ -56,6 +56,8 @@ const (
RootPartitionUUID = "6264D520-3FB9-423F-8AB8-7A0A8E3D3562"
SwapPartitionGUID = "0657FD6D-A4AB-43C4-84E5-0933C84B4F4F"
// Extended Boot Loader Partition
XBootLDRPartitionGUID = "BC13C2FF-59E6-4262-A352-B275FD6F7172"
@ -70,6 +72,9 @@ const (
// Partition type ID for ESP on dos
DosESPID = "ef00"
// Partition type ID for swap
DosSwapID = "82"
)
// pt type -> type -> ID mapping for convenience
@ -80,6 +85,7 @@ var idMap = map[PartitionTableType]map[string]string{
"data": DosLinuxTypeID,
"esp": DosESPID,
"lvm": DosLinuxTypeID,
"swap": DosSwapID,
},
PT_GPT: {
"bios": BIOSBootPartitionGUID,
@ -87,6 +93,7 @@ var idMap = map[PartitionTableType]map[string]string{
"data": FilesystemDataGUID,
"esp": EFISystemPartitionGUID,
"lvm": LVMPartitionGUID,
"swap": SwapPartitionGUID,
},
}
@ -248,13 +255,21 @@ type Mountable interface {
// GetMountPoint returns the path of the mount point.
GetMountpoint() string
// GetFSType returns the file system type, e.g. 'xfs'.
GetFSType() string
FSTabEntity
}
// GetFSSpec returns the file system spec information.
// FSTabEntity describes any entity that can appear in the fstab file.
type FSTabEntity interface {
// FSSpec for the entity (UUID and Label); the first field of fstab(5).
GetFSSpec() FSSpec
// GetFSTabOptions returns options for mounting the entity.
// The mount point (target) for a filesystem or "none" for swap areas; the second field of fstab(5).
GetFSFile() string
// The type of the filesystem or swap for swap areas; the third field of fstab(5).
GetFSType() string
// The mount options, freq, and passno for the entity; the fourth fifth, and sixth fields of fstab(5) respectively.
GetFSTabOptions() (FSTabOptions, error)
}

View file

@ -55,6 +55,10 @@ func (fs *Filesystem) GetMountpoint() string {
return fs.Mountpoint
}
func (fs *Filesystem) GetFSFile() string {
return fs.GetMountpoint()
}
func (fs *Filesystem) GetFSType() string {
if fs == nil {
return ""

View file

@ -110,12 +110,15 @@ func (vg *LVMVolumeGroup) CreateLogicalVolume(lvName string, size uint64, payloa
if lvName == "" {
// generate a name based on the payload's mountpoint
mntble, ok := payload.(Mountable)
if !ok {
return nil, fmt.Errorf("could not create logical volume: no name provided and payload is not mountable")
switch ent := payload.(type) {
case Mountable:
lvName = ent.GetMountpoint()
case *Swap:
lvName = "swap"
default:
return nil, fmt.Errorf("could not create logical volume: no name provided and payload %T is not mountable or swap", payload)
}
mountpoint := mntble.GetMountpoint()
autoName, err := vg.genLVName(mountpoint)
autoName, err := vg.genLVName(lvName)
if err != nil {
return nil, err
}
@ -133,8 +136,7 @@ func (vg *LVMVolumeGroup) CreateLogicalVolume(lvName string, size uint64, payloa
return &vg.LogicalVolumes[len(vg.LogicalVolumes)-1], nil
}
func (vg *LVMVolumeGroup) AlignUp(size uint64) uint64 {
func alignUp(size uint64) uint64 {
if size%LVMDefaultExtentSize != 0 {
size += LVMDefaultExtentSize - size%LVMDefaultExtentSize
}
@ -142,6 +144,10 @@ func (vg *LVMVolumeGroup) AlignUp(size uint64) uint64 {
return size
}
func (vg *LVMVolumeGroup) AlignUp(size uint64) uint64 {
return alignUp(size)
}
func (vg *LVMVolumeGroup) MetadataSize() uint64 {
if vg == nil {
return 0
@ -211,7 +217,7 @@ func (lv *LVMLogicalVolume) EnsureSize(s uint64) bool {
panic("LVMLogicalVolume.EnsureSize: nil entity")
}
if s > lv.Size {
lv.Size = s
lv.Size = alignUp(s)
return true
}
return false

View file

@ -590,6 +590,32 @@ func (pt *PartitionTable) ForEachMountable(cb MountableCallback) error {
return forEachMountable(pt, []Entity{pt}, cb)
}
type FSTabEntityCallback func(mnt FSTabEntity, path []Entity) error
func forEachFSTabEntity(c Container, path []Entity, cb FSTabEntityCallback) error {
for idx := uint(0); idx < c.GetItemCount(); idx++ {
child := c.GetChild(idx)
childPath := append(path, child)
var err error
switch ent := child.(type) {
case FSTabEntity:
err = cb(ent, childPath)
case Container:
err = forEachFSTabEntity(ent, childPath, cb)
}
if err != nil {
return err
}
}
return nil
}
// ForEachFSTabEntity runs the provided callback function on each FSTabEntity
// in the PartitionTable.
func (pt *PartitionTable) ForEachFSTabEntity(cb FSTabEntityCallback) error {
return forEachFSTabEntity(pt, []Entity{pt}, cb)
}
// FindMountable returns the Mountable entity with the given mountpoint in the
// PartitionTable. Returns nil if no Entity has the target as a Mountpoint.
func (pt *PartitionTable) FindMountable(mountpoint string) Mountable {
@ -812,18 +838,23 @@ type partitionTableFeatures struct {
FAT bool
EXT4 bool
LUKS bool
Swap bool
}
// features examines all of the PartitionTable entities
// and returns a struct with flags set for each feature used
// features examines all of the PartitionTable entities and returns a struct
// with flags set for each feature used. The meaning of "feature" here is quite
// broad. Most disk Entity types are represented by a feature and the existence
// of at least one type in the partition table means the feature is
// represented. For Filesystem entities, there is a separate feature for each
// filesystem type
func (pt *PartitionTable) features() partitionTableFeatures {
var ptFeatures partitionTableFeatures
introspectPT := func(e Entity, path []Entity) error {
switch ent := e.(type) {
case *LVMLogicalVolume:
case *LVMVolumeGroup, *LVMLogicalVolume:
ptFeatures.LVM = true
case *Btrfs:
case *Btrfs, *BtrfsSubvolume:
ptFeatures.Btrfs = true
case *Filesystem:
switch ent.GetFSType() {
@ -836,8 +867,14 @@ func (pt *PartitionTable) features() partitionTableFeatures {
case "ext4":
ptFeatures.EXT4 = true
}
case *Swap:
ptFeatures.Swap = true
case *LUKSContainer:
ptFeatures.LUKS = true
case *PartitionTable, *Partition:
// nothing to do
default:
panic(fmt.Errorf("unknown entity type %T", e))
}
return nil
}
@ -1254,25 +1291,43 @@ func addPlainPartition(pt *PartitionTable, partition blueprint.PartitionCustomiz
if err != nil {
return fmt.Errorf("error creating partition with mountpoint %q: %w", partition.Mountpoint, err)
}
// all user-defined partitions are data partitions except boot
typeName := "data"
if partition.Mountpoint == "/boot" {
// all user-defined partitions are data partitions except boot and swap
var typeName string
switch {
case partition.Mountpoint == "/boot":
typeName = "boot"
case fstype == "swap":
typeName = "swap"
default:
typeName = "data"
}
partType, err := getPartitionTypeIDfor(pt.Type, typeName)
if err != nil {
return fmt.Errorf("error getting partition type ID for %q: %w", partition.Mountpoint, err)
}
newpart := Partition{
Type: partType,
Bootable: false,
Size: partition.MinSize,
Payload: &Filesystem{
var payload PayloadEntity
switch typeName {
case "swap":
payload = &Swap{
Label: partition.Label,
FSTabOptions: "defaults", // TODO: add customization
}
default:
payload = &Filesystem{
Type: fstype,
Label: partition.Label,
Mountpoint: partition.Mountpoint,
FSTabOptions: "defaults", // TODO: add customization
},
}
}
newpart := Partition{
Type: partType,
Size: partition.MinSize,
Payload: payload,
}
pt.Partitions = append(pt.Partitions, newpart)
return nil
@ -1310,11 +1365,21 @@ func addLVMPartition(pt *PartitionTable, partition blueprint.PartitionCustomizat
if err != nil {
return fmt.Errorf("error creating logical volume %q (%s): %w", lv.Name, lv.Mountpoint, err)
}
newfs := &Filesystem{
Type: fstype,
Label: lv.Label,
Mountpoint: lv.Mountpoint,
FSTabOptions: "defaults", // TODO: add customization
var newfs PayloadEntity
switch fstype {
case "swap":
newfs = &Swap{
Label: lv.Label,
FSTabOptions: "defaults", // TODO: add customization
}
default:
newfs = &Filesystem{
Type: fstype,
Label: lv.Label,
Mountpoint: lv.Mountpoint,
FSTabOptions: "defaults", // TODO: add customization
}
}
if _, err := newvg.CreateLogicalVolume(lv.Name, lv.MinSize, newfs); err != nil {
return fmt.Errorf("error creating logical volume %q (%s): %w", lv.Name, lv.Mountpoint, err)

77
vendor/github.com/osbuild/images/pkg/disk/swap.go generated vendored Normal file
View file

@ -0,0 +1,77 @@
package disk
import (
"math/rand"
"reflect"
"github.com/google/uuid"
)
// Swap defines the payload for a swap partition. It's similar to a
// [Filesystem] but with fewer fields. It is a [PayloadEntity] and also a
// [FSTabEntity].
type Swap struct {
UUID string
Label string
// The fourth field of fstab(5); fs_mntops
FSTabOptions string
}
func init() {
payloadEntityMap["swap"] = reflect.TypeOf(Swap{})
}
func (s *Swap) EntityName() string {
return "swap"
}
func (s *Swap) Clone() Entity {
if s == nil {
return nil
}
return &Swap{
UUID: s.UUID,
Label: s.Label,
FSTabOptions: s.FSTabOptions,
}
}
// For swap, the fs_file entry in the fstab is always "none".
func (s *Swap) GetFSFile() string {
return "none"
}
// For swap, the fs_vfstype entry in the fstab is always "swap".
func (s *Swap) GetFSType() string {
return "swap"
}
func (s *Swap) GetFSSpec() FSSpec {
if s == nil {
return FSSpec{}
}
return FSSpec{
UUID: s.UUID,
Label: s.Label,
}
}
// For swap, the Freq and PassNo are always 0.
func (s *Swap) GetFSTabOptions() (FSTabOptions, error) {
if s == nil {
return FSTabOptions{}, nil
}
return FSTabOptions{
MntOps: s.FSTabOptions,
Freq: 0,
PassNo: 0,
}, nil
}
func (s *Swap) GenUUID(rng *rand.Rand) {
if s.UUID == "" {
s.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
}
}

View file

@ -288,31 +288,36 @@ func (t *imageType) Manifest(bp *blueprint.Blueprint,
// checkOptions checks the validity and compatibility of options and customizations for the image type.
// Returns ([]string, error) where []string, if non-nil, will hold any generated warnings (e.g. deprecation notices).
func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOptions) ([]string, error) {
customizations := bp.Customizations
var warnings []string
if !t.rpmOstree && options.OSTree != nil {
return warnings, fmt.Errorf("OSTree is not supported for %q", t.Name())
}
// we do not support embedding containers on ostree-derived images, only on commits themselves
if len(bp.Containers) > 0 && t.rpmOstree && (t.name != "iot-commit" && t.name != "iot-container") {
return nil, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
if options.OSTree != nil {
if err := options.OSTree.Validate(); err != nil {
return nil, err
return warnings, err
}
}
if t.bootISO && t.rpmOstree {
// ostree-based ISOs require a URL from which to pull a payload commit
if options.OSTree == nil || options.OSTree.URL == "" {
return nil, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
return warnings, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
}
}
if t.name == "iot-raw-image" || t.name == "iot-qcow2-image" {
allowed := []string{"User", "Group", "Directories", "Files", "Services", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return nil, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
// TODO: consider additional checks, such as those in "edge-simplified-installer" in RHEL distros
}
@ -323,16 +328,16 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
if t.name == "iot-simplified-installer" {
allowed := []string{"InstallationDevice", "FDO", "Ignition", "Kernel", "User", "Group", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return nil, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
if customizations.GetInstallationDevice() == "" {
return nil, fmt.Errorf("boot ISO image type %q requires specifying an installation device to install to", t.name)
return warnings, fmt.Errorf("boot ISO image type %q requires specifying an installation device to install to", t.name)
}
// FDO is optional, but when specified has some restrictions
if customizations.GetFDO() != nil {
if customizations.GetFDO().ManufacturingServerURL == "" {
return nil, fmt.Errorf("boot ISO image type %q requires specifying FDO.ManufacturingServerURL configuration to install to when using FDO", t.name)
return warnings, fmt.Errorf("boot ISO image type %q requires specifying FDO.ManufacturingServerURL configuration to install to when using FDO", t.name)
}
var diunSet int
if customizations.GetFDO().DiunPubKeyHash != "" {
@ -345,66 +350,69 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
diunSet++
}
if diunSet != 1 {
return nil, fmt.Errorf("boot ISO image type %q requires specifying one of [FDO.DiunPubKeyHash,FDO.DiunPubKeyInsecure,FDO.DiunPubKeyRootCerts] configuration to install to when using FDO", t.name)
return warnings, fmt.Errorf("boot ISO image type %q requires specifying one of [FDO.DiunPubKeyHash,FDO.DiunPubKeyInsecure,FDO.DiunPubKeyRootCerts] configuration to install to when using FDO", t.name)
}
}
// ignition is optional, we might be using FDO
if customizations.GetIgnition() != nil {
if customizations.GetIgnition().Embedded != nil && customizations.GetIgnition().FirstBoot != nil {
return nil, fmt.Errorf("both ignition embedded and firstboot configurations found")
return warnings, fmt.Errorf("both ignition embedded and firstboot configurations found")
}
if customizations.GetIgnition().FirstBoot != nil && customizations.GetIgnition().FirstBoot.ProvisioningURL == "" {
return nil, fmt.Errorf("ignition.firstboot requires a provisioning url")
return warnings, fmt.Errorf("ignition.firstboot requires a provisioning url")
}
}
} else if t.name == "iot-installer" || t.name == "image-installer" {
// "Installer" is actually not allowed for image-installer right now, but this is checked at the end
allowed := []string{"User", "Group", "FIPS", "Installer", "Timezone", "Locale"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return nil, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
} else if t.name == "live-installer" {
allowed := []string{"Installer"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return nil, fmt.Errorf(distro.NoCustomizationsAllowedError, t.name)
return warnings, fmt.Errorf(distro.NoCustomizationsAllowedError, t.name)
}
}
}
if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.rpmOstree {
return nil, fmt.Errorf("kernel boot parameter customizations are not supported for ostree types")
return warnings, fmt.Errorf("kernel boot parameter customizations are not supported for ostree types")
}
mountpoints := customizations.GetFilesystems()
partitioning, err := customizations.GetPartitioning()
if err != nil {
return nil, err
return warnings, err
}
if (len(mountpoints) > 0 || partitioning != nil) && t.rpmOstree {
return nil, fmt.Errorf("Custom mountpoints and partitioning are not supported for ostree types")
return warnings, fmt.Errorf("Custom mountpoints and partitioning are not supported for ostree types")
}
if len(mountpoints) > 0 && partitioning != nil {
return nil, fmt.Errorf("partitioning customizations cannot be used with custom filesystems (mountpoints)")
return warnings, fmt.Errorf("partitioning customizations cannot be used with custom filesystems (mountpoints)")
}
if err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies); err != nil {
return nil, err
return warnings, err
}
if err := blueprint.CheckDiskMountpointsPolicy(partitioning, policies.MountpointPolicies); err != nil {
return warnings, err
}
if err := partitioning.ValidateLayoutConstraints(); err != nil {
return nil, err
}
if osc := customizations.GetOpenSCAP(); osc != nil {
supported := oscap.IsProfileAllowed(osc.ProfileID, oscapProfileAllowList)
if !supported {
return nil, fmt.Errorf("OpenSCAP unsupported profile: %s", osc.ProfileID)
return warnings, fmt.Errorf("OpenSCAP unsupported profile: %s", osc.ProfileID)
}
if t.rpmOstree {
return nil, fmt.Errorf("OpenSCAP customizations are not supported for ostree types")
return warnings, fmt.Errorf("OpenSCAP customizations are not supported for ostree types")
}
if osc.ProfileID == "" {
return nil, fmt.Errorf("OpenSCAP profile cannot be empty")
return warnings, fmt.Errorf("OpenSCAP profile cannot be empty")
}
}
@ -414,7 +422,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
err = blueprint.ValidateDirFileCustomizations(dc, fc)
if err != nil {
return nil, err
return warnings, err
}
dcp := policies.CustomDirectoriesPolicies
@ -427,33 +435,32 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, dcp)
if err != nil {
return nil, err
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, fcp)
if err != nil {
return nil, err
return warnings, err
}
// check if repository customizations are valid
_, err = customizations.GetRepositories()
if err != nil {
return nil, err
return warnings, err
}
if customizations.GetFIPS() && !common.IsBuildHostFIPSEnabled() {
w := fmt.Sprintln(common.FIPSEnabledImageWarning)
return []string{w}, nil
warnings = append(warnings, fmt.Sprintln(common.FIPSEnabledImageWarning))
}
instCust, err := customizations.GetInstaller()
if err != nil {
return nil, err
return warnings, err
}
if instCust != nil {
// only supported by the Anaconda installer
if slices.Index([]string{"iot-installer"}, t.name) == -1 {
return nil, fmt.Errorf("installer customizations are not supported for %q", t.Name())
return warnings, fmt.Errorf("installer customizations are not supported for %q", t.Name())
}
// NOTE: the image type check is redundant with the check above, but
@ -464,17 +471,9 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
instCust.Kickstart != nil &&
len(instCust.Kickstart.Contents) > 0 &&
(customizations.GetUsers() != nil || customizations.GetGroups() != nil) {
return nil, fmt.Errorf("iot-installer installer.kickstart.contents are not supported in combination with users or groups")
return warnings, fmt.Errorf("iot-installer installer.kickstart.contents are not supported in combination with users or groups")
}
}
diskc, err := customizations.GetPartitioning()
if err != nil {
return nil, err
}
if err := diskc.ValidateLayoutConstraints(); err != nil {
return nil, fmt.Errorf("cannot use disk customization: %w", err)
}
return nil, nil
return warnings, nil
}

View file

@ -377,7 +377,7 @@ func DiskImage(workload workload.Workload,
img.Workload = workload
img.Compression = t.Compression
// TODO: move generation into LiveImage
pt, err := t.GetPartitionTable(customizations.GetFilesystems(), options, rng)
pt, err := t.GetPartitionTable(customizations, options, rng)
if err != nil {
return nil, err
}
@ -567,7 +567,7 @@ func EdgeRawImage(workload workload.Workload,
img.OSName = "rhel-edge"
// TODO: move generation into LiveImage
pt, err := t.GetPartitionTable(customizations.GetFilesystems(), options, rng)
pt, err := t.GetPartitionTable(customizations, options, rng)
if err != nil {
return nil, err
}
@ -609,7 +609,7 @@ func EdgeSimplifiedInstallerImage(workload workload.Workload,
rawImg.OSName = "rhel-edge"
// TODO: move generation into LiveImage
pt, err := t.GetPartitionTable(customizations.GetFilesystems(), options, rng)
pt, err := t.GetPartitionTable(customizations, options, rng)
if err != nil {
return nil, err
}

View file

@ -39,6 +39,12 @@ const (
BlueprintPkgsKey = "blueprint"
)
// Default directory size minimums for all image types.
var requiredDirectorySizes = map[string]uint64{
"/": 1 * datasizes.GiB,
"/usr": 2 * datasizes.GiB,
}
type ImageFunc func(workload workload.Workload, t *ImageType, customizations *blueprint.Customizations, options distro.ImageOptions, packageSets map[string]rpmmd.PackageSet, containers []container.SourceSpec, rng *rand.Rand) (image.ImageKind, error)
type PackageSetFunc func(t *ImageType) rpmmd.PackageSet
@ -180,7 +186,7 @@ func (t *ImageType) BootMode() platform.BootMode {
}
func (t *ImageType) GetPartitionTable(
mountpoints []blueprint.FilesystemCustomization,
customizations *blueprint.Customizations,
options distro.ImageOptions,
rng *rand.Rand,
) (*disk.PartitionTable, error) {
@ -193,8 +199,29 @@ func (t *ImageType) GetPartitionTable(
}
imageSize := t.Size(options.Size)
partitioning, err := customizations.GetPartitioning()
if err != nil {
return nil, err
}
if partitioning != nil {
// Use the new custom partition table to create a PT fully based on the user's customizations.
// This overrides FilesystemCustomizations, but we should never have both defined.
if options.Size > 0 {
// user specified a size on the command line, so let's override the
// customization with the calculated/rounded imageSize
partitioning.MinSize = imageSize
}
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, options.PartitioningMode, nil, rng)
partOptions := &disk.CustomPartitionTableOptions{
PartitionTableType: basePartitionTable.Type, // PT type is not customizable, it is determined by the base PT for an image type or architecture
BootMode: t.BootMode(),
DefaultFSType: disk.FS_XFS, // default fs type for RHEL
RequiredMinSizes: requiredDirectorySizes,
}
return disk.NewCustomPartitionTable(partitioning, partOptions, rng)
}
return disk.NewPartitionTable(&basePartitionTable, customizations.GetFilesystems(), imageSize, options.PartitioningMode, nil, rng)
}
func (t *ImageType) getDefaultImageConfig() *distro.ImageConfig {
@ -333,6 +360,10 @@ func (t *ImageType) Manifest(bp *blueprint.Blueprint,
// checkOptions checks the validity and compatibility of options and customizations for the image type.
// Returns ([]string, error) where []string, if non-nil, will hold any generated warnings (e.g. deprecation notices).
func (t *ImageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOptions) ([]string, error) {
if !t.RPMOSTree && options.OSTree != nil {
return nil, fmt.Errorf("OSTree is not supported for %q", t.Name())
}
if t.arch.distro.CheckOptions != nil {
return t.arch.distro.CheckOptions(t, bp, options)
}

View file

@ -9,11 +9,162 @@ import (
"github.com/osbuild/images/pkg/rpmmd"
)
func mkAMIImgTypeX86_64() *rhel.ImageType {
it := rhel.NewImageType(
"ami",
"image.raw",
"application/octet-stream",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: ec2PackageSet,
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image"},
[]string{"image"},
)
it.KernelOptions = amiKernelOptions
it.Bootable = true
it.DefaultSize = 10 * datasizes.GibiByte
it.DefaultImageConfig = defaultEc2ImageConfigX86_64()
it.BasePartitionTables = defaultBasePartitionTables
return it
}
func mkAMIImgTypeAarch64() *rhel.ImageType {
it := rhel.NewImageType(
"ami",
"image.raw",
"application/octet-stream",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: ec2PackageSet,
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image"},
[]string{"image"},
)
it.KernelOptions = amiAarch64KernelOptions
it.Bootable = true
it.DefaultSize = 10 * datasizes.GibiByte
it.DefaultImageConfig = defaultEc2ImageConfig()
it.BasePartitionTables = defaultBasePartitionTables
return it
}
// RHEL internal-only x86_64 EC2 image type
func mkEc2ImgTypeX86_64() *rhel.ImageType {
it := rhel.NewImageType(
"ec2",
"image.raw.xz",
"application/xz",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: ec2PackageSet,
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image", "xz"},
[]string{"xz"},
)
it.Compression = "xz"
it.KernelOptions = amiKernelOptions
it.Bootable = true
it.DefaultSize = 10 * datasizes.GibiByte
it.DefaultImageConfig = defaultEc2ImageConfigX86_64()
it.BasePartitionTables = defaultBasePartitionTables
return it
}
// RHEL internal-only aarch64 EC2 image type
func mkEC2ImgTypeAarch64() *rhel.ImageType {
it := rhel.NewImageType(
"ec2",
"image.raw.xz",
"application/xz",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: ec2PackageSet,
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image", "xz"},
[]string{"xz"},
)
it.Compression = "xz"
it.KernelOptions = amiAarch64KernelOptions
it.Bootable = true
it.DefaultSize = 10 * datasizes.GibiByte
it.DefaultImageConfig = defaultEc2ImageConfig()
it.BasePartitionTables = defaultBasePartitionTables
return it
}
// RHEL internal-only x86_64 EC2 HA image type
func mkEc2HaImgTypeX86_64() *rhel.ImageType {
it := rhel.NewImageType(
"ec2-ha",
"image.raw.xz",
"application/xz",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: rhelEc2HaPackageSet,
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image", "xz"},
[]string{"xz"},
)
it.Compression = "xz"
it.KernelOptions = amiKernelOptions
it.Bootable = true
it.DefaultSize = 10 * datasizes.GibiByte
it.DefaultImageConfig = defaultEc2ImageConfigX86_64()
it.BasePartitionTables = defaultBasePartitionTables
return it
}
func mkEC2SapImgTypeX86_64(osVersion string) *rhel.ImageType {
it := rhel.NewImageType(
"ec2-sap",
"image.raw.xz",
"application/xz",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: rhelEc2SapPackageSet,
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image", "xz"},
[]string{"xz"},
)
it.Compression = "xz"
it.KernelOptions = amiSapKernelOptions
it.Bootable = true
it.DefaultSize = 10 * datasizes.GibiByte
it.DefaultImageConfig = sapImageConfig(osVersion).InheritFrom(defaultEc2ImageConfigX86_64())
it.BasePartitionTables = defaultBasePartitionTables
return it
}
// IMAGE CONFIG
// TODO: move these to the EC2 environment
const amiKernelOptions = "console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=4294967295"
const (
amiKernelOptions = "console=tty0 console=ttyS0,115200n8 nvme_core.io_timeout=4294967295"
amiAarch64KernelOptions = amiKernelOptions + " iommu.strict=0"
amiSapKernelOptions = amiKernelOptions + " processor.max_cstate=1 intel_idle.max_cstate=1"
)
// default EC2 images config (common for all architectures)
func baseEc2ImageConfig() *distro.ImageConfig {
func defaultEc2ImageConfig() *distro.ImageConfig {
return &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
Timezone: common.ToPtr("UTC"),
@ -120,32 +271,28 @@ func baseEc2ImageConfig() *distro.ImageConfig {
}
}
// Default AMI (custom image built by users) images config.
// The configuration does not touch the RHSM configuration at all.
// https://issues.redhat.com/browse/COMPOSER-2157
func defaultAMIImageConfig() *distro.ImageConfig {
return baseEc2ImageConfig()
func appendEC2DracutX86_64(ic *distro.ImageConfig) *distro.ImageConfig {
ic.DracutConf = append(ic.DracutConf,
&osbuild.DracutConfStageOptions{
Filename: "ec2.conf",
Config: osbuild.DracutConfigFile{
AddDrivers: []string{
"nvme",
"xen-blkfront",
},
},
})
return ic
}
// Default AMI x86_64 (custom image built by users) images config.
// The configuration does not touch the RHSM configuration at all.
// https://issues.redhat.com/browse/COMPOSER-2157
func defaultAMIImageConfigX86_64() *distro.ImageConfig {
ic := defaultAMIImageConfig()
func defaultEc2ImageConfigX86_64() *distro.ImageConfig {
ic := defaultEc2ImageConfig()
return appendEC2DracutX86_64(ic)
}
// common ec2 image build package set
func ec2BuildPackageSet(t *rhel.ImageType) rpmmd.PackageSet {
return distroBuildPackageSet(t).Append(
rpmmd.PackageSet{
Include: []string{
"python3-pyyaml",
},
})
}
// PACKAGE SETS
func ec2CommonPackageSet(t *rhel.ImageType) rpmmd.PackageSet {
func ec2PackageSet(t *rhel.ImageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@core",
@ -202,63 +349,24 @@ func ec2CommonPackageSet(t *rhel.ImageType) rpmmd.PackageSet {
return ps
}
func mkAMIImgTypeX86_64() *rhel.ImageType {
it := rhel.NewImageType(
"ami",
"image.raw",
"application/octet-stream",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: ec2CommonPackageSet,
// rhel-ha-ec2 image package set
func rhelEc2HaPackageSet(t *rhel.ImageType) rpmmd.PackageSet {
ec2HaPackageSet := ec2PackageSet(t)
ec2HaPackageSet = ec2HaPackageSet.Append(rpmmd.PackageSet{
Include: []string{
"fence-agents-all",
"pacemaker",
"pcs",
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image"},
[]string{"image"},
)
it.KernelOptions = amiKernelOptions
it.Bootable = true
it.DefaultSize = 10 * datasizes.GibiByte
it.DefaultImageConfig = defaultAMIImageConfigX86_64()
it.BasePartitionTables = defaultBasePartitionTables
return it
})
return ec2HaPackageSet
}
func mkAMIImgTypeAarch64() *rhel.ImageType {
it := rhel.NewImageType(
"ami",
"image.raw",
"application/octet-stream",
map[string]rhel.PackageSetFunc{
rhel.BuildPkgsKey: ec2BuildPackageSet,
rhel.OSPkgsKey: ec2CommonPackageSet,
// rhel-sap-ec2 image package set
func rhelEc2SapPackageSet(t *rhel.ImageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
//"libcanberra-gtk2", // libcanberra-gtk2 is not available in RHEL-10
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image"},
[]string{"image"},
)
it.KernelOptions = "console=ttyS0,115200n8 console=tty0 nvme_core.io_timeout=4294967295 iommu.strict=0"
it.Bootable = true
it.DefaultSize = 10 * datasizes.GibiByte
it.DefaultImageConfig = defaultAMIImageConfig()
it.BasePartitionTables = defaultBasePartitionTables
return it
}
func appendEC2DracutX86_64(ic *distro.ImageConfig) *distro.ImageConfig {
ic.DracutConf = append(ic.DracutConf,
&osbuild.DracutConfStageOptions{
Filename: "ec2.conf",
Config: osbuild.DracutConfigFile{
AddDrivers: []string{
"nvme",
"xen-blkfront",
},
},
})
return ic
}.Append(ec2PackageSet(t)).Append(SapPackageSet(t))
}

View file

@ -2,15 +2,17 @@ package rhel10
import (
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/datasizes"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/rhel"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/rpmmd"
)
// Azure non-RHEL image type
func mkAzureImgType() *rhel.ImageType {
// Azure image type
func mkAzureImgType(rd *rhel.Distribution) *rhel.ImageType {
it := rhel.NewImageType(
"vhd",
"disk.vhd",
@ -27,32 +29,57 @@ func mkAzureImgType() *rhel.ImageType {
it.KernelOptions = defaultAzureKernelOptions
it.Bootable = true
it.DefaultSize = 4 * datasizes.GibiByte
it.DefaultImageConfig = defaultAzureImageConfig
it.DefaultImageConfig = defaultAzureImageConfig(rd)
it.BasePartitionTables = defaultBasePartitionTables
return it
}
// Azure BYOS image type
func mkAzureByosImgType(rd distro.Distro) *rhel.ImageType {
// Azure RHEL-internal image type
func mkAzureInternalImgType(rd *rhel.Distribution) *rhel.ImageType {
it := rhel.NewImageType(
"vhd",
"disk.vhd",
"application/x-vhd",
"azure-rhui",
"disk.vhd.xz",
"application/xz",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: azurePackageSet,
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image", "vpc"},
[]string{"vpc"},
[]string{"os", "image", "vpc", "xz"},
[]string{"xz"},
)
it.Compression = "xz"
it.KernelOptions = defaultAzureKernelOptions
it.Bootable = true
it.DefaultSize = 4 * datasizes.GibiByte
it.DefaultImageConfig = defaultAzureImageConfig
it.BasePartitionTables = defaultBasePartitionTables
it.DefaultSize = 64 * datasizes.GibiByte
it.DefaultImageConfig = defaultAzureImageConfig(rd)
it.BasePartitionTables = azureInternalBasePartitionTables
return it
}
func mkAzureSapInternalImgType(rd *rhel.Distribution) *rhel.ImageType {
it := rhel.NewImageType(
"azure-sap-rhui",
"disk.vhd.xz",
"application/xz",
map[string]rhel.PackageSetFunc{
rhel.OSPkgsKey: azureSapPackageSet,
},
rhel.DiskImage,
[]string{"build"},
[]string{"os", "image", "vpc", "xz"},
[]string{"xz"},
)
it.Compression = "xz"
it.KernelOptions = defaultAzureKernelOptions
it.Bootable = true
it.DefaultSize = 64 * datasizes.GibiByte
it.DefaultImageConfig = sapAzureImageConfig(rd)
it.BasePartitionTables = azureInternalBasePartitionTables
return it
}
@ -143,161 +170,401 @@ func azurePackageSet(t *rhel.ImageType) rpmmd.PackageSet {
return azureCommonPackageSet(t)
}
// Azure SAP image package set
// Includes the common azure package set, the common SAP packages
func azureSapPackageSet(t *rhel.ImageType) rpmmd.PackageSet {
return azureCommonPackageSet(t).Append(SapPackageSet(t))
}
// PARTITION TABLES
func azureInternalBasePartitionTables(t *rhel.ImageType) (disk.PartitionTable, bool) {
switch t.Arch().Name() {
case arch.ARCH_X86_64.String():
return disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: disk.PT_GPT,
Size: 64 * datasizes.GibiByte,
Partitions: []disk.Partition{
{
Size: 500 * datasizes.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
// NB: we currently don't support /boot on LVM
{
Size: 1 * datasizes.GibiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * datasizes.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Type: disk.LVMPartitionGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 1 * datasizes.GibiByte,
Name: "homelv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "home",
Mountpoint: "/home",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * datasizes.GibiByte,
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * datasizes.GibiByte,
Name: "tmplv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "tmp",
Mountpoint: "/tmp",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * datasizes.GibiByte,
Name: "usrlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "usr",
Mountpoint: "/usr",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * datasizes.GibiByte,
Name: "varlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "var",
Mountpoint: "/var",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
}, true
case arch.ARCH_AARCH64.String():
return disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: disk.PT_GPT,
Size: 64 * datasizes.GibiByte,
Partitions: []disk.Partition{
{
Size: 500 * datasizes.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
// NB: we currently don't support /boot on LVM
{
Size: 1 * datasizes.GibiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Type: disk.LVMPartitionGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 1 * datasizes.GibiByte,
Name: "homelv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "home",
Mountpoint: "/home",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * datasizes.GibiByte,
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * datasizes.GibiByte,
Name: "tmplv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "tmp",
Mountpoint: "/tmp",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * datasizes.GibiByte,
Name: "usrlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "usr",
Mountpoint: "/usr",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * datasizes.GibiByte,
Name: "varlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "var",
Mountpoint: "/var",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
}, true
default:
return disk.PartitionTable{}, false
}
}
// IMAGE CONFIG
// use loglevel=3 as described in the RHEL documentation and used in existing RHEL images built by MSFT
const defaultAzureKernelOptions = "ro loglevel=3 console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300"
// based on https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/deploying_rhel_9_on_microsoft_azure/assembly_deploying-a-rhel-image-as-a-virtual-machine-on-microsoft-azure_cloud-content-azure#making-configuration-changes_configure-the-image-azure
var defaultAzureImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("Etc/UTC"),
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
X11Keymap: &osbuild.X11KeymapOptions{
Layouts: []string{"us"},
},
},
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel-core",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
func defaultAzureImageConfig(rd *rhel.Distribution) *distro.ImageConfig {
ic := &distro.ImageConfig{
Timezone: common.ToPtr("Etc/UTC"),
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
X11Keymap: &osbuild.X11KeymapOptions{
Layouts: []string{"us"},
},
},
},
EnabledServices: []string{
"firewalld",
"nm-cloud-setup.service",
"nm-cloud-setup.timer",
"sshd",
"waagent",
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
ClientAliveInterval: common.ToPtr(180),
},
},
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-amdgpu.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("amdgpu"),
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel-core",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
},
},
{
Filename: "blacklist-intel-cstate.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("intel_cstate"),
EnabledServices: []string{
"firewalld",
"nm-cloud-setup.service",
"nm-cloud-setup.timer",
"sshd",
"waagent",
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
ClientAliveInterval: common.ToPtr(180),
},
},
{
Filename: "blacklist-floppy.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("floppy"),
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-amdgpu.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("amdgpu"),
},
},
{
Filename: "blacklist-intel-cstate.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("intel_cstate"),
},
},
{
Filename: "blacklist-floppy.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("floppy"),
},
},
{
Filename: "blacklist-nouveau.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("nouveau"),
osbuild.NewModprobeConfigCmdBlacklist("lbm-nouveau"),
},
},
{
Filename: "blacklist-skylake-edac.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("skx_edac"),
},
},
},
{
Filename: "blacklist-nouveau.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("nouveau"),
osbuild.NewModprobeConfigCmdBlacklist("lbm-nouveau"),
},
},
{
Filename: "blacklist-skylake-edac.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("skx_edac"),
},
},
},
CloudInit: []*osbuild.CloudInitStageOptions{
{
Filename: "10-azure-kvp.cfg",
Config: osbuild.CloudInitConfigFile{
Reporting: &osbuild.CloudInitConfigReporting{
Logging: &osbuild.CloudInitConfigReportingHandlers{
Type: "log",
CloudInit: []*osbuild.CloudInitStageOptions{
{
Filename: "10-azure-kvp.cfg",
Config: osbuild.CloudInitConfigFile{
Reporting: &osbuild.CloudInitConfigReporting{
Logging: &osbuild.CloudInitConfigReportingHandlers{
Type: "log",
},
Telemetry: &osbuild.CloudInitConfigReportingHandlers{
Type: "hyperv",
},
},
Telemetry: &osbuild.CloudInitConfigReportingHandlers{
Type: "hyperv",
},
},
{
Filename: "91-azure_datasource.cfg",
Config: osbuild.CloudInitConfigFile{
Datasource: &osbuild.CloudInitConfigDatasource{
Azure: &osbuild.CloudInitConfigDatasourceAzure{
ApplyNetworkConfig: false,
},
},
DatasourceList: []string{
"Azure",
},
},
},
},
{
Filename: "91-azure_datasource.cfg",
Config: osbuild.CloudInitConfigFile{
Datasource: &osbuild.CloudInitConfigDatasource{
Azure: &osbuild.CloudInitConfigDatasourceAzure{
ApplyNetworkConfig: false,
PwQuality: &osbuild.PwqualityConfStageOptions{
Config: osbuild.PwqualityConfConfig{
Minlen: common.ToPtr(6),
Minclass: common.ToPtr(3),
Dcredit: common.ToPtr(0),
Ucredit: common.ToPtr(0),
Lcredit: common.ToPtr(0),
Ocredit: common.ToPtr(0),
},
},
WAAgentConfig: &osbuild.WAAgentConfStageOptions{
Config: osbuild.WAAgentConfig{
RDFormat: common.ToPtr(false),
RDEnableSwap: common.ToPtr(false),
},
},
Grub2Config: &osbuild.GRUB2Config{
DisableRecovery: common.ToPtr(true),
DisableSubmenu: common.ToPtr(true),
Distributor: "$(sed 's, release .*$,,g' /etc/system-release)",
Terminal: []string{"serial", "console"},
Serial: "serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1",
Timeout: 10,
TimeoutStyle: osbuild.GRUB2ConfigTimeoutStyleCountdown,
},
UdevRules: &osbuild.UdevRulesStageOptions{
Filename: "/etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules",
Rules: osbuild.UdevRules{
osbuild.UdevRuleComment{
Comment: []string{
"Accelerated Networking on Azure exposes a new SRIOV interface to the VM.",
"This interface is transparently bonded to the synthetic interface,",
"so NetworkManager should just ignore any SRIOV interfaces.",
},
},
DatasourceList: []string{
"Azure",
osbuild.NewUdevRule(
[]osbuild.UdevKV{
{K: "SUBSYSTEM", O: "==", V: "net"},
{K: "DRIVERS", O: "==", V: "hv_pci"},
{K: "ACTION", O: "==", V: "add"},
{K: "ENV", A: "NM_UNMANAGED", O: "=", V: "1"},
},
),
},
},
SystemdUnit: []*osbuild.SystemdUnitStageOptions{
{
Unit: "nm-cloud-setup.service",
Dropin: "10-rh-enable-for-azure.conf",
Config: osbuild.SystemdServiceUnitDropin{
Service: &osbuild.SystemdUnitServiceSection{
Environment: []osbuild.EnvironmentVariable{{Key: "NM_CLOUD_SETUP_AZURE", Value: "yes"}},
},
},
},
},
},
PwQuality: &osbuild.PwqualityConfStageOptions{
Config: osbuild.PwqualityConfConfig{
Minlen: common.ToPtr(6),
Minclass: common.ToPtr(3),
Dcredit: common.ToPtr(0),
Ucredit: common.ToPtr(0),
Lcredit: common.ToPtr(0),
Ocredit: common.ToPtr(0),
},
},
WAAgentConfig: &osbuild.WAAgentConfStageOptions{
Config: osbuild.WAAgentConfig{
RDFormat: common.ToPtr(false),
RDEnableSwap: common.ToPtr(false),
},
},
Grub2Config: &osbuild.GRUB2Config{
DisableRecovery: common.ToPtr(true),
DisableSubmenu: common.ToPtr(true),
Distributor: "$(sed 's, release .*$,,g' /etc/system-release)",
Terminal: []string{"serial", "console"},
Serial: "serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1",
Timeout: 10,
TimeoutStyle: osbuild.GRUB2ConfigTimeoutStyleCountdown,
},
UdevRules: &osbuild.UdevRulesStageOptions{
Filename: "/etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules",
Rules: osbuild.UdevRules{
osbuild.UdevRuleComment{
Comment: []string{
"Accelerated Networking on Azure exposes a new SRIOV interface to the VM.",
"This interface is transparently bonded to the synthetic interface,",
"so NetworkManager should just ignore any SRIOV interfaces.",
},
},
osbuild.NewUdevRule(
[]osbuild.UdevKV{
{K: "SUBSYSTEM", O: "==", V: "net"},
{K: "DRIVERS", O: "==", V: "hv_pci"},
{K: "ACTION", O: "==", V: "add"},
{K: "ENV", A: "NM_UNMANAGED", O: "=", V: "1"},
},
),
},
},
SystemdUnit: []*osbuild.SystemdUnitStageOptions{
{
Unit: "nm-cloud-setup.service",
Dropin: "10-rh-enable-for-azure.conf",
Config: osbuild.SystemdServiceUnitDropin{
Service: &osbuild.SystemdUnitServiceSection{
Environment: []osbuild.EnvironmentVariable{{Key: "NM_CLOUD_SETUP_AZURE", Value: "yes"}},
},
},
},
},
DefaultTarget: common.ToPtr("multi-user.target"),
DefaultTarget: common.ToPtr("multi-user.target"),
}
if rd.IsRHEL() {
ic.GPGKeyFiles = append(ic.GPGKeyFiles, "/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release")
}
return ic
}
func sapAzureImageConfig(rd *rhel.Distribution) *distro.ImageConfig {
return sapImageConfig(rd.OsVersion()).InheritFrom(defaultAzureImageConfig(rd))
}

View file

@ -183,13 +183,14 @@ func newDistro(name string, major, minor int) *rhel.Distribution {
mkAMIImgTypeX86_64(),
)
aarch64.AddImageTypes(
&platform.Aarch64{
UEFIVendor: rd.Vendor(),
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
ec2Aarch64Platform := &platform.Aarch64{
UEFIVendor: rd.Vendor(),
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
}
aarch64.AddImageTypes(
ec2Aarch64Platform,
mkAMIImgTypeAarch64(),
)
@ -208,13 +209,8 @@ func newDistro(name string, major, minor int) *rhel.Distribution {
},
}
if rd.IsRHEL() { // RHEL-only (non-CentOS) image types
x86_64.AddImageTypes(azureX64Platform, mkAzureByosImgType(rd))
aarch64.AddImageTypes(azureAarch64Platform, mkAzureByosImgType(rd))
} else {
x86_64.AddImageTypes(azureX64Platform, mkAzureImgType())
aarch64.AddImageTypes(azureAarch64Platform, mkAzureImgType())
}
x86_64.AddImageTypes(azureX64Platform, mkAzureImgType(rd))
aarch64.AddImageTypes(azureAarch64Platform, mkAzureImgType(rd))
gceX86Platform := &platform.X86{
UEFIVendor: rd.Vendor(),
@ -258,6 +254,16 @@ func newDistro(name string, major, minor int) *rhel.Distribution {
mkImageInstallerImgType(),
)
if rd.IsRHEL() { // RHEL-only (non-CentOS) image types
x86_64.AddImageTypes(azureX64Platform, mkAzureInternalImgType(rd))
aarch64.AddImageTypes(azureAarch64Platform, mkAzureInternalImgType(rd))
x86_64.AddImageTypes(azureX64Platform, mkAzureSapInternalImgType(rd))
x86_64.AddImageTypes(ec2X86Platform, mkEc2ImgTypeX86_64(), mkEc2HaImgTypeX86_64(), mkEC2SapImgTypeX86_64(rd.OsVersion()))
aarch64.AddImageTypes(ec2Aarch64Platform, mkEC2ImgTypeAarch64())
}
rd.AddArches(x86_64, aarch64, ppc64le, s390x)
return rd
}

View file

@ -2,7 +2,6 @@ package rhel10
import (
"fmt"
"log"
"slices"
@ -27,9 +26,24 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
}
mountpoints := customizations.GetFilesystems()
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies)
partitioning, err := customizations.GetPartitioning()
if err != nil {
return nil, err
}
if err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies); err != nil {
return warnings, err
}
if len(mountpoints) > 0 && partitioning != nil {
return nil, fmt.Errorf("partitioning customizations cannot be used with custom filesystems (mountpoints)")
}
if err := blueprint.CheckDiskMountpointsPolicy(partitioning, policies.MountpointPolicies); err != nil {
return warnings, err
}
if err := partitioning.ValidateLayoutConstraints(); err != nil {
return warnings, err
}
@ -76,9 +90,7 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
}
if customizations.GetFIPS() && !common.IsBuildHostFIPSEnabled() {
w := fmt.Sprintln(common.FIPSEnabledImageWarning)
log.Print(w)
warnings = append(warnings, w)
warnings = append(warnings, fmt.Sprintln(common.FIPSEnabledImageWarning))
}
instCust, err := customizations.GetInstaller()

View file

@ -0,0 +1,174 @@
package rhel10
import (
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/rhel"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/rpmmd"
)
// sapImageConfig returns the SAP specific ImageConfig data
func sapImageConfig(osVersion string) *distro.ImageConfig {
return &distro.ImageConfig{
SELinuxConfig: &osbuild.SELinuxConfigStageOptions{
State: osbuild.SELinuxStatePermissive,
},
// RHBZ#1960617
Tuned: osbuild.NewTunedStageOptions("sap-hana"),
// RHBZ#1959979
Tmpfilesd: []*osbuild.TmpfilesdStageOptions{
osbuild.NewTmpfilesdStageOptions("sap.conf",
[]osbuild.TmpfilesdConfigLine{
{
Type: "x",
Path: "/tmp/.sap*",
},
{
Type: "x",
Path: "/tmp/.hdb*lock",
},
{
Type: "x",
Path: "/tmp/.trex*lock",
},
},
),
},
// RHBZ#1959963
PamLimitsConf: []*osbuild.PamLimitsConfStageOptions{
osbuild.NewPamLimitsConfStageOptions("99-sap.conf",
[]osbuild.PamLimitsConfigLine{
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
},
),
},
// RHBZ#1959962
Sysctld: []*osbuild.SysctldStageOptions{
osbuild.NewSysctldStageOptions("sap.conf",
[]osbuild.SysctldConfigLine{
{
Key: "kernel.pid_max",
Value: "4194304",
},
{
Key: "vm.max_map_count",
Value: "2147483647",
},
},
),
},
// E4S/EUS
DNFConfig: []*osbuild.DNFConfigStageOptions{
osbuild.NewDNFConfigStageOptions(
[]osbuild.DNFVariable{
{
Name: "releasever",
Value: osVersion,
},
},
nil,
),
},
}
}
func SapPackageSet(t *rhel.ImageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
// RHBZ#2076763
"@Server",
// SAP System Roles
// https://access.redhat.com/sites/default/files/attachments/rhel_system_roles_for_sap_1.pdf
"ansible-core",
"rhel-system-roles-sap",
// RHBZ#1959813
"bind-utils",
"nfs-utils",
"tcsh",
// RHBZ#1959955
"uuidd",
// RHBZ#1959923
"cairo",
"expect",
"graphviz",
//"gtk2", // gtk2 is not available in RHEL-10
"iptraf-ng",
"krb5-workstation",
"libaio",
"libatomic",
"libicu",
"libtool-ltdl",
"lm_sensors",
"net-tools",
"numactl",
"PackageKit-gtk3-module",
"xorg-x11-xauth",
// RHBZ#1960617
"tuned-profiles-sap-hana",
// RHBZ#1961168
"libnsl",
},
Exclude: []string{
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
},
}
}

View file

@ -115,6 +115,15 @@ func newDistro(name string, minor int) *rhel.Distribution {
ImageFormat: platform.FORMAT_RAW,
},
}
// Keep the RHEL EC2 x86_64 images before 8.9 BIOS-only for backward compatibility.
// RHEL-internal EC2 images and RHEL AMI images are kept intentionally in sync
// with regard to not supporting hybrid boot mode before RHEL version 8.9.
// The partitioning table for these reflects that and is also intentionally in sync.
if rd.IsRHEL() && common.VersionLessThan(rd.OsVersion(), "8.9") {
ec2X86Platform.UEFIVendor = ""
}
x86_64.AddImageTypes(
ec2X86Platform,
mkAmiImgTypeX86_64(),
@ -342,16 +351,6 @@ func newDistro(name string, minor int) *rhel.Distribution {
mkAzureSapRhuiImgType(rd),
)
// keep the RHEL EC2 x86_64 images before 8.9 BIOS-only for backward compatibility
if common.VersionLessThan(rd.OsVersion(), "8.9") {
ec2X86Platform = &platform.X86{
BIOS: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
}
}
// add ec2 image types to RHEL distro only
x86_64.AddImageTypes(
ec2X86Platform,

View file

@ -2,7 +2,6 @@ package rhel8
import (
"fmt"
"log"
"strings"
"slices"
@ -29,14 +28,14 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
if options.OSTree != nil {
if err := options.OSTree.Validate(); err != nil {
return nil, err
return warnings, err
}
}
if t.BootISO && t.RPMOSTree {
// ostree-based ISOs require a URL from which to pull a payload commit
if options.OSTree == nil || options.OSTree.URL == "" {
return nil, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.Name())
return warnings, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.Name())
}
if t.Name() == "edge-simplified-installer" {
@ -91,14 +90,10 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
// TODO(edge): directly error if these options are provided when rhel-9.5's time arrives
if t.Name() == "edge-commit" || t.Name() == "edge-container" {
if customizations.GetUsers() != nil {
w := fmt.Sprintf("Please note that user customizations on %q image type are deprecated and will be removed in the near future\n", t.Name())
log.Print(w)
warnings = append(warnings, w)
warnings = append(warnings, fmt.Sprintf("Please note that user customizations on %q image type are deprecated and will be removed in the near future\n", t.Name()))
}
if customizations.GetGroups() != nil {
w := fmt.Sprintf("Please note that group customizations on %q image type are deprecated and will be removed in the near future\n", t.Name())
log.Print(w)
warnings = append(warnings, w)
warnings = append(warnings, fmt.Sprintf("Please note that group customizations on %q image type are deprecated and will be removed in the near future\n", t.Name()))
}
}
@ -111,13 +106,27 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
}
mountpoints := customizations.GetFilesystems()
if mountpoints != nil && t.RPMOSTree {
return warnings, fmt.Errorf("Custom mountpoints are not supported for ostree types")
partitioning, err := customizations.GetPartitioning()
if err != nil {
return nil, err
}
if partitioning != nil {
return nil, fmt.Errorf("partitioning customizations are not supported on %s", t.Arch().Distro().Name())
}
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies)
if err != nil {
if mountpoints != nil && t.RPMOSTree {
return warnings, fmt.Errorf("custom mountpoints are not supported for ostree types")
}
if err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies); err != nil {
return warnings, err
}
if err := partitioning.ValidateLayoutConstraints(); err != nil {
return warnings, err
}
if err := blueprint.CheckDiskMountpointsPolicy(partitioning, policies.MountpointPolicies); err != nil {
return warnings, err
}
@ -171,7 +180,6 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
if customizations.GetFIPS() && !common.IsBuildHostFIPSEnabled() {
w := fmt.Sprintln(common.FIPSEnabledImageWarning)
log.Print(w)
warnings = append(warnings, w)
}

View file

@ -202,6 +202,15 @@ func newDistro(name string, major, minor int) *rhel.Distribution {
ImageFormat: platform.FORMAT_RAW,
},
}
// Keep the RHEL EC2 x86_64 images before 9.3 BIOS-only for backward compatibility.
// RHEL-internal EC2 images and RHEL AMI images are kept intentionally in sync
// with regard to not supporting hybrid boot mode before RHEL version 9.3.
// The partitioning table for these reflects that and is also intentionally in sync.
if rd.IsRHEL() && common.VersionLessThan(rd.OsVersion(), "9.3") {
ec2X86Platform.UEFIVendor = ""
}
x86_64.AddImageTypes(
ec2X86Platform,
mkAMIImgTypeX86_64(),
@ -337,16 +346,6 @@ func newDistro(name string, major, minor int) *rhel.Distribution {
x86_64.AddImageTypes(azureX64Platform, mkAzureSapInternalImgType(rd))
// keep the RHEL EC2 x86_64 images before 9.3 BIOS-only for backward compatibility
if common.VersionLessThan(rd.OsVersion(), "9.3") {
ec2X86Platform = &platform.X86{
BIOS: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
}
}
// add ec2 image types to RHEL distro only
x86_64.AddImageTypes(ec2X86Platform, mkEc2ImgTypeX86_64(), mkEc2HaImgTypeX86_64(), mkEC2SapImgTypeX86_64(rd.OsVersion()))

View file

@ -2,7 +2,6 @@ package rhel9
import (
"fmt"
"log"
"strings"
"slices"
@ -31,14 +30,14 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
if options.OSTree != nil {
if err := options.OSTree.Validate(); err != nil {
return nil, err
return warnings, err
}
}
if t.BootISO && t.RPMOSTree {
// ostree-based ISOs require a URL from which to pull a payload commit
if options.OSTree == nil || options.OSTree.URL == "" {
return nil, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.Name())
return warnings, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.Name())
}
if t.Name() == "edge-simplified-installer" {
@ -104,14 +103,10 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
// TODO(edge): directly error if these options are provided when rhel-9.5's time arrives
if t.Name() == "edge-commit" || t.Name() == "edge-container" {
if customizations.GetUsers() != nil {
w := fmt.Sprintf("Please note that user customizations on %q image type are deprecated and will be removed in the near future\n", t.Name())
log.Print(w)
warnings = append(warnings, w)
warnings = append(warnings, fmt.Sprintf("Please note that user customizations on %q image type are deprecated and will be removed in the near future\n", t.Name()))
}
if customizations.GetGroups() != nil {
w := fmt.Sprintf("Please note that group customizations on %q image type are deprecated and will be removed in the near future\n", t.Name())
log.Print(w)
warnings = append(warnings, w)
warnings = append(warnings, fmt.Sprintf("Please note that group customizations on %q image type are deprecated and will be removed in the near future\n", t.Name()))
}
}
@ -124,10 +119,13 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
}
mountpoints := customizations.GetFilesystems()
if mountpoints != nil && t.RPMOSTree && (t.Name() == "edge-container" || t.Name() == "edge-commit") {
return warnings, fmt.Errorf("Custom mountpoints are not supported for ostree types")
} else if mountpoints != nil && t.RPMOSTree && !(t.Name() == "edge-container" || t.Name() == "edge-commit") {
partitioning, err := customizations.GetPartitioning()
if err != nil {
return nil, err
}
if (mountpoints != nil || partitioning != nil) && t.RPMOSTree && (t.Name() == "edge-container" || t.Name() == "edge-commit") {
return warnings, fmt.Errorf("custom mountpoints and partitioning are not supported for ostree types")
} else if (mountpoints != nil || partitioning != nil) && t.RPMOSTree && !(t.Name() == "edge-container" || t.Name() == "edge-commit") {
//customization allowed for edge-raw-image,edge-ami,edge-vsphere,edge-simplified-installer
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.OstreeMountpointPolicies)
if err != nil {
@ -135,8 +133,19 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
}
}
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies)
if err != nil {
if len(mountpoints) > 0 && partitioning != nil {
return nil, fmt.Errorf("partitioning customizations cannot be used with custom filesystems (mountpoints)")
}
if err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies); err != nil {
return warnings, err
}
if err := blueprint.CheckDiskMountpointsPolicy(partitioning, policies.MountpointPolicies); err != nil {
return warnings, err
}
if err := partitioning.ValidateLayoutConstraints(); err != nil {
return warnings, err
}
@ -189,9 +198,7 @@ func checkOptions(t *rhel.ImageType, bp *blueprint.Blueprint, options distro.Ima
}
if customizations.GetFIPS() && !common.IsBuildHostFIPSEnabled() {
w := fmt.Sprintln(common.FIPSEnabledImageWarning)
log.Print(w)
warnings = append(warnings, w)
warnings = append(warnings, fmt.Sprintln(common.FIPSEnabledImageWarning))
}
instCust, err := customizations.GetInstaller()

View file

@ -1,6 +1,8 @@
package rhel9
import (
"strings"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/datasizes"
@ -24,6 +26,47 @@ func defaultBasePartitionTables(t *rhel.ImageType) (disk.PartitionTable, bool) {
switch t.Arch().Name() {
case arch.ARCH_X86_64.String():
// RHEL EC2 x86_64 images prior to 9.3 support only BIOS boot
if common.VersionLessThan(t.Arch().Distro().OsVersion(), "9.3") && t.IsRHEL() && (strings.HasPrefix(t.Name(), "ec2") || t.Name() == "ami") {
return disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: disk.PT_GPT,
Partitions: []disk.Partition{
{
Size: 1 * datasizes.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: bootSize,
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * datasizes.GibiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
}, true
}
return disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: disk.PT_GPT,

View file

@ -303,7 +303,7 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline {
Size: fmt.Sprintf("%d", p.PartitionTable.Size),
}))
for _, stage := range osbuild.GenMkfsStages(p.PartitionTable, filename) {
for _, stage := range osbuild.GenFsStages(p.PartitionTable, filename) {
pipeline.AddStage(stage)
}

View file

@ -111,7 +111,7 @@ func (p *CoreOSISOTree) serialize() osbuild.Pipeline {
Size: fmt.Sprintf("%d", p.PartitionTable.Size),
}))
for _, stage := range osbuild.GenMkfsStages(p.PartitionTable, filename) {
for _, stage := range osbuild.GenFsStages(p.PartitionTable, filename) {
pipeline.AddStage(stage)
}

View file

@ -110,17 +110,22 @@ func genMountsForBootupd(source string, pt *disk.PartitionTable) ([]Mount, error
case *disk.LVMVolumeGroup:
for i := range payload.LogicalVolumes {
lv := &payload.LogicalVolumes[i]
mountable, ok := lv.Payload.(disk.Mountable)
if !ok {
return nil, fmt.Errorf("expected LV payload %+[1]v to be mountable, got %[1]T", lv.Payload)
switch payload := lv.Payload.(type) {
case disk.Mountable:
mount, err := genOsbuildMount(lv.Name, payload)
if err != nil {
return nil, err
}
mount.Source = lv.Name
mounts = append(mounts, *mount)
case *disk.Swap:
// nothing to do
default:
return nil, fmt.Errorf("expected LV payload %+[1]v to be mountable or swap, got %[1]T", lv.Payload)
}
mount, err := genOsbuildMount(lv.Name, mountable)
if err != nil {
return nil, err
}
mount.Source = lv.Name
mounts = append(mounts, *mount)
}
case *disk.Swap:
// nothing to do
default:
return nil, fmt.Errorf("type %T not supported by bootupd handling yet", part.Payload)
}

View file

@ -1,9 +1,5 @@
package osbuild
import (
"github.com/osbuild/images/pkg/disk"
)
type BtrfsSubVolOptions struct {
Subvolumes []BtrfsSubVol `json:"subvolumes"`
}
@ -22,52 +18,3 @@ func NewBtrfsSubVol(options *BtrfsSubVolOptions, devices *map[string]Device, mou
Mounts: *mounts,
}
}
func GenBtrfsSubVolStage(filename string, pt *disk.PartitionTable) *Stage {
var subvolumes []BtrfsSubVol
genStage := func(mnt disk.Mountable, path []disk.Entity) error {
if mnt.GetFSType() != "btrfs" {
return nil
}
btrfs := mnt.(*disk.BtrfsSubvolume)
subvolumes = append(subvolumes, BtrfsSubVol{Name: "/" + btrfs.Name})
return nil
}
_ = pt.ForEachMountable(genStage)
if len(subvolumes) == 0 {
return nil
}
devices, mounts := genBtrfsMountDevices(filename, pt)
return NewBtrfsSubVol(&BtrfsSubVolOptions{subvolumes}, devices, mounts)
}
func genBtrfsMountDevices(filename string, pt *disk.PartitionTable) (*map[string]Device, *[]Mount) {
devices := make(map[string]Device, len(pt.Partitions))
mounts := make([]Mount, 0, len(pt.Partitions))
genMounts := func(ent disk.Entity, path []disk.Entity) error {
if _, isBtrfs := ent.(*disk.Btrfs); !isBtrfs {
return nil
}
stageDevices, name := getDevices(path, filename, false)
mounts = append(mounts, *NewBtrfsMount(name, name, "/", "", ""))
// update devices map with new elements from stageDevices
for devName := range stageDevices {
devices[devName] = stageDevices[devName]
}
return nil
}
_ = pt.ForEachEntity(genMounts)
return &devices, &mounts
}

View file

@ -162,6 +162,8 @@ func deviceName(p disk.Entity) string {
return payload.Name
case *disk.Btrfs:
return "btrfs-" + payload.UUID[:4]
case *disk.Swap:
return "swap-" + payload.UUID[:4]
}
panic(fmt.Sprintf("unsupported device type in deviceName: '%T'", p))
}

View file

@ -99,15 +99,11 @@ func GenImagePrepareStages(pt *disk.PartitionTable, filename string, partTool Pa
s := GenDeviceCreationStages(pt, filename)
stages = append(stages, s...)
// Generate all the filesystems on partitons and devices
s = GenMkfsStages(pt, filename)
// Generate all the filesystems, subvolumes, and swap areas on partitons
// and devices
s = GenFsStages(pt, filename)
stages = append(stages, s...)
subvolStage := GenBtrfsSubVolStage(filename, pt)
if subvolStage != nil {
stages = append(stages, subvolStage)
}
return stages
}

View file

@ -58,13 +58,13 @@ func (options *FSTabStageOptions) AddFilesystem(id string, vfsType string, path
func NewFSTabStageOptions(pt *disk.PartitionTable) (*FSTabStageOptions, error) {
var options FSTabStageOptions
genOption := func(mnt disk.Mountable, path []disk.Entity) error {
genOption := func(mnt disk.FSTabEntity, path []disk.Entity) error {
fsSpec := mnt.GetFSSpec()
fsOptions, err := mnt.GetFSTabOptions()
if err != nil {
return err
}
options.AddFilesystem(fsSpec.UUID, mnt.GetFSType(), mnt.GetMountpoint(), fsOptions.MntOps, fsOptions.Freq, fsOptions.PassNo)
options.AddFilesystem(fsSpec.UUID, mnt.GetFSType(), mnt.GetFSFile(), fsOptions.MntOps, fsOptions.Freq, fsOptions.PassNo)
return nil
}
@ -72,7 +72,7 @@ func NewFSTabStageOptions(pt *disk.PartitionTable) (*FSTabStageOptions, error) {
return fmt.Sprintf("%d%s", fs.PassNo, fs.Path)
}
err := pt.ForEachMountable(genOption)
err := pt.ForEachFSTabEntity(genOption)
if err != nil {
return nil, err
}

View file

@ -7,81 +7,96 @@ import (
"github.com/osbuild/images/pkg/disk"
)
// GenMkfsStages generates a list of org.mkfs.* stages based on a
// partition table description for a single device node
// filename is the path to the underlying image file (to be used as a source for the loopback device)
func GenMkfsStages(pt *disk.PartitionTable, filename string) []*Stage {
// GenFsStages generates a list of stages that create the filesystem and other
// related entities. Specifically, it creates stages for:
// - org.osbuild.mkfs.*: for all filesystems and btrfs volumes
// - org.osbuild.btrfs.subvol: for all btrfs subvolumes
// - org.osbuild.mkswap: for swap areas
func GenFsStages(pt *disk.PartitionTable, filename string) []*Stage {
stages := make([]*Stage, 0, len(pt.Partitions))
processedBtrfsPartitions := make(map[string]bool)
genStage := func(mnt disk.Mountable, path []disk.Entity) error {
t := mnt.GetFSType()
var stage *Stage
genStage := func(ent disk.Entity, path []disk.Entity) error {
switch e := ent.(type) {
case *disk.Filesystem:
// TODO: extract last device renaming into helper
stageDevices, lastName := getDevices(path, filename, true)
stageDevices, lastName := getDevices(path, filename, true)
// The last device in the chain must be named "device", because that's
// the device that mkfs stages run on. See the stage schemas for
// reference.
lastDevice := stageDevices[lastName]
delete(stageDevices, lastName)
stageDevices["device"] = lastDevice
// The last device in the chain must be named "device", because that's the device that mkfs stages run on.
// See their schema for reference.
lastDevice := stageDevices[lastName]
delete(stageDevices, lastName)
stageDevices["device"] = lastDevice
switch e.GetFSType() {
case "xfs":
options := &MkfsXfsStageOptions{
UUID: e.UUID,
Label: e.Label,
}
stages = append(stages, NewMkfsXfsStage(options, stageDevices))
case "vfat":
options := &MkfsFATStageOptions{
VolID: strings.Replace(e.UUID, "-", "", -1),
}
stages = append(stages, NewMkfsFATStage(options, stageDevices))
case "ext4":
options := &MkfsExt4StageOptions{
UUID: e.UUID,
Label: e.Label,
}
stages = append(stages, NewMkfsExt4Stage(options, stageDevices))
default:
panic(fmt.Sprintf("unknown fs type: %s", e.GetFSType()))
}
case *disk.Btrfs:
stageDevices, lastName := getDevices(path, filename, true)
fsSpec := mnt.GetFSSpec()
switch t {
case "xfs":
options := &MkfsXfsStageOptions{
UUID: fsSpec.UUID,
Label: fsSpec.Label,
}
stage = NewMkfsXfsStage(options, stageDevices)
case "vfat":
options := &MkfsFATStageOptions{
VolID: strings.Replace(fsSpec.UUID, "-", "", -1),
}
stage = NewMkfsFATStage(options, stageDevices)
case "btrfs":
// the disk library allows only subvolumes as Mountable, so we need to find the underlying btrfs partition
// and mkfs it
btrfsPart := findBtrfsPartition(path)
if btrfsPart == nil {
panic(fmt.Sprintf("found btrfs subvolume without btrfs partition: %s", mnt.GetMountpoint()))
}
// btrfs partitions can be shared between multiple subvolumes, so we need to make sure we only create
// one
if processedBtrfsPartitions[btrfsPart.UUID] {
return nil
}
processedBtrfsPartitions[btrfsPart.UUID] = true
// The last device in the chain must be named "device", because that's
// the device that mkfs stages run on. See the stage schemas for
// reference.
lastDevice := stageDevices[lastName]
delete(stageDevices, lastName)
stageDevices["device"] = lastDevice
options := &MkfsBtrfsStageOptions{
UUID: btrfsPart.UUID,
Label: btrfsPart.Label,
UUID: e.UUID,
Label: e.Label,
}
stage = NewMkfsBtrfsStage(options, stageDevices)
case "ext4":
options := &MkfsExt4StageOptions{
UUID: fsSpec.UUID,
Label: fsSpec.Label,
stages = append(stages, NewMkfsBtrfsStage(options, stageDevices))
// Handle subvolumes here directly instead of collecting them in
// their own case, since we already have access to the parent volume.
subvolumes := make([]BtrfsSubVol, len(e.Subvolumes))
for idx, subvol := range e.Subvolumes {
subvolumes[idx] = BtrfsSubVol{Name: "/" + strings.TrimLeft(subvol.Name, "/")}
}
stage = NewMkfsExt4Stage(options, stageDevices)
default:
panic("unknown fs type " + t)
}
stages = append(stages, stage)
// Subvolume creation does not require locking the device, nor does
// it require the renaming to "device", but let's reuse the volume
// device for convenience
mount := *NewBtrfsMount("volume", "device", "/", "", "")
stages = append(stages, NewBtrfsSubVol(&BtrfsSubVolOptions{subvolumes}, &stageDevices, &[]Mount{mount}))
case *disk.Swap:
// TODO: extract last device renaming into helper
stageDevices, lastName := getDevices(path, filename, true)
// The last device in the chain must be named "device", because that's
// the device that the mkswap stage runs on. See the stage schema
// for reference.
lastDevice := stageDevices[lastName]
delete(stageDevices, lastName)
stageDevices["device"] = lastDevice
options := &MkswapStageOptions{
UUID: e.UUID,
Label: e.Label,
}
stages = append(stages, NewMkswapStage(options, stageDevices))
}
return nil
}
_ = pt.ForEachMountable(genStage) // genStage always returns nil
_ = pt.ForEachEntity(genStage) // genStage always returns nil
return stages
}
func findBtrfsPartition(path []disk.Entity) *disk.Btrfs {
for _, e := range path {
if btrfsPartition, ok := e.(*disk.Btrfs); ok {
return btrfsPartition
}
}
return nil
}

View file

@ -0,0 +1,16 @@
package osbuild
type MkswapStageOptions struct {
UUID string `json:"uuid"`
Label string `json:"label,omitempty"`
}
func (MkswapStageOptions) isStageOptions() {}
func NewMkswapStage(options *MkswapStageOptions, devices map[string]Device) *Stage {
return &Stage{
Type: "org.osbuild.mkswap",
Options: options,
Devices: devices,
}
}

180
vendor/github.com/osbuild/images/pkg/osbuild/monitor.go generated vendored Normal file
View file

@ -0,0 +1,180 @@
package osbuild
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io"
"strings"
"time"
)
// Status is a high level aggregation of the low-level osbuild monitor
// messages. It is more structured and meant to be used by UI frontends.
//
// this is intentionally minimal at the beginning until we figure
// out the best API, exposing the jsonseq direct feels too messy
// and based on what we learn here we may consider tweaking
// the osbuild progress
type Status struct {
// Trace contains a single log line, usually very low-level or
// stage output but useful for e.g. bug reporting. Should in
// general not be displayed to the user but the concatenation
// of all "trace" lines should give the same information as
// running osbuild on a terminal
Trace string
// Message contains a high level user-visible message about
// e.g. a startus change
Message string
// Progress contains the current progress.
Progress *Progress
// Timestamp contains the timestamp the message was recieved in
Timestamp time.Time
}
// Progress provides progress information from an osbuild build.
// Each progress can have an arbitrary number of sub-progress information
//
// Note while those can be nested arbitrarly deep in practise
// we are at 2 levels currently:
// 1. overall pipeline progress
// 2. stages inside each pipeline
//
// we might get
// 3. stage progress (e.g. rpm install progress)
//
// in the future
type Progress struct {
// A human readable message about what is going on
Message string
// The amount of work already done
Done int
// The total amount of work for this (sub)progress
Total int
SubProgress *Progress
}
// NewStatusScanner returns a StatusScanner that can parse osbuild
// jsonseq monitor status messages
func NewStatusScanner(r io.Reader) *StatusScanner {
return &StatusScanner{
scanner: bufio.NewScanner(r),
contextMap: make(map[string]*contextJSON),
stageContextMap: make(map[string]*stageContextJSON),
}
}
// StatusScanner scan scan the osbuild jsonseq monitor output
type StatusScanner struct {
scanner *bufio.Scanner
contextMap map[string]*contextJSON
stageContextMap map[string]*stageContextJSON
}
// Status returns a single status struct from the scanner or nil
// if the end of the status reporting is reached.
func (sr *StatusScanner) Status() (*Status, error) {
if !sr.scanner.Scan() {
return nil, sr.scanner.Err()
}
var status statusJSON
line := sr.scanner.Bytes()
line = bytes.Trim(line, "\x1e")
if err := json.Unmarshal(line, &status); err != nil {
return nil, fmt.Errorf("cannot scan line %q: %w", line, err)
}
// keep track of the context
id := status.Context.ID
context := sr.contextMap[id]
if context == nil {
sr.contextMap[id] = &status.Context
context = &status.Context
}
ts := time.UnixMilli(int64(status.Timestamp * 1000))
pipelineName := context.Pipeline.Name
var trace, msg string
// This is a convention, "osbuild.montior" sends the high level
// status, the other messages contain low-level stdout/stderr
// output from individual stages like "org.osbuild.rpm".
if context.Origin == "osbuild.monitor" {
msg = strings.TrimSpace(status.Message)
} else {
trace = strings.TrimSpace(status.Message)
}
st := &Status{
Trace: trace,
Message: msg,
Progress: &Progress{
Done: status.Progress.Done,
Total: status.Progress.Total,
Message: fmt.Sprintf("Pipeline %s", pipelineName),
},
Timestamp: ts,
}
// add subprogress
stageID := context.Pipeline.Stage.ID
stageContext := sr.stageContextMap[stageID]
if stageContext == nil {
sr.stageContextMap[id] = &context.Pipeline.Stage
stageContext = &context.Pipeline.Stage
}
stageName := fmt.Sprintf("Stage %s", stageContext.Name)
prog := st.Progress
for subProg := status.Progress.SubProgress; subProg != nil; subProg = subProg.SubProgress {
prog.SubProgress = &Progress{
Done: subProg.Done,
Total: subProg.Total,
Message: stageName,
}
prog = prog.SubProgress
}
return st, nil
}
// statusJSON is a single status entry from the osbuild monitor
type statusJSON struct {
Context contextJSON `json:"context"`
Progress progressJSON `json:"progress"`
// Add "Result" here once
// https://github.com/osbuild/osbuild/pull/1831 is merged
Message string `json:"message"`
Timestamp float64 `json:"timestamp"`
}
// contextJSON is the context for which a status is given. Once a context
// was sent to the user from then on it is only referenced by the ID
type contextJSON struct {
Origin string `json:"origin"`
ID string `json:"id"`
Pipeline struct {
ID string `json:"id"`
Name string `json:"name"`
Stage stageContextJSON `json:"stage"`
} `json:"pipeline"`
}
type stageContextJSON struct {
Name string `json:"name"`
ID string `json:"id"`
}
// progress is the progress information associcated with a given status.
// The details about nesting are the same as for "Progress" above.
type progressJSON struct {
Name string `json:"name"`
Total int `json:"total"`
Done int `json:"done"`
SubProgress *progressJSON `json:"progress"`
}

View file

@ -75,7 +75,7 @@ func LoadAllRepositoriesFromFS(confPaths []fs.FS) (rpmmd.DistrosRepoConfigs, err
return nil, err
}
logrus.Infof("Loaded repository configuration file: %s", configFile)
logrus.Infof("Loaded repository configuration file: %s", fileEntry.Name())
distrosRepoConfigs[distro] = distroRepos
}

2
vendor/modules.txt vendored
View file

@ -1022,7 +1022,7 @@ github.com/oracle/oci-go-sdk/v54/identity
github.com/oracle/oci-go-sdk/v54/objectstorage
github.com/oracle/oci-go-sdk/v54/objectstorage/transfer
github.com/oracle/oci-go-sdk/v54/workrequests
# github.com/osbuild/images v0.102.0
# github.com/osbuild/images v0.105.0
## explicit; go 1.21.0
github.com/osbuild/images/internal/common
github.com/osbuild/images/internal/environment