debian-forge-composer/internal/distro/rhel7/distro.go
Tomáš Hozza 3569936aba distro/rhel: remove legacy member from architecture struct
Remove the `legacy` from `architecture` struct, since this information
is already contained in the platform associated with the image.

This reflects changes which were done in Fedora when it was ported to
the "new" image definitions, but were not ported to RHEL.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
2023-05-19 13:24:39 +02:00

611 lines
17 KiB
Go

package rhel7
import (
"errors"
"fmt"
"math/rand"
"sort"
"strings"
"github.com/sirupsen/logrus"
"golang.org/x/exp/slices"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/pathpolicy"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
"github.com/osbuild/osbuild-composer/internal/workload"
)
const (
// package set names
// main/common os image package set name
osPkgsKey = "os"
// blueprint package set name
blueprintPkgsKey = "blueprint"
)
// RHEL-based OS image configuration defaults
var defaultDistroImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("America/New_York"),
Locale: common.ToPtr("en_US.UTF-8"),
GPGKeyFiles: []string{
"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
},
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
},
},
}
// distribution objects without the arches > image types
var distroMap = map[string]distribution{
"rhel-7": {
name: "rhel-7",
product: "Red Hat Enterprise Linux",
osVersion: "7.9",
nick: "Maipo",
releaseVersion: "7",
modulePlatformID: "platform:el7",
vendor: "redhat",
runner: &runner.RHEL{Major: uint64(7), Minor: uint64(9)},
defaultImageConfig: defaultDistroImageConfig,
},
}
// --- Distribution ---
type distribution struct {
name string
product string
nick string
osVersion string
releaseVersion string
modulePlatformID string
vendor string
runner runner.Runner
arches map[string]distro.Arch
defaultImageConfig *distro.ImageConfig
}
func (d *distribution) Name() string {
return d.name
}
func (d *distribution) Releasever() string {
return d.releaseVersion
}
func (d *distribution) ModulePlatformID() string {
return d.modulePlatformID
}
func (d *distribution) OSTreeRef() string {
return "" // not supported
}
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]
}
}
func (d *distribution) isRHEL() bool {
return strings.HasPrefix(d.name, "rhel")
}
func (d *distribution) getDefaultImageConfig() *distro.ImageConfig {
return d.defaultImageConfig
}
// --- Architecture ---
type architecture struct {
distro *distribution
name string
imageTypes map[string]distro.ImageType
imageTypeAliases map[string]string
}
func (a *architecture) Name() string {
return a.name
}
func (a *architecture) ListImageTypes() []string {
itNames := make([]string, 0, len(a.imageTypes))
for name := range a.imageTypes {
itNames = append(itNames, name)
}
sort.Strings(itNames)
return itNames
}
func (a *architecture) GetImageType(name string) (distro.ImageType, error) {
t, exists := a.imageTypes[name]
if !exists {
aliasForName, exists := a.imageTypeAliases[name]
if !exists {
return nil, 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(platform platform.Platform, imageTypes ...imageType) {
if a.imageTypes == nil {
a.imageTypes = map[string]distro.ImageType{}
}
for idx := range imageTypes {
it := imageTypes[idx]
it.arch = a
it.platform = platform
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
}
// --- Image Type ---
type packageSetFunc func(t *imageType) rpmmd.PackageSet
type imageFunc func(workload workload.Workload, t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, packageSets map[string]rpmmd.PackageSet, containers []container.Spec, rng *rand.Rand) (image.ImageKind, error)
type imageType struct {
arch *architecture
platform platform.Platform
environment environment.Environment
workload workload.Workload
name string
nameAliases []string
filename string
compression string // TODO: remove from image definition and make it a transport option
mimeType string
packageSets map[string]packageSetFunc
packageSetChains map[string][]string
defaultImageConfig *distro.ImageConfig
kernelOptions string
defaultSize uint64
buildPipelines []string
payloadPipelines []string
exports []string
image imageFunc
// bootable image
bootable bool
// 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 {
// Not supported
return ""
}
func (t *imageType) Size(size uint64) uint64 {
if size == 0 {
size = t.defaultSize
}
return size
}
func (t *imageType) BuildPipelines() []string {
return t.buildPipelines
}
func (t *imageType) PayloadPipelines() []string {
return t.payloadPipelines
}
func (t *imageType) PayloadPackageSets() []string {
return []string{blueprintPkgsKey}
}
func (t *imageType) PackageSetsChains() map[string][]string {
return t.packageSetChains
}
func (t *imageType) Exports() []string {
if len(t.exports) == 0 {
panic(fmt.Sprintf("programming error: no exports for '%s'", t.name))
}
return t.exports
}
// getBootType returns the BootType which should be used for this particular image type
func (t *imageType) getBootType() distro.BootType {
if t.platform.GetUEFIVendor() != "" && t.platform.GetBIOSPlatform() != "" {
return distro.HybridBootType
} else if t.platform.GetUEFIVendor() != "" {
return distro.UEFIBootType
} else if t.platform.GetBIOSPlatform() != "" || t.platform.GetZiplSupport() {
return distro.LegacyBootType
}
return distro.UnsetBootType
}
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 nil, fmt.Errorf("unknown arch: " + archName)
}
imageSize := t.Size(options.Size)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, true, nil, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
// ensure that image always returns non-nil default config
imageConfig := t.defaultImageConfig
if imageConfig == nil {
imageConfig = &distro.ImageConfig{}
}
return imageConfig.InheritFrom(t.arch.distro.getDefaultImageConfig())
}
func (t *imageType) PartitionType() string {
archName := t.arch.Name()
basePartitionTable, exists := t.basePartitionTables[archName]
if !exists {
return ""
}
return basePartitionTable.Type
}
func (t *imageType) initializeManifest(bp *blueprint.Blueprint,
options distro.ImageOptions,
repos []rpmmd.RepoConfig,
packageSets map[string]rpmmd.PackageSet,
containers []container.Spec,
seed int64) (*manifest.Manifest, []string, error) {
warnings, err := t.checkOptions(bp.Customizations, options, containers)
if err != nil {
return nil, nil, err
}
w := t.workload
if w == nil {
cw := &workload.Custom{
BaseWorkload: workload.BaseWorkload{
Repos: packageSets[blueprintPkgsKey].Repositories,
},
Packages: bp.GetPackagesEx(false),
}
if services := bp.Customizations.GetServices(); services != nil {
cw.Services = services.Enabled
cw.DisabledServices = services.Disabled
}
w = cw
}
source := rand.NewSource(seed)
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(source)
if t.image == nil {
return nil, nil, nil
}
img, err := t.image(w, t, bp.Customizations, options, packageSets, containers, rng)
if err != nil {
return nil, nil, err
}
manifest := manifest.New()
_, err = img.InstantiateManifest(&manifest, repos, t.arch.distro.runner, rng)
if err != nil {
return nil, nil, err
}
return &manifest, warnings, err
}
func (t *imageType) Manifest(customizations *blueprint.Customizations,
options distro.ImageOptions,
repos []rpmmd.RepoConfig,
packageSets map[string][]rpmmd.PackageSpec,
containers []container.Spec,
seed int64) (distro.Manifest, []string, error) {
bp := &blueprint.Blueprint{Name: "empty blueprint"}
err := bp.Initialize()
if err != nil {
panic("could not initialize empty blueprint: " + err.Error())
}
bp.Customizations = customizations
// the os pipeline filters repos based on the `osPkgsKey` package set, merge the repos which
// contain a payload package set into the `osPkgsKey`, so those repos are included when
// building the rpm stage in the os pipeline
// TODO: roll this into workloads
mergedRepos := make([]rpmmd.RepoConfig, 0, len(repos))
for _, repo := range repos {
for _, pkgsKey := range t.PayloadPackageSets() {
// If the repo already contains the osPkgsKey, skip
if slices.Contains(repo.PackageSets, osPkgsKey) {
break
}
if slices.Contains(repo.PackageSets, pkgsKey) {
repo.PackageSets = append(repo.PackageSets, osPkgsKey)
}
}
mergedRepos = append(mergedRepos, repo)
}
manifest, warnings, err := t.initializeManifest(bp, options, mergedRepos, nil, containers, seed)
if err != nil {
return distro.Manifest{}, nil, err
}
ret, err := manifest.Serialize(packageSets)
if err != nil {
return ret, nil, err
}
return ret, warnings, err
}
func (t *imageType) PackageSets(bp blueprint.Blueprint, options distro.ImageOptions, repos []rpmmd.RepoConfig) 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
packageSets := make(map[string]rpmmd.PackageSet)
for name, getter := range t.packageSets {
packageSets[name] = getter(t)
}
// amend with repository information
for _, repo := range repos {
if len(repo.PackageSets) > 0 {
// only apply the repo to the listed package sets
for _, psName := range repo.PackageSets {
ps := packageSets[psName]
ps.Repositories = append(ps.Repositories, repo)
packageSets[psName] = ps
}
}
}
// Similar to above, for edge-commit and edge-container, we need to set an
// ImageRef in order to properly initialize the manifest and package
// selection.
options.OSTree.ImageRef = t.OSTreeRef()
// create a temporary container spec array with the info from the blueprint
// to initialize the manifest
containers := make([]container.Spec, len(bp.Containers))
for idx := range bp.Containers {
containers[idx] = container.Spec{
Source: bp.Containers[idx].Source,
TLSVerify: bp.Containers[idx].TLSVerify,
LocalName: bp.Containers[idx].Name,
}
}
// create a manifest object and instantiate it with the computed packageSetChains
manifest, _, err := t.initializeManifest(&bp, options, repos, packageSets, containers, 0)
if err != nil {
// TODO: handle manifest initialization errors more gracefully, we
// refuse to initialize manifests with invalid config.
logrus.Errorf("Initializing the manifest failed for %s (%s/%s): %v", t.Name(), t.arch.distro.Name(), t.arch.Name(), err)
return nil
}
return overridePackageNamesInSets(manifest.GetPackageSetChains())
}
// Runs overridePackageNames() on each package set's Include and Exclude list
// and replaces package names.
func overridePackageNamesInSets(chains map[string][]rpmmd.PackageSet) map[string][]rpmmd.PackageSet {
pkgSetChains := make(map[string][]rpmmd.PackageSet)
for name, chain := range chains {
cc := make([]rpmmd.PackageSet, len(chain))
for idx := range chain {
cc[idx] = rpmmd.PackageSet{
Include: overridePackageNames(chain[idx].Include),
Exclude: overridePackageNames(chain[idx].Exclude),
Repositories: chain[idx].Repositories,
}
}
pkgSetChains[name] = cc
}
return pkgSetChains
}
// Resolve packages to their distro-specific name. This function is a temporary
// workaround to the issue of having packages specified outside of distros (in
// internal/manifest/os.go), which should be distro agnostic. In the future,
// this should be handled more generally.
func overridePackageNames(packages []string) []string {
for idx := range packages {
switch packages[idx] {
case "python3-pyyaml":
packages[idx] = "python3-PyYAML"
}
}
return packages
}
// checkOptions checks the validity and compatibility of options and customizations for the image type.
// Returns ([]string, error) where []string, if non-nil, will hold any generated warnings (e.g. deprecation notices).
func (t *imageType) checkOptions(customizations *blueprint.Customizations, options distro.ImageOptions, containers []container.Spec) ([]string, error) {
// holds warnings (e.g. deprecation notices)
var warnings []string
if t.workload != nil {
// For now, if an image type defines its own workload, don't allow any
// user customizations.
// Soon we will have more workflows and each will define its allowed
// set of customizations. The current set of customizations defined in
// the blueprint spec corresponds to the Custom workflow.
if customizations != nil {
return warnings, fmt.Errorf("image type %q does not support customizations", t.name)
}
}
if len(containers) > 0 {
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
mountpoints := customizations.GetFilesystems()
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
if err != nil {
return warnings, err
}
if osc := customizations.GetOpenSCAP(); osc != nil {
return warnings, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported os version: %s", t.arch.distro.osVersion))
}
// Check Directory/File Customizations are valid
dc := customizations.GetDirectories()
fc := customizations.GetFiles()
err = blueprint.ValidateDirFileCustomizations(dc, fc)
if err != nil {
return warnings, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
if err != nil {
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
if err != nil {
return warnings, err
}
// check if repository customizations are valid
_, err = customizations.GetRepositories()
if err != nil {
return warnings, err
}
return warnings, nil
}
// New creates a new distro object, defining the supported architectures and image types
func New() distro.Distro {
return newDistro("rhel-7")
}
func newDistro(distroName string) distro.Distro {
rd := distroMap[distroName]
// Architecture definitions
x86_64 := architecture{
name: distro.X86_64ArchName,
distro: &rd,
}
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "0.10",
},
},
qcow2ImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VHD,
},
},
azureRhuiImgType,
)
rd.addArches(
x86_64,
)
return &rd
}