Port osbuild/images v0.33.0 with dot-notation to composer

Update the osbuild/images to the version which introduces "dot notation"
for distro release versions.

 - Replace all uses of distroregistry by distrofactory.
 - Delete local version of reporegistry and use the one from the
   osbuild/images.
 - Weldr: unify `createWeldrAPI()` and `createWeldrAPI2()` into a single
   `createTestWeldrAPI()` function`.
 - store/fixture: rework fixtures to allow overriding the host distro
   name and host architecture name. A cleanup function to restore the
   host distro and arch names is always part of the fixture struct.
 - Delete `distro_mock` package, since it is no longer used.
 - Bump the required version of osbuild to 98, because the OSCAP
   customization is using the 'compress_results' stage option, which is
   not available in older versions of osbuild.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
This commit is contained in:
Tomáš Hozza 2024-01-08 17:58:49 +01:00 committed by Achilleas Koutsou
parent f6ff8c40dd
commit 625b1578fa
1166 changed files with 154457 additions and 5508 deletions

View file

@ -1,59 +1,9 @@
package common
import (
"bufio"
"errors"
"io"
"os"
"strings"
"github.com/hashicorp/go-version"
)
func GetHostDistroName() (string, bool, bool, error) {
f, err := os.Open("/etc/os-release")
if err != nil {
return "", false, false, err
}
defer f.Close()
osrelease, err := readOSRelease(f)
if err != nil {
return "", false, false, err
}
isStream := osrelease["NAME"] == "CentOS Stream"
version := strings.Split(osrelease["VERSION_ID"], ".")
name := osrelease["ID"] + "-" + strings.Join(version, "")
// TODO: We should probably index these things by the full CPE
beta := strings.Contains(osrelease["CPE_NAME"], "beta")
return name, beta, isStream, nil
}
func readOSRelease(r io.Reader) (map[string]string, error) {
osrelease := make(map[string]string)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if len(line) == 0 {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
return nil, errors.New("readOSRelease: invalid input")
}
key := strings.TrimSpace(parts[0])
// drop all surrounding whitespace and double-quotes
value := strings.Trim(strings.TrimSpace(parts[1]), "\"")
osrelease[key] = value
}
return osrelease, nil
}
// Returns true if the version represented by the first argument is
// semantically older than the second.
//

View file

@ -0,0 +1,37 @@
package common
import (
"bufio"
"os"
"strings"
)
const (
FIPSEnabledImageWarning = `The host building this image is not ` +
`running in FIPS mode. The image will still be FIPS compliant. ` +
`If you have custom steps that generate keys or perform ` +
`cryptographic operations, those must be considered non-compliant.`
)
var (
FIPSEnabledFilePath = "/proc/sys/crypto/fips_enabled"
)
func IsBuildHostFIPSEnabled() (enabled bool) {
file, err := os.Open(FIPSEnabledFilePath)
if err != nil {
return
}
defer file.Close()
buf := []byte{}
_, err = file.Read(buf)
if err != nil {
return
}
scanner := bufio.NewScanner(file)
scanner.Scan()
if err := scanner.Err(); err != nil {
return
}
return strings.TrimSpace(scanner.Text()) == "1"
}

View file

@ -1,6 +1,13 @@
// Package blueprint contains primitives for representing weldr blueprints
package blueprint
import "fmt"
const (
dockerTransport = "docker"
containersStorageTransport = "containers-storage"
)
// A Blueprint is a high-level description of an image.
type Blueprint struct {
Name string `json:"name" toml:"name"`
@ -29,10 +36,12 @@ type Group struct {
}
type Container struct {
Source string `json:"source" toml:"source"`
Source string `json:"source,omitempty" toml:"source"`
Name string `json:"name,omitempty" toml:"name,omitempty"`
TLSVerify *bool `json:"tls-verify,omitempty" toml:"tls-verify,omitempty"`
TLSVerify *bool `json:"tls-verify,omitempty" toml:"tls-verify,omitempty"`
ContainersTransport *string `json:"containers-transport,omitempty" toml:"containers-transport,omitempty"`
StoragePath *string `json:"source-path,omitempty" toml:"source-path,omitempty"`
}
// packages, modules, and groups all resolve to rpm packages right now. This
@ -70,3 +79,31 @@ func (p Package) ToNameVersion() string {
return p.Name + "-" + p.Version
}
func (c Container) Validate() error {
if c.StoragePath != nil {
if c.ContainersTransport == nil {
// error out here, but realistically we could also just
// set the transport instead
return fmt.Errorf("Cannot specify storage location %s without a transport", *c.StoragePath)
}
if *c.ContainersTransport != containersStorageTransport {
return fmt.Errorf(
"Incompatible transport %s for storage location %s, only containers-storage transport is supported",
*c.ContainersTransport,
*c.StoragePath,
)
}
}
if c.ContainersTransport == nil {
return nil
}
if *c.ContainersTransport != dockerTransport && *c.ContainersTransport != containersStorageTransport {
return fmt.Errorf("Unknown containers-transport: %s", *c.ContainersTransport)
}
return nil
}

View file

@ -7,24 +7,25 @@ import (
)
type Customizations struct {
Hostname *string `json:"hostname,omitempty" toml:"hostname,omitempty"`
Kernel *KernelCustomization `json:"kernel,omitempty" toml:"kernel,omitempty"`
SSHKey []SSHKeyCustomization `json:"sshkey,omitempty" toml:"sshkey,omitempty"`
User []UserCustomization `json:"user,omitempty" toml:"user,omitempty"`
Group []GroupCustomization `json:"group,omitempty" toml:"group,omitempty"`
Timezone *TimezoneCustomization `json:"timezone,omitempty" toml:"timezone,omitempty"`
Locale *LocaleCustomization `json:"locale,omitempty" toml:"locale,omitempty"`
Firewall *FirewallCustomization `json:"firewall,omitempty" toml:"firewall,omitempty"`
Services *ServicesCustomization `json:"services,omitempty" toml:"services,omitempty"`
Filesystem []FilesystemCustomization `json:"filesystem,omitempty" toml:"filesystem,omitempty"`
InstallationDevice string `json:"installation_device,omitempty" toml:"installation_device,omitempty"`
FDO *FDOCustomization `json:"fdo,omitempty" toml:"fdo,omitempty"`
OpenSCAP *OpenSCAPCustomization `json:"openscap,omitempty" toml:"openscap,omitempty"`
Ignition *IgnitionCustomization `json:"ignition,omitempty" toml:"ignition,omitempty"`
Directories []DirectoryCustomization `json:"directories,omitempty" toml:"directories,omitempty"`
Files []FileCustomization `json:"files,omitempty" toml:"files,omitempty"`
Repositories []RepositoryCustomization `json:"repositories,omitempty" toml:"repositories,omitempty"`
FIPS *bool `json:"fips,omitempty" toml:"fips,omitempty"`
Hostname *string `json:"hostname,omitempty" toml:"hostname,omitempty"`
Kernel *KernelCustomization `json:"kernel,omitempty" toml:"kernel,omitempty"`
SSHKey []SSHKeyCustomization `json:"sshkey,omitempty" toml:"sshkey,omitempty"`
User []UserCustomization `json:"user,omitempty" toml:"user,omitempty"`
Group []GroupCustomization `json:"group,omitempty" toml:"group,omitempty"`
Timezone *TimezoneCustomization `json:"timezone,omitempty" toml:"timezone,omitempty"`
Locale *LocaleCustomization `json:"locale,omitempty" toml:"locale,omitempty"`
Firewall *FirewallCustomization `json:"firewall,omitempty" toml:"firewall,omitempty"`
Services *ServicesCustomization `json:"services,omitempty" toml:"services,omitempty"`
Filesystem []FilesystemCustomization `json:"filesystem,omitempty" toml:"filesystem,omitempty"`
InstallationDevice string `json:"installation_device,omitempty" toml:"installation_device,omitempty"`
FDO *FDOCustomization `json:"fdo,omitempty" toml:"fdo,omitempty"`
OpenSCAP *OpenSCAPCustomization `json:"openscap,omitempty" toml:"openscap,omitempty"`
Ignition *IgnitionCustomization `json:"ignition,omitempty" toml:"ignition,omitempty"`
Directories []DirectoryCustomization `json:"directories,omitempty" toml:"directories,omitempty"`
Files []FileCustomization `json:"files,omitempty" toml:"files,omitempty"`
Repositories []RepositoryCustomization `json:"repositories,omitempty" toml:"repositories,omitempty"`
FIPS *bool `json:"fips,omitempty" toml:"fips,omitempty"`
ContainersStorage *ContainerStorageCustomization `json:"containers-storage,omitempty" toml:"containers-storage,omitempty"`
}
type IgnitionCustomization struct {
@ -119,6 +120,13 @@ type OpenSCAPTailoringCustomizations struct {
Unselected []string `json:"unselected,omitempty" toml:"unselected,omitempty"`
}
// Configure the container storage separately from containers, since we most likely would
// like to use the same storage path for all of the containers.
type ContainerStorageCustomization struct {
// destination is always `containers-storage`, so we won't expose this
StoragePath *string `json:"destination-path,omitempty" toml:"destination-path,omitempty"`
}
type CustomizationError struct {
Message string
}
@ -365,3 +373,13 @@ func (c *Customizations) GetFIPS() bool {
}
return *c.FIPS
}
func (c *Customizations) GetContainerStorage() *ContainerStorageCustomization {
if c == nil || c.ContainersStorage == nil {
return nil
}
if *c.ContainersStorage.StoragePath == "" {
return nil
}
return c.ContainersStorage
}

View file

@ -25,6 +25,7 @@ import (
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/transports/alltransports"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
@ -34,6 +35,9 @@ import (
const (
DefaultUserAgent = "osbuild-composer/1.0"
DefaultPolicyPath = "/etc/containers/policy.json"
containersStorageTransport = "containers-storage"
dockerTransport = "docker"
)
// GetDefaultAuthFile returns the authentication file to use for the
@ -340,9 +344,32 @@ func (m RawManifest) Digest() (digest.Digest, error) {
return manifest.Digest(m.Data)
}
func getImageRef(target reference.Named, transport string, storagePath string) (types.ImageReference, error) {
switch transport {
case "", dockerTransport:
ref, err := docker.NewReference(target)
if err != nil {
return nil, err
}
return ref, nil
case containersStorageTransport:
var storage string
if storagePath != "" {
storage = fmt.Sprintf("[overlay@%s]", storagePath)
}
ref, err := alltransports.ParseImageName(fmt.Sprintf("%s:%s%s", transport, storage, target.Name()))
if err != nil {
return nil, err
}
return ref, nil
default:
return nil, fmt.Errorf("Unknown containers-transport: %s", transport)
}
}
// GetManifest fetches the raw manifest data from the server. If digest is not empty
// it will override any given tag for the Client's Target.
func (cl *Client) GetManifest(ctx context.Context, digest digest.Digest) (r RawManifest, err error) {
func (cl *Client) GetManifest(ctx context.Context, digest digest.Digest, container *SourceSpec) (r RawManifest, err error) {
target := cl.Target
if digest != "" {
@ -355,13 +382,22 @@ func (cl *Client) GetManifest(ctx context.Context, digest digest.Digest) (r RawM
target = t
}
ref, err := docker.NewReference(target)
var transport string
if container != nil && container.ContainersTransport != nil {
transport = *container.ContainersTransport
}
var storagePath string
if container != nil && container.StoragePath != nil {
storagePath = *container.StoragePath
}
ref, err := getImageRef(target, transport, storagePath)
if err != nil {
return
}
src, err := ref.NewImageSource(ctx, cl.sysCtx)
if err != nil {
return
}
@ -402,8 +438,7 @@ func (cl *Client) resolveManifestList(ctx context.Context, list manifestList) (r
return resolvedIds{}, err
}
raw, err := cl.GetManifest(ctx, digest)
raw, err := cl.GetManifest(ctx, digest, nil)
if err != nil {
return resolvedIds{}, fmt.Errorf("error getting manifest: %w", err)
}
@ -487,9 +522,9 @@ func (cl *Client) resolveRawManifest(ctx context.Context, rm RawManifest) (resol
// which is the digest of the configuration object. It uses the architecture and
// variant specified via SetArchitectureChoice or the corresponding defaults for
// the host.
func (cl *Client) Resolve(ctx context.Context, name string) (Spec, error) {
func (cl *Client) Resolve(ctx context.Context, name string, container *SourceSpec) (Spec, error) {
raw, err := cl.GetManifest(ctx, "")
raw, err := cl.GetManifest(ctx, "", container)
if err != nil {
return Spec{}, fmt.Errorf("error getting manifest: %w", err)
@ -500,7 +535,23 @@ func (cl *Client) Resolve(ctx context.Context, name string) (Spec, error) {
return Spec{}, err
}
spec := NewSpec(cl.Target, ids.Manifest, ids.Config, cl.GetTLSVerify(), ids.ListManifest.String(), name)
var transport *string
var location *string
if container != nil {
transport = container.ContainersTransport
location = container.StoragePath
}
spec := NewSpec(
cl.Target,
ids.Manifest,
ids.Config,
cl.GetTLSVerify(),
ids.ListManifest.String(),
name,
transport,
location,
)
return spec, nil
}

View file

@ -23,9 +23,12 @@ type Resolver struct {
}
type SourceSpec struct {
Source string
Name string
TLSVerify *bool
Source string
Name string
Digest *string
TLSVerify *bool
ContainersTransport *string
StoragePath *string
}
func NewResolver(arch string) Resolver {
@ -52,7 +55,7 @@ func (r *Resolver) Add(spec SourceSpec) {
}
go func() {
spec, err := client.Resolve(r.ctx, spec.Name)
spec, err := client.Resolve(r.ctx, spec.Name, &spec)
if err != nil {
err = fmt.Errorf("'%s': %w", spec.Source, err)
}

View file

@ -11,27 +11,31 @@ import (
// at the Source via Digest and ImageID. The latter one
// should remain the same in the target image as well.
type Spec struct {
Source string // does not include the manifest digest
Digest string // digest of the manifest at the Source
TLSVerify *bool // controls TLS verification
ImageID string // container image identifier
LocalName string // name to use inside the image
ListDigest string // digest of the list manifest at the Source (optional)
Source string // does not include the manifest digest
Digest string // digest of the manifest at the Source
TLSVerify *bool // controls TLS verification
ImageID string // container image identifier
LocalName string // name to use inside the image
ListDigest string // digest of the list manifest at the Source (optional)
ContainersTransport *string // the type of transport used for the container
StoragePath *string // location of the local containers-storage
}
// NewSpec creates a new Spec from the essential information.
// It also converts is the transition point from container
// specific types (digest.Digest) to generic types (string).
func NewSpec(source reference.Named, digest, imageID digest.Digest, tlsVerify *bool, listDigest string, localName string) Spec {
func NewSpec(source reference.Named, digest, imageID digest.Digest, tlsVerify *bool, listDigest string, localName string, transport *string, storagePath *string) Spec {
if localName == "" {
localName = source.String()
}
return Spec{
Source: source.Name(),
Digest: digest.String(),
TLSVerify: tlsVerify,
ImageID: imageID.String(),
LocalName: localName,
ListDigest: listDigest,
Source: source.Name(),
Digest: digest.String(),
TLSVerify: tlsVerify,
ImageID: imageID.String(),
LocalName: localName,
ListDigest: listDigest,
ContainersTransport: transport,
StoragePath: storagePath,
}
}

View file

@ -17,6 +17,8 @@ const (
BOOT_LEGACY
BOOT_UEFI
BOOT_HYBRID
UnsupportedCustomizationError = "unsupported blueprint customizations found for image type %q: (allowed: %s)"
NoCustomizationsAllowedError = "image type %q does not support customizations"
)
func (m BootMode) String() string {

View file

@ -131,6 +131,20 @@ var (
exports: []string{"commit-archive"},
}
iotBootableContainer = imageType{
name: "iot-bootable-container",
filename: "iot-bootable-container.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: bootableContainerPackageSet,
},
rpmOstree: true,
image: bootableContainerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "ostree-commit", "ostree-encapsulate"},
exports: []string{"ostree-encapsulate"},
}
iotOCIImgType = imageType{
name: "iot-container",
nameAliases: []string{"fedora-iot-container"},
@ -519,20 +533,6 @@ func (a *architecture) Distro() distro.Distro {
return a.distro
}
// New creates a new distro object, defining the supported architectures and image types
func NewF37() distro.Distro {
return newDistro(37)
}
func NewF38() distro.Distro {
return newDistro(38)
}
func NewF39() distro.Distro {
return newDistro(39)
}
func NewF40() distro.Distro {
return newDistro(40)
}
func newDistro(version int) distro.Distro {
rd := getDistro(version)
@ -843,6 +843,7 @@ func newDistro(version int) distro.Distro {
},
iotSimplifiedInstallerImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
BasePlatform: platform.BasePlatform{
@ -868,6 +869,63 @@ func newDistro(version int) distro.Distro {
)
}
if !common.VersionLessThan(rd.Releasever(), "39") {
// bootc was introduced in F39
x86_64.addImageTypes(
&platform.X86{
BasePlatform: platform.BasePlatform{
FirmwarePackages: []string{
"biosdevname",
"iwlwifi-dvm-firmware",
"iwlwifi-mvm-firmware",
"microcode_ctl",
},
},
BIOS: true,
UEFIVendor: "fedora",
},
iotBootableContainer,
)
aarch64.addImageTypes(
&platform.Aarch64{
BasePlatform: platform.BasePlatform{
FirmwarePackages: []string{
"arm-image-installer",
"bcm283x-firmware",
"brcmfmac-firmware",
"iwlwifi-mvm-firmware",
"realtek-firmware",
"uboot-images-armv8",
},
},
UEFIVendor: "fedora",
},
iotBootableContainer,
)
ppc64le.addImageTypes(
&platform.PPC64LE{
BIOS: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "1.1",
},
},
iotBootableContainer,
)
s390x.addImageTypes(
&platform.S390X{
Zipl: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "1.1",
},
},
iotBootableContainer,
)
}
ppc64le.addImageTypes(
&platform.PPC64LE{
BIOS: true,
@ -901,3 +959,29 @@ func newDistro(version int) distro.Distro {
rd.addArches(x86_64, aarch64, ppc64le, s390x)
return &rd
}
func ParseID(idStr string) (*distro.ID, error) {
id, err := distro.ParseID(idStr)
if err != nil {
return nil, err
}
if id.Name != "fedora" {
return nil, fmt.Errorf("invalid distro name: %s", id.Name)
}
if id.MinorVersion != -1 {
return nil, fmt.Errorf("fedora distro does not support minor versions")
}
return id, nil
}
func DistroFactory(idStr string) distro.Distro {
id, err := ParseID(idStr)
if err != nil {
return nil
}
return newDistro(id.MajorVersion)
}

View file

@ -138,6 +138,19 @@ func osCustomizations(
panic(fmt.Sprintf("failed to convert file customizations to fs node files: %v", err))
}
// OSTree commits do not include data in `/var` since that is tied to the
// deployment, rather than the commit. Therefore the containers need to be
// stored in a different location, like `/usr/share`, and the container
// storage engine configured accordingly.
if t.rpmOstree && len(containers) > 0 {
storagePath := "/usr/share/containers/storage"
osc.ContainersStorage = &storagePath
}
if containerStorage := c.GetContainerStorage(); containerStorage != nil {
osc.ContainersStorage = containerStorage.StoragePath
}
customRepos, err := c.GetRepositories()
if err != nil {
// This shouldn't happen and since the repos
@ -410,6 +423,32 @@ func iotCommitImage(workload workload.Workload,
return img, nil
}
func bootableContainerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
parentCommit, commitRef := makeOSTreeParentCommit(options.OSTree, t.OSTreeRef())
img := image.NewOSTreeArchive(commitRef)
d := t.arch.distro
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], containers, bp.Customizations)
img.Environment = t.environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = d.osVersion
img.Filename = t.Filename()
img.InstallWeakDeps = false
img.BootContainer = true
return img, nil
}
func iotContainerImage(workload workload.Workload,
t *imageType,
bp *blueprint.Blueprint,
@ -485,7 +524,8 @@ func iotInstallerImage(workload workload.Workload,
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.Variant = "IoT"
img.OSName = "fedora"
img.OSName = "fedora-iot"
img.Remote = "fedora-iot"
img.OSVersion = d.osVersion
img.Release = fmt.Sprintf("%s %s", d.product, d.osVersion)

View file

@ -7,7 +7,6 @@ import (
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/internal/environment"
"github.com/osbuild/images/internal/pathpolicy"
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/images/pkg/container"
@ -17,6 +16,7 @@ import (
"github.com/osbuild/images/pkg/image"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/policies"
"github.com/osbuild/images/pkg/rpmmd"
"golang.org/x/exp/slices"
)
@ -222,8 +222,14 @@ func (t *imageType) Manifest(bp *blueprint.Blueprint,
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx := range bp.Containers {
containerSources[idx] = container.SourceSpec(bp.Containers[idx])
for idx, cont := range bp.Containers {
containerSources[idx] = container.SourceSpec{
Source: cont.Source,
Name: cont.Name,
TLSVerify: cont.TLSVerify,
ContainersTransport: cont.ContainersTransport,
StoragePath: cont.StoragePath,
}
}
source := rand.NewSource(seed)
@ -256,6 +262,14 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return nil, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
if len(bp.Containers) > 0 {
for _, container := range bp.Containers {
if err := container.Validate(); err != nil {
return nil, err
}
}
}
if options.OSTree != nil {
if err := options.OSTree.Validate(); err != nil {
return nil, err
@ -272,7 +286,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
if t.name == "iot-raw-image" || t.name == "iot-qcow2-image" {
allowed := []string{"User", "Group", "Directories", "Files", "Services", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return nil, fmt.Errorf("unsupported blueprint customizations found for image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return nil, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
// TODO: consider additional checks, such as those in "edge-simplified-installer" in RHEL distros
}
@ -283,7 +297,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
if t.name == "iot-simplified-installer" {
allowed := []string{"InstallationDevice", "FDO", "Ignition", "Kernel", "User", "Group", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return nil, fmt.Errorf("unsupported blueprint customizations found for boot ISO image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return nil, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
if customizations.GetInstallationDevice() == "" {
return nil, fmt.Errorf("boot ISO image type %q requires specifying an installation device to install to", t.name)
@ -321,12 +335,12 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
} else if t.name == "iot-installer" || t.name == "image-installer" {
allowed := []string{"User", "Group", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return nil, fmt.Errorf("unsupported blueprint customizations found for boot ISO image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return nil, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
} else if t.name == "live-installer" {
allowed := []string{}
if err := customizations.CheckAllowed(allowed...); err != nil {
return nil, fmt.Errorf("unsupported blueprint customizations found for boot ISO image type %q: (allowed: None)", t.name)
return nil, fmt.Errorf(distro.NoCustomizationsAllowedError, t.name)
}
}
}
@ -341,7 +355,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return nil, fmt.Errorf("Custom mountpoints are not supported for ostree types")
}
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies)
if err != nil {
return nil, err
}
@ -368,12 +382,12 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return nil, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, policies.CustomDirectoriesPolicies)
if err != nil {
return nil, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
err = blueprint.CheckFileCustomizationsPolicy(fc, policies.CustomFilesPolicies)
if err != nil {
return nil, err
}
@ -384,5 +398,10 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return nil, err
}
if customizations.GetFIPS() && !common.IsBuildHostFIPSEnabled() {
w := fmt.Sprintln(common.FIPSEnabledImageWarning)
return []string{w}, nil
}
return nil, nil
}

View file

@ -188,6 +188,104 @@ func iotCommitPackageSet(t *imageType) rpmmd.PackageSet {
return ps
}
func bootableContainerPackageSet(t *imageType) rpmmd.PackageSet {
// Replicating package selection from centos-bootc:
// https://github.com/CentOS/centos-bootc/
ps := rpmmd.PackageSet{
Include: []string{
"acl",
"attr", // used by admins interactively
"bootc",
"bootupd",
"chrony", // NTP support
"container-selinux",
"container-selinux",
"crun",
"cryptsetup",
"dnf",
"e2fsprogs",
"fwupd", // if you're using linux-firmware, you probably also want fwupd
"iproute", "iproute-tc", // route manipulation and QoS
"iptables", "nftables", // firewall manipulation
"iptables-services", // additional firewall support
"kbd", // i18n
"keyutils", // Manipulating the kernel keyring; used by bootc
"libsss_sudo", // allow communication between sudo and SSSD for caching sudo rules by SSSD
"linux-firmware", // linux-firmware now a recommends so let's explicitly include it
"logrotate", // There are things that write outside of the journal still (such as the classic wtmp, etc.). auditd also writes outside the journal but it has its own log rotation. Anything package layered will also tend to expect files dropped in /etc/logrotate.d to work. Really, this is a legacy thing, but if we don't have it then people's disks will slowly fill up with logs.
"lsof",
"lvm2", // Storage configuration/management
"nano", // default editor
"ncurses", // provides terminal tools like clear, reset, tput, and tset
"NetworkManager-cloud-setup", // support for cloud quirks and dynamic config in real rootfs: https://github.com/coreos/fedora-coreos-tracker/issues/320
"NetworkManager", "hostname", // standard tools for configuring network/hostname
"NetworkManager-team", "teamd", // teaming https://github.com/coreos/fedora-coreos-config/pull/289 and http://bugzilla.redhat.com/1758162
"NetworkManager-tui", // interactive Networking configuration during coreos-install
"nfs-utils-coreos", "iptables-nft", // minimal NFS client
"nss-altfiles",
"openssh-clients",
"openssh-server",
"openssl",
"ostree",
"passwd", "shadow-utils", // User configuration
"podman",
"rpm-ostree",
"selinux-policy-targeted",
"sg3_utils",
"skopeo",
"socat", "net-tools", "bind-utils", // interactive network tools for admins
"sssd-client", "sssd-ad", "sssd-ipa", "sssd-krb5", "sssd-ldap", // SSSD backends
"stalld", // Boost starving threads https://github.com/coreos/fedora-coreos-tracker/issues/753
"subscription-manager", // To ensure we can enable client certs to access RHEL content
"sudo",
"systemd",
"systemd-resolved", // resolved was broken out to its own package in rawhide/f35
"tpm2-tools", // needed for tpm2 bound luks
"WALinuxAgent-udev", // udev rules for Azure (rhbz#1748432)
"xfsprogs",
"zram-generator", // zram-generator (but not zram-generator-defaults) for F33 change
},
Exclude: []string{
"cowsay", // just in case
"grubby",
"initscripts", // make sure initscripts doesn't get pulled back in https://github.com/coreos/fedora-coreos-tracker/issues/220#issuecomment-611566254
"NetworkManager-initscripts-ifcfg-rh", // do not use legacy ifcfg config format in NetworkManager See https://github.com/coreos/fedora-coreos-config/pull/1991
"nodejs",
"perl",
"perl-interpreter",
"plymouth", // for (datacenter/cloud oriented) servers, we want to see the details by default. https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/thread/HSMISZ3ETWQ4ETVLWZQJ55ARZT27AAV3/
"systemd-networkd", // we use NetworkManager
},
}
switch t.Arch().Name() {
case arch.ARCH_AARCH64.String():
ps.Append(rpmmd.PackageSet{
Include: []string{
"irqbalance",
"ostree-grub2",
},
})
case arch.ARCH_PPC64LE.String():
ps.Append(rpmmd.PackageSet{
Include: []string{
"irqbalance",
"librtas",
"powerpc-utils-core",
"ppc64-diag-rtas",
},
})
case arch.ARCH_X86_64.String():
ps.Append(rpmmd.PackageSet{
Include: []string{
"irqbalance",
},
})
}
return ps
}
// INSTALLER PACKAGE SET
func installerPackageSet(t *imageType) rpmmd.PackageSet {

50
vendor/github.com/osbuild/images/pkg/distro/host.go generated vendored Normal file
View file

@ -0,0 +1,50 @@
package distro
import (
"bufio"
"errors"
"io"
"os"
"strings"
)
// GetHostDistroName returns the name of the host distribution, such as
// "fedora-32" or "rhel-8.2". It does so by reading the /etc/os-release file.
func GetHostDistroName() (string, error) {
f, err := os.Open("/etc/os-release")
if err != nil {
return "", err
}
defer f.Close()
osrelease, err := readOSRelease(f)
if err != nil {
return "", err
}
name := osrelease["ID"] + "-" + osrelease["VERSION_ID"]
return name, nil
}
func readOSRelease(r io.Reader) (map[string]string, error) {
osrelease := make(map[string]string)
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if len(line) == 0 {
continue
}
parts := strings.SplitN(line, "=", 2)
if len(parts) != 2 {
return nil, errors.New("readOSRelease: invalid input")
}
key := strings.TrimSpace(parts[0])
// drop all surrounding whitespace and double-quotes
value := strings.Trim(strings.TrimSpace(parts[1]), "\"")
osrelease[key] = value
}
return osrelease, nil
}

82
vendor/github.com/osbuild/images/pkg/distro/id.go generated vendored Normal file
View file

@ -0,0 +1,82 @@
package distro
import (
"fmt"
"strconv"
"strings"
)
// ID represents a distro name and version
type ID struct {
Name string
MajorVersion int
// MinorVersion is -1 if not specified
MinorVersion int
}
func (id ID) String() string {
if id.MinorVersion == -1 {
return fmt.Sprintf("%s-%d", id.Name, id.MajorVersion)
}
return fmt.Sprintf("%s-%d.%d", id.Name, id.MajorVersion, id.MinorVersion)
}
type ParseError struct {
ToParse string
Msg string
Inner error
}
func (e ParseError) Error() string {
msg := fmt.Sprintf("error when parsing distro name (%s): %v", e.ToParse, e.Msg)
if e.Inner != nil {
msg += fmt.Sprintf(", inner error:\n%v", e.Inner)
}
return msg
}
// ParseID parses a distro name and version from a Distro ID string.
// This is the generic parser, which is used by all distros as the base parser.
//
// Limitations:
// - the distro name must not contain a dash
func ParseID(idStr string) (*ID, error) {
idParts := strings.Split(idStr, "-")
if len(idParts) < 2 {
return nil, ParseError{ToParse: idStr, Msg: "A dash is expected to separate distro name and version"}
}
name := strings.Join(idParts[:len(idParts)-1], "-")
version := idParts[len(idParts)-1]
versionParts := strings.Split(version, ".")
if len(versionParts) > 2 {
return nil, ParseError{ToParse: idStr, Msg: fmt.Sprintf("too many dots in the version (%d)", len(versionParts)-1)}
}
majorVersion, err := strconv.Atoi(versionParts[0])
if err != nil {
return nil, ParseError{ToParse: idStr, Msg: "parsing major version failed", Inner: err}
}
minorVersion := -1
if len(versionParts) > 1 {
minorVersion, err = strconv.Atoi(versionParts[1])
if err != nil {
return nil, ParseError{ToParse: idStr, Msg: "parsing minor version failed", Inner: err}
}
}
return &ID{
Name: name,
MajorVersion: majorVersion,
MinorVersion: minorVersion,
}, nil
}

View file

@ -48,21 +48,6 @@ var defaultDistroImageConfig = &distro.ImageConfig{
},
}
// 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
@ -193,14 +178,24 @@ func (a *architecture) Distro() distro.Distro {
return a.distro
}
// 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]
func newDistro(name string, minor int) *distribution {
var rd distribution
switch name {
case "rhel":
rd = distribution{
name: fmt.Sprintf("rhel-7.%d", minor),
product: "Red Hat Enterprise Linux",
osVersion: fmt.Sprintf("7.%d", minor),
nick: "Maipo",
releaseVersion: "7",
modulePlatformID: "platform:el7",
vendor: "redhat",
runner: &runner.RHEL{Major: uint64(7), Minor: uint64(minor)},
defaultImageConfig: defaultDistroImageConfig,
}
default:
panic(fmt.Sprintf("unknown distro name: %s", name))
}
// Architecture definitions
x86_64 := architecture{
@ -237,3 +232,33 @@ func newDistro(distroName string) distro.Distro {
return &rd
}
func ParseID(idStr string) (*distro.ID, error) {
id, err := distro.ParseID(idStr)
if err != nil {
return nil, err
}
if id.Name != "rhel" {
return nil, fmt.Errorf("invalid distro name: %s", id.Name)
}
if id.MajorVersion != 7 {
return nil, fmt.Errorf("invalid distro major version: %d", id.MajorVersion)
}
// RHEL uses minor version
if id.Name == "rhel" && id.MinorVersion == -1 {
return nil, fmt.Errorf("rhel requires minor version, but got: %d", id.MinorVersion)
}
return id, nil
}
func DistroFactory(idStr string) distro.Distro {
id, err := ParseID(idStr)
if err != nil {
return nil
}
return newDistro(id.Name, id.MinorVersion)
}

View file

@ -5,7 +5,6 @@ import (
"math/rand"
"github.com/osbuild/images/internal/environment"
"github.com/osbuild/images/internal/pathpolicy"
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/images/pkg/container"
@ -14,6 +13,7 @@ import (
"github.com/osbuild/images/pkg/image"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/policies"
"github.com/osbuild/images/pkg/rpmmd"
"golang.org/x/exp/slices"
)
@ -198,8 +198,14 @@ func (t *imageType) Manifest(bp *blueprint.Blueprint,
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx := range bp.Containers {
containerSources[idx] = container.SourceSpec(bp.Containers[idx])
for idx, cont := range bp.Containers {
containerSources[idx] = container.SourceSpec{
Source: cont.Source,
Name: cont.Name,
TLSVerify: cont.TLSVerify,
ContainersTransport: cont.ContainersTransport,
StoragePath: cont.StoragePath,
}
}
source := rand.NewSource(seed)
@ -237,7 +243,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
// 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)
return warnings, fmt.Errorf(distro.NoCustomizationsAllowedError, t.name)
}
}
@ -247,7 +253,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
mountpoints := customizations.GetFilesystems()
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies)
if err != nil {
return warnings, err
}
@ -265,12 +271,12 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return warnings, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, policies.CustomDirectoriesPolicies)
if err != nil {
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
err = blueprint.CheckFileCustomizationsPolicy(fc, policies.CustomFilesPolicies)
if err != nil {
return warnings, err
}

View file

@ -123,53 +123,12 @@ func (d *distribution) getDefaultImageConfig() *distro.ImageConfig {
return d.defaultImageConfig
}
// New creates a new distro object, defining the supported architectures and image types
func New() distro.Distro {
// default minor: create default minor version (current GA) and rename it
d := newDistro("rhel", 10)
d.name = "rhel-8"
return d
}
func NewRHEL84() distro.Distro {
return newDistro("rhel", 4)
}
func NewRHEL85() distro.Distro {
return newDistro("rhel", 5)
}
func NewRHEL86() distro.Distro {
return newDistro("rhel", 6)
}
func NewRHEL87() distro.Distro {
return newDistro("rhel", 7)
}
func NewRHEL88() distro.Distro {
return newDistro("rhel", 8)
}
func NewRHEL89() distro.Distro {
return newDistro("rhel", 9)
}
func NewRHEL810() distro.Distro {
return newDistro("rhel", 10)
}
func NewCentos() distro.Distro {
return newDistro("centos", 0)
}
func newDistro(name string, minor int) *distribution {
var rd distribution
switch name {
case "rhel":
rd = distribution{
name: fmt.Sprintf("rhel-8%d", minor),
name: fmt.Sprintf("rhel-8.%d", minor),
product: "Red Hat Enterprise Linux",
osVersion: fmt.Sprintf("8.%d", minor),
releaseVersion: "8",
@ -517,3 +476,52 @@ func newDistro(name string, minor int) *distribution {
rd.addArches(x86_64, aarch64, ppc64le)
return &rd
}
func ParseID(idStr string) (*distro.ID, error) {
id, err := distro.ParseID(idStr)
if err != nil {
return nil, err
}
if id.Name != "rhel" && id.Name != "centos" {
return nil, fmt.Errorf("invalid distro name: %s", id.Name)
}
// Backward compatibility layer for "rhel-84" or "rhel-810"
if id.Name == "rhel" && id.MinorVersion == -1 {
if id.MajorVersion/10 == 8 {
// handle single digit minor version
id.MinorVersion = id.MajorVersion % 10
id.MajorVersion = 8
} else if id.MajorVersion/100 == 8 {
// handle two digit minor version
id.MinorVersion = id.MajorVersion % 100
id.MajorVersion = 8
}
}
if id.MajorVersion != 8 {
return nil, fmt.Errorf("invalid distro major version: %d", id.MajorVersion)
}
// CentOS does not use minor version
if id.Name == "centos" && id.MinorVersion != -1 {
return nil, fmt.Errorf("centos does not use minor version, but got: %d", id.MinorVersion)
}
// RHEL uses minor version
if id.Name == "rhel" && id.MinorVersion == -1 {
return nil, fmt.Errorf("rhel requires minor version, but got: %d", id.MinorVersion)
}
return id, nil
}
func DistroFactory(idStr string) distro.Distro {
id, err := ParseID(idStr)
if err != nil {
return nil
}
return newDistro(id.Name, id.MinorVersion)
}

View file

@ -155,6 +155,19 @@ func osCustomizations(
panic(fmt.Sprintf("failed to convert file customizations to fs node files: %v", err))
}
// OSTree commits do not include data in `/var` since that is tied to the
// deployment, rather than the commit. Therefore the containers need to be
// stored in a different location, like `/usr/share`, and the container
// storage engine configured accordingly.
if t.rpmOstree && len(containers) > 0 {
storagePath := "/usr/share/containers/storage"
osc.ContainersStorage = &storagePath
}
if containerStorage := c.GetContainerStorage(); containerStorage != nil {
osc.ContainersStorage = containerStorage.StoragePath
}
// set yum repos first, so it doesn't get overridden by
// imageConfig.YUMRepos
osc.YUMRepos = imageConfig.YUMRepos

View file

@ -10,7 +10,6 @@ import (
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/internal/environment"
"github.com/osbuild/images/internal/pathpolicy"
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/images/pkg/container"
@ -20,6 +19,7 @@ import (
"github.com/osbuild/images/pkg/image"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/policies"
"github.com/osbuild/images/pkg/rpmmd"
)
@ -243,8 +243,14 @@ func (t *imageType) Manifest(bp *blueprint.Blueprint,
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx := range bp.Containers {
containerSources[idx] = container.SourceSpec(bp.Containers[idx])
for idx, cont := range bp.Containers {
containerSources[idx] = container.SourceSpec{
Source: cont.Source,
Name: cont.Name,
TLSVerify: cont.TLSVerify,
ContainersTransport: cont.ContainersTransport,
StoragePath: cont.StoragePath,
}
}
source := rand.NewSource(seed)
@ -282,7 +288,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
// 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)
return warnings, fmt.Errorf(distro.NoCustomizationsAllowedError, t.name)
}
}
// we do not support embedding containers on ostree-derived images, only on commits themselves
@ -290,6 +296,14 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
if len(bp.Containers) > 0 {
for _, container := range bp.Containers {
if err := container.Validate(); err != nil {
return nil, err
}
}
}
if options.OSTree != nil {
if err := options.OSTree.Validate(); err != nil {
return nil, err
@ -305,7 +319,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
if t.name == "edge-simplified-installer" {
allowed := []string{"InstallationDevice", "FDO", "User", "Group", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return warnings, fmt.Errorf("unsupported blueprint customizations found for boot ISO image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
if customizations.GetInstallationDevice() == "" {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying an installation device to install to", t.name)
@ -332,7 +346,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
} else if t.name == "edge-installer" {
allowed := []string{"User", "Group", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return warnings, fmt.Errorf("unsupported blueprint customizations found for boot ISO image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
}
}
@ -345,7 +359,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
allowed := []string{"User", "Group", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return warnings, fmt.Errorf("unsupported blueprint customizations found for image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
// TODO: consider additional checks, such as those in "edge-simplified-installer"
}
@ -375,7 +389,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return warnings, fmt.Errorf("Custom mountpoints are not supported for ostree types")
}
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies)
if err != nil {
return warnings, err
}
@ -403,12 +417,12 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
if err != nil {
return warnings, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, policies.CustomDirectoriesPolicies)
if err != nil {
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
err = blueprint.CheckFileCustomizationsPolicy(fc, policies.CustomFilesPolicies)
if err != nil {
return warnings, err
}
@ -419,5 +433,11 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return warnings, err
}
if customizations.GetFIPS() && !common.IsBuildHostFIPSEnabled() {
w := fmt.Sprintln(common.FIPSEnabledImageWarning)
log.Print(w)
warnings = append(warnings, w)
}
return warnings, nil
}

View file

@ -125,43 +125,12 @@ func (d *distribution) getDefaultImageConfig() *distro.ImageConfig {
return d.defaultImageConfig
}
func New() distro.Distro {
// default minor: create default minor version (current GA) and rename it
d := newDistro("rhel", 4)
d.name = "rhel-9"
return d
}
func NewCentOS9() distro.Distro {
return newDistro("centos", 0)
}
func NewRHEL90() distro.Distro {
return newDistro("rhel", 0)
}
func NewRHEL91() distro.Distro {
return newDistro("rhel", 1)
}
func NewRHEL92() distro.Distro {
return newDistro("rhel", 2)
}
func NewRHEL93() distro.Distro {
return newDistro("rhel", 3)
}
func NewRHEL94() distro.Distro {
return newDistro("rhel", 4)
}
func newDistro(name string, minor int) *distribution {
var rd distribution
switch name {
case "rhel":
rd = distribution{
name: fmt.Sprintf("rhel-9%d", minor),
name: fmt.Sprintf("rhel-9.%d", minor),
product: "Red Hat Enterprise Linux",
osVersion: fmt.Sprintf("9.%d", minor),
releaseVersion: "9",
@ -494,3 +463,49 @@ func newDistro(name string, minor int) *distribution {
rd.addArches(x86_64, aarch64, ppc64le, s390x)
return &rd
}
func ParseID(idStr string) (*distro.ID, error) {
id, err := distro.ParseID(idStr)
if err != nil {
return nil, err
}
if id.Name != "rhel" && id.Name != "centos" {
return nil, fmt.Errorf("invalid distro name: %s", id.Name)
}
// Backward compatibility layer for "rhel-93" and friends
if id.Name == "rhel" && id.MinorVersion == -1 {
if id.MajorVersion/10 == 9 {
// handle single digit minor version
id.MinorVersion = id.MajorVersion % 10
id.MajorVersion = 9
}
// two digit minor versions in the old format are not supported
}
if id.MajorVersion != 9 {
return nil, fmt.Errorf("invalid distro major version: %d", id.MajorVersion)
}
// CentOS does not use minor version
if id.Name == "centos" && id.MinorVersion != -1 {
return nil, fmt.Errorf("centos does not use minor version, but got: %d", id.MinorVersion)
}
// RHEL uses minor version
if id.Name == "rhel" && id.MinorVersion == -1 {
return nil, fmt.Errorf("rhel requires minor version, but got: %d", id.MinorVersion)
}
return id, nil
}
func DistroFactory(idStr string) distro.Distro {
id, err := ParseID(idStr)
if err != nil {
return nil
}
return newDistro(id.Name, id.MinorVersion)
}

View file

@ -152,6 +152,19 @@ func osCustomizations(
panic(fmt.Sprintf("failed to convert file customizations to fs node files: %v", err))
}
// OSTree commits do not include data in `/var` since that is tied to the
// deployment, rather than the commit. Therefore the containers need to be
// stored in a different location, like `/usr/share`, and the container
// storage engine configured accordingly.
if t.rpmOstree && len(containers) > 0 {
storagePath := "/usr/share/containers/storage"
osc.ContainersStorage = &storagePath
}
if containerStorage := c.GetContainerStorage(); containerStorage != nil {
osc.ContainersStorage = containerStorage.StoragePath
}
// set yum repos first, so it doesn't get overridden by
// imageConfig.YUMRepos
osc.YUMRepos = imageConfig.YUMRepos

View file

@ -10,7 +10,6 @@ import (
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/internal/environment"
"github.com/osbuild/images/internal/pathpolicy"
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/images/pkg/container"
@ -20,6 +19,7 @@ import (
"github.com/osbuild/images/pkg/image"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/policies"
"github.com/osbuild/images/pkg/rpmmd"
)
@ -243,8 +243,14 @@ func (t *imageType) Manifest(bp *blueprint.Blueprint,
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx := range bp.Containers {
containerSources[idx] = container.SourceSpec(bp.Containers[idx])
for idx, cont := range bp.Containers {
containerSources[idx] = container.SourceSpec{
Source: cont.Source,
Name: cont.Name,
TLSVerify: cont.TLSVerify,
ContainersTransport: cont.ContainersTransport,
StoragePath: cont.StoragePath,
}
}
source := rand.NewSource(seed)
@ -281,7 +287,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
// 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)
return warnings, fmt.Errorf(distro.NoCustomizationsAllowedError, t.name)
}
}
@ -290,6 +296,14 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
if len(bp.Containers) > 0 {
for _, container := range bp.Containers {
if err := container.Validate(); err != nil {
return nil, err
}
}
}
if options.OSTree != nil {
if err := options.OSTree.Validate(); err != nil {
return nil, err
@ -305,7 +319,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
if t.name == "edge-simplified-installer" {
allowed := []string{"InstallationDevice", "FDO", "Ignition", "Kernel", "User", "Group", "FIPS", "Filesystem"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return warnings, fmt.Errorf("unsupported blueprint customizations found for boot ISO image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
if customizations.GetInstallationDevice() == "" {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying an installation device to install to", t.name)
@ -343,7 +357,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
} else if t.name == "edge-installer" {
allowed := []string{"User", "Group", "FIPS"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return warnings, fmt.Errorf("unsupported blueprint customizations found for boot ISO image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
}
}
@ -355,7 +369,7 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
}
allowed := []string{"Ignition", "Kernel", "User", "Group", "FIPS", "Filesystem"}
if err := customizations.CheckAllowed(allowed...); err != nil {
return warnings, fmt.Errorf("unsupported blueprint customizations found for image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", "))
return warnings, fmt.Errorf(distro.UnsupportedCustomizationError, t.name, strings.Join(allowed, ", "))
}
// TODO: consider additional checks, such as those in "edge-simplified-installer"
}
@ -384,13 +398,13 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return warnings, fmt.Errorf("Custom mountpoints are not supported for edge-container and edge-commit")
} else if mountpoints != nil && t.rpmOstree && !(t.name == "edge-container" || t.name == "edge-commit") {
//customization allowed for edge-raw-image,edge-ami,edge-vsphere,edge-simplified-installer
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.OstreeMountpointPolicies)
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.OstreeMountpointPolicies)
if err != nil {
return warnings, err
}
}
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
err := blueprint.CheckMountpointsPolicy(mountpoints, policies.MountpointPolicies)
if err != nil {
return warnings, err
}
@ -418,12 +432,12 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
if err != nil {
return warnings, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, policies.CustomDirectoriesPolicies)
if err != nil {
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
err = blueprint.CheckFileCustomizationsPolicy(fc, policies.CustomFilesPolicies)
if err != nil {
return warnings, err
}
@ -434,5 +448,11 @@ func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOp
return warnings, err
}
if customizations.GetFIPS() && !common.IsBuildHostFIPSEnabled() {
w := fmt.Sprintln(common.FIPSEnabledImageWarning)
log.Print(w)
warnings = append(warnings, w)
}
return warnings, nil
}

View file

@ -7,7 +7,6 @@ import (
"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distroregistry"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/rpmmd"
@ -46,12 +45,13 @@ type TestImageType struct {
}
const (
TestDistroName = "test-distro"
TestDistro2Name = "test-distro-2"
TestDistroReleasever = "1"
TestDistro2Releasever = "2"
TestDistroModulePlatformID = "platform:test"
TestDistro2ModulePlatformID = "platform:test-2"
// The Test Distro name base. It can't be used to get a distro.Distro
// instance from the DistroFactory(), because it does not include any
// release version.
TestDistroNameBase = "test-distro"
// An ID string for a Test Distro instance with release version 1.
TestDistro1Name = TestDistroNameBase + "-1"
TestArchName = "test_arch"
TestArch2Name = "test_arch2"
@ -294,18 +294,18 @@ func (t *TestImageType) Manifest(b *blueprint.Blueprint, options distro.ImageOpt
return m, nil, nil
}
// NewTestDistro returns a new instance of TestDistro with the
// given name and modulePlatformID.
// newTestDistro returns a new instance of TestDistro with the
// given release version.
//
// It contains two architectures "test_arch" and "test_arch2".
// "test_arch" contains one image type "test_type".
// "test_arch2" contains two image types "test_type" and "test_type2".
func NewTestDistro(name, modulePlatformID, releasever string) *TestDistro {
func newTestDistro(releasever string) *TestDistro {
td := TestDistro{
name: name,
name: fmt.Sprintf("%s-%s", TestDistroNameBase, releasever),
releasever: releasever,
modulePlatformID: modulePlatformID,
ostreeRef: "test/13/x86_64/edge",
modulePlatformID: fmt.Sprintf("platform:%s-%s", TestDistroNameBase, releasever),
ostreeRef: fmt.Sprintf("test/%s/x86_64/edge", releasever),
}
ta1 := TestArch{
@ -373,19 +373,19 @@ func NewTestDistro(name, modulePlatformID, releasever string) *TestDistro {
return &td
}
// New returns new instance of TestDistro named "test-distro".
func New() *TestDistro {
return NewTestDistro(TestDistroName, TestDistroModulePlatformID, TestDistroReleasever)
}
func NewRegistry() *distroregistry.Registry {
td := New()
registry, err := distroregistry.New(td, td)
func DistroFactory(idStr string) distro.Distro {
id, err := distro.ParseID(idStr)
if err != nil {
panic(err)
return nil
}
// Override the host's architecture name with the test's name
registry.SetHostArchName(TestArchName)
return registry
if id.Name != TestDistroNameBase {
return nil
}
if id.MinorVersion != -1 {
return nil
}
return newTestDistro(fmt.Sprint(id.MajorVersion))
}

View file

@ -0,0 +1,123 @@
package distrofactory
import (
"fmt"
"sort"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/fedora"
"github.com/osbuild/images/pkg/distro/rhel7"
"github.com/osbuild/images/pkg/distro/rhel8"
"github.com/osbuild/images/pkg/distro/rhel9"
"github.com/osbuild/images/pkg/distro/test_distro"
)
// FactoryFunc is a function that returns a distro.Distro for a given distro
// represented as a string. If the string does not represent a distro, that can
// be detected by the factory, it should return nil.
type FactoryFunc func(idStr string) distro.Distro
// Factory is a list of distro.Distro factories.
type Factory struct {
factories []FactoryFunc
// distro ID string aliases
aliases map[string]string
}
// getDistro returns the distro.Distro that matches the given distro ID. If no
// distro.Distro matches the given distro ID, it returns nil. If multiple distro
// factories match the given distro ID, it panics.
func (f *Factory) getDistro(name string) distro.Distro {
var match distro.Distro
for _, f := range f.factories {
if d := f(name); d != nil {
if match != nil {
panic(fmt.Sprintf("distro ID was matched by multiple distro factories: %v, %v", match, d))
}
match = d
}
}
return match
}
// GetDistro returns the distro.Distro that matches the given distro ID. If no
// distro.Distro matches the given distro ID, it tries to translate the given
// distro ID using the aliases map and tries again. If no distro.Distro matches
// the given distro ID, it returns nil. If multiple distro factories match the
// given distro ID, it panics.
func (f *Factory) GetDistro(name string) distro.Distro {
match := f.getDistro(name)
if alias, ok := f.aliases[name]; match == nil && ok {
match = f.getDistro(alias)
}
return match
}
// FromHost returns a distro.Distro instance, that is specific to the host.
// If the host distro is not supported, nil is returned.
func (f *Factory) FromHost() distro.Distro {
hostDistroName, _ := distro.GetHostDistroName()
return f.GetDistro(hostDistroName)
}
// RegisterAliases configures the factory with aliases for distro names.
// The provided aliases map has the following constraints:
// - An alias must not mask an existing distro.
// - An alias target must map to an existing distro.
func (f *Factory) RegisterAliases(aliases map[string]string) error {
var errors []string
for alias, target := range aliases {
var targetExists bool
for _, factory := range f.factories {
if factory(alias) != nil {
errors = append(errors, fmt.Sprintf("alias '%s' masks an existing distro", alias))
}
if factory(target) != nil {
targetExists = true
}
}
if !targetExists {
errors = append(errors, fmt.Sprintf("alias '%s' targets a non-existing distro '%s'", alias, target))
}
}
// NB: iterating over a map of aliases is not deterministic, so sort the
// errors to make the output deterministic
sort.Strings(errors)
if len(errors) > 0 {
return fmt.Errorf("invalid aliases: %q", errors)
}
f.aliases = aliases
return nil
}
// New returns a Factory of distro.Distro factories for the given distros.
func New(factories ...FactoryFunc) *Factory {
return &Factory{
factories: factories,
}
}
// NewDefault returns a Factory of distro.Distro factories for all supported
// distros.
func NewDefault() *Factory {
return New(
fedora.DistroFactory,
rhel7.DistroFactory,
rhel8.DistroFactory,
rhel9.DistroFactory,
)
}
// NewTestDefault returns a Factory of distro.Distro factory for the test_distro.
func NewTestDefault() *Factory {
return New(
test_distro.DistroFactory,
)
}

View file

@ -0,0 +1,70 @@
package distroidparser
import (
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/fedora"
"github.com/osbuild/images/pkg/distro/rhel7"
"github.com/osbuild/images/pkg/distro/rhel8"
"github.com/osbuild/images/pkg/distro/rhel9"
)
var DefaultParser = NewDefaultParser()
type ParserFunc func(idStr string) (*distro.ID, error)
// Parser is a list of distro-specific idStr parsers.
type Parser struct {
parsers []ParserFunc
}
func New(parsers ...ParserFunc) *Parser {
return &Parser{parsers: parsers}
}
// Parse returns the distro.ID that matches the given distro ID string. If no
// distro.ID matches the given distro ID string, it returns nil. If multiple
// distro.IDs match the given distro ID string, it panics.
// If no distro-specific parser matches the given distro ID string, it falls back
// to the default parser.
//
// The fact that the Parser returns a distro.ID does not mean that the distro is
// actually supported or implemented. This functionality is provided as an easy
// and consistent way to parse distro IDs, while allowing distro-specific parsers.
func (p *Parser) Parse(idStr string) (*distro.ID, error) {
var match *distro.ID
for _, f := range p.parsers {
if d, err := f(idStr); err == nil {
if match != nil {
panic("distro ID was matched by multiple parsers")
}
match = d
}
}
// Fall back to the default parser
if match == nil {
return distro.ParseID(idStr)
}
return match, nil
}
// Standardize returns the standardized distro ID string for the given distro ID
// string. If the given distro ID string is not valid, it returns an error.
func (p *Parser) Standardize(idStr string) (string, error) {
id, err := p.Parse(idStr)
if err != nil {
return "", err
}
return id.String(), nil
}
func NewDefaultParser() *Parser {
return New(
fedora.ParseID,
rhel7.ParseID,
rhel8.ParseID,
rhel9.ParseID,
)
}

View file

@ -1,147 +0,0 @@
package distroregistry
import (
"fmt"
"sort"
"strings"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distro/fedora"
"github.com/osbuild/images/pkg/distro/rhel7"
"github.com/osbuild/images/pkg/distro/rhel8"
"github.com/osbuild/images/pkg/distro/rhel9"
)
// When adding support for a new distribution, add it here.
// Note that this is a constant, do not write to this array.
var supportedDistros = []func() distro.Distro{
fedora.NewF37,
fedora.NewF38,
fedora.NewF39,
fedora.NewF40,
rhel7.New,
rhel8.New,
rhel8.NewRHEL84,
rhel8.NewRHEL85,
rhel8.NewRHEL86,
rhel8.NewRHEL87,
rhel8.NewRHEL88,
rhel8.NewRHEL89,
rhel8.NewRHEL810,
rhel8.NewCentos,
rhel9.New,
rhel9.NewRHEL90,
rhel9.NewRHEL91,
rhel9.NewRHEL92,
rhel9.NewRHEL93,
rhel9.NewRHEL94,
rhel9.NewCentOS9,
}
type Registry struct {
distros map[string]distro.Distro
hostDistro distro.Distro
hostArchName string
}
func New(hostDistro distro.Distro, distros ...distro.Distro) (*Registry, error) {
reg := &Registry{
distros: make(map[string]distro.Distro),
hostDistro: hostDistro,
hostArchName: arch.Current().String(),
}
for _, d := range distros {
name := d.Name()
if _, exists := reg.distros[name]; exists {
return nil, fmt.Errorf("New: passed two distros with the same name: %s", d.Name())
}
reg.distros[name] = d
}
return reg, nil
}
// NewDefault creates a Registry with all distributions supported by
// osbuild-composer. If you need to add a distribution here, see the
// supportedDistros variable.
func NewDefault() *Registry {
var distros []distro.Distro
var hostDistro distro.Distro
// First determine the name of the Host Distro
// If there was an error, then the hostDistroName will be an empty string
// and as a result, the hostDistro will have a nil value when calling New().
// Getting the host distro later using FromHost() will return nil as well.
hostDistroName, _, _, _ := common.GetHostDistroName()
for _, supportedDistro := range supportedDistros {
distro := supportedDistro()
if distro.Name() == hostDistroName {
hostDistro = supportedDistro()
}
distros = append(distros, distro)
}
registry, err := New(hostDistro, distros...)
if err != nil {
panic(fmt.Sprintf("two supported distros have the same name, this is a programming error: %v", err))
}
return registry
}
func (r *Registry) GetDistro(name string) distro.Distro {
d, ok := r.distros[name]
if !ok {
return nil
}
return d
}
// List returns the names of all distros in a Registry, sorted alphabetically.
func (r *Registry) List() []string {
list := []string{}
for _, d := range r.distros {
list = append(list, d.Name())
}
sort.Strings(list)
return list
}
func mangleHostDistroName(name string, isBeta, isStream bool) string {
hostDistroName := name
if strings.HasPrefix(hostDistroName, "rhel-8") {
hostDistroName = "rhel-8"
}
if isBeta {
hostDistroName += "-beta"
}
// override repository for centos stream, remove when CentOS 8 is EOL
if isStream && hostDistroName == "centos-8" {
hostDistroName = "centos-stream-8"
}
return hostDistroName
}
// FromHost returns a distro instance, that is specific to the host.
// Its name may differ from other supported distros, if the host version
// is e.g. a Beta or a Stream.
func (r *Registry) FromHost() distro.Distro {
return r.hostDistro
}
// HostArchName returns the host's arch name
func (r *Registry) HostArchName() string {
return r.hostArchName
}
// SetHostArchName can be used to override the host's arch name for testing
func (r *Registry) SetHostArchName(name string) {
r.hostArchName = name
}

View file

@ -0,0 +1,134 @@
package image
import (
"fmt"
"math/rand"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/customizations/users"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/runner"
)
type AnacondaContainerInstaller struct {
Base
Platform platform.Platform
ExtraBasePackages rpmmd.PackageSet
Users []users.User
Groups []users.Group
SquashfsCompression string
ISOLabelTempl string
Product string
Variant string
OSName string
Ref string
OSVersion string
Release string
ContainerSource container.SourceSpec
Filename string
AdditionalDracutModules []string
AdditionalAnacondaModules []string
AdditionalDrivers []string
FIPS bool
}
func NewAnacondaContainerInstaller(container container.SourceSpec, ref string) *AnacondaContainerInstaller {
return &AnacondaContainerInstaller{
Base: NewBase("container-installer"),
ContainerSource: container,
Ref: ref,
}
}
func (img *AnacondaContainerInstaller) InstantiateManifest(m *manifest.Manifest,
repos []rpmmd.RepoConfig,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuild(m, runner, repos, &manifest.BuildOptions{ContainerBuildable: true})
buildPipeline.Checkpoint()
anacondaPipeline := manifest.NewAnacondaInstaller(
manifest.AnacondaInstallerTypePayload,
buildPipeline,
img.Platform,
repos,
"kernel",
img.Product,
img.OSVersion,
)
// This is only built with ELN for now
anacondaPipeline.UseRHELLoraxTemplates = true
anacondaPipeline.ExtraPackages = img.ExtraBasePackages.Include
anacondaPipeline.ExcludePackages = img.ExtraBasePackages.Exclude
anacondaPipeline.ExtraRepos = img.ExtraBasePackages.Repositories
anacondaPipeline.Users = img.Users
anacondaPipeline.Groups = img.Groups
anacondaPipeline.Variant = img.Variant
anacondaPipeline.Biosdevname = (img.Platform.GetArch() == arch.ARCH_X86_64)
anacondaPipeline.Checkpoint()
anacondaPipeline.AdditionalDracutModules = img.AdditionalDracutModules
anacondaPipeline.AdditionalAnacondaModules = img.AdditionalAnacondaModules
if img.FIPS {
anacondaPipeline.AdditionalAnacondaModules = append(
anacondaPipeline.AdditionalAnacondaModules,
"org.fedoraproject.Anaconda.Modules.Security",
)
}
anacondaPipeline.AdditionalDrivers = img.AdditionalDrivers
// TODO: replace isoLabelTmpl with more high-level properties
isoLabel := fmt.Sprintf(img.ISOLabelTempl, img.Platform.GetArch())
rootfsImagePipeline := manifest.NewISORootfsImg(buildPipeline, anacondaPipeline)
rootfsImagePipeline.Size = 4 * common.GibiByte
bootTreePipeline := manifest.NewEFIBootTree(buildPipeline, img.Product, img.OSVersion)
bootTreePipeline.Platform = img.Platform
bootTreePipeline.UEFIVendor = img.Platform.GetUEFIVendor()
bootTreePipeline.ISOLabel = isoLabel
bootTreePipeline.KernelOpts = []string{fmt.Sprintf("inst.stage2=hd:LABEL=%s", isoLabel), fmt.Sprintf("inst.ks=hd:LABEL=%s:%s", isoLabel, kspath)}
if img.FIPS {
bootTreePipeline.KernelOpts = append(bootTreePipeline.KernelOpts, "fips=1")
}
// enable ISOLinux on x86_64 only
isoLinuxEnabled := img.Platform.GetArch() == arch.ARCH_X86_64
isoTreePipeline := manifest.NewAnacondaInstallerISOTree(buildPipeline, anacondaPipeline, rootfsImagePipeline, bootTreePipeline)
isoTreePipeline.PartitionTable = efiBootPartitionTable(rng)
isoTreePipeline.Release = img.Release
isoTreePipeline.OSName = img.OSName
isoTreePipeline.Users = img.Users
isoTreePipeline.Groups = img.Groups
isoTreePipeline.SquashfsCompression = img.SquashfsCompression
// For ostree installers, always put the kickstart file in the root of the ISO
isoTreePipeline.KSPath = kspath
isoTreePipeline.PayloadPath = "/container"
isoTreePipeline.ContainerSource = &img.ContainerSource
isoTreePipeline.ISOLinux = isoLinuxEnabled
if img.FIPS {
isoTreePipeline.KernelOpts = append(isoTreePipeline.KernelOpts, "fips=1")
}
isoPipeline := manifest.NewISO(buildPipeline, isoTreePipeline, isoLabel)
isoPipeline.SetFilename(img.Filename)
isoPipeline.ISOLinux = isoLinuxEnabled
artifact := isoPipeline.Export()
return artifact, nil
}

View file

@ -9,7 +9,6 @@ import (
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/rpmmd"
@ -49,14 +48,15 @@ func (img *AnacondaLiveInstaller) InstantiateManifest(m *manifest.Manifest,
buildPipeline := manifest.NewBuild(m, runner, repos, nil)
buildPipeline.Checkpoint()
livePipeline := manifest.NewAnacondaInstaller(m,
livePipeline := manifest.NewAnacondaInstaller(
manifest.AnacondaInstallerTypeLive,
buildPipeline,
img.Platform,
repos,
"kernel",
img.Product,
img.OSVersion)
img.OSVersion,
)
livePipeline.ExtraPackages = img.ExtraBasePackages.Include
livePipeline.ExcludePackages = img.ExtraBasePackages.Exclude
@ -66,28 +66,13 @@ func (img *AnacondaLiveInstaller) InstantiateManifest(m *manifest.Manifest,
livePipeline.Checkpoint()
rootfsPartitionTable := &disk.PartitionTable{
Size: 20 * common.MebiByte,
Partitions: []disk.Partition{
{
Start: 0,
Size: 20 * common.MebiByte,
Payload: &disk.Filesystem{
Type: "vfat",
Mountpoint: "/",
UUID: disk.NewVolIDFromRand(rng),
},
},
},
}
// TODO: replace isoLabelTmpl with more high-level properties
isoLabel := fmt.Sprintf(img.ISOLabelTempl, img.Platform.GetArch())
rootfsImagePipeline := manifest.NewISORootfsImg(buildPipeline, livePipeline)
rootfsImagePipeline.Size = 8 * common.GibiByte
bootTreePipeline := manifest.NewEFIBootTree(m, buildPipeline, img.Product, img.OSVersion)
bootTreePipeline := manifest.NewEFIBootTree(buildPipeline, img.Product, img.OSVersion)
bootTreePipeline.Platform = img.Platform
bootTreePipeline.UEFIVendor = img.Platform.GetUEFIVendor()
bootTreePipeline.ISOLabel = isoLabel
@ -107,7 +92,7 @@ func (img *AnacondaLiveInstaller) InstantiateManifest(m *manifest.Manifest,
isoLinuxEnabled := img.Platform.GetArch() == arch.ARCH_X86_64
isoTreePipeline := manifest.NewAnacondaInstallerISOTree(buildPipeline, livePipeline, rootfsImagePipeline, bootTreePipeline)
isoTreePipeline.PartitionTable = rootfsPartitionTable
isoTreePipeline.PartitionTable = efiBootPartitionTable(rng)
isoTreePipeline.Release = img.Release
isoTreePipeline.OSName = img.OSName

View file

@ -8,7 +8,6 @@ import (
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/customizations/users"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/platform"
@ -31,6 +30,7 @@ type AnacondaOSTreeInstaller struct {
OSName string
OSVersion string
Release string
Remote string
Commit ostree.SourceSpec
@ -56,14 +56,15 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest,
buildPipeline := manifest.NewBuild(m, runner, repos, nil)
buildPipeline.Checkpoint()
anacondaPipeline := manifest.NewAnacondaInstaller(m,
anacondaPipeline := manifest.NewAnacondaInstaller(
manifest.AnacondaInstallerTypePayload,
buildPipeline,
img.Platform,
repos,
"kernel",
img.Product,
img.OSVersion)
img.OSVersion,
)
anacondaPipeline.ExtraPackages = img.ExtraBasePackages.Include
anacondaPipeline.ExcludePackages = img.ExtraBasePackages.Exclude
anacondaPipeline.ExtraRepos = img.ExtraBasePackages.Repositories
@ -82,28 +83,13 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest,
}
anacondaPipeline.AdditionalDrivers = img.AdditionalDrivers
rootfsPartitionTable := &disk.PartitionTable{
Size: 20 * common.MebiByte,
Partitions: []disk.Partition{
{
Start: 0,
Size: 20 * common.MebiByte,
Payload: &disk.Filesystem{
Type: "vfat",
Mountpoint: "/",
UUID: disk.NewVolIDFromRand(rng),
},
},
},
}
// TODO: replace isoLabelTmpl with more high-level properties
isoLabel := fmt.Sprintf(img.ISOLabelTempl, img.Platform.GetArch())
rootfsImagePipeline := manifest.NewISORootfsImg(buildPipeline, anacondaPipeline)
rootfsImagePipeline.Size = 4 * common.GibiByte
bootTreePipeline := manifest.NewEFIBootTree(m, buildPipeline, img.Product, img.OSVersion)
bootTreePipeline := manifest.NewEFIBootTree(buildPipeline, img.Product, img.OSVersion)
bootTreePipeline.Platform = img.Platform
bootTreePipeline.UEFIVendor = img.Platform.GetUEFIVendor()
bootTreePipeline.ISOLabel = isoLabel
@ -116,9 +102,10 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest,
isoLinuxEnabled := img.Platform.GetArch() == arch.ARCH_X86_64
isoTreePipeline := manifest.NewAnacondaInstallerISOTree(buildPipeline, anacondaPipeline, rootfsImagePipeline, bootTreePipeline)
isoTreePipeline.PartitionTable = rootfsPartitionTable
isoTreePipeline.PartitionTable = efiBootPartitionTable(rng)
isoTreePipeline.Release = img.Release
isoTreePipeline.OSName = img.OSName
isoTreePipeline.Remote = img.Remote
isoTreePipeline.Users = img.Users
isoTreePipeline.Groups = img.Groups

View file

@ -20,6 +20,24 @@ import (
const kspath = "/osbuild.ks"
func efiBootPartitionTable(rng *rand.Rand) *disk.PartitionTable {
var efibootImageSize uint64 = 20 * common.MebiByte
return &disk.PartitionTable{
Size: efibootImageSize,
Partitions: []disk.Partition{
{
Start: 0,
Size: efibootImageSize,
Payload: &disk.Filesystem{
Type: "vfat",
Mountpoint: "/",
UUID: disk.NewVolIDFromRand(rng),
},
},
},
}
}
type AnacondaTarInstaller struct {
Base
Platform platform.Platform
@ -66,14 +84,15 @@ func (img *AnacondaTarInstaller) InstantiateManifest(m *manifest.Manifest,
buildPipeline := manifest.NewBuild(m, runner, repos, nil)
buildPipeline.Checkpoint()
anacondaPipeline := manifest.NewAnacondaInstaller(m,
anacondaPipeline := manifest.NewAnacondaInstaller(
manifest.AnacondaInstallerTypePayload,
buildPipeline,
img.Platform,
repos,
"kernel",
img.Product,
img.OSVersion)
img.OSVersion,
)
anacondaPipeline.ExtraPackages = img.ExtraBasePackages.Include
anacondaPipeline.ExcludePackages = img.ExtraBasePackages.Exclude
@ -101,28 +120,13 @@ func (img *AnacondaTarInstaller) InstantiateManifest(m *manifest.Manifest,
anacondaPipeline.Checkpoint()
rootfsPartitionTable := &disk.PartitionTable{
Size: 20 * common.MebiByte,
Partitions: []disk.Partition{
{
Start: 0,
Size: 20 * common.MebiByte,
Payload: &disk.Filesystem{
Type: "vfat",
Mountpoint: "/",
UUID: disk.NewVolIDFromRand(rng),
},
},
},
}
// TODO: replace isoLabelTmpl with more high-level properties
isoLabel := fmt.Sprintf(img.ISOLabelTempl, img.Platform.GetArch())
rootfsImagePipeline := manifest.NewISORootfsImg(buildPipeline, anacondaPipeline)
rootfsImagePipeline.Size = 4 * common.GibiByte
rootfsImagePipeline.Size = 5 * common.GibiByte
bootTreePipeline := manifest.NewEFIBootTree(m, buildPipeline, img.Product, img.OSVersion)
bootTreePipeline := manifest.NewEFIBootTree(buildPipeline, img.Product, img.OSVersion)
bootTreePipeline.Platform = img.Platform
bootTreePipeline.UEFIVendor = img.Platform.GetUEFIVendor()
bootTreePipeline.ISOLabel = isoLabel
@ -137,7 +141,7 @@ func (img *AnacondaTarInstaller) InstantiateManifest(m *manifest.Manifest,
kernelOpts = append(kernelOpts, img.AdditionalKernelOpts...)
bootTreePipeline.KernelOpts = kernelOpts
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline := manifest.NewOS(buildPipeline, img.Platform, repos)
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload
@ -146,7 +150,7 @@ func (img *AnacondaTarInstaller) InstantiateManifest(m *manifest.Manifest,
isoLinuxEnabled := img.Platform.GetArch() == arch.ARCH_X86_64
isoTreePipeline := manifest.NewAnacondaInstallerISOTree(buildPipeline, anacondaPipeline, rootfsImagePipeline, bootTreePipeline)
isoTreePipeline.PartitionTable = rootfsPartitionTable
isoTreePipeline.PartitionTable = efiBootPartitionTable(rng)
isoTreePipeline.Release = img.Release
isoTreePipeline.OSName = img.OSName
isoTreePipeline.Users = img.Users

View file

@ -34,7 +34,7 @@ func (img *Archive) InstantiateManifest(m *manifest.Manifest,
buildPipeline := manifest.NewBuild(m, runner, repos, nil)
buildPipeline.Checkpoint()
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline := manifest.NewOS(buildPipeline, img.Platform, repos)
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload

View file

@ -0,0 +1,72 @@
package image
import (
"fmt"
"math/rand"
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/runner"
)
type BootcDiskImage struct {
*OSTreeDiskImage
}
func NewBootcDiskImage(container container.SourceSpec) *BootcDiskImage {
// XXX: hardcoded for now
ref := "ostree/1/1/0"
return &BootcDiskImage{
&OSTreeDiskImage{
Base: NewBase("bootc-raw-image"),
ContainerSource: &container,
Ref: ref,
OSName: "default",
},
}
}
func (img *BootcDiskImage) InstantiateManifestFromContainers(m *manifest.Manifest,
containers []container.SourceSpec,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuildFromContainer(m, runner, containers, &manifest.BuildOptions{ContainerBuildable: true})
buildPipeline.Checkpoint()
// don't support compressing non-raw images
imgFormat := img.Platform.GetImageFormat()
if imgFormat == platform.FORMAT_UNSET {
// treat unset as raw for this check
imgFormat = platform.FORMAT_RAW
}
if imgFormat != platform.FORMAT_RAW && img.Compression != "" {
panic(fmt.Sprintf("no compression is allowed with %q format for %q", imgFormat, img.name))
}
opts := &baseRawOstreeImageOpts{useBootupd: true}
baseImage := baseRawOstreeImage(img.OSTreeDiskImage, buildPipeline, opts)
switch imgFormat {
case platform.FORMAT_QCOW2:
// qcow2 runs without a build pipeline directly from "bib"
qcow2Pipeline := manifest.NewQCOW2(nil, baseImage)
qcow2Pipeline.Compat = img.Platform.GetQCOW2Compat()
qcow2Pipeline.SetFilename(img.Filename)
return qcow2Pipeline.Export(), nil
}
switch img.Compression {
case "xz":
compressedImage := manifest.NewXZ(buildPipeline, baseImage)
compressedImage.SetFilename(img.Filename)
return compressedImage.Export(), nil
case "":
baseImage.SetFilename(img.Filename)
return baseImage.Export(), nil
default:
panic(fmt.Sprintf("unsupported compression type %q on %q", img.Compression, img.name))
}
}

View file

@ -34,7 +34,7 @@ func (img *BaseContainer) InstantiateManifest(m *manifest.Manifest,
buildPipeline := manifest.NewBuild(m, runner, repos, nil)
buildPipeline.Checkpoint()
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline := manifest.NewOS(buildPipeline, img.Platform, repos)
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload

View file

@ -52,7 +52,7 @@ func (img *DiskImage) InstantiateManifest(m *manifest.Manifest,
buildPipeline := manifest.NewBuild(m, runner, repos, nil)
buildPipeline.Checkpoint()
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline := manifest.NewOS(buildPipeline, img.Platform, repos)
osPipeline.PartitionTable = img.PartitionTable
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment

View file

@ -50,7 +50,7 @@ func (img *OSTreeArchive) InstantiateManifest(m *manifest.Manifest,
buildPipeline := manifest.NewBuild(m, runner, repos, nil)
buildPipeline.Checkpoint()
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline := manifest.NewOS(buildPipeline, img.Platform, repos)
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload

View file

@ -47,7 +47,7 @@ func (img *OSTreeContainer) InstantiateManifest(m *manifest.Manifest,
buildPipeline := manifest.NewBuild(m, runner, repos, nil)
buildPipeline.Checkpoint()
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline := manifest.NewOS(buildPipeline, img.Platform, repos)
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload
@ -60,13 +60,14 @@ func (img *OSTreeContainer) InstantiateManifest(m *manifest.Manifest,
nginxConfigPath := "/etc/nginx.conf"
listenPort := "8080"
serverPipeline := manifest.NewOSTreeCommitServer(m,
serverPipeline := manifest.NewOSTreeCommitServer(
buildPipeline,
img.Platform,
repos,
commitPipeline,
nginxConfigPath,
listenPort)
listenPort,
)
serverPipeline.Language = img.ContainerLanguage
containerPipeline := manifest.NewOCIContainer(buildPipeline, serverPipeline)

View file

@ -74,13 +74,21 @@ func NewOSTreeDiskImageFromContainer(container container.SourceSpec, ref string)
}
}
func baseRawOstreeImage(img *OSTreeDiskImage, m *manifest.Manifest, buildPipeline *manifest.Build) *manifest.RawOSTreeImage {
type baseRawOstreeImageOpts struct {
useBootupd bool
}
func baseRawOstreeImage(img *OSTreeDiskImage, buildPipeline manifest.Build, opts *baseRawOstreeImageOpts) *manifest.RawOSTreeImage {
if opts == nil {
opts = &baseRawOstreeImageOpts{}
}
var osPipeline *manifest.OSTreeDeployment
switch {
case img.CommitSource != nil:
osPipeline = manifest.NewOSTreeCommitDeployment(buildPipeline, m, img.CommitSource, img.OSName, img.Platform)
osPipeline = manifest.NewOSTreeCommitDeployment(buildPipeline, img.CommitSource, img.OSName, img.Platform)
case img.ContainerSource != nil:
osPipeline = manifest.NewOSTreeContainerDeployment(buildPipeline, m, img.ContainerSource, img.Ref, img.OSName, img.Platform)
osPipeline = manifest.NewOSTreeContainerDeployment(buildPipeline, img.ContainerSource, img.Ref, img.OSName, img.Platform)
default:
panic("no content source defined for ostree image")
}
@ -98,11 +106,13 @@ func baseRawOstreeImage(img *OSTreeDiskImage, m *manifest.Manifest, buildPipelin
osPipeline.FIPS = img.FIPS
osPipeline.IgnitionPlatform = img.IgnitionPlatform
osPipeline.LockRoot = img.LockRoot
osPipeline.UseBootupd = opts.useBootupd
// other image types (e.g. live) pass the workload to the pipeline.
osPipeline.EnabledServices = img.Workload.GetServices()
osPipeline.DisabledServices = img.Workload.GetDisabledServices()
if img.Workload != nil {
osPipeline.EnabledServices = img.Workload.GetServices()
osPipeline.DisabledServices = img.Workload.GetDisabledServices()
}
return manifest.NewRawOStreeImage(buildPipeline, osPipeline, img.Platform)
}
@ -126,7 +136,7 @@ func (img *OSTreeDiskImage) InstantiateManifest(m *manifest.Manifest,
panic(fmt.Sprintf("no compression is allowed with %q format for %q", imgFormat, img.name))
}
baseImage := baseRawOstreeImage(img, m, buildPipeline)
baseImage := baseRawOstreeImage(img, buildPipeline, nil)
switch img.Platform.GetImageFormat() {
case platform.FORMAT_VMDK:
vmdkPipeline := manifest.NewVMDK(buildPipeline, baseImage)

View file

@ -4,14 +4,12 @@ import (
"fmt"
"math/rand"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/internal/environment"
"github.com/osbuild/images/internal/workload"
"github.com/osbuild/images/pkg/arch"
"github.com/osbuild/images/pkg/artifact"
"github.com/osbuild/images/pkg/customizations/fdo"
"github.com/osbuild/images/pkg/customizations/ignition"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/platform"
"github.com/osbuild/images/pkg/rpmmd"
@ -79,16 +77,17 @@ func (img *OSTreeSimplifiedInstaller) InstantiateManifest(m *manifest.Manifest,
imageFilename := "image.raw.xz"
// image in simplified installer is always compressed
compressedImage := manifest.NewXZ(buildPipeline, baseRawOstreeImage(img.rawImage, m, buildPipeline))
compressedImage := manifest.NewXZ(buildPipeline, baseRawOstreeImage(img.rawImage, buildPipeline, nil))
compressedImage.SetFilename(imageFilename)
coiPipeline := manifest.NewCoreOSInstaller(m,
coiPipeline := manifest.NewCoreOSInstaller(
buildPipeline,
img.Platform,
repos,
"kernel",
img.Product,
img.OSVersion)
img.OSVersion,
)
coiPipeline.ExtraPackages = img.ExtraBasePackages.Include
coiPipeline.ExcludePackages = img.ExtraBasePackages.Exclude
coiPipeline.ExtraRepos = img.ExtraBasePackages.Repositories
@ -100,7 +99,7 @@ func (img *OSTreeSimplifiedInstaller) InstantiateManifest(m *manifest.Manifest,
isoLabel := fmt.Sprintf(img.ISOLabelTempl, img.Platform.GetArch())
bootTreePipeline := manifest.NewEFIBootTree(m, buildPipeline, img.Product, img.OSVersion)
bootTreePipeline := manifest.NewEFIBootTree(buildPipeline, img.Product, img.OSVersion)
bootTreePipeline.Platform = img.Platform
bootTreePipeline.UEFIVendor = img.Platform.GetUEFIVendor()
bootTreePipeline.ISOLabel = isoLabel
@ -134,27 +133,12 @@ func (img *OSTreeSimplifiedInstaller) InstantiateManifest(m *manifest.Manifest,
bootTreePipeline.KernelOpts = kernelOpts
rootfsPartitionTable := &disk.PartitionTable{
Size: 20 * common.MebiByte,
Partitions: []disk.Partition{
{
Start: 0,
Size: 20 * common.MebiByte,
Payload: &disk.Filesystem{
Type: "vfat",
Mountpoint: "/",
UUID: disk.NewVolIDFromRand(rng),
},
},
},
}
// enable ISOLinux on x86_64 only
isoLinuxEnabled := img.Platform.GetArch() == arch.ARCH_X86_64
isoTreePipeline := manifest.NewCoreOSISOTree(buildPipeline, compressedImage, coiPipeline, bootTreePipeline)
isoTreePipeline.KernelOpts = kernelOpts
isoTreePipeline.PartitionTable = rootfsPartitionTable
isoTreePipeline.PartitionTable = efiBootPartitionTable(rng)
isoTreePipeline.OSName = img.OSName
isoTreePipeline.PayloadPath = fmt.Sprintf("/%s", imageFilename)
isoTreePipeline.ISOLinux = isoLinuxEnabled

View file

@ -72,11 +72,13 @@ type AnacondaInstaller struct {
AdditionalDrivers []string
Files []*fsnode.File
// Temporary
UseRHELLoraxTemplates bool
}
func NewAnacondaInstaller(m *Manifest,
installerType AnacondaInstallerType,
buildPipeline *Build,
func NewAnacondaInstaller(installerType AnacondaInstallerType,
buildPipeline Build,
platform platform.Platform,
repos []rpmmd.RepoConfig,
kernelName,
@ -84,7 +86,7 @@ func NewAnacondaInstaller(m *Manifest,
version string) *AnacondaInstaller {
name := "anaconda-tree"
p := &AnacondaInstaller{
Base: NewBase(m, name, buildPipeline),
Base: NewBase(name, buildPipeline),
Type: installerType,
platform: platform,
repos: filterRepos(repos, name),
@ -93,7 +95,6 @@ func NewAnacondaInstaller(m *Manifest,
version: version,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
@ -135,8 +136,18 @@ func (p *AnacondaInstaller) getBuildPackages(Distro) []string {
packages := p.anacondaBootPackageSet()
packages = append(packages,
"rpm",
"lorax-templates-generic",
)
if p.UseRHELLoraxTemplates {
packages = append(packages,
"lorax-templates-rhel",
)
} else {
packages = append(packages,
"lorax-templates-generic",
)
}
return packages
}
@ -269,9 +280,17 @@ func (p *AnacondaInstaller) serialize() osbuild.Pipeline {
}
if p.Type == AnacondaInstallerTypePayload {
var LoraxPath string
if p.UseRHELLoraxTemplates {
LoraxPath = "80-rhel/runtime-postinstall.tmpl"
} else {
LoraxPath = "99-generic/runtime-postinstall.tmpl"
}
pipeline.AddStage(osbuild.NewAnacondaStage(osbuild.NewAnacondaStageOptions(p.AdditionalAnacondaModules)))
pipeline.AddStage(osbuild.NewLoraxScriptStage(&osbuild.LoraxScriptStageOptions{
Path: "99-generic/runtime-postinstall.tmpl",
Path: LoraxPath,
BaseArch: p.platform.GetArch().String(),
}))
}

View file

@ -5,6 +5,7 @@ import (
"path"
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/customizations/fsnode"
"github.com/osbuild/images/pkg/customizations/users"
"github.com/osbuild/images/pkg/disk"
"github.com/osbuild/images/pkg/osbuild"
@ -22,6 +23,7 @@ type AnacondaInstallerISOTree struct {
// TODO: review optional and mandatory fields and their meaning
OSName string
Release string
Remote string
Users []users.User
Groups []users.Group
@ -37,7 +39,7 @@ type AnacondaInstallerISOTree struct {
// Anaconda pipeline.
KSPath string
// The path where the payload (tarball or ostree repo) will be stored.
// The path where the payload (tarball, ostree repo, or container) will be stored.
PayloadPath string
isoLabel string
@ -48,14 +50,18 @@ type AnacondaInstallerISOTree struct {
OSTreeCommitSource *ostree.SourceSpec
ostreeCommitSpec *ostree.CommitSpec
ContainerSource *container.SourceSpec
containerSpec *container.Spec
KernelOpts []string
// Enable ISOLinux stage
ISOLinux bool
Files []*fsnode.File
}
func NewAnacondaInstallerISOTree(buildPipeline *Build, anacondaPipeline *AnacondaInstaller, rootfsPipeline *ISORootfsImg, bootTreePipeline *EFIBootTree) *AnacondaInstallerISOTree {
func NewAnacondaInstallerISOTree(buildPipeline Build, anacondaPipeline *AnacondaInstaller, rootfsPipeline *ISORootfsImg, bootTreePipeline *EFIBootTree) *AnacondaInstallerISOTree {
// the three pipelines should all belong to the same manifest
if anacondaPipeline.Manifest() != rootfsPipeline.Manifest() ||
@ -63,14 +69,13 @@ func NewAnacondaInstallerISOTree(buildPipeline *Build, anacondaPipeline *Anacond
panic("pipelines from different manifests")
}
p := &AnacondaInstallerISOTree{
Base: NewBase(anacondaPipeline.Manifest(), "bootiso-tree", buildPipeline),
Base: NewBase("bootiso-tree", buildPipeline),
anacondaPipeline: anacondaPipeline,
rootfsPipeline: rootfsPipeline,
bootTreePipeline: bootTreePipeline,
isoLabel: bootTreePipeline.ISOLabel,
}
buildPipeline.addDependent(p)
anacondaPipeline.Manifest().addPipeline(p)
return p
}
@ -91,6 +96,32 @@ func (p *AnacondaInstallerISOTree) getOSTreeCommits() []ostree.CommitSpec {
return []ostree.CommitSpec{*p.ostreeCommitSpec}
}
func (p *AnacondaInstallerISOTree) getContainerSpecs() []container.Spec {
if p.containerSpec == nil {
return []container.Spec{}
}
return []container.Spec{*p.containerSpec}
}
func (p *AnacondaInstallerISOTree) getContainerSources() []container.SourceSpec {
if p.ContainerSource == nil {
return []container.SourceSpec{}
}
return []container.SourceSpec{
*p.ContainerSource,
}
}
func (p *AnacondaInstallerISOTree) getInline() []string {
inlineData := []string{}
// inline data for custom files
for _, file := range p.Files {
inlineData = append(inlineData, string(file.Data()))
}
return inlineData
}
func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string {
packages := []string{
"squashfs-tools",
@ -100,6 +131,10 @@ func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string {
packages = append(packages, "rpm-ostree")
}
if p.ContainerSource != nil {
packages = append(packages, "skopeo")
}
if p.OSPipeline != nil {
packages = append(packages, "tar")
}
@ -107,33 +142,57 @@ func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string {
return packages
}
func (p *AnacondaInstallerISOTree) serializeStart(_ []rpmmd.PackageSpec, _ []container.Spec, commits []ostree.CommitSpec) {
if len(commits) == 0 {
// nothing to do
return
func (p *AnacondaInstallerISOTree) serializeStart(_ []rpmmd.PackageSpec, containers []container.Spec, commits []ostree.CommitSpec) {
if p.ostreeCommitSpec != nil || p.containerSpec != nil {
panic("double call to serializeStart()")
}
if len(commits) > 1 {
panic("pipeline supports at most one ostree commit")
}
p.ostreeCommitSpec = &commits[0]
if len(containers) > 1 {
panic("pipeline supports at most one container")
}
if len(commits) > 0 {
p.ostreeCommitSpec = &commits[0]
}
if len(containers) > 0 {
p.containerSpec = &containers[0]
}
}
func (p *AnacondaInstallerISOTree) serializeEnd() {
p.ostreeCommitSpec = nil
p.containerSpec = nil
}
func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline {
// If the anaconda pipeline is a payload then we need one of two payload types
// If the anaconda pipeline is a payload then we need one of three payload types
if p.anacondaPipeline.Type == AnacondaInstallerTypePayload {
if p.ostreeCommitSpec == nil && p.OSPipeline == nil {
panic("missing ostree or ospipeline parameters in ISO tree pipeline")
count := 0
if p.ostreeCommitSpec != nil {
count++
}
// But not both payloads
if p.ostreeCommitSpec != nil && p.OSPipeline != nil {
panic("got both ostree and ospipeline parameters in ISO tree pipeline")
if p.containerSpec != nil {
count++
}
if p.OSPipeline != nil {
count++
}
if count == 0 {
panic("missing ostree, container, or ospipeline parameters in ISO tree pipeline")
}
// But not more than one payloads
if count > 1 {
panic("got multiple payloads in ISO tree pipeline")
}
}
@ -278,6 +337,7 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline {
p.Groups,
makeISORootPath(p.PayloadPath),
p.ostreeCommitSpec.Ref,
p.Remote,
p.OSName)
if err != nil {
@ -287,6 +347,69 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline {
pipeline.AddStage(osbuild.NewKickstartStage(kickstartOptions))
}
if p.containerSpec != nil {
images := osbuild.NewContainersInputForSources([]container.Spec{*p.containerSpec})
pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: []osbuild.MkdirStagePath{
{
Path: p.PayloadPath,
},
},
}))
// copy the container in
pipeline.AddStage(osbuild.NewSkopeoStageWithOCI(
p.PayloadPath,
images,
nil))
// do what we can in our kickstart stage
kickstartOptions, err := osbuild.NewKickstartStageOptionsWithOSTreeContainer(
"/osbuild-base.ks",
p.Users,
p.Groups,
path.Join("/run/install/repo", p.PayloadPath),
"oci",
"",
"")
if err != nil {
panic("failed to create kickstartstage options")
}
pipeline.AddStage(osbuild.NewKickstartStage(kickstartOptions))
// and what we can't do in a separate kickstart that we include
kickstartFile, err := fsnode.NewFile(p.KSPath, nil, nil, nil, []byte(`
%include /run/install/repo/osbuild-base.ks
rootpw --lock
lang en_US.UTF-8
keyboard us
timezone UTC
clearpart --all
reqpart --add-boot
part swap --fstype=swap --size=1024
part / --fstype=ext4 --grow
reboot --eject
`))
if err != nil {
panic(err)
}
p.Files = []*fsnode.File{kickstartFile}
pipeline.AddStages(osbuild.GenFileNodesStages(p.Files)...)
}
if p.OSPipeline != nil {
// Create the payload tarball
pipeline.AddStage(osbuild.NewTarStage(&osbuild.TarStageOptions{Filename: p.PayloadPath}, p.OSPipeline.name))

View file

@ -15,7 +15,16 @@ import (
// is not predictable nor reproducible. For the purposes of building the
// build pipeline, we do use the build host's filesystem, this means we should
// make minimal assumptions about what's available there.
type Build struct {
type Build interface {
Name() string
Checkpoint()
Manifest() *Manifest
addDependent(dep Pipeline)
}
type BuildrootFromPackages struct {
Base
runner runner.Runner
@ -34,29 +43,33 @@ type BuildOptions struct {
// NewBuild creates a new build pipeline from the repositories in repos
// and the specified packages.
func NewBuild(m *Manifest, runner runner.Runner, repos []rpmmd.RepoConfig, opts *BuildOptions) *Build {
func NewBuild(m *Manifest, runner runner.Runner, repos []rpmmd.RepoConfig, opts *BuildOptions) Build {
if opts == nil {
opts = &BuildOptions{}
}
name := "build"
pipeline := &Build{
Base: NewBase(m, name, nil),
runner: runner,
dependents: make([]Pipeline, 0),
repos: filterRepos(repos, name),
pipeline := &BuildrootFromPackages{
Base: NewBase(name, nil),
runner: runner,
dependents: make([]Pipeline, 0),
repos: filterRepos(repos, name),
containerBuildable: opts.ContainerBuildable,
}
m.addPipeline(pipeline)
return pipeline
}
func (p *Build) addDependent(dep Pipeline) {
func (p *BuildrootFromPackages) addDependent(dep Pipeline) {
p.dependents = append(p.dependents, dep)
man := p.Manifest()
if man == nil {
panic("cannot add build dependent without a manifest")
}
man.addPipeline(dep)
}
func (p *Build) getPackageSetChain(distro Distro) []rpmmd.PackageSet {
func (p *BuildrootFromPackages) getPackageSetChain(distro Distro) []rpmmd.PackageSet {
// TODO: make the /usr/bin/cp dependency conditional
// TODO: make the /usr/bin/xz dependency conditional
packages := []string{
@ -80,25 +93,25 @@ func (p *Build) getPackageSetChain(distro Distro) []rpmmd.PackageSet {
}
}
func (p *Build) getPackageSpecs() []rpmmd.PackageSpec {
func (p *BuildrootFromPackages) getPackageSpecs() []rpmmd.PackageSpec {
return p.packageSpecs
}
func (p *Build) serializeStart(packages []rpmmd.PackageSpec, _ []container.Spec, _ []ostree.CommitSpec) {
func (p *BuildrootFromPackages) serializeStart(packages []rpmmd.PackageSpec, _ []container.Spec, _ []ostree.CommitSpec) {
if len(p.packageSpecs) > 0 {
panic("double call to serializeStart()")
}
p.packageSpecs = packages
}
func (p *Build) serializeEnd() {
func (p *BuildrootFromPackages) serializeEnd() {
if len(p.packageSpecs) == 0 {
panic("serializeEnd() call when serialization not in progress")
}
p.packageSpecs = nil
}
func (p *Build) serialize() osbuild.Pipeline {
func (p *BuildrootFromPackages) serialize() osbuild.Pipeline {
if len(p.packageSpecs) == 0 {
panic("serialization not started")
}
@ -117,7 +130,7 @@ func (p *Build) serialize() osbuild.Pipeline {
// Returns a map of paths to labels for the SELinux stage based on specific
// packages found in the pipeline.
func (p *Build) getSELinuxLabels() map[string]string {
func (p *BuildrootFromPackages) getSELinuxLabels() map[string]string {
labels := make(map[string]string)
for _, pkg := range p.getPackageSpecs() {
switch pkg.Name {
@ -133,3 +146,103 @@ func (p *Build) getSELinuxLabels() map[string]string {
}
return labels
}
type BuildrootFromContainer struct {
Base
runner runner.Runner
dependents []Pipeline
containers []container.SourceSpec
containerSpecs []container.Spec
containerBuildable bool
}
// NewBuildFromContainer creates a new build pipeline from the given
// containers specs
func NewBuildFromContainer(m *Manifest, runner runner.Runner, containerSources []container.SourceSpec, opts *BuildOptions) Build {
if opts == nil {
opts = &BuildOptions{}
}
name := "build"
pipeline := &BuildrootFromContainer{
Base: NewBase(name, nil),
runner: runner,
dependents: make([]Pipeline, 0),
containers: containerSources,
containerBuildable: opts.ContainerBuildable,
}
m.addPipeline(pipeline)
return pipeline
}
func (p *BuildrootFromContainer) addDependent(dep Pipeline) {
p.dependents = append(p.dependents, dep)
man := p.Manifest()
if man == nil {
panic("cannot add build dependent without a manifest")
}
man.addPipeline(dep)
}
func (p *BuildrootFromContainer) getContainerSources() []container.SourceSpec {
return p.containers
}
func (p *BuildrootFromContainer) getContainerSpecs() []container.Spec {
return p.containerSpecs
}
func (p *BuildrootFromContainer) serializeStart(_ []rpmmd.PackageSpec, containerSpecs []container.Spec, _ []ostree.CommitSpec) {
if len(p.containerSpecs) > 0 {
panic("double call to serializeStart()")
}
p.containerSpecs = containerSpecs
}
func (p *BuildrootFromContainer) serializeEnd() {
if len(p.containerSpecs) == 0 {
panic("serializeEnd() call when serialization not in progress")
}
p.containerSpecs = nil
}
func (p *BuildrootFromContainer) getSELinuxLabels() map[string]string {
labels := map[string]string{
"/usr/bin/ostree": "system_u:object_r:install_exec_t:s0",
}
if p.containerBuildable {
labels["/usr/bin/mount"] = "system_u:object_r:install_exec_t:s0"
labels["/usr/bin/umount"] = "system_u:object_r:install_exec_t:s0"
}
return labels
}
func (p *BuildrootFromContainer) serialize() osbuild.Pipeline {
if len(p.containerSpecs) == 0 {
panic("serialization not started")
}
pipeline := p.Base.serialize()
pipeline.Runner = p.runner.String()
inputs := osbuild.NewContainersInputForSources(p.containerSpecs)
options := &osbuild.ContainerDeployOptions{
Exclude: []string{"/sysroot"},
}
stage, err := osbuild.NewContainerDeployStage(inputs, options)
if err != nil {
panic(err)
}
pipeline.AddStage(stage)
pipeline.AddStage(osbuild.NewSELinuxStage(
&osbuild.SELinuxStageOptions{
FileContexts: "etc/selinux/targeted/contexts/files/file_contexts",
Labels: p.getSELinuxLabels(),
},
))
return pipeline
}

View file

@ -36,7 +36,7 @@ type CoreOSISOTree struct {
}
func NewCoreOSISOTree(
buildPipeline *Build,
buildPipeline Build,
payloadPipeline *XZ,
coiPipeline *CoreOSInstaller,
bootTreePipeline *EFIBootTree) *CoreOSISOTree {
@ -48,14 +48,13 @@ func NewCoreOSISOTree(
}
p := &CoreOSISOTree{
Base: NewBase(coiPipeline.Manifest(), "bootiso-tree", buildPipeline),
Base: NewBase("bootiso-tree", buildPipeline),
payloadPipeline: payloadPipeline,
coiPipeline: coiPipeline,
bootTreePipeline: bootTreePipeline,
isoLabel: bootTreePipeline.ISOLabel,
}
buildPipeline.addDependent(p)
coiPipeline.Manifest().addPipeline(p)
return p
}

View file

@ -16,14 +16,13 @@ type OSTreeCommit struct {
// NewOSTreeCommit creates a new OSTree commit pipeline. The
// treePipeline is the tree representing the content of the commit.
// ref is the ref to create the commit under.
func NewOSTreeCommit(buildPipeline *Build, treePipeline *OS, ref string) *OSTreeCommit {
func NewOSTreeCommit(buildPipeline Build, treePipeline *OS, ref string) *OSTreeCommit {
p := &OSTreeCommit{
Base: NewBase(treePipeline.Manifest(), "ostree-commit", buildPipeline),
Base: NewBase("ostree-commit", buildPipeline),
treePipeline: treePipeline,
ref: ref,
}
buildPipeline.addDependent(p)
treePipeline.Manifest().addPipeline(p)
return p
}

View file

@ -36,8 +36,7 @@ type OSTreeCommitServer struct {
// is a pipeline producing an ostree commit to be served. nginxConfigPath
// is the path to the main nginx config file and listenPort is the port
// nginx will be listening on.
func NewOSTreeCommitServer(m *Manifest,
buildPipeline *Build,
func NewOSTreeCommitServer(buildPipeline Build,
platform platform.Platform,
repos []rpmmd.RepoConfig,
commitPipeline *OSTreeCommit,
@ -45,7 +44,7 @@ func NewOSTreeCommitServer(m *Manifest,
listenPort string) *OSTreeCommitServer {
name := "container-tree"
p := &OSTreeCommitServer{
Base: NewBase(m, name, buildPipeline),
Base: NewBase(name, buildPipeline),
platform: platform,
repos: filterRepos(repos, name),
commitPipeline: commitPipeline,
@ -53,11 +52,7 @@ func NewOSTreeCommitServer(m *Manifest,
listenPort: listenPort,
Language: "en_US",
}
if commitPipeline.Base.manifest != m {
panic("commit pipeline from different manifest")
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}

View file

@ -47,8 +47,7 @@ type CoreOSInstaller struct {
}
// NewCoreOSInstaller creates an CoreOS installer pipeline object.
func NewCoreOSInstaller(m *Manifest,
buildPipeline *Build,
func NewCoreOSInstaller(buildPipeline Build,
platform platform.Platform,
repos []rpmmd.RepoConfig,
kernelName,
@ -56,7 +55,7 @@ func NewCoreOSInstaller(m *Manifest,
version string) *CoreOSInstaller {
name := "coi-tree"
p := &CoreOSInstaller{
Base: NewBase(m, name, buildPipeline),
Base: NewBase(name, buildPipeline),
platform: platform,
repos: filterRepos(repos, name),
kernelName: kernelName,
@ -64,7 +63,6 @@ func NewCoreOSInstaller(m *Manifest,
version: version,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}

View file

@ -20,14 +20,13 @@ type EFIBootTree struct {
KernelOpts []string
}
func NewEFIBootTree(m *Manifest, buildPipeline *Build, product, version string) *EFIBootTree {
func NewEFIBootTree(buildPipeline Build, product, version string) *EFIBootTree {
p := &EFIBootTree{
Base: NewBase(m, "efiboot-tree", buildPipeline),
Base: NewBase("efiboot-tree", buildPipeline),
product: product,
version: version,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}

View file

@ -30,7 +30,7 @@ type ContentTest struct {
// content sources.
func NewContentTest(m *Manifest, name string, packageSets []rpmmd.PackageSet, containers []container.SourceSpec, commits []ostree.SourceSpec) *ContentTest {
pipeline := &ContentTest{
Base: NewBase(m, name, nil),
Base: NewBase(name, nil),
packageSets: packageSets,
containers: containers,
commits: commits,

View file

@ -24,15 +24,14 @@ func (p *ISO) SetFilename(filename string) {
p.filename = filename
}
func NewISO(buildPipeline *Build, treePipeline Pipeline, isoLabel string) *ISO {
func NewISO(buildPipeline Build, treePipeline Pipeline, isoLabel string) *ISO {
p := &ISO{
Base: NewBase(treePipeline.Manifest(), "bootiso", buildPipeline),
Base: NewBase("bootiso", buildPipeline),
treePipeline: treePipeline,
filename: "image.iso",
isoLabel: isoLabel,
}
buildPipeline.addDependent(p)
treePipeline.Manifest().addPipeline(p)
return p
}

View file

@ -14,13 +14,12 @@ type ISORootfsImg struct {
installerPipeline Pipeline
}
func NewISORootfsImg(buildPipeline *Build, installerPipeline Pipeline) *ISORootfsImg {
func NewISORootfsImg(buildPipeline Build, installerPipeline Pipeline) *ISORootfsImg {
p := &ISORootfsImg{
Base: NewBase(installerPipeline.Manifest(), "rootfs-image", buildPipeline),
Base: NewBase("rootfs-image", buildPipeline),
installerPipeline: installerPipeline,
}
buildPipeline.addDependent(p)
installerPipeline.Manifest().addPipeline(p)
return p
}
@ -50,7 +49,7 @@ func (p *ISORootfsImg) serialize() osbuild.Pipeline {
)
devName := "device"
devices := osbuild.Devices{devName: *lodevice}
devices := map[string]osbuild.Device{devName: *lodevice}
mkfsStage := osbuild.NewMkfsExt4Stage(mkfsStageOptions, devices)
pipeline.AddStage(mkfsStage)
@ -64,8 +63,8 @@ func (p *ISORootfsImg) serialize() osbuild.Pipeline {
},
}
copyStageInputs := osbuild.NewPipelineTreeInputs(inputName, p.installerPipeline.Name())
copyStageMounts := &osbuild.Mounts{*osbuild.NewExt4Mount(devName, devName, "/")}
copyStage := osbuild.NewCopyStage(copyStageOptions, copyStageInputs, &devices, copyStageMounts)
copyStageMounts := []osbuild.Mount{*osbuild.NewExt4Mount(devName, devName, "/")}
copyStage := osbuild.NewCopyStage(copyStageOptions, copyStageInputs, devices, copyStageMounts)
pipeline.AddStage(copyStage)
return pipeline
}

View file

@ -86,7 +86,15 @@ func (m *Manifest) addPipeline(p Pipeline) {
panic("duplicate pipeline name in manifest")
}
}
if p.Manifest() != nil {
panic("pipeline already added to a different manifest")
}
m.pipelines = append(m.pipelines, p)
p.setManifest(m)
// check that the pipeline's build pipeline is included in the same manifest
if build := p.BuildPipeline(); build != nil && build.Manifest() != m {
panic("cannot add pipeline to a different manifest than its build pipeline")
}
}
type PackageSelector func([]rpmmd.PackageSet) []rpmmd.PackageSet

View file

@ -24,14 +24,13 @@ func (p *OCIContainer) SetFilename(filename string) {
p.filename = filename
}
func NewOCIContainer(buildPipeline *Build, treePipeline TreePipeline) *OCIContainer {
func NewOCIContainer(buildPipeline Build, treePipeline TreePipeline) *OCIContainer {
p := &OCIContainer{
Base: NewBase(treePipeline.Manifest(), "container", buildPipeline),
Base: NewBase("container", buildPipeline),
treePipeline: treePipeline,
filename: "oci-archive.tar",
}
buildPipeline.addDependent(p)
treePipeline.Manifest().addPipeline(p)
return p
}

View file

@ -125,6 +125,7 @@ type OSCustomizations struct {
LeapSecTZ *string
FactAPIType *facts.APIType
Presets []osbuild.Preset
ContainersStorage *string
Subscription *subscription.ImageOptions
RHSMConfig map[subscription.RHSMStatus]*osbuild.RHSMStageOptions
@ -179,19 +180,15 @@ type OS struct {
// NewOS creates a new OS pipeline. build is the build pipeline to use for
// building the OS pipeline. platform is the target platform for the final
// image. repos are the repositories to install RPMs from.
func NewOS(m *Manifest,
buildPipeline *Build,
platform platform.Platform,
repos []rpmmd.RepoConfig) *OS {
func NewOS(buildPipeline Build, platform platform.Platform, repos []rpmmd.RepoConfig) *OS {
name := "os"
p := &OS{
Base: NewBase(m, name, buildPipeline),
Base: NewBase(name, buildPipeline),
repos: filterRepos(repos, name),
platform: platform,
InstallWeakDeps: true,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
@ -290,7 +287,7 @@ func (p *OS) getBuildPackages(distro Distro) []string {
}
if len(p.OSCustomizations.Containers) > 0 {
if p.OSTreeRef != "" {
if p.OSCustomizations.ContainersStorage != nil {
switch distro {
case DISTRO_EL8:
packages = append(packages, "python3-pytoml")
@ -406,12 +403,8 @@ func (p *OS) serialize() osbuild.Pipeline {
var storagePath string
// OSTree commits do not include data in `/var` since that is tied to the
// deployment, rather than the commit. Therefore the containers need to be
// stored in a different location, like `/usr/share`, and the container
// storage engine configured accordingly.
if p.OSTreeRef != "" {
storagePath = "/usr/share/containers/storage"
if containerStore := p.OSCustomizations.ContainersStorage; containerStore != nil {
storagePath = *containerStore
storageConf := "/etc/containers/storage.conf"
containerStoreOpts := osbuild.NewContainerStorageOptions(storageConf, storagePath)
@ -648,7 +641,7 @@ func (p *OS) serialize() osbuild.Pipeline {
),
)
} else {
options := osbuild.NewGrub2StageOptionsUnified(pt,
options := osbuild.NewGrub2StageOptions(pt,
strings.Join(kernelOptions, " "),
p.kernelVer,
p.platform.GetUEFIVendor() != "",

View file

@ -71,45 +71,44 @@ type OSTreeDeployment struct {
// Lock the root account in the deployment unless the user defined root
// user options in the build configuration.
LockRoot bool
// Use bootupd instead of grub2 as the bootloader
UseBootupd bool
}
// NewOSTreeCommitDeployment creates a pipeline for an ostree deployment from a
// commit.
func NewOSTreeCommitDeployment(buildPipeline *Build,
m *Manifest,
func NewOSTreeCommitDeployment(buildPipeline Build,
commit *ostree.SourceSpec,
osName string,
platform platform.Platform) *OSTreeDeployment {
p := &OSTreeDeployment{
Base: NewBase(m, "ostree-deployment", buildPipeline),
Base: NewBase("ostree-deployment", buildPipeline),
commitSource: commit,
osName: osName,
platform: platform,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
// NewOSTreeDeployment creates a pipeline for an ostree deployment from a
// container
func NewOSTreeContainerDeployment(buildPipeline *Build,
m *Manifest,
func NewOSTreeContainerDeployment(buildPipeline Build,
container *container.SourceSpec,
ref string,
osName string,
platform platform.Platform) *OSTreeDeployment {
p := &OSTreeDeployment{
Base: NewBase(m, "ostree-deployment", buildPipeline),
Base: NewBase("ostree-deployment", buildPipeline),
containerSource: container,
osName: osName,
ref: ref,
platform: platform,
}
buildPipeline.addDependent(p)
m.addPipeline(p)
return p
}
@ -163,7 +162,7 @@ func (p *OSTreeDeployment) serializeStart(packages []rpmmd.PackageSpec, containe
case len(containers) == 1:
p.containerSpec = &containers[0]
default:
panic(fmt.Sprintf("pipeline requires exactly one ostree commit or one container (have commits: %v; containers: %v)", commits, containers))
panic(fmt.Sprintf("pipeline %s requires exactly one ostree commit or one container (have commits: %v; containers: %v)", p.Name(), commits, containers))
}
}
@ -232,12 +231,19 @@ func (p *OSTreeDeployment) doOSTreeContainerSpec(pipeline *osbuild.Pipeline, rep
cont := *p.containerSpec
ref := p.ref
var targetImgref string
// The ostree-remote case is unusual; it may be used by FCOS/Silverblue for example to handle
// embedded GPG signatures
if p.Remote.Name != "" {
targetImgref = fmt.Sprintf("ostree-remote-registry:%s:%s", p.Remote.Name, p.containerSpec.LocalName)
} else {
targetImgref = fmt.Sprintf("ostree-unverified-registry:%s", p.containerSpec.LocalName)
}
options := &osbuild.OSTreeDeployContainerStageOptions{
OsName: p.osName,
KernelOpts: p.KernelOptionsAppend,
// NOTE: setting the target imgref to be the container source but
// we should make this configurable
TargetImgref: fmt.Sprintf("ostree-remote-registry:%s:%s", p.Remote.Name, p.containerSpec.Source),
OsName: p.osName,
KernelOpts: p.KernelOptionsAppend,
TargetImgref: targetImgref,
Mounts: []string{"/boot", "/boot/efi"},
Rootfs: &osbuild.Rootfs{
Label: "root",
@ -408,22 +414,24 @@ func (p *OSTreeDeployment) serialize() osbuild.Pipeline {
}
}
grubOptions := osbuild.NewGrub2StageOptionsUnified(p.PartitionTable,
strings.Join(kernelOpts, " "),
"",
p.platform.GetUEFIVendor() != "",
p.platform.GetBIOSPlatform(),
p.platform.GetUEFIVendor(), true)
grubOptions.Greenboot = true
grubOptions.Ignition = p.IgnitionPlatform != ""
grubOptions.Config = &osbuild.GRUB2Config{
Default: "saved",
Timeout: 1,
TerminalOutput: []string{"console"},
if !p.UseBootupd {
grubOptions := osbuild.NewGrub2StageOptions(p.PartitionTable,
strings.Join(kernelOpts, " "),
"",
p.platform.GetUEFIVendor() != "",
p.platform.GetBIOSPlatform(),
p.platform.GetUEFIVendor(), true)
grubOptions.Greenboot = true
grubOptions.Ignition = p.IgnitionPlatform != ""
grubOptions.Config = &osbuild.GRUB2Config{
Default: "saved",
Timeout: 1,
TerminalOutput: []string{"console"},
}
bootloader := osbuild.NewGRUB2Stage(grubOptions)
bootloader.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(bootloader)
}
bootloader := osbuild.NewGRUB2Stage(grubOptions)
bootloader.MountOSTree(p.osName, ref, 0)
pipeline.AddStage(bootloader)
// First create custom directories, because some of the files may depend on them
if len(p.Directories) > 0 {

View file

@ -12,14 +12,13 @@ type OSTreeEncapsulate struct {
inputPipeline Pipeline
}
func NewOSTreeEncapsulate(buildPipeline *Build, inputPipeline Pipeline, pipelinename string) *OSTreeEncapsulate {
func NewOSTreeEncapsulate(buildPipeline Build, inputPipeline Pipeline, pipelinename string) *OSTreeEncapsulate {
p := &OSTreeEncapsulate{
Base: NewBase(inputPipeline.Manifest(), pipelinename, buildPipeline),
Base: NewBase(pipelinename, buildPipeline),
inputPipeline: inputPipeline,
filename: "bootable-container.tar",
}
buildPipeline.addDependent(p)
inputPipeline.Manifest().addPipeline(p)
return p
}

View file

@ -14,13 +14,12 @@ type OVF struct {
}
// NewOVF creates a new OVF pipeline. imgPipeline is the pipeline producing the vmdk image.
func NewOVF(buidPipeline *Build, imgPipeline *VMDK) *OVF {
func NewOVF(buidPipeline Build, imgPipeline *VMDK) *OVF {
p := &OVF{
Base: NewBase(imgPipeline.Manifest(), "ovf", buidPipeline),
Base: NewBase("ovf", buidPipeline),
imgPipeline: imgPipeline,
}
buidPipeline.addDependent(p)
imgPipeline.Manifest().addPipeline(p)
return p
}

View file

@ -25,11 +25,13 @@ type Pipeline interface {
// BuildPipeline returns a reference to the pipeline that creates the build
// root for this pipeline. For build pipelines, it should return nil.
BuildPipeline() *Build
BuildPipeline() Build
// Manifest returns a reference to the Manifest which this Pipeline belongs to.
Manifest() *Manifest
setManifest(*Manifest)
getCheckpoint() bool
getExport() bool
@ -74,7 +76,7 @@ type Pipeline interface {
type Base struct {
manifest *Manifest
name string
build *Build
build Build
checkpoint bool
export bool
}
@ -102,7 +104,7 @@ func (p Base) getExport() bool {
return p.export
}
func (p Base) BuildPipeline() *Build {
func (p Base) BuildPipeline() Build {
return p.build
}
@ -110,6 +112,10 @@ func (p Base) Manifest() *Manifest {
return p.manifest
}
func (p *Base) setManifest(m *Manifest) {
p.manifest = m
}
func (p Base) getBuildPackages(Distro) []string {
return []string{}
}
@ -150,16 +156,10 @@ func (p Base) getInline() []string {
// the build host's filesystem is used as the build root. The runner specifies how to use this
// pipeline as a build pipeline, by naming the distro it contains. When the host system is used
// as a build root, then the necessary runner is autodetected.
func NewBase(m *Manifest, name string, build *Build) Base {
func NewBase(name string, build Build) Base {
p := Base{
manifest: m,
name: name,
build: build,
}
if build != nil {
if build.Base.manifest != m {
panic("build pipeline from a different manifest")
}
name: name,
build: build,
}
return p
}
@ -191,7 +191,7 @@ func (p Base) serialize() osbuild.Pipeline {
type TreePipeline interface {
Name() string
Manifest() *Manifest
BuildPipeline() *Build
BuildPipeline() Build
Platform() platform.Platform
}

View file

@ -25,14 +25,18 @@ func (p *QCOW2) SetFilename(filename string) {
// NewQCOW2 createsa new QCOW2 pipeline. imgPipeline is the pipeline producing the
// raw image. The pipeline name is the name of the new pipeline. Filename is the name
// of the produced qcow2 image.
func NewQCOW2(buildPipeline *Build, imgPipeline FilePipeline) *QCOW2 {
func NewQCOW2(buildPipeline Build, imgPipeline FilePipeline) *QCOW2 {
p := &QCOW2{
Base: NewBase(imgPipeline.Manifest(), "qcow2", buildPipeline),
Base: NewBase("qcow2", buildPipeline),
imgPipeline: imgPipeline,
filename: "image.qcow2",
}
buildPipeline.addDependent(p)
imgPipeline.Manifest().addPipeline(p)
// qcow2 can run outside the build pipeline for e.g. "bib"
if buildPipeline != nil {
buildPipeline.addDependent(p)
} else {
imgPipeline.Manifest().addPipeline(p)
}
return p
}

View file

@ -23,15 +23,14 @@ func (p *RawImage) SetFilename(filename string) {
p.filename = filename
}
func NewRawImage(buildPipeline *Build, treePipeline *OS) *RawImage {
func NewRawImage(buildPipeline Build, treePipeline *OS) *RawImage {
p := &RawImage{
Base: NewBase(treePipeline.Manifest(), "image", buildPipeline),
Base: NewBase("image", buildPipeline),
treePipeline: treePipeline,
filename: "disk.img",
}
buildPipeline.addDependent(p)
p.PartTool = osbuild.PTSfdisk // default; can be changed after initialisation
treePipeline.Manifest().addPipeline(p)
return p
}

View file

@ -25,15 +25,14 @@ func (p *RawOSTreeImage) SetFilename(filename string) {
p.filename = filename
}
func NewRawOStreeImage(buildPipeline *Build, treePipeline *OSTreeDeployment, platform platform.Platform) *RawOSTreeImage {
func NewRawOStreeImage(buildPipeline Build, treePipeline *OSTreeDeployment, platform platform.Platform) *RawOSTreeImage {
p := &RawOSTreeImage{
Base: NewBase(treePipeline.Manifest(), "image", buildPipeline),
Base: NewBase("image", buildPipeline),
treePipeline: treePipeline,
filename: "disk.img",
platform: platform,
}
buildPipeline.addDependent(p)
treePipeline.Manifest().addPipeline(p)
return p
}
@ -86,7 +85,7 @@ func (p *RawOSTreeImage) serialize() osbuild.Pipeline {
// Find the FS root mount name to use as the destination root
// for the target when copying the boot files.
var fsRootMntName string
for _, mnt := range *bootCopyMounts {
for _, mnt := range bootCopyMounts {
if mnt.Target == "/" {
fsRootMntName = mnt.Name
break
@ -111,13 +110,47 @@ func (p *RawOSTreeImage) serialize() osbuild.Pipeline {
pipeline.AddStage(stage)
}
if grubLegacy := p.treePipeline.platform.GetBIOSPlatform(); grubLegacy != "" {
pipeline.AddStage(osbuild.NewGrub2InstStage(osbuild.NewGrub2InstStageOption(p.Filename(), pt, grubLegacy)))
if p.treePipeline.UseBootupd {
p.addBootupdStage(&pipeline)
} else {
p.maybeAddGrubInstStage(&pipeline)
}
return pipeline
}
func (p *RawOSTreeImage) addBootupdStage(pipeline *osbuild.Pipeline) {
pt := p.treePipeline.PartitionTable
treeBootupdDevices, treeBootupdMounts := osbuild.GenBootupdDevicesMounts(p.Filename(), pt)
opts := &osbuild.BootupdStageOptions{
Deployment: &osbuild.OSTreeDeployment{
OSName: p.treePipeline.osName,
Ref: p.treePipeline.ref,
},
StaticConfigs: true,
}
if legacyBios := p.treePipeline.platform.GetBIOSPlatform(); legacyBios != "" {
opts.Bios = &osbuild.BootupdStageOptionsBios{
Device: "disk",
}
}
bootupd, err := osbuild.NewBootupdStage(opts, treeBootupdDevices, treeBootupdMounts)
if err != nil {
panic(err)
}
pipeline.AddStage(bootupd)
}
func (p *RawOSTreeImage) maybeAddGrubInstStage(pipeline *osbuild.Pipeline) {
pt := p.treePipeline.PartitionTable
if grubLegacy := p.treePipeline.platform.GetBIOSPlatform(); grubLegacy != "" {
pipeline.AddStage(osbuild.NewGrub2InstStage(osbuild.NewGrub2InstStageOption(p.Filename(), pt, grubLegacy)))
}
}
func (p *RawOSTreeImage) Export() *artifact.Artifact {
p.Base.export = true
return artifact.New(p.Name(), p.Filename(), nil)

View file

@ -30,14 +30,13 @@ func (p *Tar) SetFilename(filename string) {
// NewTar creates a new TarPipeline. The inputPipeline represents the
// filesystem tree which will be the contents of the tar file. The pipelinename
// is the name of the pipeline. The filename is the name of the output tar file.
func NewTar(buildPipeline *Build, inputPipeline Pipeline, pipelinename string) *Tar {
func NewTar(buildPipeline Build, inputPipeline Pipeline, pipelinename string) *Tar {
p := &Tar{
Base: NewBase(inputPipeline.Manifest(), pipelinename, buildPipeline),
Base: NewBase(pipelinename, buildPipeline),
inputPipeline: inputPipeline,
filename: "image.tar",
}
buildPipeline.addDependent(p)
inputPipeline.Manifest().addPipeline(p)
return p
}

View file

@ -25,14 +25,13 @@ func (p *VMDK) SetFilename(filename string) {
// raw image. imgOstreePipeline is the pipeline producing the raw ostree image.
// Either imgPipeline or imgOStreePipeline are required, but not both at the same time.
// Filename is the name of the produced image.
func NewVMDK(buildPipeline *Build, imgPipeline FilePipeline) *VMDK {
func NewVMDK(buildPipeline Build, imgPipeline FilePipeline) *VMDK {
p := &VMDK{
Base: NewBase(imgPipeline.Manifest(), "vmdk", buildPipeline),
Base: NewBase("vmdk", buildPipeline),
imgPipeline: imgPipeline,
filename: "image.vmdk",
}
buildPipeline.addDependent(p)
imgPipeline.Manifest().addPipeline(p)
return p
}

View file

@ -26,14 +26,13 @@ func (p *VPC) SetFilename(filename string) {
// NewVPC createsa new Qemu pipeline. imgPipeline is the pipeline producing the
// raw image. The pipeline name is the name of the new pipeline. Filename is the name
// of the produced image.
func NewVPC(buildPipeline *Build, imgPipeline *RawImage) *VPC {
func NewVPC(buildPipeline Build, imgPipeline *RawImage) *VPC {
p := &VPC{
Base: NewBase(imgPipeline.Manifest(), "vpc", buildPipeline),
Base: NewBase("vpc", buildPipeline),
imgPipeline: imgPipeline,
filename: "image.vhd",
}
buildPipeline.addDependent(p)
imgPipeline.Manifest().addPipeline(p)
return p
}

View file

@ -23,14 +23,13 @@ func (p *XZ) SetFilename(filename string) {
// NewXZ creates a new XZ pipeline. imgPipeline is the pipeline producing the
// raw image that will be xz compressed.
func NewXZ(buildPipeline *Build, imgPipeline FilePipeline) *XZ {
func NewXZ(buildPipeline Build, imgPipeline FilePipeline) *XZ {
p := &XZ{
Base: NewBase(imgPipeline.Manifest(), "xz", buildPipeline),
Base: NewBase("xz", buildPipeline),
filename: "image.xz",
imgPipeline: imgPipeline,
}
buildPipeline.addDependent(p)
imgPipeline.Manifest().addPipeline(p)
return p
}

View file

@ -0,0 +1,92 @@
package osbuild
import (
"fmt"
"sort"
"github.com/osbuild/images/pkg/disk"
)
type BootupdStageOptionsBios struct {
Device string `json:"device"`
Partition int `json:"partition,omitempty"`
}
type BootupdStageOptions struct {
Deployment *OSTreeDeployment `json:"deployment,omitempty"`
StaticConfigs bool `json:"static-configs"`
Bios *BootupdStageOptionsBios `json:"bios,omitempty"`
}
func (BootupdStageOptions) isStageOptions() {}
func (opts *BootupdStageOptions) validate(devices map[string]Device) error {
if opts.Bios != nil && opts.Bios.Device != "" {
if _, ok := devices[opts.Bios.Device]; !ok {
var devnames []string
for devname := range devices {
devnames = append(devnames, devname)
}
sort.Strings(devnames)
return fmt.Errorf("cannot find expected device %q for bootupd bios option in %v", opts.Bios.Device, devnames)
}
}
return nil
}
// validateBootupdMounts ensures that all required mounts for the bootup
// stage are generated. Right now the stage requires root, boot and boot/efi
// to find all the bootloader configs
func validateBootupdMounts(mounts []Mount) error {
requiredMounts := map[string]bool{
"/": true,
"/boot": true,
"/boot/efi": true,
}
for _, mnt := range mounts {
delete(requiredMounts, mnt.Target)
}
if len(requiredMounts) != 0 {
var missingMounts []string
for mnt := range requiredMounts {
missingMounts = append(missingMounts, mnt)
}
sort.Strings(missingMounts)
return fmt.Errorf("required mounts for bootupd stage %v missing", missingMounts)
}
return nil
}
// NewBootupdStage creates a new stage for the org.osbuild.bootupd stage. It
// requires a mount setup of "/", "/boot" and "/boot/efi" right now so that
// bootupd can find and install all required bootloader bits.
func NewBootupdStage(opts *BootupdStageOptions, devices map[string]Device, mounts []Mount) (*Stage, error) {
if err := validateBootupdMounts(mounts); err != nil {
return nil, err
}
if err := opts.validate(devices); err != nil {
return nil, err
}
return &Stage{
Type: "org.osbuild.bootupd",
Options: opts,
Devices: devices,
Mounts: mounts,
}, nil
}
func GenBootupdDevicesMounts(filename string, pt *disk.PartitionTable) (map[string]Device, []Mount) {
_, mounts, devices, err := genMountsDevicesFromPt(filename, pt)
if err != nil {
panic(err)
}
devices["disk"] = Device{
Type: "org.osbuild.loopback",
Options: &LoopbackDeviceOptions{
Filename: filename,
},
}
return devices, mounts
}

View file

@ -8,7 +8,7 @@ type ClevisLuksBindStageOptions struct {
func (ClevisLuksBindStageOptions) isStageOptions() {}
func NewClevisLuksBindStage(options *ClevisLuksBindStageOptions, devices Devices) *Stage {
func NewClevisLuksBindStage(options *ClevisLuksBindStageOptions, devices map[string]Device) *Stage {
return &Stage{
Type: "org.osbuild.clevis.luks-bind",
Options: options,

View file

@ -0,0 +1,39 @@
package osbuild
import "fmt"
type ContainerDeployInputs struct {
Images ContainersInput `json:"images"`
}
func (ContainerDeployInputs) isStageInputs() {}
type ContainerDeployOptions struct {
Exclude []string `json:"exclude"`
}
func (ContainerDeployOptions) isStageOptions() {}
func (inputs ContainerDeployInputs) validate() error {
if inputs.Images.References == nil {
return fmt.Errorf("stage requires exactly 1 input container (got nil References)")
}
if ncontainers := len(inputs.Images.References); ncontainers != 1 {
return fmt.Errorf("stage requires exactly 1 input container (got %d)", ncontainers)
}
return nil
}
func NewContainerDeployStage(images ContainersInput, options *ContainerDeployOptions) (*Stage, error) {
inputs := ContainerDeployInputs{
Images: images,
}
if err := inputs.validate(); err != nil {
return nil, err
}
return &Stage{
Type: "org.osbuild.container-deploy",
Inputs: inputs,
Options: options,
}, nil
}

View file

@ -4,32 +4,17 @@ import (
"github.com/osbuild/images/pkg/container"
)
type ContainersInputReferences interface {
isContainersInputReferences()
Len() int
}
type ContainersInputSourceRef struct {
Name string `json:"name"`
}
type ContainersInputSourceMap map[string]ContainersInputSourceRef
func (ContainersInputSourceMap) isContainersInputReferences() {}
func (cism ContainersInputSourceMap) Len() int {
return len(cism)
}
type ContainersInput struct {
inputCommon
References ContainersInputReferences `json:"references"`
References map[string]ContainersInputSourceRef `json:"references"`
}
const InputTypeContainers string = "org.osbuild.containers"
func NewContainersInputForSources(containers []container.Spec) ContainersInput {
refs := make(ContainersInputSourceMap, len(containers))
refs := make(map[string]ContainersInputSourceRef, len(containers))
for _, c := range containers {
ref := ContainersInputSourceRef{
Name: c.LocalName,
@ -40,7 +25,7 @@ func NewContainersInputForSources(containers []container.Spec) ContainersInput {
return ContainersInput{
References: refs,
inputCommon: inputCommon{
Type: InputTypeContainers,
Type: "org.osbuild.containers",
Origin: InputOriginSource,
},
}

View file

@ -2,8 +2,6 @@ package osbuild
import (
"fmt"
"reflect"
"sort"
"github.com/osbuild/images/pkg/disk"
)
@ -26,13 +24,13 @@ type CopyStagePath struct {
func (CopyStageOptions) isStageOptions() {}
func NewCopyStage(options *CopyStageOptions, inputs Inputs, devices *Devices, mounts *Mounts) *Stage {
func NewCopyStage(options *CopyStageOptions, inputs Inputs, devices map[string]Device, mounts []Mount) *Stage {
return &Stage{
Type: "org.osbuild.copy",
Options: options,
Inputs: inputs,
Devices: *devices,
Mounts: *mounts,
Devices: devices,
Mounts: mounts,
}
}
@ -59,68 +57,15 @@ func (*CopyStageFilesInputs) isStageInputs() {}
// function, not just the options, devices, and mounts.
func GenCopyFSTreeOptions(inputName, inputPipeline, filename string, pt *disk.PartitionTable) (
*CopyStageOptions,
*Devices,
*Mounts,
map[string]Device,
[]Mount,
) {
devices := make(map[string]Device, len(pt.Partitions))
mounts := make([]Mount, 0, len(pt.Partitions))
var fsRootMntName string
genMounts := func(mnt disk.Mountable, path []disk.Entity) error {
stageDevices, name := getDevices(path, filename, false)
mountpoint := mnt.GetMountpoint()
if mountpoint == "/" {
fsRootMntName = name
}
var mount *Mount
t := mnt.GetFSType()
switch t {
case "xfs":
mount = NewXfsMount(name, name, mountpoint)
case "vfat":
mount = NewFATMount(name, name, mountpoint)
case "ext4":
mount = NewExt4Mount(name, name, mountpoint)
case "btrfs":
mount = NewBtrfsMount(name, name, mountpoint)
default:
panic("unknown fs type " + t)
}
mounts = append(mounts, *mount)
// update devices map with new elements from stageDevices
for devName := range stageDevices {
if existingDevice, exists := devices[devName]; exists {
// It is usual that the a device is generated twice for the same Entity e.g. LVM VG, which is OK.
// Therefore fail only if a device with the same name is generated for two different Entities.
if !reflect.DeepEqual(existingDevice, stageDevices[devName]) {
panic(fmt.Sprintf("the device name %q has been generated for two different devices", devName))
}
}
devices[devName] = stageDevices[devName]
}
return nil
fsRootMntName, mounts, devices, err := genMountsDevicesFromPt(filename, pt)
if err != nil {
panic(err)
}
_ = pt.ForEachMountable(genMounts)
// 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
})
if fsRootMntName == "" {
panic("no mount found for the filesystem root")
}
stageMounts := Mounts(mounts)
stageDevices := Devices(devices)
options := CopyStageOptions{
Paths: []CopyStagePath{
{
@ -130,5 +75,5 @@ func GenCopyFSTreeOptions(inputName, inputPipeline, filename string, pt *disk.Pa
},
}
return &options, &stageDevices, &stageMounts
return &options, devices, mounts
}

View file

@ -2,13 +2,13 @@ package osbuild
import (
"fmt"
"reflect"
"sort"
"strings"
"github.com/osbuild/images/pkg/disk"
)
type Devices map[string]Device
type Device struct {
Type string `json:"type"`
Parent string `json:"parent,omitempty"`
@ -224,3 +224,64 @@ func pathEscape(path string) string {
return strings.ReplaceAll(path, "/", "-")
}
func genMountsDevicesFromPt(filename string, pt *disk.PartitionTable) (string, []Mount, map[string]Device, error) {
devices := make(map[string]Device, len(pt.Partitions))
mounts := make([]Mount, 0, len(pt.Partitions))
var fsRootMntName string
genMounts := func(mnt disk.Mountable, path []disk.Entity) error {
stageDevices, name := getDevices(path, filename, false)
mountpoint := mnt.GetMountpoint()
if mountpoint == "/" {
fsRootMntName = name
}
var mount *Mount
t := mnt.GetFSType()
switch t {
case "xfs":
mount = NewXfsMount(name, name, mountpoint)
case "vfat":
mount = NewFATMount(name, name, mountpoint)
case "ext4":
mount = NewExt4Mount(name, name, mountpoint)
case "btrfs":
mount = NewBtrfsMount(name, name, mountpoint)
default:
return fmt.Errorf("unknown fs type " + t)
}
mounts = append(mounts, *mount)
// update devices map with new elements from stageDevices
for devName := range stageDevices {
if existingDevice, exists := devices[devName]; exists {
// It is usual that the a device is generated twice for the same Entity e.g. LVM VG, which is OK.
// Therefore fail only if a device with the same name is generated for two different Entities.
if !reflect.DeepEqual(existingDevice, stageDevices[devName]) {
return fmt.Errorf("the device name %q has been generated for two different devices", devName)
}
}
devices[devName] = stageDevices[devName]
}
return nil
}
if err := pt.ForEachMountable(genMounts); err != nil {
return "", nil, nil, err
}
// 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
})
if fsRootMntName == "" {
return "", nil, nil, fmt.Errorf("no mount found for the filesystem root")
}
return fsRootMntName, mounts, devices, nil
}

View file

@ -4,7 +4,6 @@ import (
"github.com/google/uuid"
"github.com/osbuild/images/internal/common"
"github.com/osbuild/images/pkg/blueprint"
"github.com/osbuild/images/pkg/disk"
)
@ -54,53 +53,6 @@ func NewGRUB2Stage(options *GRUB2StageOptions) *Stage {
}
func NewGrub2StageOptions(pt *disk.PartitionTable,
kernelOptions string,
kernel *blueprint.KernelCustomization,
kernelVer string,
uefi bool,
legacy string,
vendor string,
install bool) *GRUB2StageOptions {
rootFs := pt.FindMountable("/")
if rootFs == nil {
panic("root filesystem must be defined for grub2 stage, this is a programming error")
}
stageOptions := GRUB2StageOptions{
RootFilesystemUUID: uuid.MustParse(rootFs.GetFSSpec().UUID),
KernelOptions: kernelOptions,
Legacy: legacy,
}
bootFs := pt.FindMountable("/boot")
if bootFs != nil {
bootFsUUID := uuid.MustParse(bootFs.GetFSSpec().UUID)
stageOptions.BootFilesystemUUID = &bootFsUUID
}
if uefi {
stageOptions.UEFI = &GRUB2UEFI{
Vendor: vendor,
Install: install,
Unified: legacy == "", // force unified grub scheme for pure efi systems
}
}
if kernel != nil {
if kernel.Append != "" {
stageOptions.KernelOptions += " " + kernel.Append
}
stageOptions.SavedEntry = "ffffffffffffffffffffffffffffffff-" + kernelVer
stageOptions.Config = &GRUB2Config{
Default: "saved",
}
}
return &stageOptions
}
func NewGrub2StageOptionsUnified(pt *disk.PartitionTable,
kernelOptions string,
kernelVer string,
uefi bool,

View file

@ -22,6 +22,7 @@ type LiveIMGOptions struct {
type OSTreeCommitOptions struct {
OSName string `json:"osname"`
Remote string `json:"remote"`
URL string `json:"url"`
Ref string `json:"ref"`
GPG bool `json:"gpg"`
@ -77,6 +78,7 @@ func NewKickstartStageOptionsWithOSTreeCommit(
groupCustomizations []users.Group,
ostreeURL string,
ostreeRef string,
ostreeRemote string,
osName string) (*KickstartStageOptions, error) {
options, err := NewKickstartStageOptions(path, userCustomizations, groupCustomizations)
@ -88,6 +90,7 @@ func NewKickstartStageOptionsWithOSTreeCommit(
if ostreeURL != "" {
ostreeCommitOptions := &OSTreeCommitOptions{
OSName: osName,
Remote: ostreeRemote,
URL: ostreeURL,
Ref: ostreeRef,
GPG: false,

View file

@ -42,7 +42,7 @@ func (o LUKS2CreateStageOptions) validate() error {
return nil
}
func NewLUKS2CreateStage(options *LUKS2CreateStageOptions, devices Devices) *Stage {
func NewLUKS2CreateStage(options *LUKS2CreateStageOptions, devices map[string]Device) *Stage {
if err := options.validate(); err != nil {
panic(err)
}

View file

@ -6,7 +6,7 @@ type LUKS2RemoveKeyStageOptions struct {
func (LUKS2RemoveKeyStageOptions) isStageOptions() {}
func NewLUKS2RemoveKeyStage(options *LUKS2RemoveKeyStageOptions, devices Devices) *Stage {
func NewLUKS2RemoveKeyStage(options *LUKS2RemoveKeyStageOptions, devices map[string]Device) *Stage {
return &Stage{
Type: "org.osbuild.luks2.remove-key",
Options: options,

View file

@ -35,7 +35,7 @@ type LogicalVolume struct {
Size string `json:"size"`
}
func NewLVM2CreateStage(options *LVM2CreateStageOptions, devices Devices) *Stage {
func NewLVM2CreateStage(options *LVM2CreateStageOptions, devices map[string]Device) *Stage {
if err := options.validate(); err != nil {
panic(err)
}

View file

@ -35,7 +35,7 @@ func (o LVM2MetadataStageOptions) validate() error {
return nil
}
func NewLVM2MetadataStage(options *LVM2MetadataStageOptions, devices Devices) *Stage {
func NewLVM2MetadataStage(options *LVM2MetadataStageOptions, devices map[string]Device) *Stage {
if err := options.validate(); err != nil {
panic(err)
}

View file

@ -1,7 +1,5 @@
package osbuild
type Mounts []Mount
type Mount struct {
Name string `json:"name"`
Type string `json:"type"`

View file

@ -51,7 +51,7 @@ func (inputs OSTreeDeployContainerInputs) validate() error {
if inputs.Images.References == nil {
return fmt.Errorf("stage requires exactly 1 input container (got nil References)")
}
if ncontainers := inputs.Images.References.Len(); ncontainers != 1 {
if ncontainers := len(inputs.Images.References); ncontainers != 1 {
return fmt.Errorf("stage requires exactly 1 input container (got %d)", ncontainers)
}
return nil

View file

@ -40,6 +40,8 @@ func NewSfdiskStage(options *SfdiskStageOptions, device *Device) *Stage {
return &Stage{
Type: "org.osbuild.sfdisk",
Options: options,
Devices: Devices{"device": *device},
Devices: map[string]Device{
"device": *device,
},
}
}

View file

@ -41,6 +41,8 @@ func NewSgdiskStage(options *SgdiskStageOptions, device *Device) *Stage {
return &Stage{
Type: "org.osbuild.sgdisk",
Options: options,
Devices: Devices{"device": *device},
Devices: map[string]Device{
"device": *device,
},
}
}

View file

@ -11,20 +11,38 @@ type SkopeoIndexSource struct {
func (SkopeoIndexSource) isSource() {}
type SkopeoIndexSourceImage struct {
Name string `json:"name"`
TLSVerify *bool `json:"tls-verify,omitempty"`
Name string `json:"name"`
TLSVerify *bool `json:"tls-verify,omitempty"`
ContainersTransport *string `json:"containers-transport,omitempty"`
StorageLocation *string `json:"storage-location,omitempty"`
}
type SkopeoIndexSourceItem struct {
Image SkopeoIndexSourceImage `json:"image"`
}
func validateTransport(transport *string) error {
if transport == nil {
return nil
}
if *transport != DockerTransport && *transport != ContainersStorageTransport {
return fmt.Errorf("invalid container transport: %s", *transport)
}
return nil
}
func (item SkopeoIndexSourceItem) validate() error {
if item.Image.Name == "" {
return fmt.Errorf("source item has empty name")
}
if err := validateTransport(item.Image.ContainersTransport); err != nil {
return err
}
return nil
}
@ -37,11 +55,13 @@ func NewSkopeoIndexSource() *SkopeoIndexSource {
// AddItem adds a source item to the source; will panic
// if any of the supplied options are invalid or missing
func (source *SkopeoIndexSource) AddItem(name, image string, tlsVerify *bool) {
func (source *SkopeoIndexSource) AddItem(name, image string, tlsVerify *bool, containersTransport *string, storageLocation *string) {
item := SkopeoIndexSourceItem{
Image: SkopeoIndexSourceImage{
Name: name,
TLSVerify: tlsVerify,
Name: name,
TLSVerify: tlsVerify,
ContainersTransport: containersTransport,
StorageLocation: storageLocation,
},
}

View file

@ -7,6 +7,9 @@ import (
var skopeoDigestPattern = regexp.MustCompile(`sha256:[0-9a-f]{64}`)
const DockerTransport = "docker"
const ContainersStorageTransport = "containers-storage"
type SkopeoSource struct {
Items map[string]SkopeoSourceItem `json:"items"`
}
@ -14,9 +17,11 @@ type SkopeoSource struct {
func (SkopeoSource) isSource() {}
type SkopeopSourceImage struct {
Name string `json:"name"`
Digest string `json:"digest"`
TLSVerify *bool `json:"tls-verify,omitempty"`
Name string `json:"name,omitempty"`
Digest string `json:"digest,omitempty"`
TLSVerify *bool `json:"tls-verify,omitempty"`
ContainersTransport *string `json:"containers-transport,omitempty"`
StorageLocation *string `json:"storage-location,omitempty"`
}
type SkopeoSourceItem struct {
@ -24,12 +29,14 @@ type SkopeoSourceItem struct {
}
// NewSkopeoSourceItem creates a new source item for name and digest
func NewSkopeoSourceItem(name, digest string, tlsVerify *bool) SkopeoSourceItem {
func NewSkopeoSourceItem(name, digest string, tlsVerify *bool, containersTransport *string, storageLocation *string) SkopeoSourceItem {
item := SkopeoSourceItem{
Image: SkopeopSourceImage{
Name: name,
Digest: digest,
TLSVerify: tlsVerify,
Name: name,
Digest: digest,
TLSVerify: tlsVerify,
ContainersTransport: containersTransport,
StorageLocation: storageLocation,
},
}
if err := item.validate(); err != nil {
@ -47,6 +54,10 @@ func (item SkopeoSourceItem) validate() error {
return fmt.Errorf("source item %#v has invalid digest", item)
}
if err := validateTransport(item.Image.ContainersTransport); err != nil {
return err
}
return nil
}
@ -59,8 +70,8 @@ func NewSkopeoSource() *SkopeoSource {
// AddItem adds a source item to the source; will panic
// if any of the supplied options are invalid or missing
func (source *SkopeoSource) AddItem(name, digest, image string, tlsVerify *bool) {
item := NewSkopeoSourceItem(name, digest, tlsVerify)
func (source *SkopeoSource) AddItem(name, digest, image string, tlsVerify *bool, containersTransport *string, storageLocation *string) {
item := NewSkopeoSourceItem(name, digest, tlsVerify, containersTransport, storageLocation)
if !skopeoDigestPattern.MatchString(image) {
panic(fmt.Errorf("item %#v has invalid image id", image))
}

View file

@ -95,11 +95,11 @@ func GenSources(packages []rpmmd.PackageSpec, ostreeCommits []ostree.CommitSpec,
skopeo := NewSkopeoSource()
skopeoIndex := NewSkopeoIndexSource()
for _, c := range containers {
skopeo.AddItem(c.Source, c.Digest, c.ImageID, c.TLSVerify)
skopeo.AddItem(c.Source, c.Digest, c.ImageID, c.TLSVerify, c.ContainersTransport, c.StoragePath)
// if we have a list digest, add a skopeo-index source as well
if c.ListDigest != "" {
skopeoIndex.AddItem(c.Source, c.ListDigest, c.TLSVerify)
skopeoIndex.AddItem(c.Source, c.ListDigest, c.TLSVerify, c.ContainersTransport, c.StoragePath)
}
}
if len(skopeo.Items) > 0 {

View file

@ -7,10 +7,10 @@ type Stage struct {
Type string `json:"type"`
// Stage-type specific options fully determining the operations of the
Inputs Inputs `json:"inputs,omitempty"`
Options StageOptions `json:"options,omitempty"`
Devices Devices `json:"devices,omitempty"`
Mounts Mounts `json:"mounts,omitempty"`
Inputs Inputs `json:"inputs,omitempty"`
Options StageOptions `json:"options,omitempty"`
Devices map[string]Device `json:"devices,omitempty"`
Mounts []Mount `json:"mounts,omitempty"`
}
// StageOptions specify the operations of a given stage-type.

View file

@ -17,16 +17,16 @@ func (ZiplInstStageOptions) isStageOptions() {}
// Return a new zipl.inst stage. The 'disk' parameter must represent the
// (entire) device that contains the /boot partition.
func NewZiplInstStage(options *ZiplInstStageOptions, disk *Device, devices *Devices, mounts *Mounts) *Stage {
func NewZiplInstStage(options *ZiplInstStageOptions, disk *Device, devices map[string]Device, mounts []Mount) *Stage {
// create a new devices map and add the disk to it
devmap := map[string]Device(*devices)
devmap := map[string]Device(devices)
devmap["disk"] = *disk
ziplDevices := Devices(devmap)
ziplDevices := map[string]Device(devmap)
return &Stage{
Type: "org.osbuild.zipl.inst",
Options: options,
Devices: ziplDevices,
Mounts: *mounts,
Mounts: mounts,
}
}

View file

@ -1,7 +1,11 @@
package pathpolicy
package policies
import (
"github.com/osbuild/images/internal/pathpolicy"
)
// MountpointPolicies is a set of default mountpoint policies used for filesystem customizations
var MountpointPolicies = NewPathPolicies(map[string]PathPolicy{
var MountpointPolicies = pathpolicy.NewPathPolicies(map[string]pathpolicy.PathPolicy{
"/": {},
// /etc must be on the root filesystem
"/etc": {Deny: true},
@ -31,13 +35,13 @@ var MountpointPolicies = NewPathPolicies(map[string]PathPolicy{
})
// CustomDirectoriesPolicies is a set of default policies for custom directories
var CustomDirectoriesPolicies = NewPathPolicies(map[string]PathPolicy{
var CustomDirectoriesPolicies = pathpolicy.NewPathPolicies(map[string]pathpolicy.PathPolicy{
"/": {Deny: true},
"/etc": {},
})
// CustomFilesPolicies is a set of default policies for custom files
var CustomFilesPolicies = NewPathPolicies(map[string]PathPolicy{
var CustomFilesPolicies = pathpolicy.NewPathPolicies(map[string]pathpolicy.PathPolicy{
"/": {Deny: true},
"/etc": {},
"/root": {},
@ -48,7 +52,7 @@ var CustomFilesPolicies = NewPathPolicies(map[string]PathPolicy{
})
// MountpointPolicies for ostree
var OstreeMountpointPolicies = NewPathPolicies(map[string]PathPolicy{
var OstreeMountpointPolicies = pathpolicy.NewPathPolicies(map[string]pathpolicy.PathPolicy{
"/": {},
"/ostree": {Deny: true},
"/home": {Deny: true},

View file

@ -0,0 +1,126 @@
package reporegistry
import (
"fmt"
"github.com/osbuild/images/pkg/distroidparser"
"github.com/osbuild/images/pkg/rpmmd"
)
// RepoRegistry represents a database of distro and architecture
// specific RPM repositories. Image types are considered only
// if the loaded repository definition contains any ImageTypeTags.
type RepoRegistry struct {
repos rpmmd.DistrosRepoConfigs
}
// New returns a new RepoRegistry instance with the data
// loaded from the given repoConfigPaths
func New(repoConfigPaths []string) (*RepoRegistry, error) {
repositories, err := LoadAllRepositories(repoConfigPaths)
if err != nil {
return nil, err
}
return &RepoRegistry{repositories}, nil
}
// NewTestedDefault returns a new RepoRegistry instance with the data
// loaded from the default test repositories
func NewTestedDefault() (*RepoRegistry, error) {
testReposPath := []string{"./test/data"}
return New(testReposPath)
}
func NewFromDistrosRepoConfigs(distrosRepoConfigs rpmmd.DistrosRepoConfigs) *RepoRegistry {
return &RepoRegistry{distrosRepoConfigs}
}
// ReposByImageTypeName returns a slice of rpmmd.RepoConfig instances, which should be used for building the specific
// image type name (of a given distribution and architecture). The method does not verify
// if the given image type name is actually part of the architecture definition of the provided name.
// Therefore in general, all common distro-arch-specific repositories are returned for any image type name,
// even for non-existing ones.
func (r *RepoRegistry) ReposByImageTypeName(distro, arch, imageType string) ([]rpmmd.RepoConfig, error) {
repositories := []rpmmd.RepoConfig{}
archRepos, err := r.ReposByArchName(distro, arch, true)
if err != nil {
return nil, err
}
for _, repo := range archRepos {
// Add all repositories without image_type tags
if len(repo.ImageTypeTags) == 0 {
repositories = append(repositories, repo)
continue
}
// Add all repositories tagged with the image type
for _, imageNameTag := range repo.ImageTypeTags {
if imageNameTag == imageType {
repositories = append(repositories, repo)
break
}
}
}
return repositories, nil
}
// reposByArchName returns a slice of rpmmd.RepoConfig instances, which should be used for building image types for the
// specific architecture and distribution. This includes by default all repositories without any image type tags specified.
// Depending on the `includeTagged` argument value, repositories with image type tags set will be added to the returned
// slice or not.
//
// The method does not verify if the given architecture name is actually part of the specific distribution definition.
func (r *RepoRegistry) ReposByArchName(distro, arch string, includeTagged bool) ([]rpmmd.RepoConfig, error) {
repositories := []rpmmd.RepoConfig{}
archRepos, err := r.DistroHasRepos(distro, arch)
if err != nil {
return nil, fmt.Errorf("Failed to get repositories for distribution '%s' and architecture '%s': %v", distro, arch, err)
}
for _, repo := range archRepos {
// skip repos with image type tags if specified to do so
if !includeTagged && len(repo.ImageTypeTags) != 0 {
continue
}
repositories = append(repositories, repo)
}
return repositories, nil
}
// DistroHasRepos returns the repositories for the distro+arch, and a found flag
func (r *RepoRegistry) DistroHasRepos(distro, arch string) ([]rpmmd.RepoConfig, error) {
// compatibility layer to support old repository definition filenames
// without a dot to separate major and minor release versions
stdDistroName, err := distroidparser.DefaultParser.Standardize(distro)
if err != nil {
return nil, fmt.Errorf("failed to parse distro ID string: %v", err)
}
distroRepos, found := r.repos[stdDistroName]
if !found {
return nil, fmt.Errorf("there are no repositories for distribution '%s'", stdDistroName)
}
repos, found := distroRepos[arch]
if !found {
return nil, fmt.Errorf("there are no repositories for distribution '%s' and architecture '%s'", stdDistroName, arch)
}
return repos, nil
}
// ListDistros returns a list of all distros which have a repository defined
// in the registry.
func (r *RepoRegistry) ListDistros() []string {
distros := make([]string, 0, len(r.repos))
for name := range r.repos {
distros = append(distros, name)
}
return distros
}

View file

@ -0,0 +1,100 @@
package reporegistry
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"github.com/osbuild/images/pkg/distroidparser"
"github.com/osbuild/images/pkg/rpmmd"
)
// LoadAllRepositories loads all repositories for given distros from the given list of paths.
// Behavior is the same as with the LoadRepositories() method.
func LoadAllRepositories(confPaths []string) (rpmmd.DistrosRepoConfigs, error) {
distrosRepoConfigs := rpmmd.DistrosRepoConfigs{}
for _, confPath := range confPaths {
reposPath := filepath.Join(confPath, "repositories")
fileEntries, err := os.ReadDir(reposPath)
if os.IsNotExist(err) {
continue
} else if err != nil {
return nil, err
}
for _, fileEntry := range fileEntries {
// Skip all directories
if fileEntry.IsDir() {
continue
}
// distro repositories definition is expected to be named "<distro_name>.json"
if strings.HasSuffix(fileEntry.Name(), ".json") {
distroIDStr := strings.TrimSuffix(fileEntry.Name(), ".json")
// compatibility layer to support old repository definition filenames
// without a dot to separate major and minor release versions
distro, err := distroidparser.DefaultParser.Standardize(distroIDStr)
if err != nil {
log.Printf("failed to parse distro ID string, using it as is: %v", err)
// NB: Before the introduction of distro ID standardization, the filename
// was used as the distro ID. This is kept for backward compatibility
// if the filename can't be parsed.
distro = distroIDStr
}
// skip the distro repos definition, if it has been already read
_, ok := distrosRepoConfigs[distro]
if ok {
continue
}
configFile := filepath.Join(reposPath, fileEntry.Name())
distroRepos, err := rpmmd.LoadRepositoriesFromFile(configFile)
if err != nil {
return nil, err
}
log.Println("Loaded repository configuration file:", configFile)
distrosRepoConfigs[distro] = distroRepos
}
}
}
return distrosRepoConfigs, nil
}
// LoadRepositories loads distribution repositories from the given list of paths.
// If there are duplicate distro repositories definitions found in multiple paths, the first
// encounter is preferred. For this reason, the order of paths in the passed list should
// reflect the desired preference.
func LoadRepositories(confPaths []string, distro string) (map[string][]rpmmd.RepoConfig, error) {
var repoConfigs map[string][]rpmmd.RepoConfig
path := "/repositories/" + distro + ".json"
for _, confPath := range confPaths {
var err error
repoConfigs, err = rpmmd.LoadRepositoriesFromFile(confPath + path)
if os.IsNotExist(err) {
continue
} else if err != nil {
return nil, err
}
// Found the distro repository configs in the current path
if repoConfigs != nil {
break
}
}
if repoConfigs == nil {
return nil, fmt.Errorf("LoadRepositories failed: none of the provided paths contain distro configuration")
}
return repoConfigs, nil
}

View file

@ -4,9 +4,7 @@ import (
"crypto/sha256"
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"
"time"
@ -20,6 +18,7 @@ type repository struct {
Metalink string `json:"metalink,omitempty"`
MirrorList string `json:"mirrorlist,omitempty"`
GPGKey string `json:"gpgkey,omitempty"`
GPGKeys []string `json:"gpgkeys,omitempty"`
CheckGPG bool `json:"check_gpg,omitempty"`
IgnoreSSL bool `json:"ignore_ssl,omitempty"`
RHSM bool `json:"rhsm,omitempty"`
@ -216,7 +215,7 @@ func GetVerStrFromPackageSpecListPanic(pkgs []PackageSpec, packageName string) s
return pkgVerStr
}
func loadRepositoriesFromFile(filename string) (map[string][]RepoConfig, error) {
func LoadRepositoriesFromFile(filename string) (map[string][]RepoConfig, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
@ -242,6 +241,9 @@ func loadRepositoriesFromFile(filename string) (map[string][]RepoConfig, error)
if repo.GPGKey != "" {
keys = []string{repo.GPGKey}
}
if len(repo.GPGKeys) > 0 {
keys = append(keys, repo.GPGKeys...)
}
config := RepoConfig{
Name: repo.Name,
BaseURLs: urls,
@ -262,83 +264,6 @@ func loadRepositoriesFromFile(filename string) (map[string][]RepoConfig, error)
return repoConfigs, nil
}
// LoadAllRepositories loads all repositories for given distros from the given list of paths.
// Behavior is the same as with the LoadRepositories() method.
func LoadAllRepositories(confPaths []string) (DistrosRepoConfigs, error) {
distrosRepoConfigs := DistrosRepoConfigs{}
for _, confPath := range confPaths {
reposPath := filepath.Join(confPath, "repositories")
fileEntries, err := os.ReadDir(reposPath)
if os.IsNotExist(err) {
continue
} else if err != nil {
return nil, err
}
for _, fileEntry := range fileEntries {
// Skip all directories
if fileEntry.IsDir() {
continue
}
// distro repositories definition is expected to be named "<distro_name>.json"
if strings.HasSuffix(fileEntry.Name(), ".json") {
distro := strings.TrimSuffix(fileEntry.Name(), ".json")
// skip the distro repos definition, if it has been already read
_, ok := distrosRepoConfigs[distro]
if ok {
continue
}
configFile := filepath.Join(reposPath, fileEntry.Name())
distroRepos, err := loadRepositoriesFromFile(configFile)
if err != nil {
return nil, err
}
log.Println("Loaded repository configuration file:", configFile)
distrosRepoConfigs[distro] = distroRepos
}
}
}
return distrosRepoConfigs, nil
}
// LoadRepositories loads distribution repositories from the given list of paths.
// If there are duplicate distro repositories definitions found in multiple paths, the first
// encounter is preferred. For this reason, the order of paths in the passed list should
// reflect the desired preference.
func LoadRepositories(confPaths []string, distro string) (map[string][]RepoConfig, error) {
var repoConfigs map[string][]RepoConfig
path := "/repositories/" + distro + ".json"
for _, confPath := range confPaths {
var err error
repoConfigs, err = loadRepositoriesFromFile(confPath + path)
if os.IsNotExist(err) {
continue
} else if err != nil {
return nil, err
}
// Found the distro repository configs in the current path
if repoConfigs != nil {
break
}
}
if repoConfigs == nil {
return nil, fmt.Errorf("LoadRepositories failed: none of the provided paths contain distro configuration")
}
return repoConfigs, nil
}
func (packages PackageList) Search(globPatterns ...string) (PackageList, error) {
var globs []glob.Glob