go.mod: update osbuild/images to v0.74.0

This commit is contained in:
Gianluca Zuccarelli 2024-08-06 15:20:12 +01:00 committed by Sanne Raymaekers
parent 3789ff4ce8
commit c9972f7da8
327 changed files with 8341 additions and 12785 deletions

View file

@ -3,7 +3,10 @@ package blueprint
import (
"fmt"
"reflect"
"slices"
"strings"
"github.com/osbuild/images/pkg/customizations/anaconda"
)
type Customizations struct {
@ -27,6 +30,7 @@ type Customizations struct {
FIPS *bool `json:"fips,omitempty" toml:"fips,omitempty"`
ContainersStorage *ContainerStorageCustomization `json:"containers-storage,omitempty" toml:"containers-storage,omitempty"`
Installer *InstallerCustomization `json:"installer,omitempty" toml:"installer,omitempty"`
RPM *RPMCustomization `json:"rpm,omitempty" toml:"rpm,omitempty"`
}
type IgnitionCustomization struct {
@ -114,10 +118,10 @@ type ServicesCustomization struct {
}
type OpenSCAPCustomization struct {
DataStream string `json:"datastream,omitempty" toml:"datastream,omitempty"`
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
Tailoring *OpenSCAPTailoringCustomizations `json:"tailoring,omitempty" toml:"tailoring,omitempty"`
XMLTailoring *OpenSCAPXMLTailoringCustomizations `json:"xml_tailoring,omitempty" toml:"xml_tailoring,omitempty"`
DataStream string `json:"datastream,omitempty" toml:"datastream,omitempty"`
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
Tailoring *OpenSCAPTailoringCustomizations `json:"tailoring,omitempty" toml:"tailoring,omitempty"`
JSONTailoring *OpenSCAPJSONTailoringCustomizations `json:"json_tailoring,omitempty" toml:"json_tailoring,omitempty"`
}
type OpenSCAPTailoringCustomizations struct {
@ -125,7 +129,7 @@ type OpenSCAPTailoringCustomizations struct {
Unselected []string `json:"unselected,omitempty" toml:"unselected,omitempty"`
}
type OpenSCAPXMLTailoringCustomizations struct {
type OpenSCAPJSONTailoringCustomizations struct {
ProfileID string `json:"profile_id,omitempty" toml:"profile_id,omitempty"`
Filepath string `json:"filepath,omitempty" toml:"filepath,omitempty"`
}
@ -403,12 +407,27 @@ func (c *Customizations) GetInstaller() (*InstallerCustomization, error) {
// when the user adds their own kickstart content
if c.Installer.Kickstart != nil && len(c.Installer.Kickstart.Contents) > 0 {
if c.Installer.Unattended {
return nil, fmt.Errorf("installer.unattended is not allowed when adding custom kickstart contents")
return nil, fmt.Errorf("installer.unattended is not supported when adding custom kickstart contents")
}
if len(c.Installer.SudoNopasswd) > 0 {
return nil, fmt.Errorf("installer.sudo-nopasswd is not allowed when adding custom kickstart contents")
return nil, fmt.Errorf("installer.sudo-nopasswd is not supported when adding custom kickstart contents")
}
}
// Disabling the user module isn't supported when users or groups are
// defined
if c.Installer.Modules != nil &&
slices.Contains(c.Installer.Modules.Disable, anaconda.ModuleUsers) &&
len(c.User)+len(c.Group) > 0 {
return nil, fmt.Errorf("blueprint contains user or group customizations but disables the required Users Anaconda module")
}
return c.Installer, nil
}
func (c *Customizations) GetRPM() *RPMCustomization {
if c == nil {
return nil
}
return c.RPM
}

View file

@ -10,7 +10,7 @@ import (
type FilesystemCustomization struct {
Mountpoint string `json:"mountpoint,omitempty" toml:"mountpoint,omitempty"`
MinSize uint64 `json:"minsize,omitempty" toml:"size,omitempty"`
MinSize uint64 `json:"minsize,omitempty" toml:"minsize,omitempty"`
}
func (fsc *FilesystemCustomization) UnmarshalTOML(data interface{}) error {

View file

@ -1,11 +1,17 @@
package blueprint
type InstallerCustomization struct {
Unattended bool `json:"unattended,omitempty" toml:"unattended,omitempty"`
SudoNopasswd []string `json:"sudo-nopasswd,omitempty" toml:"sudo-nopasswd,omitempty"`
Kickstart *Kickstart `json:"kickstart,omitempty" toml:"kickstart,omitempty"`
Unattended bool `json:"unattended,omitempty" toml:"unattended,omitempty"`
SudoNopasswd []string `json:"sudo-nopasswd,omitempty" toml:"sudo-nopasswd,omitempty"`
Kickstart *Kickstart `json:"kickstart,omitempty" toml:"kickstart,omitempty"`
Modules *AnacondaModules `json:"modules,omitempty" toml:"modules,omitempty"`
}
type Kickstart struct {
Contents string `json:"contents" toml:"contents"`
}
type AnacondaModules struct {
Enable []string `json:"enable,omitempty" toml:"enable,omitempty"`
Disable []string `json:"disable,omitempty" toml:"disable,omitempty"`
}

View file

@ -0,0 +1,10 @@
package blueprint
type RPMImportKeys struct {
// File paths in the image to import keys from
Files []string `json:"files,omitempty" toml:"files,omitempty"`
}
type RPMCustomization struct {
ImportKeys *RPMImportKeys `json:"import_keys,omitempty" toml:"import_keys,omitempty"`
}

View file

@ -0,0 +1,14 @@
package anaconda
const (
ModuleLocalization = "org.fedoraproject.Anaconda.Modules.Localization"
ModuleNetwork = "org.fedoraproject.Anaconda.Modules.Network"
ModulePayloads = "org.fedoraproject.Anaconda.Modules.Payloads"
ModuleRuntime = "org.fedoraproject.Anaconda.Modules.Runtime"
ModuleSecurity = "org.fedoraproject.Anaconda.Modules.Security"
ModuleServices = "org.fedoraproject.Anaconda.Modules.Services"
ModuleStorage = "org.fedoraproject.Anaconda.Modules.Storage"
ModuleSubscription = "org.fedoraproject.Anaconda.Modules.Subscription"
ModuleTimezone = "org.fedoraproject.Anaconda.Modules.Timezone"
ModuleUsers = "org.fedoraproject.Anaconda.Modules.Users"
)

View file

@ -50,81 +50,75 @@ const (
type RemediationConfig struct {
Datastream string
ProfileID string
TailoringPath string
CompressionEnabled bool
TailoringConfig *TailoringConfig
}
type TailoringConfig struct {
RemediationConfig
TailoredProfileID string
JSONFilepath string
TailoringPath string
Selected []string
Unselected []string
}
func NewConfigs(oscapConfig blueprint.OpenSCAPCustomization, defaultDatastream *string) (*RemediationConfig, *TailoringConfig, error) {
func (rc *RemediationConfig) addTailoringConfigs(tc blueprint.OpenSCAPTailoringCustomizations) (*RemediationConfig, error) {
rc.TailoringConfig = &TailoringConfig{
TailoredProfileID: fmt.Sprintf("%s_osbuild_tailoring", rc.ProfileID),
Selected: tc.Selected,
Unselected: tc.Unselected,
TailoringPath: filepath.Join(DataDir, "tailoring.xml"),
}
return rc, nil
}
func (rc *RemediationConfig) addJsonConfigs(json blueprint.OpenSCAPJSONTailoringCustomizations) (*RemediationConfig, error) {
if json.Filepath == "" {
return nil, fmt.Errorf("Filepath to an JSON tailoring file is required")
}
if json.ProfileID == "" {
return nil, fmt.Errorf("Tailoring profile ID is required for an JSON tailoring file")
}
rc.TailoringConfig = &TailoringConfig{
JSONFilepath: json.Filepath,
TailoredProfileID: json.ProfileID,
TailoringPath: filepath.Join(DataDir, "tailoring.xml"),
}
return rc, nil
}
func NewConfigs(oscapConfig blueprint.OpenSCAPCustomization, defaultDatastream *string) (*RemediationConfig, error) {
var datastream = oscapConfig.DataStream
if datastream == "" {
if defaultDatastream == nil {
return nil, nil, fmt.Errorf("No OSCAP datastream specified and the distro does not have any default set")
return nil, fmt.Errorf("No OSCAP datastream specified and the distro does not have any default set")
}
datastream = *defaultDatastream
}
remediationConfig := &RemediationConfig{
tc := oscapConfig.Tailoring
json := oscapConfig.JSONTailoring
remediationConfig := RemediationConfig{
Datastream: datastream,
ProfileID: oscapConfig.ProfileID,
CompressionEnabled: true,
}
if oscapConfig.XMLTailoring != nil && oscapConfig.Tailoring != nil {
return nil, nil, fmt.Errorf("Either XML tailoring file and profile ID must be set or custom rules (selected/unselected), not both")
switch {
case tc != nil && json != nil:
return nil, fmt.Errorf("Multiple tailoring types set, only one type can be chosen (JSON/Override rules)")
case tc != nil:
return remediationConfig.addTailoringConfigs(*tc)
case json != nil:
return remediationConfig.addJsonConfigs(*json)
default:
return &remediationConfig, nil
}
if xmlConfigs := oscapConfig.XMLTailoring; xmlConfigs != nil {
if xmlConfigs.Filepath == "" {
return nil, nil, fmt.Errorf("Filepath to an XML tailoring file is required")
}
if xmlConfigs.ProfileID == "" {
return nil, nil, fmt.Errorf("Tailoring profile ID is required for an XML tailoring file")
}
remediationConfig.ProfileID = xmlConfigs.ProfileID
remediationConfig.TailoringPath = xmlConfigs.Filepath
// since the XML tailoring file has already been provided
// we don't need the autotailor stage and the config can
// be left empty and we can just return the `remediationConfig`
return remediationConfig, nil, nil
}
tc := oscapConfig.Tailoring
if tc == nil {
return remediationConfig, nil, nil
}
tailoringPath := filepath.Join(DataDir, "tailoring.xml")
tailoredProfileID := fmt.Sprintf("%s_osbuild_tailoring", remediationConfig.ProfileID)
tailoringConfig := &TailoringConfig{
RemediationConfig: RemediationConfig{
ProfileID: remediationConfig.ProfileID,
TailoringPath: tailoringPath,
Datastream: datastream,
},
TailoredProfileID: tailoredProfileID,
Selected: tc.Selected,
Unselected: tc.Unselected,
}
// the reason for changing the remediation config profile
// after we create the tailoring configs is that the tailoring
// config needs to know about the original base profile id, but
// the remediation config needs to know the updated profile id.
remediationConfig.ProfileID = tailoredProfileID
remediationConfig.TailoringPath = tailoringPath
return remediationConfig, tailoringConfig, nil
}
func DefaultFedoraDatastream() string {

View file

@ -92,6 +92,24 @@ func (b *Btrfs) GenUUID(rng *rand.Rand) {
}
}
func (b *Btrfs) MetadataSize() uint64 {
return 0
}
func (b *Btrfs) minSize(size uint64) uint64 {
var subvolsum uint64
for _, sv := range b.Subvolumes {
subvolsum += sv.Size
}
minSize := subvolsum + b.MetadataSize()
if minSize > size {
size = minSize
}
return b.AlignUp(size)
}
type BtrfsSubvolume struct {
Name string
Size uint64

View file

@ -1,19 +1,20 @@
// Disk package contains abstract data-types to define disk-related entities.
// Package disk contains data types and functions to define and modify
// disk-related and partition-table-related entities.
//
// The disk package is a collection of interfaces and structs that can be used
// to represent an disk image with its layout. Various concrete types, such as
// to represent a disk image with its layout. Various concrete types, such as
// PartitionTable, Partition and Filesystem types are defined to model a given
// disk layout. These implement a collection of interfaces that can be used to
// navigate and operate on the various possible combinations of entities in a
// generic way. The entity data model is very generic so that it can represent
// all possible layouts, which can be arbitrarily complex, since technologies
// like logical volume management, LUKS2 container and file systems, that can
// have sub-volumes, allow for complex layouts.
// like logical volume management, LUKS2 containers and file systems, that can
// have sub-volumes, allow for complex and nested layouts.
//
// Entity and Container are the two main interfaces that are used to model the
// tree structure of a disk image layout. The other entity interfaces, such as
// Sizeable and Mountable, then describe various properties and capabilities
// of a given entity.
package disk
import (
@ -31,6 +32,9 @@ const (
// Default sector size in bytes
DefaultSectorSize = 512
// Default grain size in bytes. The grain controls how sizes of certain
// entities are rounded. For example, by default, partition sizes are
// rounded to the next MiB.
DefaultGrainBytes = uint64(1048576) // 1 MiB
// UUIDs
@ -63,6 +67,7 @@ type Entity interface {
Clone() Entity
}
// PayloadEntity is an entity that can be used as a Payload for a Container.
type PayloadEntity interface {
Entity
@ -138,6 +143,16 @@ type VolumeContainer interface {
// bytes), i.e. the storage space that needs to be reserved for
// the container itself, in contrast to the data it contains.
MetadataSize() uint64
// minSize returns the size for the VolumeContainer that is either the
// provided desired size value or the sum of all children if that is
// larger. It will also add any space required for metadata. The returned
// value should, at minimum, be large enough to fit all the children, their
// metadata, and the VolumeContainer's metadata. In other words, the
// VolumeContainer's size, or its parent size, will be able to hold the
// VolumeContainer if it is created with the exact size returned by the
// function.
minSize(size uint64) uint64
}
// FSSpec for a filesystem (UUID and Label); the first field of fstab(5)
@ -155,7 +170,7 @@ type FSTabOptions struct {
PassNo uint64
}
// ReadOnly returns true is the filesystem is mounted read-only
// ReadOnly returns true if the filesystem is mounted read-only.
func (o FSTabOptions) ReadOnly() bool {
opts := strings.Split(o.MntOps, ",")
@ -180,8 +195,8 @@ func newRandomUUIDFromReader(r io.Reader) (uuid.UUID, error) {
return id, nil
}
// NewVolIDFromRand creates a random 32 bit hex string to use as a
// volume ID for FAT filesystems
// NewVolIDFromRand creates a random 32 bit hex string to use as a volume ID
// for FAT filesystems.
func NewVolIDFromRand(r *rand.Rand) string {
volid := make([]byte, 4)
len, _ := r.Read(volid)

View file

@ -88,6 +88,11 @@ func (fs *Filesystem) GetFSTabOptions() FSTabOptions {
}
func (fs *Filesystem) GenUUID(rng *rand.Rand) {
if fs.Type == "vfat" && fs.UUID == "" {
// vfat has no uuids, it has "serial numbers" (volume IDs)
fs.UUID = NewVolIDFromRand(rng)
return
}
if fs.UUID == "" {
fs.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
}

View file

@ -10,17 +10,29 @@ import (
"github.com/osbuild/images/internal/common"
)
// Argon2id defines parameters for the key derivation function for LUKS.
type Argon2id struct {
Iterations uint
Memory uint
// Number of iterations to perform.
Iterations uint
// Amount of memory to use (in KiB).
Memory uint
// Degree of parallelism (i.e. number of threads).
Parallelism uint
}
// ClevisBind defines parameters for binding a LUKS device with a given policy.
type ClevisBind struct {
Pin string
Policy string
Pin string
Policy string
// If enabled, the passphrase will be removed from the LUKS device at the
// end of the build (using the org.osbuild.luks2.remove-key stage).
RemovePassphrase bool
}
// LUKSContainer represents a LUKS encrypted volume.
type LUKSContainer struct {
Passphrase string
UUID string
@ -29,9 +41,10 @@ type LUKSContainer struct {
Subsystem string
SectorSize uint64
// password-based key derivation function
// The password-based key derivation function's parameters.
PBKDF Argon2id
// Parameters for binding the LUKS device.
Clevis *ClevisBind
Payload Entity
@ -109,3 +122,16 @@ func (lc *LUKSContainer) MetadataSize() uint64 {
// 16 MiB is the default size for the LUKS2 header
return 16 * common.MiB
}
func (lc *LUKSContainer) minSize(size uint64) uint64 {
// since a LUKS container can contain pretty much any payload, but we only
// care about the ones that have a size, or contain children with sizes
minSize := lc.MetadataSize()
switch payload := lc.Payload.(type) {
case VolumeContainer:
minSize += payload.minSize(size)
case Sizeable:
minSize += payload.GetSize()
}
return minSize
}

View file

@ -150,6 +150,20 @@ func (vg *LVMVolumeGroup) MetadataSize() uint64 {
return 1 * common.MiB
}
func (vg *LVMVolumeGroup) minSize(size uint64) uint64 {
var lvsum uint64
for _, lv := range vg.LogicalVolumes {
lvsum += lv.Size
}
minSize := lvsum + vg.MetadataSize()
if minSize > size {
size = minSize
}
return vg.AlignUp(size)
}
type LVMLogicalVolume struct {
Name string
Size uint64

View file

@ -59,6 +59,19 @@ func (p *Partition) GetChild(n uint) Entity {
return p.Payload
}
// fitTo resizes a partition to be either the given the size or the size of its
// payload if that is larger. The payload can be larger if it is a container
// with sized children.
func (p *Partition) fitTo(size uint64) {
payload, isVC := p.Payload.(VolumeContainer)
if isVC {
if payloadMinSize := payload.minSize(size); payloadMinSize > size {
size = payloadMinSize
}
}
p.Size = size
}
func (p *Partition) GetSize() uint64 {
return p.Size
}

View file

@ -43,6 +43,60 @@ const (
DefaultPartitioningMode PartitioningMode = ""
)
// NewPartitionTable takes an existing base partition table and some parameters
// and returns a new version of the base table modified to satisfy the
// parameters.
//
// Mountpoints: New filesystems and minimum partition sizes are defined in
// mountpoints. By default, if new mountpoints are created, a partition table is
// automatically converted to LVM (see Partitioning modes below).
//
// Image size: The minimum size of the partition table, which in turn will be
// the size of the disk image. The final size of the image will either be the
// value of the imageSize argument or the sum of all partitions and their
// associated metadata, whichever is larger.
//
// Partitioning modes: The mode controls how the partition table is modified.
//
// - Raw will not convert any partition to LVM or Btrfs.
//
// - LVM will convert the partition that contains the root mountpoint '/' to an
// LVM Volume Group and create a root Logical Volume. Any extra mountpoints,
// except /boot, will be added to the Volume Group as new Logical Volumes.
//
// - Btrfs will convert the partition that contains the root mountpoint '/' to
// a Btrfs volume and create a root subvolume. Any extra mountpoints, except
// /boot, will be added to the Btrfs volume as new Btrfs subvolumes.
//
// - AutoLVM is the default mode and will convert a raw partition table to an
// LVM-based one if and only if new mountpoints are added.
//
// Directory sizes: The requiredSizes argument defines a map of minimum sizes
// for specific directories. These indirectly control the minimum sizes of
// partitions. A directory with a required size will set the minimum size of
// the partition with the mountpoint that contains the directory. Additional
// directory requirements are additive, meaning the minimum size for a
// mountpoint's partition is the sum of all the required directory sizes it
// will contain. By default, if no requiredSizes are provided, the new
// partition table will require at least 1 GiB for '/' and 2 GiB for '/usr'. In
// most cases, this translates to a requirement of 3 GiB for the root
// partition, Logical Volume, or Btrfs subvolume.
//
// General principles:
//
// Desired sizes for partitions, partition tables, volumes, directories, etc,
// are always treated as minimum sizes. This means that very often the full
// disk image size is larger than the size of the sum of the partitions due to
// metadata. The function considers that the size of volumes have higher
// priority than the size of the disk.
//
// The partition or volume container that contains '/' is always last in the
// partition table layout.
//
// In the case of raw partitioning (no LVM and no Btrfs), the partition
// containing the root filesystem is grown to fill any left over space on the
// partition table. Logical Volumes are not grown to fill the space in the
// Volume Group since they are trivial to grow on a live system.
func NewPartitionTable(basePT *PartitionTable, mountpoints []blueprint.FilesystemCustomization, imageSize uint64, mode PartitioningMode, requiredSizes map[string]uint64, rng *rand.Rand) (*PartitionTable, error) {
newPT := basePT.Clone().(*PartitionTable)
@ -143,8 +197,8 @@ func (pt *PartitionTable) Clone() Entity {
return clone
}
// AlignUp will align the given bytes to next aligned grain if not already
// aligned
// AlignUp will round up the given size value to the default grain if not
// already aligned.
func (pt *PartitionTable) AlignUp(size uint64) uint64 {
grain := DefaultGrainBytes
if size%grain == 0 {
@ -368,10 +422,10 @@ func (pt *PartitionTable) HeaderSize() uint64 {
return header
}
// Apply filesystem filesystem customization to the partiton table. If create is false
// will only apply customizations to existing partitions and return unhandled, i.e new
// ones. An error can only occur if create is set. Conversely, it will only return non
// empty list of new mountpoints if create is false.
// Apply filesystem customization to the partition table. If create is false,
// the function will only apply customizations to existing partitions and
// return a list of left-over mountpoints (i.e. mountpoints in the input that
// were not created). An error can only occur if create is set.
// Does not relayout the table, i.e. a call to relayout might be needed.
func (pt *PartitionTable) applyCustomization(mountpoints []blueprint.FilesystemCustomization, create bool) ([]blueprint.FilesystemCustomization, error) {
@ -395,10 +449,9 @@ func (pt *PartitionTable) applyCustomization(mountpoints []blueprint.FilesystemC
}
// Dynamically calculate and update the start point for each of the existing
// partitions. Adjusts the overall size of image to either the supplied
// value in `size` or to the sum of all partitions if that is larger.
// Will grow the root partition if there is any empty space.
// Returns the updated start point.
// partitions. Adjusts the overall size of image to either the supplied value
// in `size` or to the sum of all partitions if that is larger. Will grow the
// root partition if there is any empty space. Returns the updated start point.
func (pt *PartitionTable) relayout(size uint64) uint64 {
// always reserve one extra sector for the GPT header
header := pt.HeaderSize()
@ -423,6 +476,7 @@ func (pt *PartitionTable) relayout(size uint64) uint64 {
continue
}
partition.Start = start
partition.fitTo(partition.Size)
partition.Size = pt.AlignUp(partition.Size)
start += partition.Size
}
@ -433,6 +487,7 @@ func (pt *PartitionTable) relayout(size uint64) uint64 {
root := &pt.Partitions[rootIdx]
root.Start = start
root.fitTo(root.Size)
// add the extra padding specified in the partition table
footer += pt.ExtraPadding

View file

@ -8,6 +8,7 @@ import (
"github.com/osbuild/images/internal/workload"
"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/bootc"
"github.com/osbuild/images/pkg/customizations/fdo"
"github.com/osbuild/images/pkg/customizations/fsnode"
@ -57,6 +58,10 @@ func osCustomizations(
osc.Containers = containers
osc.GPGKeyFiles = imageConfig.GPGKeyFiles
if rpm := c.GetRPM(); rpm != nil && rpm.ImportKeys != nil {
osc.GPGKeyFiles = append(osc.GPGKeyFiles, rpm.ImportKeys.Files...)
}
if imageConfig.ExcludeDocs != nil {
osc.ExcludeDocs = *imageConfig.ExcludeDocs
}
@ -189,12 +194,11 @@ func osCustomizations(
}
osc.Directories = append(osc.Directories, oscapDataNode)
remediationConfig, tailoringConfig, err := oscap.NewConfigs(*oscapConfig, imageConfig.DefaultOSCAPDatastream)
remediationConfig, err := oscap.NewConfigs(*oscapConfig, imageConfig.DefaultOSCAPDatastream)
if err != nil {
panic(fmt.Errorf("error creating OpenSCAP configs: %w", err))
}
osc.OpenSCAPTailorConfig = tailoringConfig
osc.OpenSCAPRemediationConfig = remediationConfig
}
@ -425,6 +429,15 @@ func imageInstallerImage(workload workload.Workload,
img.AdditionalKernelOpts = []string{"inst.text", "inst.noninteractive"}
}
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.Platform = t.platform
img.Workload = workload
@ -624,11 +637,20 @@ func iotInstallerImage(workload workload.Workload,
// kickstart though kickstart does support setting them
img.Kickstart.Timezone, _ = customizations.GetTimezoneSettings()
img.AdditionalAnacondaModules = []string{
"org.fedoraproject.Anaconda.Modules.Timezone",
"org.fedoraproject.Anaconda.Modules.Localization",
"org.fedoraproject.Anaconda.Modules.Users",
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, []string{
anaconda.ModuleTimezone,
anaconda.ModuleLocalization,
anaconda.ModuleUsers,
}...)
img.SquashfsCompression = "lz4"

View file

@ -7,6 +7,7 @@ import (
"github.com/osbuild/images/internal/workload"
"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/fdo"
"github.com/osbuild/images/pkg/customizations/fsnode"
"github.com/osbuild/images/pkg/customizations/ignition"
@ -58,6 +59,10 @@ func osCustomizations(
osc.Containers = containers
osc.GPGKeyFiles = imageConfig.GPGKeyFiles
if rpm := c.GetRPM(); rpm != nil && rpm.ImportKeys != nil {
osc.GPGKeyFiles = append(osc.GPGKeyFiles, rpm.ImportKeys.Files...)
}
if imageConfig.ExcludeDocs != nil {
osc.ExcludeDocs = *imageConfig.ExcludeDocs
}
@ -210,12 +215,11 @@ func osCustomizations(
}
osc.Directories = append(osc.Directories, oscapDataNode)
remediationConfig, tailoringConfig, err := oscap.NewConfigs(*oscapConfig, imageConfig.DefaultOSCAPDatastream)
remediationConfig, err := oscap.NewConfigs(*oscapConfig, imageConfig.DefaultOSCAPDatastream)
if err != nil {
panic(fmt.Errorf("error creating OpenSCAP configs: %w", err))
}
osc.OpenSCAPTailorConfig = tailoringConfig
osc.OpenSCAPRemediationConfig = remediationConfig
}
@ -476,9 +480,18 @@ func EdgeInstallerImage(workload workload.Workload,
img.AdditionalDrivers = installerConfig.AdditionalDrivers
}
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...)
}
if len(img.Kickstart.Users)+len(img.Kickstart.Groups) > 0 {
// only enable the users module if needed
img.AdditionalAnacondaModules = []string{"org.fedoraproject.Anaconda.Modules.Users"}
img.AdditionalAnacondaModules = append(img.AdditionalAnacondaModules, anaconda.ModuleUsers)
}
img.ISOLabel, err = t.ISOLabel()
@ -658,7 +671,15 @@ func ImageInstallerImage(workload workload.Workload,
img.AdditionalDrivers = installerConfig.AdditionalDrivers
}
img.AdditionalAnacondaModules = []string{"org.fedoraproject.Anaconda.Modules.Users"}
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)
img.SquashfsCompression = "xz"

View file

@ -9,7 +9,7 @@ import (
)
// TODO: move these to the EC2 environment
const amiKernelOptions = "console=tty0 console=ttyS0,115200n8 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295"
const amiKernelOptions = "console=tty0 console=ttyS0,115200n8 rd.blacklist=nouveau nvme_core.io_timeout=4294967295"
// default EC2 images config (common for all architectures)
func baseEc2ImageConfig() *distro.ImageConfig {
@ -262,7 +262,7 @@ func mkAMIImgTypeAarch64() *rhel.ImageType {
[]string{"image"},
)
it.KernelOptions = "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0"
it.KernelOptions = "console=ttyS0,115200n8 console=tty0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0"
it.Bootable = true
it.DefaultSize = 10 * common.GibiByte
it.DefaultImageConfig = defaultAMIImageConfig()

View file

@ -34,19 +34,6 @@ func defaultBasePartitionTables(t *rhel.ImageType) (disk.PartitionTable, bool) {
FSTabPassNo: 2,
},
},
{
Size: 1 * common.GibiByte,
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Type: disk.FilesystemDataGUID,
@ -81,19 +68,6 @@ func defaultBasePartitionTables(t *rhel.ImageType) (disk.PartitionTable, bool) {
FSTabPassNo: 2,
},
},
{
Size: 1 * common.GibiByte,
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Type: disk.FilesystemDataGUID,
@ -119,17 +93,6 @@ func defaultBasePartitionTables(t *rhel.ImageType) (disk.PartitionTable, bool) {
Type: "41",
Bootable: true,
},
{
Size: 1 * common.GibiByte,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Payload: &disk.Filesystem{
@ -148,17 +111,6 @@ func defaultBasePartitionTables(t *rhel.ImageType) (disk.PartitionTable, bool) {
UUID: "0x14fc63d2",
Type: "dos",
Partitions: []disk.Partition{
{
Size: 1 * common.GibiByte,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Bootable: true,

View file

@ -24,7 +24,7 @@ func mkQcow2ImgType(d *rhel.Distribution) *rhel.ImageType {
)
it.DefaultImageConfig = qcowImageConfig(d)
it.KernelOptions = "console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0"
it.KernelOptions = "console=tty0 console=ttyS0,115200n8 no_timer_check"
it.DefaultSize = 10 * common.GibiByte
it.Bootable = true
it.BasePartitionTables = defaultBasePartitionTables
@ -47,7 +47,7 @@ func mkOCIImgType(d *rhel.Distribution) *rhel.ImageType {
)
it.DefaultImageConfig = qcowImageConfig(d)
it.KernelOptions = "console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0"
it.KernelOptions = "console=tty0 console=ttyS0,115200n8 no_timer_check"
it.DefaultSize = 10 * common.GibiByte
it.Bootable = true
it.BasePartitionTables = defaultBasePartitionTables
@ -72,7 +72,7 @@ func mkOpenstackImgType() *rhel.ImageType {
it.DefaultImageConfig = &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
}
it.KernelOptions = "ro net.ifnames=0"
it.KernelOptions = "ro"
it.DefaultSize = 4 * common.GibiByte
it.Bootable = true
it.BasePartitionTables = defaultBasePartitionTables

View file

@ -7,7 +7,7 @@ import (
"github.com/osbuild/images/pkg/rpmmd"
)
const vmdkKernelOptions = "ro net.ifnames=0"
const vmdkKernelOptions = "ro"
func mkVMDKImgType() *rhel.ImageType {
it := rhel.NewImageType(

View file

@ -690,6 +690,9 @@ type packageSpecs []PackageSpec
type depsolveResult struct {
Packages packageSpecs `json:"packages"`
Repos map[string]repoConfig `json:"repos"`
// (optional) contains the solver used, e.g. "dnf5"
Solver string `json:"solver"`
}
// Package specification

View file

@ -8,6 +8,7 @@ import (
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/artifact"
"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/manifest"
"github.com/osbuild/images/pkg/osbuild"
@ -31,12 +32,14 @@ type AnacondaContainerInstaller struct {
Release string
Preview bool
ContainerSource container.SourceSpec
ContainerSource container.SourceSpec
ContainerRemoveSignatures bool
Filename string
AdditionalDracutModules []string
AdditionalAnacondaModules []string
DisabledAnacondaModules []string
AdditionalDrivers []string
FIPS bool
@ -81,10 +84,11 @@ func (img *AnacondaContainerInstaller) InstantiateManifest(m *manifest.Manifest,
anacondaPipeline.Checkpoint()
anacondaPipeline.AdditionalDracutModules = img.AdditionalDracutModules
anacondaPipeline.AdditionalAnacondaModules = img.AdditionalAnacondaModules
anacondaPipeline.DisabledAnacondaModules = img.DisabledAnacondaModules
if img.FIPS {
anacondaPipeline.AdditionalAnacondaModules = append(
anacondaPipeline.AdditionalAnacondaModules,
"org.fedoraproject.Anaconda.Modules.Security",
anaconda.ModuleSecurity,
)
}
anacondaPipeline.AdditionalDrivers = img.AdditionalDrivers
@ -121,6 +125,7 @@ func (img *AnacondaContainerInstaller) InstantiateManifest(m *manifest.Manifest,
// For ostree installers, always put the kickstart file in the root of the ISO
isoTreePipeline.PayloadPath = "/container"
isoTreePipeline.PayloadRemoveSignatures = img.ContainerRemoveSignatures
isoTreePipeline.ContainerSource = &img.ContainerSource
isoTreePipeline.ISOLinux = isoLinuxEnabled

View file

@ -7,6 +7,7 @@ import (
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/customizations/anaconda"
"github.com/osbuild/images/pkg/customizations/kickstart"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/osbuild"
@ -38,6 +39,7 @@ type AnacondaOSTreeInstaller struct {
AdditionalDracutModules []string
AdditionalAnacondaModules []string
DisabledAnacondaModules []string
AdditionalDrivers []string
FIPS bool
}
@ -83,9 +85,10 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest,
if img.FIPS {
anacondaPipeline.AdditionalAnacondaModules = append(
anacondaPipeline.AdditionalAnacondaModules,
"org.fedoraproject.Anaconda.Modules.Security",
anaconda.ModuleSecurity,
)
}
anacondaPipeline.DisabledAnacondaModules = img.DisabledAnacondaModules
anacondaPipeline.AdditionalDrivers = img.AdditionalDrivers
rootfsImagePipeline := manifest.NewISORootfsImg(buildPipeline, anacondaPipeline)

View file

@ -10,6 +10,7 @@ import (
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/customizations/anaconda"
"github.com/osbuild/images/pkg/customizations/kickstart"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/manifest"
@ -68,6 +69,7 @@ type AnacondaTarInstaller struct {
AdditionalKernelOpts []string
AdditionalAnacondaModules []string
DisabledAnacondaModules []string
AdditionalDracutModules []string
AdditionalDrivers []string
}
@ -129,9 +131,10 @@ func (img *AnacondaTarInstaller) InstantiateManifest(m *manifest.Manifest,
if img.OSCustomizations.FIPS {
anacondaPipeline.AdditionalAnacondaModules = append(
anacondaPipeline.AdditionalAnacondaModules,
"org.fedoraproject.Anaconda.Modules.Security",
anaconda.ModuleSecurity,
)
}
anacondaPipeline.DisabledAnacondaModules = img.DisabledAnacondaModules
anacondaPipeline.AdditionalDracutModules = img.AdditionalDracutModules
anacondaPipeline.AdditionalDrivers = img.AdditionalDrivers

View file

@ -69,6 +69,8 @@ type AnacondaInstaller struct {
// Additional anaconda modules to enable
AdditionalAnacondaModules []string
// Anaconda modules to explicitly disable
DisabledAnacondaModules []string
// Additional dracut modules and drivers to enable
AdditionalDracutModules []string
@ -269,7 +271,7 @@ func (p *AnacondaInstaller) payloadStages() []*osbuild.Stage {
LoraxPath = "99-generic/runtime-postinstall.tmpl"
}
stages = append(stages, osbuild.NewAnacondaStage(osbuild.NewAnacondaStageOptions(p.AdditionalAnacondaModules)))
stages = append(stages, osbuild.NewAnacondaStage(osbuild.NewAnacondaStageOptions(p.AdditionalAnacondaModules, p.DisabledAnacondaModules)))
stages = append(stages, osbuild.NewLoraxScriptStage(&osbuild.LoraxScriptStageOptions{
Path: LoraxPath,
BaseArch: p.platform.GetArch().String(),

View file

@ -35,6 +35,9 @@ type AnacondaInstallerISOTree struct {
// The path where the payload (tarball, ostree repo, or container) will be stored.
PayloadPath string
// If set the skopeo stage will remove signatures during copy
PayloadRemoveSignatures bool
isoLabel string
SquashfsCompression string
@ -391,10 +394,15 @@ func (p *AnacondaInstallerISOTree) ostreeContainerStages() []*osbuild.Stage {
}))
// copy the container in
stages = append(stages, osbuild.NewSkopeoStageWithOCI(
skopeoStage := osbuild.NewSkopeoStageWithOCI(
p.PayloadPath,
image,
nil))
nil)
if p.PayloadRemoveSignatures {
opts := skopeoStage.Options.(*osbuild.SkopeoStageOptions)
opts.RemoveSignatures = common.ToPtr(true)
}
stages = append(stages, skopeoStage)
stages = append(stages, p.bootcInstallerKickstartStages()...)
return stages

View file

@ -129,7 +129,6 @@ type OSCustomizations struct {
ContainersStorage *string
// OpenSCAP config
OpenSCAPTailorConfig *oscap.TailoringConfig
OpenSCAPRemediationConfig *oscap.RemediationConfig
Subscription *subscription.ImageOptions
@ -324,7 +323,7 @@ func (p *OS) getBuildPackages(distro Distro) []string {
packages = append(packages, "skopeo")
}
if p.OpenSCAPTailorConfig != nil {
if p.OpenSCAPRemediationConfig != nil && p.OpenSCAPRemediationConfig.TailoringConfig != nil {
packages = append(packages, "openscap-utils")
}
@ -807,22 +806,15 @@ func (p *OS) serialize() osbuild.Pipeline {
}
}
if p.OpenSCAPTailorConfig != nil {
if p.OpenSCAPRemediationConfig == nil {
// This is a programming error, since it doesn't make sense
// to have tailoring configs without openscap config.
panic(fmt.Errorf("OpenSCAP autotailoring cannot be set if no OpenSCAP config has been provided"))
}
tailoringStageOpts := osbuild.NewOscapAutotailorStageOptions(p.OpenSCAPTailorConfig)
pipeline.AddStage(osbuild.NewOscapAutotailorStage(tailoringStageOpts))
}
// NOTE: We need to run the OpenSCAP stages as the last stage before SELinux
// since the remediation may change file permissions and other aspects of the
// hardened image
if p.OpenSCAPRemediationConfig != nil {
remediationStageOpts := osbuild.NewOscapRemediationStageOptions(oscap.DataDir, p.OpenSCAPRemediationConfig)
if remediationConfig := p.OpenSCAPRemediationConfig; remediationConfig != nil {
if remediationConfig.TailoringConfig != nil {
tailoringStageOpts := osbuild.NewOscapAutotailorStageOptions(remediationConfig)
pipeline.AddStage(osbuild.NewOscapAutotailorStage(tailoringStageOpts))
}
remediationStageOpts := osbuild.NewOscapRemediationStageOptions(oscap.DataDir, remediationConfig)
pipeline.AddStage(osbuild.NewOscapRemediationStage(remediationStageOpts))
}

View file

@ -174,9 +174,6 @@ func (p *RawBootcImage) serialize() osbuild.Pipeline {
// customize the image
if len(p.Groups) > 0 {
groupsStage := osbuild.GenGroupsStage(p.Groups)
if err != nil {
panic(fmt.Sprintf("group stage failed %v", err))
}
groupsStage.Mounts = mounts
groupsStage.Devices = devices
pipeline.AddStage(groupsStage)

View file

@ -1,5 +1,10 @@
package osbuild
import (
"github.com/osbuild/images/pkg/customizations/anaconda"
"golang.org/x/exp/slices"
)
type AnacondaStageOptions struct {
// Kickstart modules to enable
KickstartModules []string `json:"kickstart-modules"`
@ -15,18 +20,47 @@ func NewAnacondaStage(options *AnacondaStageOptions) *Stage {
}
}
func NewAnacondaStageOptions(additionalModules []string) *AnacondaStageOptions {
modules := []string{
"org.fedoraproject.Anaconda.Modules.Network",
"org.fedoraproject.Anaconda.Modules.Payloads",
"org.fedoraproject.Anaconda.Modules.Storage",
}
if len(additionalModules) > 0 {
modules = append(modules, additionalModules...)
}
return &AnacondaStageOptions{
KickstartModules: modules,
func defaultModuleStates() map[string]bool {
return map[string]bool{
anaconda.ModuleLocalization: false,
anaconda.ModuleNetwork: true,
anaconda.ModulePayloads: true,
anaconda.ModuleRuntime: false,
anaconda.ModuleSecurity: false,
anaconda.ModuleServices: false,
anaconda.ModuleStorage: true,
anaconda.ModuleSubscription: false,
anaconda.ModuleTimezone: false,
anaconda.ModuleUsers: false,
}
}
func setModuleStates(states map[string]bool, enable, disable []string) {
for _, modname := range enable {
states[modname] = true
}
for _, modname := range disable {
states[modname] = false
}
}
func filterEnabledModules(moduleStates map[string]bool) []string {
enabled := make([]string, 0, len(moduleStates))
for modname, state := range moduleStates {
if state {
enabled = append(enabled, modname)
}
}
// sort the list to guarantee stable manifests
slices.Sort(enabled)
return enabled
}
func NewAnacondaStageOptions(enableModules, disableModules []string) *AnacondaStageOptions {
states := defaultModuleStates()
setModuleStates(states, enableModules, disableModules)
return &AnacondaStageOptions{
KickstartModules: filterEnabledModules(states),
}
}

View file

@ -11,29 +11,57 @@ type OscapAutotailorStageOptions struct {
Config OscapAutotailorConfig `json:"config"`
}
type OscapAutotailorConfig struct {
TailoredProfileID string `json:"new_profile"`
Datastream string `json:"datastream"`
ProfileID string `json:"profile_id"`
Selected []string `json:"selected,omitempty"`
Unselected []string `json:"unselected,omitempty"`
type OscapAutotailorConfig interface {
validate() error
isAutotailorConfig()
}
func (OscapAutotailorStageOptions) isStageOptions() {}
type AutotailorKeyValueConfig struct {
NewProfile string `json:"new_profile"`
Datastream string `json:"datastream"`
ProfileID string `json:"profile_id"`
Selected []string `json:"selected,omitempty"`
Unselected []string `json:"unselected,omitempty"`
}
func (c OscapAutotailorConfig) validate() error {
func (c AutotailorKeyValueConfig) isAutotailorConfig() {}
func (c AutotailorKeyValueConfig) validate() error {
if c.Datastream == "" {
return fmt.Errorf("'datastream' must be specified")
}
if c.NewProfile == "" {
return fmt.Errorf("'new_profile' must be specified")
}
if c.ProfileID == "" {
return fmt.Errorf("'profile_id' must be specified")
}
return nil
}
type AutotailorJSONConfig struct {
TailoredProfileID string `json:"tailored_profile_id"`
Datastream string `json:"datastream"`
TailoringFile string `json:"tailoring_file"`
}
func (c AutotailorJSONConfig) isAutotailorConfig() {}
func (c AutotailorJSONConfig) validate() error {
if c.Datastream == "" {
return fmt.Errorf("'datastream' must be specified")
}
if c.TailoredProfileID == "" {
return fmt.Errorf("'new_profile' must be specified")
return fmt.Errorf("'tailored_profile_id' must be specified")
}
if c.TailoringFile == "" {
return fmt.Errorf("'tailoring_file' must be specified")
}
return nil
}
func (OscapAutotailorStageOptions) isStageOptions() {}
func NewOscapAutotailorStage(options *OscapAutotailorStageOptions) *Stage {
if err := options.Config.validate(); err != nil {
panic(err)
@ -45,25 +73,41 @@ func NewOscapAutotailorStage(options *OscapAutotailorStageOptions) *Stage {
}
}
func NewOscapAutotailorStageOptions(options *oscap.TailoringConfig) *OscapAutotailorStageOptions {
func NewOscapAutotailorStageOptions(options *oscap.RemediationConfig) *OscapAutotailorStageOptions {
if options == nil {
return nil
}
tailoringConfig := options.TailoringConfig
if tailoringConfig == nil {
return nil
}
// TODO: don't panic! unfortunately this would involve quite
// a big refactor and we still need to be a bit defensive here
if options.RemediationConfig.TailoringPath == "" {
if tailoringConfig.TailoringPath == "" {
panic(fmt.Errorf("The tailoring path for the OpenSCAP remediation config cannot be empty, this is a programming error"))
}
if tailoringConfig.JSONFilepath != "" {
return &OscapAutotailorStageOptions{
Filepath: tailoringConfig.TailoringPath,
Config: AutotailorJSONConfig{
Datastream: options.Datastream,
TailoredProfileID: tailoringConfig.TailoredProfileID,
TailoringFile: tailoringConfig.JSONFilepath,
},
}
}
return &OscapAutotailorStageOptions{
Filepath: options.RemediationConfig.TailoringPath,
Config: OscapAutotailorConfig{
TailoredProfileID: options.TailoredProfileID,
Datastream: options.RemediationConfig.Datastream,
ProfileID: options.RemediationConfig.ProfileID,
Selected: options.Selected,
Unselected: options.Unselected,
Filepath: tailoringConfig.TailoringPath,
Config: AutotailorKeyValueConfig{
Datastream: options.Datastream,
ProfileID: options.ProfileID,
NewProfile: tailoringConfig.TailoredProfileID,
Selected: tailoringConfig.Selected,
Unselected: tailoringConfig.Unselected,
},
}
}

View file

@ -81,13 +81,19 @@ func NewOscapRemediationStageOptions(dataDir string, options *oscap.RemediationC
return nil
}
config := OscapConfig{
ProfileID: options.ProfileID,
Datastream: options.Datastream,
Compression: options.CompressionEnabled,
}
if tc := options.TailoringConfig; tc != nil {
config.ProfileID = tc.TailoredProfileID
config.Tailoring = tc.TailoringPath
}
return &OscapRemediationStageOptions{
DataDir: dataDir,
Config: OscapConfig{
ProfileID: options.ProfileID,
Datastream: options.Datastream,
Tailoring: options.TailoringPath,
Compression: options.CompressionEnabled,
},
Config: config,
}
}

View file

@ -20,7 +20,8 @@ type SkopeoDestinationOCI struct {
func (SkopeoDestinationOCI) isSkopeoDestination() {}
type SkopeoStageOptions struct {
Destination SkopeoDestination `json:"destination"`
Destination SkopeoDestination `json:"destination"`
RemoveSignatures *bool `json:"remove-signatures,omitempty"`
}
func (o SkopeoStageOptions) isStageOptions() {}