diff --git a/internal/distro/rhel90/distro.go b/internal/distro/rhel90/distro.go new file mode 100644 index 000000000..b9d8da6d5 --- /dev/null +++ b/internal/distro/rhel90/distro.go @@ -0,0 +1,840 @@ +package rhel90 + +import ( + "encoding/json" + "errors" + "fmt" + "math/rand" + "path" + "sort" + "strings" + + "github.com/osbuild/osbuild-composer/internal/blueprint" + "github.com/osbuild/osbuild-composer/internal/disk" + "github.com/osbuild/osbuild-composer/internal/distro" + osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" + "github.com/osbuild/osbuild-composer/internal/rpmmd" +) + +const defaultName = "rhel-90-ga" +const osVersion = "9.0" +const releaseVersion = "9" +const modulePlatformID = "platform:el9" +const ostreeRef = "rhel/9/%s/edge" + +const ( + // package set names + + // build package set name + buildPkgsKey = "build" + + // Legacy bootable image package set name + bootLegacyPkgsKey = "boot.legacy" + + // UEFI bootable image package set name + bootUEFIPkgsKey = "boot.uefi" + + // main/common os image package set name + osPkgsKey = "packages" + + // edge os image package set name + edgePkgsKey = "edge" + + // edge build package set name + edgeBuildPkgsKey = "build.edge" + + // container package set name + containerPkgsKey = "container" + + // installer package set name + installerPkgsKey = "installer" + + // blueprint package set name + blueprintPkgsKey = "blueprint" +) + +var mountpointAllowList = []string{ + "/", "/var", "/opt", "/srv", "/usr", "/app", "/data", "/home", +} + +type distribution struct { + name string + modulePlatformID string + ostreeRef string + arches map[string]distro.Arch + packageSets map[string]rpmmd.PackageSet +} + +func (d *distribution) Name() string { + return d.name +} + +func (d *distribution) Releasever() string { + return releaseVersion +} + +func (d *distribution) ModulePlatformID() string { + return d.modulePlatformID +} + +func (d *distribution) OSTreeRef() string { + return d.ostreeRef +} + +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, errors.New("invalid architecture: " + name) + } + return arch, nil +} + +func (d *distribution) addArches(arches ...architecture) { + if d.arches == nil { + d.arches = map[string]distro.Arch{} + } + + // Do not make copies of architectures, as opposed to image types, + // because architecture definitions are not used by more than a single + // distro definition. + for idx := range arches { + d.arches[arches[idx].name] = &arches[idx] + } +} + +type architecture struct { + distro *distribution + name string + imageTypes map[string]distro.ImageType + imageTypeAliases map[string]string + packageSets map[string]rpmmd.PackageSet + legacy string + bootType distro.BootType +} + +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, errors.New("invalid image type: " + 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) addImageTypes(imageTypes ...imageType) { + if a.imageTypes == nil { + a.imageTypes = map[string]distro.ImageType{} + } + for idx := range imageTypes { + it := imageTypes[idx] + it.arch = a + a.imageTypes[it.name] = &it + for _, alias := range it.nameAliases { + if a.imageTypeAliases == nil { + a.imageTypeAliases = map[string]string{} + } + if existingAliasFor, exists := a.imageTypeAliases[alias]; exists { + panic(fmt.Sprintf("image type alias '%s' for '%s' is already defined for another image type '%s'", alias, it.name, existingAliasFor)) + } + a.imageTypeAliases[alias] = it.name + } + } +} + +func (a *architecture) Distro() distro.Distro { + return a.distro +} + +type pipelinesFunc func(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) + +type imageType struct { + arch *architecture + name string + nameAliases []string + filename string + mimeType string + packageSets map[string]rpmmd.PackageSet + enabledServices []string + disabledServices []string + defaultTarget string + kernelOptions string + defaultSize uint64 + exports []string + pipelines pipelinesFunc + + // bootISO: installable ISO + bootISO bool + // rpmOstree: edge/ostree + rpmOstree bool + // bootable image + bootable bool + // If set to a value, it is preferred over the architecture value + bootType distro.BootType + // List of valid arches for the image type + basePartitionTables distro.BasePartitionTableMap +} + +func (t *imageType) Name() string { + return t.name +} + +func (t *imageType) Arch() distro.Arch { + return t.arch +} + +func (t *imageType) Filename() string { + return t.filename +} + +func (t *imageType) MIMEType() string { + return t.mimeType +} + +func (t *imageType) OSTreeRef() string { + if t.rpmOstree { + return fmt.Sprintf(ostreeRef, t.arch.name) + } + return "" +} + +func (t *imageType) Size(size uint64) uint64 { + const MegaByte = 1024 * 1024 + // Microsoft Azure requires vhd images to be rounded up to the nearest MB + if t.name == "vhd" && size%MegaByte != 0 { + size = (size/MegaByte + 1) * MegaByte + } + if size == 0 { + size = t.defaultSize + } + return size +} + +func (t *imageType) PackageSets(bp blueprint.Blueprint) map[string]rpmmd.PackageSet { + // merge package sets that appear in the image type with the package sets + // of the same name from the distro and arch + mergedSets := make(map[string]rpmmd.PackageSet) + + imageSets := t.packageSets + archSets := t.arch.packageSets + distroSets := t.arch.distro.packageSets + for name := range imageSets { + mergedSets[name] = imageSets[name].Append(archSets[name]).Append(distroSets[name]) + } + + if _, hasPackages := imageSets[osPkgsKey]; !hasPackages { + // should this be possible?? + mergedSets[osPkgsKey] = rpmmd.PackageSet{} + } + + // build is usually not defined on the image type + // handle it explicitly when it's not + if _, hasBuild := imageSets[buildPkgsKey]; !hasBuild { + mergedSets[buildPkgsKey] = archSets[buildPkgsKey].Append(distroSets[buildPkgsKey]) + } + + // package sets from flags + if t.bootable { + var addLegacyBootPkg bool + var addUEFIBootPkg bool + + switch bt := t.getBootType(); bt { + case distro.LegacyBootType: + addLegacyBootPkg = true + case distro.UEFIBootType: + addUEFIBootPkg = true + case distro.HybridBootType: + addLegacyBootPkg = true + addUEFIBootPkg = true + default: + panic(fmt.Sprintf("unsupported boot type: %q", bt)) + } + + if addLegacyBootPkg { + mergedSets[osPkgsKey] = mergedSets[osPkgsKey].Append(archSets[bootLegacyPkgsKey]).Append(distroSets[bootLegacyPkgsKey]) + } + if addUEFIBootPkg { + mergedSets[osPkgsKey] = mergedSets[osPkgsKey].Append(archSets[bootUEFIPkgsKey]).Append(distroSets[bootUEFIPkgsKey]) + } + } + if t.rpmOstree { + // add ostree sets + mergedSets[buildPkgsKey] = mergedSets[buildPkgsKey].Append(archSets[edgeBuildPkgsKey]).Append(distroSets[edgeBuildPkgsKey]) + mergedSets[osPkgsKey] = mergedSets[osPkgsKey].Append(archSets[edgePkgsKey]).Append(distroSets[edgePkgsKey]) + } + + // blueprint packages + bpPackages := bp.GetPackages() + timezone, _ := bp.Customizations.GetTimezoneSettings() + if timezone != nil { + bpPackages = append(bpPackages, "chrony") + } + + // depsolve bp packages separately + // bp packages aren't restricted by exclude lists + mergedSets[blueprintPkgsKey] = rpmmd.PackageSet{Include: bpPackages} + kernel := bp.Customizations.GetKernel().Name + + // add bp kernel to main OS package set to avoid duplicate kernels + mergedSets[osPkgsKey] = mergedSets[osPkgsKey].Append(rpmmd.PackageSet{Include: []string{kernel}}) + return mergedSets + +} + +func (t *imageType) Exports() []string { + if len(t.exports) > 0 { + return t.exports + } + return []string{"assembler"} +} + +// getBootType returns the BootType which should be used for this particular +// combination of architecture and image type. +func (t *imageType) getBootType() distro.BootType { + bootType := t.arch.bootType + if t.bootType != distro.UnsetBootType { + bootType = t.bootType + } + return bootType +} + +func (t *imageType) supportsUEFI() bool { + bootType := t.getBootType() + if bootType == distro.HybridBootType || bootType == distro.UEFIBootType { + return true + } + return false +} + +func (t *imageType) getPartitionTable( + mountpoints []blueprint.FilesystemCustomization, + options distro.ImageOptions, + rng *rand.Rand, +) (disk.PartitionTable, error) { + archName := t.arch.Name() + + basePartitionTable, exists := t.basePartitionTables[archName] + + if !exists { + return basePartitionTable, fmt.Errorf("unknown arch: " + archName) + } + + return disk.CreatePartitionTable(mountpoints, options.Size, basePartitionTable, rng), nil +} + +// local type for ostree commit metadata used to define commit sources +type ostreeCommit struct { + Checksum string + URL string +} + +func (t *imageType) Manifest(customizations *blueprint.Customizations, + options distro.ImageOptions, + repos []rpmmd.RepoConfig, + packageSpecSets map[string][]rpmmd.PackageSpec, + seed int64) (distro.Manifest, error) { + + if err := t.checkOptions(customizations, options); err != nil { + return distro.Manifest{}, err + } + + source := rand.NewSource(seed) + rng := rand.New(source) + + pipelines, err := t.pipelines(t, customizations, options, repos, packageSpecSets, rng) + if err != nil { + return distro.Manifest{}, err + } + + // flatten spec sets for sources + allPackageSpecs := make([]rpmmd.PackageSpec, 0) + for _, specs := range packageSpecSets { + allPackageSpecs = append(allPackageSpecs, specs...) + } + + var commits []ostreeCommit + if t.bootISO && options.OSTree.Parent != "" && options.OSTree.URL != "" { + commits = []ostreeCommit{{Checksum: options.OSTree.Parent, URL: options.OSTree.URL}} + } + return json.Marshal( + osbuild.Manifest{ + Version: "2", + Pipelines: pipelines, + Sources: t.sources(allPackageSpecs, commits), + }, + ) +} + +func (t *imageType) sources(packages []rpmmd.PackageSpec, ostreeCommits []ostreeCommit) osbuild.Sources { + sources := osbuild.Sources{} + curl := &osbuild.CurlSource{ + Items: make(map[string]osbuild.CurlSourceItem), + } + for _, pkg := range packages { + item := new(osbuild.URLWithSecrets) + item.URL = pkg.RemoteLocation + if pkg.Secrets == "org.osbuild.rhsm" { + item.Secrets = &osbuild.URLSecrets{ + Name: "org.osbuild.rhsm", + } + } + curl.Items[pkg.Checksum] = item + } + if len(curl.Items) > 0 { + sources["org.osbuild.curl"] = curl + } + + ostree := &osbuild.OSTreeSource{ + Items: make(map[string]osbuild.OSTreeSourceItem), + } + for _, commit := range ostreeCommits { + item := new(osbuild.OSTreeSourceItem) + item.Remote.URL = commit.URL + ostree.Items[commit.Checksum] = *item + } + if len(ostree.Items) > 0 { + sources["org.osbuild.ostree"] = ostree + } + return sources +} + +func isMountpointAllowed(mountpoint string) bool { + for _, allowed := range mountpointAllowList { + match, _ := path.Match(allowed, mountpoint) + if match { + return true + } + // ensure that only clean mountpoints + // are valid + if strings.Contains(mountpoint, "//") { + return false + } + match = strings.HasPrefix(mountpoint, allowed+"/") + if allowed != "/" && match { + return true + } + } + return false +} + +// checkOptions checks the validity and compatibility of options and customizations for the image type. +func (t *imageType) checkOptions(customizations *blueprint.Customizations, options distro.ImageOptions) error { + if t.bootISO && t.rpmOstree { + if options.OSTree.Parent == "" { + return fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name) + } + if customizations != nil { + return fmt.Errorf("boot ISO image type %q does not support blueprint customizations", t.name) + } + } + + if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.rpmOstree { + return fmt.Errorf("kernel boot parameter customizations are not supported for ostree types") + } + + mountpoints := customizations.GetFilesystems() + + if mountpoints != nil && t.rpmOstree { + return fmt.Errorf("Custom mountpoints are not supported for ostree types") + } + + invalidMountpoints := []string{} + for _, m := range mountpoints { + if m.Mountpoint == "/usr" && m.MinSize < 2147483648 { + m.MinSize = 2147483648 + } + if !isMountpointAllowed(m.Mountpoint) { + invalidMountpoints = append(invalidMountpoints, m.Mountpoint) + } + } + + if len(invalidMountpoints) > 0 { + return fmt.Errorf("The following custom mountpoints are not supported %+q", invalidMountpoints) + } + + return nil +} + +// New creates a new distro object, defining the supported architectures and image types +func New() distro.Distro { + return newDistro(defaultName, modulePlatformID, ostreeRef) +} + +func NewHostDistro(name, modulePlatformID, ostreeRef string) distro.Distro { + return newDistro(name, modulePlatformID, ostreeRef) +} + +func newDistro(name, modulePlatformID, ostreeRef string) distro.Distro { + const GigaByte = 1024 * 1024 * 1024 + + rd := &distribution{ + name: name, + modulePlatformID: modulePlatformID, + ostreeRef: ostreeRef, + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: distroBuildPackageSet(), + edgeBuildPkgsKey: edgeBuildPackageSet(), + }, + } + + // Architecture definitions + x86_64 := architecture{ + name: distro.X86_64ArchName, + distro: rd, + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: x8664BuildPackageSet(), + bootLegacyPkgsKey: x8664LegacyBootPackageSet(), + bootUEFIPkgsKey: x8664UEFIBootPackageSet(), + edgePkgsKey: x8664EdgeCommitPackageSet(), + }, + legacy: "i386-pc", + bootType: distro.HybridBootType, + } + + aarch64 := architecture{ + name: distro.Aarch64ArchName, + distro: rd, + packageSets: map[string]rpmmd.PackageSet{ + bootUEFIPkgsKey: aarch64UEFIBootPackageSet(), + edgePkgsKey: aarch64EdgeCommitPackageSet(), + }, + bootType: distro.UEFIBootType, + } + + ppc64le := architecture{ + distro: rd, + name: distro.Ppc64leArchName, + packageSets: map[string]rpmmd.PackageSet{ + bootLegacyPkgsKey: ppc64leLegacyBootPackageSet(), + buildPkgsKey: ppc64leBuildPackageSet(), + }, + legacy: "powerpc-ieee1275", + bootType: distro.LegacyBootType, + } + s390x := architecture{ + distro: rd, + name: distro.S390xArchName, + packageSets: map[string]rpmmd.PackageSet{ + bootLegacyPkgsKey: s390xLegacyBootPackageSet(), + }, + bootType: distro.LegacyBootType, + } + + // Shared Services + edgeServices := []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", + } + + // Image Definitions + edgeCommitImgType := imageType{ + name: "edge-commit", + nameAliases: []string{"rhel-edge-commit"}, + filename: "commit.tar", + mimeType: "application/x-tar", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: edgeBuildPackageSet(), + osPkgsKey: edgeCommitPackageSet(), + }, + enabledServices: edgeServices, + rpmOstree: true, + pipelines: edgeCommitPipelines, + exports: []string{"commit-archive"}, + } + edgeOCIImgType := imageType{ + name: "edge-container", + nameAliases: []string{"rhel-edge-container"}, + filename: "container.tar", + mimeType: "application/x-tar", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: edgeBuildPackageSet(), + osPkgsKey: edgeCommitPackageSet(), + containerPkgsKey: {Include: []string{"httpd"}}, + }, + enabledServices: edgeServices, + rpmOstree: true, + bootISO: false, + pipelines: edgeContainerPipelines, + exports: []string{containerPkgsKey}, + } + edgeInstallerImgType := imageType{ + name: "edge-installer", + nameAliases: []string{"rhel-edge-installer"}, + filename: "installer.iso", + mimeType: "application/x-iso9660-image", + packageSets: map[string]rpmmd.PackageSet{ + // TODO: non-arch-specific package set handling for installers + // This image type requires build packages for installers and + // ostree/edge. For now we only have x86-64 installer build + // package sets defined. When we add installer build package sets + // for other architectures, this will need to be moved to the + // architecture and the merging will happen in the PackageSets() + // method like the other sets. + buildPkgsKey: x8664InstallerBuildPackageSet().Append(edgeBuildPackageSet()), + osPkgsKey: edgeCommitPackageSet(), + installerPkgsKey: edgeInstallerPackageSet(), + }, + enabledServices: edgeServices, + rpmOstree: true, + bootISO: true, + pipelines: edgeInstallerPipelines, + exports: []string{"bootiso"}, + } + + qcow2ImgType := imageType{ + name: "qcow2", + filename: "disk.qcow2", + mimeType: "application/x-qemu-disk", + defaultTarget: "multi-user.target", + kernelOptions: "console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0 crashkernel=auto", + packageSets: map[string]rpmmd.PackageSet{ + osPkgsKey: qcow2CommonPackageSet(), + }, + bootable: true, + defaultSize: 10 * GigaByte, + pipelines: qcow2Pipelines, + exports: []string{"qcow2"}, + basePartitionTables: defaultBasePartitionTables, + } + + vhdImgType := imageType{ + name: "vhd", + filename: "disk.vhd", + mimeType: "application/x-vhd", + packageSets: map[string]rpmmd.PackageSet{ + osPkgsKey: vhdCommonPackageSet(), + }, + enabledServices: []string{ + "sshd", + "waagent", + }, + defaultTarget: "multi-user.target", + kernelOptions: "ro biosdevname=0 rootdelay=300 console=ttyS0 earlyprintk=ttyS0 net.ifnames=0", + bootable: true, + defaultSize: 4 * GigaByte, + pipelines: vhdPipelines, + exports: []string{"vpc"}, + basePartitionTables: defaultBasePartitionTables, + } + + vmdkImgType := imageType{ + name: "vmdk", + filename: "disk.vmdk", + mimeType: "application/x-vmdk", + packageSets: map[string]rpmmd.PackageSet{ + osPkgsKey: vmdkCommonPackageSet(), + }, + kernelOptions: "ro net.ifnames=0", + bootable: true, + defaultSize: 4 * GigaByte, + pipelines: vmdkPipelines, + exports: []string{"vmdk"}, + basePartitionTables: defaultBasePartitionTables, + } + + openstackImgType := imageType{ + name: "openstack", + filename: "disk.qcow2", + mimeType: "application/x-qemu-disk", + packageSets: map[string]rpmmd.PackageSet{ + osPkgsKey: openstackCommonPackageSet(), + }, + kernelOptions: "ro net.ifnames=0", + bootable: true, + defaultSize: 4 * GigaByte, + pipelines: openstackPipelines, + exports: []string{"qcow2"}, + basePartitionTables: defaultBasePartitionTables, + } + + // EC2 services + ec2EnabledServices := []string{ + "sshd", + "NetworkManager", + "nm-cloud-setup.service", + "nm-cloud-setup.timer", + "cloud-init", + "cloud-init-local", + "cloud-config", + "cloud-final", + "reboot.target", + } + + amiImgTypeX86_64 := imageType{ + name: "ami", + filename: "image.raw", + mimeType: "application/octet-stream", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: ec2BuildPackageSet(), + osPkgsKey: ec2CommonPackageSet(), + }, + defaultTarget: "multi-user.target", + enabledServices: ec2EnabledServices, + kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", + bootable: true, + bootType: distro.LegacyBootType, + defaultSize: 10 * GigaByte, + pipelines: ec2Pipelines, + exports: []string{"image"}, + basePartitionTables: ec2BasePartitionTables, + } + + amiImgTypeAarch64 := imageType{ + name: "ami", + filename: "image.raw", + mimeType: "application/octet-stream", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: ec2BuildPackageSet(), + osPkgsKey: ec2CommonPackageSet(), + }, + defaultTarget: "multi-user.target", + enabledServices: ec2EnabledServices, + kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0 crashkernel=auto", + bootable: true, + defaultSize: 10 * GigaByte, + pipelines: ec2Pipelines, + exports: []string{"image"}, + basePartitionTables: ec2BasePartitionTables, + } + + ec2ImgTypeX86_64 := imageType{ + name: "ec2", + filename: "image.raw.xz", + mimeType: "application/xz", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: ec2BuildPackageSet(), + osPkgsKey: rhelEc2PackageSet(), + }, + defaultTarget: "multi-user.target", + enabledServices: ec2EnabledServices, + kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", + bootable: true, + bootType: distro.LegacyBootType, + defaultSize: 10 * GigaByte, + pipelines: rhelEc2Pipelines, + exports: []string{"archive"}, + basePartitionTables: ec2BasePartitionTables, + } + + ec2ImgTypeAarch64 := imageType{ + name: "ec2", + filename: "image.raw.xz", + mimeType: "application/xz", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: ec2BuildPackageSet(), + osPkgsKey: rhelEc2PackageSet(), + }, + defaultTarget: "multi-user.target", + enabledServices: ec2EnabledServices, + kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0 crashkernel=auto", + bootable: true, + defaultSize: 10 * GigaByte, + pipelines: rhelEc2Pipelines, + exports: []string{"archive"}, + basePartitionTables: ec2BasePartitionTables, + } + + ec2HaImgTypeX86_64 := imageType{ + name: "ec2-ha", + filename: "image.raw.xz", + mimeType: "application/xz", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: ec2BuildPackageSet(), + osPkgsKey: rhelEc2HaPackageSet(), + }, + defaultTarget: "multi-user.target", + enabledServices: ec2EnabledServices, + kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto", + bootable: true, + bootType: distro.LegacyBootType, + defaultSize: 10 * GigaByte, + pipelines: rhelEc2Pipelines, + exports: []string{"archive"}, + basePartitionTables: ec2BasePartitionTables, + } + + ec2SapImgTypeX86_64 := imageType{ + name: "ec2-sap", + filename: "image.raw.xz", + mimeType: "application/xz", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: ec2BuildPackageSet(), + osPkgsKey: rhelEc2SapPackageSet(), + }, + defaultTarget: "multi-user.target", + enabledServices: ec2EnabledServices, + kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto processor.max_cstate=1 intel_idle.max_cstate=1", + bootable: true, + bootType: distro.LegacyBootType, + defaultSize: 10 * GigaByte, + pipelines: rhelEc2SapPipelines, + exports: []string{"archive"}, + basePartitionTables: ec2BasePartitionTables, + } + + tarImgType := imageType{ + name: "tar", + filename: "root.tar.xz", + mimeType: "application/x-tar", + packageSets: map[string]rpmmd.PackageSet{ + osPkgsKey: { + Include: []string{"policycoreutils", "selinux-policy-targeted"}, + Exclude: []string{"rng-tools"}, + }, + }, + pipelines: tarPipelines, + exports: []string{"root-tar"}, + } + tarInstallerImgTypeX86_64 := imageType{ + name: "image-installer", + filename: "installer.iso", + mimeType: "application/x-iso9660-image", + packageSets: map[string]rpmmd.PackageSet{ + buildPkgsKey: x8664InstallerBuildPackageSet(), + osPkgsKey: bareMetalPackageSet(), + installerPkgsKey: installerPackageSet(), + }, + rpmOstree: false, + bootISO: true, + bootable: true, + pipelines: tarInstallerPipelines, + exports: []string{"bootiso"}, + } + + x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgTypeX86_64, ec2ImgTypeX86_64, ec2HaImgTypeX86_64, ec2SapImgTypeX86_64, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType) + aarch64.addImageTypes(qcow2ImgType, openstackImgType, amiImgTypeAarch64, ec2ImgTypeAarch64, tarImgType, edgeCommitImgType, edgeOCIImgType) + ppc64le.addImageTypes(qcow2ImgType, tarImgType) + s390x.addImageTypes(qcow2ImgType, tarImgType) + + rd.addArches(x86_64, aarch64, ppc64le, s390x) + return rd +} diff --git a/internal/distro/rhel90/distro_test.go b/internal/distro/rhel90/distro_test.go new file mode 100644 index 000000000..b40f0c1e0 --- /dev/null +++ b/internal/distro/rhel90/distro_test.go @@ -0,0 +1,790 @@ +package rhel90_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/osbuild/osbuild-composer/internal/blueprint" + "github.com/osbuild/osbuild-composer/internal/distro" + "github.com/osbuild/osbuild-composer/internal/distro/distro_test_common" + rhel90 "github.com/osbuild/osbuild-composer/internal/distro/rhel90beta" +) + +type rhelFamilyDistro struct { + name string + distro distro.Distro +} + +var rhelFamilyDistros = []rhelFamilyDistro{ + { + name: "rhel", + distro: rhel90.New(), + }, +} + +func TestFilenameFromType(t *testing.T) { + type args struct { + outputFormat string + } + type wantResult struct { + filename string + mimeType string + wantErr bool + } + tests := []struct { + name string + args args + want wantResult + }{ + { + name: "ami", + args: args{"ami"}, + want: wantResult{ + filename: "image.raw", + mimeType: "application/octet-stream", + }, + }, + { + name: "ec2", + args: args{"ec2"}, + want: wantResult{ + filename: "image.raw.xz", + mimeType: "application/xz", + }, + }, + { + name: "ec2-ha", + args: args{"ec2-ha"}, + want: wantResult{ + filename: "image.raw.xz", + mimeType: "application/xz", + }, + }, + { + name: "ec2-sap", + args: args{"ec2-sap"}, + want: wantResult{ + filename: "image.raw.xz", + mimeType: "application/xz", + }, + }, + { + name: "qcow2", + args: args{"qcow2"}, + want: wantResult{ + filename: "disk.qcow2", + mimeType: "application/x-qemu-disk", + }, + }, + { + name: "openstack", + args: args{"openstack"}, + want: wantResult{ + filename: "disk.qcow2", + mimeType: "application/x-qemu-disk", + }, + }, + { + name: "vhd", + args: args{"vhd"}, + want: wantResult{ + filename: "disk.vhd", + mimeType: "application/x-vhd", + }, + }, + { + name: "vmdk", + args: args{"vmdk"}, + want: wantResult{ + filename: "disk.vmdk", + mimeType: "application/x-vmdk", + }, + }, + { + name: "tar", + args: args{"tar"}, + want: wantResult{ + filename: "root.tar.xz", + mimeType: "application/x-tar", + }, + }, + { + name: "image-installer", + args: args{"image-installer"}, + want: wantResult{ + filename: "installer.iso", + mimeType: "application/x-iso9660-image", + }, + }, + { + name: "edge-commit", + args: args{"edge-commit"}, + want: wantResult{ + filename: "commit.tar", + mimeType: "application/x-tar", + }, + }, + // Alias + { + name: "rhel-edge-commit", + args: args{"rhel-edge-commit"}, + want: wantResult{ + filename: "commit.tar", + mimeType: "application/x-tar", + }, + }, + { + name: "edge-container", + args: args{"edge-container"}, + want: wantResult{ + filename: "container.tar", + mimeType: "application/x-tar", + }, + }, + // Alias + { + name: "rhel-edge-container", + args: args{"rhel-edge-container"}, + want: wantResult{ + filename: "container.tar", + mimeType: "application/x-tar", + }, + }, + { + name: "edge-installer", + args: args{"edge-installer"}, + want: wantResult{ + filename: "installer.iso", + mimeType: "application/x-iso9660-image", + }, + }, + // Alias + { + name: "rhel-edge-installer", + args: args{"rhel-edge-installer"}, + want: wantResult{ + filename: "installer.iso", + mimeType: "application/x-iso9660-image", + }, + }, + { + name: "invalid-output-type", + args: args{"foobar"}, + want: wantResult{wantErr: true}, + }, + } + for _, dist := range rhelFamilyDistros { + t.Run(dist.name, func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dist := dist.distro + arch, _ := dist.GetArch("x86_64") + imgType, err := arch.GetImageType(tt.args.outputFormat) + if (err != nil) != tt.want.wantErr { + t.Errorf("Arch.GetImageType() error = %v, wantErr %v", err, tt.want.wantErr) + return + } + if !tt.want.wantErr { + gotFilename := imgType.Filename() + gotMIMEType := imgType.MIMEType() + if gotFilename != tt.want.filename { + t.Errorf("ImageType.Filename() got = %v, want %v", gotFilename, tt.want.filename) + } + if gotMIMEType != tt.want.mimeType { + t.Errorf("ImageType.MIMEType() got1 = %v, want %v", gotMIMEType, tt.want.mimeType) + } + } + }) + } + }) + } +} + +func TestImageType_BuildPackages(t *testing.T) { + x8664BuildPackages := []string{ + "dnf", + "dosfstools", + "e2fsprogs", + "grub2-efi-x64", + "grub2-pc", + "policycoreutils", + "shim-x64", + "systemd", + "tar", + "qemu-img", + "xz", + } + aarch64BuildPackages := []string{ + "dnf", + "dosfstools", + "e2fsprogs", + "policycoreutils", + "qemu-img", + "systemd", + "tar", + "xz", + } + buildPackages := map[string][]string{ + "x86_64": x8664BuildPackages, + "aarch64": aarch64BuildPackages, + } + for _, dist := range rhelFamilyDistros { + t.Run(dist.name, func(t *testing.T) { + d := dist.distro + for _, archLabel := range d.ListArches() { + archStruct, err := d.GetArch(archLabel) + if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) { + continue + } + for _, itLabel := range archStruct.ListImageTypes() { + itStruct, err := archStruct.GetImageType(itLabel) + if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) { + continue + } + buildPkgs := itStruct.PackageSets(blueprint.Blueprint{})["build"] + assert.NotNil(t, buildPkgs) + assert.ElementsMatch(t, buildPackages[archLabel], buildPkgs.Include) + } + } + }) + } +} + +func TestImageType_Name(t *testing.T) { + imgMap := []struct { + arch string + imgNames []string + }{ + { + arch: "x86_64", + imgNames: []string{ + "qcow2", + "openstack", + "vhd", + "vmdk", + "ami", + "ec2", + "ec2-ha", + "ec2-sap", + "edge-commit", + "edge-container", + "edge-installer", + "tar", + "image-installer", + }, + }, + { + arch: "aarch64", + imgNames: []string{ + "qcow2", + "openstack", + "ami", + "ec2", + "edge-commit", + "edge-container", + "tar", + }, + }, + { + arch: "ppc64le", + imgNames: []string{ + "qcow2", + "tar", + }, + }, + { + arch: "s390x", + imgNames: []string{ + "qcow2", + "tar", + }, + }, + } + + for _, dist := range rhelFamilyDistros { + t.Run(dist.name, func(t *testing.T) { + for _, mapping := range imgMap { + if mapping.arch == "s390x" && dist.name == "centos" { + continue + } + arch, err := dist.distro.GetArch(mapping.arch) + if assert.NoError(t, err) { + for _, imgName := range mapping.imgNames { + if imgName == "edge-commit" && dist.name == "centos" { + continue + } + imgType, err := arch.GetImageType(imgName) + if assert.NoError(t, err) { + assert.Equalf(t, imgName, imgType.Name(), "arch: %s", mapping.arch) + } + } + } + } + }) + } +} + +func TestImageTypeAliases(t *testing.T) { + type args struct { + imageTypeAliases []string + } + type wantResult struct { + imageTypeName string + } + tests := []struct { + name string + args args + want wantResult + }{ + { + name: "edge-commit aliases", + args: args{ + imageTypeAliases: []string{"rhel-edge-commit"}, + }, + want: wantResult{ + imageTypeName: "edge-commit", + }, + }, + { + name: "edge-container aliases", + args: args{ + imageTypeAliases: []string{"rhel-edge-container"}, + }, + want: wantResult{ + imageTypeName: "edge-container", + }, + }, + { + name: "edge-installer aliases", + args: args{ + imageTypeAliases: []string{"rhel-edge-installer"}, + }, + want: wantResult{ + imageTypeName: "edge-installer", + }, + }, + } + for _, dist := range rhelFamilyDistros { + t.Run(dist.name, func(t *testing.T) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dist := dist.distro + for _, archName := range dist.ListArches() { + t.Run(archName, func(t *testing.T) { + arch, err := dist.GetArch(archName) + require.Nilf(t, err, + "failed to get architecture '%s', previously listed as supported for the distro '%s'", + archName, dist.Name()) + // Test image type aliases only if the aliased image type is supported for the arch + if _, err = arch.GetImageType(tt.want.imageTypeName); err != nil { + t.Skipf("aliased image type '%s' is not supported for architecture '%s'", + tt.want.imageTypeName, archName) + } + for _, alias := range tt.args.imageTypeAliases { + t.Run(fmt.Sprintf("'%s' alias for image type '%s'", alias, tt.want.imageTypeName), + func(t *testing.T) { + gotImage, err := arch.GetImageType(alias) + require.Nilf(t, err, "arch.GetImageType() for image type alias '%s' failed: %v", + alias, err) + assert.Equalf(t, tt.want.imageTypeName, gotImage.Name(), + "got unexpected image type name for alias '%s'. got = %s, want = %s", + alias, tt.want.imageTypeName, gotImage.Name()) + }) + } + }) + } + }) + } + }) + } +} + +// Check that Manifest() function returns an error for unsupported +// configurations. +func TestDistro_ManifestError(t *testing.T) { + // Currently, the only unsupported configuration is OSTree commit types + // with Kernel boot options + r9distro := rhel90.New() + bp := blueprint.Blueprint{ + Customizations: &blueprint.Customizations{ + Kernel: &blueprint.KernelCustomization{ + Append: "debug", + }, + }, + } + + for _, archName := range r9distro.ListArches() { + arch, _ := r9distro.GetArch(archName) + for _, imgTypeName := range arch.ListImageTypes() { + imgType, _ := arch.GetImageType(imgTypeName) + imgOpts := distro.ImageOptions{ + Size: imgType.Size(0), + } + _, err := imgType.Manifest(bp.Customizations, imgOpts, nil, nil, 0) + if imgTypeName == "edge-commit" || imgTypeName == "edge-container" { + assert.EqualError(t, err, "kernel boot parameter customizations are not supported for ostree types") + } else if imgTypeName == "edge-installer" { + assert.EqualError(t, err, "boot ISO image type \"edge-installer\" requires specifying a URL from which to retrieve the OSTree commit") + } else { + assert.NoError(t, err) + } + } + } +} + +func TestArchitecture_ListImageTypes(t *testing.T) { + imgMap := []struct { + arch string + imgNames []string + rhelAdditionalImageTypes []string + }{ + { + arch: "x86_64", + imgNames: []string{ + "qcow2", + "openstack", + "vhd", + "vmdk", + "ami", + "ec2", + "ec2-ha", + "ec2-sap", + "edge-commit", + "edge-container", + "edge-installer", + "tar", + "image-installer", + }, + }, + { + arch: "aarch64", + imgNames: []string{ + "qcow2", + "openstack", + "ami", + "ec2", + "edge-commit", + "edge-container", + "tar", + }, + }, + { + arch: "ppc64le", + imgNames: []string{ + "qcow2", + "tar", + }, + }, + { + arch: "s390x", + imgNames: []string{ + "qcow2", + "tar", + }, + }, + } + + for _, dist := range rhelFamilyDistros { + t.Run(dist.name, func(t *testing.T) { + for _, mapping := range imgMap { + arch, err := dist.distro.GetArch(mapping.arch) + require.NoError(t, err) + imageTypes := arch.ListImageTypes() + + var expectedImageTypes []string + expectedImageTypes = append(expectedImageTypes, mapping.imgNames...) + if dist.name == "rhel" { + expectedImageTypes = append(expectedImageTypes, mapping.rhelAdditionalImageTypes...) + } + + require.ElementsMatch(t, expectedImageTypes, imageTypes) + } + }) + } +} + +func TestRhel90_ListArches(t *testing.T) { + arches := rhel90.New().ListArches() + assert.Equal(t, []string{"aarch64", "ppc64le", "s390x", "x86_64"}, arches) +} + +func TestRhel90_GetArch(t *testing.T) { + arches := []struct { + name string + errorExpected bool + errorExpectedInCentos bool + }{ + { + name: "x86_64", + }, + { + name: "aarch64", + }, + { + name: "ppc64le", + }, + { + name: "s390x", + }, + { + name: "foo-arch", + errorExpected: true, + }, + } + + for _, dist := range rhelFamilyDistros { + t.Run(dist.name, func(t *testing.T) { + for _, a := range arches { + actualArch, err := dist.distro.GetArch(a.name) + if a.errorExpected || (a.errorExpectedInCentos && dist.name == "centos") { + assert.Nil(t, actualArch) + assert.Error(t, err) + } else { + assert.Equal(t, a.name, actualArch.Name()) + assert.NoError(t, err) + } + } + }) + } +} + +func TestRhel90_Name(t *testing.T) { + distro := rhel90.New() + assert.Equal(t, "rhel-90-beta", distro.Name()) +} + +func TestRhel90Base_Name(t *testing.T) { + distro := rhel90.NewRHEL90() + assert.Equal(t, "rhel-90", distro.Name()) +} + +func TestRhel90_ModulePlatformID(t *testing.T) { + distro := rhel90.New() + assert.Equal(t, "platform:el9", distro.ModulePlatformID()) +} + +func TestRhel90_KernelOption(t *testing.T) { + distro_test_common.TestDistro_KernelOption(t, rhel90.New()) +} + +func TestDistro_CustomFileSystemManifestError(t *testing.T) { + r9distro := rhel90.New() + bp := blueprint.Blueprint{ + Customizations: &blueprint.Customizations{ + Filesystem: []blueprint.FilesystemCustomization{ + { + MinSize: 1024, + Mountpoint: "/boot", + }, + }, + }, + } + for _, archName := range r9distro.ListArches() { + arch, _ := r9distro.GetArch(archName) + for _, imgTypeName := range arch.ListImageTypes() { + imgType, _ := arch.GetImageType(imgTypeName) + _, err := imgType.Manifest(bp.Customizations, distro.ImageOptions{}, nil, nil, 0) + if imgTypeName == "edge-commit" || imgTypeName == "edge-container" { + assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types") + } else if imgTypeName == "edge-installer" { + continue + } else { + assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/boot\"]") + } + } + } +} + +func TestDistro_TestRootMountPoint(t *testing.T) { + r9distro := rhel90.New() + bp := blueprint.Blueprint{ + Customizations: &blueprint.Customizations{ + Filesystem: []blueprint.FilesystemCustomization{ + { + MinSize: 1024, + Mountpoint: "/", + }, + }, + }, + } + for _, archName := range r9distro.ListArches() { + arch, _ := r9distro.GetArch(archName) + for _, imgTypeName := range arch.ListImageTypes() { + imgType, _ := arch.GetImageType(imgTypeName) + _, err := imgType.Manifest(bp.Customizations, distro.ImageOptions{}, nil, nil, 0) + if imgTypeName == "edge-commit" || imgTypeName == "edge-container" { + assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types") + } else if imgTypeName == "edge-installer" { + continue + } else { + assert.NoError(t, err) + } + } + } +} + +func TestDistro_CustomFileSystemSubDirectories(t *testing.T) { + r9distro := rhel90.New() + bp := blueprint.Blueprint{ + Customizations: &blueprint.Customizations{ + Filesystem: []blueprint.FilesystemCustomization{ + { + MinSize: 1024, + Mountpoint: "/var/log", + }, + { + MinSize: 1024, + Mountpoint: "/var/log/audit", + }, + }, + }, + } + for _, archName := range r9distro.ListArches() { + arch, _ := r9distro.GetArch(archName) + for _, imgTypeName := range arch.ListImageTypes() { + imgType, _ := arch.GetImageType(imgTypeName) + _, err := imgType.Manifest(bp.Customizations, distro.ImageOptions{}, nil, nil, 0) + if strings.HasPrefix(imgTypeName, "edge-") { + continue + } else { + assert.NoError(t, err) + } + } + } +} + +func TestDistro_MountpointsWithArbitraryDepthAllowed(t *testing.T) { + r9distro := rhel90.New() + bp := blueprint.Blueprint{ + Customizations: &blueprint.Customizations{ + Filesystem: []blueprint.FilesystemCustomization{ + { + MinSize: 1024, + Mountpoint: "/var/a", + }, + { + MinSize: 1024, + Mountpoint: "/var/a/b", + }, + { + MinSize: 1024, + Mountpoint: "/var/a/b/c", + }, + { + MinSize: 1024, + Mountpoint: "/var/a/b/c/d", + }, + }, + }, + } + for _, archName := range r9distro.ListArches() { + arch, _ := r9distro.GetArch(archName) + for _, imgTypeName := range arch.ListImageTypes() { + imgType, _ := arch.GetImageType(imgTypeName) + _, err := imgType.Manifest(bp.Customizations, distro.ImageOptions{}, nil, nil, 0) + if strings.HasPrefix(imgTypeName, "edge-") { + continue + } else { + assert.NoError(t, err) + } + } + } +} + +func TestDistro_DirtyMountpointsNotAllowed(t *testing.T) { + r9distro := rhel90.New() + bp := blueprint.Blueprint{ + Customizations: &blueprint.Customizations{ + Filesystem: []blueprint.FilesystemCustomization{ + { + MinSize: 1024, + Mountpoint: "//", + }, + { + MinSize: 1024, + Mountpoint: "/var//", + }, + { + MinSize: 1024, + Mountpoint: "/var//log/audit/", + }, + }, + }, + } + for _, archName := range r9distro.ListArches() { + arch, _ := r9distro.GetArch(archName) + for _, imgTypeName := range arch.ListImageTypes() { + imgType, _ := arch.GetImageType(imgTypeName) + _, err := imgType.Manifest(bp.Customizations, distro.ImageOptions{}, nil, nil, 0) + if strings.HasPrefix(imgTypeName, "edge-") { + continue + } else { + assert.EqualError(t, err, "The following custom mountpoints are not supported [\"//\" \"/var//\" \"/var//log/audit/\"]") + } + } + } +} + +func TestDistro_CustomFileSystemPatternMatching(t *testing.T) { + r9distro := rhel90.New() + bp := blueprint.Blueprint{ + Customizations: &blueprint.Customizations{ + Filesystem: []blueprint.FilesystemCustomization{ + { + MinSize: 1024, + Mountpoint: "/variable", + }, + { + MinSize: 1024, + Mountpoint: "/variable/log/audit", + }, + }, + }, + } + for _, archName := range r9distro.ListArches() { + arch, _ := r9distro.GetArch(archName) + for _, imgTypeName := range arch.ListImageTypes() { + imgType, _ := arch.GetImageType(imgTypeName) + _, err := imgType.Manifest(bp.Customizations, distro.ImageOptions{}, nil, nil, 0) + if imgTypeName == "edge-commit" || imgTypeName == "edge-container" { + assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types") + } else if imgTypeName == "edge-installer" { + continue + } else { + assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/variable\" \"/variable/log/audit\"]") + } + } + } +} + +func TestDistro_CustomUsrPartitionNotLargeEnough(t *testing.T) { + r9distro := rhel90.New() + bp := blueprint.Blueprint{ + Customizations: &blueprint.Customizations{ + Filesystem: []blueprint.FilesystemCustomization{ + { + MinSize: 1024, + Mountpoint: "/usr", + }, + }, + }, + } + for _, archName := range r9distro.ListArches() { + arch, _ := r9distro.GetArch(archName) + for _, imgTypeName := range arch.ListImageTypes() { + imgType, _ := arch.GetImageType(imgTypeName) + _, err := imgType.Manifest(bp.Customizations, distro.ImageOptions{}, nil, nil, 0) + if imgTypeName == "edge-commit" || imgTypeName == "edge-container" { + assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types") + } else if imgTypeName == "edge-installer" { + continue + } else { + assert.NoError(t, err) + } + } + } +} diff --git a/internal/distro/rhel90/package_sets.go b/internal/distro/rhel90/package_sets.go new file mode 100644 index 000000000..7290e4100 --- /dev/null +++ b/internal/distro/rhel90/package_sets.go @@ -0,0 +1,451 @@ +package rhel90 + +// This file defines package sets that are used by more than one image type. + +import "github.com/osbuild/osbuild-composer/internal/rpmmd" + +// BUILD PACKAGE SETS + +// distro-wide build package set +func distroBuildPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "dnf", "dosfstools", "e2fsprogs", "glibc", "lorax-templates-generic", + "lorax-templates-rhel", "policycoreutils", + "python3-iniparse", "qemu-img", "selinux-policy-targeted", "systemd", + "tar", "xfsprogs", "xz", + }, + } +} + +// x86_64 build package set +func x8664BuildPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{"grub2-pc"}, + } +} + +// ppc64le build package set +func ppc64leBuildPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{"grub2-ppc64le", "grub2-ppc64le-modules"}, + } +} + +// common ec2 image build package set +func ec2BuildPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{"python3-pyyaml"}, + } +} + +// common edge image build package set +func edgeBuildPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{"rpm-ostree"}, + Exclude: nil, + } +} + +// x86_64 installer ISO build package set +// TODO: separate into common installer and arch specific sets +func x8664InstallerBuildPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "efibootmgr", "grub2-efi-ia32-cdboot", + "grub2-efi-x64", "grub2-efi-x64-cdboot", "grub2-pc", + "grub2-pc-modules", "grub2-tools", "grub2-tools-efi", + "grub2-tools-extra", "grub2-tools-minimal", "isomd5sum", + "rpm-ostree", "shim-ia32", "shim-x64", "squashfs-tools", + "syslinux", "syslinux-nonlinux", "xorriso", + }, + } +} + +// BOOT PACKAGE SETS + +// x86_64 Legacy arch-specific boot package set +func x8664LegacyBootPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{"dracut-config-generic", "grub2-pc"}, + } +} + +// x86_64 UEFI arch-specific boot package set +func x8664UEFIBootPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{"dracut-config-generic", "grub2-efi-x64", "shim-x64"}, + } +} + +// aarch64 UEFI arch-specific boot package set +func aarch64UEFIBootPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "dracut-config-generic", "efibootmgr", "grub2-efi-aa64", + "grub2-tools", "shim-aa64", + }, + } +} + +// ppc64le Legacy arch-specific boot package set +func ppc64leLegacyBootPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "dracut-config-generic", "powerpc-utils", "grub2-ppc64le", + "grub2-ppc64le-modules", + }, + } +} + +// s390x Legacy arch-specific boot package set +func s390xLegacyBootPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{"dracut-config-generic", "s390utils-base"}, + } +} + +// OS package sets + +func qcow2CommonPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "@core", "authselect-compat", "chrony", "cloud-init", + "cloud-utils-growpart", "cockpit-system", "cockpit-ws", + "dnf", "dnf-utils", "dosfstools", + "insights-client", "NetworkManager", "nfs-utils", + "oddjob", "oddjob-mkhomedir", "psmisc", "python3-jsonschema", + "qemu-guest-agent", "redhat-release", "redhat-release-eula", + "rsync", "subscription-manager-cockpit", "tar", "tcpdump", "yum", + }, + Exclude: []string{ + "aic94xx-firmware", "alsa-firmware", "alsa-lib", + "alsa-tools-firmware", "biosdevname", "dnf-plugin-spacewalk", + "dracut-config-rescue", "fedora-release", "fedora-repos", + "firewalld", "iprutils", "ivtv-firmware", + "iwl100-firmware", "iwl1000-firmware", "iwl105-firmware", + "iwl135-firmware", "iwl2000-firmware", "iwl2030-firmware", + "iwl3160-firmware", "iwl3945-firmware", "iwl4965-firmware", + "iwl5000-firmware", "iwl5150-firmware", "iwl6000-firmware", + "iwl6000g2a-firmware", "iwl6000g2b-firmware", "iwl6050-firmware", + "iwl7260-firmware", "langpacks-*", "langpacks-en", "langpacks-en", + "libertas-sd8686-firmware", "libertas-sd8787-firmware", + "libertas-usb8388-firmware", "nss", "plymouth", "rng-tools", + "udisks2", + }, + } +} + +func vhdCommonPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + // Defaults + "@Core", "langpacks-en", + + // From the lorax kickstart + "selinux-policy-targeted", "chrony", "WALinuxAgent", "python3", + "net-tools", "cloud-init", "cloud-utils-growpart", "gdisk", + + // removed from defaults but required to boot in azure + "dhcp-client", + }, + Exclude: []string{ + "dracut-config-rescue", "rng-tools", + }, + } +} + +func vmdkCommonPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "@core", "chrony", "firewalld", "langpacks-en", "open-vm-tools", + "selinux-policy-targeted", + }, + Exclude: []string{ + "dracut-config-rescue", "rng-tools", + }, + } + +} + +func openstackCommonPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + // Defaults + "@Core", "langpacks-en", + + // From the lorax kickstart + "selinux-policy-targeted", "cloud-init", "qemu-guest-agent", + "spice-vdagent", + }, + Exclude: []string{ + "dracut-config-rescue", "rng-tools", + }, + } + +} + +func ec2CommonPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "@core", "authselect-compat", "chrony", "cloud-init", "cloud-utils-growpart", + "dhcp-client", "yum-utils", "dracut-config-generic", "gdisk", + "grub2", "insights-client", "langpacks-en", "NetworkManager", + "NetworkManager-cloud-setup", "redhat-release", + "redhat-release-eula", "rsync", "tar", "qemu-guest-agent", + }, + Exclude: []string{ + "aic94xx-firmware", "alsa-firmware", + "alsa-tools-firmware", "biosdevname", "firewalld", "iprutils", "ivtv-firmware", + "iwl1000-firmware", "iwl100-firmware", "iwl105-firmware", + "iwl135-firmware", "iwl2000-firmware", "iwl2030-firmware", + "iwl3160-firmware", "iwl3945-firmware", "iwl4965-firmware", + "iwl5000-firmware", "iwl5150-firmware", "iwl6000-firmware", + "iwl6000g2a-firmware", "iwl6000g2b-firmware", "iwl6050-firmware", + "iwl7260-firmware", "libertas-sd8686-firmware", + "libertas-sd8787-firmware", "libertas-usb8388-firmware", + "plymouth", + }, + } +} + +// rhel-ec2 image package set +func rhelEc2PackageSet() rpmmd.PackageSet { + ec2PackageSet := ec2CommonPackageSet() + ec2PackageSet.Include = append(ec2PackageSet.Include, "rh-amazon-rhui-client") + ec2PackageSet.Exclude = append(ec2PackageSet.Exclude, "alsa-lib") + return ec2PackageSet +} + +// rhel-ha-ec2 image package set +func rhelEc2HaPackageSet() rpmmd.PackageSet { + ec2HaPackageSet := ec2CommonPackageSet() + ec2HaPackageSet.Include = append(ec2HaPackageSet.Include, + "fence-agents-all", + "pacemaker", + "pcs", + "rh-amazon-rhui-client-ha", + ) + ec2HaPackageSet.Exclude = append(ec2HaPackageSet.Exclude, "alsa-lib") + return ec2HaPackageSet +} + +// rhel-sap-ec2 image package set +func rhelEc2SapPackageSet() rpmmd.PackageSet { + ec2SapPackageSet := ec2CommonPackageSet() + ec2SapPackageSet.Include = append(ec2SapPackageSet.Include, + // SAP System Roles + // https://access.redhat.com/sites/default/files/attachments/rhel_system_roles_for_sap_1.pdf + "ansible-core", + "rhel-system-roles-sap", + // RHBZ#1959813 + "bind-utils", + //"compat-sap-c++-9", // not needed on RHEL-9 + "nfs-utils", + "tcsh", + // RHBZ#1959955 + "uuidd", + // RHBZ#1959923 + "cairo", + "expect", + "graphviz", + "gtk2", + "iptraf-ng", + "krb5-workstation", + "libaio", + "libatomic", + "libcanberra-gtk2", + "libicu", + //"libpng12", // not needed on RHEL-9 + "libtool-ltdl", + "lm_sensors", + "net-tools", + "numactl", + "PackageKit-gtk3-module", + "xorg-x11-xauth", + // RHBZ#1960617 + "tuned-profiles-sap-hana", + // RHBZ#1961168 + "libnsl", + // RHUI client + "rh-amazon-rhui-client-sap-bundle-e4s", + ) + return ec2SapPackageSet +} + +// edge commit OS package set +func edgeCommitPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "redhat-release", "glibc", "glibc-minimal-langpack", + "nss-altfiles", "dracut-config-generic", "dracut-network", + "basesystem", "bash", "platform-python", "shadow-utils", "chrony", + "setup", "shadow-utils", "sudo", "systemd", "coreutils", + "util-linux", "curl", "vim-minimal", "rpm", "rpm-ostree", "polkit", + "lvm2", "cryptsetup", "pinentry", "e2fsprogs", "dosfstools", + "keyutils", "gnupg2", "attr", "xz", "gzip", "firewalld", + "iptables", "NetworkManager", "NetworkManager-wifi", + "NetworkManager-wwan", "wpa_supplicant", "dnsmasq", "traceroute", + "hostname", "iproute", "iputils", "openssh-clients", "procps-ng", + "rootfiles", "openssh-server", "passwd", "policycoreutils", + "policycoreutils-python-utils", "selinux-policy-targeted", + "setools-console", "less", "tar", "rsync", "fwupd", "usbguard", + "bash-completion", "tmux", "ima-evm-utils", "audit", "podman", + "container-selinux", "skopeo", "criu", "slirp4netns", + "fuse-overlayfs", "clevis", "clevis-dracut", "clevis-luks", + "greenboot", "greenboot-grub2", "greenboot-rpm-ostree-grub2", + "greenboot-reboot", "greenboot-status", + }, + Exclude: []string{"rng-tools"}, + } +} + +func x8664EdgeCommitPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "grub2", "grub2-efi-x64", "efibootmgr", "shim-x64", + "microcode_ctl", "iwl1000-firmware", "iwl100-firmware", + "iwl105-firmware", "iwl135-firmware", "iwl2000-firmware", + "iwl2030-firmware", "iwl3160-firmware", "iwl5000-firmware", + "iwl5150-firmware", "iwl6050-firmware", + "iwl7260-firmware", + }, + Exclude: nil, + } +} + +func aarch64EdgeCommitPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{"grub2-efi-aa64", "efibootmgr", "shim-aa64", "iwl7260-firmware"}, + Exclude: nil, + } +} + +// INSTALLER PACKAGE SET +func installerPackageSet() rpmmd.PackageSet { + // TODO: simplify + return rpmmd.PackageSet{ + Include: []string{ + "aajohan-comfortaa-fonts", "abattis-cantarell-fonts", + "alsa-firmware", "alsa-tools-firmware", "anaconda", + "anaconda-dracut", "anaconda-install-env-deps", "anaconda-widgets", + "audit", "bind-utils", "biosdevname", "bitmap-fangsongti-fonts", + "bzip2", "cryptsetup", "curl", "dbus-x11", "dejavu-sans-fonts", + "dejavu-sans-mono-fonts", "device-mapper-persistent-data", + "dmidecode", "dnf", "dracut-config-generic", "dracut-network", + /*"dump",*/ "efibootmgr", "ethtool", "ftp", "gdb-gdbserver", "gdisk", + /*"gfs2-utils",*/ "glibc-all-langpacks", "gnome-kiosk", + "google-noto-sans-cjk-ttc-fonts", "grub2-efi-ia32-cdboot", + "grub2-efi-x64-cdboot", "grub2-tools", "grub2-tools-efi", + "grub2-tools-extra", "grub2-tools-minimal", "grubby", + "gsettings-desktop-schemas", "hdparm", "hexedit", "hostname", + "initscripts", "ipmitool", "iwl1000-firmware", "iwl100-firmware", + "iwl105-firmware", "iwl135-firmware", "iwl2000-firmware", + "iwl2030-firmware", "iwl3160-firmware", /*"iwl3945-firmware",*/ + /*"iwl4965-firmware",*/ "iwl5000-firmware", "iwl5150-firmware", + /*"iwl6000-firmware",*/ "iwl6000g2a-firmware", "iwl6000g2b-firmware", + "iwl6050-firmware", "iwl7260-firmware", "jomolhari-fonts", + "kacst-farsi-fonts", "kacst-qurn-fonts", "kbd", "kbd-misc", + "kdump-anaconda-addon", "kernel", "khmeros-base-fonts", "less", + "libblockdev-lvm-dbus", /*"libertas-sd8686-firmware",*/ + /*"libertas-sd8787-firmware",*/ /*"libertas-usb8388-firmware",*/ + /*"libertas-usb8388-olpc-firmware",*/ "libibverbs", + "libreport-plugin-bugzilla", "libreport-plugin-reportuploader", + /*"libreport-rhel-anaconda-bugzilla",*/ "librsvg2", "linux-firmware", + "lklug-fonts", "lohit-assamese-fonts", "lohit-bengali-fonts", + "lohit-devanagari-fonts", "lohit-gujarati-fonts", + "lohit-gurmukhi-fonts", "lohit-kannada-fonts", "lohit-odia-fonts", + "lohit-tamil-fonts", "lohit-telugu-fonts", "lsof", "madan-fonts", + "memtest86+" /*"metacity",*/, "mtr", "mt-st", "net-tools", "nfs-utils", + "nmap-ncat", "nm-connection-editor", "nss-tools", + "openssh-clients", "openssh-server", "oscap-anaconda-addon", + "ostree", "pciutils", "perl-interpreter", "pigz", "plymouth", + "prefixdevname", "python3-pyatspi", "rdma-core", + "redhat-release-eula", "rng-tools", "rpcbind", "rpm-ostree", + "rsync", "rsyslog", "selinux-policy-targeted", "sg3_utils", + "shim-ia32", "shim-x64", "sil-abyssinica-fonts", + "sil-padauk-fonts", "sil-scheherazade-fonts", "smartmontools", + "smc-meera-fonts", "spice-vdagent", "strace", "syslinux", + "systemd" /*"system-storage-manager",*/, "tar", + "thai-scalable-waree-fonts", "tigervnc-server-minimal", + "tigervnc-server-module", "udisks2", "udisks2-iscsi", "usbutils", + "vim-minimal", "volume_key", "wget", "xfsdump", "xfsprogs", + "xorg-x11-drivers", "xorg-x11-fonts-misc", "xorg-x11-server-utils", + "xorg-x11-server-Xorg", "xorg-x11-xauth", "xz", + }, + } +} + +func edgeInstallerPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "aajohan-comfortaa-fonts", "abattis-cantarell-fonts", + "alsa-firmware", "alsa-tools-firmware", "anaconda", + "anaconda-dracut", "anaconda-install-env-deps", "anaconda-widgets", + "audit", "bind-utils", "biosdevname", "bitmap-fangsongti-fonts", + "bzip2", "cryptsetup", "curl", "dbus-x11", "dejavu-sans-fonts", + "dejavu-sans-mono-fonts", "device-mapper-persistent-data", + "dmidecode", "dnf", "dracut-config-generic", "dracut-network", + /*"dump",*/ "efibootmgr", "ethtool", "ftp", "gdb-gdbserver", "gdisk", + /*"gfs2-utils",*/ "glibc-all-langpacks", "gnome-kiosk", + "google-noto-sans-cjk-ttc-fonts", "grub2-efi-ia32-cdboot", + "grub2-efi-x64-cdboot", "grub2-tools", "grub2-tools-efi", + "grub2-tools-extra", "grub2-tools-minimal", "grubby", + "gsettings-desktop-schemas", "hdparm", "hexedit", "hostname", + "initscripts", "ipmitool", "iwl1000-firmware", "iwl100-firmware", + "iwl105-firmware", "iwl135-firmware", "iwl2000-firmware", + "iwl2030-firmware", "iwl3160-firmware", /*"iwl3945-firmware",*/ + /*"iwl4965-firmware",*/ "iwl5000-firmware", "iwl5150-firmware", + "iwl6000g2a-firmware", "iwl6000g2b-firmware", + "iwl6050-firmware", "iwl7260-firmware", "jomolhari-fonts", + "kacst-farsi-fonts", "kacst-qurn-fonts", "kbd", "kbd-misc", + "kdump-anaconda-addon", "kernel", "khmeros-base-fonts", "less", + "libblockdev-lvm-dbus", /*"libertas-sd8686-firmware",*/ + /*"libertas-sd8787-firmware",*/ /*"libertas-usb8388-firmware",*/ + /*"libertas-usb8388-olpc-firmware",*/ "libibverbs", + "libreport-plugin-bugzilla", "libreport-plugin-reportuploader", + /*"libreport-rhel-anaconda-bugzilla",*/ "librsvg2", "linux-firmware", + "lklug-fonts", "lohit-assamese-fonts", "lohit-bengali-fonts", + "lohit-devanagari-fonts", "lohit-gujarati-fonts", + "lohit-gurmukhi-fonts", "lohit-kannada-fonts", "lohit-odia-fonts", + "lohit-tamil-fonts", "lohit-telugu-fonts", "lsof", "madan-fonts", + "memtest86+" /*"metacity",*/, "mtr", "mt-st", "net-tools", "nfs-utils", + "nmap-ncat", "nm-connection-editor", "nss-tools", + "openssh-clients", "openssh-server", "oscap-anaconda-addon", + "ostree", "pciutils", "perl-interpreter", "pigz", "plymouth", + "prefixdevname", "python3-pyatspi", "rdma-core", + "redhat-release-eula", "rng-tools", "rpcbind", "rpm-ostree", + "rsync", "rsyslog", "selinux-policy-targeted", "sg3_utils", + "shim-ia32", "shim-x64", "sil-abyssinica-fonts", + "sil-padauk-fonts", "sil-scheherazade-fonts", "smartmontools", + "smc-meera-fonts", "spice-vdagent", "strace", "syslinux", + "systemd" /*"system-storage-manager",*/, "tar", + "thai-scalable-waree-fonts", "tigervnc-server-minimal", + "tigervnc-server-module", "udisks2", "udisks2-iscsi", "usbutils", + "vim-minimal", "volume_key", "wget", "xfsdump", "xfsprogs", + "xorg-x11-drivers", "xorg-x11-fonts-misc", "xorg-x11-server-utils", + "xorg-x11-server-Xorg", "xorg-x11-xauth", "xz", + }, + Exclude: nil, + } +} + +func bareMetalPackageSet() rpmmd.PackageSet { + return rpmmd.PackageSet{ + Include: []string{ + "authselect-compat", "chrony", "cockpit-system", "cockpit-ws", + "@core", "dhcp-client", "dnf", "dnf-utils", "dosfstools", + "insights-client", "iwl1000-firmware", "iwl100-firmware", + "iwl105-firmware", "iwl135-firmware", "iwl2000-firmware", + "iwl2030-firmware", "iwl3160-firmware", "iwl5000-firmware", + "iwl5150-firmware", "iwl6000g2a-firmware", "iwl6000g2b-firmware", + "iwl6050-firmware", "iwl7260-firmware", "lvm2", "net-tools", + "NetworkManager", "nfs-utils", "oddjob", "oddjob-mkhomedir", + "policycoreutils", "psmisc", "python3-jsonschema", + "qemu-guest-agent", "redhat-release", "redhat-release-eula", + "rsync", "selinux-policy-targeted", "subscription-manager-cockpit", + "tar", "tcpdump", "yum", + }, + Exclude: nil, + } +} diff --git a/internal/distro/rhel90/partition_tables.go b/internal/distro/rhel90/partition_tables.go new file mode 100644 index 000000000..461a33846 --- /dev/null +++ b/internal/distro/rhel90/partition_tables.go @@ -0,0 +1,183 @@ +package rhel90 + +import ( + "github.com/osbuild/osbuild-composer/internal/disk" + "github.com/osbuild/osbuild-composer/internal/distro" +) + +var defaultBasePartitionTables = distro.BasePartitionTableMap{ + distro.X86_64ArchName: disk.PartitionTable{ + UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0", + Type: "gpt", + Partitions: []disk.Partition{ + { + Size: 2048, + Bootable: true, + Type: disk.BIOSBootPartitionGUID, + UUID: disk.BIOSBootPartitionUUID, + }, + { + Size: 204800, + Type: disk.EFISystemPartitionGUID, + UUID: disk.EFISystemPartitionUUID, + Filesystem: &disk.Filesystem{ + Type: "vfat", + UUID: disk.EFIFilesystemUUID, + Mountpoint: "/boot/efi", + FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt", + FSTabFreq: 0, + FSTabPassNo: 2, + }, + }, + { + Type: disk.FilesystemDataGUID, + UUID: disk.RootPartitionUUID, + Filesystem: &disk.Filesystem{ + Type: "xfs", + Label: "root", + Mountpoint: "/", + FSTabOptions: "defaults", + FSTabFreq: 0, + FSTabPassNo: 0, + }, + }, + }, + }, + distro.Aarch64ArchName: disk.PartitionTable{ + UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0", + Type: "gpt", + Partitions: []disk.Partition{ + { + Size: 204800, + Type: disk.EFISystemPartitionGUID, + UUID: disk.EFISystemPartitionUUID, + Filesystem: &disk.Filesystem{ + Type: "vfat", + UUID: disk.EFIFilesystemUUID, + Mountpoint: "/boot/efi", + FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt", + FSTabFreq: 0, + FSTabPassNo: 2, + }, + }, + { + Type: disk.FilesystemDataGUID, + UUID: disk.RootPartitionUUID, + Filesystem: &disk.Filesystem{ + Type: "xfs", + Label: "root", + Mountpoint: "/", + FSTabOptions: "defaults", + FSTabFreq: 0, + FSTabPassNo: 0, + }, + }, + }, + }, + distro.Ppc64leArchName: disk.PartitionTable{ + UUID: "0x14fc63d2", + Type: "dos", + Partitions: []disk.Partition{ + { + Size: 8192, + Type: "41", + Bootable: true, + }, + { + Filesystem: &disk.Filesystem{ + Type: "xfs", + Mountpoint: "/", + FSTabOptions: "defaults", + FSTabFreq: 0, + FSTabPassNo: 0, + }, + }, + }, + }, + distro.S390xArchName: disk.PartitionTable{ + UUID: "0x14fc63d2", + Type: "dos", + Partitions: []disk.Partition{ + { + Bootable: true, + Filesystem: &disk.Filesystem{ + Type: "xfs", + Mountpoint: "/", + FSTabOptions: "defaults", + FSTabFreq: 0, + FSTabPassNo: 0, + }, + }, + }, + }, +} + +var ec2BasePartitionTables = distro.BasePartitionTableMap{ + distro.X86_64ArchName: disk.PartitionTable{ + UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0", + Type: "gpt", + Partitions: []disk.Partition{ + { + Size: 2048, + Bootable: true, + Type: disk.BIOSBootPartitionGUID, + UUID: disk.BIOSBootPartitionUUID, + }, + { + Type: disk.FilesystemDataGUID, + UUID: disk.RootPartitionUUID, + Filesystem: &disk.Filesystem{ + Type: "xfs", + Label: "root", + Mountpoint: "/", + FSTabOptions: "defaults", + FSTabFreq: 0, + FSTabPassNo: 0, + }, + }, + }, + }, + distro.Aarch64ArchName: disk.PartitionTable{ + UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0", + Type: "gpt", + Partitions: []disk.Partition{ + { + Size: 409600, + Type: disk.EFISystemPartitionGUID, + UUID: disk.EFISystemPartitionUUID, + Filesystem: &disk.Filesystem{ + Type: "vfat", + UUID: disk.EFIFilesystemUUID, + Mountpoint: "/boot/efi", + FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt", + FSTabFreq: 0, + FSTabPassNo: 2, + }, + }, + { + Size: 1048576, + Type: disk.FilesystemDataGUID, + UUID: disk.FilesystemDataUUID, + Filesystem: &disk.Filesystem{ + Type: "xfs", + Mountpoint: "/boot", + FSTabOptions: "defaults", + FSTabFreq: 0, + FSTabPassNo: 0, + }, + }, + { + Type: disk.FilesystemDataGUID, + UUID: disk.RootPartitionUUID, + Filesystem: &disk.Filesystem{ + Type: "xfs", + Label: "root", + Mountpoint: "/", + FSTabOptions: "defaults", + FSTabFreq: 0, + FSTabPassNo: 0, + }, + }, + }, + }, +} diff --git a/internal/distro/rhel90/pipelines.go b/internal/distro/rhel90/pipelines.go new file mode 100644 index 000000000..ef775faa3 --- /dev/null +++ b/internal/distro/rhel90/pipelines.go @@ -0,0 +1,1177 @@ +package rhel90 + +import ( + "fmt" + "math/rand" + "strings" + + "github.com/osbuild/osbuild-composer/internal/blueprint" + "github.com/osbuild/osbuild-composer/internal/common" + "github.com/osbuild/osbuild-composer/internal/disk" + "github.com/osbuild/osbuild-composer/internal/distro" + "github.com/osbuild/osbuild-composer/internal/osbuild2" + osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" + "github.com/osbuild/osbuild-composer/internal/rpmmd" +) + +func qcow2Pipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + + treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) + if err != nil { + return nil, err + } + + partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng) + if err != nil { + return nil, err + } + + treePipeline = prependKernelCmdlineStage(treePipeline, t, &partitionTable) + + if options.Subscription == nil { + // RHSM DNF plugins should be by default disabled on RHEL Guest KVM images + treePipeline.AddStage(osbuild.NewRHSMStage(&osbuild.RHSMStageOptions{ + DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{ + ProductID: &osbuild.RHSMStageOptionsDnfPlugin{ + Enabled: false, + }, + SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{ + Enabled: false, + }, + }, + })) + } + treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) + kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) + treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) + treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + pipelines = append(pipelines, *treePipeline) + + diskfile := "disk.img" + imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, &partitionTable, t.arch, kernelVer) + pipelines = append(pipelines, *imagePipeline) + + qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, "qcow2", "1.1") + pipelines = append(pipelines, *qemuPipeline) + + return pipelines, nil +} + +func prependKernelCmdlineStage(pipeline *osbuild.Pipeline, t *imageType, pt *disk.PartitionTable) *osbuild.Pipeline { + rootFsUUID := pt.RootPartition().Filesystem.UUID + kernelStage := osbuild.NewKernelCmdlineStage(kernelCmdlineStageOptions(rootFsUUID, t.kernelOptions)) + pipeline.Stages = append([]*osbuild.Stage{kernelStage}, pipeline.Stages...) + return pipeline +} + +func vhdPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) + if err != nil { + return nil, err + } + + partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng) + if err != nil { + return nil, err + } + + treePipeline = prependKernelCmdlineStage(treePipeline, t, &partitionTable) + treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) + kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) + treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) + treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + pipelines = append(pipelines, *treePipeline) + + diskfile := "disk.img" + imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, &partitionTable, t.arch, kernelVer) + pipelines = append(pipelines, *imagePipeline) + if err != nil { + return nil, err + } + + qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, "vpc", "") + pipelines = append(pipelines, *qemuPipeline) + return pipelines, nil +} + +func vmdkPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) + if err != nil { + return nil, err + } + + partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng) + if err != nil { + return nil, err + } + + treePipeline = prependKernelCmdlineStage(treePipeline, t, &partitionTable) + treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) + kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) + treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) + treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + pipelines = append(pipelines, *treePipeline) + + diskfile := "disk.img" + imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, &partitionTable, t.arch, kernelVer) + pipelines = append(pipelines, *imagePipeline) + if err != nil { + return nil, err + } + + qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, "vmdk", "") + pipelines = append(pipelines, *qemuPipeline) + return pipelines, nil +} + +func openstackPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) + if err != nil { + return nil, err + } + + partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng) + if err != nil { + return nil, err + } + treePipeline = prependKernelCmdlineStage(treePipeline, t, &partitionTable) + treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) + kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) + treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) + treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + pipelines = append(pipelines, *treePipeline) + + diskfile := "disk.img" + imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, &partitionTable, t.arch, kernelVer) + pipelines = append(pipelines, *imagePipeline) + if err != nil { + return nil, err + } + + qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, "qcow2", "") + pipelines = append(pipelines, *qemuPipeline) + return pipelines, nil +} + +// ec2BaseTreePipeline returns the base OS pipeline common for all EC2 image types. +// +// The expectation is that specific EC2 image types can extend the returned pipeline +// by appending additional stages. +// +// The argument `withRHUI` should be set to `true` only if the image package set includes RHUI client packages. +// +// Note: the caller of this function has to append the `osbuild.NewSELinuxStage(selinuxStageOptions(false))` stage +// as the last one to the returned pipeline. The stage is not appended on purpose, to allow caller to append +// any additional stages to the pipeline, but before the SELinuxStage, which must be always the last one. +func ec2BaseTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, + c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, + defaultTarget string, withRHUI bool, pt *disk.PartitionTable) (*osbuild.Pipeline, error) { + p := new(osbuild.Pipeline) + p.Name = "os" + p.Build = "name:build" + packages = append(packages, bpPackages...) + p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) + + // If the /boot is on a separate partition, the prefix for the BLS stage must be "" + if pt.BootPartition() == nil { + p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{})) + } else { + p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{Prefix: common.StringToPtr("")})) + } + + language, keyboard := c.GetPrimaryLocale() + if language != nil { + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) + } else { + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) + } + if keyboard != nil { + p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard})) + } else { + p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{ + Keymap: "us", + X11Keymap: &osbuild.X11KeymapOptions{ + Layouts: []string{"us"}, + }, + })) + } + + if hostname := c.GetHostname(); hostname != nil { + p.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: *hostname})) + } + + timezone, ntpServers := c.GetTimezoneSettings() + if timezone != nil { + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone})) + } else { + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: "UTC"})) + } + + if len(ntpServers) > 0 { + p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) + } else { + p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{ + Servers: []osbuild.ChronyConfigServer{ + { + Hostname: "169.254.169.123", + Prefer: common.BoolToPtr(true), + Iburst: common.BoolToPtr(true), + Minpoll: common.IntToPtr(4), + Maxpoll: common.IntToPtr(4), + }, + }, + // empty string will remove any occurrences of the option from the configuration + LeapsecTz: common.StringToPtr(""), + })) + } + + if groups := c.GetGroups(); len(groups) > 0 { + p.AddStage(osbuild.NewGroupsStage(groupStageOptions(groups))) + } + + if users := c.GetUsers(); len(users) > 0 { + userOptions, err := userStageOptions(users) + if err != nil { + return nil, err + } + p.AddStage(osbuild.NewUsersStage(userOptions)) + } + + if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { + p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) + } + + if firewall := c.GetFirewall(); firewall != nil { + p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) + } + + p.AddStage(osbuild.NewSystemdLogindStage(&osbuild.SystemdLogindStageOptions{ + Filename: "00-getty-fixes.conf", + Config: osbuild.SystemdLogindConfigDropin{ + + Login: osbuild.SystemdLogindConfigLoginSection{ + NAutoVTs: common.IntToPtr(0), + }, + }, + })) + + p.AddStage(osbuild.NewSysconfigStage(&osbuild.SysconfigStageOptions{ + Kernel: osbuild.SysconfigKernelOptions{ + UpdateDefault: true, + DefaultKernel: "kernel", + }, + Network: osbuild.SysconfigNetworkOptions{ + Networking: true, + NoZeroConf: true, + }, + NetworkScripts: &osbuild.NetworkScriptsOptions{ + IfcfgFiles: map[string]osbuild.IfcfgFile{ + "eth0": { + Device: "eth0", + Bootproto: osbuild.IfcfgBootprotoDHCP, + OnBoot: common.BoolToPtr(true), + Type: osbuild.IfcfgTypeEthernet, + UserCtl: common.BoolToPtr(true), + PeerDNS: common.BoolToPtr(true), + IPv6Init: common.BoolToPtr(false), + }, + }, + }, + })) + + p.AddStage(osbuild.NewCloudInitStage(&osbuild.CloudInitStageOptions{ + Filename: "00-rhel-default-user.cfg", + Config: osbuild.CloudInitConfigFile{ + SystemInfo: &osbuild.CloudInitConfigSystemInfo{ + DefaultUser: &osbuild.CloudInitConfigDefaultUser{ + Name: "ec2-user", + }, + }, + }, + })) + + p.AddStage(osbuild.NewModprobeStage(&osbuild.ModprobeStageOptions{ + Filename: "blacklist-nouveau.conf", + Commands: osbuild.ModprobeConfigCmdList{ + osbuild.NewModprobeConfigCmdBlacklist("nouveau"), + }, + })) + + p.AddStage(osbuild.NewDracutConfStage(&osbuild.DracutConfStageOptions{ + Filename: "sgdisk.conf", + Config: osbuild.DracutConfigFile{ + Install: []string{"sgdisk"}, + }, + })) + + // RHBZ#1822853 + p.AddStage(osbuild.NewSystemdUnitStage(&osbuild.SystemdUnitStageOptions{ + Unit: "nm-cloud-setup.service", + Dropin: "10-rh-enable-for-ec2.conf", + Config: osbuild.SystemdServiceUnitDropin{ + Service: &osbuild.SystemdUnitServiceSection{ + Environment: "NM_CLOUD_SETUP_EC2=yes", + }, + }, + })) + + p.AddStage(osbuild.NewAuthselectStage(&osbuild.AuthselectStageOptions{ + Profile: "sssd", + })) + + if options.Subscription != nil { + commands := []string{ + fmt.Sprintf("/usr/sbin/subscription-manager register --org=%s --activationkey=%s --serverurl %s --baseurl %s", options.Subscription.Organization, options.Subscription.ActivationKey, options.Subscription.ServerUrl, options.Subscription.BaseUrl), + } + if options.Subscription.Insights { + commands = append(commands, "/usr/bin/insights-client --register") + } + + p.AddStage(osbuild.NewFirstBootStage(&osbuild.FirstBootStageOptions{ + Commands: commands, + WaitForNetwork: true, + })) + } else { + // The EC2 images should keep the RHSM DNF plugins enabled (RHBZ#1996670) + rhsmStageOptions := &osbuild.RHSMStageOptions{ + // RHBZ#1932802 + SubMan: &osbuild.RHSMStageOptionsSubMan{ + Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{ + AutoRegistration: common.BoolToPtr(true), + }, + }, + } + + // Disable RHSM redhat.repo management only if the image uses RHUI + // for content. Otherwise subscribing the system manually after booting + // it would result in empty redhat.repo. Without RHUI, such system + // would have no way to get Red Hat content, but enable the repo + // management manually, which would be very confusing. + // RHBZ#1932802 + if withRHUI { + rhsmStageOptions.SubMan.Rhsm = &osbuild.SubManConfigRHSMSection{ + ManageRepos: common.BoolToPtr(false), + } + } + + p.AddStage(osbuild.NewRHSMStage(rhsmStageOptions)) + } + + return p, nil +} + +func ec2X86_64BaseTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, + c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, + defaultTarget string, withRHUI bool, pt *disk.PartitionTable) (*osbuild.Pipeline, error) { + + treePipeline, err := ec2BaseTreePipeline(repos, packages, bpPackages, c, options, enabledServices, disabledServices, defaultTarget, withRHUI, pt) + if err != nil { + return nil, err + } + + // EC2 x86_64-specific stages + // Add 'nvme' driver to handle the case when initramfs is getting forcefully + // rebuild on a Xen instance (and not able to boot on Nitro after that). + treePipeline.AddStage(osbuild.NewDracutConfStage(&osbuild.DracutConfStageOptions{ + Filename: "ec2.conf", + Config: osbuild.DracutConfigFile{ + AddDrivers: []string{ + "nvme", + "xen-blkfront", + }, + }, + })) + + return treePipeline, nil +} + +func ec2CommonPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, + repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, + rng *rand.Rand, withRHUI bool, diskfile string) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + + partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng) + if err != nil { + return nil, err + } + + var treePipeline *osbuild.Pipeline + switch arch := t.arch.Name(); arch { + // rhel-ec2-x86_64, rhel-ha-ec2 + case distro.X86_64ArchName: + treePipeline, err = ec2X86_64BaseTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, withRHUI, &partitionTable) + // rhel-ec2-aarch64 + case distro.Aarch64ArchName: + treePipeline, err = ec2BaseTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, withRHUI, &partitionTable) + default: + return nil, fmt.Errorf("ec2CommonPipelines: unsupported image architecture: %q", arch) + } + if err != nil { + return nil, err + } + + treePipeline = prependKernelCmdlineStage(treePipeline, t, &partitionTable) + treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) + kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) + treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) + // The last stage must be the SELinux stage + treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + pipelines = append(pipelines, *treePipeline) + + imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, &partitionTable, t.arch, kernelVer) + pipelines = append(pipelines, *imagePipeline) + return pipelines, nil +} + +func ec2SapPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, + repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, + rng *rand.Rand, withRHUI bool, diskfile string) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + + partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng) + if err != nil { + return nil, err + } + + var treePipeline *osbuild.Pipeline + switch arch := t.arch.Name(); arch { + // rhel-sap-ec2 + case distro.X86_64ArchName: + treePipeline, err = ec2X86_64BaseTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, withRHUI, &partitionTable) + default: + return nil, fmt.Errorf("ec2SapPipelines: unsupported image architecture: %q", arch) + } + if err != nil { + return nil, err + } + + // SAP-specific configuration + treePipeline.AddStage(osbuild.NewSELinuxConfigStage(&osbuild.SELinuxConfigStageOptions{ + State: osbuild.SELinuxStatePermissive, + })) + + // RHBZ#1960617 + treePipeline.AddStage(osbuild.NewTunedStage(osbuild.NewTunedStageOptions("sap-hana"))) + + // RHBZ#1959979 + treePipeline.AddStage(osbuild.NewTmpfilesdStage(osbuild.NewTmpfilesdStageOptions("sap.conf", + []osbuild.TmpfilesdConfigLine{ + { + Type: "x", + Path: "/tmp/.sap*", + }, + { + Type: "x", + Path: "/tmp/.hdb*lock", + }, + { + Type: "x", + Path: "/tmp/.trex*lock", + }, + }, + ))) + + // RHBZ#1959963 + treePipeline.AddStage(osbuild.NewPamLimitsConfStage(osbuild.NewPamLimitsConfStageOptions("99-sap.conf", + []osbuild.PamLimitsConfigLine{ + { + Domain: "@sapsys", + Type: osbuild.PamLimitsTypeHard, + Item: osbuild.PamLimitsItemNofile, + Value: osbuild.PamLimitsValueInt(65536), + }, + { + Domain: "@sapsys", + Type: osbuild.PamLimitsTypeSoft, + Item: osbuild.PamLimitsItemNofile, + Value: osbuild.PamLimitsValueInt(65536), + }, + { + Domain: "@dba", + Type: osbuild.PamLimitsTypeHard, + Item: osbuild.PamLimitsItemNofile, + Value: osbuild.PamLimitsValueInt(65536), + }, + { + Domain: "@dba", + Type: osbuild.PamLimitsTypeSoft, + Item: osbuild.PamLimitsItemNofile, + Value: osbuild.PamLimitsValueInt(65536), + }, + { + Domain: "@sapsys", + Type: osbuild.PamLimitsTypeHard, + Item: osbuild.PamLimitsItemNproc, + Value: osbuild.PamLimitsValueUnlimited, + }, + { + Domain: "@sapsys", + Type: osbuild.PamLimitsTypeSoft, + Item: osbuild.PamLimitsItemNproc, + Value: osbuild.PamLimitsValueUnlimited, + }, + { + Domain: "@dba", + Type: osbuild.PamLimitsTypeHard, + Item: osbuild.PamLimitsItemNproc, + Value: osbuild.PamLimitsValueUnlimited, + }, + { + Domain: "@dba", + Type: osbuild.PamLimitsTypeSoft, + Item: osbuild.PamLimitsItemNproc, + Value: osbuild.PamLimitsValueUnlimited, + }, + }, + ))) + + // RHBZ#1959962 + treePipeline.AddStage(osbuild.NewSysctldStage(osbuild.NewSysctldStageOptions("sap.conf", + []osbuild.SysctldConfigLine{ + { + Key: "kernel.pid_max", + Value: "4194304", + }, + { + Key: "vm.max_map_count", + Value: "2147483647", + }, + }, + ))) + + // E4S/EUS + treePipeline.AddStage(osbuild.NewDNFConfigStage(osbuild.NewDNFConfigStageOptions( + []osbuild.DNFVariable{ + { + Name: "releasever", + Value: "9.0", + }, + }, + ))) + + treePipeline = prependKernelCmdlineStage(treePipeline, t, &partitionTable) + treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) + kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) + treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) + // The last stage must be the SELinux stage + treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + pipelines = append(pipelines, *treePipeline) + + imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, &partitionTable, t.arch, kernelVer) + pipelines = append(pipelines, *imagePipeline) + return pipelines, nil +} + +// ec2Pipelines returns pipelines which produce uncompressed EC2 images which are expected to use RHSM for content +func ec2Pipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + return ec2CommonPipelines(t, customizations, options, repos, packageSetSpecs, rng, false, t.Filename()) +} + +// rhelEc2Pipelines returns pipelines which produce XZ-compressed EC2 images which are expected to use RHUI for content +func rhelEc2Pipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + rawImageFilename := "image.raw" + + pipelines, err := ec2CommonPipelines(t, customizations, options, repos, packageSetSpecs, rng, true, rawImageFilename) + if err != nil { + return nil, err + } + + lastPipeline := pipelines[len(pipelines)-1] + pipelines = append(pipelines, *xzArchivePipeline(lastPipeline.Name, rawImageFilename, t.Filename())) + + return pipelines, nil +} + +// rhelEc2SapPipelines returns pipelines which produce XZ-compressed EC2 SAP images which are expected to use RHUI for content +func rhelEc2SapPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + rawImageFilename := "image.raw" + + pipelines, err := ec2SapPipelines(t, customizations, options, repos, packageSetSpecs, rng, true, rawImageFilename) + if err != nil { + return nil, err + } + + lastPipeline := pipelines[len(pipelines)-1] + pipelines = append(pipelines, *xzArchivePipeline(lastPipeline.Name, rawImageFilename, t.Filename())) + + return pipelines, nil +} + +func tarPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + + treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) + if err != nil { + return nil, err + } + treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + pipelines = append(pipelines, *treePipeline) + tarPipeline := osbuild.Pipeline{ + Name: "root-tar", + Build: "name:build", + } + tarPipeline.AddStage(tarStage("os", "root.tar.xz")) + pipelines = append(pipelines, tarPipeline) + return pipelines, nil +} + +func edgeInstallerPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + installerPackages := packageSetSpecs[installerPkgsKey] + kernelVer := kernelVerStr(installerPackages, "kernel", t.Arch().Name()) + ostreeRepoPath := "/ostree/repo" + pipelines = append(pipelines, *anacondaTreePipeline(repos, installerPackages, kernelVer, t.Arch().Name(), ostreePayloadStages(options, ostreeRepoPath))) + pipelines = append(pipelines, *bootISOTreePipeline(kernelVer, t.Arch().Name(), ostreeKickstartStageOptions(fmt.Sprintf("file://%s", ostreeRepoPath), options.OSTree.Ref))) + pipelines = append(pipelines, *bootISOPipeline(t.Filename(), t.Arch().Name())) + return pipelines, nil +} + +func tarInstallerPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + + treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) + if err != nil { + return nil, err + } + treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + pipelines = append(pipelines, *treePipeline) + + kernelPkg := new(rpmmd.PackageSpec) + installerPackages := packageSetSpecs[installerPkgsKey] + for _, pkg := range installerPackages { + if pkg.Name == "kernel" { + kernelPkg = &pkg + break + } + } + if kernelPkg == nil { + return nil, fmt.Errorf("kernel package not found in installer package set") + } + kernelVer := fmt.Sprintf("%s-%s.%s", kernelPkg.Version, kernelPkg.Release, kernelPkg.Arch) + + tarPath := "/liveimg.tar" + tarPayloadStages := []*osbuild.Stage{tarStage("os", tarPath)} + pipelines = append(pipelines, *anacondaTreePipeline(repos, installerPackages, kernelVer, t.Arch().Name(), tarPayloadStages)) + pipelines = append(pipelines, *bootISOTreePipeline(kernelVer, t.Arch().Name(), tarKickstartStageOptions(fmt.Sprintf("file://%s", tarPath)))) + pipelines = append(pipelines, *bootISOPipeline(t.Filename(), t.Arch().Name())) + return pipelines, nil +} + +func edgeCorePipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec) ([]osbuild.Pipeline, error) { + pipelines := make([]osbuild.Pipeline, 0) + pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) + + treePipeline, err := ostreeTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) + if err != nil { + return nil, err + } + + pipelines = append(pipelines, *treePipeline) + pipelines = append(pipelines, *ostreeCommitPipeline(options)) + + return pipelines, nil +} + +func edgeCommitPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines, err := edgeCorePipelines(t, customizations, options, repos, packageSetSpecs) + if err != nil { + return nil, err + } + tarPipeline := osbuild.Pipeline{ + Name: "commit-archive", + Build: "name:build", + } + tarPipeline.AddStage(tarStage("ostree-commit", t.Filename())) + pipelines = append(pipelines, tarPipeline) + return pipelines, nil +} + +func edgeContainerPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) { + pipelines, err := edgeCorePipelines(t, customizations, options, repos, packageSetSpecs) + if err != nil { + return nil, err + } + pipelines = append(pipelines, *containerTreePipeline(repos, packageSetSpecs[containerPkgsKey], options, customizations)) + pipelines = append(pipelines, *containerPipeline(t)) + return pipelines, nil +} + +func buildPipeline(repos []rpmmd.RepoConfig, buildPackageSpecs []rpmmd.PackageSpec) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "build" + p.Runner = "org.osbuild.rhel90" + p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(buildPackageSpecs))) + p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(true))) + return p +} + +func osPipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, defaultTarget string) (*osbuild.Pipeline, error) { + p := new(osbuild.Pipeline) + p.Name = "os" + p.Build = "name:build" + packages = append(packages, bpPackages...) + p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) + p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{})) + language, keyboard := c.GetPrimaryLocale() + if language != nil { + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) + } else { + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) + } + if keyboard != nil { + p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard})) + } + if hostname := c.GetHostname(); hostname != nil { + p.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: *hostname})) + } + + timezone, ntpServers := c.GetTimezoneSettings() + if timezone != nil { + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone})) + } else { + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: "America/New_York"})) + } + + if len(ntpServers) > 0 { + p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) + } + + if groups := c.GetGroups(); len(groups) > 0 { + p.AddStage(osbuild.NewGroupsStage(groupStageOptions(groups))) + } + + if users := c.GetUsers(); len(users) > 0 { + userOptions, err := userStageOptions(users) + if err != nil { + return nil, err + } + p.AddStage(osbuild.NewUsersStage(userOptions)) + } + + if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { + p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) + } + + if firewall := c.GetFirewall(); firewall != nil { + p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) + } + + // These are the current defaults for the sysconfig stage. This can be changed to be image type exclusive if different configs are needed. + p.AddStage(osbuild.NewSysconfigStage(&osbuild.SysconfigStageOptions{ + Kernel: osbuild.SysconfigKernelOptions{ + UpdateDefault: true, + DefaultKernel: "kernel", + }, + Network: osbuild.SysconfigNetworkOptions{ + Networking: true, + NoZeroConf: true, + }, + })) + + if options.Subscription != nil { + commands := []string{ + fmt.Sprintf("/usr/sbin/subscription-manager register --org=%s --activationkey=%s --serverurl %s --baseurl %s", options.Subscription.Organization, options.Subscription.ActivationKey, options.Subscription.ServerUrl, options.Subscription.BaseUrl), + } + if options.Subscription.Insights { + commands = append(commands, "/usr/bin/insights-client --register") + } + + p.AddStage(osbuild.NewFirstBootStage(&osbuild.FirstBootStageOptions{ + Commands: commands, + WaitForNetwork: true, + }, + )) + } + return p, nil +} + +func ostreeTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, defaultTarget string) (*osbuild.Pipeline, error) { + p := new(osbuild.Pipeline) + p.Name = "ostree-tree" + p.Build = "name:build" + + packages = append(packages, bpPackages...) + p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) + p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{})) + language, keyboard := c.GetPrimaryLocale() + if language != nil { + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) + } else { + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) + } + if keyboard != nil { + p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard})) + } + if hostname := c.GetHostname(); hostname != nil { + p.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: *hostname})) + } + + timezone, ntpServers := c.GetTimezoneSettings() + if timezone != nil { + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone})) + } else { + p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: "America/New_York"})) + } + + if len(ntpServers) > 0 { + p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) + } + + if groups := c.GetGroups(); len(groups) > 0 { + p.AddStage(osbuild.NewGroupsStage(groupStageOptions(groups))) + } + + if users := c.GetUsers(); len(users) > 0 { + userOptions, err := userStageOptions(users) + if err != nil { + return nil, err + } + p.AddStage(osbuild.NewUsersStage(userOptions)) + p.AddStage(osbuild.NewFirstBootStage(usersFirstBootOptions(userOptions))) + } + + if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { + p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) + } + + if firewall := c.GetFirewall(); firewall != nil { + p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) + } + + // These are the current defaults for the sysconfig stage. This can be changed to be image type exclusive if different configs are needed. + p.AddStage(osbuild.NewSysconfigStage(&osbuild.SysconfigStageOptions{ + Kernel: osbuild.SysconfigKernelOptions{ + UpdateDefault: true, + DefaultKernel: "kernel", + }, + Network: osbuild.SysconfigNetworkOptions{ + Networking: true, + NoZeroConf: true, + }, + })) + + if options.Subscription != nil { + commands := []string{ + fmt.Sprintf("/usr/sbin/subscription-manager register --org=%s --activationkey=%s --serverurl %s --baseurl %s", options.Subscription.Organization, options.Subscription.ActivationKey, options.Subscription.ServerUrl, options.Subscription.BaseUrl), + } + if options.Subscription.Insights { + commands = append(commands, "/usr/bin/insights-client --register") + } + + p.AddStage(osbuild.NewFirstBootStage(&osbuild.FirstBootStageOptions{ + Commands: commands, + WaitForNetwork: true, + }, + )) + } + + p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) + p.AddStage(osbuild.NewOSTreePrepTreeStage(&osbuild.OSTreePrepTreeStageOptions{ + EtcGroupMembers: []string{ + // NOTE: We may want to make this configurable. + "wheel", "docker", + }, + })) + return p, nil +} +func ostreeCommitPipeline(options distro.ImageOptions) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "ostree-commit" + p.Build = "name:build" + p.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: "/repo"})) + + commitStageInput := new(osbuild.OSTreeCommitStageInput) + commitStageInput.Type = "org.osbuild.tree" + commitStageInput.Origin = "org.osbuild.pipeline" + commitStageInput.References = osbuild.OSTreeCommitStageReferences{"name:ostree-tree"} + + p.AddStage(osbuild.NewOSTreeCommitStage( + &osbuild.OSTreeCommitStageOptions{ + Ref: options.OSTree.Ref, + OSVersion: osVersion, + Parent: options.OSTree.Parent, + }, + &osbuild.OSTreeCommitStageInputs{Tree: commitStageInput}), + ) + return p +} + +func tarStage(source, filename string) *osbuild.Stage { + tree := new(osbuild.TarStageInput) + tree.Type = "org.osbuild.tree" + tree.Origin = "org.osbuild.pipeline" + tree.References = []string{"name:" + source} + return osbuild.NewTarStage(&osbuild.TarStageOptions{Filename: filename}, &osbuild.TarStageInputs{Tree: tree}) +} + +func containerTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, options distro.ImageOptions, c *blueprint.Customizations) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "container-tree" + p.Build = "name:build" + p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) + language, _ := c.GetPrimaryLocale() + if language != nil { + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) + } else { + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US"})) + } + p.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: "/var/www/html/repo"})) + + p.AddStage(osbuild.NewOSTreePullStage( + &osbuild.OSTreePullStageOptions{Repo: "/var/www/html/repo"}, + ostreePullStageInputs("org.osbuild.pipeline", "name:ostree-commit", options.OSTree.Ref), + )) + return p +} + +func containerPipeline(t *imageType) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "container" + p.Build = "name:build" + options := &osbuild.OCIArchiveStageOptions{ + Architecture: t.arch.Name(), + Filename: t.Filename(), + Config: &osbuild.OCIArchiveConfig{ + Cmd: []string{"httpd", "-D", "FOREGROUND"}, + ExposedPorts: []string{"80"}, + }, + } + baseInput := new(osbuild.OCIArchiveStageInput) + baseInput.Type = "org.osbuild.tree" + baseInput.Origin = "org.osbuild.pipeline" + baseInput.References = []string{"name:container-tree"} + inputs := &osbuild.OCIArchiveStageInputs{Base: baseInput} + p.AddStage(osbuild.NewOCIArchiveStage(options, inputs)) + return p +} + +func ostreePayloadStages(options distro.ImageOptions, ostreeRepoPath string) []*osbuild.Stage { + stages := make([]*osbuild.Stage, 0) + + // ostree commit payload + stages = append(stages, osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: ostreeRepoPath})) + stages = append(stages, osbuild.NewOSTreePullStage( + &osbuild.OSTreePullStageOptions{Repo: ostreeRepoPath}, + ostreePullStageInputs("org.osbuild.source", options.OSTree.Parent, options.OSTree.Ref), + )) + + return stages +} + +func anacondaTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, kernelVer string, arch string, payloadStages []*osbuild.Stage) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "anaconda-tree" + p.Build = "name:build" + p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) + for _, stage := range payloadStages { + p.AddStage(stage) + } + p.AddStage(osbuild.NewBuildstampStage(buildStampStageOptions(arch))) + p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) + + rootPassword := "" + rootUser := osbuild.UsersStageOptionsUser{ + Password: &rootPassword, + } + + installUID := 0 + installGID := 0 + installHome := "/root" + installShell := "/usr/libexec/anaconda/run-anaconda" + installPassword := "" + installUser := osbuild.UsersStageOptionsUser{ + UID: &installUID, + GID: &installGID, + Home: &installHome, + Shell: &installShell, + Password: &installPassword, + } + usersStageOptions := &osbuild.UsersStageOptions{ + Users: map[string]osbuild.UsersStageOptionsUser{ + "root": rootUser, + "install": installUser, + }, + } + + p.AddStage(osbuild.NewUsersStage(usersStageOptions)) + p.AddStage(osbuild.NewAnacondaStage(anacondaStageOptions())) + p.AddStage(osbuild.NewLoraxScriptStage(loraxScriptStageOptions(arch))) + p.AddStage(osbuild.NewDracutStage(dracutStageOptions(kernelVer))) + + return p +} + +func bootISOTreePipeline(kernelVer string, arch string, ksOptions *osbuild.KickstartStageOptions) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "bootiso-tree" + p.Build = "name:build" + + p.AddStage(osbuild.NewBootISOMonoStage(bootISOMonoStageOptions(kernelVer, arch), bootISOMonoStageInputs())) + p.AddStage(osbuild.NewKickstartStage(ksOptions)) + p.AddStage(osbuild.NewDiscinfoStage(discinfoStageOptions(arch))) + + return p +} +func bootISOPipeline(filename string, arch string) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "bootiso" + p.Build = "name:build" + + p.AddStage(osbuild.NewXorrisofsStage(xorrisofsStageOptions(filename, arch), xorrisofsStageInputs())) + p.AddStage(osbuild.NewImplantisomd5Stage(&osbuild.Implantisomd5StageOptions{Filename: filename})) + + return p +} + +func liveImagePipeline(inputPipelineName string, outputFilename string, pt *disk.PartitionTable, arch *architecture, kernelVer string) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "image" + p.Build = "name:build" + + p.AddStage(osbuild.NewTruncateStage(&osbuild.TruncateStageOptions{Filename: outputFilename, Size: fmt.Sprintf("%d", pt.Size)})) + sfOptions := sfdiskStageOptions(pt) + loopback := osbuild.NewLoopbackDevice(&osbuild.LoopbackDeviceOptions{Filename: outputFilename}) + p.AddStage(osbuild.NewSfdiskStage(sfOptions, loopback)) + + for _, stage := range mkfsStages(pt, loopback) { + p.AddStage(stage) + } + + inputName := "root-tree" + copyOptions, copyDevices, copyMounts := copyFSTreeOptions(inputName, inputPipelineName, pt, loopback) + copyInputs := copyPipelineTreeInputs(inputName, inputPipelineName) + p.AddStage(osbuild.NewCopyStage(copyOptions, copyInputs, copyDevices, copyMounts)) + p.AddStage(bootloaderInstStage(outputFilename, pt, arch, kernelVer, copyDevices, copyMounts, loopback)) + return p +} + +func xzArchivePipeline(inputPipelineName, inputFilename, outputFilename string) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = "archive" + p.Build = "name:build" + + p.AddStage(osbuild.NewXzStage( + osbuild.NewXzStageOptions(outputFilename), + osbuild.NewFilesInputs(osbuild.NewFilesInputReferencesPipeline(inputPipelineName, inputFilename)), + )) + + return p +} + +// mkfsStages generates a list of org.osbuild.mkfs.* stages based on a +// partition table description for a single device node +func mkfsStages(pt *disk.PartitionTable, device *osbuild.Device) []*osbuild2.Stage { + stages := make([]*osbuild2.Stage, 0, len(pt.Partitions)) + + // assume loopback device for simplicity since it's the only one currently supported + // panic if the conversion fails + devOptions, ok := device.Options.(*osbuild.LoopbackDeviceOptions) + if !ok { + panic("mkfsStages: failed to convert device options to loopback options") + } + + for _, p := range pt.Partitions { + if p.Filesystem == nil { + // no filesystem for partition (e.g., BIOS boot) + continue + } + var stage *osbuild.Stage + stageDevice := osbuild.NewLoopbackDevice( + &osbuild.LoopbackDeviceOptions{ + Filename: devOptions.Filename, + Start: p.Start, + Size: p.Size, + }, + ) + switch p.Filesystem.Type { + case "xfs": + options := &osbuild.MkfsXfsStageOptions{ + UUID: p.Filesystem.UUID, + Label: p.Filesystem.Label, + } + stage = osbuild.NewMkfsXfsStage(options, stageDevice) + case "vfat": + options := &osbuild.MkfsFATStageOptions{ + VolID: strings.Replace(p.Filesystem.UUID, "-", "", -1), + } + stage = osbuild.NewMkfsFATStage(options, stageDevice) + case "btrfs": + options := &osbuild.MkfsBtrfsStageOptions{ + UUID: p.Filesystem.UUID, + Label: p.Filesystem.Label, + } + stage = osbuild.NewMkfsBtrfsStage(options, stageDevice) + case "ext4": + options := &osbuild.MkfsExt4StageOptions{ + UUID: p.Filesystem.UUID, + Label: p.Filesystem.Label, + } + stage = osbuild.NewMkfsExt4Stage(options, stageDevice) + default: + panic("unknown fs type " + p.Type) + } + stages = append(stages, stage) + } + return stages +} + +func qemuPipeline(inputPipelineName, inputFilename, outputFilename, format, qcow2Compat string) *osbuild.Pipeline { + p := new(osbuild.Pipeline) + p.Name = format + p.Build = "name:build" + + qemuStage := osbuild.NewQEMUStage(qemuStageOptions(outputFilename, format, qcow2Compat), qemuStageInputs(inputPipelineName, inputFilename)) + p.AddStage(qemuStage) + return p +} + +func bootloaderConfigStage(t *imageType, partitionTable disk.PartitionTable, kernel *blueprint.KernelCustomization, kernelVer string) *osbuild.Stage { + if t.arch.name == distro.S390xArchName { + return osbuild.NewZiplStage(new(osbuild.ZiplStageOptions)) + } + + kernelOptions := t.kernelOptions + uefi := t.supportsUEFI() + legacy := t.arch.legacy + return osbuild.NewGRUB2Stage(grub2StageOptions(partitionTable.RootPartition(), partitionTable.BootPartition(), kernelOptions, kernel, kernelVer, uefi, legacy)) +} + +func bootloaderInstStage(filename string, pt *disk.PartitionTable, arch *architecture, kernelVer string, devices *osbuild.Devices, mounts *osbuild.Mounts, disk *osbuild.Device) *osbuild.Stage { + platform := arch.legacy + if platform != "" { + return osbuild.NewGrub2InstStage(grub2InstStageOptions(filename, pt, platform)) + } + + if arch.name == distro.S390xArchName { + return osbuild.NewZiplInstStage(ziplInstStageOptions(kernelVer, pt), disk, devices, mounts) + } + + return nil +} + +func kernelVerStr(pkgs []rpmmd.PackageSpec, kernelName, arch string) string { + kernelPkg := new(rpmmd.PackageSpec) + for _, pkg := range pkgs { + if pkg.Name == kernelName { + kernelPkg = &pkg + break + } + } + if kernelPkg == nil { + panic(fmt.Sprintf("kernel package %q not found", kernelName)) + } + return fmt.Sprintf("%s-%s.%s", kernelPkg.Version, kernelPkg.Release, kernelPkg.Arch) +} diff --git a/internal/distro/rhel90/stage_inputs.go b/internal/distro/rhel90/stage_inputs.go new file mode 100644 index 000000000..b96b513c2 --- /dev/null +++ b/internal/distro/rhel90/stage_inputs.go @@ -0,0 +1,74 @@ +package rhel90 + +import ( + osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" + "github.com/osbuild/osbuild-composer/internal/rpmmd" +) + +func bootISOMonoStageInputs() *osbuild.BootISOMonoStageInputs { + rootfsInput := new(osbuild.BootISOMonoStageInput) + rootfsInput.Type = "org.osbuild.tree" + rootfsInput.Origin = "org.osbuild.pipeline" + rootfsInput.References = osbuild.BootISOMonoStageReferences{"name:anaconda-tree"} + return &osbuild.BootISOMonoStageInputs{ + RootFS: rootfsInput, + } +} + +func rpmStageInputs(specs []rpmmd.PackageSpec) *osbuild.RPMStageInputs { + stageInput := new(osbuild.RPMStageInput) + stageInput.Type = "org.osbuild.files" + stageInput.Origin = "org.osbuild.source" + stageInput.References = pkgRefs(specs) + return &osbuild.RPMStageInputs{Packages: stageInput} +} + +func pkgRefs(specs []rpmmd.PackageSpec) osbuild.RPMStageReferences { + refs := make([]string, len(specs)) + for idx, pkg := range specs { + refs[idx] = pkg.Checksum + } + return refs +} + +func ostreePullStageInputs(origin, source, commitRef string) *osbuild.OSTreePullStageInputs { + pullStageInput := new(osbuild.OSTreePullStageInput) + pullStageInput.Type = "org.osbuild.ostree" + pullStageInput.Origin = origin + + inputRefs := make(map[string]osbuild.OSTreePullStageReference) + inputRefs[source] = osbuild.OSTreePullStageReference{Ref: commitRef} + pullStageInput.References = inputRefs + return &osbuild.OSTreePullStageInputs{Commits: pullStageInput} +} + +func xorrisofsStageInputs() *osbuild.XorrisofsStageInputs { + input := new(osbuild.XorrisofsStageInput) + input.Type = "org.osbuild.tree" + input.Origin = "org.osbuild.pipeline" + input.References = osbuild.XorrisofsStageReferences{"name:bootiso-tree"} + return &osbuild.XorrisofsStageInputs{Tree: input} +} + +func copyPipelineTreeInputs(name, inputPipeline string) *osbuild.CopyStageInputs { + inputName := "root-tree" + treeInput := osbuild.CopyStageInput{} + treeInput.Type = "org.osbuild.tree" + treeInput.Origin = "org.osbuild.pipeline" + treeInput.References = []string{"name:" + inputPipeline} + return &osbuild.CopyStageInputs{inputName: treeInput} +} + +func qemuStageInputs(stage, file string) *osbuild.QEMUStageInputs { + stageKey := "name:" + stage + ref := map[string]osbuild.QEMUFile{ + stageKey: { + File: file, + }, + } + input := new(osbuild.QEMUStageInput) + input.Type = "org.osbuild.files" + input.Origin = "org.osbuild.pipeline" + input.References = ref + return &osbuild.QEMUStageInputs{Image: input} +} diff --git a/internal/distro/rhel90/stage_options.go b/internal/distro/rhel90/stage_options.go new file mode 100644 index 000000000..7456136fc --- /dev/null +++ b/internal/distro/rhel90/stage_options.go @@ -0,0 +1,514 @@ +package rhel90 + +import ( + "fmt" + "path/filepath" + "sort" + + "github.com/google/uuid" + + "github.com/osbuild/osbuild-composer/internal/blueprint" + "github.com/osbuild/osbuild-composer/internal/crypt" + "github.com/osbuild/osbuild-composer/internal/disk" + osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" + "github.com/osbuild/osbuild-composer/internal/rpmmd" +) + +const ( + kspath = "/osbuild.ks" +) + +func rpmStageOptions(repos []rpmmd.RepoConfig) *osbuild.RPMStageOptions { + var gpgKeys []string + for _, repo := range repos { + if repo.GPGKey == "" { + continue + } + gpgKeys = append(gpgKeys, repo.GPGKey) + } + + return &osbuild.RPMStageOptions{ + GPGKeys: gpgKeys, + } +} + +// selinuxStageOptions returns the options for the org.osbuild.selinux stage. +// Setting the argument to 'true' relabels the '/usr/bin/cp' and '/usr/bin/tar' +// binaries with 'install_exec_t'. This should be set in the build root. +func selinuxStageOptions(labelcp bool) *osbuild.SELinuxStageOptions { + options := &osbuild.SELinuxStageOptions{ + FileContexts: "etc/selinux/targeted/contexts/files/file_contexts", + } + if labelcp { + options.Labels = map[string]string{ + "/usr/bin/cp": "system_u:object_r:install_exec_t:s0", + "/usr/bin/tar": "system_u:object_r:install_exec_t:s0", + } + } + return options +} + +func userStageOptions(users []blueprint.UserCustomization) (*osbuild.UsersStageOptions, error) { + options := osbuild.UsersStageOptions{ + Users: make(map[string]osbuild.UsersStageOptionsUser), + } + + for _, c := range users { + if c.Password != nil && !crypt.PasswordIsCrypted(*c.Password) { + cryptedPassword, err := crypt.CryptSHA512(*c.Password) + if err != nil { + return nil, err + } + + c.Password = &cryptedPassword + } + + user := osbuild.UsersStageOptionsUser{ + Groups: c.Groups, + Description: c.Description, + Home: c.Home, + Shell: c.Shell, + Password: c.Password, + Key: c.Key, + } + + user.UID = c.UID + user.GID = c.GID + + options.Users[c.Name] = user + } + + return &options, nil +} + +func usersFirstBootOptions(usersStageOptions *osbuild.UsersStageOptions) *osbuild.FirstBootStageOptions { + cmds := make([]string, 0, 3*len(usersStageOptions.Users)+1) + // workaround for creating authorized_keys file for user + varhome := filepath.Join("/var", "home") + for name, user := range usersStageOptions.Users { + if user.Key != nil { + sshdir := filepath.Join(varhome, name, ".ssh") + cmds = append(cmds, fmt.Sprintf("mkdir -p %s", sshdir)) + cmds = append(cmds, fmt.Sprintf("sh -c 'echo %q >> %q'", *user.Key, filepath.Join(sshdir, "authorized_keys"))) + cmds = append(cmds, fmt.Sprintf("chown %s:%s -Rc %s", name, name, sshdir)) + } + } + cmds = append(cmds, fmt.Sprintf("restorecon -rvF %s", varhome)) + options := &osbuild.FirstBootStageOptions{ + Commands: cmds, + WaitForNetwork: false, + } + + return options +} + +func groupStageOptions(groups []blueprint.GroupCustomization) *osbuild.GroupsStageOptions { + options := osbuild.GroupsStageOptions{ + Groups: map[string]osbuild.GroupsStageOptionsGroup{}, + } + + for _, group := range groups { + groupData := osbuild.GroupsStageOptionsGroup{ + Name: group.Name, + } + groupData.GID = group.GID + + options.Groups[group.Name] = groupData + } + + return &options +} + +func firewallStageOptions(firewall *blueprint.FirewallCustomization) *osbuild.FirewallStageOptions { + options := osbuild.FirewallStageOptions{ + Ports: firewall.Ports, + } + + if firewall.Services != nil { + options.EnabledServices = firewall.Services.Enabled + options.DisabledServices = firewall.Services.Disabled + } + + return &options +} + +func systemdStageOptions(enabledServices, disabledServices []string, s *blueprint.ServicesCustomization, target string) *osbuild.SystemdStageOptions { + if s != nil { + enabledServices = append(enabledServices, s.Enabled...) + disabledServices = append(disabledServices, s.Disabled...) + } + return &osbuild.SystemdStageOptions{ + EnabledServices: enabledServices, + DisabledServices: disabledServices, + DefaultTarget: target, + } +} + +func buildStampStageOptions(arch string) *osbuild.BuildstampStageOptions { + return &osbuild.BuildstampStageOptions{ + Arch: arch, + Product: "Red Hat Enterprise Linux", + Version: osVersion, + Variant: "edge", + Final: true, + } +} + +func anacondaStageOptions() *osbuild.AnacondaStageOptions { + return &osbuild.AnacondaStageOptions{ + KickstartModules: []string{ + "org.fedoraproject.Anaconda.Modules.Network", + "org.fedoraproject.Anaconda.Modules.Payloads", + "org.fedoraproject.Anaconda.Modules.Storage", + }, + } +} + +func loraxScriptStageOptions(arch string) *osbuild.LoraxScriptStageOptions { + return &osbuild.LoraxScriptStageOptions{ + Path: "99-generic/runtime-postinstall.tmpl", + BaseArch: arch, + } +} + +func dracutStageOptions(kernelVer string) *osbuild.DracutStageOptions { + kernel := []string{kernelVer} + modules := []string{ + "bash", + "systemd", + "fips", + "systemd-initrd", + "modsign", + "nss-softokn", + "rdma", + "rngd", + "i18n", + "convertfs", + "network-manager", + "network", + "ifcfg", + "url-lib", + "drm", + "plymouth", + "prefixdevname", + "prefixdevname-tools", + "anaconda", + "crypt", + "dm", + "dmsquash-live", + "kernel-modules", + "kernel-modules-extra", + "kernel-network-modules", + "livenet", + "lvm", + "mdraid", + "multipath", + "qemu", + "qemu-net", + "fcoe", + "fcoe-uefi", + "iscsi", + "lunmask", + "nfs", + "resume", + "rootfs-block", + "terminfo", + "udev-rules", + "biosdevname", + "dracut-systemd", + "pollcdrom", + "usrmount", + "base", + "fs-lib", + "img-lib", + "shutdown", + "uefi-lib", + } + return &osbuild.DracutStageOptions{ + Kernel: kernel, + Modules: modules, + Install: []string{"/.buildstamp"}, + } +} + +func tarKickstartStageOptions(tarURL string) *osbuild.KickstartStageOptions { + return &osbuild.KickstartStageOptions{ + Path: kspath, + LiveIMG: &osbuild.LiveIMG{ + URL: tarURL, + }, + } +} + +func ostreeKickstartStageOptions(ostreeURL, ostreeRef string) *osbuild.KickstartStageOptions { + return &osbuild.KickstartStageOptions{ + Path: kspath, + OSTree: &osbuild.OSTreeOptions{ + OSName: "rhel", + URL: ostreeURL, + Ref: ostreeRef, + GPG: false, + }, + } +} + +func bootISOMonoStageOptions(kernelVer string, arch string) *osbuild.BootISOMonoStageOptions { + comprOptions := new(osbuild.FSCompressionOptions) + if bcj := osbuild.BCJOption(arch); bcj != "" { + comprOptions.BCJ = bcj + } + isolabel := fmt.Sprintf("RHEL-9-0-0-BaseOS-%s", arch) + return &osbuild.BootISOMonoStageOptions{ + Product: osbuild.Product{ + Name: "Red Hat Enterprise Linux", + Version: osVersion, + }, + ISOLabel: isolabel, + Kernel: kernelVer, + KernelOpts: fmt.Sprintf("inst.ks=hd:LABEL=%s:%s", isolabel, kspath), + EFI: osbuild.EFI{ + Architectures: []string{ + "IA32", + "X64", + }, + Vendor: "redhat", + }, + ISOLinux: osbuild.ISOLinux{ + Enabled: true, + Debug: false, + }, + Templates: "80-rhel", + RootFS: osbuild.RootFS{ + Size: 9216, + Compression: osbuild.FSCompression{ + Method: "xz", + Options: comprOptions, + }, + }, + } +} + +func discinfoStageOptions(arch string) *osbuild.DiscinfoStageOptions { + return &osbuild.DiscinfoStageOptions{ + BaseArch: arch, + Release: "202010217.n.0", + } +} + +func xorrisofsStageOptions(filename string, arch string) *osbuild.XorrisofsStageOptions { + return &osbuild.XorrisofsStageOptions{ + Filename: filename, + VolID: fmt.Sprintf("RHEL-9-0-0-BaseOS-%s", arch), + SysID: "LINUX", + Boot: &osbuild.XorrisofsBoot{ + Image: "isolinux/isolinux.bin", + Catalog: "isolinux/boot.cat", + }, + EFI: "images/efiboot.img", + IsohybridMBR: "/usr/share/syslinux/isohdpfx.bin", + } +} + +func grub2StageOptions(rootPartition *disk.Partition, bootPartition *disk.Partition, kernelOptions string, + kernel *blueprint.KernelCustomization, kernelVer string, uefi bool, legacy string) *osbuild.GRUB2StageOptions { + if rootPartition == nil { + panic("root partition must be defined for grub2 stage, this is a programming error") + } + + stageOptions := osbuild.GRUB2StageOptions{ + RootFilesystemUUID: uuid.MustParse(rootPartition.Filesystem.UUID), + KernelOptions: kernelOptions, + Legacy: legacy, + } + + if bootPartition != nil { + bootFsUUID := uuid.MustParse(bootPartition.Filesystem.UUID) + stageOptions.BootFilesystemUUID = &bootFsUUID + } + + if uefi { + stageOptions.UEFI = &osbuild.GRUB2UEFI{ + Vendor: "redhat", + Unified: legacy == "", // force unified grub scheme for pure efi systems + } + } + + if !uefi { + stageOptions.Legacy = legacy + } + + if kernel != nil { + if kernel.Append != "" { + stageOptions.KernelOptions += " " + kernel.Append + } + stageOptions.SavedEntry = "ffffffffffffffffffffffffffffffff-" + kernelVer + } + + return &stageOptions +} + +// sfdiskStageOptions creates the options and devices properties for an +// org.osbuild.sfdisk stage based on a partition table description +func sfdiskStageOptions(pt *disk.PartitionTable) *osbuild.SfdiskStageOptions { + partitions := make([]osbuild.Partition, len(pt.Partitions)) + for idx, p := range pt.Partitions { + partitions[idx] = osbuild.Partition{ + Bootable: p.Bootable, + Size: p.Size, + Start: p.Start, + Type: p.Type, + UUID: p.UUID, + } + } + stageOptions := &osbuild.SfdiskStageOptions{ + Label: pt.Type, + UUID: pt.UUID, + Partitions: partitions, + } + + return stageOptions +} + +// copyFSTreeOptions creates the options, inputs, devices, and mounts properties +// for an org.osbuild.copy stage for a given source tree using a partition +// table description to define the mounts +func copyFSTreeOptions(inputName, inputPipeline string, pt *disk.PartitionTable, device *osbuild.Device) ( + *osbuild.CopyStageOptions, + *osbuild.Devices, + *osbuild.Mounts, +) { + // assume loopback device for simplicity since it's the only one currently supported + // panic if the conversion fails + devOptions, ok := device.Options.(*osbuild.LoopbackDeviceOptions) + if !ok { + panic("copyStageOptions: failed to convert device options to loopback options") + } + + devices := make(map[string]osbuild.Device, len(pt.Partitions)) + mounts := make([]osbuild.Mount, 0, len(pt.Partitions)) + for _, p := range pt.Partitions { + if p.Filesystem == nil { + // no filesystem for partition (e.g., BIOS boot) + continue + } + name := filepath.Base(p.Filesystem.Mountpoint) + if name == "/" { + name = "root" + } + devices[name] = *osbuild.NewLoopbackDevice( + &osbuild.LoopbackDeviceOptions{ + Filename: devOptions.Filename, + Start: p.Start, + Size: p.Size, + }, + ) + var mount *osbuild.Mount + switch p.Filesystem.Type { + case "xfs": + mount = osbuild.NewXfsMount(name, name, p.Filesystem.Mountpoint) + case "vfat": + mount = osbuild.NewFATMount(name, name, p.Filesystem.Mountpoint) + case "ext4": + mount = osbuild.NewExt4Mount(name, name, p.Filesystem.Mountpoint) + case "btrfs": + mount = osbuild.NewBtrfsMount(name, name, p.Filesystem.Mountpoint) + default: + panic("unknown fs type " + p.Type) + } + mounts = append(mounts, *mount) + } + + // sort the mounts, using < should just work because: + // - a parent directory should be always before its children: + // / < /boot + // - the order of siblings doesn't matter + sort.Slice(mounts, func(i, j int) bool { + return mounts[i].Target < mounts[j].Target + }) + + stageMounts := osbuild.Mounts(mounts) + stageDevices := osbuild.Devices(devices) + + options := osbuild.CopyStageOptions{ + Paths: []osbuild.CopyStagePath{ + { + From: fmt.Sprintf("input://%s/", inputName), + To: "mount://root/", + }, + }, + } + + return &options, &stageDevices, &stageMounts +} + +func grub2InstStageOptions(filename string, pt *disk.PartitionTable, platform string) *osbuild.Grub2InstStageOptions { + bootPartIndex := pt.BootPartitionIndex() + if bootPartIndex == -1 { + panic("failed to find boot or root partition for grub2.inst stage") + } + core := osbuild.CoreMkImage{ + Type: "mkimage", + PartLabel: pt.Type, + Filesystem: pt.Partitions[bootPartIndex].Filesystem.Type, + } + + prefix := osbuild.PrefixPartition{ + Type: "partition", + PartLabel: pt.Type, + Number: uint(bootPartIndex), + Path: "/boot/grub2", + } + + return &osbuild.Grub2InstStageOptions{ + Filename: filename, + Platform: platform, + Location: pt.Partitions[0].Start, + Core: core, + Prefix: prefix, + } +} + +func ziplInstStageOptions(kernel string, pt *disk.PartitionTable) *osbuild.ZiplInstStageOptions { + bootPartIndex := pt.BootPartitionIndex() + if bootPartIndex == -1 { + panic("failed to find boot or root partition for zipl.inst stage") + } + + return &osbuild.ZiplInstStageOptions{ + Kernel: kernel, + Location: pt.Partitions[bootPartIndex].Start, + } +} + +func qemuStageOptions(filename, format, compat string) *osbuild.QEMUStageOptions { + var options osbuild.QEMUFormatOptions + switch format { + case "qcow2": + options = osbuild.Qcow2Options{ + Type: "qcow2", + Compat: compat, + } + case "vpc": + options = osbuild.VPCOptions{ + Type: "vpc", + } + case "vmdk": + options = osbuild.VMDKOptions{ + Type: "vmdk", + } + default: + panic("unknown format in qemu stage: " + format) + } + + return &osbuild.QEMUStageOptions{ + Filename: filename, + Format: options, + } +} + +func kernelCmdlineStageOptions(rootUUID string, kernelOptions string) *osbuild.KernelCmdlineStageOptions { + return &osbuild.KernelCmdlineStageOptions{ + RootFsUUID: rootUUID, + KernelOpts: kernelOptions, + } +}