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>
611 lines
17 KiB
Go
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
|
|
}
|