EDGE image types are defined under a different name for RHEL-8.5, specifically they don't contain the "rhel-" prefix any more. To ensure backward compatibility, add image type aliases for all EDGE image types with the "rhel-" prefix. Image type aliases are used only when getting a specific imageType instance by its name. When listing all available image types for an architecture, only the current image type names are returned, without any aliases. This prevents the image types from being exposed multiple times under different names via Weldr API. Extend the distro unit tests to test image type aliases. Signed-off-by: Tomas Hozza <thozza@redhat.com>
602 lines
16 KiB
Go
602 lines
16 KiB
Go
package rhel85
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"sort"
|
|
|
|
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
|
"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-85"
|
|
const osVersion = "8.5"
|
|
const modulePlatformID = "platform:el8"
|
|
const ostreeRef = "rhel/8/%s/edge"
|
|
|
|
const (
|
|
// package set names
|
|
|
|
// build package set name
|
|
buildPkgsKey = "build"
|
|
|
|
// bootable image package set name
|
|
bootPkgsKey = "boot"
|
|
|
|
// 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"
|
|
)
|
|
|
|
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) 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
|
|
uefi bool
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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 {
|
|
// add boot sets
|
|
mergedSets[osPkgsKey] = mergedSets[osPkgsKey].Append(archSets[bootPkgsKey]).Append(distroSets[bootPkgsKey])
|
|
}
|
|
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}
|
|
return mergedSets
|
|
|
|
}
|
|
|
|
func (t *imageType) Exports() []string {
|
|
if len(t.exports) > 0 {
|
|
return t.exports
|
|
}
|
|
return []string{"assembler"}
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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")
|
|
}
|
|
|
|
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: "x86_64",
|
|
distro: rd,
|
|
packageSets: map[string]rpmmd.PackageSet{
|
|
buildPkgsKey: x8664BuildPackageSet(),
|
|
bootPkgsKey: x8664BootPackageSet(),
|
|
edgePkgsKey: x8664EdgeCommitPackageSet(),
|
|
},
|
|
legacy: "i386-pc",
|
|
uefi: true,
|
|
}
|
|
|
|
aarch64 := architecture{
|
|
name: "aarch64",
|
|
distro: rd,
|
|
packageSets: map[string]rpmmd.PackageSet{
|
|
bootPkgsKey: aarch64BootPackageSet(),
|
|
edgePkgsKey: aarch64EdgeCommitPackageSet(),
|
|
},
|
|
uefi: true,
|
|
}
|
|
|
|
ppc64le := architecture{
|
|
distro: rd,
|
|
name: "ppc64le",
|
|
packageSets: map[string]rpmmd.PackageSet{
|
|
bootPkgsKey: ppc64leBootPackageSet(),
|
|
buildPkgsKey: ppc64leBuildPackageSet(),
|
|
},
|
|
legacy: "powerpc-ieee1275",
|
|
uefi: false,
|
|
}
|
|
s390x := architecture{
|
|
distro: rd,
|
|
name: "s390x",
|
|
packageSets: map[string]rpmmd.PackageSet{
|
|
bootPkgsKey: s390xBootPackageSet(),
|
|
},
|
|
uefi: false,
|
|
}
|
|
|
|
// Shared Services
|
|
edgeServices := []string{
|
|
"NetworkManager.service", "firewalld.service", "sshd.service",
|
|
}
|
|
|
|
// 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{
|
|
buildPkgsKey: 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"},
|
|
}
|
|
|
|
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"},
|
|
}
|
|
|
|
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"},
|
|
}
|
|
|
|
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"},
|
|
}
|
|
|
|
amiImgType := imageType{
|
|
name: "ami",
|
|
filename: "image.raw",
|
|
mimeType: "application/octet-stream",
|
|
packageSets: map[string]rpmmd.PackageSet{
|
|
osPkgsKey: amiCommonPackageSet(),
|
|
},
|
|
defaultTarget: "multi-user.target",
|
|
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto",
|
|
bootable: true,
|
|
defaultSize: 6 * GigaByte,
|
|
pipelines: amiPipelines,
|
|
exports: []string{"image"},
|
|
}
|
|
|
|
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: "tar-installer",
|
|
filename: "installer.iso",
|
|
mimeType: "application/x-iso9660-image",
|
|
packageSets: map[string]rpmmd.PackageSet{
|
|
buildPkgsKey: x8664InstallerBuildPackageSet(),
|
|
osPkgsKey: {
|
|
Include: []string{"lvm2", "policycoreutils", "selinux-policy-targeted"},
|
|
Exclude: []string{"rng-tools"},
|
|
},
|
|
installerPkgsKey: installerPackageSet(),
|
|
},
|
|
rpmOstree: false,
|
|
bootISO: true,
|
|
pipelines: tarInstallerPipelines,
|
|
exports: []string{"bootiso"},
|
|
}
|
|
|
|
x86_64.addImageTypes(qcow2ImgType, vhdImgType, vmdkImgType, openstackImgType, amiImgType, tarImgType, tarInstallerImgTypeX86_64, edgeCommitImgType, edgeInstallerImgType, edgeOCIImgType)
|
|
aarch64.addImageTypes(qcow2ImgType, openstackImgType, amiImgType, tarImgType, edgeCommitImgType, edgeOCIImgType)
|
|
ppc64le.addImageTypes(qcow2ImgType, tarImgType)
|
|
s390x.addImageTypes(qcow2ImgType, tarImgType)
|
|
|
|
rd.addArches(x86_64, aarch64, ppc64le, s390x)
|
|
return rd
|
|
}
|