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

Includes modularity support.
This commit is contained in:
Sanne Raymaekers 2025-03-10 11:29:18 +01:00 committed by Tomáš Hozza
parent 5e3d6aff54
commit 536b7d95c5
37 changed files with 655 additions and 157 deletions

View file

@ -73,6 +73,7 @@ type crBlueprint struct {
Version string `json:"version,omitempty"`
Packages []blueprint.Package `json:"packages,omitempty"`
Modules []blueprint.Package `json:"modules,omitempty"`
EnabledModules []blueprint.EnabledModule `json:"enabled_modules" toml:"enabled_modules"`
Groups []blueprint.Group `json:"groups,omitempty"`
Containers []blueprint.Container `json:"containers,omitempty"`
Customizations *blueprint.Customizations `json:"customizations,omitempty"`

2
go.mod
View file

@ -44,7 +44,7 @@ require (
github.com/labstack/gommon v0.4.2
github.com/openshift-online/ocm-sdk-go v0.1.438
github.com/oracle/oci-go-sdk/v54 v54.0.0
github.com/osbuild/images v0.120.0
github.com/osbuild/images v0.123.0
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d
github.com/osbuild/pulp-client v0.1.0
github.com/pkg/errors v0.9.1

4
go.sum
View file

@ -547,8 +547,8 @@ github.com/openshift-online/ocm-sdk-go v0.1.438 h1:tsLCCUzbLCTL4RZG02y9RuopmGCXp
github.com/openshift-online/ocm-sdk-go v0.1.438/go.mod h1:CiAu2jwl3ITKOxkeV0Qnhzv4gs35AmpIzVABQLtcI2Y=
github.com/oracle/oci-go-sdk/v54 v54.0.0 h1:CDLjeSejv2aDpElAJrhKpi6zvT/zhZCZuXchUUZ+LS4=
github.com/oracle/oci-go-sdk/v54 v54.0.0/go.mod h1:+t+yvcFGVp+3ZnztnyxqXfQDsMlq8U25faBLa+mqCMc=
github.com/osbuild/images v0.120.0 h1:6zXCp59AG03qajZlg/GJ07Fr4E6z5qaZshOuWgAse7g=
github.com/osbuild/images v0.120.0/go.mod h1:Ag87vmyxooiPQBJEDILbypG8/SRIear75YA78NwLix0=
github.com/osbuild/images v0.123.0 h1:9b2sfl6751dpAEU3wR0bMN1d/bEhbJ39N5a/9ZVCxcg=
github.com/osbuild/images v0.123.0/go.mod h1:Ag87vmyxooiPQBJEDILbypG8/SRIear75YA78NwLix0=
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d h1:r9BFPDv0uuA9k1947Jybcxs36c/pTywWS1gjeizvtcQ=
github.com/osbuild/osbuild-composer/pkg/splunk_logger v0.0.0-20240814102216-0239db53236d/go.mod h1:zR1iu/hOuf+OQNJlk70tju9IqzzM4ycq0ectkFBm94U=
github.com/osbuild/pulp-client v0.1.0 h1:L0C4ezBJGTamN3BKdv+rKLuq/WxXJbsFwz/Hj7aEmJ8=

View file

@ -21,6 +21,10 @@ type RepositoryCustomization struct {
SSLVerify *bool `json:"sslverify,omitempty" toml:"sslverify,omitempty"`
ModuleHotfixes *bool `json:"module_hotfixes,omitempty" toml:"module_hotfixes,omitempty"`
Filename string `json:"filename,omitempty" toml:"filename,omitempty"`
// When set the repository will be used during the depsolve of
// payload repositories to install packages from it.
InstallFrom bool `json:"install_from" toml:"install_from"`
}
const repoFilenameRegex = "^[\\w.-]{1,250}\\.repo$"

View file

@ -0,0 +1,14 @@
package dependencies
import (
_ "embed"
)
//go:embed osbuild
var minimumOSBuildVersion string
// MinimumOSBuildVersion returns the minimum version of osbuild required by this
// version of images module.
func MinimumOSBuildVersion() string {
return minimumOSBuildVersion
}

View file

@ -0,0 +1 @@
139

View file

@ -3,6 +3,7 @@ package workload
type Custom struct {
BaseWorkload
Packages []string
EnabledModules []string
Services []string
DisabledServices []string
}
@ -11,6 +12,10 @@ func (p *Custom) GetPackages() []string {
return p.Packages
}
func (p *Custom) GetEnabledModules() []string {
return p.EnabledModules
}
func (p *Custom) GetServices() []string {
return p.Services
}

View file

@ -4,6 +4,7 @@ import "github.com/osbuild/images/pkg/rpmmd"
type Workload interface {
GetPackages() []string
GetEnabledModules() []string
GetRepos() []rpmmd.RepoConfig
GetServices() []string
GetDisabledServices() []string
@ -17,6 +18,10 @@ func (p BaseWorkload) GetPackages() []string {
return []string{}
}
func (p BaseWorkload) GetEnabledModules() []string {
return []string{}
}
func (p BaseWorkload) GetRepos() []rpmmd.RepoConfig {
return p.Repos
}

View file

@ -3,11 +3,16 @@ package blueprint
// A Blueprint is a high-level description of an image.
type Blueprint struct {
Name string `json:"name" toml:"name"`
Description string `json:"description" toml:"description"`
Version string `json:"version,omitempty" toml:"version,omitempty"`
Packages []Package `json:"packages" toml:"packages"`
Modules []Package `json:"modules" toml:"modules"`
Name string `json:"name" toml:"name"`
Description string `json:"description" toml:"description"`
Version string `json:"version,omitempty" toml:"version,omitempty"`
Packages []Package `json:"packages" toml:"packages"`
Modules []Package `json:"modules" toml:"modules"`
// Note, this is called "enabled modules" because we already have "modules" except
// the "modules" refers to packages and "enabled modules" refers to modularity modules.
EnabledModules []EnabledModule `json:"enabled_modules" toml:"enabled_modules"`
Groups []Group `json:"groups" toml:"groups"`
Containers []Container `json:"containers,omitempty" toml:"containers,omitempty"`
Customizations *Customizations `json:"customizations,omitempty" toml:"customizations"`
@ -23,6 +28,12 @@ type Package struct {
Version string `json:"version,omitempty" toml:"version,omitempty"`
}
// A module specifies a modularity stream.
type EnabledModule struct {
Name string `json:"name" toml:"name"`
Stream string `json:"stream,omitempty" toml:"stream,omitempty"`
}
// A group specifies an package group.
type Group struct {
Name string `json:"name" toml:"name"`
@ -63,6 +74,16 @@ func (b *Blueprint) GetPackagesEx(bootable bool) []string {
return packages
}
func (b *Blueprint) GetEnabledModules() []string {
modules := []string{}
for _, mod := range b.EnabledModules {
modules = append(modules, mod.ToNameStream())
}
return modules
}
func (p Package) ToNameVersion() string {
// Omit version to prevent all packages with prefix of name to be installed
if p.Version == "*" || p.Version == "" {
@ -71,3 +92,7 @@ func (p Package) ToNameVersion() string {
return p.Name + "-" + p.Version
}
func (p EnabledModule) ToNameStream() string {
return p.Name + ":" + p.Stream
}

View file

@ -376,8 +376,8 @@ func (c *Customizations) GetRepositories() ([]RepositoryCustomization, error) {
return nil, nil
}
for idx := range c.Repositories {
err := validateCustomRepository(&c.Repositories[idx])
for _, repo := range c.Repositories {
err := validateCustomRepository(&repo)
if err != nil {
return nil, err
}

View file

@ -25,6 +25,10 @@ type RepositoryCustomization struct {
SSLVerify *bool `json:"sslverify,omitempty" toml:"sslverify,omitempty"`
ModuleHotfixes *bool `json:"module_hotfixes,omitempty" toml:"module_hotfixes,omitempty"`
Filename string `json:"filename,omitempty" toml:"filename,omitempty"`
// When set the repository will be used during the depsolve of
// payload repositories to install packages from it.
InstallFrom bool `json:"install_from" toml:"install_from"`
}
const repoFilenameRegex = "^[\\w.-]{1,250}\\.repo$"
@ -76,6 +80,17 @@ func (rc *RepositoryCustomization) getFilename() string {
return rc.Filename
}
func RepoCustomizationsInstallFromOnly(repos []RepositoryCustomization) []rpmmd.RepoConfig {
var res []rpmmd.RepoConfig
for _, repo := range repos {
if !repo.InstallFrom {
continue
}
res = append(res, repo.customRepoToRepoConfig())
}
return res
}
func RepoCustomizationsToRepoConfigAndGPGKeyFiles(repos []RepositoryCustomization) (map[string][]rpmmd.RepoConfig, []*fsnode.File, error) {
if len(repos) == 0 {
return nil, nil, nil

View file

@ -57,35 +57,11 @@ var (
"/": 1 * datasizes.GiB,
"/usr": 2 * datasizes.GiB,
}
)
// Services
iotServices = []string{
"NetworkManager.service",
"firewalld.service",
"sshd.service",
"zezere_ignition.timer",
"zezere_ignition_banner.service",
"greenboot-grub2-set-counter",
"greenboot-grub2-set-success",
"greenboot-healthcheck",
"greenboot-rpm-ostree-grub2-check-fallback",
"greenboot-status",
"greenboot-task-runner",
"redboot-auto-reboot",
"redboot-task-runner",
"parsec",
"dbus-parsec",
}
minimalRawServices = []string{
"NetworkManager.service",
"firewalld.service",
"initial-setup.service",
"sshd.service",
}
// Image Definitions
imageInstallerImgType = imageType{
// Image Definitions
func mkImageInstallerImgType(d distribution) imageType {
return imageType{
name: "image-installer",
nameAliases: []string{"fedora-image-installer"},
filename: "installer.iso",
@ -93,11 +69,7 @@ var (
packageSets: map[string]packageSetFunc{
osPkgsKey: func(t *imageType) rpmmd.PackageSet {
// use the minimal raw image type for the OS package set
ft := &imageType{
name: "minimal-raw",
arch: t.arch,
}
return packagesets.Load(ft, VersionReplacements())
return packagesets.Load(t, "minimal-raw", VersionReplacements())
},
installerPkgsKey: packageSetLoader,
},
@ -115,8 +87,10 @@ var (
exports: []string{"bootiso"},
requiredPartitionSizes: requiredDirectorySizes,
}
}
liveInstallerImgType = imageType{
func mkLiveInstallerImgType(d distribution) imageType {
return imageType{
name: "live-installer",
nameAliases: []string{},
filename: "live-installer.iso",
@ -137,8 +111,10 @@ var (
exports: []string{"bootiso"},
requiredPartitionSizes: requiredDirectorySizes,
}
}
iotCommitImgType = imageType{
func mkIotCommitImgType(d distribution) imageType {
return imageType{
name: "iot-commit",
nameAliases: []string{"fedora-iot-commit"},
filename: "commit.tar",
@ -147,8 +123,9 @@ var (
osPkgsKey: packageSetLoader,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: iotServices,
DracutConf: []*osbuild.DracutConfStageOptions{osbuild.FIPSDracutConfStageOptions},
EnabledServices: iotServicesForVersion(&d),
DracutConf: []*osbuild.DracutConfStageOptions{osbuild.FIPSDracutConfStageOptions},
MachineIdUninitialized: common.ToPtr(false),
},
rpmOstree: true,
image: iotCommitImage,
@ -157,14 +134,19 @@ var (
exports: []string{"commit-archive"},
requiredPartitionSizes: requiredDirectorySizes,
}
}
iotBootableContainer = imageType{
func mkIotBootableContainer(d distribution) imageType {
return imageType{
name: "iot-bootable-container",
filename: "iot-bootable-container.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: packageSetLoader,
},
defaultImageConfig: &distro.ImageConfig{
MachineIdUninitialized: common.ToPtr(false),
},
rpmOstree: true,
image: bootableContainerImage,
buildPipelines: []string{"build"},
@ -172,8 +154,10 @@ var (
exports: []string{"ostree-encapsulate"},
requiredPartitionSizes: requiredDirectorySizes,
}
}
iotOCIImgType = imageType{
func mkIotOCIImgType(d distribution) imageType {
return imageType{
name: "iot-container",
nameAliases: []string{"fedora-iot-container"},
filename: "container.tar",
@ -185,8 +169,9 @@ var (
},
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: iotServices,
DracutConf: []*osbuild.DracutConfStageOptions{osbuild.FIPSDracutConfStageOptions},
EnabledServices: iotServicesForVersion(&d),
DracutConf: []*osbuild.DracutConfStageOptions{osbuild.FIPSDracutConfStageOptions},
MachineIdUninitialized: common.ToPtr(false),
},
rpmOstree: true,
bootISO: false,
@ -196,8 +181,10 @@ var (
exports: []string{"container"},
requiredPartitionSizes: requiredDirectorySizes,
}
}
iotInstallerImgType = imageType{
func mkIotInstallerImgType(d distribution) imageType {
return imageType{
name: "iot-installer",
nameAliases: []string{"fedora-iot-installer"},
filename: "installer.iso",
@ -206,8 +193,8 @@ var (
installerPkgsKey: packageSetLoader,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: iotServicesForVersion(&d),
Locale: common.ToPtr("en_US.UTF-8"),
EnabledServices: iotServices,
},
rpmOstree: true,
bootISO: true,
@ -218,8 +205,10 @@ var (
exports: []string{"bootiso"},
requiredPartitionSizes: requiredDirectorySizes,
}
}
iotSimplifiedInstallerImgType = imageType{
func mkIotSimplifiedInstallerImgType(d distribution) imageType {
return imageType{
name: "iot-simplified-installer",
filename: "simplified-installer.iso",
mimeType: "application/x-iso9660-image",
@ -227,7 +216,7 @@ var (
installerPkgsKey: packageSetLoader,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: iotServices,
EnabledServices: iotServicesForVersion(&d),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
@ -249,8 +238,10 @@ var (
kernelOptions: ostreeDeploymentKernelOptions,
requiredPartitionSizes: requiredDirectorySizes,
}
}
iotRawImgType = imageType{
func mkIotRawImgType(d distribution) imageType {
return imageType{
name: "iot-raw-image",
nameAliases: []string{"fedora-iot-raw-image"},
filename: "image.raw.xz",
@ -281,8 +272,10 @@ var (
// override them (and make them smaller, in this case).
requiredPartitionSizes: map[string]uint64{},
}
}
iotQcow2ImgType = imageType{
func mkIotQcow2ImgType(d distribution) imageType {
return imageType{
name: "iot-qcow2-image",
filename: "image.qcow2",
mimeType: "application/x-qemu-disk",
@ -307,8 +300,10 @@ var (
kernelOptions: ostreeDeploymentKernelOptions,
requiredPartitionSizes: requiredDirectorySizes,
}
}
qcow2ImgType = imageType{
func mkQcow2ImgType(d distribution) imageType {
return imageType{
name: "qcow2",
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
@ -329,7 +324,9 @@ var (
basePartitionTables: defaultBasePartitionTables,
requiredPartitionSizes: requiredDirectorySizes,
}
}
var (
vmdkDefaultImageConfig = &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
EnabledServices: []string{
@ -339,8 +336,10 @@ var (
"cloud-init-local.service",
},
}
)
vmdkImgType = imageType{
func mkVmdkImgType(d distribution) imageType {
return imageType{
name: "vmdk",
filename: "disk.vmdk",
mimeType: "application/x-vmdk",
@ -358,8 +357,10 @@ var (
basePartitionTables: defaultBasePartitionTables,
requiredPartitionSizes: requiredDirectorySizes,
}
}
ovaImgType = imageType{
func mkOvaImgType(d distribution) imageType {
return imageType{
name: "ova",
filename: "image.ova",
mimeType: "application/ovf",
@ -377,8 +378,10 @@ var (
basePartitionTables: defaultBasePartitionTables,
requiredPartitionSizes: requiredDirectorySizes,
}
}
containerImgType = imageType{
func mkContainerImgType(d distribution) imageType {
return imageType{
name: "container",
filename: "container.tar",
mimeType: "application/x-tar",
@ -398,8 +401,10 @@ var (
exports: []string{"container"},
requiredPartitionSizes: requiredDirectorySizes,
}
}
wslImgType = imageType{
func mkWslImgType(d distribution) imageType {
return imageType{
name: "wsl",
filename: "wsl.tar",
mimeType: "application/x-tar",
@ -424,8 +429,10 @@ var (
exports: []string{"container"},
requiredPartitionSizes: requiredDirectorySizes,
}
}
minimalrawImgType = imageType{
func mkMinimalRawImgType(d distribution) imageType {
return imageType{
name: "minimal-raw",
filename: "disk.raw.xz",
compression: "xz",
@ -434,7 +441,7 @@ var (
osPkgsKey: packageSetLoader,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: minimalRawServices,
EnabledServices: minimalServicesForVersion(&d),
// NOTE: temporary workaround for a bug in initial-setup that
// requires a kickstart file in the root directory.
Files: []*fsnode.File{initialSetupKickstart()},
@ -442,6 +449,7 @@ var (
// Overwrite the default Grub2 timeout value.
Timeout: 5,
},
InstallWeakDeps: common.ToPtr(common.VersionLessThan(d.osVersion, VERSION_MINIMAL_WEAKDEPS)),
},
rpmOstree: false,
kernelOptions: defaultKernelOptions,
@ -454,7 +462,7 @@ var (
basePartitionTables: minimalrawPartitionTables,
requiredPartitionSizes: requiredDirectorySizes,
}
)
}
type distribution struct {
name string
@ -473,6 +481,8 @@ var defaultDistroImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("UTC"),
Locale: common.ToPtr("C.UTF-8"),
DefaultOSCAPDatastream: common.ToPtr(oscap.DefaultFedoraDatastream()),
InstallWeakDeps: common.ToPtr(true),
MachineIdUninitialized: common.ToPtr(true),
}
func defaultDistroInstallerConfig(d *distribution) *distro.InstallerConfig {
@ -663,6 +673,8 @@ func newDistro(version int) distro.Distro {
distro: &rd,
}
qcow2ImgType := mkQcow2ImgType(rd)
ociImgType := qcow2ImgType
ociImgType.name = "oci"
@ -696,7 +708,7 @@ func newDistro(version int) distro.Distro {
}
vhdImgType.defaultImageConfig = vhdConfig.InheritFrom(qcow2ImgType.defaultImageConfig)
minimalrawZstdImgType := minimalrawImgType
minimalrawZstdImgType := mkMinimalRawImgType(rd)
minimalrawZstdImgType.name = "minimal-raw-zst"
minimalrawZstdImgType.filename = "disk.raw.zst"
minimalrawZstdImgType.mimeType = "application/zstd"
@ -744,7 +756,7 @@ func newDistro(version int) distro.Distro {
ImageFormat: platform.FORMAT_VMDK,
},
},
vmdkImgType,
mkVmdkImgType(rd),
)
x86_64.addImageTypes(
&platform.X86{
@ -754,7 +766,7 @@ func newDistro(version int) distro.Distro {
ImageFormat: platform.FORMAT_OVA,
},
},
ovaImgType,
mkOvaImgType(rd),
)
x86_64.addImageTypes(
&platform.X86{
@ -768,15 +780,22 @@ func newDistro(version int) distro.Distro {
)
x86_64.addImageTypes(
&platform.X86{},
containerImgType,
wslImgType,
mkContainerImgType(rd),
mkWslImgType(rd),
)
// add distro installer configuration to all installer types
distroInstallerConfig := defaultDistroInstallerConfig(&rd)
liveInstallerImgType := mkLiveInstallerImgType(rd)
liveInstallerImgType.defaultInstallerConfig = distroInstallerConfig
imageInstallerImgType := mkImageInstallerImgType(rd)
imageInstallerImgType.defaultInstallerConfig = distroInstallerConfig
iotInstallerImgType := mkIotInstallerImgType(rd)
iotInstallerImgType.defaultInstallerConfig = distroInstallerConfig
x86_64.addImageTypes(
&platform.X86{
BasePlatform: platform.BasePlatform{
@ -790,8 +809,8 @@ func newDistro(version int) distro.Distro {
BIOS: true,
UEFIVendor: "fedora",
},
iotOCIImgType,
iotCommitImgType,
mkIotOCIImgType(rd),
mkIotCommitImgType(rd),
iotInstallerImgType,
imageInstallerImgType,
liveInstallerImgType,
@ -804,7 +823,7 @@ func newDistro(version int) distro.Distro {
BIOS: false,
UEFIVendor: "fedora",
},
iotRawImgType,
mkIotRawImgType(rd),
)
x86_64.addImageTypes(
&platform.X86{
@ -814,7 +833,7 @@ func newDistro(version int) distro.Distro {
BIOS: false,
UEFIVendor: "fedora",
},
iotQcow2ImgType,
mkIotQcow2ImgType(rd),
)
aarch64.addImageTypes(
&platform.Aarch64{
@ -833,7 +852,7 @@ func newDistro(version int) distro.Distro {
QCOW2Compat: "1.1",
},
},
iotQcow2ImgType,
mkIotQcow2ImgType(rd),
ociImgType,
qcow2ImgType,
)
@ -848,7 +867,7 @@ func newDistro(version int) distro.Distro {
)
aarch64.addImageTypes(
&platform.Aarch64{},
containerImgType,
mkContainerImgType(rd),
)
aarch64.addImageTypes(
&platform.Aarch64{
@ -865,9 +884,9 @@ func newDistro(version int) distro.Distro {
UEFIVendor: "fedora",
},
imageInstallerImgType,
iotCommitImgType,
mkIotCommitImgType(rd),
iotInstallerImgType,
iotOCIImgType,
mkIotOCIImgType(rd),
liveInstallerImgType,
)
aarch64.addImageTypes(
@ -909,7 +928,7 @@ func newDistro(version int) distro.Distro {
{"/usr/lib/ostree-boot/efi/start_x.elf", "/boot/efi/"},
},
},
iotRawImgType,
mkIotRawImgType(rd),
)
x86_64.addImageTypes(
&platform.X86{
@ -918,7 +937,7 @@ func newDistro(version int) distro.Distro {
ImageFormat: platform.FORMAT_RAW,
},
},
minimalrawImgType,
mkMinimalRawImgType(rd),
minimalrawZstdImgType,
)
aarch64.addImageTypes(
@ -936,11 +955,13 @@ func newDistro(version int) distro.Distro {
{"/usr/share/uboot/rpi_arm64/u-boot.bin", "/boot/efi/rpi-u-boot.bin"},
},
},
minimalrawImgType,
mkMinimalRawImgType(rd),
minimalrawZstdImgType,
)
iotSimplifiedInstallerImgType := mkIotSimplifiedInstallerImgType(rd)
iotSimplifiedInstallerImgType.defaultInstallerConfig = distroInstallerConfig
x86_64.addImageTypes(
&platform.X86{
BasePlatform: platform.BasePlatform{
@ -1002,7 +1023,7 @@ func newDistro(version int) distro.Distro {
BIOS: true,
UEFIVendor: "fedora",
},
iotBootableContainer,
mkIotBootableContainer(rd),
)
aarch64.addImageTypes(
&platform.Aarch64{
@ -1018,7 +1039,7 @@ func newDistro(version int) distro.Distro {
},
UEFIVendor: "fedora",
},
iotBootableContainer,
mkIotBootableContainer(rd),
)
ppc64le.addImageTypes(
@ -1029,7 +1050,7 @@ func newDistro(version int) distro.Distro {
QCOW2Compat: "1.1",
},
},
iotBootableContainer,
mkIotBootableContainer(rd),
)
s390x.addImageTypes(
@ -1040,7 +1061,7 @@ func newDistro(version int) distro.Distro {
QCOW2Compat: "1.1",
},
},
iotBootableContainer,
mkIotBootableContainer(rd),
)
ppc64le.addImageTypes(
@ -1055,7 +1076,7 @@ func newDistro(version int) distro.Distro {
)
ppc64le.addImageTypes(
&platform.PPC64LE{},
containerImgType,
mkContainerImgType(rd),
)
s390x.addImageTypes(
@ -1070,14 +1091,14 @@ func newDistro(version int) distro.Distro {
)
s390x.addImageTypes(
&platform.S390X{},
containerImgType,
mkContainerImgType(rd),
)
// XXX: there is no "qcow2" for riscv64 yet because there is
// no "@Fedora Cloud Server" group
riscv64.addImageTypes(
&platform.RISCV64{},
containerImgType,
mkContainerImgType(rd),
)
riscv64.addImageTypes(
&platform.RISCV64{
@ -1086,7 +1107,7 @@ func newDistro(version int) distro.Distro {
ImageFormat: platform.FORMAT_RAW,
},
},
minimalrawImgType,
mkMinimalRawImgType(rd),
minimalrawZstdImgType,
)
@ -1119,3 +1140,44 @@ func DistroFactory(idStr string) distro.Distro {
return newDistro(id.MajorVersion)
}
func iotServicesForVersion(d *distribution) []string {
services := []string{
"NetworkManager.service",
"firewalld.service",
"sshd.service",
"greenboot-grub2-set-counter",
"greenboot-grub2-set-success",
"greenboot-healthcheck",
"greenboot-rpm-ostree-grub2-check-fallback",
"greenboot-status",
"greenboot-task-runner",
"redboot-auto-reboot",
"redboot-task-runner",
}
if common.VersionLessThan(d.osVersion, "42") {
services = append(services, []string{
"zezere_ignition.timer",
"zezere_ignition_banner.service",
"parsec",
"dbus-parsec",
}...)
}
return services
}
func minimalServicesForVersion(d *distribution) []string {
services := []string{
"NetworkManager.service",
"initial-setup.service",
"sshd.service",
}
if common.VersionLessThan(d.osVersion, "43") {
services = append(services, []string{"firewalld.service"}...)
}
return services
}

View file

@ -110,6 +110,10 @@ func osCustomizations(
osc.Hostname = "localhost.localdomain"
}
if imageConfig.InstallWeakDeps != nil {
osc.InstallWeakDeps = *imageConfig.InstallWeakDeps
}
timezone, ntpServers := c.GetTimezoneSettings()
if timezone != nil {
osc.Timezone = *timezone
@ -234,6 +238,10 @@ func osCustomizations(
osc.CACerts = ca.PEMCerts
}
if imageConfig.MachineIdUninitialized != nil {
osc.MachineIdUninitialized = *imageConfig.MachineIdUninitialized
}
return osc, nil
}
@ -334,7 +342,7 @@ func diskImage(workload workload.Workload,
img.Compression = t.compression
if bp.Minimal {
// Disable weak dependencies if the 'minimal' option is enabled
img.InstallWeakDeps = common.ToPtr(false)
img.OSCustomizations.InstallWeakDeps = false
}
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(bp.Customizations, options, rng)
@ -345,9 +353,6 @@ func diskImage(workload workload.Workload,
img.Filename = t.Filename()
d := t.arch.distro
img.FirstBoot = common.VersionGreaterThanOrEqual(d.osVersion, VERSION_FIRSTBOOT)
return img, nil
}

View file

@ -254,11 +254,22 @@ func (t *imageType) Manifest(bp *blueprint.Blueprint,
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: payloadRepos,
Repos: workloadRepos,
},
Packages: bp.GetPackagesEx(false),
Packages: bp.GetPackagesEx(false),
EnabledModules: bp.GetEnabledModules(),
}
if services := bp.Customizations.GetServices(); services != nil {
cw.Services = services.Enabled

View file

@ -6,5 +6,5 @@ import (
)
func packageSetLoader(t *imageType) rpmmd.PackageSet {
return packagesets.Load(t, VersionReplacements())
return packagesets.Load(t, "", VersionReplacements())
}

View file

@ -11,6 +11,9 @@ const VERSION_ROOTFS_SQUASHFS = "41"
// other Fedora variants.
const VERSION_FIRSTBOOT = "43"
// Version at which we stop installing weak dependencies for Fedora Minimal
const VERSION_MINIMAL_WEAKDEPS = "43"
func VersionReplacements() map[string]string {
return map[string]string{
"VERSION_BRANCHED": VERSION_BRANCHED,

View file

@ -94,6 +94,15 @@ type ImageConfig struct {
LockRootUser *bool
IgnitionPlatform *string
// InstallWeakDeps enables installation of weak dependencies for packages
// that are statically defined for the pipeline.
InstallWeakDeps *bool
// How to handle the /etc/machine-id file, when set to true it causes the
// machine id to be set to 'uninitialized' which causes ConditionFirstboot
// to be triggered in systemd
MachineIdUninitialized *bool
}
// InheritFrom inherits unset values from the provided parent configuration and

View file

@ -102,7 +102,6 @@ iot_commit: &iot_commit
- "criu"
- "cryptsetup"
- "curl"
- "dbus-parsec"
- "dosfstools"
- "dracut-config-generic"
- "dracut-network"
@ -131,7 +130,6 @@ iot_commit: &iot_commit
- "iputils"
- "iwd"
- "iwlwifi-mvm-firmware"
- "kernel-tools"
- "keyutils"
- "less"
- "libsss_sudo"
@ -142,11 +140,9 @@ iot_commit: &iot_commit
- "openssh-clients"
- "openssh-server"
- "openssl"
- "parsec"
- "pinentry"
- "podman"
- "policycoreutils"
- "policycoreutils-python-utils"
- "polkit"
- "procps-ng"
- "realtek-firmware"
@ -175,13 +171,19 @@ iot_commit: &iot_commit
- "wpa_supplicant"
- "xfsprogs"
- "xz"
- "zezere-ignition"
- "zram-generator"
condition:
version_less_than:
"41":
include:
- "dnsmasq"
"42":
include:
- "dbus-parsec"
- "kernel-tools"
- "parsec"
- "policycoreutils-python-utils"
- "zezere-ignition"
"43":
include:
- "basesystem"

View file

@ -3,17 +3,21 @@ package packagesets
import (
"embed"
"fmt"
"io/fs"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/rpmmd"
"gopkg.in/yaml.v3"
)
//go:embed */*.yaml
var Data embed.FS
var data embed.FS
var DataFS fs.FS = data
type packageSet struct {
Include []string `yaml:"include"`
@ -25,19 +29,31 @@ type conditions struct {
Architecture map[string]packageSet `yaml:"architecture,omitempty"`
VersionLessThan map[string]packageSet `yaml:"version_less_than,omitempty"`
VersionGreaterOrEqual map[string]packageSet `yaml:"version_greater_or_equal,omitempty"`
DistroName map[string]packageSet `yaml:"distro_name,omitempty"`
}
func Load(it distro.ImageType, replacements map[string]string) rpmmd.PackageSet {
typeName := strings.ReplaceAll(it.Name(), "-", "_")
// Load loads the PackageSet from the yaml source file discovered via the
// imagetype. By default the imagetype name is used to load the packageset
// but with "overrideTypeName" this can be overriden (useful for e.g.
// installer image types).
func Load(it distro.ImageType, overrideTypeName string, replacements map[string]string) rpmmd.PackageSet {
typeName := it.Name()
if overrideTypeName != "" {
typeName = overrideTypeName
}
typeName = strings.ReplaceAll(typeName, "-", "_")
arch := it.Arch()
archName := arch.Name()
distribution := arch.Distro()
distroNameVer := distribution.Name()
distroName := strings.SplitN(distroNameVer, "-", 2)[0]
// we need to split from the right for "centos-stream-10" like
// distro names, sadly go has no rsplit() so we do it manually
// XXX: we cannot use distroidparser here because of import cycles
distroName := distroNameVer[:strings.LastIndex(distroNameVer, "-")]
distroVersion := distribution.OsVersion()
distroSets, err := Data.Open(filepath.Join(distroName, "package_sets.yaml"))
distroSets, err := DataFS.Open(filepath.Join(distroName, "package_sets.yaml"))
if err != nil {
panic(err)
}
@ -67,6 +83,12 @@ func Load(it distro.ImageType, replacements map[string]string) rpmmd.PackageSet
Exclude: archSet.Exclude,
})
}
if distroNameSet, ok := pkgSet.Condition.DistroName[distroName]; ok {
rpmmdPkgSet = rpmmdPkgSet.Append(rpmmd.PackageSet{
Include: distroNameSet.Include,
Exclude: distroNameSet.Exclude,
})
}
for ltVer, ltSet := range pkgSet.Condition.VersionLessThan {
if r, ok := replacements[ltVer]; ok {

View file

@ -279,6 +279,10 @@ func osCustomizations(
osc.CACerts = ca.PEMCerts
}
if imageConfig.InstallWeakDeps != nil {
osc.InstallWeakDeps = *imageConfig.InstallWeakDeps
}
return osc, nil
}

View file

@ -304,11 +304,22 @@ func (t *ImageType) Manifest(bp *blueprint.Blueprint,
w := t.Workload
if w == nil {
// XXX: this needs to get duplicaed in exactly the same
// way in fedora/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: payloadRepos,
Repos: workloadRepos,
},
Packages: bp.GetPackagesEx(false),
Packages: bp.GetPackagesEx(false),
EnabledModules: bp.GetEnabledModules(),
}
if services := bp.Customizations.GetServices(); services != nil {
cw.Services = services.Enabled

View file

@ -66,6 +66,7 @@ func defaultDistroImageConfig(d *rhel.Distribution) *distro.ImageConfig {
},
},
DefaultOSCAPDatastream: common.ToPtr(oscap.DefaultRHEL10Datastream(d.IsRHEL())),
InstallWeakDeps: common.ToPtr(true),
}
}

View file

@ -33,6 +33,7 @@ func defaultDistroImageConfig(d *rhel.Distribution) *distro.ImageConfig {
},
KernelOptionsBootloader: common.ToPtr(true),
NoBLS: common.ToPtr(true), // RHEL 7 grub does not support BLS
InstallWeakDeps: common.ToPtr(true),
}
}

View file

@ -54,6 +54,7 @@ func defaultDistroImageConfig(d *rhel.Distribution) *distro.ImageConfig {
},
KernelOptionsBootloader: common.ToPtr(true),
DefaultOSCAPDatastream: common.ToPtr(oscap.DefaultRHEL8Datastream(d.IsRHEL())),
InstallWeakDeps: common.ToPtr(true),
}
}

View file

@ -69,6 +69,7 @@ func defaultDistroImageConfig(d *rhel.Distribution) *distro.ImageConfig {
},
},
DefaultOSCAPDatastream: common.ToPtr(oscap.DefaultRHEL9Datastream(d.IsRHEL())),
InstallWeakDeps: common.ToPtr(true),
}
}

View file

@ -168,6 +168,7 @@ type Solver struct {
// DepsolveResult contains the results of a depsolve operation.
type DepsolveResult struct {
Packages []rpmmd.PackageSpec
Modules []rpmmd.ModuleSpec
Repos []rpmmd.RepoConfig
SBOM *sbom.Document
Solver string
@ -240,7 +241,7 @@ func (s *Solver) Depsolve(pkgSets []rpmmd.PackageSet, sbomType sbom.StandardType
return nil, fmt.Errorf("decoding depsolve result failed: %w", err)
}
packages, repos := result.toRPMMD(rhsmMap)
packages, modules, repos := result.toRPMMD(rhsmMap)
var sbomDoc *sbom.Document
if sbomType != sbom.StandardTypeNone {
@ -252,6 +253,7 @@ func (s *Solver) Depsolve(pkgSets []rpmmd.PackageSet, sbomType sbom.StandardType
return &DepsolveResult{
Packages: packages,
Modules: modules,
Repos: repos,
SBOM: sbomDoc,
Solver: result.Solver,
@ -463,9 +465,10 @@ func (s *Solver) makeDepsolveRequest(pkgSets []rpmmd.PackageSet, sbomType sbom.S
transactions := make([]transactionArgs, len(pkgSets))
for dsIdx, pkgSet := range pkgSets {
transactions[dsIdx] = transactionArgs{
PackageSpecs: pkgSet.Include,
ExcludeSpecs: pkgSet.Exclude,
InstallWeakDeps: pkgSet.InstallWeakDeps,
PackageSpecs: pkgSet.Include,
ExcludeSpecs: pkgSet.Exclude,
ModuleEnableSpecs: pkgSet.EnabledModules,
InstallWeakDeps: pkgSet.InstallWeakDeps,
}
for _, jobRepo := range pkgSet.Repositories {
@ -577,7 +580,7 @@ func (s *Solver) makeSearchRequest(repos []rpmmd.RepoConfig, packages []string)
// convert internal a list of PackageSpecs and map of repoConfig to the rpmmd
// equivalents and attach key and subscription information based on the
// repository configs.
func (result depsolveResult) toRPMMD(rhsm map[string]bool) ([]rpmmd.PackageSpec, []rpmmd.RepoConfig) {
func (result depsolveResult) toRPMMD(rhsm map[string]bool) ([]rpmmd.PackageSpec, []rpmmd.ModuleSpec, []rpmmd.RepoConfig) {
pkgs := result.Packages
repos := result.Repos
rpmDependencies := make([]rpmmd.PackageSpec, len(pkgs))
@ -610,6 +613,22 @@ func (result depsolveResult) toRPMMD(rhsm map[string]bool) ([]rpmmd.PackageSpec,
}
}
mods := result.Modules
moduleSpecs := make([]rpmmd.ModuleSpec, len(mods))
i := 0
for _, mod := range mods {
moduleSpecs[i].ModuleConfigFile.Data.Name = mod.ModuleConfigFile.Data.Name
moduleSpecs[i].ModuleConfigFile.Data.Stream = mod.ModuleConfigFile.Data.Stream
moduleSpecs[i].ModuleConfigFile.Data.State = mod.ModuleConfigFile.Data.State
moduleSpecs[i].ModuleConfigFile.Data.Profiles = mod.ModuleConfigFile.Data.Profiles
moduleSpecs[i].FailsafeFile.Path = mod.FailsafeFile.Path
moduleSpecs[i].FailsafeFile.Data = mod.FailsafeFile.Data
i++
}
repoConfigs := make([]rpmmd.RepoConfig, 0, len(repos))
for repoID := range repos {
repo := repos[repoID]
@ -635,7 +654,7 @@ func (result depsolveResult) toRPMMD(rhsm map[string]bool) ([]rpmmd.PackageSpec,
SSLClientCert: repo.SSLClientCert,
})
}
return rpmDependencies, repoConfigs
return rpmDependencies, moduleSpecs, repoConfigs
}
// Request command and arguments for dnf-json
@ -723,6 +742,9 @@ type transactionArgs struct {
// Packages to exclude from results
ExcludeSpecs []string `json:"exclude-specs"`
// Modules to enable during depsolve
ModuleEnableSpecs []string `json:"module-enable-specs,omitempty"`
// IDs of repositories to use for this depsolve
RepoIDs []string `json:"repo-ids"`
@ -779,7 +801,7 @@ type ModuleConfigData struct {
type ModuleFailsafeFile struct {
Path string `json:"path"`
Data string `json:"string"`
Data string `json:"data"`
}
// dnf-json error structure

View file

@ -0,0 +1,69 @@
// Package experimentalflags provides functionality for reading
// options defined in an environment variable named
// IMAGE_BUILDER_EXPERIMENTAL.
//
// These functions should be used to determine, in a common way, if
// experimental features should be enabled when using the libarary.
package experimentalflags
import (
"os"
"strconv"
"strings"
)
const envKEY = "IMAGE_BUILDER_EXPERIMENTAL"
func experimentalOptions() map[string]string {
expMap := map[string]string{}
env := os.Getenv(envKEY)
if env == "" {
return expMap
}
for _, s := range strings.Split(env, ",") {
l := strings.SplitN(s, "=", 2)
switch len(l) {
case 1:
expMap[l[0]] = "true"
case 2:
expMap[l[0]] = l[1]
}
}
return expMap
}
// Bool returns true if there is a boolean option with the given
// option name.
//
// Example usage by the user:
//
// IMAGE_BUILDER_EXPERIMENTAL=skip-foo,skip-bar=1,skip-baz=true
//
// would result in experimetnalflags.Bool("skip-foo") -> true
func Bool(option string) bool {
expMap := experimentalOptions()
b, err := strconv.ParseBool(expMap[option])
if err != nil {
// not much we can do for invalid inputs, just assume false
return false
}
return b
}
// String returns the user set string for the given experimental feature.
//
// Note that currently no quoting or escaping is supported, so a string
// can (currently) not contain a "," or a "=".
//
// Example usage by the user:
//
// IMAGE_BUILDER_EXPERIMENTAL=key=value
//
// would result in experimetnalflags.String("key") -> "value"
func String(option string) string {
expMap := experimentalOptions()
return expMap[option]
}

View file

@ -39,8 +39,6 @@ type DiskImage struct {
// InstallWeakDeps enables installation of weak dependencies for packages
// that are statically defined for the payload pipeline of the image.
InstallWeakDeps *bool
FirstBoot bool
}
func NewDiskImage() *DiskImage {
@ -65,10 +63,10 @@ func (img *DiskImage) InstantiateManifest(m *manifest.Manifest,
osPipeline.OSProduct = img.OSProduct
osPipeline.OSVersion = img.OSVersion
osPipeline.OSNick = img.OSNick
if img.InstallWeakDeps != nil {
osPipeline.InstallWeakDeps = *img.InstallWeakDeps
}
osPipeline.FirstBoot = img.FirstBoot
rawImagePipeline := manifest.NewRawImage(buildPipeline, osPipeline)
rawImagePipeline.PartTool = img.PartTool

View file

@ -3,7 +3,9 @@ package manifest
import (
"fmt"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/experimentalflags"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/runner"
@ -57,6 +59,11 @@ func NewBuild(m *Manifest, runner runner.Runner, repos []rpmmd.RepoConfig, opts
repos: filterRepos(repos, name),
containerBuildable: opts.ContainerBuildable,
}
// This allows to bootstrap the buildroot with a custom container
// for e.g. cross-arch-build experiments,
maybeAddExperimentalContainerBootstrap(m, runner, opts, pipeline)
m.addPipeline(pipeline)
return pipeline
}
@ -149,6 +156,40 @@ func (p *BuildrootFromPackages) getSELinuxLabels() map[string]string {
return labels
}
// maybeAddExperimentalContainerBootstrap will return a container buildroot
// if the "IMAGE_BUILDER_EXPERIMENTAL=bootstrap=<container-ref>" is
// defined. This allows us to do cross-arch build experimentation.
//
// A "bootstrap" container has only these requirements:
// - python3 for the runners
// - rpm so that the real buildroot rpms can get installed
// - setfiles so that the selinux stage for the real buildroot can run
// (and does not even need a working dnf or repo setup).
func maybeAddExperimentalContainerBootstrap(m *Manifest, runner runner.Runner, opts *BuildOptions, build *BuildrootFromPackages) {
bootstrapBuildrootRef := experimentalflags.String("bootstrap")
if bootstrapBuildrootRef == "" {
return
}
cntSrcs := []container.SourceSpec{
{
Source: bootstrapBuildrootRef,
Name: bootstrapBuildrootRef,
TLSVerify: common.ToPtr(false),
},
}
name := "bootstrap-buildroot"
bootstrapPipeline := &BuildrootFromContainer{
Base: NewBase(name, nil),
runner: runner,
dependents: make([]Pipeline, 0),
containers: cntSrcs,
disableSelinux: true,
}
m.addPipeline(bootstrapPipeline)
build.build = bootstrapPipeline
}
type BuildrootFromContainer struct {
Base
@ -159,6 +200,7 @@ type BuildrootFromContainer struct {
containerSpecs []container.Spec
containerBuildable bool
disableSelinux bool
}
// NewBuildFromContainer creates a new build pipeline from the given
@ -213,6 +255,10 @@ func (p *BuildrootFromContainer) serializeEnd() {
}
func (p *BuildrootFromContainer) getSELinuxLabels() map[string]string {
if p.disableSelinux {
return nil
}
labels := map[string]string{
"/usr/bin/ostree": "system_u:object_r:install_exec_t:s0",
}
@ -242,13 +288,15 @@ func (p *BuildrootFromContainer) serialize() osbuild.Pipeline {
panic(err)
}
pipeline.AddStage(stage)
pipeline.AddStage(osbuild.NewSELinuxStage(
&osbuild.SELinuxStageOptions{
FileContexts: "etc/selinux/targeted/contexts/files/file_contexts",
ExcludePaths: []string{"/sysroot"},
Labels: p.getSELinuxLabels(),
},
))
if !p.disableSelinux {
pipeline.AddStage(osbuild.NewSELinuxStage(
&osbuild.SELinuxStageOptions{
FileContexts: "etc/selinux/targeted/contexts/files/file_contexts",
ExcludePaths: []string{"/sysroot"},
Labels: p.getSELinuxLabels(),
},
))
}
return pipeline
}

View file

@ -22,15 +22,6 @@ import (
"github.com/osbuild/images/pkg/rpmmd"
)
type Arch uint64
const (
ARCH_X86_64 Arch = iota
ARCH_AARCH64
ARCH_S390X
ARCH_PPC64LE
)
type Distro uint64
const (

View file

@ -149,10 +149,14 @@ type OSCustomizations struct {
// instead of BLS. Required for legacy systems like RHEL 7.
NoBLS bool
// FirstBoot sets if the machine-id should be written with the
// magic value that determines if the machine is being booted for the
// first time.
FirstBoot bool
// InstallWeakDeps enables installation of weak dependencies for packages
// that are statically defined for the pipeline.
// Defaults to True.
InstallWeakDeps bool
// Determines if the machine id should be set to "uninitialized" which allows
// "ConditionFirstBoot" to work in systemd
MachineIdUninitialized bool
}
// OS represents the filesystem tree of the target image. This roughly
@ -184,6 +188,7 @@ type OS struct {
// content-related fields
repos []rpmmd.RepoConfig
packageSpecs []rpmmd.PackageSpec
moduleSpecs []rpmmd.ModuleSpec
containerSpecs []container.Spec
ostreeParentSpec *ostree.CommitSpec
@ -193,11 +198,6 @@ type OS struct {
OSProduct string
OSVersion string
OSNick string
// InstallWeakDeps enables installation of weak dependencies for packages
// that are statically defined for the pipeline.
// Defaults to True.
InstallWeakDeps bool
}
// NewOS creates a new OS pipeline. build is the build pipeline to use for
@ -206,10 +206,9 @@ type OS struct {
func NewOS(buildPipeline Build, platform platform.Platform, repos []rpmmd.RepoConfig) *OS {
name := "os"
p := &OS{
Base: NewBase(name, buildPipeline),
repos: filterRepos(repos, name),
platform: platform,
InstallWeakDeps: true,
Base: NewBase(name, buildPipeline),
repos: filterRepos(repos, name),
platform: platform,
}
buildPipeline.addDependent(p)
return p
@ -281,10 +280,17 @@ func (p *OS) getPackageSetChain(Distro) []rpmmd.PackageSet {
if p.Workload != nil {
workloadPackages := p.Workload.GetPackages()
if len(workloadPackages) > 0 {
chain = append(chain, rpmmd.PackageSet{
ps := rpmmd.PackageSet{
Include: workloadPackages,
Repositories: append(osRepos, p.Workload.GetRepos()...),
})
}
workloadModules := p.Workload.GetEnabledModules()
if len(workloadModules) > 0 {
ps.EnabledModules = workloadModules
}
chain = append(chain, ps)
}
}
@ -387,6 +393,7 @@ func (p *OS) serializeStart(inputs Inputs) {
}
p.packageSpecs = inputs.Depsolved.Packages
p.moduleSpecs = inputs.Depsolved.Modules
p.containerSpecs = inputs.Containers
if len(inputs.Commits) > 0 {
if len(inputs.Commits) > 1 {
@ -742,6 +749,36 @@ func (p *OS) serialize() osbuild.Pipeline {
pipeline.AddStages(osbuild.GenFileNodesStages(p.Files)...)
}
// write modularity related configuration files
if len(p.moduleSpecs) > 0 {
pipeline.AddStages(osbuild.GenDNFModuleConfigStages(p.moduleSpecs)...)
var failsafeFiles []*fsnode.File
// the failsafe file is a blob of YAML returned directly from the depsolver,
// we write them as 'normal files' without a special stage
for _, module := range p.moduleSpecs {
moduleFailsafeFile, err := fsnode.NewFile(module.FailsafeFile.Path, nil, nil, nil, []byte(module.FailsafeFile.Data))
if err != nil {
panic("failed to create module failsafe file")
}
failsafeFiles = append(failsafeFiles, moduleFailsafeFile)
}
failsafeDir, err := fsnode.NewDirectory("/var/lib/dnf/modulefailsafe", nil, nil, nil, true)
if err != nil {
panic("failed to create module failsafe directory")
}
pipeline.AddStages(osbuild.GenDirectoryNodesStages([]*fsnode.Directory{failsafeDir})...)
pipeline.AddStages(osbuild.GenFileNodesStages(failsafeFiles)...)
p.Files = append(p.Files, failsafeFiles...)
}
enabledServices := []string{}
disabledServices := []string{}
maskedServices := []string{}
@ -813,7 +850,7 @@ func (p *OS) serialize() osbuild.Pipeline {
pipeline.AddStage(osbuild.NewCAStageStage())
}
if p.FirstBoot {
if p.MachineIdUninitialized {
pipeline.AddStage(osbuild.NewMachineIdStage(&osbuild.MachineIdStageOptions{
FirstBoot: osbuild.MachineIdFirstBootYes,
}))

View file

@ -210,11 +210,22 @@ func (p *RawBootcImage) serialize() osbuild.Pipeline {
// First create custom directories, because some of the custom files may depend on them
if len(p.Directories) > 0 {
pipeline.AddStages(osbuild.GenDirectoryNodesStages(p.Directories)...)
stages := osbuild.GenDirectoryNodesStages(p.Directories)
for _, stage := range stages {
stage.Mounts = mounts
stage.Devices = devices
}
pipeline.AddStages(stages...)
}
if len(p.Files) > 0 {
pipeline.AddStages(osbuild.GenFileNodesStages(p.Files)...)
stages := osbuild.GenFileNodesStages(p.Files)
for _, stage := range stages {
stage.Mounts = mounts
stage.Devices = devices
}
pipeline.AddStages(stages...)
}
// XXX: maybe go back to adding this conditionally when we stop

View file

@ -0,0 +1,62 @@
package osbuild
import (
"github.com/osbuild/images/pkg/rpmmd"
)
type DNFModuleConfig struct {
Name string `json:"name,omitempty"`
Stream string `json:"stream,omitempty"`
State string `json:"state,omitempty"`
Profiles []string `json:"profiles"`
}
type DNFModuleConfigStageOptions struct {
Config *DNFModuleConfig `json:"conf,omitempty"`
}
func (DNFModuleConfigStageOptions) isStageOptions() {}
// NewDNFModuleConfigStageOptions creates a new DNFConfig Stage options object.
func NewDNFModuleConfigStageOptions(config *DNFModuleConfig) *DNFModuleConfigStageOptions {
return &DNFModuleConfigStageOptions{
Config: config,
}
}
func (o DNFModuleConfigStageOptions) validate() error {
return nil
}
// NewDNFModuleConfigStage creates a new DNFModuleConfig Stage object.
func NewDNFModuleConfigStage(options *DNFModuleConfigStageOptions) *Stage {
if err := options.validate(); err != nil {
panic(err)
}
return &Stage{
Type: "org.osbuild.dnf.module-config",
Options: options,
}
}
func GenDNFModuleConfigStages(modules []rpmmd.ModuleSpec) []*Stage {
stages := make([]*Stage, len(modules))
for _, module := range modules {
data := module.ModuleConfigFile.Data
stage := NewDNFModuleConfigStage(&DNFModuleConfigStageOptions{
Config: &DNFModuleConfig{
Name: data.Name,
Stream: data.Stream,
State: data.State,
Profiles: data.Profiles,
},
})
stages = append(stages, stage)
}
return stages
}

View file

@ -3,9 +3,9 @@ package osbuild
type MachineIdFirstBoot string
const (
MachineIdFirstBootYes MachineIdFirstBoot = "yes"
MachineIdFirstBootNo MachineIdFirstBoot = "no"
MachineIdFirstBootPreserver MachineIdFirstBoot = "preserve"
MachineIdFirstBootYes MachineIdFirstBoot = "yes"
MachineIdFirstBootNo MachineIdFirstBoot = "no"
MachineIdFirstBootPreserve MachineIdFirstBoot = "preserve"
)
type MachineIdStageOptions struct {

View file

@ -9,7 +9,10 @@ import (
"os/exec"
"strings"
"github.com/osbuild/images/data/dependencies"
"github.com/osbuild/images/pkg/datasizes"
"github.com/hashicorp/go-version"
)
// Run an instance of osbuild, returning a parsed osbuild.Result.
@ -18,6 +21,10 @@ import (
// does not return an error in this case. Instead, the failure is communicated
// with its corresponding logs through osbuild.Result.
func RunOSBuild(manifest []byte, store, outputDirectory string, exports, checkpoints, extraEnv []string, result bool, errorWriter io.Writer) (*Result, error) {
if err := CheckMinimumOSBuildVersion(); err != nil {
return nil, err
}
var stdoutBuffer bytes.Buffer
var res Result
@ -96,6 +103,30 @@ func RunOSBuild(manifest []byte, store, outputDirectory string, exports, checkpo
return &res, nil
}
func CheckMinimumOSBuildVersion() error {
osbuildVersion, err := OSBuildVersion()
if err != nil {
return fmt.Errorf("error getting osbuild version: %v", err)
}
minVersion, err := version.NewVersion(dependencies.MinimumOSBuildVersion())
if err != nil {
return fmt.Errorf("error parsing minimum osbuild version: %v", err)
}
currentVersion, err := version.NewVersion(osbuildVersion)
if err != nil {
return fmt.Errorf("error parsing current osbuild version: %v", err)
}
if currentVersion.LessThan(minVersion) {
return fmt.Errorf("osbuild version %q is lower than the minimum required version %q",
osbuildVersion, dependencies.MinimumOSBuildVersion())
}
return nil
}
// OSBuildVersion returns the version of osbuild.
func OSBuildVersion() (string, error) {
var stdoutBuffer bytes.Buffer

View file

@ -141,6 +141,7 @@ func (pkg Package) ToPackageInfo() PackageInfo {
type PackageSet struct {
Include []string
Exclude []string
EnabledModules []string
Repositories []RepoConfig
InstallWeakDeps bool
}
@ -150,6 +151,7 @@ type PackageSet struct {
func (ps PackageSet) Append(other PackageSet) PackageSet {
ps.Include = append(ps.Include, other.Include...)
ps.Exclude = append(ps.Exclude, other.Exclude...)
ps.EnabledModules = append(ps.EnabledModules, other.EnabledModules...)
return ps
}
@ -199,6 +201,28 @@ type PackageInfo struct {
Dependencies []PackageSpec `json:"dependencies,omitempty"`
}
type ModuleSpec struct {
ModuleConfigFile ModuleConfigFile `json:"module-file"`
FailsafeFile ModuleFailsafeFile `json:"failsafe-file"`
}
type ModuleConfigFile struct {
Path string `json:"path"`
Data ModuleConfigData `json:"data"`
}
type ModuleConfigData struct {
Name string `json:"name"`
Stream string `json:"stream"`
Profiles []string `json:"profiles"`
State string `json:"state"`
}
type ModuleFailsafeFile struct {
Path string `json:"path"`
Data string `json:"data"`
}
// GetEVRA returns the package's Epoch:Version-Release.Arch string
func (ps *PackageSpec) GetEVRA() string {
if ps.Epoch == 0 {

4
vendor/modules.txt vendored
View file

@ -1037,8 +1037,9 @@ github.com/oracle/oci-go-sdk/v54/identity
github.com/oracle/oci-go-sdk/v54/objectstorage
github.com/oracle/oci-go-sdk/v54/objectstorage/transfer
github.com/oracle/oci-go-sdk/v54/workrequests
# github.com/osbuild/images v0.120.0
# github.com/osbuild/images v0.123.0
## explicit; go 1.22.8
github.com/osbuild/images/data/dependencies
github.com/osbuild/images/data/repositories
github.com/osbuild/images/internal/common
github.com/osbuild/images/internal/environment
@ -1073,6 +1074,7 @@ github.com/osbuild/images/pkg/distro/test_distro
github.com/osbuild/images/pkg/distrofactory
github.com/osbuild/images/pkg/distroidparser
github.com/osbuild/images/pkg/dnfjson
github.com/osbuild/images/pkg/experimentalflags
github.com/osbuild/images/pkg/image
github.com/osbuild/images/pkg/manifest
github.com/osbuild/images/pkg/osbuild