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

tag v0.149.0
Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com>

Changes with 0.149.0

----------------
  * Update dependencies 2025-05-25 (osbuild/images#1560)
    * Author: SchutzBot, Reviewers: Simon de Vlieger, Tomáš Hozza
  * Update osbuild dependency commit ID to latest (osbuild/images#1522)
    * Author: SchutzBot, Reviewers: Simon de Vlieger, Tomáš Hozza
  * Update snapshots to 20250515 (osbuild/images#1524)
    * Author: SchutzBot, Reviewers: Simon de Vlieger, Tomáš Hozza
  * `vagrant-libvirt` implementation (HMS-6116) (osbuild/images#1548)
    * Author: Simon de Vlieger, Reviewers: Achilleas Koutsou, Tomáš Hozza
  * fedora: tweaks after all imageTypes are YAML (osbuild/images#1518)
    * Author: Michael Vogt, Reviewers: Simon de Vlieger, Tomáš Hozza
  * gha: do not break gobump output (osbuild/images#1561)
    * Author: Lukáš Zapletal, Reviewers: Simon de Vlieger, Tomáš Hozza
  * repositories: AlmaLinux 10 (osbuild/images#1567)
    * Author: Simon de Vlieger, Reviewers: Achilleas Koutsou, Lukáš Zapletal, Neal Gompa (ニール・ゴンパ)
  * vagrant: image config for default vagrant user (HMS-6116) (osbuild/images#1565)
    * Author: Simon de Vlieger, Reviewers: Achilleas Koutsou, Michael Vogt

— Somewhere on the Internet, 2025-05-27

---

tag v0.150.0
Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com>

Changes with 0.150.0

----------------
  * Replace hardcoded kickstart %post scripts with new stage options and bootc switch with custom kickstart content (HMS-6051) (osbuild/images#1527)
    * Author: Achilleas Koutsou, Reviewers: Simon de Vlieger, Tomáš Hozza
  * test: install yamllint for tests (osbuild/images#1572)
    * Author: Achilleas Koutsou, Reviewers: Lukáš Zapletal, Simon de Vlieger, Tomáš Hozza

— Somewhere on the Internet, 2025-06-02

---

tag v0.151.0
Tagger: imagebuilder-bot <imagebuilder-bots+imagebuilder-bot@redhat.com>

Changes with 0.151.0

----------------
  * Introduce new Azure CVM image type (HMS-5636) (osbuild/images#1318)
    * Author: Achilleas Koutsou, Reviewers: Nobody
  * Many: support using string with unit for byte-sized partitioning fields in YAML distro definitions (osbuild/images#1579)
    * Author: Tomáš Hozza, Reviewers: Achilleas Koutsou, Brian C. Lane
  * Update osbuild dependency commit ID to latest (osbuild/images#1587)
    * Author: SchutzBot, Reviewers: Achilleas Koutsou, Tomáš Hozza
  * Update snapshots to 20250601 (osbuild/images#1573)
    * Author: SchutzBot, Reviewers: Achilleas Koutsou, Lukáš Zapletal
  * bootc: Make installed rootfs configurable (osbuild/images#1555)
    * Author: Mbarak Bujra, Reviewers: Michael Vogt, Tomáš Hozza
  * distro: create new ImageConfig.DNFConfig (osbuild/images#1583)
    * Author: Michael Vogt, Reviewers: Simon de Vlieger, Tomáš Hozza
  * distro: make "fedora" a "generic" distro (osbuild/images#1563)
    * Author: Michael Vogt, Reviewers: Nobody
  * image: If using a separate build container, copy bootc customization to it (osbuild/images#1571)
    * Author: Alexander Larsson, Reviewers: Achilleas Koutsou, Tomáš Hozza
  * manifest/ostree: explicitly include shadow-utils (osbuild/images#1585)
    * Author: Simon de Vlieger, Reviewers: Achilleas Koutsou, Michael Vogt
  * osbuild/tar: explicit compression (HMS-8573, HMS-6116) (osbuild/images#1581)
    * Author: Simon de Vlieger, Reviewers: Achilleas Koutsou, Tomáš Hozza
  * tests: bump fedora versions to 41 (osbuild/images#1438)
    * Author: Lukáš Zapletal, Reviewers: Brian C. Lane, Michael Vogt

— Somewhere on the Internet, 2025-06-09

---
This commit is contained in:
Achilleas Koutsou 2025-06-10 15:43:18 +02:00 committed by Gianluca Zuccarelli
parent cedc351bbd
commit deccaf9548
82 changed files with 2844 additions and 1175 deletions

View file

@ -0,0 +1,238 @@
package generic
import (
"bytes"
"errors"
"fmt"
"sort"
"text/template"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/defs"
"github.com/osbuild/images/pkg/platform"
)
const (
// package set names
// main/common os image package set name
osPkgsKey = "os"
// container package set name
containerPkgsKey = "container"
// installer package set name
installerPkgsKey = "installer"
// blueprint package set name
blueprintPkgsKey = "blueprint"
)
var (
ErrDistroNotFound = errors.New("distribution not found")
)
// distribution implements the distro.Distro interface
var _ = distro.Distro(&distribution{})
type distribution struct {
defs.DistroYAML
arches map[string]*architecture
// XXX: move into defs.DistroYAML? the downside of doing this
// is that we would have to duplicate the default image config
// accross the centos/alma/rhel distros.yaml, otherwise we
// just load it from the imagetypes file/dir and it is natually
// "in-sync"
defaultImageConfig *distro.ImageConfig
}
func (d *distribution) getISOLabelFunc(isoLabel string) isoLabelFunc {
return func(t *imageType) string {
type inputs struct {
Product string
OsVersion string
Arch string
ImgTypeLabel string
}
templ := common.Must(template.New("iso-label").Parse(d.DistroYAML.ISOLabelTmpl))
var buf bytes.Buffer
err := templ.Execute(&buf, inputs{
Product: t.Arch().Distro().Product(),
OsVersion: t.Arch().Distro().OsVersion(),
Arch: t.Arch().Name(),
ImgTypeLabel: isoLabel,
})
if err != nil {
// XXX: cleanup isoLabelFunc to allow error
panic(err)
}
return buf.String()
}
}
func newDistro(nameVer string) (distro.Distro, error) {
distroYAML, err := defs.Distro(nameVer)
if err != nil {
return nil, err
}
if distroYAML == nil {
return nil, nil
}
rd := &distribution{
DistroYAML: *distroYAML,
defaultImageConfig: common.Must(defs.DistroImageConfig(nameVer)),
arches: make(map[string]*architecture),
}
its, err := defs.ImageTypes(rd.Name())
if err != nil {
return nil, err
}
for _, imgTypeYAML := range its {
// use as marker for images that are not converted to
// YAML yet
if imgTypeYAML.Filename == "" {
continue
}
for _, pl := range imgTypeYAML.Platforms {
ar, ok := rd.arches[pl.Arch.String()]
if !ok {
ar = newArchitecture(rd, pl.Arch.String())
rd.arches[pl.Arch.String()] = ar
}
it := newImageTypeFrom(rd, ar, imgTypeYAML)
if err := ar.addImageType(&pl, it); err != nil {
return nil, err
}
}
}
return rd, nil
}
func (d *distribution) Name() string {
return d.DistroYAML.Name
}
func (d *distribution) Codename() string {
return d.DistroYAML.Codename
}
func (d *distribution) Releasever() string {
return d.DistroYAML.ReleaseVersion
}
func (d *distribution) OsVersion() string {
return d.DistroYAML.OsVersion
}
func (d *distribution) Product() string {
return d.DistroYAML.Product
}
func (d *distribution) ModulePlatformID() string {
return d.DistroYAML.ModulePlatformID
}
func (d *distribution) OSTreeRef() string {
return d.DistroYAML.OSTreeRefTmpl
}
func (d *distribution) ListArches() []string {
archNames := make([]string, 0, len(d.arches))
for name := range d.arches {
archNames = append(archNames, name)
}
sort.Strings(archNames)
return archNames
}
func (d *distribution) GetArch(name string) (distro.Arch, error) {
arch, exists := d.arches[name]
if !exists {
return nil, fmt.Errorf("invalid architecture: %v", name)
}
return arch, nil
}
// architecture implements the distro.Arch interface
var _ = distro.Arch(&architecture{})
type architecture struct {
distro *distribution
name string
imageTypes map[string]distro.ImageType
imageTypeAliases map[string]string
}
func newArchitecture(rd *distribution, name string) *architecture {
return &architecture{
distro: rd,
name: name,
imageTypes: make(map[string]distro.ImageType),
imageTypeAliases: make(map[string]string),
}
}
func (a *architecture) Name() string {
return a.name
}
func (a *architecture) ListImageTypes() []string {
itNames := make([]string, 0, len(a.imageTypes))
for name := range a.imageTypes {
itNames = append(itNames, name)
}
sort.Strings(itNames)
return itNames
}
func (a *architecture) GetImageType(name string) (distro.ImageType, error) {
t, exists := a.imageTypes[name]
if !exists {
aliasForName, exists := a.imageTypeAliases[name]
if !exists {
return nil, fmt.Errorf("invalid image type: %v", name)
}
t, exists = a.imageTypes[aliasForName]
if !exists {
panic(fmt.Sprintf("image type '%s' is an alias to a non-existing image type '%s'", name, aliasForName))
}
}
return t, nil
}
func (a *architecture) addImageType(platform platform.Platform, it imageType) error {
it.arch = a
it.platform = platform
a.imageTypes[it.Name()] = &it
for _, alias := range it.ImageTypeYAML.NameAliases {
if a.imageTypeAliases == nil {
a.imageTypeAliases = map[string]string{}
}
if existingAliasFor, exists := a.imageTypeAliases[alias]; exists {
return fmt.Errorf("image type alias '%s' for '%s' is already defined for another image type '%s'", alias, it.Name(), existingAliasFor)
}
a.imageTypeAliases[alias] = it.Name()
}
return nil
}
func (a *architecture) Distro() distro.Distro {
return a.distro
}
func DistroFactory(idStr string) distro.Distro {
distro, err := newDistro(idStr)
if errors.Is(err, ErrDistroNotFound) {
return nil
}
if err != nil {
panic(err)
}
return distro
}

View file

@ -0,0 +1,48 @@
package generic
import (
"fmt"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/distro/defs"
)
func newImageTypeFrom(d *distribution, ar *architecture, imgYAML defs.ImageTypeYAML) imageType {
typName := imgYAML.Name()
it := imageType{
ImageTypeYAML: imgYAML,
isoLabel: d.getISOLabelFunc(imgYAML.ISOLabel),
}
it.defaultImageConfig = common.Must(defs.ImageConfig(d.Name(), ar.name, typName))
it.defaultInstallerConfig = common.Must(defs.InstallerConfig(d.Name(), ar.name, typName))
switch imgYAML.Image {
case "disk":
it.image = diskImage
case "container":
it.image = containerImage
case "image_installer":
it.image = imageInstallerImage
case "live_installer":
it.image = liveInstallerImage
case "bootable_container":
it.image = bootableContainerImage
case "iot":
it.image = iotImage
case "iot_commit":
it.image = iotCommitImage
case "iot_container":
it.image = iotContainerImage
case "iot_installer":
it.image = iotInstallerImage
case "iot_simplified_installer":
it.image = iotSimplifiedInstallerImage
case "tar":
it.image = tarImage
default:
err := fmt.Errorf("unknown image func: %v for %v", imgYAML.Image, imgYAML.Name())
panic(err)
}
return it
}

View file

@ -0,0 +1,977 @@
package generic
import (
"fmt"
"math/rand"
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/arch"
"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"
"github.com/osbuild/images/pkg/customizations/ignition"
"github.com/osbuild/images/pkg/customizations/kickstart"
"github.com/osbuild/images/pkg/customizations/oscap"
"github.com/osbuild/images/pkg/customizations/users"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/image"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/rpmmd"
)
func osCustomizations(t *imageType, osPackageSet rpmmd.PackageSet, containers []container.SourceSpec, c *blueprint.Customizations) (manifest.OSCustomizations, error) {
imageConfig := t.getDefaultImageConfig()
osc := manifest.OSCustomizations{}
if t.ImageTypeYAML.Bootable || t.ImageTypeYAML.RPMOSTree {
osc.KernelName = c.GetKernel().Name
var kernelOptions []string
// XXX: keep in sync with the identical copy in rhel/images.go
if t.defaultImageConfig != nil && len(t.defaultImageConfig.KernelOptions) > 0 {
kernelOptions = append(kernelOptions, t.defaultImageConfig.KernelOptions...)
}
if bpKernel := c.GetKernel(); bpKernel.Append != "" {
kernelOptions = append(kernelOptions, bpKernel.Append)
}
osc.KernelOptionsAppend = kernelOptions
}
osc.FIPS = c.GetFIPS()
osc.BasePackages = osPackageSet.Include
osc.ExcludeBasePackages = osPackageSet.Exclude
osc.ExtraBaseRepos = osPackageSet.Repositories
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
}
if !t.ImageTypeYAML.BootISO {
// don't put users and groups in the payload of an installer
// add them via kickstart instead
osc.Groups = users.GroupsFromBP(c.GetGroups())
osc.Users = users.UsersFromBP(c.GetUsers())
osc.Users = append(osc.Users, imageConfig.Users...)
}
osc.EnabledServices = imageConfig.EnabledServices
osc.DisabledServices = imageConfig.DisabledServices
osc.MaskedServices = imageConfig.MaskedServices
if imageConfig.DefaultTarget != nil {
osc.DefaultTarget = *imageConfig.DefaultTarget
}
if fw := c.GetFirewall(); fw != nil {
options := osbuild.FirewallStageOptions{
Ports: fw.Ports,
}
if fw.Services != nil {
options.EnabledServices = fw.Services.Enabled
options.DisabledServices = fw.Services.Disabled
}
osc.Firewall = &options
}
language, keyboard := c.GetPrimaryLocale()
if language != nil {
osc.Language = *language
} else if imageConfig.Locale != nil {
osc.Language = *imageConfig.Locale
}
if keyboard != nil {
osc.Keyboard = keyboard
} else if imageConfig.Keyboard != nil {
osc.Keyboard = &imageConfig.Keyboard.Keymap
}
if hostname := c.GetHostname(); hostname != nil {
osc.Hostname = *hostname
} else if imageConfig.Hostname != nil {
osc.Hostname = *imageConfig.Hostname
}
if imageConfig.InstallWeakDeps != nil {
osc.InstallWeakDeps = *imageConfig.InstallWeakDeps
}
timezone, ntpServers := c.GetTimezoneSettings()
if timezone != nil {
osc.Timezone = *timezone
} else if imageConfig.Timezone != nil {
osc.Timezone = *imageConfig.Timezone
}
if len(ntpServers) > 0 {
chronyServers := make([]osbuild.ChronyConfigServer, 0, len(ntpServers))
for _, server := range ntpServers {
chronyServers = append(chronyServers, osbuild.ChronyConfigServer{Hostname: server})
}
osc.ChronyConfig = &osbuild.ChronyStageOptions{
Servers: chronyServers,
}
} else if imageConfig.TimeSynchronization != nil {
osc.ChronyConfig = imageConfig.TimeSynchronization
}
// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
osc.SElinux = "targeted"
}
var err error
osc.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(c.GetDirectories())
if err != nil {
// In theory this should never happen, because the blueprint directory customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert directory customizations to fs node directories: %v", err))
}
osc.Files, err = blueprint.FileCustomizationsToFsNodeFiles(c.GetFiles())
if err != nil {
// In theory this should never happen, because the blueprint file customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert file customizations to fs node files: %v", err))
}
// OSTree commits do not include data in `/var` since that is tied to the
// deployment, rather than the commit. Therefore the containers need to be
// stored in a different location, like `/usr/share`, and the container
// storage engine configured accordingly.
if t.ImageTypeYAML.RPMOSTree && len(containers) > 0 {
storagePath := "/usr/share/containers/storage"
osc.ContainersStorage = &storagePath
}
if containerStorage := c.GetContainerStorage(); containerStorage != nil {
osc.ContainersStorage = containerStorage.StoragePath
}
customRepos, err := c.GetRepositories()
if err != nil {
// This shouldn't happen and since the repos
// should have already been validated
panic(fmt.Sprintf("failed to get custom repos: %v", err))
}
// This function returns a map of filename and corresponding yum repos
// and a list of fs node files for the inline gpg keys so we can save
// them to disk. This step also swaps the inline gpg key with the path
// to the file in the os file tree
yumRepos, gpgKeyFiles, err := blueprint.RepoCustomizationsToRepoConfigAndGPGKeyFiles(customRepos)
if err != nil {
panic(fmt.Sprintf("failed to convert inline gpgkeys to fs node files: %v", err))
}
// add the gpg key files to the list of files to be added to the tree
if len(gpgKeyFiles) > 0 {
osc.Files = append(osc.Files, gpgKeyFiles...)
}
for filename, repos := range yumRepos {
osc.YUMRepos = append(osc.YUMRepos, osbuild.NewYumReposStageOptions(filename, repos))
}
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
if t.ImageTypeYAML.RPMOSTree {
panic("unexpected oscap options for ostree image type")
}
oscapDataNode, err := fsnode.NewDirectory(oscap.DataDir, nil, nil, nil, true)
if err != nil {
panic(fmt.Sprintf("unexpected error creating required OpenSCAP directory: %s", oscap.DataDir))
}
osc.Directories = append(osc.Directories, oscapDataNode)
remediationConfig, err := oscap.NewConfigs(*oscapConfig, imageConfig.DefaultOSCAPDatastream)
if err != nil {
panic(fmt.Errorf("error creating OpenSCAP configs: %w", err))
}
osc.OpenSCAPRemediationConfig = remediationConfig
}
osc.ShellInit = imageConfig.ShellInit
osc.Grub2Config = imageConfig.Grub2Config
osc.Sysconfig = imageConfig.SysconfigStageOptions()
osc.SystemdLogind = imageConfig.SystemdLogind
osc.CloudInit = imageConfig.CloudInit
osc.Modprobe = imageConfig.Modprobe
osc.DracutConf = imageConfig.DracutConf
osc.SystemdDropin = imageConfig.SystemdDropin
osc.SystemdUnit = imageConfig.SystemdUnit
osc.Authselect = imageConfig.Authselect
osc.SELinuxConfig = imageConfig.SELinuxConfig
osc.Tuned = imageConfig.Tuned
osc.Tmpfilesd = imageConfig.Tmpfilesd
osc.PamLimitsConf = imageConfig.PamLimitsConf
osc.Sysctld = imageConfig.Sysctld
osc.DNFConfig = imageConfig.DNFConfigOptions(t.arch.distro.OsVersion())
osc.SshdConfig = imageConfig.SshdConfig
osc.AuthConfig = imageConfig.Authconfig
osc.PwQuality = imageConfig.PwQuality
osc.WSLConfig = imageConfig.WSLConfStageOptions()
osc.NetworkManager = imageConfig.NetworkManager
osc.Files = append(osc.Files, imageConfig.Files...)
osc.Directories = append(osc.Directories, imageConfig.Directories...)
ca, err := c.GetCACerts()
if err != nil {
panic(fmt.Sprintf("unexpected error checking CA certs: %v", err))
}
if ca != nil {
osc.CACerts = ca.PEMCerts
}
if imageConfig.MachineIdUninitialized != nil {
osc.MachineIdUninitialized = *imageConfig.MachineIdUninitialized
}
if imageConfig.MountUnits != nil {
osc.MountUnits = *imageConfig.MountUnits
}
return osc, nil
}
func ostreeDeploymentCustomizations(
t *imageType,
c *blueprint.Customizations) (manifest.OSTreeDeploymentCustomizations, error) {
if !t.ImageTypeYAML.RPMOSTree || !t.ImageTypeYAML.Bootable {
return manifest.OSTreeDeploymentCustomizations{}, fmt.Errorf("ostree deployment customizations are only supported for bootable rpm-ostree images")
}
imageConfig := t.getDefaultImageConfig()
deploymentConf := manifest.OSTreeDeploymentCustomizations{}
var kernelOptions []string
if len(t.defaultImageConfig.KernelOptions) > 0 {
kernelOptions = append(kernelOptions, t.defaultImageConfig.KernelOptions...)
}
if bpKernel := c.GetKernel(); bpKernel != nil && bpKernel.Append != "" {
kernelOptions = append(kernelOptions, bpKernel.Append)
}
if imageConfig.IgnitionPlatform != nil {
deploymentConf.IgnitionPlatform = *imageConfig.IgnitionPlatform
}
switch deploymentConf.IgnitionPlatform {
case "metal":
if bpIgnition := c.GetIgnition(); bpIgnition != nil && bpIgnition.FirstBoot != nil && bpIgnition.FirstBoot.ProvisioningURL != "" {
kernelOptions = append(kernelOptions, "ignition.config.url="+bpIgnition.FirstBoot.ProvisioningURL)
}
}
deploymentConf.KernelOptionsAppend = kernelOptions
deploymentConf.FIPS = c.GetFIPS()
deploymentConf.Users = users.UsersFromBP(c.GetUsers())
deploymentConf.Groups = users.GroupsFromBP(c.GetGroups())
var err error
deploymentConf.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(c.GetDirectories())
if err != nil {
return manifest.OSTreeDeploymentCustomizations{}, err
}
deploymentConf.Files, err = blueprint.FileCustomizationsToFsNodeFiles(c.GetFiles())
if err != nil {
return manifest.OSTreeDeploymentCustomizations{}, err
}
language, keyboard := c.GetPrimaryLocale()
if language != nil {
deploymentConf.Locale = *language
} else if imageConfig.Locale != nil {
deploymentConf.Locale = *imageConfig.Locale
}
if keyboard != nil {
deploymentConf.Keyboard = *keyboard
} else if imageConfig.Keyboard != nil {
deploymentConf.Keyboard = imageConfig.Keyboard.Keymap
}
if imageConfig.OSTreeConfSysrootReadOnly != nil {
deploymentConf.SysrootReadOnly = *imageConfig.OSTreeConfSysrootReadOnly
}
if imageConfig.LockRootUser != nil {
deploymentConf.LockRoot = *imageConfig.LockRootUser
}
for _, fs := range c.GetFilesystems() {
deploymentConf.CustomFileSystems = append(deploymentConf.CustomFileSystems, fs.Mountpoint)
}
return deploymentConf, nil
}
// IMAGES
func diskImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewDiskImage()
img.Platform = t.platform
var err error
img.OSCustomizations, err = osCustomizations(t, packageSets[osPkgsKey], containers, bp.Customizations)
if err != nil {
return nil, err
}
img.Environment = &t.ImageTypeYAML.Environment
img.Workload = workload
img.Compression = t.ImageTypeYAML.Compression
if bp.Minimal {
// Disable weak dependencies if the 'minimal' option is enabled
img.OSCustomizations.InstallWeakDeps = false
}
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(bp.Customizations, options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
return img, nil
}
func tarImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewArchive()
img.Platform = t.platform
var err error
img.OSCustomizations, err = osCustomizations(t, packageSets[osPkgsKey], containers, bp.Customizations)
if err != nil {
return nil, err
}
img.Environment = &t.ImageTypeYAML.Environment
img.Workload = workload
img.Filename = t.Filename()
return img, nil
}
func containerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewBaseContainer()
img.Platform = t.platform
var err error
img.OSCustomizations, err = osCustomizations(t, packageSets[osPkgsKey], containers, bp.Customizations)
if err != nil {
return nil, err
}
img.Environment = &t.ImageTypeYAML.Environment
img.Workload = workload
img.Filename = t.Filename()
return img, nil
}
func liveInstallerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewAnacondaLiveInstaller()
img.Platform = t.platform
img.Workload = workload
img.ExtraBasePackages = packageSets[installerPkgsKey]
d := t.arch.distro
img.Product = d.Product()
img.Variant = "Workstation"
img.OSVersion = d.OsVersion()
img.Release = fmt.Sprintf("%s %s", d.DistroYAML.Product, d.OsVersion())
img.Preview = d.DistroYAML.Preview
var err error
img.ISOLabel, err = t.ISOLabel()
if err != nil {
return nil, err
}
img.Filename = t.Filename()
// Enable grub2 BIOS iso on x86_64 only
if img.Platform.GetArch() == arch.ARCH_X86_64 {
img.ISOBoot = manifest.Grub2ISOBoot
}
if locale := t.getDefaultImageConfig().Locale; locale != nil {
img.Locale = *locale
}
installerConfig, err := t.getDefaultInstallerConfig()
if err != nil {
return nil, err
}
if installerConfig != nil {
img.AdditionalDracutModules = append(img.AdditionalDracutModules, installerConfig.AdditionalDracutModules...)
img.AdditionalDrivers = append(img.AdditionalDrivers, installerConfig.AdditionalDrivers...)
if installerConfig.SquashfsRootfs != nil && *installerConfig.SquashfsRootfs {
img.RootfsType = manifest.SquashfsRootfs
}
}
imgConfig := t.getDefaultImageConfig()
if imgConfig != nil && imgConfig.IsoRootfsType != nil {
img.RootfsType = *imgConfig.IsoRootfsType
}
return img, nil
}
func imageInstallerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
customizations := bp.Customizations
img := image.NewAnacondaTarInstaller()
var err error
img.OSCustomizations, err = osCustomizations(t, packageSets[osPkgsKey], containers, bp.Customizations)
if err != nil {
return nil, err
}
img.Kickstart, err = kickstart.New(customizations)
if err != nil {
return nil, err
}
img.Kickstart.Language = &img.OSCustomizations.Language
img.Kickstart.Keyboard = img.OSCustomizations.Keyboard
img.Kickstart.Timezone = &img.OSCustomizations.Timezone
if img.Kickstart.Unattended {
// NOTE: this is not supported right now because the
// image-installer on Fedora isn't working when unattended.
// These options are probably necessary but could change.
// Unattended/non-interactive installations are better set to text
// time since they might be running headless and a UI is
// unnecessary.
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.AdditionalAnacondaModules = append(img.AdditionalAnacondaModules, anaconda.ModuleUsers)
img.Platform = t.platform
img.Workload = workload
img.ExtraBasePackages = packageSets[installerPkgsKey]
installerConfig, err := t.getDefaultInstallerConfig()
if err != nil {
return nil, err
}
if installerConfig != nil {
img.AdditionalDracutModules = append(img.AdditionalDracutModules, installerConfig.AdditionalDracutModules...)
img.AdditionalDrivers = append(img.AdditionalDrivers, installerConfig.AdditionalDrivers...)
if installerConfig.SquashfsRootfs != nil && *installerConfig.SquashfsRootfs {
img.RootfsType = manifest.SquashfsRootfs
}
}
d := t.arch.distro
img.Product = d.DistroYAML.Product
img.OSVersion = d.OsVersion()
img.Release = fmt.Sprintf("%s %s", d.DistroYAML.Product, d.OsVersion())
img.Variant = t.Variant
img.Preview = d.DistroYAML.Preview
img.ISOLabel, err = t.ISOLabel()
if err != nil {
return nil, err
}
img.Filename = t.Filename()
img.RootfsCompression = "xz" // This also triggers using the bcj filter
imgConfig := t.getDefaultImageConfig()
if imgConfig != nil && imgConfig.IsoRootfsType != nil {
img.RootfsType = *imgConfig.IsoRootfsType
}
// Enable grub2 BIOS iso on x86_64 only
if img.Platform.GetArch() == arch.ARCH_X86_64 {
img.ISOBoot = manifest.Grub2ISOBoot
}
return img, nil
}
func iotCommitImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
parentCommit, commitRef := makeOSTreeParentCommit(options.OSTree, t.OSTreeRef())
img := image.NewOSTreeArchive(commitRef)
d := t.arch.distro
img.Platform = t.platform
var err error
img.OSCustomizations, err = osCustomizations(t, packageSets[osPkgsKey], containers, bp.Customizations)
if err != nil {
return nil, err
}
// see https://github.com/ostreedev/ostree/issues/2840
img.OSCustomizations.Presets = []osbuild.Preset{
{
Name: "ignition-firstboot-complete.service",
State: osbuild.StateEnable,
},
{
Name: "coreos-ignition-write-issues.service",
State: osbuild.StateEnable,
},
{
Name: "fdo-client-linuxapp.service",
State: osbuild.StateEnable,
},
}
img.Environment = &t.ImageTypeYAML.Environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = d.OsVersion()
img.Filename = t.Filename()
img.InstallWeakDeps = false
return img, nil
}
func bootableContainerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
parentCommit, commitRef := makeOSTreeParentCommit(options.OSTree, t.OSTreeRef())
img := image.NewOSTreeArchive(commitRef)
d := t.arch.distro
img.Platform = t.platform
var err error
img.OSCustomizations, err = osCustomizations(t, packageSets[osPkgsKey], containers, bp.Customizations)
if err != nil {
return nil, err
}
img.Environment = &t.ImageTypeYAML.Environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = d.OsVersion()
img.Filename = t.Filename()
img.InstallWeakDeps = false
img.BootContainer = true
id, err := distro.ParseID(d.Name())
if err != nil {
return nil, err
}
img.BootcConfig = &bootc.Config{
Filename: fmt.Sprintf("20-%s.toml", id.Name),
RootFilesystemType: "ext4",
}
return img, nil
}
func iotContainerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
parentCommit, commitRef := makeOSTreeParentCommit(options.OSTree, t.OSTreeRef())
img := image.NewOSTreeContainer(commitRef)
d := t.arch.distro
img.Platform = t.platform
var err error
img.OSCustomizations, err = osCustomizations(t, packageSets[osPkgsKey], containers, bp.Customizations)
if err != nil {
return nil, err
}
// see https://github.com/ostreedev/ostree/issues/2840
img.OSCustomizations.Presets = []osbuild.Preset{
{
Name: "ignition-firstboot-complete.service",
State: osbuild.StateEnable,
},
{
Name: "coreos-ignition-write-issues.service",
State: osbuild.StateEnable,
},
{
Name: "fdo-client-linuxapp.service",
State: osbuild.StateEnable,
},
}
img.ContainerLanguage = img.OSCustomizations.Language
img.Environment = &t.ImageTypeYAML.Environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = d.OsVersion()
img.ExtraContainerPackages = packageSets[containerPkgsKey]
img.Filename = t.Filename()
return img, nil
}
func iotInstallerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
d := t.arch.distro
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
img := image.NewAnacondaOSTreeInstaller(commit)
customizations := bp.Customizations
img.FIPS = customizations.GetFIPS()
img.Platform = t.platform
img.ExtraBasePackages = packageSets[installerPkgsKey]
img.Kickstart, err = kickstart.New(customizations)
if err != nil {
return nil, err
}
img.Kickstart.OSTree = &kickstart.OSTree{
OSName: t.OSTree.Name,
Remote: t.OSTree.Remote,
}
img.Kickstart.Path = osbuild.KickstartPathOSBuild
img.Kickstart.Language, img.Kickstart.Keyboard = customizations.GetPrimaryLocale()
// ignore ntp servers - we don't currently support setting these in the
// kickstart though kickstart does support setting them
img.Kickstart.Timezone, _ = customizations.GetTimezoneSettings()
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,
}...)
installerConfig, err := t.getDefaultInstallerConfig()
if err != nil {
return nil, err
}
if installerConfig != nil {
img.AdditionalDracutModules = append(img.AdditionalDracutModules, installerConfig.AdditionalDracutModules...)
img.AdditionalDrivers = append(img.AdditionalDrivers, installerConfig.AdditionalDrivers...)
if installerConfig.SquashfsRootfs != nil && *installerConfig.SquashfsRootfs {
img.RootfsType = manifest.SquashfsRootfs
}
}
// On Fedora anaconda needs dbus-broker, but isn't added when dracut runs.
img.AdditionalDracutModules = append(img.AdditionalDracutModules, "dbus-broker")
img.Product = d.DistroYAML.Product
img.Variant = "IoT"
img.OSVersion = d.OsVersion()
img.Release = fmt.Sprintf("%s %s", d.DistroYAML.Product, d.OsVersion())
img.Preview = d.DistroYAML.Preview
img.ISOLabel, err = t.ISOLabel()
if err != nil {
return nil, err
}
img.Filename = t.Filename()
img.RootfsCompression = "xz" // This also triggers using the bcj filter
imgConfig := t.getDefaultImageConfig()
if imgConfig != nil && imgConfig.IsoRootfsType != nil {
img.RootfsType = *imgConfig.IsoRootfsType
}
// Enable grub2 BIOS iso on x86_64 only
if img.Platform.GetArch() == arch.ARCH_X86_64 {
img.ISOBoot = manifest.Grub2ISOBoot
}
if locale := t.getDefaultImageConfig().Locale; locale != nil {
img.Locale = *locale
}
return img, nil
}
func iotImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
img := image.NewOSTreeDiskImageFromCommit(commit)
customizations := bp.Customizations
deploymentConfig, err := ostreeDeploymentCustomizations(t, customizations)
if err != nil {
return nil, err
}
img.OSTreeDeploymentCustomizations = deploymentConfig
img.Platform = t.platform
img.Workload = workload
img.Remote = ostree.Remote{
Name: t.OSTree.Remote,
}
img.OSName = t.OSTree.Remote
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations, options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
img.Compression = t.ImageTypeYAML.Compression
return img, nil
}
func iotSimplifiedInstallerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
rawImg := image.NewOSTreeDiskImageFromCommit(commit)
customizations := bp.Customizations
deploymentConfig, err := ostreeDeploymentCustomizations(t, customizations)
if err != nil {
return nil, err
}
rawImg.OSTreeDeploymentCustomizations = deploymentConfig
rawImg.Platform = t.platform
rawImg.Workload = workload
rawImg.Remote = ostree.Remote{
Name: t.OSTree.Remote,
}
rawImg.OSName = t.OSTree.Name
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations, options, rng)
if err != nil {
return nil, err
}
rawImg.PartitionTable = pt
rawImg.Filename = t.Filename()
img := image.NewOSTreeSimplifiedInstaller(rawImg, customizations.InstallationDevice)
img.ExtraBasePackages = packageSets[installerPkgsKey]
// img.Workload = workload
img.Platform = t.platform
img.Filename = t.Filename()
if bpFDO := customizations.GetFDO(); bpFDO != nil {
img.FDO = fdo.FromBP(*bpFDO)
}
// ignition configs from blueprint
if bpIgnition := customizations.GetIgnition(); bpIgnition != nil {
if bpIgnition.Embedded != nil {
var err error
img.IgnitionEmbedded, err = ignition.EmbeddedOptionsFromBP(*bpIgnition.Embedded)
if err != nil {
return nil, err
}
}
}
installerConfig, err := t.getDefaultInstallerConfig()
if err != nil {
return nil, err
}
if installerConfig != nil {
img.AdditionalDracutModules = append(img.AdditionalDracutModules, installerConfig.AdditionalDracutModules...)
img.AdditionalDrivers = append(img.AdditionalDrivers, installerConfig.AdditionalDrivers...)
}
img.AdditionalDracutModules = append(img.AdditionalDracutModules, "dbus-broker")
d := t.arch.distro
img.Product = d.DistroYAML.Product
img.Variant = "IoT"
img.OSName = t.OSTree.Name
img.OSVersion = d.OsVersion()
img.ISOLabel, err = t.ISOLabel()
if err != nil {
return nil, err
}
return img, nil
}
// Create an ostree SourceSpec to define an ostree parent commit using the user
// options and the default ref for the image type. Additionally returns the
// ref to be used for the new commit to be created.
func makeOSTreeParentCommit(options *ostree.ImageOptions, defaultRef string) (*ostree.SourceSpec, string) {
commitRef := defaultRef
if options == nil {
// nothing to do
return nil, commitRef
}
if options.ImageRef != "" {
// user option overrides default commit ref
commitRef = options.ImageRef
}
var parentCommit *ostree.SourceSpec
if options.URL == "" {
// no parent
return nil, commitRef
}
// ostree URL specified: set source spec for parent commit
parentRef := options.ParentRef
if parentRef == "" {
// parent ref not set: use image ref
parentRef = commitRef
}
parentCommit = &ostree.SourceSpec{
URL: options.URL,
Ref: parentRef,
RHSM: options.RHSM,
}
return parentCommit, commitRef
}
// Create an ostree SourceSpec to define an ostree payload using the user options and the default ref for the image type.
func makeOSTreePayloadCommit(options *ostree.ImageOptions, defaultRef string) (ostree.SourceSpec, error) {
if options == nil || options.URL == "" {
// this should be caught by checkOptions() in distro, but it's good
// to guard against it here as well
return ostree.SourceSpec{}, fmt.Errorf("ostree commit URL required")
}
commitRef := defaultRef
if options.ImageRef != "" {
// user option overrides default commit ref
commitRef = options.ImageRef
}
return ostree.SourceSpec{
URL: options.URL,
Ref: commitRef,
RHSM: options.RHSM,
}, nil
}

View file

@ -0,0 +1,515 @@
package generic
import (
"errors"
"fmt"
"math/rand"
"strings"
"slices"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/customizations/oscap"
"github.com/osbuild/images/pkg/datasizes"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/defs"
"github.com/osbuild/images/pkg/experimentalflags"
"github.com/osbuild/images/pkg/image"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/policies"
"github.com/osbuild/images/pkg/rpmmd"
)
type imageFunc func(workload workload.Workload, t *imageType, bp *blueprint.Blueprint, options distro.ImageOptions, packageSets map[string]rpmmd.PackageSet, containers []container.SourceSpec, rng *rand.Rand) (image.ImageKind, error)
type isoLabelFunc func(t *imageType) string
// imageType implements the distro.ImageType interface
var _ = distro.ImageType(&imageType{})
type imageType struct {
defs.ImageTypeYAML
arch *architecture
platform platform.Platform
// XXX: make definable via YAML
workload workload.Workload
// XXX: make member function ImageTypeYAML
defaultImageConfig *distro.ImageConfig
defaultInstallerConfig *distro.InstallerConfig
image imageFunc
isoLabel isoLabelFunc
}
func (t *imageType) Name() string {
return t.ImageTypeYAML.Name()
}
func (t *imageType) Arch() distro.Arch {
return t.arch
}
func (t *imageType) Filename() string {
return t.ImageTypeYAML.Filename
}
func (t *imageType) MIMEType() string {
return t.ImageTypeYAML.MimeType
}
func (t *imageType) OSTreeRef() string {
d := t.arch.distro
if t.ImageTypeYAML.RPMOSTree {
return fmt.Sprintf(d.OSTreeRef(), t.arch.Name())
}
return ""
}
func (t *imageType) ISOLabel() (string, error) {
if !t.ImageTypeYAML.BootISO {
return "", fmt.Errorf("image type %q is not an ISO", t.Name())
}
if t.isoLabel != nil {
return t.isoLabel(t), nil
}
return "", nil
}
func (t *imageType) Size(size uint64) uint64 {
// Microsoft Azure requires vhd images to be rounded up to the nearest MB
if t.ImageTypeYAML.Name() == "vhd" && size%datasizes.MebiByte != 0 {
size = (size/datasizes.MebiByte + 1) * datasizes.MebiByte
}
if size == 0 {
size = t.ImageTypeYAML.DefaultSize
}
return size
}
func (t *imageType) BuildPipelines() []string {
return t.ImageTypeYAML.BuildPipelines
}
func (t *imageType) PayloadPipelines() []string {
return t.ImageTypeYAML.PayloadPipelines
}
func (t *imageType) PayloadPackageSets() []string {
return []string{blueprintPkgsKey}
}
func (t *imageType) Exports() []string {
if len(t.ImageTypeYAML.Exports) > 0 {
return t.ImageTypeYAML.Exports
}
return []string{"assembler"}
}
func (t *imageType) BootMode() platform.BootMode {
if t.platform.GetUEFIVendor() != "" && t.platform.GetBIOSPlatform() != "" {
return platform.BOOT_HYBRID
} else if t.platform.GetUEFIVendor() != "" {
return platform.BOOT_UEFI
} else if t.platform.GetBIOSPlatform() != "" || t.platform.GetZiplSupport() {
return platform.BOOT_LEGACY
}
return platform.BOOT_NONE
}
func (t *imageType) BasePartitionTable() (*disk.PartitionTable, error) {
return defs.PartitionTable(t)
}
func (t *imageType) getPartitionTable(customizations *blueprint.Customizations, options distro.ImageOptions, rng *rand.Rand) (*disk.PartitionTable, error) {
basePartitionTable, err := t.BasePartitionTable()
if err != nil {
return nil, err
}
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
}
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: t.arch.distro.DefaultFSType,
RequiredMinSizes: t.ImageTypeYAML.RequiredPartitionSizes,
Architecture: t.platform.GetArch(),
}
return disk.NewCustomPartitionTable(partitioning, partOptions, rng)
}
partitioningMode := options.PartitioningMode
if t.ImageTypeYAML.RPMOSTree {
// IoT supports only LVM, force it.
// Raw is not supported, return an error if it is requested
// TODO Need a central location for logic like this
if partitioningMode == disk.RawPartitioningMode {
return nil, fmt.Errorf("partitioning mode raw not supported for %s on %s", t.Name(), t.arch.Name())
}
partitioningMode = disk.AutoLVMPartitioningMode
}
mountpoints := customizations.GetFilesystems()
return disk.NewPartitionTable(basePartitionTable, mountpoints, imageSize, partitioningMode, t.platform.GetArch(), t.ImageTypeYAML.RequiredPartitionSizes, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
// ensure that image always returns non-nil default config
imageConfig := t.defaultImageConfig
if imageConfig == nil {
imageConfig = &distro.ImageConfig{}
}
return imageConfig.InheritFrom(t.arch.distro.defaultImageConfig)
}
func (t *imageType) getDefaultInstallerConfig() (*distro.InstallerConfig, error) {
if !t.ImageTypeYAML.BootISO {
return nil, fmt.Errorf("image type %q is not an ISO", t.Name())
}
return t.defaultInstallerConfig, nil
}
func (t *imageType) PartitionType() disk.PartitionTableType {
basePartitionTable, err := t.BasePartitionTable()
if errors.Is(err, defs.ErrNoPartitionTableForImgType) {
return disk.PT_NONE
}
if err != nil {
panic(err)
}
return basePartitionTable.Type
}
func (t *imageType) Manifest(bp *blueprint.Blueprint,
options distro.ImageOptions,
repos []rpmmd.RepoConfig,
seedp *int64) (*manifest.Manifest, []string, error) {
seed := distro.SeedFrom(seedp)
warnings, err := t.checkOptions(bp, options)
if err != nil {
return nil, nil, err
}
// merge package sets that appear in the image type with the package sets
// of the same name from the distro and arch
staticPackageSets := make(map[string]rpmmd.PackageSet)
// don't add any static packages if Minimal was selected
if !bp.Minimal {
pkgSets, err := defs.PackageSets(t)
if err != nil {
return nil, nil, err
}
for name, pkgSet := range pkgSets {
staticPackageSets[name] = pkgSet
}
}
// amend with repository information and collect payload repos
payloadRepos := make([]rpmmd.RepoConfig, 0)
for _, repo := range repos {
if len(repo.PackageSets) > 0 {
// only apply the repo to the listed package sets
for _, psName := range repo.PackageSets {
if slices.Contains(t.PayloadPackageSets(), psName) {
payloadRepos = append(payloadRepos, repo)
}
ps := staticPackageSets[psName]
ps.Repositories = append(ps.Repositories, repo)
staticPackageSets[psName] = ps
}
}
}
w := t.workload
if w == nil {
// XXX: this needs to get duplicaed in exactly the same
// way in rhel/imagetype.go
workloadRepos := payloadRepos
customRepos, err := bp.Customizations.GetRepositories()
if err != nil {
return nil, nil, err
}
installFromRepos := blueprint.RepoCustomizationsInstallFromOnly(customRepos)
workloadRepos = append(workloadRepos, installFromRepos...)
cw := &workload.Custom{
BaseWorkload: workload.BaseWorkload{
Repos: workloadRepos,
},
Packages: bp.GetPackagesEx(false),
EnabledModules: bp.GetEnabledModules(),
}
if services := bp.Customizations.GetServices(); services != nil {
cw.Services = services.Enabled
cw.DisabledServices = services.Disabled
cw.MaskedServices = services.Masked
}
w = cw
}
if experimentalflags.Bool("no-fstab") {
if t.defaultImageConfig == nil {
t.defaultImageConfig = &distro.ImageConfig{
MountUnits: common.ToPtr(true),
}
} else {
t.defaultImageConfig.MountUnits = common.ToPtr(true)
}
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx, cont := range bp.Containers {
containerSources[idx] = container.SourceSpec{
Source: cont.Source,
Name: cont.Name,
TLSVerify: cont.TLSVerify,
Local: cont.LocalStorage,
}
}
source := rand.NewSource(seed)
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(source)
img, err := t.image(w, t, bp, options, staticPackageSets, containerSources, rng)
if err != nil {
return nil, nil, err
}
mf := manifest.New()
mf.Distro = manifest.DISTRO_FEDORA
if options.UseBootstrapContainer {
mf.DistroBootstrapRef = bootstrapContainerFor(t)
}
_, err = img.InstantiateManifest(&mf, repos, &t.arch.distro.DistroYAML.Runner, rng)
if err != nil {
return nil, nil, err
}
return &mf, warnings, err
}
// 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.ImageTypeYAML.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.ImageTypeYAML.RPMOSTree && (t.Name() != "iot-commit" && t.Name() != "iot-container") {
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 warnings, err
}
}
if t.ImageTypeYAML.BootISO && t.ImageTypeYAML.RPMOSTree {
// ostree-based ISOs require a URL from which to pull a payload commit
if options.OSTree == nil || options.OSTree.URL == "" {
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-xz" || t.Name() == "iot-qcow2" {
allowed := []string{"User", "Group", "Directories", "Files", "Services", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
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
}
// BootISOs have limited support for customizations.
// TODO: Support kernel name selection for image-installer
if t.ImageTypeYAML.BootISO {
if t.Name() == "iot-simplified-installer" {
allowed := []string{"InstallationDevice", "FDO", "Ignition", "Kernel", "User", "Group", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.Name(), strings.Join(allowed, ", "))
}
if customizations.GetInstallationDevice() == "" {
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 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 != "" {
diunSet++
}
if customizations.GetFDO().DiunPubKeyInsecure != "" {
diunSet++
}
if customizations.GetFDO().DiunPubKeyRootCerts != "" {
diunSet++
}
if diunSet != 1 {
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 warnings, fmt.Errorf("both ignition embedded and firstboot configurations found")
}
if customizations.GetIgnition().FirstBoot != nil && customizations.GetIgnition().FirstBoot.ProvisioningURL == "" {
return warnings, fmt.Errorf("ignition.firstboot requires a provisioning url")
}
}
} else if t.Name() == "iot-installer" || t.Name() == "minimal-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 warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.Name(), strings.Join(allowed, ", "))
}
} else if t.Name() == "workstation-live-installer" {
allowed := []string{"Installer"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return warnings, fmt.Errorf(distro.NoCustomizationsAllowedError, t.Name())
}
}
}
if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.ImageTypeYAML.RPMOSTree {
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 warnings, err
}
if (len(mountpoints) > 0 || partitioning != nil) && t.ImageTypeYAML.RPMOSTree {
return warnings, fmt.Errorf("Custom mountpoints and partitioning are not supported for ostree types")
}
if len(mountpoints) > 0 && partitioning != nil {
return warnings, 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 nil, err
}
if osc := customizations.GetOpenSCAP(); osc != nil {
supported := oscap.IsProfileAllowed(osc.ProfileID, t.arch.distro.DistroYAML.OscapProfilesAllowList)
if !supported {
return warnings, fmt.Errorf("OpenSCAP unsupported profile: %s", osc.ProfileID)
}
if t.ImageTypeYAML.RPMOSTree {
return warnings, fmt.Errorf("OpenSCAP customizations are not supported for ostree types")
}
if osc.ProfileID == "" {
return warnings, fmt.Errorf("OpenSCAP profile cannot be empty")
}
}
// Check Directory/File Customizations are valid
dc := customizations.GetDirectories()
fc := customizations.GetFiles()
err = blueprint.ValidateDirFileCustomizations(dc, fc)
if err != nil {
return warnings, err
}
dcp := policies.CustomDirectoriesPolicies
fcp := policies.CustomFilesPolicies
if t.ImageTypeYAML.RPMOSTree {
dcp = policies.OstreeCustomDirectoriesPolicies
fcp = policies.OstreeCustomFilesPolicies
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, dcp)
if err != nil {
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, fcp)
if err != nil {
return warnings, err
}
// check if repository customizations are valid
_, err = customizations.GetRepositories()
if err != nil {
return warnings, err
}
if customizations.GetFIPS() && !common.IsBuildHostFIPSEnabled() {
warnings = append(warnings, fmt.Sprintln(common.FIPSEnabledImageWarning))
}
instCust, err := customizations.GetInstaller()
if err != nil {
return warnings, err
}
if instCust != nil {
// only supported by the Anaconda installer
if slices.Index([]string{"iot-installer"}, t.Name()) == -1 {
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
// let's keep it explicit in case one of the two changes.
// The kickstart contents is incompatible with the users and groups
// customization only for the iot-installer.
if t.Name() == "iot-installer" &&
instCust.Kickstart != nil &&
len(instCust.Kickstart.Contents) > 0 &&
(customizations.GetUsers() != nil || customizations.GetGroups() != nil) {
return warnings, fmt.Errorf("iot-installer installer.kickstart.contents are not supported in combination with users or groups")
}
}
return warnings, nil
}
func bootstrapContainerFor(t *imageType) string {
a := common.Must(arch.FromString(t.arch.name))
return t.arch.distro.DistroYAML.BootstrapContainers[a]
}