Pass through all repos to the initalizeManifest() function. Each pipeline will then select which repositories it needs based on the PackageSets field of each repository. Before, we only passed global repos down to the manifest generators and pipeline-specific repositories would only be used if they were attached to package sets and were handled explicitly by a pipeline generator. The repositories of the "blueprint" package set are explicitly added to the workload and returned by the "os" pipeline. The repositories of the "installer" package set are explicitly added to the "anaconda-tree" pipeline. If a repository was specified for any other pipeline, for example "build", the repositories for the that package set were never added to the pipeline. Fixes #3290
568 lines
15 KiB
Go
568 lines
15 KiB
Go
package rhel7
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"math/rand"
|
|
"sort"
|
|
"strings"
|
|
|
|
"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"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
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
|
|
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(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
|
|
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
|
|
// 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 {
|
|
// 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
|
|
// 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) 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, 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, error) {
|
|
|
|
if err := t.checkOptions(bp.Customizations, options, containers); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO: let image types specify valid workloads, rather than
|
|
// always assume Custom.
|
|
w := &workload.Custom{
|
|
BaseWorkload: workload.BaseWorkload{
|
|
Repos: packageSets[blueprintPkgsKey].Repositories,
|
|
},
|
|
Packages: bp.GetPackagesEx(false),
|
|
}
|
|
if services := bp.Customizations.GetServices(); services != nil {
|
|
w.Services = services.Enabled
|
|
w.DisabledServices = services.Disabled
|
|
}
|
|
|
|
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
|
|
}
|
|
img, err := t.image(w, t, bp.Customizations, options, packageSets, containers, rng)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
manifest := manifest.New()
|
|
_, err = img.InstantiateManifest(&manifest, repos, t.arch.distro.runner, rng)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &manifest, 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, error) {
|
|
|
|
bp := &blueprint.Blueprint{Name: "empty blueprint"}
|
|
err := bp.Initialize()
|
|
if err != nil {
|
|
panic("could not initialize empty blueprint: " + err.Error())
|
|
}
|
|
bp.Customizations = customizations
|
|
|
|
manifest, err := t.initializeManifest(bp, options, repos, nil, containers, seed)
|
|
if err != nil {
|
|
return distro.Manifest{}, err
|
|
}
|
|
|
|
return manifest.Serialize(packageSets)
|
|
}
|
|
|
|
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.
|
|
func (t *imageType) checkOptions(customizations *blueprint.Customizations, options distro.ImageOptions, containers []container.Spec) error {
|
|
|
|
if len(containers) > 0 {
|
|
return 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 err
|
|
}
|
|
|
|
if osc := customizations.GetOpenSCAP(); osc != nil {
|
|
return 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 err
|
|
}
|
|
|
|
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return 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,
|
|
legacy: "i386-pc",
|
|
bootType: distro.HybridBootType,
|
|
}
|
|
|
|
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
|
|
}
|