split: replace internal packages with images library

Remove all the internal package that are now in the
github.com/osbuild/images package and vendor it.

A new function in internal/blueprint/ converts from an osbuild-composer
blueprint to an images blueprint.  This is necessary for keeping the
blueprint implementation in both packages.  In the future, the images
package will change the blueprint (and most likely rename it) and it
will only be part of the osbuild-composer internals and interface.  The
Convert() function will be responsible for converting the blueprint into
the new configuration object.
This commit is contained in:
Achilleas Koutsou 2023-06-14 16:34:41 +02:00 committed by Sanne Raymaekers
parent d59199670f
commit 0e4a9e586f
446 changed files with 5690 additions and 13312 deletions

View file

@ -1,31 +0,0 @@
package artifact
type Artifact struct {
export string
filename string
mimeType string
}
func New(export, filename string, mimeType *string) *Artifact {
artifact := &Artifact{
export: export,
filename: filename,
mimeType: "application/octet-stream",
}
if mimeType != nil {
artifact.mimeType = *mimeType
}
return artifact
}
func (a *Artifact) Export() string {
return a.export
}
func (a *Artifact) Filename() string {
return a.filename
}
func (a *Artifact) MIMEType() string {
return a.mimeType
}

View file

@ -5,9 +5,10 @@ import (
"encoding/json"
"fmt"
"github.com/osbuild/osbuild-composer/internal/crypt"
"github.com/osbuild/images/pkg/crypt"
"github.com/coreos/go-semver/semver"
iblueprint "github.com/osbuild/images/pkg/blueprint"
)
// A Blueprint is a high-level description of an image.
@ -182,3 +183,150 @@ func (b *Blueprint) CryptPasswords() error {
return nil
}
func Convert(bp Blueprint) iblueprint.Blueprint {
pkgs := make([]iblueprint.Package, len(bp.Packages))
for idx := range bp.Packages {
pkgs[idx] = iblueprint.Package(bp.Packages[idx])
}
modules := make([]iblueprint.Package, len(bp.Modules))
for idx := range bp.Modules {
modules[idx] = iblueprint.Package(bp.Modules[idx])
}
groups := make([]iblueprint.Group, len(bp.Groups))
for idx := range bp.Groups {
groups[idx] = iblueprint.Group(bp.Groups[idx])
}
containers := make([]iblueprint.Container, len(bp.Containers))
for idx := range bp.Containers {
containers[idx] = iblueprint.Container(bp.Containers[idx])
}
customizations := iblueprint.Customizations{}
if c := bp.Customizations; c != nil {
customizations = iblueprint.Customizations{
Hostname: c.Hostname,
InstallationDevice: c.InstallationDevice,
}
if fdo := c.FDO; fdo != nil {
ifdo := iblueprint.FDOCustomization(*fdo)
customizations.FDO = &ifdo
}
if oscap := c.OpenSCAP; oscap != nil {
ioscap := iblueprint.OpenSCAPCustomization(*oscap)
customizations.OpenSCAP = &ioscap
}
if ign := c.Ignition; ign != nil {
iign := iblueprint.IgnitionCustomization{}
if embed := ign.Embedded; embed != nil {
iembed := iblueprint.EmbeddedIgnitionCustomization(*embed)
iign.Embedded = &iembed
}
if fb := ign.FirstBoot; fb != nil {
ifb := iblueprint.FirstBootIgnitionCustomization(*fb)
iign.FirstBoot = &ifb
}
customizations.Ignition = &iign
}
if dirs := c.Directories; dirs != nil {
idirs := make([]iblueprint.DirectoryCustomization, len(dirs))
for idx := range dirs {
idirs[idx] = iblueprint.DirectoryCustomization(dirs[idx])
}
customizations.Directories = idirs
}
if files := c.Files; files != nil {
ifiles := make([]iblueprint.FileCustomization, len(files))
for idx := range files {
ifiles[idx] = iblueprint.FileCustomization(files[idx])
}
customizations.Files = ifiles
}
if repos := c.Repositories; repos != nil {
irepos := make([]iblueprint.RepositoryCustomization, len(repos))
for idx := range repos {
irepos[idx] = iblueprint.RepositoryCustomization(repos[idx])
}
customizations.Repositories = irepos
}
if kernel := c.Kernel; kernel != nil {
ikernel := iblueprint.KernelCustomization(*kernel)
customizations.Kernel = &ikernel
}
if sshkeys := c.SSHKey; sshkeys != nil {
isshkeys := make([]iblueprint.SSHKeyCustomization, len(sshkeys))
for idx := range sshkeys {
isshkeys[idx] = iblueprint.SSHKeyCustomization(sshkeys[idx])
}
customizations.SSHKey = isshkeys
}
if users := c.User; users != nil {
iusers := make([]iblueprint.UserCustomization, len(users))
for idx := range users {
iusers[idx] = iblueprint.UserCustomization(users[idx])
}
customizations.User = iusers
}
if groups := c.Group; groups != nil {
igroups := make([]iblueprint.GroupCustomization, len(groups))
for idx := range groups {
igroups[idx] = iblueprint.GroupCustomization(groups[idx])
}
customizations.Group = igroups
}
if fs := c.Filesystem; fs != nil {
ifs := make([]iblueprint.FilesystemCustomization, len(fs))
for idx := range fs {
ifs[idx] = iblueprint.FilesystemCustomization(fs[idx])
}
customizations.Filesystem = ifs
}
if tz := c.Timezone; tz != nil {
itz := iblueprint.TimezoneCustomization(*tz)
customizations.Timezone = &itz
}
if locale := c.Locale; locale != nil {
ilocale := iblueprint.LocaleCustomization(*locale)
customizations.Locale = &ilocale
}
if fw := c.Firewall; fw != nil {
ifw := iblueprint.FirewallCustomization{
Ports: fw.Ports,
}
if services := fw.Services; services != nil {
iservices := iblueprint.FirewallServicesCustomization(*services)
ifw.Services = &iservices
}
if zones := fw.Zones; zones != nil {
izones := make([]iblueprint.FirewallZoneCustomization, len(zones))
for idx := range zones {
izones[idx] = iblueprint.FirewallZoneCustomization(zones[idx])
}
ifw.Zones = izones
}
customizations.Firewall = &ifw
}
if services := c.Services; services != nil {
iservices := iblueprint.ServicesCustomization(*services)
customizations.Services = &iservices
}
}
ibp := iblueprint.Blueprint{
Name: bp.Name,
Description: bp.Description,
Version: bp.Version,
Packages: pkgs,
Modules: modules,
Groups: groups,
Containers: containers,
Customizations: &customizations,
Distro: bp.Distro,
}
return ibp
}

View file

@ -6,9 +6,9 @@ import (
"regexp"
"strings"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/fsnode"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
type RepositoryCustomization struct {

View file

@ -4,9 +4,9 @@ import (
"fmt"
"testing"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/fsnode"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/stretchr/testify/assert"
)

View file

@ -8,7 +8,7 @@ import (
"net/http"
// "strings"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/osbuild-composer/internal/weldr"
)

View file

@ -8,7 +8,7 @@ import (
"net/http"
// "strings"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/osbuild-composer/internal/weldr"
)

View file

@ -16,13 +16,13 @@ import (
"path/filepath"
"testing"
"github.com/osbuild/osbuild-composer/internal/distro/test_distro"
"github.com/osbuild/osbuild-composer/internal/distroregistry"
"github.com/osbuild/images/pkg/distro/test_distro"
"github.com/osbuild/images/pkg/distroregistry"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/osbuild-composer/internal/dnfjson"
dnfjson_mock "github.com/osbuild/osbuild-composer/internal/mocks/dnfjson"
rpmmd_mock "github.com/osbuild/osbuild-composer/internal/mocks/rpmmd"
"github.com/osbuild/osbuild-composer/internal/reporegistry"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/weldr"
)

View file

@ -3,7 +3,7 @@ package cloudapi
import (
"net/http"
"github.com/osbuild/osbuild-composer/internal/distroregistry"
"github.com/osbuild/images/pkg/distroregistry"
"github.com/osbuild/osbuild-composer/internal/worker"
v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2"

View file

@ -15,15 +15,15 @@ import (
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree"
"github.com/osbuild/images/pkg/rhsm/facts"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/images/pkg/subscription"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/rhsm/facts"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
"github.com/osbuild/osbuild-composer/internal/target"
"github.com/osbuild/osbuild-composer/internal/worker"
"github.com/osbuild/osbuild-composer/internal/worker/clienterrors"

View file

@ -19,14 +19,14 @@ import (
"github.com/osbuild/osbuild-composer/pkg/jobqueue"
"github.com/osbuild/images/pkg/container"
"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/osbuild-composer/internal/auth"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distroregistry"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/prometheus"
"github.com/osbuild/osbuild-composer/internal/target"
"github.com/osbuild/osbuild-composer/internal/worker"
@ -117,7 +117,8 @@ func (s *Server) enqueueCompose(distribution distro.Distro, bp blueprint.Bluepri
}
ir := irs[0]
manifestSource, _, err := ir.imageType.Manifest(&bp, ir.imageOptions, ir.repositories, manifestSeed)
ibp := blueprint.Convert(bp)
manifestSource, _, err := ir.imageType.Manifest(&ibp, ir.imageOptions, ir.repositories, manifestSeed)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
@ -237,7 +238,8 @@ func (s *Server) enqueueKojiCompose(taskID uint64, server, name, version, releas
var kojiFilenames []string
var buildIDs []uuid.UUID
for _, ir := range irs {
manifestSource, _, err := ir.imageType.Manifest(&bp, ir.imageOptions, ir.repositories, manifestSeed)
ibp := blueprint.Convert(bp)
manifestSource, _, err := ir.imageType.Manifest(&ibp, ir.imageOptions, ir.repositories, manifestSeed)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}

View file

@ -0,0 +1,564 @@
package v2
import (
"context"
"encoding/json"
"fmt"
"net/http"
"strings"
"sync"
"time"
"github.com/getkin/kin-openapi/openapi3"
"github.com/getkin/kin-openapi/routers"
legacyrouter "github.com/getkin/kin-openapi/routers/legacy"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/sirupsen/logrus"
"github.com/osbuild/osbuild-composer/pkg/jobqueue"
<<<<<<< HEAD
"github.com/osbuild/osbuild-composer/internal/auth"
=======
"github.com/osbuild/images/pkg/container"
"github.com/osbuild/images/pkg/distro"
"github.com/osbuild/images/pkg/distroregistry"
"github.com/osbuild/images/pkg/manifest"
"github.com/osbuild/images/pkg/ostree"
>>>>>>> 294a8e564 (split: wip)
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/prometheus"
"github.com/osbuild/osbuild-composer/internal/target"
"github.com/osbuild/osbuild-composer/internal/worker"
"github.com/osbuild/osbuild-composer/internal/worker/clienterrors"
)
// Server represents the state of the cloud Server
type Server struct {
workers *worker.Server
distros *distroregistry.Registry
config ServerConfig
router routers.Router
goroutinesCtx context.Context
goroutinesCtxCancel context.CancelFunc
goroutinesGroup sync.WaitGroup
}
type ServerConfig struct {
TenantProviderFields []string
JWTEnabled bool
}
func NewServer(workers *worker.Server, distros *distroregistry.Registry, config ServerConfig) *Server {
ctx, cancel := context.WithCancel(context.Background())
spec, err := GetSwagger()
if err != nil {
panic(err)
}
loader := openapi3.NewLoader()
if err := spec.Validate(loader.Context); err != nil {
panic(err)
}
router, err := legacyrouter.NewRouter(spec)
if err != nil {
panic(err)
}
server := &Server{
workers: workers,
distros: distros,
config: config,
router: router,
goroutinesCtx: ctx,
goroutinesCtxCancel: cancel,
}
return server
}
func (s *Server) Handler(path string) http.Handler {
e := echo.New()
e.Binder = binder{}
e.HTTPErrorHandler = s.HTTPErrorHandler
e.Pre(common.OperationIDMiddleware)
e.Use(middleware.Recover())
e.Logger = common.Logger()
handler := apiHandlers{
server: s,
}
mws := []echo.MiddlewareFunc{
prometheus.StatusMiddleware(prometheus.ComposerSubsystem),
}
if s.config.JWTEnabled {
mws = append(mws, auth.TenantChannelMiddleware(s.config.TenantProviderFields, HTTPError(ErrorTenantNotFound)))
}
mws = append(mws,
prometheus.HTTPDurationMiddleware(prometheus.ComposerSubsystem),
prometheus.MetricsMiddleware, s.ValidateRequest)
RegisterHandlers(e.Group(path, mws...), &handler)
return e
}
func (s *Server) Shutdown() {
s.goroutinesCtxCancel()
s.goroutinesGroup.Wait()
}
func (s *Server) enqueueCompose(distribution distro.Distro, bp blueprint.Blueprint, manifestSeed int64, irs []imageRequest, channel string) (uuid.UUID, error) {
var id uuid.UUID
if len(irs) != 1 {
return id, HTTPError(ErrorInvalidNumberOfImageBuilds)
}
ir := irs[0]
ibp := blueprint.Convert(bp)
manifestSource, _, err := ir.imageType.Manifest(&ibp, ir.imageOptions, ir.repositories, manifestSeed)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
depsolveJobID, err := s.workers.EnqueueDepsolve(&worker.DepsolveJob{
PackageSets: manifestSource.GetPackageSetChains(),
ModulePlatformID: distribution.ModulePlatformID(),
Arch: ir.arch.Name(),
Releasever: distribution.Releasever(),
}, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
dependencies := []uuid.UUID{depsolveJobID}
var containerResolveJobID uuid.UUID
containerSources := manifestSource.GetContainerSourceSpecs()
if len(containerSources) > 1 {
// only one pipeline can embed containers
pipelines := make([]string, 0, len(containerSources))
for name := range containerSources {
pipelines = append(pipelines, name)
}
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, fmt.Errorf("manifest returned %d pipelines with containers (at most 1 is supported): %s", len(containerSources), strings.Join(pipelines, ", ")))
}
for _, sources := range containerSources {
workerResolveSpecs := make([]worker.ContainerSpec, len(sources))
for idx, source := range sources {
workerResolveSpecs[idx] = worker.ContainerSpec{
Source: source.Source,
Name: source.Name,
TLSVerify: source.TLSVerify,
}
}
job := worker.ContainerResolveJob{
Arch: ir.arch.Name(),
Specs: workerResolveSpecs,
}
jobId, err := s.workers.EnqueueContainerResolveJob(&job, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
containerResolveJobID = jobId
dependencies = append(dependencies, containerResolveJobID)
break // there can be only one
}
var ostreeResolveJobID uuid.UUID
commitSources := manifestSource.GetOSTreeSourceSpecs()
if len(commitSources) > 1 {
// only one pipeline can specify an ostree commit for content
pipelines := make([]string, 0, len(commitSources))
for name := range commitSources {
pipelines = append(pipelines, name)
}
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, fmt.Errorf("manifest returned %d pipelines with ostree commits (at most 1 is supported): %s", len(commitSources), strings.Join(pipelines, ", ")))
}
for _, sources := range commitSources {
workerResolveSpecs := make([]worker.OSTreeResolveSpec, len(sources))
for idx, source := range sources {
// ostree.SourceSpec is directly convertible to worker.OSTreeResolveSpec
workerResolveSpecs[idx] = worker.OSTreeResolveSpec(source)
}
jobID, err := s.workers.EnqueueOSTreeResolveJob(&worker.OSTreeResolveJob{Specs: workerResolveSpecs}, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
ostreeResolveJobID = jobID
dependencies = append(dependencies, ostreeResolveJobID)
break // there can be only one
}
manifestJobID, err := s.workers.EnqueueManifestJobByID(&worker.ManifestJobByID{}, dependencies, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
id, err = s.workers.EnqueueOSBuildAsDependency(ir.arch.Name(), &worker.OSBuildJob{
Targets: []*target.Target{ir.target},
PipelineNames: &worker.PipelineNames{
Build: ir.imageType.BuildPipelines(),
Payload: ir.imageType.PayloadPipelines(),
},
}, []uuid.UUID{manifestJobID}, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
s.goroutinesGroup.Add(1)
go func() {
serializeManifest(s.goroutinesCtx, manifestSource, s.workers, depsolveJobID, containerResolveJobID, ostreeResolveJobID, manifestJobID, manifestSeed)
defer s.goroutinesGroup.Done()
}()
return id, nil
}
func (s *Server) enqueueKojiCompose(taskID uint64, server, name, version, release string, distribution distro.Distro, bp blueprint.Blueprint, manifestSeed int64, irs []imageRequest, channel string) (uuid.UUID, error) {
var id uuid.UUID
kojiDirectory := "osbuild-cg/osbuild-composer-koji-" + uuid.New().String()
initID, err := s.workers.EnqueueKojiInit(&worker.KojiInitJob{
Server: server,
Name: name,
Version: version,
Release: release,
}, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
var kojiFilenames []string
var buildIDs []uuid.UUID
for _, ir := range irs {
ibp := blueprint.Convert(bp)
manifestSource, _, err := ir.imageType.Manifest(&ibp, ir.imageOptions, ir.repositories, manifestSeed)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
depsolveJobID, err := s.workers.EnqueueDepsolve(&worker.DepsolveJob{
PackageSets: manifestSource.GetPackageSetChains(),
ModulePlatformID: distribution.ModulePlatformID(),
Arch: ir.arch.Name(),
Releasever: distribution.Releasever(),
}, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
dependencies := []uuid.UUID{depsolveJobID}
var containerResolveJobID uuid.UUID
containerSources := manifestSource.GetContainerSourceSpecs()
if len(containerSources) > 1 {
// only one pipeline can embed containers
pipelines := make([]string, 0, len(containerSources))
for name := range containerSources {
pipelines = append(pipelines, name)
}
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, fmt.Errorf("manifest returned %d pipelines with containers (at most 1 is supported): %s", len(containerSources), strings.Join(pipelines, ", ")))
}
for _, sources := range containerSources {
workerResolveSpecs := make([]worker.ContainerSpec, len(sources))
for idx, source := range sources {
workerResolveSpecs[idx] = worker.ContainerSpec{
Source: source.Source,
Name: source.Name,
TLSVerify: source.TLSVerify,
}
}
job := worker.ContainerResolveJob{
Arch: ir.arch.Name(),
Specs: make([]worker.ContainerSpec, len(bp.Containers)),
}
jobId, err := s.workers.EnqueueContainerResolveJob(&job, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
containerResolveJobID = jobId
dependencies = append(dependencies, containerResolveJobID)
break // there can be only one
}
var ostreeResolveJobID uuid.UUID
commitSources := manifestSource.GetOSTreeSourceSpecs()
if len(commitSources) > 1 {
// only one pipeline can specify an ostree commit for content
pipelines := make([]string, 0, len(commitSources))
for name := range commitSources {
pipelines = append(pipelines, name)
}
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, fmt.Errorf("manifest returned %d pipelines with ostree commits (at most 1 is supported): %s", len(commitSources), strings.Join(pipelines, ", ")))
}
for _, sources := range commitSources {
workerResolveSpecs := make([]worker.OSTreeResolveSpec, len(sources))
for idx, source := range sources {
// ostree.SourceSpec is directly convertible to worker.OSTreeResolveSpec
workerResolveSpecs[idx] = worker.OSTreeResolveSpec(source)
}
jobID, err := s.workers.EnqueueOSTreeResolveJob(&worker.OSTreeResolveJob{Specs: workerResolveSpecs}, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
ostreeResolveJobID = jobID
dependencies = append(dependencies, ostreeResolveJobID)
break // there can be only one
}
manifestJobID, err := s.workers.EnqueueManifestJobByID(&worker.ManifestJobByID{}, dependencies, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
kojiFilename := fmt.Sprintf(
"%s-%s-%s.%s%s",
name,
version,
release,
ir.arch.Name(),
splitExtension(ir.imageType.Filename()),
)
kojiTarget := target.NewKojiTarget(&target.KojiTargetOptions{
Server: server,
UploadDirectory: kojiDirectory,
})
kojiTarget.OsbuildArtifact.ExportFilename = ir.imageType.Filename()
kojiTarget.OsbuildArtifact.ExportName = ir.imageType.Exports()[0]
kojiTarget.ImageName = kojiFilename
targets := []*target.Target{kojiTarget}
// add any cloud upload target if defined
if ir.target != nil {
targets = append(targets, ir.target)
}
buildID, err := s.workers.EnqueueOSBuildAsDependency(ir.arch.Name(), &worker.OSBuildJob{
PipelineNames: &worker.PipelineNames{
Build: ir.imageType.BuildPipelines(),
Payload: ir.imageType.PayloadPipelines(),
},
Targets: targets,
ManifestDynArgsIdx: common.ToPtr(1),
}, []uuid.UUID{initID, manifestJobID}, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
kojiFilenames = append(kojiFilenames, kojiFilename)
buildIDs = append(buildIDs, buildID)
// copy the image request while passing it into the goroutine to prevent data races
s.goroutinesGroup.Add(1)
go func(ir imageRequest) {
serializeManifest(s.goroutinesCtx, manifestSource, s.workers, depsolveJobID, containerResolveJobID, ostreeResolveJobID, manifestJobID, manifestSeed)
defer s.goroutinesGroup.Done()
}(ir)
}
id, err = s.workers.EnqueueKojiFinalize(&worker.KojiFinalizeJob{
Server: server,
Name: name,
Version: version,
Release: release,
KojiFilenames: kojiFilenames,
KojiDirectory: kojiDirectory,
TaskID: taskID,
StartTime: uint64(time.Now().Unix()),
}, initID, buildIDs, channel)
if err != nil {
return id, HTTPErrorWithInternal(ErrorEnqueueingJob, err)
}
return id, nil
}
func serializeManifest(ctx context.Context, manifestSource *manifest.Manifest, workers *worker.Server, depsolveJobID, containerResolveJobID, ostreeResolveJobID, manifestJobID uuid.UUID, seed int64) {
ctx, cancel := context.WithTimeout(ctx, time.Minute*5)
defer cancel()
// wait until job is in a pending state
var token uuid.UUID
var dynArgs []json.RawMessage
var err error
logWithId := logrus.WithField("jobId", manifestJobID)
for {
_, token, _, _, dynArgs, err = workers.RequestJobById(ctx, "", manifestJobID)
if err == jobqueue.ErrNotPending {
logWithId.Debug("Manifest job not pending, waiting for depsolve job to finish")
time.Sleep(time.Millisecond * 50)
select {
case <-ctx.Done():
logWithId.Warning("Manifest job dependencies took longer than 5 minutes to finish, or the server is shutting down, returning to avoid dangling routines")
break
default:
continue
}
}
if err != nil {
logWithId.Errorf("Error requesting manifest job: %v", err)
return
}
break
}
jobResult := &worker.ManifestJobByIDResult{
Manifest: nil,
}
defer func() {
if jobResult.JobError != nil {
logWithId.Errorf("Error in manifest job %v: %v", jobResult.JobError.Reason, err)
}
result, err := json.Marshal(jobResult)
if err != nil {
logWithId.Errorf("Error marshalling manifest job results: %v", err)
}
err = workers.FinishJob(token, result)
if err != nil {
logWithId.Errorf("Error finishing manifest job: %v", err)
}
}()
if len(dynArgs) == 0 {
reason := "No dynamic arguments"
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorNoDynamicArgs, reason, nil)
return
}
var depsolveResults worker.DepsolveJobResult
err = json.Unmarshal(dynArgs[0], &depsolveResults)
if err != nil {
reason := "Error parsing dynamic arguments"
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorParsingDynamicArgs, reason, nil)
return
}
_, err = workers.DepsolveJobInfo(depsolveJobID, &depsolveResults)
if err != nil {
reason := "Error reading depsolve status"
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorReadingJobStatus, reason, nil)
return
}
if jobErr := depsolveResults.JobError; jobErr != nil {
if jobErr.ID == clienterrors.ErrorDNFDepsolveError || jobErr.ID == clienterrors.ErrorDNFMarkingErrors {
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorDepsolveDependency, "Error in depsolve job dependency input, bad package set requested", nil)
return
}
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorDepsolveDependency, "Error in depsolve job dependency", nil)
return
}
if len(depsolveResults.PackageSpecs) == 0 {
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorEmptyPackageSpecs, "Received empty package specs", nil)
return
}
var containerSpecs map[string][]container.Spec
if containerResolveJobID != uuid.Nil {
// Container resolve job
var result worker.ContainerResolveJobResult
_, err := workers.ContainerResolveJobInfo(containerResolveJobID, &result)
if err != nil {
reason := "Error reading container resolve job status"
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorReadingJobStatus, reason, nil)
return
}
if jobErr := result.JobError; jobErr != nil {
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorContainerDependency, "Error in container resolve job dependency", nil)
return
}
// NOTE: The container resolve job doesn't hold the pipeline name for
// the container embedding, so we need to get it from the manifest
// content field. There should be only one.
var containerEmbedPipeline string
for name := range manifestSource.GetContainerSourceSpecs() {
containerEmbedPipeline = name
break
}
pipelineSpecs := make([]container.Spec, len(result.Specs))
for idx, resultSpec := range result.Specs {
pipelineSpecs[idx] = container.Spec{
Source: resultSpec.Source,
Digest: resultSpec.Digest,
LocalName: resultSpec.Name,
TLSVerify: resultSpec.TLSVerify,
ImageID: resultSpec.ImageID,
ListDigest: resultSpec.ListDigest,
}
}
containerSpecs = map[string][]container.Spec{
containerEmbedPipeline: pipelineSpecs,
}
}
var ostreeCommitSpecs map[string][]ostree.CommitSpec
if ostreeResolveJobID != uuid.Nil {
var result worker.OSTreeResolveJobResult
_, err := workers.OSTreeResolveJobInfo(ostreeResolveJobID, &result)
if err != nil {
reason := "Error reading ostree resolve job status"
logrus.Errorf("%s: %v", reason, err)
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorReadingJobStatus, reason, nil)
return
}
if jobErr := result.JobError; jobErr != nil {
jobResult.JobError = clienterrors.WorkerClientError(clienterrors.ErrorOSTreeDependency, "Error in ostree resolve job dependency", nil)
return
}
// NOTE: The ostree resolve job doesn't hold the pipeline name for the
// ostree commits, so we need to get it from the manifest content
// field. There should be only one.
var ostreeCommitPipeline string
for name := range manifestSource.GetOSTreeSourceSpecs() {
ostreeCommitPipeline = name
break
}
commitSpecs := make([]ostree.CommitSpec, len(result.Specs))
for idx, resultSpec := range result.Specs {
commitSpecs[idx] = ostree.CommitSpec{
Ref: resultSpec.Ref,
URL: resultSpec.URL,
Checksum: resultSpec.Checksum,
}
if resultSpec.RHSM {
// NOTE: Older workers don't set the Secrets string in the result
// spec so let's add it here for backwards compatibility. This
// should be removed after a few versions when all workers have
// been updated.
resultSpec.Secrets = "org.osbuild.rhsm.consumer"
}
}
ostreeCommitSpecs = map[string][]ostree.CommitSpec{
ostreeCommitPipeline: commitSpecs,
}
}
ms, err := manifestSource.Serialize(depsolveResults.PackageSpecs, containerSpecs, ostreeCommitSpecs)
jobResult.Manifest = ms
}

View file

@ -6,8 +6,8 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
func TestSplitExtension(t *testing.T) {

View file

@ -11,9 +11,9 @@ import (
"github.com/google/uuid"
"github.com/stretchr/testify/require"
"github.com/osbuild/images/pkg/distro/test_distro"
"github.com/osbuild/images/pkg/osbuild"
v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2"
"github.com/osbuild/osbuild-composer/internal/distro/test_distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/target"
"github.com/osbuild/osbuild-composer/internal/test"
"github.com/osbuild/osbuild-composer/internal/worker"

View file

@ -15,8 +15,8 @@ import (
"github.com/osbuild/osbuild-composer/pkg/jobqueue"
"github.com/osbuild/images/pkg/distro/test_distro"
"github.com/osbuild/osbuild-composer/internal/cloudapi/v2"
"github.com/osbuild/osbuild-composer/internal/distro/test_distro"
"github.com/osbuild/osbuild-composer/internal/test"
"github.com/osbuild/osbuild-composer/internal/worker"
)

View file

@ -12,13 +12,13 @@ import (
"github.com/osbuild/osbuild-composer/pkg/jobqueue"
"github.com/osbuild/images/pkg/distro/test_distro"
"github.com/osbuild/images/pkg/osbuild"
"github.com/osbuild/images/pkg/ostree/mock_ostree_repo"
"github.com/osbuild/images/pkg/rpmmd"
v2 "github.com/osbuild/osbuild-composer/internal/cloudapi/v2"
"github.com/osbuild/osbuild-composer/internal/distro/test_distro"
"github.com/osbuild/osbuild-composer/internal/jobqueue/fsjobqueue"
distro_mock "github.com/osbuild/osbuild-composer/internal/mocks/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/ostree/mock_ostree_repo"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/target"
"github.com/osbuild/osbuild-composer/internal/test"
"github.com/osbuild/osbuild-composer/internal/worker"

View file

@ -1,506 +0,0 @@
// package container implements a client for a container
// registry. It can be used to upload container images.
package container
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
_ "github.com/containers/image/v5/docker/archive"
_ "github.com/containers/image/v5/oci/archive"
_ "github.com/containers/image/v5/oci/layout"
"golang.org/x/sys/unix"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/containers/common/pkg/retry"
"github.com/containers/image/v5/copy"
"github.com/containers/image/v5/docker"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
"github.com/containers/image/v5/signature"
"github.com/containers/image/v5/transports"
"github.com/containers/image/v5/types"
"github.com/opencontainers/go-digest"
imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
)
const (
DefaultUserAgent = "osbuild-composer/1.0"
DefaultPolicyPath = "/etc/containers/policy.json"
)
// GetDefaultAuthFile returns the authentication file to use for the
// current environment.
//
// This is basically a re-implementation of `getPathToAuthWithOS` from
// containers/image/pkg/docker/config/config.go[1], but we ensure that
// the returned path is either accessible. This is needed since any
// other error than os.ErrNotExist will lead to an overall failure and
// thus prevent any operation even with public resources.
//
// [1] https://github.com/containers/image/blob/55ea76c7db702ed1af60924a0b57c8da533d9e5a/pkg/docker/config/config.go#L506
func GetDefaultAuthFile() string {
checkAccess := func(path string) bool {
err := unix.Access(path, unix.R_OK)
return err == nil
}
if authFile := os.Getenv("REGISTRY_AUTH_FILE"); authFile != "" {
if checkAccess(authFile) {
return authFile
}
}
if runtimeDir := os.Getenv("XDG_RUNTIME_DIR"); runtimeDir != "" {
if checkAccess(runtimeDir) {
return filepath.Join(runtimeDir, "containers", "auth.json")
}
}
if rundir := filepath.FromSlash("/run/containers"); checkAccess(rundir) {
return filepath.Join(rundir, strconv.Itoa(os.Getuid()), "auth.json")
}
return filepath.FromSlash("/var/empty/containers-auth.json")
}
// ApplyDefaultPath checks if the target includes a domain and if it doesn't adds the default ones
// to the returned string. If also returns a bool indicating whether the defaults were applied
func ApplyDefaultDomainPath(target, defaultDomain, defaultPath string) (string, bool) {
appliedDefaults := false
i := strings.IndexRune(target, '/')
if i == -1 || (!strings.ContainsAny(target[:i], ".:") && target[:i] != "localhost") {
if defaultDomain != "" {
base := defaultDomain
if defaultPath != "" {
base = fmt.Sprintf("%s/%s", base, defaultPath)
}
target = fmt.Sprintf("%s/%s", base, target)
appliedDefaults = true
}
}
return target, appliedDefaults
}
// A Client to interact with the given Target object at a
// container registry, like e.g. uploading an image to.
// All mentioned defaults are only set when using the
// NewClient constructor.
type Client struct {
Target reference.Named // the target object to interact with
ReportWriter io.Writer // used for writing status reports, defaults to os.Stdout
PrecomputeDigests bool // precompute digest in order to avoid uploads
MaxRetries int // how often to retry http requests
UserAgent string // user agent string to use for requests, defaults to DefaultUserAgent
// internal state
policy *signature.Policy
sysCtx *types.SystemContext
}
// NewClient constructs a new Client for target with default options.
// It will add the "latest" tag if target does not contain it.
func NewClient(target string) (*Client, error) {
ref, err := reference.ParseNormalizedNamed(target)
if err != nil {
return nil, fmt.Errorf("failed to parse '%s': %w", target, err)
}
var policy *signature.Policy
if _, err := os.Stat(DefaultPolicyPath); err == nil {
policy, err = signature.NewPolicyFromFile(DefaultPolicyPath)
if err != nil {
return nil, err
}
} else {
policy = &signature.Policy{
Default: []signature.PolicyRequirement{
signature.NewPRInsecureAcceptAnything(),
},
}
}
client := Client{
Target: reference.TagNameOnly(ref),
ReportWriter: os.Stdout,
PrecomputeDigests: true,
UserAgent: DefaultUserAgent,
sysCtx: &types.SystemContext{
RegistriesDirPath: "",
SystemRegistriesConfPath: "",
BigFilesTemporaryDir: "/var/tmp",
OSChoice: "linux",
AuthFilePath: GetDefaultAuthFile(),
},
policy: policy,
}
return &client, nil
}
// SetAuthFilePath sets the location of the `containers-auth.json(5)` file.
func (cl *Client) SetAuthFilePath(path string) {
cl.sysCtx.AuthFilePath = path
}
// GetAuthFilePath gets the location of the `containers-auth.json(5)` file.
func (cl *Client) GetAuthFilePath() string {
return cl.sysCtx.AuthFilePath
}
func (cl *Client) SetArchitectureChoice(arch string) {
// Translate some well-known Composer architecture strings
// into the corresponding container ones
variant := ""
switch arch {
case "x86_64":
arch = "amd64"
case "aarch64":
arch = "arm64"
if variant == "" {
variant = "v8"
}
case "armhfp":
arch = "arm"
if variant == "" {
variant = "v7"
}
//ppc64le and s390x are the same
}
cl.sysCtx.ArchitectureChoice = arch
cl.sysCtx.VariantChoice = variant
}
func (cl *Client) SetVariantChoice(variant string) {
cl.sysCtx.VariantChoice = variant
}
// SetCredentials will set username and password for Client
func (cl *Client) SetCredentials(username, password string) {
if cl.sysCtx.DockerAuthConfig == nil {
cl.sysCtx.DockerAuthConfig = &types.DockerAuthConfig{}
}
cl.sysCtx.DockerAuthConfig.Username = username
cl.sysCtx.DockerAuthConfig.Password = password
}
func (cl *Client) SetDockerCertPath(path string) {
cl.sysCtx.DockerCertPath = path
}
// SetSkipTLSVerify controls if TLS verification happens when
// making requests. If nil is passed it falls back to the default.
func (cl *Client) SetTLSVerify(verify *bool) {
if verify == nil {
cl.sysCtx.DockerInsecureSkipTLSVerify = types.OptionalBoolUndefined
} else {
cl.sysCtx.DockerInsecureSkipTLSVerify = types.NewOptionalBool(!*verify)
}
}
// GetSkipTLSVerify returns current TLS verification state.
func (cl *Client) GetTLSVerify() *bool {
skip := cl.sysCtx.DockerInsecureSkipTLSVerify
if skip == types.OptionalBoolUndefined {
return nil
}
// NB: we invert the state, i.e. verify == (skip == false)
return common.ToPtr(skip == types.OptionalBoolFalse)
}
// SkipTLSVerify is a convenience helper that internally calls
// SetTLSVerify with false
func (cl *Client) SkipTLSVerify() {
cl.SetTLSVerify(common.ToPtr(false))
}
func parseImageName(name string) (types.ImageReference, error) {
parts := strings.SplitN(name, ":", 2)
if len(parts) != 2 {
return nil, fmt.Errorf("invalid image name '%s'", name)
}
transport := transports.Get(parts[0])
if transport == nil {
return nil, fmt.Errorf("unknown transport '%s'", parts[0])
}
return transport.ParseReference(parts[1])
}
// UploadImage takes an container image located at from and uploads it
// to the Target of Client. If tag is set, i.e. not the empty string,
// it will replace any previously set tag or digest of the target.
// Returns the digest of the manifest that was written to the server.
func (cl *Client) UploadImage(ctx context.Context, from, tag string) (digest.Digest, error) {
targetCtx := *cl.sysCtx
targetCtx.DockerRegistryPushPrecomputeDigests = cl.PrecomputeDigests
policyContext, err := signature.NewPolicyContext(cl.policy)
if err != nil {
return "", err
}
srcRef, err := parseImageName(from)
if err != nil {
return "", fmt.Errorf("invalid source name '%s': %w", from, err)
}
target := cl.Target
if tag != "" {
target = reference.TrimNamed(target)
target, err = reference.WithTag(target, tag)
if err != nil {
return "", fmt.Errorf("error creating reference with tag '%s': %w", tag, err)
}
}
destRef, err := docker.NewReference(target)
if err != nil {
return "", err
}
retryOpts := retry.RetryOptions{
MaxRetry: cl.MaxRetries,
}
var manifestDigest digest.Digest
err = retry.RetryIfNecessary(ctx, func() error {
manifestBytes, err := copy.Image(ctx, policyContext, destRef, srcRef, &copy.Options{
RemoveSignatures: false,
SignBy: "",
SignPassphrase: "",
ReportWriter: cl.ReportWriter,
SourceCtx: cl.sysCtx,
DestinationCtx: &targetCtx,
ForceManifestMIMEType: "",
ImageListSelection: copy.CopyAllImages,
PreserveDigests: false,
})
if err != nil {
return err
}
manifestDigest, err = manifest.Digest(manifestBytes)
return err
}, &retryOpts)
if err != nil {
return "", err
}
return manifestDigest, nil
}
// A RawManifest contains the raw manifest Data and its MimeType
type RawManifest struct {
Data []byte
MimeType string
}
// Digest computes the digest from the raw manifest data
func (m RawManifest) Digest() (digest.Digest, error) {
return manifest.Digest(m.Data)
}
// 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) {
target := cl.Target
if digest != "" {
t := reference.TrimNamed(cl.Target)
t, err = reference.WithDigest(t, digest)
if err != nil {
return
}
target = t
}
ref, err := docker.NewReference(target)
if err != nil {
return
}
src, err := ref.NewImageSource(ctx, cl.sysCtx)
if err != nil {
return
}
defer func() {
if e := src.Close(); e != nil {
err = fmt.Errorf("could not close image: %w", e)
}
}()
retryOpts := retry.RetryOptions{
MaxRetry: cl.MaxRetries,
}
if err = retry.RetryIfNecessary(ctx, func() error {
r.Data, r.MimeType, err = src.GetManifest(ctx, nil)
return err
}, &retryOpts); err != nil {
return
}
return
}
type manifestList interface {
ChooseInstance(ctx *types.SystemContext) (digest.Digest, error)
}
type resolvedIds struct {
Manifest digest.Digest
Config digest.Digest
ListManifest digest.Digest
}
func (cl *Client) resolveManifestList(ctx context.Context, list manifestList) (resolvedIds, error) {
digest, err := list.ChooseInstance(cl.sysCtx)
if err != nil {
return resolvedIds{}, err
}
raw, err := cl.GetManifest(ctx, digest)
if err != nil {
return resolvedIds{}, fmt.Errorf("error getting manifest: %w", err)
}
ids, err := cl.resolveRawManifest(ctx, raw)
if err != nil {
return resolvedIds{}, err
}
return ids, err
}
func (cl *Client) resolveRawManifest(ctx context.Context, rm RawManifest) (resolvedIds, error) {
var imageID digest.Digest
switch rm.MimeType {
case manifest.DockerV2ListMediaType:
list, err := manifest.Schema2ListFromManifest(rm.Data)
if err != nil {
return resolvedIds{}, err
}
// Save digest of the manifest list as well.
ids, err := cl.resolveManifestList(ctx, list)
if err != nil {
return resolvedIds{}, err
}
// NOTE: Comment in Digest() source says this should never fail. Ignore the error.
ids.ListManifest, _ = rm.Digest()
return ids, nil
case imgspecv1.MediaTypeImageIndex:
index, err := manifest.OCI1IndexFromManifest(rm.Data)
if err != nil {
return resolvedIds{}, err
}
// Save digest of the manifest list as well.
ids, err := cl.resolveManifestList(ctx, index)
if err != nil {
return resolvedIds{}, err
}
// NOTE: Comment in Digest() source says this should never fail. Ignore the error.
ids.ListManifest, _ = rm.Digest()
return ids, nil
case imgspecv1.MediaTypeImageManifest:
m, err := manifest.OCI1FromManifest(rm.Data)
if err != nil {
return resolvedIds{}, nil
}
imageID = m.ConfigInfo().Digest
case manifest.DockerV2Schema2MediaType:
m, err := manifest.Schema2FromManifest(rm.Data)
if err != nil {
return resolvedIds{}, nil
}
imageID = m.ConfigInfo().Digest
default:
return resolvedIds{}, fmt.Errorf("unsupported manifest format '%s'", rm.MimeType)
}
dg, err := rm.Digest()
if err != nil {
return resolvedIds{}, err
}
return resolvedIds{
Manifest: dg,
Config: imageID,
}, nil
}
// Resolve the Client's Target to the manifest digest and the corresponding image id
// 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) {
raw, err := cl.GetManifest(ctx, "")
if err != nil {
return Spec{}, fmt.Errorf("error getting manifest: %w", err)
}
ids, err := cl.resolveRawManifest(ctx, raw)
if err != nil {
return Spec{}, err
}
spec := NewSpec(cl.Target, ids.Manifest, ids.Config, cl.GetTLSVerify(), ids.ListManifest.String(), name)
return spec, nil
}

View file

@ -1,103 +0,0 @@
package container_test
import (
"context"
"os"
"testing"
"time"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/stretchr/testify/assert"
)
//
func TestClientResolve(t *testing.T) {
registry := NewTestRegistry()
defer registry.Close()
repo := registry.AddRepo("library/osbuild")
listDigest := repo.AddImage(
[]Blob{NewDataBlobFromBase64(rootLayer)},
[]string{"amd64", "ppc64le"},
"cool container",
time.Time{})
ref := registry.GetRef("library/osbuild")
client, err := container.NewClient(ref)
assert.NoError(t, err)
assert.NotNil(t, client)
client.SkipTLSVerify()
ctx := context.Background()
client.SetArchitectureChoice("amd64")
spec, err := client.Resolve(ctx, "")
assert.NoError(t, err)
assert.Equal(t, container.Spec{
Source: ref,
Digest: "sha256:f29b6cd42a94a574583439addcd6694e6224f0e4b32044c9e3aee4c4856c2a50",
ImageID: "sha256:c2ecf25cf190e76b12b07436ad5140d4ba53d8a136d498705e57a006837a720f",
TLSVerify: client.GetTLSVerify(),
LocalName: client.Target.String(),
ListDigest: listDigest,
}, spec)
client.SetArchitectureChoice("ppc64le")
spec, err = client.Resolve(ctx, "")
assert.NoError(t, err)
assert.Equal(t, container.Spec{
Source: ref,
Digest: "sha256:d49eebefb6c7ce5505594bef652bd4adc36f413861bd44209d9b9486310b1264",
ImageID: "sha256:d2ab8fea7f08a22f03b30c13c6ea443121f25e87202a7496e93736efa6fe345a",
TLSVerify: client.GetTLSVerify(),
LocalName: client.Target.String(),
ListDigest: listDigest,
}, spec)
// don't have that architecture
client.SetArchitectureChoice("s390x")
_, err = client.Resolve(ctx, "")
assert.Error(t, err)
}
func TestClientAuthFilePath(t *testing.T) {
client, err := container.NewClient("quay.io/osbuild/osbuild")
assert.NoError(t, err)
authFilePath := client.GetAuthFilePath()
assert.NotEmpty(t, authFilePath)
assert.Equal(t, authFilePath, container.GetDefaultAuthFile())
// make sure the file is accessible
_, err = os.ReadFile(authFilePath)
assert.True(t, err == nil || os.IsNotExist(err))
t.Run("XDG_RUNTIME_DIR", func(t *testing.T) {
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir == "" {
t.Skip("XDG_RUNTIME_DIR not set, skipping test")
return
}
t.Cleanup(func() {
os.Setenv("XDG_RUNTIME_DIR", runtimeDir)
})
os.Unsetenv("XDG_RUNTIME_DIR")
authFilePath := container.GetDefaultAuthFile()
assert.NotEmpty(t, authFilePath)
_, err = os.ReadFile(authFilePath)
assert.True(t, err == nil || os.IsNotExist(err))
})
}

View file

@ -1,385 +0,0 @@
package container_test
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"strings"
"time"
"github.com/opencontainers/go-digest"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/containers/image/v5/docker/reference"
"github.com/containers/image/v5/manifest"
)
const rootLayer = `H4sIAAAJbogA/+SWUYqDMBCG53lP4V5g9x8dzRX2Bvtc0VIhEIhKe/wSKxgU6ktjC/O9hMzAQDL8
/8yltdb9DLeB0gEGKhHCg/UJsBAL54zKFBAC54ZzyrCUSMfYDydPgHfu6R/s5VePilOfzF/of/bv
vG2+lqhyFNGPddP53yjyegCBKcuNROZ77AmBoP+CmbIyqpEM5fqf+3/ubJtsCuz7P1b+L1Du/4f5
v+vrsVPu/Vq9P3ANk//d+x/MZv8TKNf/Qfqf9v9v5fLXK3/lKEc5ypm4AwAA//8DAE6E6nIAEgAA
`
// The following code implements a toy container registry to test with
// Blob interface
type Blob interface {
GetSize() int64
GetMediaType() string
GetDigest() digest.Digest
Reader() io.Reader
}
// dataBlob //
type dataBlob struct {
Data []byte
MediaType string
}
func NewDataBlobFromBase64(text string) dataBlob {
data, err := base64.StdEncoding.DecodeString(text)
if err != nil {
panic("decoding of text failed")
}
return dataBlob{
Data: data,
}
}
// Blob interface implementation
func (b dataBlob) GetSize() int64 {
return int64(len(b.Data))
}
func (b dataBlob) GetMediaType() string {
if b.MediaType != "" {
return b.MediaType
}
return manifest.DockerV2Schema2LayerMediaType
}
func (b dataBlob) GetDigest() digest.Digest {
return digest.FromBytes(b.Data)
}
func (b dataBlob) Reader() io.Reader {
return bytes.NewReader(b.Data)
}
func MakeDescriptorForBlob(b Blob) manifest.Schema2Descriptor {
return manifest.Schema2Descriptor{
MediaType: b.GetMediaType(),
Size: b.GetSize(),
Digest: b.GetDigest(),
}
}
// Repo //
type Repo struct {
blobs map[string]Blob
manifests map[string]*manifest.Schema2
images map[string]*manifest.Schema2List
tags map[string]string
}
func NewRepo() *Repo {
return &Repo{
blobs: make(map[string]Blob),
manifests: make(map[string]*manifest.Schema2),
tags: make(map[string]string),
images: make(map[string]*manifest.Schema2List),
}
}
func (r *Repo) AddBlob(b Blob) manifest.Schema2Descriptor {
desc := MakeDescriptorForBlob(b)
r.blobs[desc.Digest.String()] = b
return desc
}
func (r *Repo) AddObject(v interface{}, mediaType string) manifest.Schema2Descriptor {
data, err := json.MarshalIndent(v, "", " ")
if err != nil {
panic("could not marshal image object")
}
blob := dataBlob{
Data: data,
MediaType: mediaType,
}
return r.AddBlob(blob)
}
func (r *Repo) AddManifest(mf *manifest.Schema2) manifest.Schema2Descriptor {
desc := r.AddObject(mf, mf.MediaType)
r.manifests[desc.Digest.String()] = mf
return desc
}
func (r *Repo) AddImage(layers []Blob, arches []string, comment string, ctime time.Time) string {
blobs := make([]manifest.Schema2Descriptor, len(layers))
for i, layer := range layers {
blobs[i] = r.AddBlob(layer)
}
manifests := make([]manifest.Schema2ManifestDescriptor, len(arches))
for i, arch := range arches {
img := manifest.Schema2V1Image{
Architecture: arch,
OS: "linux",
Author: "osbuild",
Comment: comment,
Created: ctime,
}
// Add the config object
config := r.AddObject(img, manifest.DockerV2Schema2ConfigMediaType)
// make and add the manifest object
schema := manifest.Schema2FromComponents(config, blobs)
mf := r.AddManifest(schema)
desc := manifest.Schema2ManifestDescriptor{
Schema2Descriptor: mf,
Platform: manifest.Schema2PlatformSpec{
Architecture: arch,
OS: "linux",
},
}
manifests[i] = desc
}
list := manifest.Schema2ListFromComponents(manifests)
desc := r.AddObject(list, list.MediaType)
checksum := desc.Digest.String()
r.images[checksum] = list
r.tags["latest"] = checksum
return checksum
}
func (r *Repo) AddTag(checksum, tag string) {
if _, ok := r.images[checksum]; !ok {
panic("cannot tag: image not found: " + checksum)
}
r.tags[tag] = checksum
}
func WriteBlob(blob Blob, w http.ResponseWriter) {
w.Header().Add("Content-Type", blob.GetMediaType())
w.Header().Add("Content-Length", fmt.Sprintf("%d", blob.GetSize()))
w.Header().Add("Docker-Content-Digest", blob.GetDigest().String())
w.WriteHeader(http.StatusOK)
reader := blob.Reader()
_, err := io.Copy(w, reader)
if err != nil {
fmt.Fprintf(os.Stderr, "error writing blob: %v", err)
}
}
func BlobIsManifest(blob Blob) bool {
mt := blob.GetMediaType()
return mt == manifest.DockerV2Schema2MediaType || mt == manifest.DockerV2ListMediaType
}
func (r *Repo) ServeManifest(ref string, w http.ResponseWriter, req *http.Request) {
if checksum, ok := r.tags[ref]; ok {
ref = checksum
}
blob, ok := r.blobs[ref]
if !ok || !BlobIsManifest(blob) {
fmt.Fprintf(os.Stderr, "manifest %s not found", ref)
http.NotFound(w, req)
return
}
WriteBlob(blob, w)
}
func (r *Repo) ServeBlob(ref string, w http.ResponseWriter, req *http.Request) {
blob, ok := r.blobs[ref]
if !ok {
fmt.Fprintf(os.Stderr, "blob %s not found", ref)
http.NotFound(w, req)
return
}
WriteBlob(blob, w)
}
// Registry //
type Registry struct {
server *httptest.Server
repos map[string]*Repo
}
func (reg *Registry) ServeHTTP(w http.ResponseWriter, req *http.Request) {
parts := strings.SplitN(req.URL.Path, "?", 1)
paths := strings.Split(strings.Trim(parts[0], "/"), "/")
// Possbile routes
// [1] version-check: /v2/
// [2] blobs: /v2/<repo_name>/blobs/<digest>
// [3] manifest: /v2/<repo_name>/manifests/<ref>
//
// we need at least 4 path components and path has to start with "/v2"
if len(paths) < 1 || paths[0] != "v2" {
http.NotFound(w, req)
return
}
// [1] version check
if len(paths) == 1 {
w.WriteHeader(200)
return
} else if len(paths) < 4 {
http.NotFound(w, req)
return
}
// we asserted that we have at least 4 path components
ref := paths[len(paths)-1]
cmd := paths[len(paths)-2]
repoName := strings.Join(paths[1:len(paths)-2], "/")
repo, ok := reg.repos[repoName]
if !ok {
fmt.Fprintf(os.Stderr, "repo %s not found", repoName)
http.NotFound(w, req)
return
}
if cmd == "manifests" {
repo.ServeManifest(ref, w, req)
} else if cmd == "blobs" {
repo.ServeBlob(ref, w, req)
} else {
http.NotFound(w, req)
}
}
func NewTestRegistry() *Registry {
reg := &Registry{
repos: make(map[string]*Repo),
}
reg.server = httptest.NewTLSServer(reg)
return reg
}
func (reg *Registry) AddRepo(name string) *Repo {
repo := NewRepo()
reg.repos[name] = repo
return repo
}
func (reg *Registry) GetRef(repo string) string {
return fmt.Sprintf("%s/%s", reg.server.Listener.Addr().String(), repo)
}
func (reg *Registry) Resolve(target, arch string) (container.Spec, error) {
ref, err := reference.ParseNormalizedNamed(target)
if err != nil {
return container.Spec{}, fmt.Errorf("failed to parse '%s': %w", target, err)
}
domain := reference.Domain(ref)
tag := "latest"
var checksum string
if tagged, ok := ref.(reference.NamedTagged); ok {
tag = tagged.Tag()
}
if digested, ok := ref.(reference.Digested); ok {
checksum = string(digested.Digest())
}
if domain != reg.server.Listener.Addr().String() {
return container.Spec{}, fmt.Errorf("unknown domain")
}
ref = reference.TrimNamed(ref)
path := reference.Path(ref)
repo, ok := reg.repos[path]
if !ok {
return container.Spec{}, fmt.Errorf("unknown repo")
}
if checksum == "" {
checksum, ok = repo.tags[tag]
if !ok {
return container.Spec{}, fmt.Errorf("unknown tag")
}
}
lst, ok := repo.images[checksum]
listDigest := checksum
if ok {
checksum = ""
for _, m := range lst.Manifests {
if m.Platform.Architecture == arch {
checksum = m.Digest.String()
break
}
}
if checksum == "" {
return container.Spec{}, fmt.Errorf("unsupported architecture")
}
}
mf, ok := repo.manifests[checksum]
if !ok {
return container.Spec{}, fmt.Errorf("unknown digest")
}
return container.Spec{
Source: ref.String(),
Digest: checksum,
ImageID: mf.ConfigDescriptor.Digest.String(),
LocalName: target,
TLSVerify: common.ToPtr(false),
ListDigest: listDigest,
}, nil
}
func (reg *Registry) Close() {
reg.server.Close()
}

View file

@ -1,83 +0,0 @@
package container
import (
"context"
"fmt"
"strings"
)
type resolveResult struct {
spec Spec
err error
}
type Resolver struct {
jobs int
queue chan resolveResult
ctx context.Context
Arch string
AuthFilePath string
}
type SourceSpec struct {
Source string
Name string
TLSVerify *bool
}
func NewResolver(arch string) Resolver {
return Resolver{
ctx: context.Background(),
queue: make(chan resolveResult, 2),
Arch: arch,
}
}
func (r *Resolver) Add(spec SourceSpec) {
client, err := NewClient(spec.Source)
r.jobs += 1
if err != nil {
r.queue <- resolveResult{err: err}
return
}
client.SetTLSVerify(spec.TLSVerify)
client.SetArchitectureChoice(r.Arch)
if r.AuthFilePath != "" {
client.SetAuthFilePath(r.AuthFilePath)
}
go func() {
spec, err := client.Resolve(r.ctx, spec.Name)
if err != nil {
err = fmt.Errorf("'%s': %w", spec.Source, err)
}
r.queue <- resolveResult{spec: spec, err: err}
}()
}
func (r *Resolver) Finish() ([]Spec, error) {
specs := make([]Spec, 0, r.jobs)
errs := make([]string, 0, r.jobs)
for r.jobs > 0 {
result := <-r.queue
r.jobs -= 1
if result.err == nil {
specs = append(specs, result.spec)
} else {
errs = append(errs, result.err.Error())
}
}
if len(errs) > 0 {
detail := strings.Join(errs, "; ")
return specs, fmt.Errorf("failed to resolve container: %s", detail)
}
return specs, nil
}

View file

@ -1,85 +0,0 @@
package container_test
import (
"fmt"
"sort"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
)
type lessCompare func(i, j int) bool
func makeSpecSorter(specs []container.Spec) lessCompare {
return func(i, j int) bool {
return specs[i].Digest < specs[j].Digest
}
}
func TestResolver(t *testing.T) {
registry := NewTestRegistry()
defer registry.Close()
repo := registry.AddRepo("library/osbuild")
ref := registry.GetRef("library/osbuild")
refs := make([]string, 10)
for i := 0; i < len(refs); i++ {
checksum := repo.AddImage(
[]Blob{NewDataBlobFromBase64(rootLayer)},
[]string{"amd64", "ppc64le"},
fmt.Sprintf("image %d", i),
time.Time{})
tag := fmt.Sprintf("%d", i)
repo.AddTag(checksum, tag)
refs[i] = fmt.Sprintf("%s:%s", ref, tag)
}
resolver := container.NewResolver("amd64")
for _, r := range refs {
resolver.Add(container.SourceSpec{r, "", common.ToPtr(false)})
}
have, err := resolver.Finish()
assert.NoError(t, err)
assert.NotNil(t, have)
assert.Len(t, have, len(refs))
want := make([]container.Spec, len(refs))
for i, r := range refs {
spec, err := registry.Resolve(r, "amd64")
assert.NoError(t, err)
want[i] = spec
}
sort.Slice(have, makeSpecSorter(have))
sort.Slice(want, makeSpecSorter(want))
assert.ElementsMatch(t, have, want)
}
func TestResolverFail(t *testing.T) {
resolver := container.NewResolver("amd64")
resolver.Add(container.SourceSpec{"invalid-reference@${IMAGE_DIGEST}", "", common.ToPtr(false)})
specs, err := resolver.Finish()
assert.Error(t, err)
assert.Len(t, specs, 0)
registry := NewTestRegistry()
defer registry.Close()
resolver.Add(container.SourceSpec{registry.GetRef("repo"), "", common.ToPtr(false)})
specs, err = resolver.Finish()
assert.Error(t, err)
assert.Len(t, specs, 0)
}

View file

@ -1,37 +0,0 @@
package container
import (
"github.com/containers/image/v5/docker/reference"
"github.com/opencontainers/go-digest"
)
// A Spec is the specification of how to get a specific
// container from a Source and under what LocalName to
// store it in an image. The container is identified by
// 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)
}
// 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 {
if localName == "" {
localName = source.String()
}
return Spec{
Source: source.Name(),
Digest: digest.String(),
TLSVerify: tlsVerify,
ImageID: imageID.String(),
LocalName: localName,
ListDigest: listDigest,
}
}

View file

@ -1,59 +0,0 @@
package crypt
import (
"crypto/rand"
"math/big"
"strings"
)
// CryptSHA512 encrypts the given password with SHA512 and a random salt.
//
// Note that this function is not deterministic.
func CryptSHA512(phrase string) (string, error) {
const SHA512SaltLength = 16
salt, err := genSalt(SHA512SaltLength)
if err != nil {
return "", nil
}
hashSettings := "$6$" + salt
return crypt(phrase, hashSettings)
}
func genSalt(length int) (string, error) {
saltChars := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789./"
b := make([]byte, length)
for i := range b {
runeIndex, err := rand.Int(rand.Reader, big.NewInt(int64(len(saltChars))))
if err != nil {
return "", err
}
b[i] = saltChars[runeIndex.Int64()]
}
return string(b), nil
}
// PasswordIsCrypted returns true if the password appears to be an encrypted
// one, according to a very simple heuristic.
//
// Any string starting with one of $2$, $6$ or $5$ is considered to be
// encrypted. Any other string is consdirede to be unencrypted.
//
// This functionality is taken from pylorax.
func PasswordIsCrypted(s string) bool {
// taken from lorax src: src/pylorax/api/compose.py:533
prefixes := [...]string{"$2b$", "$6$", "$5$"}
for _, prefix := range prefixes {
if strings.HasPrefix(s, prefix) {
return true
}
}
return false
}

View file

@ -1,87 +0,0 @@
//go:build !darwin
// Copied from https://github.com/amoghe/go-crypt/blob/b3e291286513a0c993f7c4dd7060d327d2d56143/crypt_r.go
// Original sources are under MIT license:
// The MIT License (MIT)
//
// Copyright (c) 2015 Akshay Moghe
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Package crypt provides wrappers around functions available in crypt.h
//
// It wraps around the GNU specific extension (crypt_r) when it is available
// (i.e. where GOOS=linux). This makes the go function reentrant (and thus
// callable from concurrent goroutines).
package crypt
import (
"unsafe"
)
/*
#cgo LDFLAGS: -lcrypt
// this is needed for Ubuntu
#define _GNU_SOURCE
#include <stdlib.h>
#include <string.h>
#include <crypt.h>
char *gnu_ext_crypt(char *pass, char *salt) {
char *enc = NULL;
char *ret = NULL;
struct crypt_data data;
data.initialized = 0;
enc = crypt_r(pass, salt, &data);
if(enc == NULL) {
return NULL;
}
ret = (char *)malloc((strlen(enc)+1) * sizeof(char)); // for trailing null
strcpy(ret, enc);
ret[strlen(enc)]= '\0';
return ret;
}
*/
import "C"
// Crypt provides a wrapper around the glibc crypt_r() function.
// For the meaning of the arguments, refer to the package README.
func crypt(pass, salt string) (string, error) {
c_pass := C.CString(pass)
defer C.free(unsafe.Pointer(c_pass))
c_salt := C.CString(salt)
defer C.free(unsafe.Pointer(c_salt))
c_enc, err := C.gnu_ext_crypt(c_pass, c_salt)
if c_enc == nil {
return "", err
}
defer C.free(unsafe.Pointer(c_enc))
// Return nil error if the string is non-nil.
// As per the errno.h manpage, functions are allowed to set errno
// on success. Caller should ignore errno on success.
return C.GoString(c_enc), nil
}

View file

@ -1,7 +0,0 @@
//go:build darwin
package crypt
func crypt(pass, salt string) (string, error) {
panic("You must not run osbuild-composer on macOS!")
}

View file

@ -1,64 +0,0 @@
//go:build !darwin
package crypt
import (
"testing"
"github.com/stretchr/testify/assert"
)
func Test_crypt_PasswordIsCrypted(t *testing.T) {
tests := []struct {
name string
password string
want bool
}{
{
name: "bcrypt",
password: "$2b$04$123465789012345678901uac5A8egfBuZVHMrDZsQzR96IqNBivCy",
want: true,
}, {
name: "sha256",
password: "$5$1234567890123456$v.2bOKKLlpmUSKn0rxJmgnh.e3wOKivAVNZmNrOsoA3",
want: true,
}, {
name: "sha512",
password: "$6$1234567890123456$d.pgKQFaiD8bRiExg5NesbGR/3u51YvxeYaQXPzx4C6oSYREw8VoReiuYZjx0V9OhGVTZFqhc6emAxT1RC5BV.",
want: true,
}, {
name: "scrypt",
password: "$7$123456789012345", //not actual hash output from scrypt
want: false,
}, {
name: "plain",
password: "password",
want: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := PasswordIsCrypted(test.password); got != test.want {
t.Errorf("PasswordIsCrypted() =%v, want %v", got, test.want)
}
})
}
}
func TestCryptSHA512(t *testing.T) {
retPassFirst, err := CryptSHA512("testPass")
assert.NoError(t, err)
retPassSecond, _ := CryptSHA512("testPass")
expectedPassStart := "$6$"
assert.Equal(t, expectedPassStart, retPassFirst[0:3])
assert.NotEqual(t, retPassFirst, retPassSecond)
}
func TestGenSalt(t *testing.T) {
length := 10
retSaltFirst, err := genSalt(length)
assert.NoError(t, err)
retSaltSecond, _ := genSalt(length)
assert.NotEqual(t, retSaltFirst, retSaltSecond)
}

View file

@ -1,158 +0,0 @@
package disk
import (
"fmt"
"math/rand"
"strings"
"github.com/google/uuid"
)
type Btrfs struct {
UUID string
Label string
Mountpoint string
Subvolumes []BtrfsSubvolume
}
func (b *Btrfs) IsContainer() bool {
return true
}
func (b *Btrfs) Clone() Entity {
if b == nil {
return nil
}
clone := &Btrfs{
UUID: b.UUID,
Label: b.Label,
Mountpoint: b.Mountpoint,
Subvolumes: make([]BtrfsSubvolume, len(b.Subvolumes)),
}
for idx, subvol := range b.Subvolumes {
entClone := subvol.Clone()
svClone, cloneOk := entClone.(*BtrfsSubvolume)
if !cloneOk {
panic("BtrfsSubvolume.Clone() returned an Entity that cannot be converted to *BtrfsSubvolume; this is a programming error")
}
clone.Subvolumes[idx] = *svClone
}
return clone
}
func (b *Btrfs) GetItemCount() uint {
return uint(len(b.Subvolumes))
}
func (b *Btrfs) GetChild(n uint) Entity {
return &b.Subvolumes[n]
}
func (b *Btrfs) CreateMountpoint(mountpoint string, size uint64) (Entity, error) {
name := mountpoint
if name == "/" {
name = "root"
}
subvolume := BtrfsSubvolume{
Size: size,
Mountpoint: mountpoint,
GroupID: 0,
UUID: b.UUID, // subvolumes inherit UUID of main volume
Name: name,
}
b.Subvolumes = append(b.Subvolumes, subvolume)
return &b.Subvolumes[len(b.Subvolumes)-1], nil
}
func (b *Btrfs) AlignUp(size uint64) uint64 {
return size // No extra alignment necessary for subvolumes
}
func (b *Btrfs) GenUUID(rng *rand.Rand) {
if b.UUID == "" {
b.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
}
}
type BtrfsSubvolume struct {
Name string
Size uint64
Mountpoint string
GroupID uint64
MntOps string
// UUID of the parent volume
UUID string
}
func (subvol *BtrfsSubvolume) IsContainer() bool {
return false
}
func (bs *BtrfsSubvolume) Clone() Entity {
if bs == nil {
return nil
}
return &BtrfsSubvolume{
Name: bs.Name,
Size: bs.Size,
Mountpoint: bs.Mountpoint,
GroupID: bs.GroupID,
MntOps: bs.MntOps,
UUID: bs.UUID,
}
}
func (bs *BtrfsSubvolume) GetSize() uint64 {
if bs == nil {
return 0
}
return bs.Size
}
func (bs *BtrfsSubvolume) EnsureSize(s uint64) bool {
if s > bs.Size {
bs.Size = s
return true
}
return false
}
func (bs *BtrfsSubvolume) GetMountpoint() string {
if bs == nil {
return ""
}
return bs.Mountpoint
}
func (bs *BtrfsSubvolume) GetFSType() string {
return "btrfs"
}
func (bs *BtrfsSubvolume) GetFSSpec() FSSpec {
if bs == nil {
return FSSpec{}
}
return FSSpec{
UUID: bs.UUID,
Label: bs.Name,
}
}
func (bs *BtrfsSubvolume) GetFSTabOptions() FSTabOptions {
if bs == nil {
return FSTabOptions{}
}
ops := strings.Join([]string{bs.MntOps, fmt.Sprintf("subvol=%s", bs.Name)}, ",")
return FSTabOptions{
MntOps: ops,
Freq: 0,
PassNo: 0,
}
}

View file

@ -1,170 +0,0 @@
// Disk package contains abstract data-types to define disk-related entities.
//
// The disk package is a collection of interfaces and structs that can be used
// to represent an disk image with its layout. Various concrete types, such as
// PartitionTable, Partition and Filesystem types are defined to model a given
// disk layout. These implement a collection of interfaces that can be used to
// navigate and operate on the various possible combinations of entities in a
// generic way. The entity data model is very generic so that it can represent
// all possible layouts, which can be arbitrarily complex, since technologies
// like logical volume management, LUKS2 container and file systems, that can
// have sub-volumes, allow for complex layouts.
// Entity and Container are the two main interfaces that are used to model the
// tree structure of a disk image layout. The other entity interfaces, such as
// Sizeable and Mountable, then describe various properties and capabilities
// of a given entity.
package disk
import (
"encoding/hex"
"io"
"math/rand"
"github.com/google/uuid"
)
const (
// Default sector size in bytes
DefaultSectorSize = 512
DefaultGrainBytes = uint64(1048576) // 1 MiB
// UUIDs
BIOSBootPartitionGUID = "21686148-6449-6E6F-744E-656564454649"
BIOSBootPartitionUUID = "FAC7F1FB-3E8D-4137-A512-961DE09A5549"
FilesystemDataGUID = "0FC63DAF-8483-4772-8E79-3D69D8477DE4"
FilesystemDataUUID = "CB07C243-BC44-4717-853E-28852021225B"
EFISystemPartitionGUID = "C12A7328-F81F-11D2-BA4B-00A0C93EC93B"
EFISystemPartitionUUID = "68B2905B-DF3E-4FB3-80FA-49D1E773AA33"
EFIFilesystemUUID = "7B77-95E7"
LVMPartitionGUID = "E6D6D379-F507-44C2-A23C-238F2A3DF928"
PRePartitionGUID = "9E1A2D38-C612-4316-AA26-8B49521E5A8B"
RootPartitionUUID = "6264D520-3FB9-423F-8AB8-7A0A8E3D3562"
// Extended Boot Loader Partition
XBootLDRPartitionGUID = "BC13C2FF-59E6-4262-A352-B275FD6F7172"
)
// Entity is the base interface for all disk-related entities.
type Entity interface {
// IsContainer indicates if the implementing type can
// contain any other entities.
IsContainer() bool
// Clone returns a deep copy of the entity.
Clone() Entity
}
// Container is the interface for entities that can contain other entities.
// Together with the base Entity interface this allows to model a generic
// entity tree of theoretically arbitrary depth and width.
type Container interface {
Entity
// GetItemCount returns the number of actual child entities.
GetItemCount() uint
// GetChild returns the child entity at the given index.
GetChild(n uint) Entity
}
// Sizeable is implemented by entities that carry size information.
type Sizeable interface {
// EnsureSize will resize the entity to the given size in case
// it is currently smaller. Returns if the size was changed.
EnsureSize(size uint64) bool
// GetSize returns the size of the entity in bytes.
GetSize() uint64
}
// A Mountable entity is an entity that can be mounted.
type Mountable interface {
// GetMountPoint returns the path of the mount point.
GetMountpoint() string
// GetFSType returns the file system type, e.g. 'xfs'.
GetFSType() string
// GetFSSpec returns the file system spec information.
GetFSSpec() FSSpec
// GetFSTabOptions returns options for mounting the entity.
GetFSTabOptions() FSTabOptions
}
// A MountpointCreator is a container that is able to create new volumes.
//
// CreateMountpoint creates a new mountpoint with the given size and
// returns the entity that represents the new mountpoint.
type MountpointCreator interface {
CreateMountpoint(mountpoint string, size uint64) (Entity, error)
// AlignUp will align the given bytes according to the
// requirements of the container type.
AlignUp(size uint64) uint64
}
// A UniqueEntity is an entity that can be uniquely identified via a UUID.
//
// GenUUID generates a UUID for the entity if it does not yet have one.
type UniqueEntity interface {
Entity
GenUUID(rng *rand.Rand)
}
// VolumeContainer is a specific container that contains volume entities
type VolumeContainer interface {
// MetadataSize returns the size of the container's metadata (in
// bytes), i.e. the storage space that needs to be reserved for
// the container itself, in contrast to the data it contains.
MetadataSize() uint64
}
// FSSpec for a filesystem (UUID and Label); the first field of fstab(5)
type FSSpec struct {
UUID string
Label string
}
type FSTabOptions struct {
// The fourth field of fstab(5); fs_mntops
MntOps string
// The fifth field of fstab(5); fs_freq
Freq uint64
// The sixth field of fstab(5); fs_passno
PassNo uint64
}
// uuid generator helpers
// GeneratesnewRandomUUIDFromReader generates a new random UUID (version
// 4 using) via the given random number generator.
func newRandomUUIDFromReader(r io.Reader) (uuid.UUID, error) {
var id uuid.UUID
_, err := io.ReadFull(r, id[:])
if err != nil {
return uuid.Nil, err
}
id[6] = (id[6] & 0x0f) | 0x40 // Version 4
id[8] = (id[8] & 0x3f) | 0x80 // Variant is 10
return id, nil
}
// NewVolIDFromRand creates a random 32 bit hex string to use as a
// volume ID for FAT filesystems
func NewVolIDFromRand(r *rand.Rand) string {
volid := make([]byte, 4)
len, _ := r.Read(volid)
if len != 4 {
panic("expected four random bytes")
}
return hex.EncodeToString(volid)
}

File diff suppressed because it is too large Load diff

View file

@ -1,85 +0,0 @@
package disk
import (
"math/rand"
"github.com/google/uuid"
)
// Filesystem related functions
type Filesystem struct {
Type string
// ID of the filesystem, vfat doesn't use traditional UUIDs, therefore this
// is just a string.
UUID string
Label string
Mountpoint string
// The fourth field of fstab(5); fs_mntops
FSTabOptions string
// The fifth field of fstab(5); fs_freq
FSTabFreq uint64
// The sixth field of fstab(5); fs_passno
FSTabPassNo uint64
}
func (fs *Filesystem) IsContainer() bool {
return false
}
// Clone the filesystem structure
func (fs *Filesystem) Clone() Entity {
if fs == nil {
return nil
}
return &Filesystem{
Type: fs.Type,
UUID: fs.UUID,
Label: fs.Label,
Mountpoint: fs.Mountpoint,
FSTabOptions: fs.FSTabOptions,
FSTabFreq: fs.FSTabFreq,
FSTabPassNo: fs.FSTabPassNo,
}
}
func (fs *Filesystem) GetMountpoint() string {
if fs == nil {
return ""
}
return fs.Mountpoint
}
func (fs *Filesystem) GetFSType() string {
if fs == nil {
return ""
}
return fs.Type
}
func (fs *Filesystem) GetFSSpec() FSSpec {
if fs == nil {
return FSSpec{}
}
return FSSpec{
UUID: fs.UUID,
Label: fs.Label,
}
}
func (fs *Filesystem) GetFSTabOptions() FSTabOptions {
if fs == nil {
return FSTabOptions{}
}
return FSTabOptions{
MntOps: fs.FSTabOptions,
Freq: fs.FSTabFreq,
PassNo: fs.FSTabPassNo,
}
}
func (fs *Filesystem) GenUUID(rng *rand.Rand) {
if fs.UUID == "" {
fs.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
}
}

View file

@ -1,100 +0,0 @@
package disk
import (
"fmt"
"math/rand"
"github.com/google/uuid"
)
type Argon2id struct {
Iterations uint
Memory uint
Parallelism uint
}
type ClevisBind struct {
Pin string
Policy string
RemovePassphrase bool
}
type LUKSContainer struct {
Passphrase string
UUID string
Cipher string
Label string
Subsystem string
SectorSize uint64
// password-based key derivation function
PBKDF Argon2id
Clevis *ClevisBind
Payload Entity
}
func (lc *LUKSContainer) IsContainer() bool {
return true
}
func (lc *LUKSContainer) GetItemCount() uint {
if lc.Payload == nil {
return 0
}
return 1
}
func (lc *LUKSContainer) GetChild(n uint) Entity {
if n != 0 {
panic(fmt.Sprintf("invalid child index for LUKSContainer: %d != 0", n))
}
return lc.Payload
}
func (lc *LUKSContainer) Clone() Entity {
if lc == nil {
return nil
}
clc := &LUKSContainer{
Passphrase: lc.Passphrase,
UUID: lc.UUID,
Cipher: lc.Cipher,
Label: lc.Label,
Subsystem: lc.Subsystem,
SectorSize: lc.SectorSize,
PBKDF: Argon2id{
Iterations: lc.PBKDF.Iterations,
Memory: lc.PBKDF.Memory,
Parallelism: lc.PBKDF.Parallelism,
},
Payload: lc.Payload.Clone(),
}
if lc.Clevis != nil {
clc.Clevis = &ClevisBind{
Pin: lc.Clevis.Pin,
Policy: lc.Clevis.Policy,
RemovePassphrase: lc.Clevis.RemovePassphrase,
}
}
return clc
}
func (lc *LUKSContainer) GenUUID(rng *rand.Rand) {
if lc == nil {
return
}
if lc.UUID == "" {
lc.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
}
}
func (lc *LUKSContainer) MetadataSize() uint64 {
if lc == nil {
return 0
}
// 16 MiB is the default size for the LUKS2 header
return 16 * 1024 * 1024
}

View file

@ -1,205 +0,0 @@
package disk
import (
"fmt"
"strings"
"github.com/osbuild/osbuild-composer/internal/common"
)
// Default physical extent size in bytes: logical volumes
// created inside the VG will be aligned to this.
const LVMDefaultExtentSize = 4 * common.MebiByte
type LVMVolumeGroup struct {
Name string
Description string
LogicalVolumes []LVMLogicalVolume
}
func (vg *LVMVolumeGroup) IsContainer() bool {
return true
}
func (vg *LVMVolumeGroup) Clone() Entity {
if vg == nil {
return nil
}
clone := &LVMVolumeGroup{
Name: vg.Name,
Description: vg.Description,
LogicalVolumes: make([]LVMLogicalVolume, len(vg.LogicalVolumes)),
}
for idx, lv := range vg.LogicalVolumes {
ent := lv.Clone()
// lv.Clone() will return nil only if the logical volume is nil
if ent == nil {
panic(fmt.Sprintf("logical volume %d in a LVM volume group is nil; this is a programming error", idx))
}
lv, cloneOk := ent.(*LVMLogicalVolume)
if !cloneOk {
panic("LVMLogicalVolume.Clone() returned an Entity that cannot be converted to *LVMLogicalVolume; this is a programming error")
}
clone.LogicalVolumes[idx] = *lv
}
return clone
}
func (vg *LVMVolumeGroup) GetItemCount() uint {
if vg == nil {
return 0
}
return uint(len(vg.LogicalVolumes))
}
func (vg *LVMVolumeGroup) GetChild(n uint) Entity {
if vg == nil {
panic("LVMVolumeGroup.GetChild: nil entity")
}
return &vg.LogicalVolumes[n]
}
func (vg *LVMVolumeGroup) CreateMountpoint(mountpoint string, size uint64) (Entity, error) {
filesystem := Filesystem{
Type: "xfs",
Mountpoint: mountpoint,
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
}
return vg.CreateLogicalVolume(mountpoint, size, &filesystem)
}
func (vg *LVMVolumeGroup) CreateLogicalVolume(lvName string, size uint64, payload Entity) (Entity, error) {
if vg == nil {
panic("LVMVolumeGroup.CreateLogicalVolume: nil entity")
}
names := make(map[string]bool, len(vg.LogicalVolumes))
for _, lv := range vg.LogicalVolumes {
names[lv.Name] = true
}
base := lvname(lvName)
var exists bool
name := base
// Make sure that we don't collide with an existing volume, e.g. 'home/test'
// and /home/test_test would collide. We try 100 times and then give up. This
// is mimicking what blivet does. See blivet/blivet.py#L1060 commit 2eb4bd4
for i := 0; i < 100; i++ {
exists = names[name]
if !exists {
break
}
name = fmt.Sprintf("%s%02d", base, i)
}
if exists {
return nil, fmt.Errorf("could not create logical volume: name collision")
}
lv := LVMLogicalVolume{
Name: name,
Size: vg.AlignUp(size),
Payload: payload,
}
vg.LogicalVolumes = append(vg.LogicalVolumes, lv)
return &vg.LogicalVolumes[len(vg.LogicalVolumes)-1], nil
}
func (vg *LVMVolumeGroup) AlignUp(size uint64) uint64 {
if size%LVMDefaultExtentSize != 0 {
size += LVMDefaultExtentSize - size%LVMDefaultExtentSize
}
return size
}
func (vg *LVMVolumeGroup) MetadataSize() uint64 {
if vg == nil {
return 0
}
// LVM2 allows for a lot of customizations that will affect the size
// of the metadata and its location and thus the start of the physical
// extent. For now we assume the default which results in a start of
// the physical extent 1 MiB
return 1024 * 1024
}
type LVMLogicalVolume struct {
Name string
Size uint64
Payload Entity
}
func (lv *LVMLogicalVolume) IsContainer() bool {
return true
}
func (lv *LVMLogicalVolume) Clone() Entity {
if lv == nil {
return nil
}
return &LVMLogicalVolume{
Name: lv.Name,
Size: lv.Size,
Payload: lv.Payload.Clone(),
}
}
func (lv *LVMLogicalVolume) GetItemCount() uint {
if lv == nil || lv.Payload == nil {
return 0
}
return 1
}
func (lv *LVMLogicalVolume) GetChild(n uint) Entity {
if n != 0 || lv == nil {
panic(fmt.Sprintf("invalid child index for LVMLogicalVolume: %d != 0", n))
}
return lv.Payload
}
func (lv *LVMLogicalVolume) GetSize() uint64 {
if lv == nil {
return 0
}
return lv.Size
}
func (lv *LVMLogicalVolume) EnsureSize(s uint64) bool {
if lv == nil {
panic("LVMLogicalVolume.EnsureSize: nil entity")
}
if s > lv.Size {
lv.Size = s
return true
}
return false
}
// lvname returns a name for a logical volume based on the mountpoint.
func lvname(path string) string {
if path == "/" {
return "rootlv"
}
path = strings.TrimLeft(path, "/")
return strings.ReplaceAll(path, "/", "_") + "lv"
}

View file

@ -1,40 +0,0 @@
package disk
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestLVMVCreateMountpoint(t *testing.T) {
assert := assert.New(t)
vg := &LVMVolumeGroup{
Name: "root",
Description: "root volume group",
}
entity, err := vg.CreateMountpoint("/", 0)
assert.NoError(err)
rootlv := entity.(*LVMLogicalVolume)
assert.Equal("rootlv", rootlv.Name)
_, err = vg.CreateMountpoint("/home_test", 0)
assert.NoError(err)
entity, err = vg.CreateMountpoint("/home/test", 0)
assert.NoError(err)
dedup := entity.(*LVMLogicalVolume)
assert.Equal("home_testlv00", dedup.Name)
// Lets collide it
for i := 0; i < 98; i++ {
_, err = vg.CreateMountpoint("/home/test", 0)
assert.NoError(err)
}
_, err = vg.CreateMountpoint("/home/test", 0)
assert.Error(err)
}

View file

@ -1,87 +0,0 @@
package disk
import (
"fmt"
)
type Partition struct {
Start uint64 // Start of the partition in bytes
Size uint64 // Size of the partition in bytes
Type string // Partition type, e.g. 0x83 for MBR or a UUID for gpt
Bootable bool // `Legacy BIOS bootable` (GPT) or `active` (DOS) flag
// ID of the partition, dos doesn't use traditional UUIDs, therefore this
// is just a string.
UUID string
// If nil, the partition is raw; It doesn't contain a payload.
Payload Entity
}
func (p *Partition) IsContainer() bool {
return true
}
func (p *Partition) Clone() Entity {
if p == nil {
return nil
}
partition := &Partition{
Start: p.Start,
Size: p.Size,
Type: p.Type,
Bootable: p.Bootable,
UUID: p.UUID,
}
if p.Payload != nil {
partition.Payload = p.Payload.Clone()
}
return partition
}
func (pt *Partition) GetItemCount() uint {
if pt == nil || pt.Payload == nil {
return 0
}
return 1
}
func (p *Partition) GetChild(n uint) Entity {
if n != 0 {
panic(fmt.Sprintf("invalid child index for Partition: %d != 0", n))
}
return p.Payload
}
func (p *Partition) GetSize() uint64 {
return p.Size
}
// Ensure the partition has at least the given size. Will do nothing
// if the partition is already larger. Returns if the size changed.
func (p *Partition) EnsureSize(s uint64) bool {
if s > p.Size {
p.Size = s
return true
}
return false
}
func (p *Partition) IsBIOSBoot() bool {
if p == nil {
return false
}
return p.Type == BIOSBootPartitionGUID
}
func (p *Partition) IsPReP() bool {
if p == nil {
return false
}
return p.Type == "41" || p.Type == PRePartitionGUID
}

View file

@ -1,691 +0,0 @@
package disk
import (
"fmt"
"math/rand"
"path/filepath"
"github.com/google/uuid"
"github.com/osbuild/osbuild-composer/internal/blueprint"
)
type PartitionTable struct {
Size uint64 // Size of the disk (in bytes).
UUID string // Unique identifier of the partition table (GPT only).
Type string // Partition table type, e.g. dos, gpt.
Partitions []Partition
SectorSize uint64 // Sector size in bytes
ExtraPadding uint64 // Extra space at the end of the partition table (sectors)
}
func NewPartitionTable(basePT *PartitionTable, mountpoints []blueprint.FilesystemCustomization, imageSize uint64, lvmify bool, requiredSizes map[string]uint64, rng *rand.Rand) (*PartitionTable, error) {
newPT := basePT.Clone().(*PartitionTable)
// first pass: enlarge existing mountpoints and collect new ones
newMountpoints, _ := newPT.applyCustomization(mountpoints, false)
// if there is any new mountpoint and lvmify is enabled, ensure we have LVM layout
if lvmify && len(newMountpoints) > 0 {
err := newPT.ensureLVM()
if err != nil {
return nil, err
}
}
// second pass: deal with new mountpoints and newly created ones, after switching to
// the LVM layout, if requested, which might introduce new mount points, i.e. `/boot`
_, err := newPT.applyCustomization(newMountpoints, true)
if err != nil {
return nil, err
}
// If no separate requiredSizes are given then we use our defaults
if requiredSizes == nil {
requiredSizes = map[string]uint64{
"/": 1073741824,
"/usr": 2147483648,
}
}
if len(requiredSizes) != 0 {
newPT.EnsureDirectorySizes(requiredSizes)
}
// Calculate partition table offsets and sizes
newPT.relayout(imageSize)
// Generate new UUIDs for filesystems and partitions
newPT.GenerateUUIDs(rng)
return newPT, nil
}
func (pt *PartitionTable) IsContainer() bool {
return true
}
func (pt *PartitionTable) Clone() Entity {
if pt == nil {
return nil
}
clone := &PartitionTable{
Size: pt.Size,
UUID: pt.UUID,
Type: pt.Type,
Partitions: make([]Partition, len(pt.Partitions)),
SectorSize: pt.SectorSize,
ExtraPadding: pt.ExtraPadding,
}
for idx, partition := range pt.Partitions {
ent := partition.Clone()
// partition.Clone() will return nil only if the partition is nil
if ent == nil {
panic(fmt.Sprintf("partition %d in a Partition Table is nil; this is a programming error", idx))
}
part, cloneOk := ent.(*Partition)
if !cloneOk {
panic("PartitionTable.Clone() returned an Entity that cannot be converted to *PartitionTable; this is a programming error")
}
clone.Partitions[idx] = *part
}
return clone
}
// AlignUp will align the given bytes to next aligned grain if not already
// aligned
func (pt *PartitionTable) AlignUp(size uint64) uint64 {
grain := DefaultGrainBytes
if size%grain == 0 {
// already aligned: return unchanged
return size
}
return ((size + grain) / grain) * grain
}
// Convert the given bytes to the number of sectors.
func (pt *PartitionTable) BytesToSectors(size uint64) uint64 {
sectorSize := pt.SectorSize
if sectorSize == 0 {
sectorSize = DefaultSectorSize
}
return size / sectorSize
}
// Convert the given number of sectors to bytes.
func (pt *PartitionTable) SectorsToBytes(size uint64) uint64 {
sectorSize := pt.SectorSize
if sectorSize == 0 {
sectorSize = DefaultSectorSize
}
return size * sectorSize
}
// Returns if the partition table contains a filesystem with the given
// mount point.
func (pt *PartitionTable) ContainsMountpoint(mountpoint string) bool {
return len(entityPath(pt, mountpoint)) > 0
}
// Generate all needed UUIDs for all the partiton and filesystems
//
// Will not overwrite existing UUIDs and only generate UUIDs for
// partitions if the layout is GPT.
func (pt *PartitionTable) GenerateUUIDs(rng *rand.Rand) {
setuuid := func(ent Entity, path []Entity) error {
if ui, ok := ent.(UniqueEntity); ok {
ui.GenUUID(rng)
}
return nil
}
_ = pt.ForEachEntity(setuuid)
// if this is a MBR partition table, there is no need to generate
// uuids for the partitions themselves
if pt.Type != "gpt" {
return
}
for idx, part := range pt.Partitions {
if part.UUID == "" {
pt.Partitions[idx].UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
}
}
}
func (pt *PartitionTable) GetItemCount() uint {
return uint(len(pt.Partitions))
}
func (pt *PartitionTable) GetChild(n uint) Entity {
return &pt.Partitions[n]
}
func (pt *PartitionTable) GetSize() uint64 {
return pt.Size
}
func (pt *PartitionTable) EnsureSize(s uint64) bool {
if s > pt.Size {
pt.Size = s
return true
}
return false
}
func (pt *PartitionTable) findDirectoryEntityPath(dir string) []Entity {
if path := entityPath(pt, dir); path != nil {
return path
}
parent := filepath.Dir(dir)
if dir == parent {
// invalid dir or pt has no root
return nil
}
// move up the directory path and check again
return pt.findDirectoryEntityPath(parent)
}
// EnsureDirectorySizes takes a mapping of directory paths to sizes (in bytes)
// and resizes the appropriate partitions such that they are at least the size
// of the sum of their subdirectories plus their own sizes.
// The function will panic if any of the directory paths are invalid.
func (pt *PartitionTable) EnsureDirectorySizes(dirSizeMap map[string]uint64) {
type mntSize struct {
entPath []Entity
newSize uint64
}
// add up the required size for each directory grouped by their mountpoints
mntSizeMap := make(map[string]*mntSize)
for dir, size := range dirSizeMap {
entPath := pt.findDirectoryEntityPath(dir)
if entPath == nil {
panic(fmt.Sprintf("EnsureDirectorySizes: invalid dir path %q", dir))
}
mnt := entPath[0].(Mountable)
mountpoint := mnt.GetMountpoint()
if _, ok := mntSizeMap[mountpoint]; !ok {
mntSizeMap[mountpoint] = &mntSize{entPath, 0}
}
es := mntSizeMap[mountpoint]
es.newSize += size
}
// resize all the entities in the map
for _, es := range mntSizeMap {
resizeEntityBranch(es.entPath, es.newSize)
}
}
func (pt *PartitionTable) CreateMountpoint(mountpoint string, size uint64) (Entity, error) {
filesystem := Filesystem{
Type: "xfs",
Mountpoint: mountpoint,
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
}
partition := Partition{
Size: size,
Payload: &filesystem,
}
n := len(pt.Partitions)
var maxNo int
if pt.Type == "gpt" {
switch mountpoint {
case "/boot":
partition.Type = XBootLDRPartitionGUID
default:
partition.Type = FilesystemDataGUID
}
maxNo = 128
} else {
maxNo = 4
}
if n == maxNo {
return nil, fmt.Errorf("maximum number of partitions reached (%d)", maxNo)
}
pt.Partitions = append(pt.Partitions, partition)
return &pt.Partitions[len(pt.Partitions)-1], nil
}
type EntityCallback func(e Entity, path []Entity) error
func forEachEntity(e Entity, path []Entity, cb EntityCallback) error {
childPath := append(path, e)
err := cb(e, childPath)
if err != nil {
return err
}
c, ok := e.(Container)
if !ok {
return nil
}
for idx := uint(0); idx < c.GetItemCount(); idx++ {
child := c.GetChild(idx)
err = forEachEntity(child, childPath, cb)
if err != nil {
return err
}
}
return nil
}
// ForEachEntity runs the provided callback function on each entity in
// the PartitionTable.
func (pt *PartitionTable) ForEachEntity(cb EntityCallback) error {
return forEachEntity(pt, []Entity{}, cb)
}
func (pt *PartitionTable) HeaderSize() uint64 {
// always reserve one extra sector for the GPT header
// this also ensure we have enough space for the MBR
header := pt.SectorsToBytes(1)
if pt.Type == "dos" {
return header
}
// calculate the space we need for
parts := len(pt.Partitions)
// reserve a minimum of 128 partition entires
if parts < 128 {
parts = 128
}
// Assume that each partition entry is 128 bytes
// which might not be the case if the partition
// name exceeds 72 bytes
header += uint64(parts * 128)
return header
}
// Apply filesystem filesystem customization to the partiton table. If create is false
// will only apply customizations to existing partitions and return unhandled, i.e new
// ones. An error can only occur if create is set. Conversely, it will only return non
// empty list of new mountpoints if create is false.
// Does not relayout the table, i.e. a call to relayout might be needed.
func (pt *PartitionTable) applyCustomization(mountpoints []blueprint.FilesystemCustomization, create bool) ([]blueprint.FilesystemCustomization, error) {
newMountpoints := []blueprint.FilesystemCustomization{}
for _, mnt := range mountpoints {
size := clampFSSize(mnt.Mountpoint, mnt.MinSize)
if path := entityPath(pt, mnt.Mountpoint); len(path) != 0 {
size = alignEntityBranch(path, size)
resizeEntityBranch(path, size)
} else {
if !create {
newMountpoints = append(newMountpoints, mnt)
} else if err := pt.createFilesystem(mnt.Mountpoint, size); err != nil {
return nil, err
}
}
}
return newMountpoints, nil
}
// Dynamically calculate and update the start point for each of the existing
// partitions. Adjusts the overall size of image to either the supplied
// value in `size` or to the sum of all partitions if that is lager.
// Will grow the root partition if there is any empty space.
// Returns the updated start point.
func (pt *PartitionTable) relayout(size uint64) uint64 {
// always reserve one extra sector for the GPT header
header := pt.HeaderSize()
footer := uint64(0)
// The GPT header is also at the end of the partition table
if pt.Type == "gpt" {
footer = header
}
start := pt.AlignUp(header)
size = pt.AlignUp(size)
var rootIdx = -1
for idx := range pt.Partitions {
partition := &pt.Partitions[idx]
if len(entityPath(partition, "/")) != 0 {
rootIdx = idx
continue
}
partition.Start = start
partition.Size = pt.AlignUp(partition.Size)
start += partition.Size
}
if rootIdx < 0 {
panic("no root filesystem found; this is a programming error")
}
root := &pt.Partitions[rootIdx]
root.Start = start
// add the extra padding specified in the partition table
footer += pt.ExtraPadding
// If the sum of all partitions is bigger then the specified size,
// we use that instead. Grow the partition table size if needed.
end := pt.AlignUp(root.Start + footer + root.Size)
if end > size {
size = end
}
if size > pt.Size {
pt.Size = size
}
// If there is space left in the partition table, grow root
root.Size = pt.Size - root.Start
// Finally we shrink the last partition, i.e. the root partition,
// to leave space for the footer, e.g. the secondary GPT header.
root.Size -= footer
return start
}
func (pt *PartitionTable) createFilesystem(mountpoint string, size uint64) error {
rootPath := entityPath(pt, "/")
if rootPath == nil {
panic("no root mountpoint for PartitionTable")
}
var vc MountpointCreator
var entity Entity
var idx int
for idx, entity = range rootPath {
var ok bool
if vc, ok = entity.(MountpointCreator); ok {
break
}
}
if vc == nil {
panic("could not find root volume container")
}
newVol, err := vc.CreateMountpoint(mountpoint, 0)
if err != nil {
return fmt.Errorf("failed creating volume: " + err.Error())
}
vcPath := append([]Entity{newVol}, rootPath[idx:]...)
size = alignEntityBranch(vcPath, size)
resizeEntityBranch(vcPath, size)
return nil
}
// entityPath stats at ent and searches for an Entity with a Mountpoint equal
// to the target. Returns a slice of all the Entities leading to the Mountable
// in reverse order. If no Entity has the target as a Mountpoint, returns nil.
// If a slice is returned, the last element is always the starting Entity ent
// and the first element is always a Mountable with a Mountpoint equal to the
// target.
func entityPath(ent Entity, target string) []Entity {
switch e := ent.(type) {
case Mountable:
if target == e.GetMountpoint() {
return []Entity{ent}
}
case Container:
for idx := uint(0); idx < e.GetItemCount(); idx++ {
child := e.GetChild(idx)
path := entityPath(child, target)
if path != nil {
path = append(path, e)
return path
}
}
}
return nil
}
type MountableCallback func(mnt Mountable, path []Entity) error
func forEachMountable(c Container, path []Entity, cb MountableCallback) error {
for idx := uint(0); idx < c.GetItemCount(); idx++ {
child := c.GetChild(idx)
childPath := append(path, child)
var err error
switch ent := child.(type) {
case Mountable:
err = cb(ent, childPath)
case Container:
err = forEachMountable(ent, childPath, cb)
}
if err != nil {
return err
}
}
return nil
}
// ForEachMountable runs the provided callback function on each Mountable in
// the PartitionTable.
func (pt *PartitionTable) ForEachMountable(cb MountableCallback) error {
return forEachMountable(pt, []Entity{pt}, cb)
}
// FindMountable returns the Mountable entity with the given mountpoint in the
// PartitionTable. Returns nil if no Entity has the target as a Mountpoint.
func (pt *PartitionTable) FindMountable(mountpoint string) Mountable {
path := entityPath(pt, mountpoint)
if len(path) == 0 {
return nil
}
// first path element is guaranteed to be Mountable
return path[0].(Mountable)
}
func clampFSSize(mountpoint string, size uint64) uint64 {
// set a minimum size of 1GB for all mountpoints
// with the exception for '/boot' (= 500 MB)
var minSize uint64 = 1073741824
if mountpoint == "/boot" {
minSize = 524288000
}
if minSize > size {
return minSize
}
return size
}
func alignEntityBranch(path []Entity, size uint64) uint64 {
if len(path) == 0 {
return size
}
element := path[0]
if c, ok := element.(MountpointCreator); ok {
size = c.AlignUp(size)
}
return alignEntityBranch(path[1:], size)
}
// resizeEntityBranch resizes the first entity in the specified path to be at
// least the specified size and then grows every entity up the path to the
// PartitionTable accordingly.
func resizeEntityBranch(path []Entity, size uint64) {
if len(path) == 0 {
return
}
element := path[0]
if c, ok := element.(Container); ok {
containerSize := uint64(0)
for idx := uint(0); idx < c.GetItemCount(); idx++ {
if s, ok := c.GetChild(idx).(Sizeable); ok {
containerSize += s.GetSize()
} else {
break
}
}
if vc, ok := element.(VolumeContainer); ok {
containerSize += vc.MetadataSize()
}
if containerSize > size {
size = containerSize
}
}
if sz, ok := element.(Sizeable); ok {
if !sz.EnsureSize(size) {
return
}
}
resizeEntityBranch(path[1:], size)
}
// GenUUID generates and sets UUIDs for all Partitions in the PartitionTable if
// the layout is GPT.
func (pt *PartitionTable) GenUUID(rng *rand.Rand) {
if pt.UUID == "" {
pt.UUID = uuid.Must(newRandomUUIDFromReader(rng)).String()
}
}
// ensureLVM will ensure that the root partition is on an LVM volume, i.e. if
// it currently is not, it will wrap it in one
func (pt *PartitionTable) ensureLVM() error {
rootPath := entityPath(pt, "/")
if rootPath == nil {
panic("no root mountpoint for PartitionTable")
}
// we need a /boot partition to boot LVM, ensure one exists
bootPath := entityPath(pt, "/boot")
if bootPath == nil {
_, err := pt.CreateMountpoint("/boot", 512*1024*1024)
if err != nil {
return err
}
rootPath = entityPath(pt, "/")
}
parent := rootPath[1] // NB: entityPath has reversed order
if _, ok := parent.(*LVMLogicalVolume); ok {
return nil
} else if part, ok := parent.(*Partition); ok {
filesystem := part.Payload
vg := &LVMVolumeGroup{
Name: "rootvg",
Description: "created via lvm2 and osbuild",
}
_, err := vg.CreateLogicalVolume("root", part.Size, filesystem)
if err != nil {
panic(fmt.Sprintf("Could not create LV: %v", err))
}
part.Payload = vg
// reset it so it will be grown later
part.Size = 0
if pt.Type == "gpt" {
part.Type = LVMPartitionGUID
} else {
part.Type = "8e"
}
} else {
panic("unsupported parent for LVM")
}
return nil
}
func (pt *PartitionTable) GetBuildPackages() []string {
packages := []string{}
hasLVM := false
hasBtrfs := false
hasXFS := false
hasFAT := false
hasEXT4 := false
hasLUKS := false
introspectPT := func(e Entity, path []Entity) error {
switch ent := e.(type) {
case *LVMLogicalVolume:
hasLVM = true
case *Btrfs:
hasBtrfs = true
case *Filesystem:
switch ent.GetFSType() {
case "vfat":
hasFAT = true
case "btrfs":
hasBtrfs = true
case "xfs":
hasXFS = true
case "ext4":
hasEXT4 = true
}
case *LUKSContainer:
hasLUKS = true
}
return nil
}
_ = pt.ForEachEntity(introspectPT)
// TODO: LUKS
if hasLVM {
packages = append(packages, "lvm2")
}
if hasBtrfs {
packages = append(packages, "btrfs-progs")
}
if hasXFS {
packages = append(packages, "xfsprogs")
}
if hasFAT {
packages = append(packages, "dosfstools")
}
if hasEXT4 {
packages = append(packages, "e2fsprogs")
}
if hasLUKS {
packages = append(packages,
"clevis",
"clevis-luks",
"cryptsetup",
)
}
return packages
}

View file

@ -1,161 +0,0 @@
package distro
import (
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/rhsm/facts"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
type BootMode uint64
const (
BOOT_NONE BootMode = iota
BOOT_LEGACY
BOOT_UEFI
BOOT_HYBRID
)
func (m BootMode) String() string {
switch m {
case BOOT_NONE:
return "none"
case BOOT_LEGACY:
return "legacy"
case BOOT_UEFI:
return "uefi"
case BOOT_HYBRID:
return "hybrid"
default:
panic("invalid boot mode")
}
}
// A Distro represents composer's notion of what a given distribution is.
type Distro interface {
// Returns the name of the distro.
Name() string
// Returns the release version of the distro. This is used in repo
// files on the host system and required for the subscription support.
Releasever() string
// Returns the module platform id of the distro. This is used by DNF
// for modularity support.
ModulePlatformID() string
// Returns the ostree reference template
OSTreeRef() string
// Returns a sorted list of the names of the architectures this distro
// supports.
ListArches() []string
// Returns an object representing the given architecture as support
// by this distro.
GetArch(arch string) (Arch, error)
}
// An Arch represents a given distribution's support for a given architecture.
type Arch interface {
// Returns the name of the architecture.
Name() string
// Returns a sorted list of the names of the image types this architecture
// supports.
ListImageTypes() []string
// Returns an object representing a given image format for this architecture,
// on this distro.
GetImageType(imageType string) (ImageType, error)
// Returns the parent distro
Distro() Distro
}
// An ImageType represents a given distribution's support for a given Image Type
// for a given architecture.
type ImageType interface {
// Returns the name of the image type.
Name() string
// Returns the parent architecture
Arch() Arch
// Returns the canonical filename for the image type.
Filename() string
// Retrns the MIME-type for the image type.
MIMEType() string
// Returns the default OSTree ref for the image type.
OSTreeRef() string
// Returns the proper image size for a given output format. If the input size
// is 0 the default value for the format will be returned.
Size(size uint64) uint64
// Returns the corresponding partion type ("gpt", "dos") or "" the image type
// has no partition table. Only support for RHEL 8.5+
PartitionType() string
// Returns the corresponding boot mode ("legacy", "uefi", "hybrid") or "none"
BootMode() BootMode
// Returns the names of the pipelines that set up the build environment (buildroot).
BuildPipelines() []string
// Returns the names of the pipelines that create the image.
PayloadPipelines() []string
// Returns the package set names safe to install custom packages via custom repositories.
PayloadPackageSets() []string
// Returns named arrays of package set names which should be depsolved in a chain.
PackageSetsChains() map[string][]string
// Returns the names of the stages that will produce the build output.
Exports() []string
// Returns an osbuild manifest, containing the sources and pipeline necessary
// to build an image, given output format with all packages and customizations
// specified in the given blueprint; it also returns any warnings (e.g.
// deprecation notices) generated by the manifest.
// The packageSpecSets must be labelled in the same way as the originating PackageSets.
Manifest(bp *blueprint.Blueprint, options ImageOptions, repos []rpmmd.RepoConfig, seed int64) (*manifest.Manifest, []string, error)
}
// The ImageOptions specify options for a specific image build
type ImageOptions struct {
Size uint64
OSTree *ostree.ImageOptions
Subscription *subscription.ImageOptions
Facts *facts.ImageOptions
}
type BasePartitionTableMap map[string]disk.PartitionTable
// Fallbacks: When a new method is added to an interface to provide to provide
// information that isn't available for older implementations, the older
// methods should return a fallback/default value by calling the appropriate
// function from below.
// Example: Exports() simply returns "assembler" for older image type
// implementations that didn't produce v1 manifests that have named pipelines.
func BuildPipelinesFallback() []string {
return []string{"build"}
}
func PayloadPipelinesFallback() []string {
return []string{"os", "assembler"}
}
func ExportsFallback() []string {
return []string{"assembler"}
}
func PayloadPackageSets() []string {
return []string{}
}

View file

@ -1,566 +0,0 @@
package distro_test
import (
"crypto/sha256"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distro/distro_test_common"
"github.com/osbuild/osbuild-composer/internal/distroregistry"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDistro_Manifest(t *testing.T) {
distro_test_common.TestDistro_Manifest(
t,
"../../test/data/manifests/",
"*",
distroregistry.NewDefault(),
false, // This test case does not check for changes in the imageType package sets!
"",
"",
)
}
// Ensure that all package sets defined in the package set chains are defined for the image type
func TestImageType_PackageSetsChains(t *testing.T) {
distros := distroregistry.NewDefault()
for _, distroName := range distros.List() {
d := distros.GetDistro(distroName)
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
require.Nil(t, err)
for _, imageTypeName := range arch.ListImageTypes() {
t.Run(fmt.Sprintf("%s/%s/%s", distroName, archName, imageTypeName), func(t *testing.T) {
imageType, err := arch.GetImageType(imageTypeName)
require.Nil(t, err)
// set up bare minimum args for image type
var customizations *blueprint.Customizations
if imageType.Name() == "edge-simplified-installer" {
customizations = &blueprint.Customizations{
InstallationDevice: "/dev/null",
}
}
bp := blueprint.Blueprint{
Customizations: customizations,
}
options := distro.ImageOptions{
OSTree: &ostree.ImageOptions{
URL: "https://example.com", // required by some image types
},
}
manifest, _, err := imageType.Manifest(&bp, options, nil, 0)
require.NoError(t, err)
imagePkgSets := manifest.GetPackageSetChains()
for packageSetName := range imageType.PackageSetsChains() {
_, ok := imagePkgSets[packageSetName]
if !ok {
// in the new pipeline generation logic the name of the package
// set chains are taken from the pipelines and do not match the
// package set names.
// TODO: redefine package set chains to make this unneccesary
switch packageSetName {
case "packages":
_, ok = imagePkgSets["os"]
if !ok {
_, ok = imagePkgSets["ostree-tree"]
}
}
}
assert.Truef(t, ok, "package set %q defined in a package set chain is not present in the image package sets", packageSetName)
}
})
}
}
}
}
// Ensure all image types report the correct names for their pipelines.
// Each image type contains a list of build and payload pipelines. They are
// needed for knowing the names of pipelines from the static object without
// having access to a manifest, which we need when parsing metadata from build
// results.
func TestImageTypePipelineNames(t *testing.T) {
// types for parsing the opaque manifest with just the fields we care about
type rpmStageOptions struct {
GPGKeys []string `json:"gpgkeys"`
}
type stage struct {
Type string `json:"type"`
Options rpmStageOptions `json:"options"`
}
type pipeline struct {
Name string `json:"name"`
Stages []stage `json:"stages"`
}
type manifest struct {
Pipelines []pipeline `json:"pipelines"`
}
assert := assert.New(t)
distros := distroregistry.NewDefault()
for _, distroName := range distros.List() {
d := distros.GetDistro(distroName)
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.Nil(err)
for _, imageTypeName := range arch.ListImageTypes() {
t.Run(fmt.Sprintf("%s/%s/%s", distroName, archName, imageTypeName), func(t *testing.T) {
imageType, err := arch.GetImageType(imageTypeName)
assert.Nil(err)
// set up bare minimum args for image type
var customizations *blueprint.Customizations
if imageType.Name() == "edge-simplified-installer" {
customizations = &blueprint.Customizations{
InstallationDevice: "/dev/null",
}
}
bp := blueprint.Blueprint{
Customizations: customizations,
}
options := distro.ImageOptions{}
// this repo's gpg keys should get included in the os
// pipeline's rpm stage
repos := []rpmmd.RepoConfig{
{
Name: "payload",
BaseURLs: []string{"http://payload.example.com"},
PackageSets: imageType.PayloadPackageSets(),
GPGKeys: []string{"payload-gpg-key"},
CheckGPG: common.ToPtr(true),
},
}
seed := int64(0)
// Add ostree options for image types that require them
options.OSTree = &ostree.ImageOptions{
URL: "https://example.com",
}
// Pipelines that require package sets will fail if none
// are defined. OS pipelines require a kernel.
// Add kernel and filesystem to every pipeline so that the
// manifest creation doesn't fail.
allPipelines := append(imageType.BuildPipelines(), imageType.PayloadPipelines()...)
minimalPackageSet := []rpmmd.PackageSpec{
{Name: "kernel", Checksum: "sha256:a0c936696eb7d5ee3192bf53b9d281cecbb40ca9db520de72cb95817ad92ac72"},
{Name: "filesystem", Checksum: "sha256:6b4bf18ba28ccbdd49f2716c9f33c9211155ff703fa6c195c78a07bd160da0eb"},
}
packageSets := make(map[string][]rpmmd.PackageSpec, len(allPipelines))
for _, plName := range allPipelines {
packageSets[plName] = minimalPackageSet
}
m, _, err := imageType.Manifest(&bp, options, repos, seed)
assert.NoError(err)
containers := make(map[string][]container.Spec, 0)
ostreeSources := m.GetOSTreeSourceSpecs()
commits := make(map[string][]ostree.CommitSpec, len(ostreeSources))
for name, commitSources := range ostreeSources {
commitSpecs := make([]ostree.CommitSpec, len(commitSources))
for idx, commitSource := range commitSources {
commitSpecs[idx] = ostree.CommitSpec{
Ref: commitSource.Ref,
URL: commitSource.URL,
Checksum: fmt.Sprintf("%x", sha256.Sum256([]byte(commitSource.URL+commitSource.Ref))),
}
}
commits[name] = commitSpecs
}
mf, err := m.Serialize(packageSets, containers, commits)
assert.NoError(err)
pm := new(manifest)
err = json.Unmarshal(mf, pm)
assert.NoError(err)
assert.Equal(len(allPipelines), len(pm.Pipelines))
for idx := range pm.Pipelines {
// manifest pipeline names should be identical to the ones
// defined in the image type and in the same order
assert.Equal(allPipelines[idx], pm.Pipelines[idx].Name)
if pm.Pipelines[idx].Name == "os" {
rpmStagePresent := false
for _, s := range pm.Pipelines[idx].Stages {
if s.Type == "org.osbuild.rpm" {
rpmStagePresent = true
if imageTypeName != "azure-eap7-rhui" {
// NOTE (akoutsou): Ideally, at some point we will
// have a good way of reading what's supported by
// each image type and we can skip or adapt tests
// based on this information. For image types with
// a preset workload, payload packages are ignored
// and dropped and so are the payload
// repo gpg keys.
assert.Equal(repos[0].GPGKeys, s.Options.GPGKeys)
}
}
}
// make sure the gpg keys check was reached
assert.True(rpmStagePresent)
}
}
// The last pipeline should match the export pipeline.
// This might change in the future, but for now, let's make
// sure they match.
assert.Equal(imageType.Exports()[0], pm.Pipelines[len(pm.Pipelines)-1].Name)
})
}
}
}
}
// Ensure repositories are assigned to package sets properly.
//
// Each package set should include all the global repositories as well as any
// pipeline/package-set specific repositories.
func TestPipelineRepositories(t *testing.T) {
require := require.New(t)
type testCase struct {
// Repo configs for pipeline generator
repos []rpmmd.RepoConfig
// Expected result: map of pipelines to repo names (we only check names for the test).
// Use the pipeline name * for global repos.
result map[string][]stringSet
}
testCases := map[string]testCase{
"globalonly": { // only global repos: most common scenario
repos: []rpmmd.RepoConfig{
{
Name: "global-1",
BaseURLs: []string{"http://global-1.example.com"},
},
{
Name: "global-2",
BaseURLs: []string{"http://global-2.example.com"},
},
},
result: map[string][]stringSet{
"*": {newStringSet([]string{"global-1", "global-2"})},
},
},
"global+build": { // global repos with build-specific repos: secondary common scenario
repos: []rpmmd.RepoConfig{
{
Name: "global-11",
BaseURLs: []string{"http://global-11.example.com"},
},
{
Name: "global-12",
BaseURLs: []string{"http://global-12.example.com"},
},
{
Name: "build-1",
BaseURLs: []string{"http://build-1.example.com"},
PackageSets: []string{"build"},
},
{
Name: "build-2",
BaseURLs: []string{"http://build-2.example.com"},
PackageSets: []string{"build"},
},
},
result: map[string][]stringSet{
"*": {newStringSet([]string{"global-11", "global-12"})},
"build": {newStringSet([]string{"build-1", "build-2"})},
},
},
"global+os": { // global repos with os-specific repos
repos: []rpmmd.RepoConfig{
{
Name: "global-21",
BaseURLs: []string{"http://global-11.example.com"},
},
{
Name: "global-22",
BaseURLs: []string{"http://global-12.example.com"},
},
{
Name: "os-1",
BaseURLs: []string{"http://os-1.example.com"},
PackageSets: []string{"os"},
},
{
Name: "os-2",
BaseURLs: []string{"http://os-2.example.com"},
PackageSets: []string{"os"},
},
},
result: map[string][]stringSet{
"*": {newStringSet([]string{"global-21", "global-22"})},
"os": {newStringSet([]string{"os-1", "os-2"}), newStringSet([]string{"os-1", "os-2"})},
},
},
"global+os+payload": { // global repos with os-specific repos and (user-defined) payload repositories
repos: []rpmmd.RepoConfig{
{
Name: "global-21",
BaseURLs: []string{"http://global-11.example.com"},
},
{
Name: "global-22",
BaseURLs: []string{"http://global-12.example.com"},
},
{
Name: "os-1",
BaseURLs: []string{"http://os-1.example.com"},
PackageSets: []string{"os"},
},
{
Name: "os-2",
BaseURLs: []string{"http://os-2.example.com"},
PackageSets: []string{"os"},
},
{
Name: "payload",
BaseURLs: []string{"http://payload.example.com"},
// User-defined payload repositories automatically get the "blueprint" key.
// This is handled by the APIs.
PackageSets: []string{"blueprint"},
},
},
result: map[string][]stringSet{
"*": {newStringSet([]string{"global-21", "global-22"})},
"os": {
// chain with payload repo only in the second set for the blueprint package depsolve
newStringSet([]string{"os-1", "os-2"}),
newStringSet([]string{"os-1", "os-2", "payload"})},
},
},
"noglobal": { // no global repositories; only pipeline restricted ones (unrealistic but technically valid)
repos: []rpmmd.RepoConfig{
{
Name: "build-1",
BaseURLs: []string{"http://build-1.example.com"},
PackageSets: []string{"build"},
},
{
Name: "build-2",
BaseURLs: []string{"http://build-2.example.com"},
PackageSets: []string{"build"},
},
{
Name: "os-1",
BaseURLs: []string{"http://os-1.example.com"},
PackageSets: []string{"os"},
},
{
Name: "os-2",
BaseURLs: []string{"http://os-2.example.com"},
PackageSets: []string{"os"},
},
{
Name: "anaconda-1",
BaseURLs: []string{"http://anaconda-1.example.com"},
PackageSets: []string{"anaconda-tree"},
},
{
Name: "container-1",
BaseURLs: []string{"http://container-1.example.com"},
PackageSets: []string{"container-tree"},
},
{
Name: "coi-1",
BaseURLs: []string{"http://coi-1.example.com"},
PackageSets: []string{"coi-tree"},
},
},
result: map[string][]stringSet{
"*": nil,
"build": {newStringSet([]string{"build-1", "build-2"})},
"os": {newStringSet([]string{"os-1", "os-2"}), newStringSet([]string{"os-1", "os-2"})},
"anaconda-tree": {newStringSet([]string{"anaconda-1"})},
"container-tree": {newStringSet([]string{"container-1"})},
"coi-tree": {newStringSet([]string{"coi-1"})},
},
},
"global+unknown": { // package set names that don't match a pipeline are ignored
repos: []rpmmd.RepoConfig{
{
Name: "global-1",
BaseURLs: []string{"http://global-1.example.com"},
},
{
Name: "global-2",
BaseURLs: []string{"http://global-2.example.com"},
},
{
Name: "custom-1",
BaseURLs: []string{"http://custom.example.com"},
PackageSets: []string{"notapipeline"},
},
},
result: map[string][]stringSet{
"*": {newStringSet([]string{"global-1", "global-2"})},
},
},
"none": { // empty
repos: []rpmmd.RepoConfig{},
result: map[string][]stringSet{},
},
}
distros := distroregistry.NewDefault()
for tName, tCase := range testCases {
t.Run(tName, func(t *testing.T) {
for _, distroName := range distros.List() {
d := distros.GetDistro(distroName)
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
require.Nil(err)
for _, imageTypeName := range arch.ListImageTypes() {
if imageTypeName == "azure-eap7-rhui" {
// NOTE (akoutsou): Ideally, at some point we will
// have a good way of reading what's supported by
// each image type and we can skip or adapt tests
// based on this information. For image types with
// a preset workload, payload packages are ignored
// and dropped.
continue
}
t.Run(fmt.Sprintf("%s/%s/%s", distroName, archName, imageTypeName), func(t *testing.T) {
imageType, err := arch.GetImageType(imageTypeName)
require.Nil(err)
// set up bare minimum args for image type
var customizations *blueprint.Customizations
if imageType.Name() == "edge-simplified-installer" {
customizations = &blueprint.Customizations{
InstallationDevice: "/dev/null",
}
}
bp := blueprint.Blueprint{
Customizations: customizations,
Packages: []blueprint.Package{
{Name: "filesystem"},
},
}
options := distro.ImageOptions{}
// Add ostree options for image types that require them
options.OSTree = &ostree.ImageOptions{
URL: "https://example.com",
}
repos := tCase.repos
manifest, _, err := imageType.Manifest(&bp, options, repos, 0)
require.NoError(err)
packageSets := manifest.GetPackageSetChains()
var globals stringSet
if len(tCase.result["*"]) > 0 {
globals = tCase.result["*"][0]
}
for psName, psChain := range packageSets {
expChain := tCase.result[psName]
if len(expChain) > 0 {
// if we specified an expected chain it should match the returned.
if len(expChain) != len(psChain) {
t.Fatalf("expected %d package sets in the %q chain; got %d", len(expChain), psName, len(psChain))
}
} else {
// if we didn't, initialise to empty before merging globals
expChain = make([]stringSet, len(psChain))
}
for idx := range expChain {
// merge the globals into each expected set
expChain[idx] = expChain[idx].Merge(globals)
}
for setIdx, set := range psChain {
// collect repositories in the package set
repoNamesSet := newStringSet(nil)
for _, repo := range set.Repositories {
repoNamesSet.Add(repo.Name)
}
// expected set for current package set should be merged with globals
expected := expChain[setIdx]
if !repoNamesSet.Equals(expected) {
t.Errorf("repos for package set %q [idx: %d] %s (distro %q image type %q) do not match expected %s", psName, setIdx, repoNamesSet, d.Name(), imageType.Name(), expected)
}
}
}
})
}
}
}
})
}
}
// a very basic implementation of a Set of strings
type stringSet struct {
elems map[string]bool
}
func newStringSet(init []string) stringSet {
s := stringSet{elems: make(map[string]bool)}
for _, elem := range init {
s.Add(elem)
}
return s
}
func (s stringSet) String() string {
elemSlice := make([]string, 0, len(s.elems))
for elem := range s.elems {
elemSlice = append(elemSlice, elem)
}
return "{" + strings.Join(elemSlice, ", ") + "}"
}
func (s stringSet) Add(elem string) {
s.elems[elem] = true
}
func (s stringSet) Contains(elem string) bool {
return s.elems[elem]
}
func (s stringSet) Equals(other stringSet) bool {
if len(s.elems) != len(other.elems) {
return false
}
for elem := range s.elems {
if !other.Contains(elem) {
return false
}
}
return true
}
func (s stringSet) Merge(other stringSet) stringSet {
merged := newStringSet(nil)
for elem := range s.elems {
merged.Add(elem)
}
for elem := range other.elems {
merged.Add(elem)
}
return merged
}

View file

@ -1,623 +0,0 @@
package distro_test_common
import (
"encoding/json"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distroregistry"
"github.com/osbuild/osbuild-composer/internal/dnfjson"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/rhsm/facts"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
const RandomTestSeed = 0
func TestDistro_Manifest(t *testing.T, pipelinePath string, prefix string, registry *distroregistry.Registry, depsolvePkgSets bool, dnfCacheDir, dnfJsonPath string) {
assert := assert.New(t)
fileNames, err := filepath.Glob(filepath.Join(pipelinePath, prefix))
assert.NoErrorf(err, "Could not read pipelines directory '%s': %v", pipelinePath, err)
require.Greaterf(t, len(fileNames), 0, "No pipelines found in %s for %s", pipelinePath, prefix)
for _, fileName := range fileNames {
type repository struct {
BaseURL string `json:"baseurl,omitempty"`
Metalink string `json:"metalink,omitempty"`
MirrorList string `json:"mirrorlist,omitempty"`
GPGKey string `json:"gpgkey,omitempty"`
CheckGPG bool `json:"check_gpg,omitempty"`
PackageSets []string `json:"package-sets,omitempty"`
}
type ostreeOptions struct {
Ref string `json:"ref"`
URL string `json:"url"`
Parent string `json:"parent"`
RHSM bool `json:"rhsm"`
}
type composeRequest struct {
Distro string `json:"distro"`
Arch string `json:"arch"`
ImageType string `json:"image-type"`
Repositories []repository `json:"repositories"`
Blueprint *blueprint.Blueprint `json:"blueprint"`
OSTree ostreeOptions `json:"ostree"`
}
var tt struct {
ComposeRequest *composeRequest `json:"compose-request"`
PackageSpecSets map[string][]rpmmd.PackageSpec `json:"rpmmd"`
Manifest manifest.OSBuildManifest `json:"manifest,omitempty"`
Containers map[string][]container.Spec `json:"containers,omitempty"`
OSTreeCommits map[string][]ostree.CommitSpec `json:"ostree-commits,omitempty"`
}
file, err := os.ReadFile(fileName)
assert.NoErrorf(err, "Could not read test-case '%s': %v", fileName, err)
err = json.Unmarshal([]byte(file), &tt)
assert.NoErrorf(err, "Could not parse test-case '%s': %v", fileName, err)
if tt.ComposeRequest == nil || tt.ComposeRequest.Blueprint == nil {
t.Logf("Skipping '%s'.", fileName)
continue
}
repos := make([]rpmmd.RepoConfig, len(tt.ComposeRequest.Repositories))
for i, repo := range tt.ComposeRequest.Repositories {
var urls []string
if repo.BaseURL != "" {
urls = []string{repo.BaseURL}
}
var keys []string
if repo.GPGKey != "" {
keys = []string{repo.GPGKey}
}
repos[i] = rpmmd.RepoConfig{
Name: fmt.Sprintf("repo-%d", i),
BaseURLs: urls,
Metalink: repo.Metalink,
MirrorList: repo.MirrorList,
GPGKeys: keys,
CheckGPG: common.ToPtr(repo.CheckGPG),
PackageSets: repo.PackageSets,
}
}
t.Run(path.Base(fileName), func(t *testing.T) {
require.NoError(t, err)
d := registry.GetDistro(tt.ComposeRequest.Distro)
if d == nil {
t.Errorf("unknown distro: %v", tt.ComposeRequest.Distro)
return
}
arch, err := d.GetArch(tt.ComposeRequest.Arch)
if err != nil {
t.Errorf("unknown arch: %v", tt.ComposeRequest.Arch)
return
}
imageType, err := arch.GetImageType(tt.ComposeRequest.ImageType)
if err != nil {
t.Errorf("unknown image type: %v", tt.ComposeRequest.ImageType)
return
}
ostreeOptions := &ostree.ImageOptions{
ImageRef: tt.ComposeRequest.OSTree.Ref,
ParentRef: tt.ComposeRequest.OSTree.Parent,
URL: tt.ComposeRequest.OSTree.URL,
RHSM: tt.ComposeRequest.OSTree.RHSM,
}
options := distro.ImageOptions{
Size: imageType.Size(0),
OSTree: ostreeOptions,
Facts: &facts.ImageOptions{
APIType: facts.TEST_APITYPE,
},
}
var imgPackageSpecSets map[string][]rpmmd.PackageSpec
// depsolve the image's package set to catch changes in the image's default package set.
// downside is that this takes long time
if depsolvePkgSets {
require.NotEmptyf(t, dnfCacheDir, "DNF cache directory path must be provided when chosen to depsolve image package sets")
require.NotEmptyf(t, dnfJsonPath, "path to 'dnf-json' must be provided when chosen to depsolve image package sets")
imgPackageSpecSets = getImageTypePkgSpecSets(
imageType,
*tt.ComposeRequest.Blueprint,
options,
repos,
dnfCacheDir,
dnfJsonPath,
)
} else {
imgPackageSpecSets = tt.PackageSpecSets
}
manifest, _, err := imageType.Manifest(tt.ComposeRequest.Blueprint, options, repos, RandomTestSeed)
if err != nil {
t.Errorf("distro.Manifest() error = %v", err)
return
}
got, err := manifest.Serialize(imgPackageSpecSets, tt.Containers, tt.OSTreeCommits)
if (err == nil && tt.Manifest == nil) || (err != nil && tt.Manifest != nil) {
t.Errorf("distro.Manifest() error = %v", err)
return
}
if tt.Manifest != nil {
var expected, actual interface{}
err = json.Unmarshal(tt.Manifest, &expected)
require.NoError(t, err)
err = json.Unmarshal(got, &actual)
require.NoError(t, err)
diff := cmp.Diff(expected, actual)
require.Emptyf(t, diff, "Distro: %s\nArch: %s\nImage type: %s\nTest case file: %s\n", d.Name(), arch.Name(), imageType.Name(), fileName)
}
})
}
}
func getImageTypePkgSpecSets(imageType distro.ImageType, bp blueprint.Blueprint, options distro.ImageOptions, repos []rpmmd.RepoConfig, cacheDir, dnfJsonPath string) map[string][]rpmmd.PackageSpec {
manifest, _, err := imageType.Manifest(&bp, options, repos, 0)
if err != nil {
panic("Could not generate manifest for package sets: " + err.Error())
}
imgPackageSets := manifest.GetPackageSetChains()
solver := dnfjson.NewSolver(imageType.Arch().Distro().ModulePlatformID(),
imageType.Arch().Distro().Releasever(),
imageType.Arch().Name(),
imageType.Arch().Distro().Name(),
cacheDir)
solver.SetDNFJSONPath(dnfJsonPath)
depsolvedSets := make(map[string][]rpmmd.PackageSpec)
for name, packages := range imgPackageSets {
res, err := solver.Depsolve(packages)
if err != nil {
panic("Could not depsolve: " + err.Error())
}
depsolvedSets[name] = res
}
return depsolvedSets
}
func isOSTree(imgType distro.ImageType) bool {
return imgType.OSTreeRef() != ""
}
var knownKernels = []string{"kernel", "kernel-debug", "kernel-rt"}
// Returns the number of known kernels in the package list
func kernelCount(imgType distro.ImageType, bp blueprint.Blueprint) int {
ostreeOptions := &ostree.ImageOptions{
URL: "https://example.com", // required by some image types
}
manifest, _, err := imgType.Manifest(&bp, distro.ImageOptions{OSTree: ostreeOptions}, nil, 0)
if err != nil {
panic(err)
}
sets := manifest.GetPackageSetChains()
// Use a map to count unique kernels in a package set. If the same kernel
// name appears twice, it will only be installed once, so we only count it
// once.
kernels := make(map[string]bool)
for _, name := range []string{
// payload package set names
"os", "ostree-tree", "anaconda-tree",
"packages", "installer",
} {
for _, pset := range sets[name] {
for _, pkg := range pset.Include {
for _, kernel := range knownKernels {
if kernel == pkg {
kernels[kernel] = true
}
}
}
if len(kernels) > 0 {
// BUG: some RHEL image types contain both 'packages'
// and 'installer' even though only 'installer' is used
// this counts the kernel package twice. None of these
// sets should appear more than once, so return the count
// for the first package set that has at least one kernel.
return len(kernels)
}
}
}
return len(kernels)
}
func TestDistro_KernelOption(t *testing.T, d distro.Distro) {
skipList := map[string]bool{
// Ostree installers and raw images download a payload to embed or
// deploy. The kernel is part of the payload so it doesn't appear in
// the image type's package lists.
"iot-installer": true,
"edge-installer": true,
"edge-simplified-installer": true,
"iot-raw-image": true,
"edge-raw-image": true,
"edge-ami": true,
// the tar image type is a minimal image type which is not expected to
// be usable without a blueprint (see commit 83a63aaf172f556f6176e6099ffaa2b5357b58f5).
"tar": true,
// containers don't have kernels
"container": true,
// image installer on Fedora doesn't support kernel customizations
// on RHEL we support kernel name
// TODO: Remove when we unify the allowed options
"image-installer": true,
"live-installer": true,
}
{ // empty blueprint: all image types should just have the default kernel
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.NoError(t, err)
for _, typeName := range arch.ListImageTypes() {
if skipList[typeName] {
continue
}
imgType, err := arch.GetImageType(typeName)
assert.NoError(t, err)
nk := kernelCount(imgType, blueprint.Blueprint{})
if nk != 1 {
assert.Fail(t, fmt.Sprintf("%s Kernel count", d.Name()),
"Image type %s (arch %s) specifies %d Kernel packages", typeName, archName, nk)
}
}
}
}
{ // kernel in blueprint: the specified kernel replaces the default
for _, kernelName := range []string{"kernel", "kernel-debug"} {
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Kernel: &blueprint.KernelCustomization{
Name: kernelName,
},
},
}
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.NoError(t, err)
for _, typeName := range arch.ListImageTypes() {
if typeName != "image-installer" {
continue
}
if typeName != "live-installer" {
continue
}
if skipList[typeName] {
continue
}
imgType, err := arch.GetImageType(typeName)
assert.NoError(t, err)
nk := kernelCount(imgType, bp)
// ostree image types should have only one kernel
// other image types should have at least 1
if nk < 1 || (nk != 1 && isOSTree(imgType)) {
assert.Fail(t, fmt.Sprintf("%s Kernel count", d.Name()),
"Image type %s (arch %s) specifies %d Kernel packages", typeName, archName, nk)
}
}
}
}
}
}
func TestDistro_OSTreeOptions(t *testing.T, d distro.Distro) {
// test that ostree parameters are properly resolved by image functions that should support them
typesWithParent := map[string]bool{ // image types that support specifying a parent commit
"edge-commit": true,
"edge-container": true,
"iot-commit": true,
"iot-container": true,
}
typesWithPayload := map[string]bool{
"edge-ami": true,
"edge-installer": true,
"edge-raw-image": true,
"edge-simplified-installer": true,
"iot-ami": true,
"iot-installer": true,
"iot-raw-image": true,
"iot-simplified-installer": true,
}
assert := assert.New(t)
{ // empty options: payload ref should equal default
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.NoError(err)
for _, typeName := range arch.ListImageTypes() {
bp := &blueprint.Blueprint{}
if strings.HasSuffix(typeName, "simplified-installer") {
// simplified installers require installation device
bp = &blueprint.Blueprint{
Customizations: &blueprint.Customizations{
InstallationDevice: "/dev/sda42",
},
}
}
imgType, err := arch.GetImageType(typeName)
assert.NoError(err)
ostreeOptions := ostree.ImageOptions{}
if typesWithPayload[typeName] {
// payload types require URL
ostreeOptions.URL = "https://example.com/repo"
}
options := distro.ImageOptions{OSTree: &ostreeOptions}
m, _, err := imgType.Manifest(bp, options, nil, 0)
assert.NoError(err)
nrefs := 0
// If a manifest returns an ostree source spec, the ref should
// match the default.
for _, commits := range m.GetOSTreeSourceSpecs() {
for _, commit := range commits {
assert.Equal(options.OSTree.URL, commit.URL, "url does not match expected for image type %q\n", typeName)
assert.Equal(imgType.OSTreeRef(), commit.Ref, "ref does not match expected for image type %q\n", typeName)
nrefs++
}
}
nexpected := 0
if typesWithPayload[typeName] {
// image types with payload should return a ref
nexpected = 1
}
assert.Equal(nexpected, nrefs, "incorrect ref count for image type %q\n", typeName)
}
}
}
{ // ImageRef set: should be returned as payload ref - no parent for commits and containers
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.NoError(err)
for _, typeName := range arch.ListImageTypes() {
bp := &blueprint.Blueprint{}
if strings.HasSuffix(typeName, "simplified-installer") {
// simplified installers require installation device
bp = &blueprint.Blueprint{
Customizations: &blueprint.Customizations{
InstallationDevice: "/dev/sda42",
},
}
}
imgType, err := arch.GetImageType(typeName)
assert.NoError(err)
ostreeOptions := ostree.ImageOptions{
ImageRef: "test/x86_64/01",
}
if typesWithPayload[typeName] {
// payload types require URL
ostreeOptions.URL = "https://example.com/repo"
}
options := distro.ImageOptions{OSTree: &ostreeOptions}
m, _, err := imgType.Manifest(bp, options, nil, 0)
assert.NoError(err)
nrefs := 0
// if a manifest returns an ostree source spec, the ref should
// match the default
for _, commits := range m.GetOSTreeSourceSpecs() {
for _, commit := range commits {
assert.Equal(options.OSTree.URL, commit.URL, "url does not match expected for image type %q\n", typeName)
assert.Equal(options.OSTree.ImageRef, commit.Ref, "ref does not match expected for image type %q\n", typeName)
nrefs++
}
}
nexpected := 0
if typesWithPayload[typeName] {
// image types with payload should return a ref
nexpected = 1
}
assert.Equal(nexpected, nrefs, "incorrect ref count for image type %q\n", typeName)
}
}
}
{ // URL always specified: should add a parent to image types that support it and the ref should match the option
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.NoError(err)
for _, typeName := range arch.ListImageTypes() {
bp := &blueprint.Blueprint{}
if strings.HasSuffix(typeName, "simplified-installer") {
// simplified installers require installation device
bp = &blueprint.Blueprint{
Customizations: &blueprint.Customizations{
InstallationDevice: "/dev/sda42",
},
}
}
imgType, err := arch.GetImageType(typeName)
assert.NoError(err)
ostreeOptions := ostree.ImageOptions{
ImageRef: "test/x86_64/01",
URL: "https://example.com/repo",
}
options := distro.ImageOptions{OSTree: &ostreeOptions}
m, _, err := imgType.Manifest(bp, options, nil, 0)
assert.NoError(err)
nrefs := 0
for _, commits := range m.GetOSTreeSourceSpecs() {
for _, commit := range commits {
assert.Equal(options.OSTree.URL, commit.URL, "url does not match expected for image type %q\n", typeName)
assert.Equal(options.OSTree.ImageRef, commit.Ref, "ref does not match expected for image type %q\n", typeName)
nrefs++
}
}
nexpected := 0
if typesWithPayload[typeName] || typesWithParent[typeName] {
// image types with payload or parent should return a ref
nexpected = 1
}
assert.Equal(nexpected, nrefs, "incorrect ref count for image type %q\n", typeName)
}
}
}
{ // URL and parent ref always specified: payload ref should be default - parent ref should match option
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.NoError(err)
for _, typeName := range arch.ListImageTypes() {
bp := &blueprint.Blueprint{}
if strings.HasSuffix(typeName, "simplified-installer") {
// simplified installers require installation device
bp = &blueprint.Blueprint{
Customizations: &blueprint.Customizations{
InstallationDevice: "/dev/sda42",
},
}
}
imgType, err := arch.GetImageType(typeName)
assert.NoError(err)
ostreeOptions := ostree.ImageOptions{
ParentRef: "test/x86_64/01",
URL: "https://example.com/repo",
}
options := distro.ImageOptions{OSTree: &ostreeOptions}
m, _, err := imgType.Manifest(bp, options, nil, 0)
assert.NoError(err)
nrefs := 0
for _, commits := range m.GetOSTreeSourceSpecs() {
for _, commit := range commits {
assert.Equal(options.OSTree.URL, commit.URL, "url does not match expected for image type %q\n", typeName)
if typesWithPayload[typeName] {
// payload ref should fall back to default
assert.Equal(imgType.OSTreeRef(), commit.Ref, "ref does not match expected for image type %q\n", typeName)
} else if typesWithParent[typeName] {
// parent ref should match option
assert.Equal(options.OSTree.ParentRef, commit.Ref, "ref does not match expected for image type %q\n", typeName)
} else {
// image type requires ostree commit but isn't specified: this shouldn't happen
panic(fmt.Sprintf("image type %q requires ostree commit but is not covered by test", typeName))
}
nrefs++
}
}
nexpected := 0
if typesWithPayload[typeName] || typesWithParent[typeName] {
// image types with payload or parent should return a ref
nexpected = 1
}
assert.Equal(nexpected, nrefs, "incorrect ref count for image type %q\n", typeName)
}
}
}
{ // All options set: all refs should match the corresponding option
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.NoError(err)
for _, typeName := range arch.ListImageTypes() {
bp := &blueprint.Blueprint{}
if strings.HasSuffix(typeName, "simplified-installer") {
// simplified installers require installation device
bp = &blueprint.Blueprint{
Customizations: &blueprint.Customizations{
InstallationDevice: "/dev/sda42",
},
}
}
imgType, err := arch.GetImageType(typeName)
assert.NoError(err)
ostreeOptions := ostree.ImageOptions{
ImageRef: "test/x86_64/01",
ParentRef: "test/x86_64/02",
URL: "https://example.com/repo",
}
options := distro.ImageOptions{OSTree: &ostreeOptions}
m, _, err := imgType.Manifest(bp, options, nil, 0)
assert.NoError(err)
nrefs := 0
for _, commits := range m.GetOSTreeSourceSpecs() {
for _, commit := range commits {
assert.Equal(options.OSTree.URL, commit.URL, "url does not match expected for image type %q\n", typeName)
if typesWithPayload[typeName] {
// payload ref should match image ref
assert.Equal(options.OSTree.ImageRef, commit.Ref, "ref does not match expected for image type %q\n", typeName)
} else if typesWithParent[typeName] {
// parent ref should match option
assert.Equal(options.OSTree.ParentRef, commit.Ref, "ref does not match expected for image type %q\n", typeName)
} else {
// image type requires ostree commit but isn't specified: this shouldn't happen
panic(fmt.Sprintf("image type %q requires ostree commit but is not covered by test", typeName))
}
nrefs++
}
}
nexpected := 0
if typesWithPayload[typeName] || typesWithParent[typeName] {
// image types with payload or parent should return a ref
nexpected = 1
}
assert.Equal(nexpected, nrefs, "incorrect ref count for image type %q\n", typeName)
}
}
}
{ // Parent set without URL: always causes error
for _, archName := range d.ListArches() {
arch, err := d.GetArch(archName)
assert.NoError(err)
for _, typeName := range arch.ListImageTypes() {
bp := &blueprint.Blueprint{}
if strings.HasSuffix(typeName, "simplified-installer") {
// simplified installers require installation device
bp = &blueprint.Blueprint{
Customizations: &blueprint.Customizations{
InstallationDevice: "/dev/sda42",
},
}
}
imgType, err := arch.GetImageType(typeName)
assert.NoError(err)
ostreeOptions := ostree.ImageOptions{
ParentRef: "test/x86_64/02",
}
options := distro.ImageOptions{OSTree: &ostreeOptions}
_, _, err = imgType.Manifest(bp, options, nil, 0)
assert.Error(err)
}
}
}
}

View file

@ -1,722 +0,0 @@
package fedora
import (
"errors"
"fmt"
"sort"
"strconv"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
)
const (
// package set names
// main/common os image package set name
osPkgsKey = "os"
// container package set name
containerPkgsKey = "container"
// installer package set name
installerPkgsKey = "installer"
// blueprint package set name
blueprintPkgsKey = "blueprint"
//Kernel options for ami, qcow2, openstack, vhd and vmdk types
defaultKernelOptions = "ro no_timer_check console=ttyS0,115200n8 biosdevname=0 net.ifnames=0"
)
var (
oscapProfileAllowList = []oscap.Profile{
oscap.Ospp,
oscap.PciDss,
oscap.Standard,
}
// Services
iotServices = []string{
"NetworkManager.service",
"firewalld.service",
"rngd.service",
"sshd.service",
"zezere_ignition.timer",
"zezere_ignition_banner.service",
"greenboot-grub2-set-counter",
"greenboot-grub2-set-success",
"greenboot-healthcheck",
"greenboot-rpm-ostree-grub2-check-fallback",
"greenboot-status",
"greenboot-task-runner",
"redboot-auto-reboot",
"redboot-task-runner",
"parsec",
"dbus-parsec",
}
// Image Definitions
imageInstallerImgType = imageType{
name: "image-installer",
nameAliases: []string{"fedora-image-installer"},
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
osPkgsKey: minimalrpmPackageSet,
installerPkgsKey: imageInstallerPackageSet,
},
bootable: true,
bootISO: true,
rpmOstree: false,
image: imageInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"anaconda-tree", "rootfs-image", "efiboot-tree", "os", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
}
liveInstallerImgType = imageType{
name: "live-installer",
nameAliases: []string{},
filename: "live-installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
installerPkgsKey: liveInstallerPackageSet,
},
bootable: true,
bootISO: true,
rpmOstree: false,
image: liveInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"anaconda-tree", "rootfs-image", "efiboot-tree", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
}
iotCommitImgType = imageType{
name: "iot-commit",
nameAliases: []string{"fedora-iot-commit"},
filename: "commit.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: iotCommitPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: iotServices,
},
rpmOstree: true,
image: iotCommitImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "ostree-commit", "commit-archive"},
exports: []string{"commit-archive"},
}
iotOCIImgType = imageType{
name: "iot-container",
nameAliases: []string{"fedora-iot-container"},
filename: "container.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: iotCommitPackageSet,
containerPkgsKey: func(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{}
},
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: iotServices,
},
rpmOstree: true,
bootISO: false,
image: iotContainerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "ostree-commit", "container-tree", "container"},
exports: []string{"container"},
}
iotInstallerImgType = imageType{
name: "iot-installer",
nameAliases: []string{"fedora-iot-installer"},
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
installerPkgsKey: iotInstallerPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
EnabledServices: iotServices,
},
rpmOstree: true,
bootISO: true,
image: iotInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"anaconda-tree", "rootfs-image", "efiboot-tree", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
}
iotRawImgType = imageType{
name: "iot-raw-image",
nameAliases: []string{"fedora-iot-raw-image"},
filename: "image.raw.xz",
compression: "xz",
mimeType: "application/xz",
packageSets: map[string]packageSetFunc{},
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
},
defaultSize: 4 * common.GibiByte,
rpmOstree: true,
bootable: true,
image: iotRawImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"ostree-deployment", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: iotBasePartitionTables,
// Passing an empty map into the required partition sizes disables the
// default partition sizes normally set so our `basePartitionTables` can
// override them (and make them smaller, in this case).
requiredPartitionSizes: map[string]uint64{},
}
qcow2ImgType = imageType{
name: "qcow2",
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
packageSets: map[string]packageSetFunc{
osPkgsKey: qcow2CommonPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
DefaultTarget: common.ToPtr("multi-user.target"),
EnabledServices: []string{
"cloud-init.service",
"cloud-config.service",
"cloud-final.service",
"cloud-init-local.service",
},
},
kernelOptions: defaultKernelOptions,
bootable: true,
defaultSize: 5 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "qcow2"},
exports: []string{"qcow2"},
basePartitionTables: defaultBasePartitionTables,
}
vhdImgType = imageType{
name: "vhd",
filename: "disk.vhd",
mimeType: "application/x-vhd",
packageSets: map[string]packageSetFunc{
osPkgsKey: vhdCommonPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
EnabledServices: []string{
"sshd",
},
DefaultTarget: common.ToPtr("multi-user.target"),
DisabledServices: []string{
"proc-sys-fs-binfmt_misc.mount",
"loadmodules.service",
},
},
kernelOptions: defaultKernelOptions,
bootable: true,
defaultSize: 2 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc"},
exports: []string{"vpc"},
basePartitionTables: defaultBasePartitionTables,
environment: &environment.Azure{},
}
vmdkDefaultImageConfig = &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
EnabledServices: []string{
"cloud-init.service",
"cloud-config.service",
"cloud-final.service",
"cloud-init-local.service",
},
}
vmdkImgType = imageType{
name: "vmdk",
filename: "disk.vmdk",
mimeType: "application/x-vmdk",
packageSets: map[string]packageSetFunc{
osPkgsKey: vmdkCommonPackageSet,
},
defaultImageConfig: vmdkDefaultImageConfig,
kernelOptions: defaultKernelOptions,
bootable: true,
defaultSize: 2 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vmdk"},
exports: []string{"vmdk"},
basePartitionTables: defaultBasePartitionTables,
}
ovaImgType = imageType{
name: "ova",
filename: "image.ova",
mimeType: "application/ovf",
packageSets: map[string]packageSetFunc{
osPkgsKey: vmdkCommonPackageSet,
},
defaultImageConfig: vmdkDefaultImageConfig,
kernelOptions: defaultKernelOptions,
bootable: true,
defaultSize: 2 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vmdk", "ovf", "archive"},
exports: []string{"archive"},
basePartitionTables: defaultBasePartitionTables,
}
containerImgType = imageType{
name: "container",
filename: "container.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: containerPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
NoSElinux: common.ToPtr(true),
ExcludeDocs: common.ToPtr(true),
Locale: common.ToPtr("C.UTF-8"),
Timezone: common.ToPtr("Etc/UTC"),
},
image: containerImage,
bootable: false,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "container"},
exports: []string{"container"},
}
minimalrawImgType = imageType{
name: "minimal-raw",
filename: "raw.img",
mimeType: "application/disk",
packageSets: map[string]packageSetFunc{
osPkgsKey: minimalrpmPackageSet,
},
rpmOstree: false,
kernelOptions: defaultKernelOptions,
bootable: true,
defaultSize: 2 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image"},
exports: []string{"image"},
basePartitionTables: defaultBasePartitionTables,
}
)
type distribution struct {
name string
product string
osVersion string
releaseVersion string
modulePlatformID string
ostreeRefTmpl string
isolabelTmpl string
runner runner.Runner
arches map[string]distro.Arch
defaultImageConfig *distro.ImageConfig
}
// Fedora based OS image configuration defaults
var defaultDistroImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("UTC"),
Locale: common.ToPtr("en_US"),
}
func getDistro(version int) distribution {
return distribution{
name: fmt.Sprintf("fedora-%d", version),
product: "Fedora",
osVersion: strconv.Itoa(version),
releaseVersion: strconv.Itoa(version),
modulePlatformID: fmt.Sprintf("platform:f%d", version),
ostreeRefTmpl: fmt.Sprintf("fedora/%d/%%s/iot", version),
isolabelTmpl: fmt.Sprintf("Fedora-%d-BaseOS-%%s", version),
runner: &runner.Fedora{Version: uint64(version)},
defaultImageConfig: defaultDistroImageConfig,
}
}
func (d *distribution) Name() string {
return d.name
}
func (d *distribution) Releasever() string {
return d.releaseVersion
}
func (d *distribution) ModulePlatformID() string {
return d.modulePlatformID
}
func (d *distribution) OSTreeRef() string {
return d.ostreeRefTmpl
}
func (d *distribution) ListArches() []string {
archNames := make([]string, 0, len(d.arches))
for name := range d.arches {
archNames = append(archNames, name)
}
sort.Strings(archNames)
return archNames
}
func (d *distribution) GetArch(name string) (distro.Arch, error) {
arch, exists := d.arches[name]
if !exists {
return nil, errors.New("invalid architecture: " + name)
}
return arch, nil
}
func (d *distribution) addArches(arches ...architecture) {
if d.arches == nil {
d.arches = map[string]distro.Arch{}
}
// Do not make copies of architectures, as opposed to image types,
// because architecture definitions are not used by more than a single
// distro definition.
for idx := range arches {
d.arches[arches[idx].name] = &arches[idx]
}
}
func (d *distribution) getDefaultImageConfig() *distro.ImageConfig {
return d.defaultImageConfig
}
type architecture struct {
distro *distribution
name string
imageTypes map[string]distro.ImageType
imageTypeAliases map[string]string
}
func (a *architecture) Name() string {
return a.name
}
func (a *architecture) ListImageTypes() []string {
itNames := make([]string, 0, len(a.imageTypes))
for name := range a.imageTypes {
itNames = append(itNames, name)
}
sort.Strings(itNames)
return itNames
}
func (a *architecture) GetImageType(name string) (distro.ImageType, error) {
t, exists := a.imageTypes[name]
if !exists {
aliasForName, exists := a.imageTypeAliases[name]
if !exists {
return nil, errors.New("invalid image type: " + name)
}
t, exists = a.imageTypes[aliasForName]
if !exists {
panic(fmt.Sprintf("image type '%s' is an alias to a non-existing image type '%s'", name, aliasForName))
}
}
return t, nil
}
func (a *architecture) addImageTypes(platform platform.Platform, imageTypes ...imageType) {
if a.imageTypes == nil {
a.imageTypes = map[string]distro.ImageType{}
}
for idx := range imageTypes {
it := imageTypes[idx]
it.arch = a
it.platform = platform
a.imageTypes[it.name] = &it
for _, alias := range it.nameAliases {
if a.imageTypeAliases == nil {
a.imageTypeAliases = map[string]string{}
}
if existingAliasFor, exists := a.imageTypeAliases[alias]; exists {
panic(fmt.Sprintf("image type alias '%s' for '%s' is already defined for another image type '%s'", alias, it.name, existingAliasFor))
}
a.imageTypeAliases[alias] = it.name
}
}
}
func (a *architecture) Distro() distro.Distro {
return a.distro
}
// 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 newDistro(version int) distro.Distro {
rd := getDistro(version)
// Architecture definitions
x86_64 := architecture{
name: platform.ARCH_X86_64.String(),
distro: &rd,
}
aarch64 := architecture{
name: platform.ARCH_AARCH64.String(),
distro: &rd,
}
ociImgType := qcow2ImgType
ociImgType.name = "oci"
amiImgType := qcow2ImgType
amiImgType.name = "ami"
amiImgType.filename = "image.raw"
amiImgType.mimeType = "application/octet-stream"
amiImgType.payloadPipelines = []string{"os", "image"}
amiImgType.exports = []string{"image"}
amiImgType.environment = &environment.EC2{}
openstackImgType := qcow2ImgType
openstackImgType.name = "openstack"
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "1.1",
},
},
qcow2ImgType,
ociImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
},
},
openstackImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VHD,
},
},
vhdImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VMDK,
},
},
vmdkImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_OVA,
},
},
ovaImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
},
amiImgType,
)
x86_64.addImageTypes(
&platform.X86{},
containerImgType,
)
x86_64.addImageTypes(
&platform.X86{
BasePlatform: platform.BasePlatform{
FirmwarePackages: []string{
"microcode_ctl", // ??
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6050-firmware",
},
},
BIOS: true,
UEFIVendor: "fedora",
},
iotOCIImgType,
iotCommitImgType,
iotInstallerImgType,
imageInstallerImgType,
liveInstallerImgType,
)
x86_64.addImageTypes(
&platform.X86{
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
BIOS: false,
UEFIVendor: "fedora",
},
iotRawImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
},
amiImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "1.1",
},
},
qcow2ImgType,
ociImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
},
},
openstackImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{},
containerImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
BasePlatform: platform.BasePlatform{
FirmwarePackages: []string{
"uboot-images-armv8", // ??
"bcm283x-firmware",
"arm-image-installer", // ??
},
},
UEFIVendor: "fedora",
},
iotCommitImgType,
iotOCIImgType,
iotInstallerImgType,
imageInstallerImgType,
liveInstallerImgType,
)
aarch64.addImageTypes(
&platform.Aarch64_IoT{
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
UEFIVendor: "fedora",
BootFiles: [][2]string{
{"/usr/lib/ostree-boot/efi/bcm2710-rpi-2-b.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2710-rpi-3-b-plus.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2710-rpi-3-b.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2710-rpi-cm3.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2710-rpi-zero-2-w.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2710-rpi-zero-2.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2711-rpi-4-b.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2711-rpi-400.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2711-rpi-cm4.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bcm2711-rpi-cm4s.dtb", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/bootcode.bin", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/config.txt", "/boot/efi/config.txt"},
{"/usr/lib/ostree-boot/efi/fixup.dat", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/fixup4.dat", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/fixup4cd.dat", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/fixup4db.dat", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/fixup4x.dat", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/fixup_cd.dat", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/fixup_db.dat", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/fixup_x.dat", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/overlays", "/boot/efi/"},
{"/usr/share/uboot/rpi_arm64/u-boot.bin", "/boot/efi/rpi-u-boot.bin"},
{"/usr/lib/ostree-boot/efi/start.elf", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/start4.elf", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/start4cd.elf", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/start4db.elf", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/start4x.elf", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/start_cd.elf", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/start_db.elf", "/boot/efi/"},
{"/usr/lib/ostree-boot/efi/start_x.elf", "/boot/efi/"},
},
},
iotRawImgType,
)
x86_64.addImageTypes(
&platform.X86{
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
},
minimalrawImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: "fedora",
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
},
minimalrawImgType,
)
rd.addArches(x86_64, aarch64)
return &rd
}

View file

@ -1,815 +0,0 @@
package fedora_test
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distro/distro_test_common"
"github.com/osbuild/osbuild-composer/internal/distro/fedora"
)
type fedoraFamilyDistro struct {
name string
distro distro.Distro
}
var fedoraFamilyDistros = []fedoraFamilyDistro{
{
name: "fedora",
distro: fedora.NewF37(),
},
{
name: "fedora",
distro: fedora.NewF38(),
},
{
name: "fedora",
distro: fedora.NewF39(),
},
}
func TestFilenameFromType(t *testing.T) {
type args struct {
outputFormat string
}
type wantResult struct {
filename string
mimeType string
wantErr bool
}
tests := []struct {
name string
args args
want wantResult
}{
{
name: "ami",
args: args{"ami"},
want: wantResult{
filename: "image.raw",
mimeType: "application/octet-stream",
},
},
{
name: "qcow2",
args: args{"qcow2"},
want: wantResult{
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
},
},
{
name: "openstack",
args: args{"openstack"},
want: wantResult{
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
},
},
{
name: "vhd",
args: args{"vhd"},
want: wantResult{
filename: "disk.vhd",
mimeType: "application/x-vhd",
},
},
{
name: "vmdk",
args: args{"vmdk"},
want: wantResult{
filename: "disk.vmdk",
mimeType: "application/x-vmdk",
},
},
{
name: "ova",
args: args{"ova"},
want: wantResult{
filename: "image.ova",
mimeType: "application/ovf",
},
},
{
name: "container",
args: args{"container"},
want: wantResult{
filename: "container.tar",
mimeType: "application/x-tar",
},
},
{
name: "iot-commit",
args: args{"iot-commit"},
want: wantResult{
filename: "commit.tar",
mimeType: "application/x-tar",
},
},
{ // Alias
name: "fedora-iot-commit",
args: args{"fedora-iot-commit"},
want: wantResult{
filename: "commit.tar",
mimeType: "application/x-tar",
},
},
{
name: "iot-container",
args: args{"iot-container"},
want: wantResult{
filename: "container.tar",
mimeType: "application/x-tar",
},
},
{ // Alias
name: "fedora-iot-container",
args: args{"fedora-iot-container"},
want: wantResult{
filename: "container.tar",
mimeType: "application/x-tar",
},
},
{
name: "iot-installer",
args: args{"iot-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{ // Alias
name: "fedora-iot-installer",
args: args{"fedora-iot-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{
name: "live-installer",
args: args{"live-installer"},
want: wantResult{
filename: "live-installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{
name: "image-installer",
args: args{"image-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{ // Alias
name: "fedora-image-installer",
args: args{"fedora-image-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{
name: "invalid-output-type",
args: args{"foobar"},
want: wantResult{wantErr: true},
},
{
name: "minimal-raw",
args: args{"minimal-raw"},
want: wantResult{
filename: "raw.img",
mimeType: "application/disk",
},
},
}
for _, dist := range fedoraFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dist := dist.distro
arch, _ := dist.GetArch("x86_64")
imgType, err := arch.GetImageType(tt.args.outputFormat)
if (err != nil) != tt.want.wantErr {
t.Errorf("Arch.GetImageType() error = %v, wantErr %v", err, tt.want.wantErr)
return
}
if !tt.want.wantErr {
gotFilename := imgType.Filename()
gotMIMEType := imgType.MIMEType()
if gotFilename != tt.want.filename {
t.Errorf("ImageType.Filename() got = %v, want %v", gotFilename, tt.want.filename)
}
if gotMIMEType != tt.want.mimeType {
t.Errorf("ImageType.MIMEType() got1 = %v, want %v", gotMIMEType, tt.want.mimeType)
}
}
})
}
})
}
}
func TestImageType_BuildPackages(t *testing.T) {
x8664BuildPackages := []string{
"dnf",
"dosfstools",
"e2fsprogs",
"policycoreutils",
"qemu-img",
"selinux-policy-targeted",
"systemd",
"tar",
"xz",
"grub2-pc",
}
aarch64BuildPackages := []string{
"dnf",
"dosfstools",
"e2fsprogs",
"policycoreutils",
"qemu-img",
"selinux-policy-targeted",
"systemd",
"tar",
"xz",
}
buildPackages := map[string][]string{
"x86_64": x8664BuildPackages,
"aarch64": aarch64BuildPackages,
}
for _, dist := range fedoraFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
d := dist.distro
for _, archLabel := range d.ListArches() {
archStruct, err := d.GetArch(archLabel)
if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) {
continue
}
for _, itLabel := range archStruct.ListImageTypes() {
itStruct, err := archStruct.GetImageType(itLabel)
if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) {
continue
}
manifest, _, err := itStruct.Manifest(&blueprint.Blueprint{}, distro.ImageOptions{}, nil, 0)
assert.NoError(t, err)
buildPkgs := manifest.GetPackageSetChains()["build"]
assert.NotNil(t, buildPkgs)
assert.Len(t, buildPkgs, 1)
assert.ElementsMatch(t, buildPackages[archLabel], buildPkgs[0].Include)
}
}
})
}
}
func TestImageType_Name(t *testing.T) {
imgMap := []struct {
arch string
imgNames []string
}{
{
arch: "x86_64",
imgNames: []string{
"qcow2",
"openstack",
"vhd",
"vmdk",
"ova",
"ami",
"iot-commit",
"iot-container",
"iot-installer",
"iot-raw-image",
"oci",
"image-installer",
"live-installer",
"minimal-raw",
},
},
{
arch: "aarch64",
imgNames: []string{
"qcow2",
"openstack",
"ami",
"oci",
"iot-commit",
"iot-container",
"iot-installer",
"iot-raw-image",
"image-installer",
"minimal-raw",
},
},
}
for _, dist := range fedoraFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, mapping := range imgMap {
if mapping.arch == "s390x" {
continue
}
arch, err := dist.distro.GetArch(mapping.arch)
if assert.NoError(t, err) {
for _, imgName := range mapping.imgNames {
if imgName == "iot-commit" {
continue
}
imgType, err := arch.GetImageType(imgName)
if assert.NoError(t, err) {
assert.Equalf(t, imgName, imgType.Name(), "arch: %s", mapping.arch)
}
}
}
}
})
}
}
func TestImageTypeAliases(t *testing.T) {
type args struct {
imageTypeAliases []string
}
type wantResult struct {
imageTypeName string
}
tests := []struct {
name string
args args
want wantResult
}{
{
name: "iot-commit aliases",
args: args{
imageTypeAliases: []string{"fedora-iot-commit"},
},
want: wantResult{
imageTypeName: "iot-commit",
},
},
{
name: "iot-container aliases",
args: args{
imageTypeAliases: []string{"fedora-iot-container"},
},
want: wantResult{
imageTypeName: "iot-container",
},
},
{
name: "iot-installer aliases",
args: args{
imageTypeAliases: []string{"fedora-iot-installer"},
},
want: wantResult{
imageTypeName: "iot-installer",
},
},
}
for _, dist := range fedoraFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dist := dist.distro
for _, archName := range dist.ListArches() {
t.Run(archName, func(t *testing.T) {
arch, err := dist.GetArch(archName)
require.Nilf(t, err,
"failed to get architecture '%s', previously listed as supported for the distro '%s'",
archName, dist.Name())
// Test image type aliases only if the aliased image type is supported for the arch
if _, err = arch.GetImageType(tt.want.imageTypeName); err != nil {
t.Skipf("aliased image type '%s' is not supported for architecture '%s'",
tt.want.imageTypeName, archName)
}
for _, alias := range tt.args.imageTypeAliases {
t.Run(fmt.Sprintf("'%s' alias for image type '%s'", alias, tt.want.imageTypeName),
func(t *testing.T) {
gotImage, err := arch.GetImageType(alias)
require.Nilf(t, err, "arch.GetImageType() for image type alias '%s' failed: %v",
alias, err)
assert.Equalf(t, tt.want.imageTypeName, gotImage.Name(),
"got unexpected image type name for alias '%s'. got = %s, want = %s",
alias, tt.want.imageTypeName, gotImage.Name())
})
}
})
}
})
}
})
}
}
// Check that Manifest() function returns an error for unsupported
// configurations.
func TestDistro_ManifestError(t *testing.T) {
// Currently, the only unsupported configuration is OSTree commit types
// with Kernel boot options
fedoraDistro := fedora.NewF37()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Kernel: &blueprint.KernelCustomization{
Append: "debug",
},
},
}
for _, archName := range fedoraDistro.ListArches() {
arch, _ := fedoraDistro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
imgOpts := distro.ImageOptions{
Size: imgType.Size(0),
}
_, _, err := imgType.Manifest(&bp, imgOpts, nil, 0)
if imgTypeName == "iot-commit" || imgTypeName == "iot-container" {
assert.EqualError(t, err, "kernel boot parameter customizations are not supported for ostree types")
} else if imgTypeName == "iot-installer" {
assert.EqualError(t, err, fmt.Sprintf("boot ISO image type \"%s\" requires specifying a URL from which to retrieve the OSTree commit", imgTypeName))
} else if imgTypeName == "image-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: User, Group)", imgTypeName))
} else if imgTypeName == "live-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: None)", imgTypeName))
} else if imgTypeName == "iot-raw-image" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for image type %q: (allowed: User, Group, Directories, Files, Services)", imgTypeName))
} else {
assert.NoError(t, err)
}
}
}
}
func TestArchitecture_ListImageTypes(t *testing.T) {
imgMap := []struct {
arch string
imgNames []string
fedoraAdditionalImageTypes []string
}{
{
arch: "x86_64",
imgNames: []string{
"qcow2",
"openstack",
"vhd",
"vmdk",
"ova",
"ami",
"iot-commit",
"iot-container",
"iot-installer",
"iot-raw-image",
"oci",
"container",
"image-installer",
"live-installer",
"minimal-raw",
},
},
{
arch: "aarch64",
imgNames: []string{
"qcow2",
"openstack",
"ami",
"iot-commit",
"iot-container",
"iot-installer",
"iot-raw-image",
"oci",
"container",
"image-installer",
"live-installer",
"minimal-raw",
},
},
}
for _, dist := range fedoraFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, mapping := range imgMap {
arch, err := dist.distro.GetArch(mapping.arch)
require.NoError(t, err)
imageTypes := arch.ListImageTypes()
var expectedImageTypes []string
expectedImageTypes = append(expectedImageTypes, mapping.imgNames...)
if dist.name == "fedora" {
expectedImageTypes = append(expectedImageTypes, mapping.fedoraAdditionalImageTypes...)
}
require.ElementsMatch(t, expectedImageTypes, imageTypes)
}
})
}
}
func TestFedora_ListArches(t *testing.T) {
arches := fedora.NewF37().ListArches()
assert.Equal(t, []string{"aarch64", "x86_64"}, arches)
}
func TestFedora37_GetArch(t *testing.T) {
arches := []struct {
name string
errorExpected bool
errorExpectedInCentos bool
}{
{
name: "x86_64",
},
{
name: "aarch64",
},
{
name: "s390x",
errorExpected: true,
},
{
name: "ppc64le",
errorExpected: true,
},
{
name: "foo-arch",
errorExpected: true,
},
}
for _, dist := range fedoraFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, a := range arches {
actualArch, err := dist.distro.GetArch(a.name)
if a.errorExpected {
assert.Nil(t, actualArch)
assert.Error(t, err)
} else {
assert.Equal(t, a.name, actualArch.Name())
assert.NoError(t, err)
}
}
})
}
}
func TestFedora37_Name(t *testing.T) {
distro := fedora.NewF37()
assert.Equal(t, "fedora-37", distro.Name())
}
func TestFedora37_KernelOption(t *testing.T) {
distro_test_common.TestDistro_KernelOption(t, fedora.NewF37())
}
func TestFedora_OSTreeOptions(t *testing.T) {
distro_test_common.TestDistro_OSTreeOptions(t, fedora.NewF37())
}
func TestDistro_CustomFileSystemManifestError(t *testing.T) {
fedoraDistro := fedora.NewF37()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/etc",
},
},
},
}
for _, archName := range fedoraDistro.ListArches() {
arch, _ := fedoraDistro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "iot-commit" || imgTypeName == "iot-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if imgTypeName == "iot-raw-image" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for image type %q: (allowed: User, Group, Directories, Files, Services)", imgTypeName))
} else if imgTypeName == "iot-installer" || imgTypeName == "image-installer" {
continue
} else if imgTypeName == "live-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: None)", imgTypeName))
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/etc\"]")
}
}
}
}
func TestDistro_TestRootMountPoint(t *testing.T) {
fedoraDistro := fedora.NewF37()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/",
},
},
},
}
for _, archName := range fedoraDistro.ListArches() {
arch, _ := fedoraDistro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "iot-commit" || imgTypeName == "iot-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if imgTypeName == "iot-raw-image" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for image type %q: (allowed: User, Group, Directories, Files, Services)", imgTypeName))
} else if imgTypeName == "iot-installer" || imgTypeName == "image-installer" {
continue
} else if imgTypeName == "live-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: None)", imgTypeName))
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_CustomFileSystemSubDirectories(t *testing.T) {
fedoraDistro := fedora.NewF37()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/var/log",
},
{
MinSize: 1024,
Mountpoint: "/var/log/audit",
},
},
},
}
for _, archName := range fedoraDistro.ListArches() {
arch, _ := fedoraDistro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if strings.HasPrefix(imgTypeName, "iot-") || strings.HasPrefix(imgTypeName, "image-") {
continue
} else if imgTypeName == "live-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: None)", imgTypeName))
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_MountpointsWithArbitraryDepthAllowed(t *testing.T) {
fedoraDistro := fedora.NewF37()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/var/a",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b/c",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b/c/d",
},
},
},
}
for _, archName := range fedoraDistro.ListArches() {
arch, _ := fedoraDistro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if strings.HasPrefix(imgTypeName, "iot-") || strings.HasPrefix(imgTypeName, "image-") {
continue
} else if imgTypeName == "live-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: None)", imgTypeName))
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_DirtyMountpointsNotAllowed(t *testing.T) {
fedoraDistro := fedora.NewF37()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "//",
},
{
MinSize: 1024,
Mountpoint: "/var//",
},
{
MinSize: 1024,
Mountpoint: "/var//log/audit/",
},
},
},
}
for _, archName := range fedoraDistro.ListArches() {
arch, _ := fedoraDistro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if strings.HasPrefix(imgTypeName, "iot-") || strings.HasPrefix(imgTypeName, "image-") {
continue
} else if imgTypeName == "live-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: None)", imgTypeName))
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"//\" \"/var//\" \"/var//log/audit/\"]")
}
}
}
}
func TestDistro_CustomFileSystemPatternMatching(t *testing.T) {
fedoraDistro := fedora.NewF37()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/variable",
},
{
MinSize: 1024,
Mountpoint: "/variable/log/audit",
},
},
},
}
for _, archName := range fedoraDistro.ListArches() {
arch, _ := fedoraDistro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "iot-commit" || imgTypeName == "iot-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if imgTypeName == "iot-raw-image" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for image type %q: (allowed: User, Group, Directories, Files, Services)", imgTypeName))
} else if imgTypeName == "iot-installer" || imgTypeName == "image-installer" {
continue
} else if imgTypeName == "live-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: None)", imgTypeName))
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/variable\" \"/variable/log/audit\"]")
}
}
}
}
func TestDistro_CustomUsrPartitionNotLargeEnough(t *testing.T) {
fedoraDistro := fedora.NewF37()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/usr",
},
},
},
}
for _, archName := range fedoraDistro.ListArches() {
arch, _ := fedoraDistro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "iot-commit" || imgTypeName == "iot-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if imgTypeName == "iot-raw-image" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for image type %q: (allowed: User, Group, Directories, Files, Services)", imgTypeName))
} else if imgTypeName == "iot-installer" || imgTypeName == "image-installer" {
continue
} else if imgTypeName == "live-installer" {
assert.EqualError(t, err, fmt.Sprintf("unsupported blueprint customizations found for boot ISO image type \"%s\": (allowed: None)", imgTypeName))
} else {
assert.NoError(t, err)
}
}
}
}

View file

@ -1,532 +0,0 @@
package fedora
import (
"fmt"
"math/rand"
"strings"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/users"
"github.com/osbuild/osbuild-composer/internal/workload"
)
// HELPERS
func osCustomizations(
t *imageType,
osPackageSet rpmmd.PackageSet,
containers []container.SourceSpec,
c *blueprint.Customizations) manifest.OSCustomizations {
imageConfig := t.getDefaultImageConfig()
osc := manifest.OSCustomizations{}
if t.bootable || t.rpmOstree {
osc.KernelName = c.GetKernel().Name
var kernelOptions []string
if t.kernelOptions != "" {
kernelOptions = append(kernelOptions, t.kernelOptions)
}
if bpKernel := c.GetKernel(); bpKernel.Append != "" {
kernelOptions = append(kernelOptions, bpKernel.Append)
}
osc.KernelOptionsAppend = kernelOptions
}
osc.ExtraBasePackages = osPackageSet.Include
osc.ExcludeBasePackages = osPackageSet.Exclude
osc.ExtraBaseRepos = osPackageSet.Repositories
osc.Containers = containers
osc.GPGKeyFiles = imageConfig.GPGKeyFiles
if imageConfig.ExcludeDocs != nil {
osc.ExcludeDocs = *imageConfig.ExcludeDocs
}
if !t.bootISO {
// don't put users and groups in the payload of an installer
// add them via kickstart instead
osc.Groups = users.GroupsFromBP(c.GetGroups())
osc.Users = users.UsersFromBP(c.GetUsers())
}
osc.EnabledServices = imageConfig.EnabledServices
osc.DisabledServices = imageConfig.DisabledServices
if imageConfig.DefaultTarget != nil {
osc.DefaultTarget = *imageConfig.DefaultTarget
}
if fw := c.GetFirewall(); fw != nil {
options := osbuild.FirewallStageOptions{
Ports: fw.Ports,
}
if fw.Services != nil {
options.EnabledServices = fw.Services.Enabled
options.DisabledServices = fw.Services.Disabled
}
osc.Firewall = &options
}
language, keyboard := c.GetPrimaryLocale()
if language != nil {
osc.Language = *language
} else if imageConfig.Locale != nil {
osc.Language = *imageConfig.Locale
}
if keyboard != nil {
osc.Keyboard = keyboard
} else if imageConfig.Keyboard != nil {
osc.Keyboard = &imageConfig.Keyboard.Keymap
}
if hostname := c.GetHostname(); hostname != nil {
osc.Hostname = *hostname
} else {
osc.Hostname = "localhost.localdomain"
}
timezone, ntpServers := c.GetTimezoneSettings()
if timezone != nil {
osc.Timezone = *timezone
} else if imageConfig.Timezone != nil {
osc.Timezone = *imageConfig.Timezone
}
if len(ntpServers) > 0 {
for _, server := range ntpServers {
osc.NTPServers = append(osc.NTPServers, osbuild.ChronyConfigServer{Hostname: server})
}
} else if imageConfig.TimeSynchronization != nil {
osc.NTPServers = imageConfig.TimeSynchronization.Servers
}
// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
osc.SElinux = "targeted"
}
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
if t.rpmOstree {
panic("unexpected oscap options for ostree image type")
}
var datastream = oscapConfig.DataStream
if datastream == "" {
datastream = oscap.DefaultFedoraDatastream()
}
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(
osbuild.OscapConfig{
Datastream: datastream,
ProfileID: oscapConfig.ProfileID,
},
)
}
var err error
osc.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(c.GetDirectories())
if err != nil {
// In theory this should never happen, because the blueprint directory customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert directory customizations to fs node directories: %v", err))
}
osc.Files, err = blueprint.FileCustomizationsToFsNodeFiles(c.GetFiles())
if err != nil {
// In theory this should never happen, because the blueprint file customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert file customizations to fs node files: %v", err))
}
customRepos, err := c.GetRepositories()
if err != nil {
// This shouldn't happen and since the repos
// should have already been validated
panic(fmt.Sprintf("failed to get custom repos: %v", err))
}
// This function returns a map of filename and corresponding yum repos
// and a list of fs node files for the inline gpg keys so we can save
// them to disk. This step also swaps the inline gpg key with the path
// to the file in the os file tree
yumRepos, gpgKeyFiles, err := blueprint.RepoCustomizationsToRepoConfigAndGPGKeyFiles(customRepos)
if err != nil {
panic(fmt.Sprintf("failed to convert inline gpgkeys to fs node files: %v", err))
}
// add the gpg key files to the list of files to be added to the tree
if len(gpgKeyFiles) > 0 {
osc.Files = append(osc.Files, gpgKeyFiles...)
}
for filename, repos := range yumRepos {
osc.YUMRepos = append(osc.YUMRepos, osbuild.NewYumReposStageOptions(filename, repos))
}
osc.ShellInit = imageConfig.ShellInit
osc.Grub2Config = imageConfig.Grub2Config
osc.Sysconfig = imageConfig.Sysconfig
osc.SystemdLogind = imageConfig.SystemdLogind
osc.CloudInit = imageConfig.CloudInit
osc.Modprobe = imageConfig.Modprobe
osc.DracutConf = imageConfig.DracutConf
osc.SystemdUnit = imageConfig.SystemdUnit
osc.Authselect = imageConfig.Authselect
osc.SELinuxConfig = imageConfig.SELinuxConfig
osc.Tuned = imageConfig.Tuned
osc.Tmpfilesd = imageConfig.Tmpfilesd
osc.PamLimitsConf = imageConfig.PamLimitsConf
osc.Sysctld = imageConfig.Sysctld
osc.DNFConfig = imageConfig.DNFConfig
osc.SshdConfig = imageConfig.SshdConfig
osc.AuthConfig = imageConfig.Authconfig
osc.PwQuality = imageConfig.PwQuality
return osc
}
// IMAGES
func liveImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewLiveImage()
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], containers, customizations)
img.Environment = t.environment
img.Workload = workload
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
return img, nil
}
func containerImage(workload workload.Workload,
t *imageType,
c *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewBaseContainer()
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], containers, c)
img.Environment = t.environment
img.Workload = workload
img.Filename = t.Filename()
return img, nil
}
func liveInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewAnacondaLiveInstaller()
distro := t.Arch().Distro()
// If the live installer is generated for Fedora 39 or higher then we enable the web ui
// kernel options. This is a temporary thing as the check for this should really lie with
// anaconda and their `liveinst` script to determine which frontend to start.
if common.VersionLessThan(distro.Releasever(), "39") {
img.AdditionalKernelOpts = []string{}
} else {
img.AdditionalKernelOpts = []string{"inst.webui"}
}
img.Platform = t.platform
img.Workload = workload
img.ExtraBasePackages = packageSets[installerPkgsKey]
d := t.arch.distro
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.OSName = "fedora"
img.OSVersion = d.osVersion
img.Release = fmt.Sprintf("%s %s", d.product, d.osVersion)
img.Filename = t.Filename()
return img, nil
}
func imageInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewAnacondaTarInstaller()
// Enable anaconda-webui for Fedora > 38
distro := t.Arch().Distro()
if strings.HasPrefix(distro.Name(), "fedora") && !common.VersionLessThan(distro.Releasever(), "38") {
img.AdditionalAnacondaModules = []string{
"org.fedoraproject.Anaconda.Modules.Security",
"org.fedoraproject.Anaconda.Modules.Timezone",
"org.fedoraproject.Anaconda.Modules.Localization",
}
img.AdditionalKernelOpts = []string{"inst.webui", "inst.webui.remote"}
}
img.AdditionalAnacondaModules = append(img.AdditionalAnacondaModules, "org.fedoraproject.Anaconda.Modules.Users")
img.Platform = t.platform
img.Workload = workload
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], containers, customizations)
img.ExtraBasePackages = packageSets[installerPkgsKey]
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
img.SquashfsCompression = "lz4"
d := t.arch.distro
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.OSName = "fedora"
img.OSVersion = d.osVersion
img.Release = fmt.Sprintf("%s %s", d.product, d.osVersion)
img.Filename = t.Filename()
return img, nil
}
func iotCommitImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
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)
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], containers, customizations)
img.Environment = t.environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = t.arch.distro.osVersion
img.Filename = t.Filename()
return img, nil
}
func iotContainerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
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.NewOSTreeContainer(commitRef)
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], containers, customizations)
img.ContainerLanguage = img.OSCustomizations.Language
img.Environment = t.environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = t.arch.distro.osVersion
img.ExtraContainerPackages = packageSets[containerPkgsKey]
img.Filename = t.Filename()
return img, nil
}
func iotInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
d := t.arch.distro
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
img := image.NewAnacondaOSTreeInstaller(commit)
img.Platform = t.platform
img.ExtraBasePackages = packageSets[installerPkgsKey]
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
img.AdditionalAnacondaModules = []string{
"org.fedoraproject.Anaconda.Modules.Timezone",
"org.fedoraproject.Anaconda.Modules.Localization",
"org.fedoraproject.Anaconda.Modules.Users",
}
img.SquashfsCompression = "lz4"
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.Variant = "IoT"
img.OSName = "fedora"
img.OSVersion = d.osVersion
img.Release = fmt.Sprintf("%s %s", d.product, d.osVersion)
img.Filename = t.Filename()
return img, nil
}
func iotRawImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
img := image.NewOSTreeRawImage(commit)
// Set sysroot read-only only for Fedora 37+
distro := t.Arch().Distro()
if strings.HasPrefix(distro.Name(), "fedora") && !common.VersionLessThan(distro.Releasever(), "37") {
img.SysrootReadOnly = true
}
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
img.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(customizations.GetDirectories())
if err != nil {
return nil, err
}
img.Files, err = blueprint.FileCustomizationsToFsNodeFiles(customizations.GetFiles())
if err != nil {
return nil, err
}
// "rw" kernel option is required when /sysroot is mounted read-only to
// keep stateful parts of the filesystem writeable (/var/ and /etc)
img.KernelOptionsAppend = []string{"modprobe.blacklist=vc4", "rw"}
img.Keyboard = "us"
img.Locale = "C.UTF-8"
img.Platform = t.platform
img.Workload = workload
img.Remote = ostree.Remote{
Name: "fedora-iot",
URL: "https://ostree.fedoraproject.org/iot",
ContentURL: "mirrorlist=https://ostree.fedoraproject.org/iot/mirrorlist",
GPGKeyPaths: []string{"/etc/pki/rpm-gpg/"},
}
img.OSName = "fedora-iot"
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
img.Compression = t.compression
return img, nil
}
// Create an ostree SourceSpec to define an ostree parent commit using the user
// options and the default ref for the image type. Additionally returns the
// ref to be used for the new commit to be created.
func makeOSTreeParentCommit(options *ostree.ImageOptions, defaultRef string) (*ostree.SourceSpec, string) {
commitRef := defaultRef
if options == nil {
// nothing to do
return nil, commitRef
}
if options.ImageRef != "" {
// user option overrides default commit ref
commitRef = options.ImageRef
}
var parentCommit *ostree.SourceSpec
if options.URL == "" {
// no parent
return nil, commitRef
}
// ostree URL specified: set source spec for parent commit
parentRef := options.ParentRef
if parentRef == "" {
// parent ref not set: use image ref
parentRef = commitRef
}
parentCommit = &ostree.SourceSpec{
URL: options.URL,
Ref: parentRef,
RHSM: options.RHSM,
}
return parentCommit, commitRef
}
// Create an ostree SourceSpec to define an ostree payload using the user options and the default ref for the image type.
func makeOSTreePayloadCommit(options *ostree.ImageOptions, defaultRef string) (ostree.SourceSpec, error) {
if options == nil || options.URL == "" {
// this should be caught by checkOptions() in distro, but it's good
// to guard against it here as well
return ostree.SourceSpec{}, fmt.Errorf("ostree commit URL required")
}
commitRef := defaultRef
if options.ImageRef != "" {
// user option overrides default commit ref
commitRef = options.ImageRef
}
return ostree.SourceSpec{
URL: options.URL,
Ref: commitRef,
RHSM: options.RHSM,
}, nil
}

View file

@ -1,342 +0,0 @@
package fedora
import (
"fmt"
"math/rand"
"strings"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/pathpolicy"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/workload"
"golang.org/x/exp/slices"
)
type imageFunc func(workload workload.Workload, t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, packageSets map[string]rpmmd.PackageSet, containers []container.SourceSpec, rng *rand.Rand) (image.ImageKind, error)
type packageSetFunc func(t *imageType) rpmmd.PackageSet
type imageType struct {
arch *architecture
platform platform.Platform
environment environment.Environment
workload workload.Workload
name string
nameAliases []string
filename string
compression string
mimeType string
packageSets map[string]packageSetFunc
defaultImageConfig *distro.ImageConfig
kernelOptions string
defaultSize uint64
buildPipelines []string
payloadPipelines []string
exports []string
image imageFunc
// bootISO: installable ISO
bootISO bool
// rpmOstree: iot/ostree
rpmOstree bool
// bootable image
bootable bool
// List of valid arches for the image type
basePartitionTables distro.BasePartitionTableMap
requiredPartitionSizes map[string]uint64
}
func (t *imageType) Name() string {
return t.name
}
func (t *imageType) Arch() distro.Arch {
return t.arch
}
func (t *imageType) Filename() string {
return t.filename
}
func (t *imageType) MIMEType() string {
return t.mimeType
}
func (t *imageType) OSTreeRef() string {
d := t.arch.distro
if t.rpmOstree {
return fmt.Sprintf(d.ostreeRefTmpl, t.arch.Name())
}
return ""
}
func (t *imageType) Size(size uint64) uint64 {
// Microsoft Azure requires vhd images to be rounded up to the nearest MB
if t.name == "vhd" && size%common.MebiByte != 0 {
size = (size/common.MebiByte + 1) * common.MebiByte
}
if size == 0 {
size = t.defaultSize
}
return size
}
func (t *imageType) BuildPipelines() []string {
return t.buildPipelines
}
func (t *imageType) PayloadPipelines() []string {
return t.payloadPipelines
}
func (t *imageType) PayloadPackageSets() []string {
return []string{blueprintPkgsKey}
}
func (t *imageType) PackageSetsChains() map[string][]string {
return make(map[string][]string)
}
func (t *imageType) Exports() []string {
if len(t.exports) > 0 {
return t.exports
}
return []string{"assembler"}
}
func (t *imageType) BootMode() distro.BootMode {
if t.platform.GetUEFIVendor() != "" && t.platform.GetBIOSPlatform() != "" {
return distro.BOOT_HYBRID
} else if t.platform.GetUEFIVendor() != "" {
return distro.BOOT_UEFI
} else if t.platform.GetBIOSPlatform() != "" || t.platform.GetZiplSupport() {
return distro.BOOT_LEGACY
}
return distro.BOOT_NONE
}
func (t *imageType) getPartitionTable(
mountpoints []blueprint.FilesystemCustomization,
options distro.ImageOptions,
rng *rand.Rand,
) (*disk.PartitionTable, error) {
basePartitionTable, exists := t.basePartitionTables[t.arch.Name()]
if !exists {
return nil, fmt.Errorf("unknown arch: " + t.arch.Name())
}
imageSize := t.Size(options.Size)
lvmify := !t.rpmOstree
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, t.requiredPartitionSizes, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
// ensure that image always returns non-nil default config
imageConfig := t.defaultImageConfig
if imageConfig == nil {
imageConfig = &distro.ImageConfig{}
}
return imageConfig.InheritFrom(t.arch.distro.getDefaultImageConfig())
}
func (t *imageType) PartitionType() string {
basePartitionTable, exists := t.basePartitionTables[t.arch.Name()]
if !exists {
return ""
}
return basePartitionTable.Type
}
func (t *imageType) Manifest(bp *blueprint.Blueprint,
options distro.ImageOptions,
repos []rpmmd.RepoConfig,
seed int64) (*manifest.Manifest, []string, error) {
warnings, err := t.checkOptions(bp, options)
if err != nil {
return nil, nil, err
}
// merge package sets that appear in the image type with the package sets
// of the same name from the distro and arch
staticPackageSets := make(map[string]rpmmd.PackageSet)
for name, getter := range t.packageSets {
staticPackageSets[name] = getter(t)
}
// amend with repository information and collect payload repos
payloadRepos := make([]rpmmd.RepoConfig, 0)
for _, repo := range repos {
if len(repo.PackageSets) > 0 {
// only apply the repo to the listed package sets
for _, psName := range repo.PackageSets {
if slices.Contains(t.PayloadPackageSets(), psName) {
payloadRepos = append(payloadRepos, repo)
}
ps := staticPackageSets[psName]
ps.Repositories = append(ps.Repositories, repo)
staticPackageSets[psName] = ps
}
}
}
w := t.workload
if w == nil {
cw := &workload.Custom{
BaseWorkload: workload.BaseWorkload{
Repos: payloadRepos,
},
Packages: bp.GetPackagesEx(false),
}
if services := bp.Customizations.GetServices(); services != nil {
cw.Services = services.Enabled
cw.DisabledServices = services.Disabled
}
w = cw
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx := range bp.Containers {
containerSources[idx] = container.SourceSpec(bp.Containers[idx])
}
source := rand.NewSource(seed)
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(source)
img, err := t.image(w, t, bp.Customizations, options, staticPackageSets, containerSources, rng)
if err != nil {
return nil, nil, err
}
mf := manifest.New()
mf.Distro = manifest.DISTRO_FEDORA
_, err = img.InstantiateManifest(&mf, repos, t.arch.distro.runner, rng)
if err != nil {
return nil, nil, err
}
return &mf, warnings, err
}
// checkOptions checks the validity and compatibility of options and customizations for the image type.
// Returns ([]string, error) where []string, if non-nil, will hold any generated warnings (e.g. deprecation notices).
func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOptions) ([]string, error) {
customizations := bp.Customizations
// we do not support embedding containers on ostree-derived images, only on commits themselves
if len(bp.Containers) > 0 && t.rpmOstree && (t.name != "iot-commit" && t.name != "iot-container") {
return nil, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
ostreeURL := ""
if options.OSTree != nil {
if options.OSTree.ParentRef != "" && options.OSTree.URL == "" {
// specifying parent ref also requires URL
return nil, ostree.NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
}
ostreeURL = options.OSTree.URL
}
if t.bootISO && t.rpmOstree {
// ostree-based ISOs require a URL from which to pull a payload commit
if ostreeURL == "" {
return nil, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
}
}
if t.name == "iot-raw-image" {
allowed := []string{"User", "Group", "Directories", "Files", "Services"}
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, ", "))
}
// TODO: consider additional checks, such as those in "edge-simplified-installer" in RHEL distros
}
// BootISO's have limited support for customizations.
// TODO: Support kernel name selection for image-installer
if t.bootISO {
if t.name == "iot-installer" || t.name == "image-installer" {
allowed := []string{"User", "Group"}
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, ", "))
}
} 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)
}
}
}
if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.rpmOstree {
return nil, fmt.Errorf("kernel boot parameter customizations are not supported for ostree types")
}
mountpoints := customizations.GetFilesystems()
if mountpoints != nil && t.rpmOstree {
return nil, fmt.Errorf("Custom mountpoints are not supported for ostree types")
}
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
if err != nil {
return nil, err
}
if osc := customizations.GetOpenSCAP(); osc != nil {
supported := oscap.IsProfileAllowed(osc.ProfileID, oscapProfileAllowList)
if !supported {
return nil, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported profile: %s", osc.ProfileID))
}
if t.rpmOstree {
return nil, fmt.Errorf("OpenSCAP customizations are not supported for ostree types")
}
if osc.ProfileID == "" {
return nil, fmt.Errorf("OpenSCAP profile cannot be empty")
}
}
// Check Directory/File Customizations are valid
dc := customizations.GetDirectories()
fc := customizations.GetFiles()
err = blueprint.ValidateDirFileCustomizations(dc, fc)
if err != nil {
return nil, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
if err != nil {
return nil, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
if err != nil {
return nil, err
}
// check if repository customizations are valid
_, err = customizations.GetRepositories()
if err != nil {
return nil, err
}
return nil, nil
}

View file

@ -1,541 +0,0 @@
package fedora
// This file defines package sets that are used by more than one image type.
import (
"fmt"
"strconv"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
func qcow2CommonPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"@Fedora Cloud Server",
"chrony", // not mentioned in the kickstart, anaconda pulls it when setting the timezone
"langpacks-en",
"qemu-guest-agent",
},
Exclude: []string{
"dracut-config-rescue",
"firewalld",
"geolite2-city",
"geolite2-country",
"plymouth",
},
}
}
func vhdCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@core",
"chrony",
"langpacks-en",
"net-tools",
"ntfsprogs",
"libxcrypt-compat",
"initscripts",
"glibc-all-langpacks",
},
Exclude: []string{
"dracut-config-rescue",
"geolite2-city",
"geolite2-country",
"zram-generator-defaults",
},
}
return ps
}
func vmdkCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@Fedora Cloud Server",
"chrony",
"systemd-udev",
"langpacks-en",
"open-vm-tools",
},
Exclude: []string{
"dracut-config-rescue",
"etables",
"firewalld",
"geolite2-city",
"geolite2-country",
"gobject-introspection",
"plymouth",
"zram-generator-defaults",
"grubby-deprecated",
"extlinux-bootloader",
},
}
return ps
}
// fedora iot commit OS package set
func iotCommitPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"fedora-release-iot",
"glibc",
"glibc-minimal-langpack",
"nss-altfiles",
"sssd-client",
"libsss_sudo",
"shadow-utils",
"dracut-network",
"polkit",
"lvm2",
"cryptsetup",
"pinentry",
"keyutils",
"cracklib-dicts",
"e2fsprogs",
"xfsprogs",
"dosfstools",
"gnupg2",
"basesystem",
"python3",
"bash",
"xz",
"gzip",
"coreutils",
"which",
"curl",
"firewalld",
"iptables",
"NetworkManager",
"NetworkManager-wifi",
"NetworkManager-wwan",
"wpa_supplicant",
"iwd",
"tpm2-pkcs11",
"dnsmasq",
"traceroute",
"hostname",
"iproute",
"iputils",
"openssh-clients",
"openssh-server",
"passwd",
"policycoreutils",
"procps-ng",
"rootfiles",
"rpm",
"smartmontools-selinux",
"setup",
"shadow-utils",
"sudo",
"systemd",
"util-linux",
"vim-minimal",
"less",
"tar",
"fwupd",
"usbguard",
"greenboot",
"ignition",
"zezere-ignition",
"rsync",
"attr",
"ima-evm-utils",
"bash-completion",
"tmux",
"screen",
"policycoreutils-python-utils",
"setools-console",
"audit",
"rng-tools",
"chrony",
"bluez",
"bluez-libs",
"bluez-mesh",
"kernel-tools",
"libgpiod-utils",
"podman",
"container-selinux",
"skopeo",
"criu",
"slirp4netns",
"fuse-overlayfs",
"clevis",
"clevis-dracut",
"clevis-luks",
"clevis-pin-tpm2",
"parsec",
"dbus-parsec",
"iwl7260-firmware",
"iwlax2xx-firmware",
"greenboot-default-health-checks",
},
}
return ps
}
// INSTALLER PACKAGE SET
func installerPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"anaconda-dracut",
"curl",
"dracut-config-generic",
"dracut-network",
"hostname",
"iwl100-firmware",
"iwl1000-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"kernel",
"less",
"nfs-utils",
"openssh-clients",
"ostree",
"plymouth",
"rng-tools",
"rpcbind",
"selinux-policy-targeted",
"systemd",
"tar",
"xfsprogs",
"xz",
},
}
return ps
}
func anacondaPackageSet(t *imageType) rpmmd.PackageSet {
// common installer packages
ps := installerPackageSet(t)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"aajohan-comfortaa-fonts",
"abattis-cantarell-fonts",
"alsa-firmware",
"alsa-tools-firmware",
"anaconda",
"anaconda-dracut",
"anaconda-install-env-deps",
"anaconda-widgets",
"audit",
"bind-utils",
"bitmap-fangsongti-fonts",
"bzip2",
"cryptsetup",
"curl",
"dbus-x11",
"dejavu-sans-fonts",
"dejavu-sans-mono-fonts",
"device-mapper-persistent-data",
"dmidecode",
"dnf",
"dracut-config-generic",
"dracut-network",
"efibootmgr",
"ethtool",
"fcoe-utils",
"ftp",
"gdb-gdbserver",
"gdisk",
"glibc-all-langpacks",
"gnome-kiosk",
"google-noto-sans-cjk-ttc-fonts",
"grub2-tools",
"grub2-tools-extra",
"grub2-tools-minimal",
"grubby",
"gsettings-desktop-schemas",
"hdparm",
"hexedit",
"hostname",
"initscripts",
"ipmitool",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"jomolhari-fonts",
"kacst-farsi-fonts",
"kacst-qurn-fonts",
"kbd",
"kbd-misc",
"kdump-anaconda-addon",
"kernel",
"khmeros-base-fonts",
"less",
"libblockdev-lvm-dbus",
"libibverbs",
"libreport-plugin-bugzilla",
"libreport-plugin-reportuploader",
"librsvg2",
"linux-firmware",
"lldpad",
"lohit-assamese-fonts",
"lohit-bengali-fonts",
"lohit-devanagari-fonts",
"lohit-gujarati-fonts",
"lohit-gurmukhi-fonts",
"lohit-kannada-fonts",
"lohit-odia-fonts",
"lohit-tamil-fonts",
"lohit-telugu-fonts",
"lsof",
"madan-fonts",
"mtr",
"mt-st",
"net-tools",
"nfs-utils",
"nmap-ncat",
"nm-connection-editor",
"nss-tools",
"openssh-clients",
"openssh-server",
"oscap-anaconda-addon",
"ostree",
"pciutils",
"perl-interpreter",
"pigz",
"plymouth",
"python3-pyatspi",
"rdma-core",
"rit-meera-new-fonts",
"rng-tools",
"rpcbind",
"rpm-ostree",
"rsync",
"rsyslog",
"selinux-policy-targeted",
"sg3_utils",
"sil-abyssinica-fonts",
"sil-padauk-fonts",
"sil-scheherazade-new-fonts",
"smartmontools",
"spice-vdagent",
"strace",
"systemd",
"tar",
"thai-scalable-waree-fonts",
"tigervnc-server-minimal",
"tigervnc-server-module",
"udisks2",
"udisks2-iscsi",
"usbutils",
"vim-minimal",
"volume_key",
"wget",
"xfsdump",
"xfsprogs",
"xorg-x11-drivers",
"xorg-x11-fonts-misc",
"xorg-x11-server-Xorg",
"xorg-x11-xauth",
"metacity",
"xrdb",
"xz",
},
})
if common.VersionLessThan(t.arch.distro.osVersion, "39") {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"lklug-fonts", // orphaned, unavailable in F39
},
})
}
switch t.Arch().Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"biosdevname",
"dmidecode",
"grub2-tools-efi",
"memtest86+",
},
})
case platform.ARCH_AARCH64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"dmidecode",
},
})
default:
panic(fmt.Sprintf("unsupported arch: %s", t.Arch().Name()))
}
return ps
}
func iotInstallerPackageSet(t *imageType) rpmmd.PackageSet {
// include anaconda packages
ps := anacondaPackageSet(t)
releasever := t.Arch().Distro().Releasever()
version, err := strconv.Atoi(releasever)
if err != nil {
panic("cannot convert releasever to int: " + err.Error())
}
if version >= 38 {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"fedora-release-iot",
},
})
}
return ps
}
func liveInstallerPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@workstation-product-environment",
"@anaconda-tools",
"anaconda-install-env-deps",
"anaconda-live",
"anaconda-dracut",
"dracut-live",
"glibc-all-langpacks",
"kernel",
"kernel-modules",
"kernel-modules-extra",
"livesys-scripts",
"rng-tools",
"rdma-core",
"gnome-kiosk",
},
Exclude: []string{
"@dial-up",
"@input-methods",
"@standard",
"device-mapper-multipath",
"fcoe-utils",
"gfs2-utils",
"reiserfs-utils",
},
}
// We want to generate a preview image when rawhide is built
if !common.VersionLessThan(t.arch.distro.osVersion, "39") {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"anaconda-webui",
},
})
}
return ps
}
func imageInstallerPackageSet(t *imageType) rpmmd.PackageSet {
ps := anacondaPackageSet(t)
releasever := t.Arch().Distro().Releasever()
version, err := strconv.Atoi(releasever)
if err != nil {
panic("cannot convert releasever to int: " + err.Error())
}
// We want to generate a preview image when rawhide is built
if version >= 38 {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"anaconda-webui",
},
})
}
return ps
}
func containerPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"bash",
"coreutils",
"dnf-yum",
"dnf",
"fedora-release-container",
"fedora-repos-modular",
"glibc-minimal-langpack",
"rootfiles",
"rpm",
"sudo",
"tar",
"util-linux-core",
"vim-minimal",
},
Exclude: []string{
"crypto-policies-scripts",
"dbus-broker",
"deltarpm",
"dosfstools",
"e2fsprogs",
"elfutils-debuginfod-client",
"fuse-libs",
"gawk-all-langpacks",
"glibc-gconv-extra",
"glibc-langpack-en",
"gnupg2-smime",
"grubby",
"kernel-core",
"kernel-debug-core",
"kernel",
"langpacks-en_GB",
"langpacks-en",
"libss",
"libxcrypt-compat",
"nano",
"openssl-pkcs11",
"pinentry",
"python3-unbound",
"shared-mime-info",
"sssd-client",
"sudo-python-plugin",
"systemd",
"trousers",
"whois-nls",
"xkeyboard-config",
},
}
return ps
}
func minimalrpmPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"@core",
},
}
}

View file

@ -1,202 +0,0 @@
package fedora
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/platform"
)
var defaultBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 1 * common.MebiByte, // 1MB
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: 200 * common.MebiByte, // 200 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte, // 500 MB
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "ext4",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte, // 2GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "ext4",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 200 * common.MebiByte, // 200 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte, // 500 MB
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "ext4",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte, // 2GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "ext4",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
}
var iotBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 501 * common.MebiByte, // 501 MiB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "umask=0077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 1 * common.GibiByte, // 1 GiB
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "ext4",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 1,
FSTabPassNo: 2,
},
},
{
Size: 2569 * common.MebiByte, // 2.5 GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "ext4",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 1,
FSTabPassNo: 1,
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "0xc1748067",
Type: "dos",
Partitions: []disk.Partition{
{
Size: 501 * common.MebiByte, // 501 MiB
Type: "06",
Bootable: true,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "umask=0077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 1 * common.GibiByte, // 1 GiB
Type: "83",
Payload: &disk.Filesystem{
Type: "ext4",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 1,
FSTabPassNo: 2,
},
},
{
Size: 2569 * common.MebiByte, // 2.5 GiB
Type: "83",
Payload: &disk.Filesystem{
Type: "ext4",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 1,
FSTabPassNo: 1,
},
},
},
},
}

View file

@ -1,89 +0,0 @@
package distro
import (
"fmt"
"reflect"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/shell"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
// ImageConfig represents a (default) configuration applied to the image
type ImageConfig struct {
Timezone *string
TimeSynchronization *osbuild.ChronyStageOptions
Locale *string
Keyboard *osbuild.KeymapStageOptions
EnabledServices []string
DisabledServices []string
DefaultTarget *string
Sysconfig []*osbuild.SysconfigStageOptions
// List of files from which to import GPG keys into the RPM database
GPGKeyFiles []string
// Disable SELinux labelling
NoSElinux *bool
// Do not use. Forces auto-relabelling on first boot.
// See https://github.com/osbuild/osbuild/commit/52cb27631b587c1df177cd17625c5b473e1e85d2
SELinuxForceRelabel *bool
// Disable documentation
ExcludeDocs *bool
ShellInit []shell.InitFile
// for RHSM configuration, we need to potentially distinguish the case
// when the user want the image to be subscribed on first boot and when not
RHSMConfig map[subscription.RHSMStatus]*osbuild.RHSMStageOptions
SystemdLogind []*osbuild.SystemdLogindStageOptions
CloudInit []*osbuild.CloudInitStageOptions
Modprobe []*osbuild.ModprobeStageOptions
DracutConf []*osbuild.DracutConfStageOptions
SystemdUnit []*osbuild.SystemdUnitStageOptions
Authselect *osbuild.AuthselectStageOptions
SELinuxConfig *osbuild.SELinuxConfigStageOptions
Tuned *osbuild.TunedStageOptions
Tmpfilesd []*osbuild.TmpfilesdStageOptions
PamLimitsConf []*osbuild.PamLimitsConfStageOptions
Sysctld []*osbuild.SysctldStageOptions
DNFConfig []*osbuild.DNFConfigStageOptions
SshdConfig *osbuild.SshdConfigStageOptions
Authconfig *osbuild.AuthconfigStageOptions
PwQuality *osbuild.PwqualityConfStageOptions
WAAgentConfig *osbuild.WAAgentConfStageOptions
Grub2Config *osbuild.GRUB2Config
DNFAutomaticConfig *osbuild.DNFAutomaticConfigStageOptions
YumConfig *osbuild.YumConfigStageOptions
YUMRepos []*osbuild.YumReposStageOptions
Firewall *osbuild.FirewallStageOptions
UdevRules *osbuild.UdevRulesStageOptions
GCPGuestAgentConfig *osbuild.GcpGuestAgentConfigOptions
}
// InheritFrom inherits unset values from the provided parent configuration and
// returns a new structure instance, which is a result of the inheritance.
func (c *ImageConfig) InheritFrom(parentConfig *ImageConfig) *ImageConfig {
finalConfig := ImageConfig(*c)
if parentConfig != nil {
// iterate over all struct fields and copy unset values from the parent
for i := 0; i < reflect.TypeOf(*c).NumField(); i++ {
fieldName := reflect.TypeOf(*c).Field(i).Name
field := reflect.ValueOf(&finalConfig).Elem().FieldByName(fieldName)
// Only container types or pointer are supported.
// The reason is that with basic types, we can't distinguish between unset value and zero value.
if kind := field.Kind(); kind != reflect.Ptr && kind != reflect.Slice && kind != reflect.Map {
panic(fmt.Sprintf("unsupported field type: %s (only container types or pointer are supported)",
field.Kind()))
}
if field.IsNil() {
field.Set(reflect.ValueOf(parentConfig).Elem().FieldByName(fieldName))
}
}
}
return &finalConfig
}

View file

@ -1,218 +0,0 @@
package distro
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/osbuild"
)
func TestImageConfigInheritFrom(t *testing.T) {
tests := []struct {
name string
distroConfig *ImageConfig
imageConfig *ImageConfig
expectedConfig *ImageConfig
}{
{
name: "inheritance with overridden values",
distroConfig: &ImageConfig{
Timezone: common.ToPtr("America/New_York"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}},
},
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
EnabledServices: []string{"sshd"},
DisabledServices: []string{"named"},
DefaultTarget: common.ToPtr("multi-user.target"),
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
NetworkScripts: &osbuild.NetworkScriptsOptions{
IfcfgFiles: map[string]osbuild.IfcfgFile{
"eth0": {
Device: "eth0",
Bootproto: osbuild.IfcfgBootprotoDHCP,
OnBoot: common.ToPtr(true),
Type: osbuild.IfcfgTypeEthernet,
UserCtl: common.ToPtr(true),
PeerDNS: common.ToPtr(true),
IPv6Init: common.ToPtr(false),
},
},
},
},
},
},
imageConfig: &ImageConfig{
Timezone: common.ToPtr("UTC"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{
{
Hostname: "169.254.169.123",
Prefer: common.ToPtr(true),
Iburst: common.ToPtr(true),
Minpoll: common.ToPtr(4),
Maxpoll: common.ToPtr(4),
},
},
LeapsecTz: common.ToPtr(""),
},
},
expectedConfig: &ImageConfig{
Timezone: common.ToPtr("UTC"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{
{
Hostname: "169.254.169.123",
Prefer: common.ToPtr(true),
Iburst: common.ToPtr(true),
Minpoll: common.ToPtr(4),
Maxpoll: common.ToPtr(4),
},
},
LeapsecTz: common.ToPtr(""),
},
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
EnabledServices: []string{"sshd"},
DisabledServices: []string{"named"},
DefaultTarget: common.ToPtr("multi-user.target"),
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
NetworkScripts: &osbuild.NetworkScriptsOptions{
IfcfgFiles: map[string]osbuild.IfcfgFile{
"eth0": {
Device: "eth0",
Bootproto: osbuild.IfcfgBootprotoDHCP,
OnBoot: common.ToPtr(true),
Type: osbuild.IfcfgTypeEthernet,
UserCtl: common.ToPtr(true),
PeerDNS: common.ToPtr(true),
IPv6Init: common.ToPtr(false),
},
},
},
},
},
},
},
{
name: "empty image type configuration",
distroConfig: &ImageConfig{
Timezone: common.ToPtr("America/New_York"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}},
},
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
EnabledServices: []string{"sshd"},
DisabledServices: []string{"named"},
DefaultTarget: common.ToPtr("multi-user.target"),
},
imageConfig: &ImageConfig{},
expectedConfig: &ImageConfig{
Timezone: common.ToPtr("America/New_York"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}},
},
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
EnabledServices: []string{"sshd"},
DisabledServices: []string{"named"},
DefaultTarget: common.ToPtr("multi-user.target"),
},
},
{
name: "empty distro configuration",
distroConfig: &ImageConfig{},
imageConfig: &ImageConfig{
Timezone: common.ToPtr("America/New_York"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}},
},
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
EnabledServices: []string{"sshd"},
DisabledServices: []string{"named"},
DefaultTarget: common.ToPtr("multi-user.target"),
},
expectedConfig: &ImageConfig{
Timezone: common.ToPtr("America/New_York"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}},
},
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
EnabledServices: []string{"sshd"},
DisabledServices: []string{"named"},
DefaultTarget: common.ToPtr("multi-user.target"),
},
},
{
name: "empty distro configuration",
distroConfig: nil,
imageConfig: &ImageConfig{
Timezone: common.ToPtr("America/New_York"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}},
},
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
EnabledServices: []string{"sshd"},
DisabledServices: []string{"named"},
DefaultTarget: common.ToPtr("multi-user.target"),
},
expectedConfig: &ImageConfig{
Timezone: common.ToPtr("America/New_York"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "127.0.0.1"}},
},
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
EnabledServices: []string{"sshd"},
DisabledServices: []string{"named"},
DefaultTarget: common.ToPtr("multi-user.target"),
},
},
}
for idx, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equalf(t, tt.expectedConfig, tt.imageConfig.InheritFrom(tt.distroConfig), "test case %q failed (idx %d)", tt.name, idx)
})
}
}

View file

@ -1,384 +0,0 @@
package rhel7
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
var azureRhuiImgType = imageType{
name: "azure-rhui",
filename: "disk.vhd.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: azureRhuiCommonPackageSet,
},
packageSetChains: map[string][]string{
osPkgsKey: {osPkgsKey, blueprintPkgsKey},
},
defaultImageConfig: azureDefaultImgConfig,
kernelOptions: "ro crashkernel=auto console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300 scsi_mod.use_blk_mq=y",
bootable: true,
defaultSize: 64 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc", "xz"},
exports: []string{"xz"},
basePartitionTables: azureRhuiBasePartitionTables,
}
var azureDefaultImgConfig = &distro.ImageConfig{
Timezone: common.ToPtr("Etc/UTC"),
Locale: common.ToPtr("en_US.UTF-8"),
GPGKeyFiles: []string{
"/etc/pki/rpm-gpg/RPM-GPG-KEY-microsoft-azure-release",
"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
},
SELinuxForceRelabel: common.ToPtr(true),
Authconfig: &osbuild.AuthconfigStageOptions{},
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel-core",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
},
},
EnabledServices: []string{
"cloud-config",
"cloud-final",
"cloud-init-local",
"cloud-init",
"firewalld",
"NetworkManager",
"sshd",
"waagent",
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
ClientAliveInterval: common.ToPtr(180),
},
},
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-amdgpu.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("amdgpu"),
},
},
{
Filename: "blacklist-intel-cstate.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("intel_cstate"),
},
},
{
Filename: "blacklist-floppy.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("floppy"),
},
},
{
Filename: "blacklist-nouveau.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("nouveau"),
osbuild.NewModprobeConfigCmdBlacklist("lbm-nouveau"),
},
},
{
Filename: "blacklist-skylake-edac.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("skx_edac"),
},
},
},
CloudInit: []*osbuild.CloudInitStageOptions{
{
Filename: "06_logging_override.cfg",
Config: osbuild.CloudInitConfigFile{
Output: &osbuild.CloudInitConfigOutput{
All: common.ToPtr("| tee -a /var/log/cloud-init-output.log"),
},
},
},
{
Filename: "10-azure-kvp.cfg",
Config: osbuild.CloudInitConfigFile{
Reporting: &osbuild.CloudInitConfigReporting{
Logging: &osbuild.CloudInitConfigReportingHandlers{
Type: "log",
},
Telemetry: &osbuild.CloudInitConfigReportingHandlers{
Type: "hyperv",
},
},
},
},
{
Filename: "91-azure_datasource.cfg",
Config: osbuild.CloudInitConfigFile{
Datasource: &osbuild.CloudInitConfigDatasource{
Azure: &osbuild.CloudInitConfigDatasourceAzure{
ApplyNetworkConfig: false,
},
},
DatasourceList: []string{
"Azure",
},
},
},
},
PwQuality: &osbuild.PwqualityConfStageOptions{
Config: osbuild.PwqualityConfConfig{
Minlen: common.ToPtr(6),
Minclass: common.ToPtr(3),
Dcredit: common.ToPtr(0),
Ucredit: common.ToPtr(0),
Lcredit: common.ToPtr(0),
Ocredit: common.ToPtr(0),
},
},
WAAgentConfig: &osbuild.WAAgentConfStageOptions{
Config: osbuild.WAAgentConfig{
RDFormat: common.ToPtr(false),
RDEnableSwap: common.ToPtr(false),
},
},
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
YumPlugins: &osbuild.RHSMStageOptionsDnfPlugins{
SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
},
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
Rhsm: &osbuild.SubManConfigRHSMSection{
ManageRepos: common.ToPtr(false),
},
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
Grub2Config: &osbuild.GRUB2Config{
TerminalInput: []string{"serial", "console"},
TerminalOutput: []string{"serial", "console"},
Serial: "serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1",
Timeout: 10,
},
UdevRules: &osbuild.UdevRulesStageOptions{
Filename: "/etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules",
Rules: osbuild.UdevRules{
osbuild.UdevRuleComment{
Comment: []string{
"Accelerated Networking on Azure exposes a new SRIOV interface to the VM.",
"This interface is transparently bonded to the synthetic interface,",
"so NetworkManager should just ignore any SRIOV interfaces.",
},
},
osbuild.NewUdevRule(
[]osbuild.UdevKV{
{K: "SUBSYSTEM", O: "==", V: "net"},
{K: "DRIVERS", O: "==", V: "hv_pci"},
{K: "ACTION", O: "==", V: "add"},
{K: "ENV", A: "NM_UNMANAGED", O: "=", V: "1"},
},
),
},
},
YumConfig: &osbuild.YumConfigStageOptions{
Config: &osbuild.YumConfigConfig{
HttpCaching: common.ToPtr("packages"),
},
Plugins: &osbuild.YumConfigPlugins{
Langpacks: &osbuild.YumConfigPluginsLangpacks{
Locales: []string{"en_US.UTF-8"},
},
},
},
DefaultTarget: common.ToPtr("multi-user.target"),
}
func azureRhuiCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@base",
"@core",
"authconfig",
"bpftool",
"bzip2",
"chrony",
"cloud-init",
"cloud-utils-growpart",
"dracut-config-generic",
"dracut-norescue",
"efibootmgr",
"firewalld",
"gdisk",
"grub2-efi-x64",
"grub2-pc",
"grub2",
"hyperv-daemons",
"kernel",
"lvm2",
"redhat-release-eula",
"redhat-support-tool",
"rh-dotnetcore11",
"rhn-setup",
"rhui-azure-rhel7",
"rsync",
"shim-x64",
"tar",
"tcpdump",
"WALinuxAgent",
"yum-rhn-plugin",
"yum-utils",
},
Exclude: []string{
"dracut-config-rescue",
"mariadb-libs",
"NetworkManager-config-server",
"postfix",
},
}
if t.arch.distro.isRHEL() {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"insights-client",
},
})
}
return ps
}
var azureRhuiBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Size: 64 * common.GibiByte,
Partitions: []disk.Partition{
{
Size: 500 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Type: disk.LVMPartitionGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 1 * common.GibiByte,
Name: "homelv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "home",
Mountpoint: "/home",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "tmplv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "tmp",
Mountpoint: "/tmp",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "usrlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "usr",
Mountpoint: "/usr",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte, // firedrill: 8 GB
Name: "varlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "var",
Mountpoint: "/var",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
},
}

View file

@ -1,235 +0,0 @@
package rhel7
import (
"errors"
"fmt"
"sort"
"strings"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/runner"
)
const (
// package set names
// main/common os image package set name
osPkgsKey = "os"
// blueprint package set name
blueprintPkgsKey = "blueprint"
)
// RHEL-based OS image configuration defaults
var defaultDistroImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("America/New_York"),
Locale: common.ToPtr("en_US.UTF-8"),
GPGKeyFiles: []string{
"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
},
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
},
},
}
// distribution objects without the arches > image types
var distroMap = map[string]distribution{
"rhel-7": {
name: "rhel-7",
product: "Red Hat Enterprise Linux",
osVersion: "7.9",
nick: "Maipo",
releaseVersion: "7",
modulePlatformID: "platform:el7",
vendor: "redhat",
runner: &runner.RHEL{Major: uint64(7), Minor: uint64(9)},
defaultImageConfig: defaultDistroImageConfig,
},
}
// --- Distribution ---
type distribution struct {
name string
product string
nick string
osVersion string
releaseVersion string
modulePlatformID string
vendor string
runner runner.Runner
arches map[string]distro.Arch
defaultImageConfig *distro.ImageConfig
}
func (d *distribution) Name() string {
return d.name
}
func (d *distribution) Releasever() string {
return d.releaseVersion
}
func (d *distribution) ModulePlatformID() string {
return d.modulePlatformID
}
func (d *distribution) OSTreeRef() string {
return "" // not supported
}
func (d *distribution) ListArches() []string {
archNames := make([]string, 0, len(d.arches))
for name := range d.arches {
archNames = append(archNames, name)
}
sort.Strings(archNames)
return archNames
}
func (d *distribution) GetArch(name string) (distro.Arch, error) {
arch, exists := d.arches[name]
if !exists {
return nil, errors.New("invalid architecture: " + name)
}
return arch, nil
}
func (d *distribution) addArches(arches ...architecture) {
if d.arches == nil {
d.arches = map[string]distro.Arch{}
}
// Do not make copies of architectures, as opposed to image types,
// because architecture definitions are not used by more than a single
// distro definition.
for idx := range arches {
d.arches[arches[idx].name] = &arches[idx]
}
}
func (d *distribution) isRHEL() bool {
return strings.HasPrefix(d.name, "rhel")
}
func (d *distribution) getDefaultImageConfig() *distro.ImageConfig {
return d.defaultImageConfig
}
// --- Architecture ---
type architecture struct {
distro *distribution
name string
imageTypes map[string]distro.ImageType
imageTypeAliases map[string]string
}
func (a *architecture) Name() string {
return a.name
}
func (a *architecture) ListImageTypes() []string {
itNames := make([]string, 0, len(a.imageTypes))
for name := range a.imageTypes {
itNames = append(itNames, name)
}
sort.Strings(itNames)
return itNames
}
func (a *architecture) GetImageType(name string) (distro.ImageType, error) {
t, exists := a.imageTypes[name]
if !exists {
aliasForName, exists := a.imageTypeAliases[name]
if !exists {
return nil, errors.New("invalid image type: " + name)
}
t, exists = a.imageTypes[aliasForName]
if !exists {
panic(fmt.Sprintf("image type '%s' is an alias to a non-existing image type '%s'", name, aliasForName))
}
}
return t, nil
}
func (a *architecture) addImageTypes(platform platform.Platform, imageTypes ...imageType) {
if a.imageTypes == nil {
a.imageTypes = map[string]distro.ImageType{}
}
for idx := range imageTypes {
it := imageTypes[idx]
it.arch = a
it.platform = platform
a.imageTypes[it.name] = &it
for _, alias := range it.nameAliases {
if a.imageTypeAliases == nil {
a.imageTypeAliases = map[string]string{}
}
if existingAliasFor, exists := a.imageTypeAliases[alias]; exists {
panic(fmt.Sprintf("image type alias '%s' for '%s' is already defined for another image type '%s'", alias, it.name, existingAliasFor))
}
a.imageTypeAliases[alias] = it.name
}
}
}
func (a *architecture) Distro() distro.Distro {
return a.distro
}
// New creates a new distro object, defining the supported architectures and image types
func New() distro.Distro {
return newDistro("rhel-7")
}
func newDistro(distroName string) distro.Distro {
rd := distroMap[distroName]
// Architecture definitions
x86_64 := architecture{
name: platform.ARCH_X86_64.String(),
distro: &rd,
}
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "0.10",
},
},
qcow2ImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VHD,
},
},
azureRhuiImgType,
)
rd.addArches(
x86_64,
)
return &rd
}

View file

@ -1,452 +0,0 @@
package rhel7_test
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distro/distro_test_common"
"github.com/osbuild/osbuild-composer/internal/distro/rhel7"
)
type rhelFamilyDistro struct {
name string
distro distro.Distro
}
var rhelFamilyDistros = []rhelFamilyDistro{
{
name: "rhel",
distro: rhel7.New(),
},
}
func TestFilenameFromType(t *testing.T) {
type args struct {
outputFormat string
}
type wantResult struct {
filename string
mimeType string
wantErr bool
}
tests := []struct {
name string
args args
want wantResult
}{
{
name: "qcow2",
args: args{"qcow2"},
want: wantResult{
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
},
},
{
name: "azure-rhui",
args: args{"azure-rhui"},
want: wantResult{
filename: "disk.vhd.xz",
mimeType: "application/xz",
},
},
{
name: "invalid-output-type",
args: args{"foobar"},
want: wantResult{wantErr: true},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dist := dist.distro
arch, _ := dist.GetArch("x86_64")
imgType, err := arch.GetImageType(tt.args.outputFormat)
if (err != nil) != tt.want.wantErr {
t.Errorf("Arch.GetImageType() error = %v, wantErr %v", err, tt.want.wantErr)
return
}
if !tt.want.wantErr {
gotFilename := imgType.Filename()
gotMIMEType := imgType.MIMEType()
if gotFilename != tt.want.filename {
t.Errorf("ImageType.Filename() got = %v, want %v", gotFilename, tt.want.filename)
}
if gotMIMEType != tt.want.mimeType {
t.Errorf("ImageType.MIMEType() got1 = %v, want %v", gotMIMEType, tt.want.mimeType)
}
}
})
}
})
}
}
func TestImageType_BuildPackages(t *testing.T) {
x8664BuildPackages := []string{
"dnf",
"dosfstools",
"e2fsprogs",
"grub2-efi-x64",
"grub2-pc",
"policycoreutils",
"shim-x64",
"systemd",
"tar",
"qemu-img",
"xz",
}
buildPackages := map[string][]string{
"x86_64": x8664BuildPackages,
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
d := dist.distro
for _, archLabel := range d.ListArches() {
archStruct, err := d.GetArch(archLabel)
if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) {
continue
}
for _, itLabel := range archStruct.ListImageTypes() {
itStruct, err := archStruct.GetImageType(itLabel)
if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) {
continue
}
manifest, _, err := itStruct.Manifest(&blueprint.Blueprint{}, distro.ImageOptions{}, nil, 0)
assert.NoError(t, err)
buildPkgs := manifest.GetPackageSetChains()["build"]
assert.NotNil(t, buildPkgs)
assert.Len(t, buildPkgs, 1)
assert.ElementsMatch(t, buildPackages[archLabel], buildPkgs[0].Include)
}
}
})
}
}
func TestImageType_Name(t *testing.T) {
imgMap := []struct {
arch string
imgNames []string
}{
{
arch: "x86_64",
imgNames: []string{
"qcow2",
"azure-rhui",
},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, mapping := range imgMap {
arch, err := dist.distro.GetArch(mapping.arch)
if assert.NoError(t, err) {
for _, imgName := range mapping.imgNames {
imgType, err := arch.GetImageType(imgName)
if assert.NoError(t, err) {
assert.Equalf(t, imgName, imgType.Name(), "arch: %s", mapping.arch)
}
}
}
}
})
}
}
// Check that Manifest() function returns an error for unsupported
// configurations.
func TestDistro_ManifestError(t *testing.T) {
r7distro := rhel7.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Kernel: &blueprint.KernelCustomization{
Append: "debug",
},
},
}
for _, archName := range r7distro.ListArches() {
arch, _ := r7distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
imgOpts := distro.ImageOptions{
Size: imgType.Size(0),
}
_, _, err := imgType.Manifest(&bp, imgOpts, nil, 0)
assert.NoError(t, err)
}
}
}
func TestArchitecture_ListImageTypes(t *testing.T) {
imgMap := []struct {
arch string
imgNames []string
rhelAdditionalImageTypes []string
}{
{
arch: "x86_64",
imgNames: []string{
"qcow2",
"azure-rhui",
},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, mapping := range imgMap {
arch, err := dist.distro.GetArch(mapping.arch)
require.NoError(t, err)
imageTypes := arch.ListImageTypes()
var expectedImageTypes []string
expectedImageTypes = append(expectedImageTypes, mapping.imgNames...)
if dist.name == "rhel" {
expectedImageTypes = append(expectedImageTypes, mapping.rhelAdditionalImageTypes...)
}
require.ElementsMatch(t, expectedImageTypes, imageTypes)
}
})
}
}
func TestRhel7_ListArches(t *testing.T) {
arches := rhel7.New().ListArches()
assert.Equal(t, []string{"x86_64"}, arches)
}
func TestRhel7_GetArch(t *testing.T) {
arches := []struct {
name string
errorExpected bool
errorExpectedInCentos bool
}{
{
name: "x86_64",
},
{
name: "foo-arch",
errorExpected: true,
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, a := range arches {
actualArch, err := dist.distro.GetArch(a.name)
if a.errorExpected || (a.errorExpectedInCentos && dist.name == "centos") {
assert.Nil(t, actualArch)
assert.Error(t, err)
} else {
assert.Equal(t, a.name, actualArch.Name())
assert.NoError(t, err)
}
}
})
}
}
func TestRhel7_Name(t *testing.T) {
distro := rhel7.New()
assert.Equal(t, "rhel-7", distro.Name())
}
func TestRhel7_ModulePlatformID(t *testing.T) {
distro := rhel7.New()
assert.Equal(t, "platform:el7", distro.ModulePlatformID())
}
func TestRhel7_KernelOption(t *testing.T) {
distro_test_common.TestDistro_KernelOption(t, rhel7.New())
}
func TestDistro_CustomFileSystemManifestError(t *testing.T) {
r7distro := rhel7.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/etc",
},
},
},
}
for _, archName := range r7distro.ListArches() {
arch, _ := r7distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/etc\"]")
}
}
}
func TestDistro_TestRootMountPoint(t *testing.T) {
r7distro := rhel7.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/",
},
},
},
}
for _, archName := range r7distro.ListArches() {
arch, _ := r7distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
assert.NoError(t, err)
}
}
}
func TestDistro_CustomFileSystemSubDirectories(t *testing.T) {
r7distro := rhel7.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/var/log",
},
{
MinSize: 1024,
Mountpoint: "/var/log/audit",
},
},
},
}
for _, archName := range r7distro.ListArches() {
arch, _ := r7distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
assert.NoError(t, err)
}
}
}
func TestDistro_MountpointsWithArbitraryDepthAllowed(t *testing.T) {
r7distro := rhel7.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/var/a",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b/c",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b/c/d",
},
},
},
}
for _, archName := range r7distro.ListArches() {
arch, _ := r7distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
assert.NoError(t, err)
}
}
}
func TestDistro_DirtyMountpointsNotAllowed(t *testing.T) {
r7distro := rhel7.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "//",
},
{
MinSize: 1024,
Mountpoint: "/var//",
},
{
MinSize: 1024,
Mountpoint: "/var//log/audit/",
},
},
},
}
for _, archName := range r7distro.ListArches() {
arch, _ := r7distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"//\" \"/var//\" \"/var//log/audit/\"]")
}
}
}
func TestDistro_CustomFileSystemPatternMatching(t *testing.T) {
r7distro := rhel7.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/variable",
},
{
MinSize: 1024,
Mountpoint: "/variable/log/audit",
},
},
},
}
for _, archName := range r7distro.ListArches() {
arch, _ := r7distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/variable\" \"/variable/log/audit\"]")
}
}
}
func TestDistro_CustomUsrPartitionNotLargeEnough(t *testing.T) {
r7distro := rhel7.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/usr",
},
},
},
}
for _, archName := range r7distro.ListArches() {
arch, _ := r7distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
assert.NoError(t, err)
}
}
}

View file

@ -1,250 +0,0 @@
package rhel7
import (
"fmt"
"math/rand"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/users"
"github.com/osbuild/osbuild-composer/internal/workload"
)
func osCustomizations(
t *imageType,
osPackageSet rpmmd.PackageSet,
options distro.ImageOptions,
containers []container.SourceSpec,
c *blueprint.Customizations,
) manifest.OSCustomizations {
imageConfig := t.getDefaultImageConfig()
osc := manifest.OSCustomizations{}
if t.bootable {
osc.KernelName = c.GetKernel().Name
var kernelOptions []string
if t.kernelOptions != "" {
kernelOptions = append(kernelOptions, t.kernelOptions)
}
if bpKernel := c.GetKernel(); bpKernel.Append != "" {
kernelOptions = append(kernelOptions, bpKernel.Append)
}
osc.KernelOptionsAppend = kernelOptions
if t.platform.GetArch() != platform.ARCH_S390X {
osc.KernelOptionsBootloader = true
}
}
osc.ExtraBasePackages = osPackageSet.Include
osc.ExcludeBasePackages = osPackageSet.Exclude
osc.ExtraBaseRepos = osPackageSet.Repositories
osc.Containers = containers
osc.GPGKeyFiles = imageConfig.GPGKeyFiles
if imageConfig.ExcludeDocs != nil {
osc.ExcludeDocs = *imageConfig.ExcludeDocs
}
// don't put users and groups in the payload of an installer
// add them via kickstart instead
osc.Groups = users.GroupsFromBP(c.GetGroups())
osc.Users = users.UsersFromBP(c.GetUsers())
osc.EnabledServices = imageConfig.EnabledServices
osc.DisabledServices = imageConfig.DisabledServices
if imageConfig.DefaultTarget != nil {
osc.DefaultTarget = *imageConfig.DefaultTarget
}
osc.Firewall = imageConfig.Firewall
if fw := c.GetFirewall(); fw != nil {
options := osbuild.FirewallStageOptions{
Ports: fw.Ports,
}
if fw.Services != nil {
options.EnabledServices = fw.Services.Enabled
options.DisabledServices = fw.Services.Disabled
}
if fw.Zones != nil {
for _, z := range fw.Zones {
options.Zones = append(options.Zones, osbuild.FirewallZone{
Name: *z.Name,
Sources: z.Sources,
})
}
}
osc.Firewall = &options
}
language, keyboard := c.GetPrimaryLocale()
if language != nil {
osc.Language = *language
} else if imageConfig.Locale != nil {
osc.Language = *imageConfig.Locale
}
if keyboard != nil {
osc.Keyboard = keyboard
} else if imageConfig.Keyboard != nil {
osc.Keyboard = &imageConfig.Keyboard.Keymap
if imageConfig.Keyboard.X11Keymap != nil {
osc.X11KeymapLayouts = imageConfig.Keyboard.X11Keymap.Layouts
}
}
if hostname := c.GetHostname(); hostname != nil {
osc.Hostname = *hostname
}
timezone, ntpServers := c.GetTimezoneSettings()
if timezone != nil {
osc.Timezone = *timezone
} else if imageConfig.Timezone != nil {
osc.Timezone = *imageConfig.Timezone
}
if len(ntpServers) > 0 {
for _, server := range ntpServers {
osc.NTPServers = append(osc.NTPServers, osbuild.ChronyConfigServer{Hostname: server})
}
} else if imageConfig.TimeSynchronization != nil {
osc.NTPServers = imageConfig.TimeSynchronization.Servers
osc.LeapSecTZ = imageConfig.TimeSynchronization.LeapsecTz
}
// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
osc.SElinux = "targeted"
osc.SELinuxForceRelabel = imageConfig.SELinuxForceRelabel
}
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(
osbuild.OscapConfig{
Datastream: oscapConfig.DataStream,
ProfileID: oscapConfig.ProfileID,
},
)
}
if t.arch.distro.isRHEL() && options.Facts != nil {
osc.FactAPIType = &options.Facts.APIType
}
var err error
osc.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(c.GetDirectories())
if err != nil {
// In theory this should never happen, because the blueprint directory customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert directory customizations to fs node directories: %v", err))
}
osc.Files, err = blueprint.FileCustomizationsToFsNodeFiles(c.GetFiles())
if err != nil {
// In theory this should never happen, because the blueprint file customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert file customizations to fs node files: %v", err))
}
// set yum repos first, so it doesn't get overridden by
// imageConfig.YUMRepos
osc.YUMRepos = imageConfig.YUMRepos
customRepos, err := c.GetRepositories()
if err != nil {
// This shouldn't happen and since the repos
// should have already been validated
panic(fmt.Sprintf("failed to get custom repos: %v", err))
}
// This function returns a map of filename and corresponding yum repos
// and a list of fs node files for the inline gpg keys so we can save
// them to disk. This step also swaps the inline gpg key with the path
// to the file in the os file tree
yumRepos, gpgKeyFiles, err := blueprint.RepoCustomizationsToRepoConfigAndGPGKeyFiles(customRepos)
if err != nil {
panic(fmt.Sprintf("failed to convert inline gpgkeys to fs node files: %v", err))
}
// add the gpg key files to the list of files to be added to the tree
if len(gpgKeyFiles) > 0 {
osc.Files = append(osc.Files, gpgKeyFiles...)
}
for filename, repos := range yumRepos {
osc.YUMRepos = append(osc.YUMRepos, osbuild.NewYumReposStageOptions(filename, repos))
}
osc.ShellInit = imageConfig.ShellInit
osc.Grub2Config = imageConfig.Grub2Config
osc.Sysconfig = imageConfig.Sysconfig
osc.SystemdLogind = imageConfig.SystemdLogind
osc.CloudInit = imageConfig.CloudInit
osc.Modprobe = imageConfig.Modprobe
osc.DracutConf = imageConfig.DracutConf
osc.SystemdUnit = imageConfig.SystemdUnit
osc.Authselect = imageConfig.Authselect
osc.SELinuxConfig = imageConfig.SELinuxConfig
osc.Tuned = imageConfig.Tuned
osc.Tmpfilesd = imageConfig.Tmpfilesd
osc.PamLimitsConf = imageConfig.PamLimitsConf
osc.Sysctld = imageConfig.Sysctld
osc.DNFConfig = imageConfig.DNFConfig
osc.DNFAutomaticConfig = imageConfig.DNFAutomaticConfig
osc.YUMConfig = imageConfig.YumConfig
osc.SshdConfig = imageConfig.SshdConfig
osc.AuthConfig = imageConfig.Authconfig
osc.PwQuality = imageConfig.PwQuality
osc.RHSMConfig = imageConfig.RHSMConfig
osc.Subscription = options.Subscription
osc.WAAgentConfig = imageConfig.WAAgentConfig
osc.UdevRules = imageConfig.UdevRules
osc.GCPGuestAgentConfig = imageConfig.GCPGuestAgentConfig
return osc
}
func liveImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewLiveImage()
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.Environment = t.environment
img.Workload = workload
img.Compression = t.compression
img.PartTool = osbuild.PTSgdisk // all RHEL 7 images should use sgdisk
img.ForceSize = common.ToPtr(false) // RHEL 7 qemu vpc subformat does not support force_size
img.NoBLS = true // RHEL 7 grub does not support BLS
img.OSProduct = t.arch.distro.product
img.OSVersion = t.arch.distro.osVersion
img.OSNick = t.arch.distro.nick
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
return img, nil
}

View file

@ -1,285 +0,0 @@
package rhel7
import (
"fmt"
"math/rand"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/pathpolicy"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/workload"
"golang.org/x/exp/slices"
)
type packageSetFunc func(t *imageType) rpmmd.PackageSet
type imageFunc func(workload workload.Workload, t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, packageSets map[string]rpmmd.PackageSet, containers []container.SourceSpec, rng *rand.Rand) (image.ImageKind, error)
type imageType struct {
arch *architecture
platform platform.Platform
environment environment.Environment
workload workload.Workload
name string
nameAliases []string
filename string
compression string // TODO: remove from image definition and make it a transport option
mimeType string
packageSets map[string]packageSetFunc
packageSetChains map[string][]string
defaultImageConfig *distro.ImageConfig
kernelOptions string
defaultSize uint64
buildPipelines []string
payloadPipelines []string
exports []string
image imageFunc
// bootable image
bootable bool
// List of valid arches for the image type
basePartitionTables distro.BasePartitionTableMap
}
func (t *imageType) Name() string {
return t.name
}
func (t *imageType) Arch() distro.Arch {
return t.arch
}
func (t *imageType) Filename() string {
return t.filename
}
func (t *imageType) MIMEType() string {
return t.mimeType
}
func (t *imageType) OSTreeRef() string {
// Not supported
return ""
}
func (t *imageType) Size(size uint64) uint64 {
if size == 0 {
size = t.defaultSize
}
return size
}
func (t *imageType) BuildPipelines() []string {
return t.buildPipelines
}
func (t *imageType) PayloadPipelines() []string {
return t.payloadPipelines
}
func (t *imageType) PayloadPackageSets() []string {
return []string{blueprintPkgsKey}
}
func (t *imageType) PackageSetsChains() map[string][]string {
return t.packageSetChains
}
func (t *imageType) Exports() []string {
if len(t.exports) == 0 {
panic(fmt.Sprintf("programming error: no exports for '%s'", t.name))
}
return t.exports
}
func (t *imageType) BootMode() distro.BootMode {
if t.platform.GetUEFIVendor() != "" && t.platform.GetBIOSPlatform() != "" {
return distro.BOOT_HYBRID
} else if t.platform.GetUEFIVendor() != "" {
return distro.BOOT_UEFI
} else if t.platform.GetBIOSPlatform() != "" || t.platform.GetZiplSupport() {
return distro.BOOT_LEGACY
}
return distro.BOOT_NONE
}
func (t *imageType) getPartitionTable(
mountpoints []blueprint.FilesystemCustomization,
options distro.ImageOptions,
rng *rand.Rand,
) (*disk.PartitionTable, error) {
archName := t.arch.Name()
basePartitionTable, exists := t.basePartitionTables[archName]
if !exists {
return nil, fmt.Errorf("unknown arch: " + archName)
}
imageSize := t.Size(options.Size)
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, true, nil, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
// ensure that image always returns non-nil default config
imageConfig := t.defaultImageConfig
if imageConfig == nil {
imageConfig = &distro.ImageConfig{}
}
return imageConfig.InheritFrom(t.arch.distro.getDefaultImageConfig())
}
func (t *imageType) PartitionType() string {
archName := t.arch.Name()
basePartitionTable, exists := t.basePartitionTables[archName]
if !exists {
return ""
}
return basePartitionTable.Type
}
func (t *imageType) Manifest(bp *blueprint.Blueprint,
options distro.ImageOptions,
repos []rpmmd.RepoConfig,
seed int64) (*manifest.Manifest, []string, error) {
warnings, err := t.checkOptions(bp, options)
if err != nil {
return nil, nil, err
}
// merge package sets that appear in the image type with the package sets
// of the same name from the distro and arch
staticPackageSets := make(map[string]rpmmd.PackageSet)
for name, getter := range t.packageSets {
staticPackageSets[name] = getter(t)
}
// amend with repository information and collect payload repos
payloadRepos := make([]rpmmd.RepoConfig, 0)
for _, repo := range repos {
if len(repo.PackageSets) > 0 {
// only apply the repo to the listed package sets
for _, psName := range repo.PackageSets {
if slices.Contains(t.PayloadPackageSets(), psName) {
payloadRepos = append(payloadRepos, repo)
}
ps := staticPackageSets[psName]
ps.Repositories = append(ps.Repositories, repo)
staticPackageSets[psName] = ps
}
}
}
w := t.workload
if w == nil {
cw := &workload.Custom{
BaseWorkload: workload.BaseWorkload{
Repos: payloadRepos,
},
Packages: bp.GetPackagesEx(false),
}
if services := bp.Customizations.GetServices(); services != nil {
cw.Services = services.Enabled
cw.DisabledServices = services.Disabled
}
w = cw
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx := range bp.Containers {
containerSources[idx] = container.SourceSpec(bp.Containers[idx])
}
source := rand.NewSource(seed)
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(source)
if t.image == nil {
return nil, nil, nil
}
img, err := t.image(w, t, bp.Customizations, options, staticPackageSets, containerSources, rng)
if err != nil {
return nil, nil, err
}
mf := manifest.New()
mf.Distro = manifest.DISTRO_EL7
_, err = img.InstantiateManifest(&mf, repos, t.arch.distro.runner, rng)
if err != nil {
return nil, nil, err
}
return &mf, warnings, err
}
// checkOptions checks the validity and compatibility of options and customizations for the image type.
// Returns ([]string, error) where []string, if non-nil, will hold any generated warnings (e.g. deprecation notices).
func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOptions) ([]string, error) {
customizations := bp.Customizations
// holds warnings (e.g. deprecation notices)
var warnings []string
if t.workload != nil {
// For now, if an image type defines its own workload, don't allow any
// user customizations.
// Soon we will have more workflows and each will define its allowed
// set of customizations. The current set of customizations defined in
// the blueprint spec corresponds to the Custom workflow.
if customizations != nil {
return warnings, fmt.Errorf("image type %q does not support customizations", t.name)
}
}
if len(bp.Containers) > 0 {
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
mountpoints := customizations.GetFilesystems()
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
if err != nil {
return warnings, err
}
if osc := customizations.GetOpenSCAP(); osc != nil {
return warnings, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported os version: %s", t.arch.distro.osVersion))
}
// Check Directory/File Customizations are valid
dc := customizations.GetDirectories()
fc := customizations.GetFiles()
err = blueprint.ValidateDirFileCustomizations(dc, fc)
if err != nil {
return warnings, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
if err != nil {
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
if err != nil {
return warnings, err
}
// check if repository customizations are valid
_, err = customizations.GetRepositories()
if err != nil {
return warnings, err
}
return warnings, nil
}

View file

@ -1,15 +0,0 @@
package rhel7
import (
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
// packages that are only in some (sub)-distributions
func distroSpecificPackageSet(t *imageType) rpmmd.PackageSet {
if t.arch.distro.isRHEL() {
return rpmmd.PackageSet{
Include: []string{"insights-client"},
}
}
return rpmmd.PackageSet{}
}

View file

@ -1,65 +0,0 @@
package rhel7
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/platform"
)
// ////////// Partition table //////////
var defaultBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 1 * common.MebiByte, // 1MB
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: 200 * common.MebiByte, // 200 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte, // 500 MB
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte, // 2GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
}

View file

@ -1,126 +0,0 @@
package rhel7
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
var qcow2ImgType = imageType{
name: "qcow2",
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
kernelOptions: "console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0 crashkernel=auto",
packageSets: map[string]packageSetFunc{
osPkgsKey: qcow2CommonPackageSet,
},
defaultImageConfig: qcow2DefaultImgConfig,
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "qcow2"},
exports: []string{"qcow2"},
basePartitionTables: defaultBasePartitionTables,
}
var qcow2DefaultImgConfig = &distro.ImageConfig{
DefaultTarget: common.ToPtr("multi-user.target"),
SELinuxForceRelabel: common.ToPtr(true),
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
NetworkScripts: &osbuild.NetworkScriptsOptions{
IfcfgFiles: map[string]osbuild.IfcfgFile{
"eth0": {
Device: "eth0",
Bootproto: osbuild.IfcfgBootprotoDHCP,
OnBoot: common.ToPtr(true),
Type: osbuild.IfcfgTypeEthernet,
UserCtl: common.ToPtr(true),
PeerDNS: common.ToPtr(true),
IPv6Init: common.ToPtr(false),
},
},
},
},
},
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
YumPlugins: &osbuild.RHSMStageOptionsDnfPlugins{
ProductID: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
},
},
},
}
func qcow2CommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@core",
"kernel",
"nfs-utils",
"yum-utils",
"cloud-init",
//"ovirt-guest-agent-common",
"rhn-setup",
"yum-rhn-plugin",
"cloud-utils-growpart",
"dracut-config-generic",
"tar",
"tcpdump",
"rsync",
},
Exclude: []string{
"biosdevname",
"dracut-config-rescue",
"iprutils",
"NetworkManager-team",
"NetworkManager-tui",
"NetworkManager",
"plymouth",
"aic94xx-firmware",
"alsa-firmware",
"alsa-lib",
"alsa-tools-firmware",
"ivtv-firmware",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"libertas-sd8686-firmware",
"libertas-sd8787-firmware",
"libertas-usb8388-firmware",
},
}.Append(distroSpecificPackageSet(t))
return ps
}

View file

@ -1,511 +0,0 @@
package rhel8
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
func amiImgTypeX86_64(rd distribution) imageType {
it := imageType{
name: "ami",
filename: "image.raw",
mimeType: "application/octet-stream",
packageSets: map[string]packageSetFunc{
osPkgsKey: ec2CommonPackageSet,
},
defaultImageConfig: defaultAMIImageConfigX86_64(rd),
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image"},
exports: []string{"image"},
basePartitionTables: ec2BasePartitionTables,
}
return it
}
func ec2ImgTypeX86_64(rd distribution) imageType {
basePartitionTables := ec2BasePartitionTables
// use legacy partition tables for RHEL 8.8 and older
if common.VersionLessThan(rd.osVersion, "8.9") {
basePartitionTables = ec2LegacyBasePartitionTables
}
it := imageType{
name: "ec2",
filename: "image.raw.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: rhelEc2PackageSet,
},
defaultImageConfig: defaultEc2ImageConfigX86_64(rd),
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: basePartitionTables,
}
return it
}
func ec2HaImgTypeX86_64(rd distribution) imageType {
basePartitionTables := ec2BasePartitionTables
// use legacy partition tables for RHEL 8.8 and older
if common.VersionLessThan(rd.osVersion, "8.9") {
basePartitionTables = ec2LegacyBasePartitionTables
}
it := imageType{
name: "ec2-ha",
filename: "image.raw.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: rhelEc2HaPackageSet,
},
defaultImageConfig: defaultEc2ImageConfigX86_64(rd),
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: basePartitionTables,
}
return it
}
func amiImgTypeAarch64(rd distribution) imageType {
it := imageType{
name: "ami",
filename: "image.raw",
mimeType: "application/octet-stream",
packageSets: map[string]packageSetFunc{
osPkgsKey: ec2CommonPackageSet,
},
defaultImageConfig: defaultAMIImageConfig(rd),
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0 crashkernel=auto",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image"},
exports: []string{"image"},
basePartitionTables: ec2BasePartitionTables,
}
return it
}
func ec2ImgTypeAarch64(rd distribution) imageType {
basePartitionTables := ec2BasePartitionTables
// use legacy partition tables for RHEL 8.8 and older
if common.VersionLessThan(rd.osVersion, "8.9") {
basePartitionTables = ec2LegacyBasePartitionTables
}
it := imageType{
name: "ec2",
filename: "image.raw.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: rhelEc2PackageSet,
},
defaultImageConfig: defaultEc2ImageConfig(rd),
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0 crashkernel=auto",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: basePartitionTables,
}
return it
}
func ec2SapImgTypeX86_64(rd distribution) imageType {
basePartitionTables := ec2BasePartitionTables
// use legacy partition tables for RHEL 8.8 and older
if common.VersionLessThan(rd.osVersion, "8.9") {
basePartitionTables = ec2LegacyBasePartitionTables
}
it := imageType{
name: "ec2-sap",
filename: "image.raw.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: rhelEc2SapPackageSet,
},
defaultImageConfig: defaultEc2SapImageConfigX86_64(rd),
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 crashkernel=auto processor.max_cstate=1 intel_idle.max_cstate=1",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: basePartitionTables,
}
return it
}
// default EC2 images config (common for all architectures)
func baseEc2ImageConfig() *distro.ImageConfig {
return &distro.ImageConfig{
Timezone: common.ToPtr("UTC"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{
{
Hostname: "169.254.169.123",
Prefer: common.ToPtr(true),
Iburst: common.ToPtr(true),
Minpoll: common.ToPtr(4),
Maxpoll: common.ToPtr(4),
},
},
// empty string will remove any occurrences of the option from the configuration
LeapsecTz: common.ToPtr(""),
},
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
X11Keymap: &osbuild.X11KeymapOptions{
Layouts: []string{"us"},
},
},
EnabledServices: []string{
"sshd",
"NetworkManager",
"nm-cloud-setup.service",
"nm-cloud-setup.timer",
"cloud-init",
"cloud-init-local",
"cloud-config",
"cloud-final",
"reboot.target",
},
DefaultTarget: common.ToPtr("multi-user.target"),
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
NetworkScripts: &osbuild.NetworkScriptsOptions{
IfcfgFiles: map[string]osbuild.IfcfgFile{
"eth0": {
Device: "eth0",
Bootproto: osbuild.IfcfgBootprotoDHCP,
OnBoot: common.ToPtr(true),
Type: osbuild.IfcfgTypeEthernet,
UserCtl: common.ToPtr(true),
PeerDNS: common.ToPtr(true),
IPv6Init: common.ToPtr(false),
},
},
},
},
},
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
// RHBZ#1932802
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
Rhsm: &osbuild.SubManConfigRHSMSection{
ManageRepos: common.ToPtr(false),
},
},
},
subscription.RHSMConfigWithSubscription: {
// RHBZ#1932802
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
SystemdLogind: []*osbuild.SystemdLogindStageOptions{
{
Filename: "00-getty-fixes.conf",
Config: osbuild.SystemdLogindConfigDropin{
Login: osbuild.SystemdLogindConfigLoginSection{
NAutoVTs: common.ToPtr(0),
},
},
},
},
CloudInit: []*osbuild.CloudInitStageOptions{
{
Filename: "00-rhel-default-user.cfg",
Config: osbuild.CloudInitConfigFile{
SystemInfo: &osbuild.CloudInitConfigSystemInfo{
DefaultUser: &osbuild.CloudInitConfigDefaultUser{
Name: "ec2-user",
},
},
},
},
},
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-nouveau.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("nouveau"),
},
},
// COMPOSER-1807
{
Filename: "blacklist-amdgpu.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("amdgpu"),
},
},
},
DracutConf: []*osbuild.DracutConfStageOptions{
{
Filename: "sgdisk.conf",
Config: osbuild.DracutConfigFile{
Install: []string{"sgdisk"},
},
},
},
SystemdUnit: []*osbuild.SystemdUnitStageOptions{
// RHBZ#1822863
{
Unit: "nm-cloud-setup.service",
Dropin: "10-rh-enable-for-ec2.conf",
Config: osbuild.SystemdServiceUnitDropin{
Service: &osbuild.SystemdUnitServiceSection{
Environment: "NM_CLOUD_SETUP_EC2=yes",
},
},
},
},
Authselect: &osbuild.AuthselectStageOptions{
Profile: "sssd",
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
PasswordAuthentication: common.ToPtr(false),
},
},
}
}
func defaultEc2ImageConfig(rd distribution) *distro.ImageConfig {
ic := baseEc2ImageConfig()
if rd.isRHEL() && common.VersionLessThan(rd.osVersion, "9.1") {
ic = appendRHSM(ic)
// Disable RHSM redhat.repo management
rhsmConf := ic.RHSMConfig[subscription.RHSMConfigNoSubscription]
rhsmConf.SubMan.Rhsm = &osbuild.SubManConfigRHSMSection{ManageRepos: common.ToPtr(false)}
ic.RHSMConfig[subscription.RHSMConfigNoSubscription] = rhsmConf
}
// The RHSM configuration should not be applied since 8.7, but it is instead done by installing the redhat-cloud-client-configuration package.
// See COMPOSER-1804 for more information.
rhel87PlusEc2ImageConfigOverride := &distro.ImageConfig{
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{},
}
if !common.VersionLessThan(rd.osVersion, "8.7") {
ic = rhel87PlusEc2ImageConfigOverride.InheritFrom(ic)
}
return ic
}
// default AMI (EC2 BYOS) images config
func defaultAMIImageConfig(rd distribution) *distro.ImageConfig {
ic := defaultEc2ImageConfig(rd)
if rd.isRHEL() {
// defaultAMIImageConfig() adds the rhsm options only for RHEL < 9.1
// Add it unconditionally for AMI
ic = appendRHSM(ic)
}
return ic
}
func defaultEc2ImageConfigX86_64(rd distribution) *distro.ImageConfig {
ic := defaultEc2ImageConfig(rd)
return appendEC2DracutX86_64(ic)
}
func defaultAMIImageConfigX86_64(rd distribution) *distro.ImageConfig {
ic := defaultAMIImageConfig(rd).InheritFrom(defaultEc2ImageConfigX86_64(rd))
return appendEC2DracutX86_64(ic)
}
func defaultEc2SapImageConfigX86_64(rd distribution) *distro.ImageConfig {
// default EC2-SAP image config (x86_64)
return sapImageConfig(rd).InheritFrom(defaultEc2ImageConfigX86_64(rd))
}
// common package set for RHEL (BYOS/RHUI) and CentOS Stream images
func ec2CommonPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"@core",
"authselect-compat",
"chrony",
"cloud-init",
"cloud-utils-growpart",
"dhcp-client",
"dracut-config-generic",
"dracut-norescue",
"gdisk",
"grub2",
"langpacks-en",
"NetworkManager",
"NetworkManager-cloud-setup",
"redhat-release",
"redhat-release-eula",
"rsync",
"tar",
"yum-utils",
},
Exclude: []string{
"aic94xx-firmware",
"alsa-firmware",
"alsa-tools-firmware",
"biosdevname",
"firewalld",
"iprutils",
"ivtv-firmware",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"libertas-sd8686-firmware",
"libertas-sd8787-firmware",
"libertas-usb8388-firmware",
"plymouth",
// RHBZ#2075815
"qemu-guest-agent",
},
}.Append(distroSpecificPackageSet(t))
}
// common rhel ec2 RHUI image package set
func rhelEc2CommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := ec2CommonPackageSet(t)
// Include "redhat-cloud-client-configuration" on 8.7+ (COMPOSER-1804)
if !common.VersionLessThan(t.arch.distro.osVersion, "8.7") {
ps.Include = append(ps.Include, "redhat-cloud-client-configuration")
}
return ps
}
// rhel-ec2 image package set
func rhelEc2PackageSet(t *imageType) rpmmd.PackageSet {
ec2PackageSet := rhelEc2CommonPackageSet(t)
ec2PackageSet.Include = append(ec2PackageSet.Include, "rh-amazon-rhui-client")
ec2PackageSet.Exclude = append(ec2PackageSet.Exclude, "alsa-lib")
return ec2PackageSet
}
// rhel-ha-ec2 image package set
func rhelEc2HaPackageSet(t *imageType) rpmmd.PackageSet {
ec2HaPackageSet := rhelEc2CommonPackageSet(t)
ec2HaPackageSet.Include = append(ec2HaPackageSet.Include,
"fence-agents-all",
"pacemaker",
"pcs",
"rh-amazon-rhui-client-ha",
)
ec2HaPackageSet.Exclude = append(ec2HaPackageSet.Exclude, "alsa-lib")
return ec2HaPackageSet
}
// rhel-sap-ec2 image package set
// Includes the common ec2 package set, the common SAP packages, and
// the amazon rhui sap package
func rhelEc2SapPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"rh-amazon-rhui-client-sap-bundle-e4s",
},
}.Append(rhelEc2CommonPackageSet(t)).Append(SapPackageSet(t))
}
// Add RHSM config options to ImageConfig.
// Used for RHEL distros.
func appendRHSM(ic *distro.ImageConfig) *distro.ImageConfig {
rhsm := &distro.ImageConfig{
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
// RHBZ#1932802
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// Don't disable RHSM redhat.repo management on the AMI
// image, which is BYOS and does not use RHUI for content.
// Otherwise subscribing the system manually after booting
// it would result in empty redhat.repo. Without RHUI, such
// system would have no way to get Red Hat content, but
// enable the repo management manually, which would be very
// confusing.
},
},
subscription.RHSMConfigWithSubscription: {
// RHBZ#1932802
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
}
return rhsm.InheritFrom(ic)
}
func appendEC2DracutX86_64(ic *distro.ImageConfig) *distro.ImageConfig {
ic.DracutConf = append(ic.DracutConf,
&osbuild.DracutConfStageOptions{
Filename: "ec2.conf",
Config: osbuild.DracutConfigFile{
AddDrivers: []string{
"nvme",
"xen-blkfront",
},
},
})
return ic
}

View file

@ -1,73 +0,0 @@
package rhel8
import (
"errors"
"fmt"
"sort"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/platform"
)
type architecture struct {
distro *distribution
name string
imageTypes map[string]distro.ImageType
imageTypeAliases map[string]string
}
func (a *architecture) Name() string {
return a.name
}
func (a *architecture) ListImageTypes() []string {
itNames := make([]string, 0, len(a.imageTypes))
for name := range a.imageTypes {
itNames = append(itNames, name)
}
sort.Strings(itNames)
return itNames
}
func (a *architecture) GetImageType(name string) (distro.ImageType, error) {
t, exists := a.imageTypes[name]
if !exists {
aliasForName, exists := a.imageTypeAliases[name]
if !exists {
return nil, errors.New("invalid image type: " + name)
}
t, exists = a.imageTypes[aliasForName]
if !exists {
panic(fmt.Sprintf("image type '%s' is an alias to a non-existing image type '%s'", name, aliasForName))
}
}
return t, nil
}
func (a *architecture) addImageTypes(platform platform.Platform, imageTypes ...imageType) {
if a.imageTypes == nil {
a.imageTypes = map[string]distro.ImageType{}
}
for idx := range imageTypes {
it := imageTypes[idx]
if _, e := a.imageTypes[it.name]; e {
panic("already added: " + it.name)
}
it.arch = a
it.platform = platform
a.imageTypes[it.name] = &it
for _, alias := range it.nameAliases {
if a.imageTypeAliases == nil {
a.imageTypeAliases = map[string]string{}
}
if existingAliasFor, exists := a.imageTypeAliases[alias]; exists {
panic(fmt.Sprintf("image type alias '%s' for '%s' is already defined for another image type '%s'", alias, it.name, existingAliasFor))
}
a.imageTypeAliases[alias] = it.name
}
}
}
func (a *architecture) Distro() distro.Distro {
return a.distro
}

View file

@ -1,722 +0,0 @@
package rhel8
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/shell"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
const defaultAzureKernelOptions = "ro crashkernel=auto console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300"
func azureRhuiImgType() imageType {
return imageType{
name: "azure-rhui",
filename: "disk.vhd.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: azureRhuiPackageSet,
},
defaultImageConfig: defaultAzureRhuiImageConfig.InheritFrom(defaultVhdImageConfig()),
kernelOptions: defaultAzureKernelOptions,
bootable: true,
defaultSize: 64 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc", "xz"},
exports: []string{"xz"},
basePartitionTables: azureRhuiBasePartitionTables,
}
}
func azureSapRhuiImgType(rd distribution) imageType {
return imageType{
name: "azure-sap-rhui",
filename: "disk.vhd.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: azureSapPackageSet,
},
defaultImageConfig: defaultAzureRhuiImageConfig.InheritFrom(sapAzureImageConfig(rd)),
kernelOptions: defaultAzureKernelOptions,
bootable: true,
defaultSize: 64 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc", "xz"},
exports: []string{"xz"},
basePartitionTables: azureRhuiBasePartitionTables,
}
}
func azureByosImgType() imageType {
return imageType{
name: "vhd",
filename: "disk.vhd",
mimeType: "application/x-vhd",
packageSets: map[string]packageSetFunc{
osPkgsKey: azurePackageSet,
},
defaultImageConfig: defaultAzureByosImageConfig.InheritFrom(defaultVhdImageConfig()),
kernelOptions: defaultAzureKernelOptions,
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc"},
exports: []string{"vpc"},
basePartitionTables: defaultBasePartitionTables,
}
}
// Azure non-RHEL image type
func azureImgType() imageType {
return imageType{
name: "vhd",
filename: "disk.vhd",
mimeType: "application/x-vhd",
packageSets: map[string]packageSetFunc{
osPkgsKey: azurePackageSet,
},
defaultImageConfig: defaultVhdImageConfig(),
kernelOptions: defaultAzureKernelOptions,
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc"},
exports: []string{"vpc"},
basePartitionTables: defaultBasePartitionTables,
}
}
func azureEap7RhuiImgType() imageType {
return imageType{
name: "azure-eap7-rhui",
workload: eapWorkload(),
filename: "disk.vhd.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: azureEapPackageSet,
},
defaultImageConfig: defaultAzureEapImageConfig.InheritFrom(defaultAzureRhuiImageConfig.InheritFrom(defaultAzureImageConfig)),
kernelOptions: defaultAzureKernelOptions,
bootable: true,
defaultSize: 64 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc", "xz"},
exports: []string{"xz"},
basePartitionTables: azureRhuiBasePartitionTables,
}
}
// PACKAGE SETS
// Common Azure image package set
func azureCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@Server",
"NetworkManager",
"NetworkManager-cloud-setup",
"WALinuxAgent",
"bzip2",
"cloud-init",
"cloud-utils-growpart",
"cryptsetup-reencrypt",
"dracut-config-generic",
"dracut-norescue",
"efibootmgr",
"gdisk",
"hyperv-daemons",
"kernel",
"kernel-core",
"kernel-modules",
"langpacks-en",
"lvm2",
"nvme-cli",
"patch",
"rng-tools",
"selinux-policy-targeted",
"uuid",
"yum-utils",
},
Exclude: []string{
"NetworkManager-config-server",
"aic94xx-firmware",
"alsa-firmware",
"alsa-sof-firmware",
"alsa-tools-firmware",
"biosdevname",
"bolt",
"buildah",
"cockpit-podman",
"containernetworking-plugins",
"dnf-plugin-spacewalk",
"dracut-config-rescue",
"glibc-all-langpacks",
"iprutils",
"ivtv-firmware",
"iwl100-firmware",
"iwl1000-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"libertas-sd8686-firmware",
"libertas-sd8787-firmware",
"libertas-usb8388-firmware",
"plymouth",
"podman",
"python3-dnf-plugin-spacewalk",
"python3-hwdata",
"python3-rhnlib",
"rhn-check",
"rhn-client-tools",
"rhn-setup",
"rhnlib",
"rhnsd",
"usb_modeswitch",
},
}.Append(distroSpecificPackageSet(t))
if t.arch.distro.isRHEL() {
ps.Append(rpmmd.PackageSet{
Include: []string{
"insights-client",
"rhc",
},
})
}
return ps
}
// Azure BYOS image package set
func azurePackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"firewalld",
},
Exclude: []string{
"alsa-lib",
},
}.Append(azureCommonPackageSet(t))
}
// Azure RHUI image package set
func azureRhuiPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"firewalld",
"rhui-azure-rhel8",
},
Exclude: []string{
"alsa-lib",
},
}.Append(azureCommonPackageSet(t))
}
// Azure SAP image package set
// Includes the common azure package set, the common SAP packages, and
// the azure rhui sap package.
func azureSapPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"firewalld",
"rhui-azure-rhel8-sap-ha",
},
}.Append(azureCommonPackageSet(t)).Append(SapPackageSet(t))
}
func azureEapPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"rhui-azure-rhel8",
},
Exclude: []string{
"firewalld",
},
}.Append(azureCommonPackageSet(t))
}
// PARTITION TABLES
var azureRhuiBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Size: 64 * common.GibiByte,
Partitions: []disk.Partition{
{
Size: 500 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Type: disk.LVMPartitionGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 1 * common.GibiByte,
Name: "homelv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "home",
Mountpoint: "/home",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "tmplv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "tmp",
Mountpoint: "/tmp",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "usrlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "usr",
Mountpoint: "/usr",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "varlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "var",
Mountpoint: "/var",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Size: 64 * common.GibiByte,
Partitions: []disk.Partition{
{
Size: 500 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Type: disk.LVMPartitionGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 1 * common.GibiByte,
Name: "homelv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "home",
Mountpoint: "/home",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "tmplv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "tmp",
Mountpoint: "/tmp",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "usrlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "usr",
Mountpoint: "/usr",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "varlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "var",
Mountpoint: "/var",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
},
}
var defaultAzureImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("Etc/UTC"),
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
X11Keymap: &osbuild.X11KeymapOptions{
Layouts: []string{"us"},
},
},
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel-core",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
},
},
EnabledServices: []string{
"nm-cloud-setup.service",
"nm-cloud-setup.timer",
"sshd",
"systemd-resolved",
"waagent",
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
ClientAliveInterval: common.ToPtr(180),
},
},
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-amdgpu.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("amdgpu"),
},
},
{
Filename: "blacklist-intel-cstate.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("intel_cstate"),
},
},
{
Filename: "blacklist-floppy.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("floppy"),
},
},
{
Filename: "blacklist-nouveau.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("nouveau"),
osbuild.NewModprobeConfigCmdBlacklist("lbm-nouveau"),
},
},
{
Filename: "blacklist-skylake-edac.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("skx_edac"),
},
},
},
CloudInit: []*osbuild.CloudInitStageOptions{
{
Filename: "10-azure-kvp.cfg",
Config: osbuild.CloudInitConfigFile{
Reporting: &osbuild.CloudInitConfigReporting{
Logging: &osbuild.CloudInitConfigReportingHandlers{
Type: "log",
},
Telemetry: &osbuild.CloudInitConfigReportingHandlers{
Type: "hyperv",
},
},
},
},
{
Filename: "91-azure_datasource.cfg",
Config: osbuild.CloudInitConfigFile{
Datasource: &osbuild.CloudInitConfigDatasource{
Azure: &osbuild.CloudInitConfigDatasourceAzure{
ApplyNetworkConfig: false,
},
},
DatasourceList: []string{
"Azure",
},
},
},
},
PwQuality: &osbuild.PwqualityConfStageOptions{
Config: osbuild.PwqualityConfConfig{
Minlen: common.ToPtr(6),
Minclass: common.ToPtr(3),
Dcredit: common.ToPtr(0),
Ucredit: common.ToPtr(0),
Lcredit: common.ToPtr(0),
Ocredit: common.ToPtr(0),
},
},
WAAgentConfig: &osbuild.WAAgentConfStageOptions{
Config: osbuild.WAAgentConfig{
RDFormat: common.ToPtr(false),
RDEnableSwap: common.ToPtr(false),
},
},
Grub2Config: &osbuild.GRUB2Config{
TerminalInput: []string{"serial", "console"},
TerminalOutput: []string{"serial", "console"},
Serial: "serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1",
Timeout: 10,
},
UdevRules: &osbuild.UdevRulesStageOptions{
Filename: "/etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules",
Rules: osbuild.UdevRules{
osbuild.UdevRuleComment{
Comment: []string{
"Accelerated Networking on Azure exposes a new SRIOV interface to the VM.",
"This interface is transparently bonded to the synthetic interface,",
"so NetworkManager should just ignore any SRIOV interfaces.",
},
},
osbuild.NewUdevRule(
[]osbuild.UdevKV{
{K: "SUBSYSTEM", O: "==", V: "net"},
{K: "DRIVERS", O: "==", V: "hv_pci"},
{K: "ACTION", O: "==", V: "add"},
{K: "ENV", A: "NM_UNMANAGED", O: "=", V: "1"},
},
),
},
},
SystemdUnit: []*osbuild.SystemdUnitStageOptions{
{
Unit: "nm-cloud-setup.service",
Dropin: "10-rh-enable-for-azure.conf",
Config: osbuild.SystemdServiceUnitDropin{
Service: &osbuild.SystemdUnitServiceSection{
Environment: "NM_CLOUD_SETUP_AZURE=yes",
},
},
},
},
DefaultTarget: common.ToPtr("multi-user.target"),
}
// Diff of the default Image Config compare to the `defaultAzureImageConfig`
var defaultAzureByosImageConfig = &distro.ImageConfig{
GPGKeyFiles: []string{
"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
},
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// Don't disable RHSM redhat.repo management on the GCE
// image, which is BYOS and does not use RHUI for content.
// Otherwise subscribing the system manually after booting
// it would result in empty redhat.repo. Without RHUI, such
// system would have no way to get Red Hat content, but
// enable the repo management manually, which would be very
// confusing.
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
}
// Diff of the default Image Config compare to the `defaultAzureImageConfig`
var defaultAzureRhuiImageConfig = &distro.ImageConfig{
GPGKeyFiles: []string{
"/etc/pki/rpm-gpg/RPM-GPG-KEY-microsoft-azure-release",
"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
},
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{
SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
},
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
Rhsm: &osbuild.SubManConfigRHSMSection{
ManageRepos: common.ToPtr(false),
},
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
}
const wildflyPath = "/opt/rh/eap7/root/usr/share/wildfly"
var defaultAzureEapImageConfig = &distro.ImageConfig{
// shell env vars for EAP
ShellInit: []shell.InitFile{
{
Filename: "eap_env.sh",
Variables: []shell.EnvironmentVariable{
{
Key: "EAP_HOME",
Value: wildflyPath,
},
{
Key: "JBOSS_HOME",
Value: wildflyPath,
},
},
},
},
}
func defaultVhdImageConfig() *distro.ImageConfig {
imageConfig := &distro.ImageConfig{
EnabledServices: append(defaultAzureImageConfig.EnabledServices, "firewalld"),
}
return imageConfig.InheritFrom(defaultAzureImageConfig)
}
func sapAzureImageConfig(rd distribution) *distro.ImageConfig {
return sapImageConfig(rd).InheritFrom(defaultVhdImageConfig())
}

View file

@ -1,302 +0,0 @@
package rhel8
import (
"fmt"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
func imageInstaller() imageType {
return imageType{
name: "image-installer",
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
osPkgsKey: bareMetalPackageSet,
installerPkgsKey: anacondaPackageSet,
},
rpmOstree: false,
bootISO: true,
bootable: true,
image: imageInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"anaconda-tree", "rootfs-image", "efiboot-tree", "os", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
}
}
func tarImgType() imageType {
return imageType{
name: "tar",
filename: "root.tar.xz",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: func(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{"policycoreutils", "selinux-policy-targeted"},
Exclude: []string{"rng-tools"},
}
},
},
image: tarImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "archive"},
exports: []string{"archive"},
}
}
func bareMetalPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@core",
"authselect-compat",
"chrony",
"cockpit-system",
"cockpit-ws",
"dhcp-client",
"dnf",
"dnf-utils",
"dosfstools",
"dracut-norescue",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"lvm2",
"net-tools",
"NetworkManager",
"nfs-utils",
"oddjob",
"oddjob-mkhomedir",
"policycoreutils",
"psmisc",
"python3-jsonschema",
"qemu-guest-agent",
"redhat-release",
"redhat-release-eula",
"rsync",
"selinux-policy-targeted",
"tar",
"tcpdump",
"yum",
},
Exclude: nil,
}.Append(distroSpecificPackageSet(t))
// Ensure to not pull in subscription-manager on non-RHEL distro
if t.arch.distro.isRHEL() {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"subscription-manager-cockpit",
},
})
}
return ps
}
func installerPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"anaconda-dracut",
"curl",
"dracut-config-generic",
"dracut-network",
"hostname",
"iwl100-firmware",
"iwl1000-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"kernel",
"less",
"nfs-utils",
"openssh-clients",
"ostree",
"plymouth",
"prefixdevname",
"rng-tools",
"rpcbind",
"selinux-policy-targeted",
"systemd",
"tar",
"xfsprogs",
"xz",
},
}
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"biosdevname",
},
})
}
return ps
}
func anacondaPackageSet(t *imageType) rpmmd.PackageSet {
// common installer packages
ps := installerPackageSet(t)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"aajohan-comfortaa-fonts",
"abattis-cantarell-fonts",
"alsa-firmware",
"alsa-tools-firmware",
"anaconda",
"anaconda-install-env-deps",
"anaconda-widgets",
"audit",
"bind-utils",
"bitmap-fangsongti-fonts",
"bzip2",
"cryptsetup",
"dbus-x11",
"dejavu-sans-fonts",
"dejavu-sans-mono-fonts",
"device-mapper-persistent-data",
"dnf",
"dump",
"ethtool",
"fcoe-utils",
"ftp",
"gdb-gdbserver",
"gdisk",
"gfs2-utils",
"glibc-all-langpacks",
"google-noto-sans-cjk-ttc-fonts",
"gsettings-desktop-schemas",
"hdparm",
"hexedit",
"initscripts",
"ipmitool",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"jomolhari-fonts",
"kacst-farsi-fonts",
"kacst-qurn-fonts",
"kbd",
"kbd-misc",
"kdump-anaconda-addon",
"khmeros-base-fonts",
"libblockdev-lvm-dbus",
"libertas-sd8686-firmware",
"libertas-sd8787-firmware",
"libertas-usb8388-firmware",
"libertas-usb8388-olpc-firmware",
"libibverbs",
"libreport-plugin-bugzilla",
"libreport-plugin-reportuploader",
"libreport-rhel-anaconda-bugzilla",
"librsvg2",
"linux-firmware",
"lklug-fonts",
"lldpad",
"lohit-assamese-fonts",
"lohit-bengali-fonts",
"lohit-devanagari-fonts",
"lohit-gujarati-fonts",
"lohit-gurmukhi-fonts",
"lohit-kannada-fonts",
"lohit-odia-fonts",
"lohit-tamil-fonts",
"lohit-telugu-fonts",
"lsof",
"madan-fonts",
"metacity",
"mtr",
"mt-st",
"net-tools",
"nmap-ncat",
"nm-connection-editor",
"nss-tools",
"openssh-server",
"oscap-anaconda-addon",
"pciutils",
"perl-interpreter",
"pigz",
"python3-pyatspi",
"rdma-core",
"redhat-release-eula",
"rpm-ostree",
"rsync",
"rsyslog",
"sg3_utils",
"sil-abyssinica-fonts",
"sil-padauk-fonts",
"sil-scheherazade-fonts",
"smartmontools",
"smc-meera-fonts",
"spice-vdagent",
"strace",
"system-storage-manager",
"thai-scalable-waree-fonts",
"tigervnc-server-minimal",
"tigervnc-server-module",
"udisks2",
"udisks2-iscsi",
"usbutils",
"vim-minimal",
"volume_key",
"wget",
"xfsdump",
"xorg-x11-drivers",
"xorg-x11-fonts-misc",
"xorg-x11-server-utils",
"xorg-x11-server-Xorg",
"xorg-x11-xauth",
},
})
ps = ps.Append(anacondaBootPackageSet(t))
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"biosdevname",
"dmidecode",
"memtest86+",
},
})
case platform.ARCH_AARCH64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"dmidecode",
},
})
default:
panic(fmt.Sprintf("unsupported arch: %s", t.arch.Name()))
}
return ps
}

View file

@ -1,506 +0,0 @@
package rhel8
import (
"errors"
"fmt"
"sort"
"strings"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/runner"
)
var (
// rhel8 allow all
oscapProfileAllowList = []oscap.Profile{
oscap.AnssiBp28Enhanced,
oscap.AnssiBp28High,
oscap.AnssiBp28Intermediary,
oscap.AnssiBp28Minimal,
oscap.Cis,
oscap.CisServerL1,
oscap.CisWorkstationL1,
oscap.CisWorkstationL2,
oscap.Cui,
oscap.E8,
oscap.Hippa,
oscap.IsmO,
oscap.Ospp,
oscap.PciDss,
oscap.Stig,
oscap.StigGui,
}
)
type distribution struct {
name string
product string
osVersion string
releaseVersion string
modulePlatformID string
vendor string
ostreeRefTmpl string
isolabelTmpl string
runner runner.Runner
arches map[string]distro.Arch
defaultImageConfig *distro.ImageConfig
}
// RHEL-based OS image configuration defaults
var defaultDistroImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("America/New_York"),
Locale: common.ToPtr("en_US.UTF-8"),
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
},
},
}
func (d *distribution) Name() string {
return d.name
}
func (d *distribution) Releasever() string {
return d.releaseVersion
}
func (d *distribution) ModulePlatformID() string {
return d.modulePlatformID
}
func (d *distribution) OSTreeRef() string {
return d.ostreeRefTmpl
}
func (d *distribution) ListArches() []string {
archNames := make([]string, 0, len(d.arches))
for name := range d.arches {
archNames = append(archNames, name)
}
sort.Strings(archNames)
return archNames
}
func (d *distribution) GetArch(name string) (distro.Arch, error) {
arch, exists := d.arches[name]
if !exists {
return nil, errors.New("invalid architecture: " + name)
}
return arch, nil
}
func (d *distribution) addArches(arches ...architecture) {
if d.arches == nil {
d.arches = map[string]distro.Arch{}
}
// Do not make copies of architectures, as opposed to image types,
// because architecture definitions are not used by more than a single
// distro definition.
for idx := range arches {
d.arches[arches[idx].name] = &arches[idx]
}
}
func (d *distribution) isRHEL() bool {
return strings.HasPrefix(d.name, "rhel")
}
func (d *distribution) getDefaultImageConfig() *distro.ImageConfig {
return d.defaultImageConfig
}
// 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", 7)
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 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),
product: "Red Hat Enterprise Linux",
osVersion: fmt.Sprintf("8.%d", minor),
releaseVersion: "8",
modulePlatformID: "platform:el8",
vendor: "redhat",
ostreeRefTmpl: "rhel/8/%s/edge",
isolabelTmpl: fmt.Sprintf("RHEL-8-%d-0-BaseOS-%%s", minor),
runner: &runner.RHEL{Major: uint64(8), Minor: uint64(minor)},
defaultImageConfig: defaultDistroImageConfig,
}
case "centos":
rd = distribution{
name: "centos-8",
product: "CentOS Stream",
osVersion: "8-stream",
releaseVersion: "8",
modulePlatformID: "platform:el8",
vendor: "centos",
ostreeRefTmpl: "centos/8/%s/edge",
isolabelTmpl: "CentOS-Stream-8-%s-dvd",
runner: &runner.CentOS{Version: uint64(8)},
defaultImageConfig: defaultDistroImageConfig,
}
default:
panic(fmt.Sprintf("unknown distro name: %s", name))
}
// Architecture definitions
x86_64 := architecture{
name: platform.ARCH_X86_64.String(),
distro: &rd,
}
aarch64 := architecture{
name: platform.ARCH_AARCH64.String(),
distro: &rd,
}
ppc64le := architecture{
distro: &rd,
name: platform.ARCH_PPC64LE.String(),
}
s390x := architecture{
distro: &rd,
name: platform.ARCH_S390X.String(),
}
ociImgType := qcow2ImgType(rd)
ociImgType.name = "oci"
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "0.10",
},
},
qcow2ImgType(rd),
ociImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
},
},
openstackImgType(),
)
ec2X86Platform := &platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
}
x86_64.addImageTypes(
ec2X86Platform,
amiImgTypeX86_64(rd),
)
bareMetalX86Platform := &platform.X86{
BasePlatform: platform.BasePlatform{
FirmwarePackages: []string{
"microcode_ctl", // ??
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6050-firmware",
},
},
BIOS: true,
UEFIVendor: rd.vendor,
}
x86_64.addImageTypes(
bareMetalX86Platform,
edgeOCIImgType(rd),
edgeCommitImgType(rd),
edgeInstallerImgType(rd),
imageInstaller(),
)
gceX86Platform := &platform.X86{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_GCE,
},
}
x86_64.addImageTypes(
gceX86Platform,
gceImgType(rd),
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VMDK,
},
},
vmdkImgType(),
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_OVA,
},
},
ovaImgType(),
)
x86_64.addImageTypes(
&platform.X86{},
tarImgType(),
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "0.10",
},
},
qcow2ImgType(rd),
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
},
},
openstackImgType(),
)
aarch64.addImageTypes(
&platform.Aarch64{},
tarImgType(),
)
bareMetalAarch64Platform := &platform.Aarch64{
BasePlatform: platform.BasePlatform{},
UEFIVendor: rd.vendor,
}
aarch64.addImageTypes(
bareMetalAarch64Platform,
edgeOCIImgType(rd),
edgeCommitImgType(rd),
edgeInstallerImgType(rd),
imageInstaller(),
)
rawAarch64Platform := &platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
}
aarch64.addImageTypes(
rawAarch64Platform,
amiImgTypeAarch64(rd),
)
ppc64le.addImageTypes(
&platform.PPC64LE{
BIOS: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "0.10",
},
},
qcow2ImgType(rd),
)
ppc64le.addImageTypes(
&platform.PPC64LE{},
tarImgType(),
)
s390x.addImageTypes(
&platform.S390X{
Zipl: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "0.10",
},
},
qcow2ImgType(rd),
)
s390x.addImageTypes(
&platform.S390X{},
tarImgType(),
)
azureX64Platform := &platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VHD,
},
}
azureAarch64Platform := &platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VHD,
},
}
rawUEFIx86Platform := &platform.X86{
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
BIOS: false,
UEFIVendor: rd.vendor,
}
if rd.isRHEL() {
if !common.VersionLessThan(rd.osVersion, "8.6") {
// image types only available on 8.6 and later on RHEL
// These edge image types require FDO which aren't available on older versions
x86_64.addImageTypes(
bareMetalX86Platform,
edgeRawImgType(),
)
x86_64.addImageTypes(
rawUEFIx86Platform,
edgeSimplifiedInstallerImgType(rd),
)
azureEap := azureEap7RhuiImgType()
x86_64.addImageTypes(azureX64Platform, azureEap)
aarch64.addImageTypes(
rawAarch64Platform,
edgeRawImgType(),
edgeSimplifiedInstallerImgType(rd),
)
// The Azure image types require hyperv-daemons which isn't available on older versions
aarch64.addImageTypes(azureAarch64Platform, azureRhuiImgType(), azureByosImgType())
}
// add azure to RHEL distro only
x86_64.addImageTypes(azureX64Platform, azureRhuiImgType(), azureByosImgType(), azureSapRhuiImgType(rd))
// keep the RHEL EC2 x86_64 images before 8.9 BIOS-only for backward compatibility
if common.VersionLessThan(rd.osVersion, "8.9") {
ec2X86Platform = &platform.X86{
BIOS: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
}
}
// add ec2 image types to RHEL distro only
x86_64.addImageTypes(ec2X86Platform, ec2ImgTypeX86_64(rd), ec2HaImgTypeX86_64(rd))
aarch64.addImageTypes(rawAarch64Platform, ec2ImgTypeAarch64(rd))
if rd.osVersion != "8.5" {
// NOTE: RHEL 8.5 is going away and these image types require some
// work to get working, so we just disable them here until the
// whole distro gets deleted
x86_64.addImageTypes(ec2X86Platform, ec2SapImgTypeX86_64(rd))
}
// add GCE RHUI image to RHEL only
x86_64.addImageTypes(gceX86Platform, gceRhuiImgType(rd))
// add s390x to RHEL distro only
rd.addArches(s390x)
} else {
x86_64.addImageTypes(
bareMetalX86Platform,
edgeRawImgType(),
)
x86_64.addImageTypes(
rawUEFIx86Platform,
edgeSimplifiedInstallerImgType(rd),
)
x86_64.addImageTypes(azureX64Platform, azureImgType())
aarch64.addImageTypes(
rawAarch64Platform,
edgeRawImgType(),
edgeSimplifiedInstallerImgType(rd),
)
aarch64.addImageTypes(azureAarch64Platform, azureImgType())
}
rd.addArches(x86_64, aarch64, ppc64le)
return &rd
}

View file

@ -1,73 +0,0 @@
package rhel8
import (
"fmt"
"math/rand"
"testing"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var testBasicImageType = imageType{
name: "test",
basePartitionTables: defaultBasePartitionTables,
}
var testEc2ImageType = imageType{
name: "test_ec2",
basePartitionTables: ec2BasePartitionTables,
}
var mountpoints = []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/usr",
},
}
// math/rand is good enough in this case
/* #nosec G404 */
var rng = rand.New(rand.NewSource(0))
func TestDistro_UnsupportedArch(t *testing.T) {
testBasicImageType.arch = &architecture{
name: "unsupported_arch",
}
_, err := testBasicImageType.getPartitionTable(mountpoints, distro.ImageOptions{}, rng)
require.EqualError(t, err, fmt.Sprintf("no partition table defined for architecture %q for image type %q", testBasicImageType.arch.name, testBasicImageType.name))
}
func TestDistro_DefaultPartitionTables(t *testing.T) {
rhel8distro := New()
for _, archName := range rhel8distro.ListArches() {
testBasicImageType.arch = &architecture{
name: archName,
}
pt, err := testBasicImageType.getPartitionTable(mountpoints, distro.ImageOptions{}, rng)
require.Nil(t, err)
for _, m := range mountpoints {
assert.True(t, pt.ContainsMountpoint(m.Mountpoint))
}
}
}
func TestDistro_Ec2PartitionTables(t *testing.T) {
rhel8distro := New()
for _, archName := range rhel8distro.ListArches() {
testEc2ImageType.arch = &architecture{
name: archName,
}
pt, err := testEc2ImageType.getPartitionTable(mountpoints, distro.ImageOptions{}, rng)
if _, exists := testEc2ImageType.basePartitionTables[archName]; exists {
require.Nil(t, err)
for _, m := range mountpoints {
assert.True(t, pt.ContainsMountpoint(m.Mountpoint))
}
} else {
require.EqualError(t, err, fmt.Sprintf("no partition table defined for architecture %q for image type %q", testEc2ImageType.arch.name, testEc2ImageType.name))
}
}
}

View file

@ -1,907 +0,0 @@
package rhel8_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distro/distro_test_common"
"github.com/osbuild/osbuild-composer/internal/distro/rhel8"
"github.com/osbuild/osbuild-composer/internal/platform"
)
type rhelFamilyDistro struct {
name string
distro distro.Distro
}
var rhelFamilyDistros = []rhelFamilyDistro{
{
name: "rhel",
distro: rhel8.New(),
},
}
func TestFilenameFromType(t *testing.T) {
type args struct {
outputFormat string
}
type wantResult struct {
filename string
mimeType string
wantErr bool
}
tests := []struct {
name string
args args
want wantResult
}{
{
name: "ami",
args: args{"ami"},
want: wantResult{
filename: "image.raw",
mimeType: "application/octet-stream",
},
},
{
name: "ec2",
args: args{"ec2"},
want: wantResult{
filename: "image.raw.xz",
mimeType: "application/xz",
},
},
{
name: "ec2-ha",
args: args{"ec2-ha"},
want: wantResult{
filename: "image.raw.xz",
mimeType: "application/xz",
},
},
{
name: "ec2-sap",
args: args{"ec2-sap"},
want: wantResult{
filename: "image.raw.xz",
mimeType: "application/xz",
},
},
{
name: "qcow2",
args: args{"qcow2"},
want: wantResult{
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
},
},
{
name: "openstack",
args: args{"openstack"},
want: wantResult{
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
},
},
{
name: "vhd",
args: args{"vhd"},
want: wantResult{
filename: "disk.vhd",
mimeType: "application/x-vhd",
},
},
{
name: "azure-rhui",
args: args{"azure-rhui"},
want: wantResult{
filename: "disk.vhd.xz",
mimeType: "application/xz",
},
},
{
name: "azure-sap-rhui",
args: args{"azure-sap-rhui"},
want: wantResult{
filename: "disk.vhd.xz",
mimeType: "application/xz",
},
},
{
name: "vmdk",
args: args{"vmdk"},
want: wantResult{
filename: "disk.vmdk",
mimeType: "application/x-vmdk",
},
},
{
name: "ova",
args: args{"ova"},
want: wantResult{
filename: "image.ova",
mimeType: "application/ovf",
},
},
{
name: "tar",
args: args{"tar"},
want: wantResult{
filename: "root.tar.xz",
mimeType: "application/x-tar",
},
},
{
name: "image-installer",
args: args{"image-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{
name: "edge-commit",
args: args{"edge-commit"},
want: wantResult{
filename: "commit.tar",
mimeType: "application/x-tar",
},
},
// Alias
{
name: "rhel-edge-commit",
args: args{"rhel-edge-commit"},
want: wantResult{
filename: "commit.tar",
mimeType: "application/x-tar",
},
},
{
name: "edge-container",
args: args{"edge-container"},
want: wantResult{
filename: "container.tar",
mimeType: "application/x-tar",
},
},
// Alias
{
name: "rhel-edge-container",
args: args{"rhel-edge-container"},
want: wantResult{
filename: "container.tar",
mimeType: "application/x-tar",
},
},
{
name: "edge-installer",
args: args{"edge-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
// Alias
{
name: "rhel-edge-installer",
args: args{"rhel-edge-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{
name: "gce",
args: args{"gce"},
want: wantResult{
filename: "image.tar.gz",
mimeType: "application/gzip",
},
},
{
name: "gce-rhui",
args: args{"gce-rhui"},
want: wantResult{
filename: "image.tar.gz",
mimeType: "application/gzip",
},
},
{
name: "invalid-output-type",
args: args{"foobar"},
want: wantResult{wantErr: true},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dist := dist.distro
arch, _ := dist.GetArch("x86_64")
imgType, err := arch.GetImageType(tt.args.outputFormat)
if (err != nil) != tt.want.wantErr {
t.Errorf("Arch.GetImageType() error = %v, wantErr %v", err, tt.want.wantErr)
return
}
if !tt.want.wantErr {
gotFilename := imgType.Filename()
gotMIMEType := imgType.MIMEType()
if gotFilename != tt.want.filename {
t.Errorf("ImageType.Filename() got = %v, want %v", gotFilename, tt.want.filename)
}
if gotMIMEType != tt.want.mimeType {
t.Errorf("ImageType.MIMEType() got1 = %v, want %v", gotMIMEType, tt.want.mimeType)
}
}
})
}
})
}
}
func TestImageType_BuildPackages(t *testing.T) {
x8664BuildPackages := []string{
"dnf",
"dosfstools",
"e2fsprogs",
"grub2-efi-x64",
"grub2-pc",
"policycoreutils",
"shim-x64",
"systemd",
"tar",
"qemu-img",
"xz",
}
aarch64BuildPackages := []string{
"dnf",
"dosfstools",
"e2fsprogs",
"policycoreutils",
"qemu-img",
"systemd",
"tar",
"xz",
}
buildPackages := map[string][]string{
"x86_64": x8664BuildPackages,
"aarch64": aarch64BuildPackages,
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
d := dist.distro
for _, archLabel := range d.ListArches() {
archStruct, err := d.GetArch(archLabel)
if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) {
continue
}
for _, itLabel := range archStruct.ListImageTypes() {
itStruct, err := archStruct.GetImageType(itLabel)
if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) {
continue
}
manifest, _, err := itStruct.Manifest(&blueprint.Blueprint{}, distro.ImageOptions{}, nil, 0)
assert.NoError(t, err)
buildPkgs := manifest.GetPackageSetChains()["build"]
assert.NotNil(t, buildPkgs)
assert.Len(t, buildPkgs, 1)
assert.ElementsMatch(t, buildPackages[archLabel], buildPkgs[0].Include)
}
}
})
}
}
func TestImageType_Name(t *testing.T) {
imgMap := []struct {
arch string
imgNames []string
}{
{
arch: "x86_64",
imgNames: []string{
"qcow2",
"openstack",
"vhd",
"azure-rhui",
"azure-sap-rhui",
"azure-eap7-rhui",
"vmdk",
"ova",
"ami",
"ec2",
"ec2-ha",
"ec2-sap",
"gce",
"gce-rhui",
"edge-commit",
"edge-container",
"edge-installer",
"tar",
"image-installer",
},
},
{
arch: "aarch64",
imgNames: []string{
"qcow2",
"openstack",
"vhd",
"azure-rhui",
"ami",
"ec2",
"edge-commit",
"edge-container",
"tar",
},
},
{
arch: "ppc64le",
imgNames: []string{
"qcow2",
"tar",
},
},
{
arch: "s390x",
imgNames: []string{
"qcow2",
"tar",
},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, mapping := range imgMap {
if mapping.arch == platform.ARCH_S390X.String() && dist.name == "centos" {
continue
}
arch, err := dist.distro.GetArch(mapping.arch)
if assert.NoError(t, err) {
for _, imgName := range mapping.imgNames {
if imgName == "edge-commit" && dist.name == "centos" {
continue
}
imgType, err := arch.GetImageType(imgName)
if assert.NoError(t, err) {
assert.Equalf(t, imgName, imgType.Name(), "arch: %s", mapping.arch)
}
}
}
}
})
}
}
func TestImageTypeAliases(t *testing.T) {
type args struct {
imageTypeAliases []string
}
type wantResult struct {
imageTypeName string
}
tests := []struct {
name string
args args
want wantResult
}{
{
name: "edge-commit aliases",
args: args{
imageTypeAliases: []string{"rhel-edge-commit"},
},
want: wantResult{
imageTypeName: "edge-commit",
},
},
{
name: "edge-container aliases",
args: args{
imageTypeAliases: []string{"rhel-edge-container"},
},
want: wantResult{
imageTypeName: "edge-container",
},
},
{
name: "edge-installer aliases",
args: args{
imageTypeAliases: []string{"rhel-edge-installer"},
},
want: wantResult{
imageTypeName: "edge-installer",
},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dist := dist.distro
for _, archName := range dist.ListArches() {
t.Run(archName, func(t *testing.T) {
arch, err := dist.GetArch(archName)
require.Nilf(t, err,
"failed to get architecture '%s', previously listed as supported for the distro '%s'",
archName, dist.Name())
// Test image type aliases only if the aliased image type is supported for the arch
if _, err = arch.GetImageType(tt.want.imageTypeName); err != nil {
t.Skipf("aliased image type '%s' is not supported for architecture '%s'",
tt.want.imageTypeName, archName)
}
for _, alias := range tt.args.imageTypeAliases {
t.Run(fmt.Sprintf("'%s' alias for image type '%s'", alias, tt.want.imageTypeName),
func(t *testing.T) {
gotImage, err := arch.GetImageType(alias)
require.Nilf(t, err, "arch.GetImageType() for image type alias '%s' failed: %v",
alias, err)
assert.Equalf(t, tt.want.imageTypeName, gotImage.Name(),
"got unexpected image type name for alias '%s'. got = %s, want = %s",
alias, tt.want.imageTypeName, gotImage.Name())
})
}
})
}
})
}
})
}
}
// Check that Manifest() function returns an error for unsupported
// configurations.
func TestDistro_ManifestError(t *testing.T) {
// Currently, the only unsupported configuration is OSTree commit types
// with Kernel boot options
r8distro := rhel8.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Kernel: &blueprint.KernelCustomization{
Append: "debug",
},
},
}
for _, archName := range r8distro.ListArches() {
arch, _ := r8distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
imgOpts := distro.ImageOptions{
Size: imgType.Size(0),
}
_, _, err := imgType.Manifest(&bp, imgOpts, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "kernel boot parameter customizations are not supported for ostree types")
} else if imgTypeName == "edge-raw-image" {
assert.EqualError(t, err, "edge raw images require specifying a URL from which to retrieve the OSTree commit")
} else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" {
assert.EqualError(t, err, fmt.Sprintf("boot ISO image type \"%s\" requires specifying a URL from which to retrieve the OSTree commit", imgTypeName))
} else if imgTypeName == "azure-eap7-rhui" {
assert.EqualError(t, err, fmt.Sprintf("image type \"%s\" does not support customizations", imgTypeName))
} else {
assert.NoError(t, err)
}
}
}
}
func TestArchitecture_ListImageTypes(t *testing.T) {
imgMap := []struct {
arch string
imgNames []string
rhelAdditionalImageTypes []string
}{
{
arch: "x86_64",
imgNames: []string{
"qcow2",
"openstack",
"vhd",
"azure-rhui",
"azure-sap-rhui",
"azure-eap7-rhui",
"vmdk",
"ova",
"ami",
"ec2",
"ec2-ha",
"ec2-sap",
"gce",
"gce-rhui",
"edge-commit",
"edge-container",
"edge-installer",
"edge-raw-image",
"edge-simplified-installer",
"tar",
"image-installer",
"oci",
},
},
{
arch: "aarch64",
imgNames: []string{
"qcow2",
"openstack",
"vhd",
"azure-rhui",
"ami",
"ec2",
"edge-commit",
"edge-container",
"edge-installer",
"edge-simplified-installer",
"edge-raw-image",
"tar",
"image-installer",
},
},
{
arch: "ppc64le",
imgNames: []string{
"qcow2",
"tar",
},
},
{
arch: "s390x",
imgNames: []string{
"qcow2",
"tar",
},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, mapping := range imgMap {
arch, err := dist.distro.GetArch(mapping.arch)
require.NoError(t, err)
imageTypes := arch.ListImageTypes()
var expectedImageTypes []string
expectedImageTypes = append(expectedImageTypes, mapping.imgNames...)
if dist.name == "rhel" {
expectedImageTypes = append(expectedImageTypes, mapping.rhelAdditionalImageTypes...)
}
require.ElementsMatch(t, expectedImageTypes, imageTypes)
}
})
}
}
func TestRHEL8_ListArches(t *testing.T) {
arches := rhel8.New().ListArches()
assert.Equal(t, []string{"aarch64", "ppc64le", "s390x", "x86_64"}, arches)
}
func TestRHEL8_GetArch(t *testing.T) {
arches := []struct {
name string
errorExpected bool
errorExpectedInCentos bool
}{
{
name: "x86_64",
},
{
name: "aarch64",
},
{
name: "ppc64le",
},
{
name: "s390x",
},
{
name: "foo-arch",
errorExpected: true,
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, a := range arches {
actualArch, err := dist.distro.GetArch(a.name)
if a.errorExpected || (a.errorExpectedInCentos && dist.name == "centos") {
assert.Nil(t, actualArch)
assert.Error(t, err)
} else {
assert.Equal(t, a.name, actualArch.Name())
assert.NoError(t, err)
}
}
})
}
}
func TestRhel8_Name(t *testing.T) {
distro := rhel8.New()
assert.Equal(t, "rhel-8", distro.Name())
}
func TestRhel8_ModulePlatformID(t *testing.T) {
distro := rhel8.New()
assert.Equal(t, "platform:el8", distro.ModulePlatformID())
}
func TestRhel86_KernelOption(t *testing.T) {
distro_test_common.TestDistro_KernelOption(t, rhel8.New())
}
func TestRhel8_OSTreeOptions(t *testing.T) {
distro_test_common.TestDistro_OSTreeOptions(t, rhel8.New())
}
func TestDistro_CustomFileSystemManifestError(t *testing.T) {
r8distro := rhel8.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/etc",
},
},
},
}
unsupported := map[string]bool{
"edge-installer": true,
"edge-simplified-installer": true,
"edge-raw-image": true,
"azure-eap7-rhui": true,
}
for _, archName := range r8distro.ListArches() {
arch, _ := r8distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if unsupported[imgTypeName] {
assert.Error(t, err)
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/etc\"]")
}
}
}
}
func TestDistro_TestRootMountPoint(t *testing.T) {
r8distro := rhel8.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/",
},
},
},
}
unsupported := map[string]bool{
"edge-installer": true,
"edge-simplified-installer": true,
"edge-raw-image": true,
"azure-eap7-rhui": true,
}
for _, archName := range r8distro.ListArches() {
arch, _ := r8distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if unsupported[imgTypeName] {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_CustomFileSystemSubDirectories(t *testing.T) {
r8distro := rhel8.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/var/log",
},
{
MinSize: 1024,
Mountpoint: "/var/log/audit",
},
},
},
}
unsupported := map[string]bool{
"edge-commit": true,
"edge-container": true,
"edge-installer": true,
"edge-simplified-installer": true,
"edge-raw-image": true,
"azure-eap7-rhui": true,
}
for _, archName := range r8distro.ListArches() {
arch, _ := r8distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if unsupported[imgTypeName] {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_MountpointsWithArbitraryDepthAllowed(t *testing.T) {
r8distro := rhel8.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/var/a",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b/c",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b/c/d",
},
},
},
}
unsupported := map[string]bool{
"edge-commit": true,
"edge-container": true,
"edge-installer": true,
"edge-simplified-installer": true,
"edge-raw-image": true,
"azure-eap7-rhui": true,
}
for _, archName := range r8distro.ListArches() {
arch, _ := r8distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if unsupported[imgTypeName] {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_DirtyMountpointsNotAllowed(t *testing.T) {
r8distro := rhel8.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "//",
},
{
MinSize: 1024,
Mountpoint: "/var//",
},
{
MinSize: 1024,
Mountpoint: "/var//log/audit/",
},
},
},
}
unsupported := map[string]bool{
"edge-commit": true,
"edge-container": true,
"edge-installer": true,
"edge-simplified-installer": true,
"edge-raw-image": true,
"azure-eap7-rhui": true,
}
for _, archName := range r8distro.ListArches() {
arch, _ := r8distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if unsupported[imgTypeName] {
assert.Error(t, err)
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"//\" \"/var//\" \"/var//log/audit/\"]")
}
}
}
}
func TestDistro_CustomFileSystemPatternMatching(t *testing.T) {
r8distro := rhel8.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/variable",
},
{
MinSize: 1024,
Mountpoint: "/variable/log/audit",
},
},
},
}
unsupported := map[string]bool{
"edge-installer": true,
"edge-simplified-installer": true,
"edge-raw-image": true,
"azure-eap7-rhui": true,
}
for _, archName := range r8distro.ListArches() {
arch, _ := r8distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if unsupported[imgTypeName] {
assert.Error(t, err)
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/variable\" \"/variable/log/audit\"]")
}
}
}
}
func TestDistro_CustomUsrPartitionNotLargeEnough(t *testing.T) {
r8distro := rhel8.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/usr",
},
},
},
}
unsupported := map[string]bool{
"edge-installer": true,
"edge-simplified-installer": true,
"edge-raw-image": true,
"azure-eap7-rhui": true,
}
for _, archName := range r8distro.ListArches() {
arch, _ := r8distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if unsupported[imgTypeName] {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
}
}
}

View file

@ -1,381 +0,0 @@
package rhel8
import (
"fmt"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
func edgeCommitImgType(rd distribution) imageType {
it := imageType{
name: "edge-commit",
nameAliases: []string{"rhel-edge-commit"},
filename: "commit.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: edgeCommitPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: edgeServices(rd),
},
rpmOstree: true,
image: edgeCommitImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "ostree-commit", "commit-archive"},
exports: []string{"commit-archive"},
}
return it
}
func edgeOCIImgType(rd distribution) imageType {
it := imageType{
name: "edge-container",
nameAliases: []string{"rhel-edge-container"},
filename: "container.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: edgeCommitPackageSet,
containerPkgsKey: func(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{"nginx"},
}
},
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: edgeServices(rd),
},
rpmOstree: true,
bootISO: false,
image: edgeContainerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "ostree-commit", "container-tree", "container"},
exports: []string{"container"},
}
return it
}
func edgeRawImgType() imageType {
it := imageType{
name: "edge-raw-image",
nameAliases: []string{"rhel-edge-raw-image"},
filename: "image.raw.xz",
compression: "xz",
mimeType: "application/xz",
packageSets: nil,
defaultSize: 10 * common.GibiByte,
rpmOstree: true,
bootable: true,
bootISO: false,
image: edgeRawImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"ostree-deployment", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: edgeBasePartitionTables,
}
return it
}
func edgeInstallerImgType(rd distribution) imageType {
it := imageType{
name: "edge-installer",
nameAliases: []string{"rhel-edge-installer"},
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
// TODO: non-arch-specific package set handling for installers
// This image type requires build packages for installers and
// ostree/edge. For now we only have x86-64 installer build
// package sets defined. When we add installer build package sets
// for other architectures, this will need to be moved to the
// architecture and the merging will happen in the PackageSets()
// method like the other sets.
installerPkgsKey: edgeInstallerPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: edgeServices(rd),
},
rpmOstree: true,
bootISO: true,
image: edgeInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"anaconda-tree", "rootfs-image", "efiboot-tree", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
}
return it
}
func edgeSimplifiedInstallerImgType(rd distribution) imageType {
it := imageType{
name: "edge-simplified-installer",
nameAliases: []string{"rhel-edge-simplified-installer"},
filename: "simplified-installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
// TODO: non-arch-specific package set handling for installers
// This image type requires build packages for installers and
// ostree/edge. For now we only have x86-64 installer build
// package sets defined. When we add installer build package sets
// for other architectures, this will need to be moved to the
// architecture and the merging will happen in the PackageSets()
// method like the other sets.
installerPkgsKey: edgeSimplifiedInstallerPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: edgeServices(rd),
},
defaultSize: 10 * common.GibiByte,
rpmOstree: true,
bootable: true,
bootISO: true,
image: edgeSimplifiedInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"ostree-deployment", "image", "xz", "coi-tree", "efiboot-tree", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
basePartitionTables: edgeBasePartitionTables,
}
return it
}
// edge commit OS package set
func edgeCommitPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"attr",
"audit",
"basesystem",
"bash",
"bash-completion",
"chrony",
"clevis",
"clevis-dracut",
"clevis-luks",
"container-selinux",
"coreutils",
"criu",
"cryptsetup",
"curl",
"dnsmasq",
"dosfstools",
"dracut-config-generic",
"dracut-network",
"e2fsprogs",
"firewalld",
"fuse-overlayfs",
"fwupd",
"glibc",
"glibc-minimal-langpack",
"gnupg2",
"greenboot",
"gzip",
"hostname",
"ima-evm-utils",
"iproute",
"iptables",
"iputils",
"keyutils",
"less",
"lvm2",
"NetworkManager",
"NetworkManager-wifi",
"NetworkManager-wwan",
"nss-altfiles",
"openssh-clients",
"openssh-server",
"passwd",
"pinentry",
"platform-python",
"podman",
"policycoreutils",
"policycoreutils-python-utils",
"polkit",
"procps-ng",
"redhat-release",
"rootfiles",
"rpm",
"rpm-ostree",
"rsync",
"selinux-policy-targeted",
"setools-console",
"setup",
"shadow-utils",
"shadow-utils",
"skopeo",
"slirp4netns",
"sudo",
"systemd",
"tar",
"tmux",
"traceroute",
"usbguard",
"util-linux",
"vim-minimal",
"wpa_supplicant",
"xz",
},
Exclude: []string{"rng-tools"},
}
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(x8664EdgeCommitPackageSet(t))
case platform.ARCH_AARCH64.String():
ps = ps.Append(aarch64EdgeCommitPackageSet(t))
}
if t.arch.distro.isRHEL() && common.VersionLessThan(t.arch.distro.osVersion, "8.6") {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"greenboot-grub2",
"greenboot-reboot",
"greenboot-rpm-ostree-grub2",
"greenboot-status",
},
})
} else {
// 8.6+ and CS8
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"fdo-client",
"fdo-owner-cli",
"greenboot-default-health-checks",
"sos",
},
})
}
return ps
}
func x8664EdgeCommitPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"efibootmgr",
"grub2",
"grub2-efi-x64",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"microcode_ctl",
"shim-x64",
},
Exclude: nil,
}
}
func aarch64EdgeCommitPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"efibootmgr",
"grub2-efi-aa64",
"iwl7260-firmware",
"shim-aa64",
},
Exclude: nil,
}
}
func edgeInstallerPackageSet(t *imageType) rpmmd.PackageSet {
return anacondaPackageSet(t)
}
func edgeSimplifiedInstallerPackageSet(t *imageType) rpmmd.PackageSet {
// common installer packages
ps := installerPackageSet(t)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"attr",
"basesystem",
"binutils",
"bsdtar",
"clevis-dracut",
"clevis-luks",
"cloud-utils-growpart",
"coreos-installer",
"coreos-installer-dracut",
"coreutils",
"device-mapper-multipath",
"dnsmasq",
"dosfstools",
"dracut-live",
"e2fsprogs",
"fcoe-utils",
"fdo-init",
"gzip",
"ima-evm-utils",
"iproute",
"iptables",
"iputils",
"iscsi-initiator-utils",
"keyutils",
"lldpad",
"lvm2",
"passwd",
"policycoreutils",
"policycoreutils-python-utils",
"procps-ng",
"redhat-logos",
"rootfiles",
"setools-console",
"sudo",
"traceroute",
"util-linux",
},
Exclude: nil,
})
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(x8664EdgeCommitPackageSet(t))
case platform.ARCH_AARCH64.String():
ps = ps.Append(aarch64EdgeCommitPackageSet(t))
default:
panic(fmt.Sprintf("unsupported arch: %s", t.arch.Name()))
}
return ps
}
func edgeServices(rd distribution) []string {
// Common Services
var edgeServices = []string{"NetworkManager.service", "firewalld.service", "sshd.service"}
if rd.osVersion == "8.4" {
// greenboot services aren't enabled by default in 8.4
edgeServices = append(edgeServices,
"greenboot-grub2-set-counter",
"greenboot-grub2-set-success",
"greenboot-healthcheck",
"greenboot-rpm-ostree-grub2-check-fallback",
"greenboot-status",
"greenboot-task-runner",
"redboot-auto-reboot",
"redboot-task-runner")
}
if !(rd.isRHEL() && common.VersionLessThan(rd.osVersion, "8.6")) {
// enable fdo-client only on RHEL 8.6+ and CS8
// TODO(runcom): move fdo-client-linuxapp.service to presets?
edgeServices = append(edgeServices, "fdo-client-linuxapp.service")
}
return edgeServices
}

View file

@ -1,299 +0,0 @@
package rhel8
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
const gceKernelOptions = "net.ifnames=0 biosdevname=0 scsi_mod.use_blk_mq=Y crashkernel=auto console=ttyS0,38400n8d"
func gceImgType(rd distribution) imageType {
return imageType{
name: "gce",
filename: "image.tar.gz",
mimeType: "application/gzip",
packageSets: map[string]packageSetFunc{
osPkgsKey: gcePackageSet,
},
defaultImageConfig: defaultGceByosImageConfig(rd),
kernelOptions: gceKernelOptions,
bootable: true,
defaultSize: 20 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "archive"},
exports: []string{"archive"},
// TODO: the base partition table still contains the BIOS boot partition, but the image is UEFI-only
basePartitionTables: defaultBasePartitionTables,
}
}
func gceRhuiImgType(rd distribution) imageType {
return imageType{
name: "gce-rhui",
filename: "image.tar.gz",
mimeType: "application/gzip",
packageSets: map[string]packageSetFunc{
osPkgsKey: gceRhuiPackageSet,
},
defaultImageConfig: defaultGceRhuiImageConfig(rd),
kernelOptions: gceKernelOptions,
bootable: true,
defaultSize: 20 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "archive"},
exports: []string{"archive"},
// TODO: the base partition table still contains the BIOS boot partition, but the image is UEFI-only
basePartitionTables: defaultBasePartitionTables,
}
}
func defaultGceByosImageConfig(rd distribution) *distro.ImageConfig {
ic := &distro.ImageConfig{
Timezone: common.ToPtr("UTC"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "metadata.google.internal"}},
},
Firewall: &osbuild.FirewallStageOptions{
DefaultZone: "trusted",
},
EnabledServices: []string{
"sshd",
"rngd",
"dnf-automatic.timer",
},
DisabledServices: []string{
"sshd-keygen@",
"reboot.target",
},
DefaultTarget: common.ToPtr("multi-user.target"),
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
DNFConfig: []*osbuild.DNFConfigStageOptions{
{
Config: &osbuild.DNFConfig{
Main: &osbuild.DNFConfigMain{
IPResolve: "4",
},
},
},
},
DNFAutomaticConfig: &osbuild.DNFAutomaticConfigStageOptions{
Config: &osbuild.DNFAutomaticConfig{
Commands: &osbuild.DNFAutomaticConfigCommands{
ApplyUpdates: common.ToPtr(true),
UpgradeType: osbuild.DNFAutomaticUpgradeTypeSecurity,
},
},
},
YUMRepos: []*osbuild.YumReposStageOptions{
{
Filename: "google-cloud.repo",
Repos: []osbuild.YumRepository{
{
Id: "google-compute-engine",
Name: "Google Compute Engine",
BaseURLs: []string{"https://packages.cloud.google.com/yum/repos/google-compute-engine-el8-x86_64-stable"},
Enabled: common.ToPtr(true),
GPGCheck: common.ToPtr(true),
RepoGPGCheck: common.ToPtr(false),
GPGKey: []string{
"https://packages.cloud.google.com/yum/doc/yum-key.gpg",
"https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg",
},
},
},
},
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
PasswordAuthentication: common.ToPtr(false),
ClientAliveInterval: common.ToPtr(420),
PermitRootLogin: osbuild.PermitRootLoginValueNo,
},
},
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
DefaultKernel: "kernel-core",
UpdateDefault: true,
},
},
},
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-floppy.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("floppy"),
},
},
},
GCPGuestAgentConfig: &osbuild.GcpGuestAgentConfigOptions{
ConfigScope: osbuild.GcpGuestAgentConfigScopeDistro,
Config: &osbuild.GcpGuestAgentConfig{
InstanceSetup: &osbuild.GcpGuestAgentConfigInstanceSetup{
SetBotoConfig: common.ToPtr(false),
},
},
},
}
if rd.osVersion == "8.4" {
// NOTE(akoutsou): these are enabled in the package preset, but for
// some reason do not get enabled on 8.4.
// the reason is unknown and deeply mysterious
ic.EnabledServices = append(ic.EnabledServices,
"google-oslogin-cache.timer",
"google-guest-agent.service",
"google-shutdown-scripts.service",
"google-startup-scripts.service",
"google-osconfig-agent.service",
)
}
if rd.isRHEL() {
ic.RHSMConfig = map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// Don't disable RHSM redhat.repo management on the GCE
// image, which is BYOS and does not use RHUI for content.
// Otherwise subscribing the system manually after booting
// it would result in empty redhat.repo. Without RHUI, such
// system would have no way to get Red Hat content, but
// enable the repo management manually, which would be very
// confusing.
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
}
}
return ic
}
func defaultGceRhuiImageConfig(rd distribution) *distro.ImageConfig {
ic := &distro.ImageConfig{
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
Rhsm: &osbuild.SubManConfigRHSMSection{
ManageRepos: common.ToPtr(false),
},
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
}
ic = ic.InheritFrom(defaultGceByosImageConfig(rd))
return ic
}
// common GCE image
func gceCommonPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"@core",
"langpacks-en", // not in Google's KS
"acpid",
"dhcp-client",
"dnf-automatic",
"net-tools",
//"openssh-server", included in core
"python3",
"rng-tools",
"tar",
"vim",
// GCE guest tools
"google-compute-engine",
"google-osconfig-agent",
"gce-disk-expand",
// Not explicitly included in GCP kickstart, but present on the image
// for time synchronization
"chrony",
"timedatex",
// EFI
"grub2-tools-efi",
},
Exclude: []string{
"alsa-utils",
"b43-fwcutter",
"dmraid",
"eject",
"gpm",
"irqbalance",
"microcode_ctl",
"smartmontools",
"aic94xx-firmware",
"atmel-firmware",
"b43-openfwwf",
"bfa-firmware",
"ipw2100-firmware",
"ipw2200-firmware",
"ivtv-firmware",
"iwl100-firmware",
"iwl1000-firmware",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6000g2a-firmware",
"iwl6050-firmware",
"kernel-firmware",
"libertas-usb8388-firmware",
"ql2100-firmware",
"ql2200-firmware",
"ql23xx-firmware",
"ql2400-firmware",
"ql2500-firmware",
"rt61pci-firmware",
"rt73usb-firmware",
"xorg-x11-drv-ati-firmware",
"zd1211-firmware",
// RHBZ#2075815
"qemu-guest-agent",
},
}.Append(distroSpecificPackageSet(t))
}
// GCE BYOS image
func gcePackageSet(t *imageType) rpmmd.PackageSet {
return gceCommonPackageSet(t)
}
// GCE RHUI image
func gceRhuiPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"google-rhui-client-rhel8",
},
}.Append(gceCommonPackageSet(t))
}

View file

@ -1,573 +0,0 @@
package rhel8
import (
"fmt"
"math/rand"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/fdo"
"github.com/osbuild/osbuild-composer/internal/ignition"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/users"
"github.com/osbuild/osbuild-composer/internal/workload"
)
func osCustomizations(
t *imageType,
osPackageSet rpmmd.PackageSet,
options distro.ImageOptions,
containers []container.SourceSpec,
c *blueprint.Customizations,
) manifest.OSCustomizations {
imageConfig := t.getDefaultImageConfig()
osc := manifest.OSCustomizations{}
if t.bootable || t.rpmOstree {
osc.KernelName = c.GetKernel().Name
var kernelOptions []string
if t.kernelOptions != "" {
kernelOptions = append(kernelOptions, t.kernelOptions)
}
if bpKernel := c.GetKernel(); bpKernel.Append != "" {
kernelOptions = append(kernelOptions, bpKernel.Append)
}
osc.KernelOptionsAppend = kernelOptions
if t.platform.GetArch() != platform.ARCH_S390X {
osc.KernelOptionsBootloader = true
}
}
osc.ExtraBasePackages = osPackageSet.Include
osc.ExcludeBasePackages = osPackageSet.Exclude
osc.ExtraBaseRepos = osPackageSet.Repositories
osc.Containers = containers
osc.GPGKeyFiles = imageConfig.GPGKeyFiles
if imageConfig.ExcludeDocs != nil {
osc.ExcludeDocs = *imageConfig.ExcludeDocs
}
if !t.bootISO {
// don't put users and groups in the payload of an installer
// add them via kickstart instead
osc.Groups = users.GroupsFromBP(c.GetGroups())
osc.Users = users.UsersFromBP(c.GetUsers())
}
osc.EnabledServices = imageConfig.EnabledServices
osc.DisabledServices = imageConfig.DisabledServices
if imageConfig.DefaultTarget != nil {
osc.DefaultTarget = *imageConfig.DefaultTarget
}
osc.Firewall = imageConfig.Firewall
if fw := c.GetFirewall(); fw != nil {
options := osbuild.FirewallStageOptions{
Ports: fw.Ports,
}
if fw.Services != nil {
options.EnabledServices = fw.Services.Enabled
options.DisabledServices = fw.Services.Disabled
}
if fw.Zones != nil {
for _, z := range fw.Zones {
options.Zones = append(options.Zones, osbuild.FirewallZone{
Name: *z.Name,
Sources: z.Sources,
})
}
}
osc.Firewall = &options
}
language, keyboard := c.GetPrimaryLocale()
if language != nil {
osc.Language = *language
} else if imageConfig.Locale != nil {
osc.Language = *imageConfig.Locale
}
if keyboard != nil {
osc.Keyboard = keyboard
} else if imageConfig.Keyboard != nil {
osc.Keyboard = &imageConfig.Keyboard.Keymap
if imageConfig.Keyboard.X11Keymap != nil {
osc.X11KeymapLayouts = imageConfig.Keyboard.X11Keymap.Layouts
}
}
if hostname := c.GetHostname(); hostname != nil {
osc.Hostname = *hostname
}
timezone, ntpServers := c.GetTimezoneSettings()
if timezone != nil {
osc.Timezone = *timezone
} else if imageConfig.Timezone != nil {
osc.Timezone = *imageConfig.Timezone
}
if len(ntpServers) > 0 {
for _, server := range ntpServers {
osc.NTPServers = append(osc.NTPServers, osbuild.ChronyConfigServer{Hostname: server})
}
} else if imageConfig.TimeSynchronization != nil {
osc.NTPServers = imageConfig.TimeSynchronization.Servers
osc.LeapSecTZ = imageConfig.TimeSynchronization.LeapsecTz
}
// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
osc.SElinux = "targeted"
}
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
if t.rpmOstree {
panic("unexpected oscap options for ostree image type")
}
var datastream = oscapConfig.DataStream
if datastream == "" {
datastream = oscap.DefaultRHEL8Datastream(t.arch.distro.isRHEL())
}
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(
osbuild.OscapConfig{
Datastream: datastream,
ProfileID: oscapConfig.ProfileID,
},
)
}
if t.arch.distro.isRHEL() && options.Facts != nil {
osc.FactAPIType = &options.Facts.APIType
}
var err error
osc.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(c.GetDirectories())
if err != nil {
// In theory this should never happen, because the blueprint directory customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert directory customizations to fs node directories: %v", err))
}
osc.Files, err = blueprint.FileCustomizationsToFsNodeFiles(c.GetFiles())
if err != nil {
// In theory this should never happen, because the blueprint file customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert file customizations to fs node files: %v", err))
}
// set yum repos first, so it doesn't get overridden by
// imageConfig.YUMRepos
osc.YUMRepos = imageConfig.YUMRepos
customRepos, err := c.GetRepositories()
if err != nil {
// This shouldn't happen and since the repos
// should have already been validated
panic(fmt.Sprintf("failed to get custom repos: %v", err))
}
// This function returns a map of filename and corresponding yum repos
// and a list of fs node files for the inline gpg keys so we can save
// them to disk. This step also swaps the inline gpg key with the path
// to the file in the os file tree
yumRepos, gpgKeyFiles, err := blueprint.RepoCustomizationsToRepoConfigAndGPGKeyFiles(customRepos)
if err != nil {
panic(fmt.Sprintf("failed to convert inline gpgkeys to fs node files: %v", err))
}
// add the gpg key files to the list of files to be added to the tree
if len(gpgKeyFiles) > 0 {
osc.Files = append(osc.Files, gpgKeyFiles...)
}
for filename, repos := range yumRepos {
osc.YUMRepos = append(osc.YUMRepos, osbuild.NewYumReposStageOptions(filename, repos))
}
osc.ShellInit = imageConfig.ShellInit
osc.Grub2Config = imageConfig.Grub2Config
osc.Sysconfig = imageConfig.Sysconfig
osc.SystemdLogind = imageConfig.SystemdLogind
osc.CloudInit = imageConfig.CloudInit
osc.Modprobe = imageConfig.Modprobe
osc.DracutConf = imageConfig.DracutConf
osc.SystemdUnit = imageConfig.SystemdUnit
osc.Authselect = imageConfig.Authselect
osc.SELinuxConfig = imageConfig.SELinuxConfig
osc.Tuned = imageConfig.Tuned
osc.Tmpfilesd = imageConfig.Tmpfilesd
osc.PamLimitsConf = imageConfig.PamLimitsConf
osc.Sysctld = imageConfig.Sysctld
osc.DNFConfig = imageConfig.DNFConfig
osc.DNFAutomaticConfig = imageConfig.DNFAutomaticConfig
osc.SshdConfig = imageConfig.SshdConfig
osc.AuthConfig = imageConfig.Authconfig
osc.PwQuality = imageConfig.PwQuality
osc.RHSMConfig = imageConfig.RHSMConfig
osc.Subscription = options.Subscription
osc.WAAgentConfig = imageConfig.WAAgentConfig
osc.UdevRules = imageConfig.UdevRules
osc.GCPGuestAgentConfig = imageConfig.GCPGuestAgentConfig
return osc
}
func liveImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewLiveImage()
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.Environment = t.environment
img.Workload = workload
img.Compression = t.compression
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
return img, nil
}
func imageInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewAnacondaTarInstaller()
img.Platform = t.platform
img.Workload = workload
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.ExtraBasePackages = packageSets[installerPkgsKey]
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
img.AdditionalDracutModules = []string{"prefixdevname", "prefixdevname-tools"}
img.AdditionalAnacondaModules = []string{"org.fedoraproject.Anaconda.Modules.Users"}
img.SquashfsCompression = "xz"
// put the kickstart file in the root of the iso
img.ISORootKickstart = true
d := t.arch.distro
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.OSName = "redhat"
img.OSVersion = d.osVersion
img.Release = fmt.Sprintf("%s %s", d.product, d.osVersion)
img.Filename = t.Filename()
return img, nil
}
func tarImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewArchive()
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.Environment = t.environment
img.Workload = workload
img.Filename = t.Filename()
return img, nil
}
func edgeCommitImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
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)
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.Environment = t.environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = t.arch.distro.osVersion
img.Filename = t.Filename()
return img, nil
}
func edgeContainerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
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.NewOSTreeContainer(commitRef)
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.ContainerLanguage = img.OSCustomizations.Language
img.Environment = t.environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = t.arch.distro.osVersion
img.ExtraContainerPackages = packageSets[containerPkgsKey]
img.Filename = t.Filename()
return img, nil
}
func edgeInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
d := t.arch.distro
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
img := image.NewAnacondaOSTreeInstaller(commit)
img.Platform = t.platform
img.ExtraBasePackages = packageSets[installerPkgsKey]
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
img.SquashfsCompression = "xz"
img.AdditionalDracutModules = []string{"prefixdevname", "prefixdevname-tools"}
if len(img.Users)+len(img.Groups) > 0 {
// only enable the users module if needed
img.AdditionalAnacondaModules = []string{"org.fedoraproject.Anaconda.Modules.Users"}
}
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.Variant = "edge"
img.OSName = "rhel"
img.OSVersion = d.osVersion
img.Release = fmt.Sprintf("%s %s", d.product, d.osVersion)
img.Filename = t.Filename()
return img, nil
}
func edgeRawImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
img := image.NewOSTreeRawImage(commit)
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
img.KernelOptionsAppend = []string{"modprobe.blacklist=vc4"}
// TODO: move to image config
img.Keyboard = "us"
img.Locale = "C.UTF-8"
img.Platform = t.platform
img.Workload = workload
img.Remote = ostree.Remote{
Name: "rhel-edge",
URL: options.OSTree.URL,
ContentURL: options.OSTree.ContentURL,
}
img.OSName = "redhat"
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
img.Compression = t.compression
return img, nil
}
func edgeSimplifiedInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
rawImg := image.NewOSTreeRawImage(commit)
rawImg.Users = users.UsersFromBP(customizations.GetUsers())
rawImg.Groups = users.GroupsFromBP(customizations.GetGroups())
rawImg.KernelOptionsAppend = []string{"modprobe.blacklist=vc4"}
rawImg.Keyboard = "us"
rawImg.Locale = "C.UTF-8"
rawImg.Platform = t.platform
rawImg.Workload = workload
rawImg.Remote = ostree.Remote{
Name: "rhel-edge",
URL: options.OSTree.URL,
ContentURL: options.OSTree.ContentURL,
}
rawImg.OSName = "redhat"
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
rawImg.PartitionTable = pt
rawImg.Filename = t.Filename()
img := image.NewOSTreeSimplifiedInstaller(rawImg, customizations.InstallationDevice)
img.ExtraBasePackages = packageSets[installerPkgsKey]
// img.Workload = workload
img.Platform = t.platform
img.Filename = t.Filename()
if bpFDO := customizations.GetFDO(); bpFDO != nil {
img.FDO = fdo.FromBP(*bpFDO)
}
// ignition configs from blueprint
if bpIgnition := customizations.GetIgnition(); bpIgnition != nil {
if bpIgnition.FirstBoot != nil {
img.IgnitionFirstBoot = ignition.FirstbootOptionsFromBP(*bpIgnition.FirstBoot)
}
if bpIgnition.Embedded != nil {
var err error
img.IgnitionEmbedded, err = ignition.EmbeddedOptionsFromBP(*bpIgnition.Embedded)
if err != nil {
return nil, err
}
}
}
d := t.arch.distro
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.Variant = "edge"
img.OSName = "redhat"
img.OSVersion = d.osVersion
img.AdditionalDracutModules = []string{"prefixdevname", "prefixdevname-tools"}
return img, nil
}
// Create an ostree SourceSpec to define an ostree parent commit using the user
// options and the default ref for the image type. Additionally returns the
// ref to be used for the new commit to be created.
func makeOSTreeParentCommit(options *ostree.ImageOptions, defaultRef string) (*ostree.SourceSpec, string) {
commitRef := defaultRef
if options == nil {
// nothing to do
return nil, commitRef
}
if options.ImageRef != "" {
// user option overrides default commit ref
commitRef = options.ImageRef
}
var parentCommit *ostree.SourceSpec
if options.URL == "" {
// no parent
return nil, commitRef
}
// ostree URL specified: set source spec for parent commit
parentRef := options.ParentRef
if parentRef == "" {
// parent ref not set: use image ref
parentRef = commitRef
}
parentCommit = &ostree.SourceSpec{
URL: options.URL,
Ref: parentRef,
RHSM: options.RHSM,
}
return parentCommit, commitRef
}
// Create an ostree SourceSpec to define an ostree payload using the user options and the default ref for the image type.
func makeOSTreePayloadCommit(options *ostree.ImageOptions, defaultRef string) (ostree.SourceSpec, error) {
if options == nil || options.URL == "" {
// this should be caught by checkOptions() in distro, but it's good
// to guard against it here as well
return ostree.SourceSpec{}, fmt.Errorf("ostree commit URL required")
}
commitRef := defaultRef
if options.ImageRef != "" {
// user option overrides default commit ref
commitRef = options.ImageRef
}
return ostree.SourceSpec{
URL: options.URL,
Ref: commitRef,
RHSM: options.RHSM,
}, nil
}

View file

@ -1,418 +0,0 @@
package rhel8
import (
"fmt"
"log"
"math/rand"
"strings"
"golang.org/x/exp/slices"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/pathpolicy"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/workload"
)
const (
// package set names
// main/common os image package set name
osPkgsKey = "os"
// container package set name
containerPkgsKey = "container"
// installer package set name
installerPkgsKey = "installer"
// blueprint package set name
blueprintPkgsKey = "blueprint"
)
type imageFunc func(workload workload.Workload, t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, packageSets map[string]rpmmd.PackageSet, containers []container.SourceSpec, rng *rand.Rand) (image.ImageKind, error)
type packageSetFunc func(t *imageType) rpmmd.PackageSet
type imageType struct {
arch *architecture
platform platform.Platform
environment environment.Environment
workload workload.Workload
name string
nameAliases []string
filename string
compression string // TODO: remove from image definition and make it a transport option
mimeType string
packageSets map[string]packageSetFunc
defaultImageConfig *distro.ImageConfig
kernelOptions string
defaultSize uint64
buildPipelines []string
payloadPipelines []string
exports []string
image imageFunc
// bootISO: installable ISO
bootISO bool
// rpmOstree: edge/ostree
rpmOstree bool
// bootable image
bootable bool
// List of valid arches for the image type
basePartitionTables distro.BasePartitionTableMap
}
func (t *imageType) Name() string {
return t.name
}
func (t *imageType) Arch() distro.Arch {
return t.arch
}
func (t *imageType) Filename() string {
return t.filename
}
func (t *imageType) MIMEType() string {
return t.mimeType
}
func (t *imageType) OSTreeRef() string {
d := t.arch.distro
if t.rpmOstree {
return fmt.Sprintf(d.ostreeRefTmpl, t.Arch().Name())
}
return ""
}
func (t *imageType) Size(size uint64) uint64 {
// Microsoft Azure requires vhd images to be rounded up to the nearest MB
if t.name == "vhd" && size%common.MebiByte != 0 {
size = (size/common.MebiByte + 1) * common.MebiByte
}
if size == 0 {
size = t.defaultSize
}
return size
}
func (t *imageType) BuildPipelines() []string {
return t.buildPipelines
}
func (t *imageType) PayloadPipelines() []string {
return t.payloadPipelines
}
func (t *imageType) PayloadPackageSets() []string {
return []string{blueprintPkgsKey}
}
func (t *imageType) PackageSetsChains() map[string][]string {
return nil
}
func (t *imageType) Exports() []string {
if len(t.exports) > 0 {
return t.exports
}
return []string{"assembler"}
}
func (t *imageType) BootMode() distro.BootMode {
if t.platform.GetUEFIVendor() != "" && t.platform.GetBIOSPlatform() != "" {
return distro.BOOT_HYBRID
} else if t.platform.GetUEFIVendor() != "" {
return distro.BOOT_UEFI
} else if t.platform.GetBIOSPlatform() != "" || t.platform.GetZiplSupport() {
return distro.BOOT_LEGACY
}
return distro.BOOT_NONE
}
func (t *imageType) getPartitionTable(
mountpoints []blueprint.FilesystemCustomization,
options distro.ImageOptions,
rng *rand.Rand,
) (*disk.PartitionTable, error) {
archName := t.arch.Name()
basePartitionTable, exists := t.basePartitionTables[archName]
if !exists {
return nil, fmt.Errorf("no partition table defined for architecture %q for image type %q", archName, t.Name())
}
imageSize := t.Size(options.Size)
lvmify := !t.rpmOstree
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, nil, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
// ensure that image always returns non-nil default config
imageConfig := t.defaultImageConfig
if imageConfig == nil {
imageConfig = &distro.ImageConfig{}
}
return imageConfig.InheritFrom(t.arch.distro.getDefaultImageConfig())
}
func (t *imageType) PartitionType() string {
archName := t.arch.Name()
basePartitionTable, exists := t.basePartitionTables[archName]
if !exists {
return ""
}
return basePartitionTable.Type
}
func (t *imageType) Manifest(bp *blueprint.Blueprint,
options distro.ImageOptions,
repos []rpmmd.RepoConfig,
seed int64) (*manifest.Manifest, []string, error) {
warnings, err := t.checkOptions(bp, options)
if err != nil {
return nil, nil, err
}
// merge package sets that appear in the image type with the package sets
// of the same name from the distro and arch
staticPackageSets := make(map[string]rpmmd.PackageSet)
for name, getter := range t.packageSets {
staticPackageSets[name] = getter(t)
}
// amend with repository information and collect payload repos
payloadRepos := make([]rpmmd.RepoConfig, 0)
for _, repo := range repos {
if len(repo.PackageSets) > 0 {
// only apply the repo to the listed package sets
for _, psName := range repo.PackageSets {
if slices.Contains(t.PayloadPackageSets(), psName) {
payloadRepos = append(payloadRepos, repo)
}
ps := staticPackageSets[psName]
ps.Repositories = append(ps.Repositories, repo)
staticPackageSets[psName] = ps
}
}
}
w := t.workload
if w == nil {
cw := &workload.Custom{
BaseWorkload: workload.BaseWorkload{
Repos: payloadRepos,
},
Packages: bp.GetPackagesEx(false),
}
if services := bp.Customizations.GetServices(); services != nil {
cw.Services = services.Enabled
cw.DisabledServices = services.Disabled
}
w = cw
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx := range bp.Containers {
containerSources[idx] = container.SourceSpec(bp.Containers[idx])
}
source := rand.NewSource(seed)
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(source)
if t.image == nil {
return nil, nil, nil
}
img, err := t.image(w, t, bp.Customizations, options, staticPackageSets, containerSources, rng)
if err != nil {
return nil, nil, err
}
mf := manifest.New()
mf.Distro = manifest.DISTRO_EL8
_, err = img.InstantiateManifest(&mf, repos, t.arch.distro.runner, rng)
if err != nil {
return nil, nil, err
}
return &mf, warnings, err
}
// checkOptions checks the validity and compatibility of options and customizations for the image type.
// Returns ([]string, error) where []string, if non-nil, will hold any generated warnings (e.g. deprecation notices).
func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOptions) ([]string, error) {
customizations := bp.Customizations
// holds warnings (e.g. deprecation notices)
var warnings []string
if t.workload != nil {
// For now, if an image type defines its own workload, don't allow any
// user customizations.
// Soon we will have more workflows and each will define its allowed
// set of customizations. The current set of customizations defined in
// the blueprint spec corresponds to the Custom workflow.
if customizations != nil {
return warnings, fmt.Errorf("image type %q does not support customizations", t.name)
}
}
// we do not support embedding containers on ostree-derived images, only on commits themselves
if len(bp.Containers) > 0 && t.rpmOstree && (t.name != "edge-commit" && t.name != "edge-container") {
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
ostreeURL := ""
if options.OSTree != nil {
if options.OSTree.ParentRef != "" && options.OSTree.URL == "" {
// specifying parent ref also requires URL
return nil, ostree.NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
}
ostreeURL = options.OSTree.URL
}
if t.bootISO && t.rpmOstree {
// ostree-based ISOs require a URL from which to pull a payload commit
if ostreeURL == "" {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
}
if t.name == "edge-simplified-installer" {
allowed := []string{"InstallationDevice", "FDO", "User", "Group"}
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, ", "))
}
if customizations.GetInstallationDevice() == "" {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying an installation device to install to", t.name)
}
//making fdo optional so that simplified installer can be composed w/o the FDO section in the blueprint
if customizations.GetFDO() != nil {
if customizations.GetFDO().ManufacturingServerURL == "" {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying FDO.ManufacturingServerURL configuration to install to", t.name)
}
var diunSet int
if customizations.GetFDO().DiunPubKeyHash != "" {
diunSet++
}
if customizations.GetFDO().DiunPubKeyInsecure != "" {
diunSet++
}
if customizations.GetFDO().DiunPubKeyRootCerts != "" {
diunSet++
}
if diunSet != 1 {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying one of [FDO.DiunPubKeyHash,FDO.DiunPubKeyInsecure,FDO.DiunPubKeyRootCerts] configuration to install to", t.name)
}
}
} else if t.name == "edge-installer" {
allowed := []string{"User", "Group"}
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, ", "))
}
}
}
if t.name == "edge-raw-image" {
// ostree-based bootable images require a URL from which to pull a payload commit
if ostreeURL == "" {
return warnings, fmt.Errorf("edge raw images require specifying a URL from which to retrieve the OSTree commit")
}
allowed := []string{"User", "Group"}
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, ", "))
}
// TODO: consider additional checks, such as those in "edge-simplified-installer"
}
// warn that user & group customizations on edge-commit, edge-container are deprecated
// TODO(edge): directly error if these options are provided when rhel-9.5's time arrives
if t.name == "edge-commit" || t.name == "edge-container" {
if customizations.GetUsers() != nil {
w := fmt.Sprintf("Please note that user customizations on %q image type are deprecated and will be removed in the near future\n", t.name)
log.Print(w)
warnings = append(warnings, w)
}
if customizations.GetGroups() != nil {
w := fmt.Sprintf("Please note that group customizations on %q image type are deprecated and will be removed in the near future\n", t.name)
log.Print(w)
warnings = append(warnings, w)
}
}
if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.rpmOstree && (!t.bootable || t.bootISO) {
return warnings, fmt.Errorf("kernel boot parameter customizations are not supported for ostree types")
}
mountpoints := customizations.GetFilesystems()
if mountpoints != nil && t.rpmOstree {
return warnings, fmt.Errorf("Custom mountpoints are not supported for ostree types")
}
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
if err != nil {
return warnings, err
}
if osc := customizations.GetOpenSCAP(); osc != nil {
// only add support for RHEL 8.7 and above.
if common.VersionLessThan(t.arch.distro.osVersion, "8.7") {
return warnings, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported os version: %s", t.arch.distro.osVersion))
}
supported := oscap.IsProfileAllowed(osc.ProfileID, oscapProfileAllowList)
if !supported {
return warnings, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported profile: %s", osc.ProfileID))
}
if t.rpmOstree {
return warnings, fmt.Errorf("OpenSCAP customizations are not supported for ostree types")
}
if osc.ProfileID == "" {
return warnings, fmt.Errorf("OpenSCAP profile cannot be empty")
}
}
// Check Directory/File Customizations are valid
dc := customizations.GetDirectories()
fc := customizations.GetFiles()
err = blueprint.ValidateDirFileCustomizations(dc, fc)
if err != nil {
return warnings, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
if err != nil {
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
if err != nil {
return warnings, err
}
// check if repository customizations are valid
_, err = customizations.GetRepositories()
if err != nil {
return warnings, err
}
return warnings, nil
}

View file

@ -1,75 +0,0 @@
package rhel8
// This file defines package sets that are used by more than one image type.
import (
"fmt"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
// installer boot package sets, needed for booting and
// also in the build host
func anacondaBootPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{}
grubCommon := rpmmd.PackageSet{
Include: []string{
"grub2-tools",
"grub2-tools-extra",
"grub2-tools-minimal",
},
}
efiCommon := rpmmd.PackageSet{
Include: []string{
"efibootmgr",
},
}
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(grubCommon)
ps = ps.Append(efiCommon)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"grub2-efi-ia32-cdboot",
"grub2-efi-x64",
"grub2-efi-x64-cdboot",
"grub2-pc",
"grub2-pc-modules",
"shim-ia32",
"shim-x64",
"syslinux",
"syslinux-nonlinux",
},
})
case platform.ARCH_AARCH64.String():
ps = ps.Append(grubCommon)
ps = ps.Append(efiCommon)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"grub2-efi-aa64-cdboot",
"grub2-efi-aa64",
"shim-aa64",
},
})
default:
panic(fmt.Sprintf("unsupported arch: %s", t.arch.Name()))
}
return ps
}
// packages that are only in some (sub)-distributions
func distroSpecificPackageSet(t *imageType) rpmmd.PackageSet {
if t.arch.distro.isRHEL() {
return rpmmd.PackageSet{
Include: []string{"insights-client"},
}
}
return rpmmd.PackageSet{}
}

View file

@ -1,425 +0,0 @@
package rhel8
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/platform"
)
var defaultBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 1 * common.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: 100 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 2 * common.GibiByte, // 2 GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 100 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 2 * common.GibiByte, // 2 GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_PPC64LE.String(): disk.PartitionTable{
UUID: "0x14fc63d2",
Type: "dos",
Partitions: []disk.Partition{
{
Size: 4 * common.MebiByte,
Type: "41",
Bootable: true,
},
{
Size: 2 * common.GibiByte, // 2 GiB
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_S390X.String(): disk.PartitionTable{
UUID: "0x14fc63d2",
Type: "dos",
Partitions: []disk.Partition{
{
Size: 2 * common.GibiByte, // 2 GiB
Bootable: true,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
}
var ec2BasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 1 * common.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: 200 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte,
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 200 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte,
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
}
// ec2LegacyBasePartitionTables is the partition table layout for RHEL EC2
// images prior to 8.9. It is used for backwards compatibility.
var ec2LegacyBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 1 * common.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: 2 * common.GibiByte, // 2 GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 200 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 512 * common.MebiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte, // 2 GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
}
var edgeBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 1 * common.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: 127 * common.MebiByte, // 127 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 384 * common.MebiByte, // 384 MB
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 1,
FSTabPassNo: 1,
},
},
{
Size: 2 * common.GibiByte, // 2 GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LUKSContainer{
Label: "crypt_root",
Cipher: "cipher_null",
Passphrase: "osbuild",
PBKDF: disk.Argon2id{
Memory: 32,
Iterations: 4,
Parallelism: 1,
},
Clevis: &disk.ClevisBind{
Pin: "null",
Policy: "{}",
RemovePassphrase: true,
},
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 127 * common.MebiByte, // 127 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 384 * common.MebiByte, // 384 MB
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 1,
FSTabPassNo: 1,
},
},
{
Size: 2 * common.GibiByte, // 2 GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LUKSContainer{
Label: "crypt_root",
Cipher: "cipher_null",
Passphrase: "osbuild",
PBKDF: disk.Argon2id{
Memory: 32,
Iterations: 4,
Parallelism: 1,
},
Clevis: &disk.ClevisBind{
Pin: "null",
Policy: "{}",
RemovePassphrase: true,
},
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
}

View file

@ -1,168 +0,0 @@
package rhel8
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
func qcow2ImgType(rd distribution) imageType {
it := imageType{
name: "qcow2",
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
kernelOptions: "console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0 crashkernel=auto",
packageSets: map[string]packageSetFunc{
osPkgsKey: qcow2CommonPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
DefaultTarget: common.ToPtr("multi-user.target"),
},
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "qcow2"},
exports: []string{"qcow2"},
basePartitionTables: defaultBasePartitionTables,
}
if rd.isRHEL() {
it.defaultImageConfig.RHSMConfig = map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{
ProductID: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
},
},
}
}
return it
}
func openstackImgType() imageType {
return imageType{
name: "openstack",
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
packageSets: map[string]packageSetFunc{
osPkgsKey: openstackCommonPackageSet,
},
kernelOptions: "ro net.ifnames=0",
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "qcow2"},
exports: []string{"qcow2"},
basePartitionTables: defaultBasePartitionTables,
}
}
func qcow2CommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@core",
"authselect-compat",
"chrony",
"cloud-init",
"cloud-utils-growpart",
"cockpit-system",
"cockpit-ws",
"dhcp-client",
"dnf",
"dnf-utils",
"dosfstools",
"dracut-norescue",
"net-tools",
"NetworkManager",
"nfs-utils",
"oddjob",
"oddjob-mkhomedir",
"psmisc",
"python3-jsonschema",
"qemu-guest-agent",
"redhat-release",
"redhat-release-eula",
"rsync",
"tar",
"tcpdump",
"yum",
},
Exclude: []string{
"aic94xx-firmware",
"alsa-firmware",
"alsa-lib",
"alsa-tools-firmware",
"biosdevname",
"dnf-plugin-spacewalk",
"dracut-config-rescue",
"fedora-release",
"fedora-repos",
"firewalld",
"fwupd",
"iprutils",
"ivtv-firmware",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"langpacks-*",
"langpacks-en",
"langpacks-en",
"libertas-sd8686-firmware",
"libertas-sd8787-firmware",
"libertas-usb8388-firmware",
"nss",
"plymouth",
"rng-tools",
"udisks2",
},
}.Append(distroSpecificPackageSet(t))
// Ensure to not pull in subscription-manager on non-RHEL distro
if t.arch.distro.isRHEL() {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"subscription-manager-cockpit",
},
})
}
return ps
}
func openstackCommonPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
// Defaults
"@Core", "langpacks-en",
// From the lorax kickstart
"selinux-policy-targeted", "cloud-init", "qemu-guest-agent",
"spice-vdagent",
},
Exclude: []string{
"dracut-config-rescue", "rng-tools",
},
}
}

View file

@ -1,174 +0,0 @@
package rhel8
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
// sapImageConfig returns the SAP specific ImageConfig data
func sapImageConfig(rd distribution) *distro.ImageConfig {
return &distro.ImageConfig{
SELinuxConfig: &osbuild.SELinuxConfigStageOptions{
State: osbuild.SELinuxStatePermissive,
},
// RHBZ#1960617
Tuned: osbuild.NewTunedStageOptions("sap-hana"),
// RHBZ#1959979
Tmpfilesd: []*osbuild.TmpfilesdStageOptions{
osbuild.NewTmpfilesdStageOptions("sap.conf",
[]osbuild.TmpfilesdConfigLine{
{
Type: "x",
Path: "/tmp/.sap*",
},
{
Type: "x",
Path: "/tmp/.hdb*lock",
},
{
Type: "x",
Path: "/tmp/.trex*lock",
},
},
),
},
// RHBZ#1959963
PamLimitsConf: []*osbuild.PamLimitsConfStageOptions{
osbuild.NewPamLimitsConfStageOptions("99-sap.conf",
[]osbuild.PamLimitsConfigLine{
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
},
),
},
// RHBZ#1959962
Sysctld: []*osbuild.SysctldStageOptions{
osbuild.NewSysctldStageOptions("sap.conf",
[]osbuild.SysctldConfigLine{
{
Key: "kernel.pid_max",
Value: "4194304",
},
{
Key: "vm.max_map_count",
Value: "2147483647",
},
},
),
},
// E4S/EUS
DNFConfig: []*osbuild.DNFConfigStageOptions{
osbuild.NewDNFConfigStageOptions(
[]osbuild.DNFVariable{
{
Name: "releasever",
Value: rd.osVersion,
},
},
nil,
),
},
}
}
func SapPackageSet(t *imageType) rpmmd.PackageSet {
packageSet := rpmmd.PackageSet{
Include: []string{
// RHBZ#2074107
"@Server",
// SAP System Roles
// https://access.redhat.com/sites/default/files/attachments/rhel_system_roles_for_sap_1.pdf
"rhel-system-roles-sap",
// RHBZ#1959813
"bind-utils",
"compat-sap-c++-9",
"compat-sap-c++-10", // RHBZ#2074114
"nfs-utils",
"tcsh",
// RHBZ#1959955
"uuidd",
// RHBZ#1959923
"cairo",
"expect",
"graphviz",
"gtk2",
"iptraf-ng",
"krb5-workstation",
"libaio",
"libatomic",
"libcanberra-gtk2",
"libicu",
"libpng12",
"libtool-ltdl",
"lm_sensors",
"net-tools",
"numactl",
"PackageKit-gtk3-module",
"xorg-x11-xauth",
// RHBZ#1960617
"tuned-profiles-sap-hana",
// RHBZ#1961168
"libnsl",
},
}
if common.VersionLessThan(t.arch.distro.osVersion, "8.6") {
packageSet = packageSet.Append(rpmmd.PackageSet{
Include: []string{"ansible"},
})
} else {
// 8.6+ and CS8 (image type does not exist on 8.5)
packageSet = packageSet.Append(rpmmd.PackageSet{
Include: []string{"ansible-core"}, // RHBZ#2077356
})
}
return packageSet
}

View file

@ -1,64 +0,0 @@
package rhel8
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
const vmdkKernelOptions = "ro net.ifnames=0"
func vmdkImgType() imageType {
return imageType{
name: "vmdk",
filename: "disk.vmdk",
mimeType: "application/x-vmdk",
packageSets: map[string]packageSetFunc{
osPkgsKey: vmdkCommonPackageSet,
},
kernelOptions: vmdkKernelOptions,
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vmdk"},
exports: []string{"vmdk"},
basePartitionTables: defaultBasePartitionTables,
}
}
func ovaImgType() imageType {
return imageType{
name: "ova",
filename: "image.ova",
mimeType: "application/ovf",
packageSets: map[string]packageSetFunc{
osPkgsKey: vmdkCommonPackageSet,
},
kernelOptions: vmdkKernelOptions,
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vmdk", "ovf", "archive"},
exports: []string{"archive"},
basePartitionTables: defaultBasePartitionTables,
}
}
func vmdkCommonPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"@core",
"chrony",
"cloud-init",
"firewalld",
"langpacks-en",
"open-vm-tools",
"selinux-policy-targeted",
},
Exclude: []string{
"dracut-config-rescue",
"rng-tools",
},
}
}

View file

@ -1,26 +0,0 @@
package rhel8
import "github.com/osbuild/osbuild-composer/internal/workload"
// rhel8Workload is a RHEL-8-specific implementation of the workload interface
// for internal workload variants.
type rhel8Workload struct {
workload.BaseWorkload
packages []string
}
func (w rhel8Workload) GetPackages() []string {
return w.packages
}
func eapWorkload() workload.Workload {
w := rhel8Workload{}
w.packages = []string{
"java-1.8.0-openjdk",
"java-1.8.0-openjdk-devel",
"eap7-wildfly",
"eap7-artemis-native-wildfly",
}
return &w
}

View file

@ -1,475 +0,0 @@
package rhel9
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
const amiKernelOptions = "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295"
var (
amiImgTypeX86_64 = imageType{
name: "ami",
filename: "image.raw",
mimeType: "application/octet-stream",
packageSets: map[string]packageSetFunc{
osPkgsKey: ec2CommonPackageSet,
},
kernelOptions: amiKernelOptions,
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image"},
exports: []string{"image"},
basePartitionTables: defaultBasePartitionTables,
}
ec2ImgTypeX86_64 = imageType{
name: "ec2",
filename: "image.raw.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: rhelEc2PackageSet,
},
kernelOptions: amiKernelOptions,
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: defaultBasePartitionTables,
}
ec2HaImgTypeX86_64 = imageType{
name: "ec2-ha",
filename: "image.raw.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
buildPkgsKey: ec2BuildPackageSet,
osPkgsKey: rhelEc2HaPackageSet,
},
kernelOptions: amiKernelOptions,
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: defaultBasePartitionTables,
}
amiImgTypeAarch64 = imageType{
name: "ami",
filename: "image.raw",
mimeType: "application/octet-stream",
packageSets: map[string]packageSetFunc{
buildPkgsKey: ec2BuildPackageSet,
osPkgsKey: ec2CommonPackageSet,
},
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image"},
exports: []string{"image"},
basePartitionTables: defaultBasePartitionTables,
}
ec2ImgTypeAarch64 = imageType{
name: "ec2",
filename: "image.raw.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
buildPkgsKey: ec2BuildPackageSet,
osPkgsKey: rhelEc2PackageSet,
},
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 iommu.strict=0",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: defaultBasePartitionTables,
}
ec2SapImgTypeX86_64 = imageType{
name: "ec2-sap",
filename: "image.raw.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
buildPkgsKey: ec2BuildPackageSet,
osPkgsKey: rhelEc2SapPackageSet,
},
kernelOptions: "console=ttyS0,115200n8 console=tty0 net.ifnames=0 rd.blacklist=nouveau nvme_core.io_timeout=4294967295 processor.max_cstate=1 intel_idle.max_cstate=1",
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: defaultBasePartitionTables,
}
)
// default EC2 images config (common for all architectures)
func baseEc2ImageConfig() *distro.ImageConfig {
return &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
Timezone: common.ToPtr("UTC"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{
{
Hostname: "169.254.169.123",
Prefer: common.ToPtr(true),
Iburst: common.ToPtr(true),
Minpoll: common.ToPtr(4),
Maxpoll: common.ToPtr(4),
},
},
// empty string will remove any occurrences of the option from the configuration
LeapsecTz: common.ToPtr(""),
},
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
X11Keymap: &osbuild.X11KeymapOptions{
Layouts: []string{"us"},
},
},
EnabledServices: []string{
"sshd",
"NetworkManager",
"nm-cloud-setup.service",
"nm-cloud-setup.timer",
"cloud-init",
"cloud-init-local",
"cloud-config",
"cloud-final",
"reboot.target",
"tuned",
},
DefaultTarget: common.ToPtr("multi-user.target"),
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
NetworkScripts: &osbuild.NetworkScriptsOptions{
IfcfgFiles: map[string]osbuild.IfcfgFile{
"eth0": {
Device: "eth0",
Bootproto: osbuild.IfcfgBootprotoDHCP,
OnBoot: common.ToPtr(true),
Type: osbuild.IfcfgTypeEthernet,
UserCtl: common.ToPtr(true),
PeerDNS: common.ToPtr(true),
IPv6Init: common.ToPtr(false),
},
},
},
},
},
SystemdLogind: []*osbuild.SystemdLogindStageOptions{
{
Filename: "00-getty-fixes.conf",
Config: osbuild.SystemdLogindConfigDropin{
Login: osbuild.SystemdLogindConfigLoginSection{
NAutoVTs: common.ToPtr(0),
},
},
},
},
CloudInit: []*osbuild.CloudInitStageOptions{
{
Filename: "00-rhel-default-user.cfg",
Config: osbuild.CloudInitConfigFile{
SystemInfo: &osbuild.CloudInitConfigSystemInfo{
DefaultUser: &osbuild.CloudInitConfigDefaultUser{
Name: "ec2-user",
},
},
},
},
},
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-nouveau.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("nouveau"),
},
},
{
Filename: "blacklist-amdgpu.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("amdgpu"),
},
},
},
// COMPOSER-1807
DracutConf: []*osbuild.DracutConfStageOptions{
{
Filename: "sgdisk.conf",
Config: osbuild.DracutConfigFile{
Install: []string{"sgdisk"},
},
},
},
SystemdUnit: []*osbuild.SystemdUnitStageOptions{
// RHBZ#1822863
{
Unit: "nm-cloud-setup.service",
Dropin: "10-rh-enable-for-ec2.conf",
Config: osbuild.SystemdServiceUnitDropin{
Service: &osbuild.SystemdUnitServiceSection{
Environment: "NM_CLOUD_SETUP_EC2=yes",
},
},
},
},
Authselect: &osbuild.AuthselectStageOptions{
Profile: "sssd",
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
PasswordAuthentication: common.ToPtr(false),
},
},
}
}
func defaultEc2ImageConfig(osVersion string, rhsm bool) *distro.ImageConfig {
ic := baseEc2ImageConfig()
if rhsm && common.VersionLessThan(osVersion, "9.1") {
ic = appendRHSM(ic)
// Disable RHSM redhat.repo management
rhsmConf := ic.RHSMConfig[subscription.RHSMConfigNoSubscription]
rhsmConf.SubMan.Rhsm = &osbuild.SubManConfigRHSMSection{ManageRepos: common.ToPtr(false)}
ic.RHSMConfig[subscription.RHSMConfigNoSubscription] = rhsmConf
}
return ic
}
// default AMI (EC2 BYOS) images config
func defaultAMIImageConfig(osVersion string, rhsm bool) *distro.ImageConfig {
ic := defaultEc2ImageConfig(osVersion, rhsm)
if rhsm {
// defaultAMIImageConfig() adds the rhsm options only for RHEL < 9.1
// Add it unconditionally for AMI
ic = appendRHSM(ic)
}
return ic
}
func defaultEc2ImageConfigX86_64(osVersion string, rhsm bool) *distro.ImageConfig {
ic := defaultEc2ImageConfig(osVersion, rhsm)
return appendEC2DracutX86_64(ic)
}
func defaultAMIImageConfigX86_64(osVersion string, rhsm bool) *distro.ImageConfig {
ic := defaultAMIImageConfig(osVersion, rhsm).InheritFrom(defaultEc2ImageConfigX86_64(osVersion, rhsm))
return appendEC2DracutX86_64(ic)
}
// common ec2 image build package set
func ec2BuildPackageSet(t *imageType) rpmmd.PackageSet {
return distroBuildPackageSet(t).Append(
rpmmd.PackageSet{
Include: []string{
"python3-pyyaml",
},
})
}
func ec2CommonPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"authselect-compat",
"chrony",
"cloud-init",
"cloud-utils-growpart",
"dhcp-client",
"yum-utils",
"dracut-config-generic",
"gdisk",
"grub2",
"langpacks-en",
"NetworkManager-cloud-setup",
"redhat-release",
"redhat-release-eula",
"rsync",
"tar",
},
Exclude: []string{
"aic94xx-firmware",
"alsa-firmware",
"alsa-tools-firmware",
"biosdevname",
"iprutils",
"ivtv-firmware",
"libertas-sd8787-firmware",
"plymouth",
// RHBZ#2064087
"dracut-config-rescue",
// RHBZ#2075815
"qemu-guest-agent",
},
}.Append(coreOsCommonPackageSet(t)).Append(distroSpecificPackageSet(t))
}
// common rhel ec2 RHUI image package set
func rhelEc2CommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := ec2CommonPackageSet(t)
// Include "redhat-cloud-client-configuration" on 9.1+ (COMPOSER-1805)
if !common.VersionLessThan(t.arch.distro.osVersion, "9.1") {
ps.Include = append(ps.Include, "redhat-cloud-client-configuration")
}
return ps
}
// rhel-ec2 image package set
func rhelEc2PackageSet(t *imageType) rpmmd.PackageSet {
ec2PackageSet := rhelEc2CommonPackageSet(t)
ec2PackageSet = ec2PackageSet.Append(rpmmd.PackageSet{
Include: []string{
"rh-amazon-rhui-client",
},
Exclude: []string{
"alsa-lib",
},
})
return ec2PackageSet
}
// rhel-ha-ec2 image package set
func rhelEc2HaPackageSet(t *imageType) rpmmd.PackageSet {
ec2HaPackageSet := rhelEc2CommonPackageSet(t)
ec2HaPackageSet = ec2HaPackageSet.Append(rpmmd.PackageSet{
Include: []string{
"fence-agents-all",
"pacemaker",
"pcs",
"rh-amazon-rhui-client-ha",
},
Exclude: []string{
"alsa-lib",
},
})
return ec2HaPackageSet
}
// rhel-sap-ec2 image package set
// Includes the common ec2 package set, the common SAP packages, and
// the amazon rhui sap package
func rhelEc2SapPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"rh-amazon-rhui-client-sap-bundle-e4s",
},
}.Append(rhelEc2CommonPackageSet(t)).Append(SapPackageSet(t))
}
func mkEc2ImgTypeX86_64(osVersion string, rhsm bool) imageType {
it := ec2ImgTypeX86_64
ic := defaultEc2ImageConfigX86_64(osVersion, rhsm)
it.defaultImageConfig = ic
return it
}
func mkAMIImgTypeX86_64(osVersion string, rhsm bool) imageType {
it := amiImgTypeX86_64
ic := defaultAMIImageConfigX86_64(osVersion, rhsm)
it.defaultImageConfig = ic
return it
}
func mkEC2SapImgTypeX86_64(osVersion string, rhsm bool) imageType {
it := ec2SapImgTypeX86_64
it.defaultImageConfig = sapImageConfig(osVersion).InheritFrom(defaultEc2ImageConfigX86_64(osVersion, rhsm))
return it
}
func mkEc2HaImgTypeX86_64(osVersion string, rhsm bool) imageType {
it := ec2HaImgTypeX86_64
ic := defaultEc2ImageConfigX86_64(osVersion, rhsm)
it.defaultImageConfig = ic
return it
}
func mkAMIImgTypeAarch64(osVersion string, rhsm bool) imageType {
it := amiImgTypeAarch64
ic := defaultAMIImageConfig(osVersion, rhsm)
it.defaultImageConfig = ic
return it
}
func mkEC2ImgTypeAarch64(osVersion string, rhsm bool) imageType {
it := ec2ImgTypeAarch64
ic := defaultEc2ImageConfig(osVersion, rhsm)
it.defaultImageConfig = ic
return it
}
// Add RHSM config options to ImageConfig.
// Used for RHEL distros.
func appendRHSM(ic *distro.ImageConfig) *distro.ImageConfig {
rhsm := &distro.ImageConfig{
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
// RHBZ#1932802
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// Don't disable RHSM redhat.repo management on the AMI
// image, which is BYOS and does not use RHUI for content.
// Otherwise subscribing the system manually after booting
// it would result in empty redhat.repo. Without RHUI, such
// system would have no way to get Red Hat content, but
// enable the repo management manually, which would be very
// confusing.
},
},
subscription.RHSMConfigWithSubscription: {
// RHBZ#1932802
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
}
return rhsm.InheritFrom(ic)
}
func appendEC2DracutX86_64(ic *distro.ImageConfig) *distro.ImageConfig {
ic.DracutConf = append(ic.DracutConf,
&osbuild.DracutConfStageOptions{
Filename: "ec2.conf",
Config: osbuild.DracutConfigFile{
AddDrivers: []string{
"nvme",
"xen-blkfront",
},
},
})
return ic
}

View file

@ -1,70 +0,0 @@
package rhel9
import (
"errors"
"fmt"
"sort"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/platform"
)
type architecture struct {
distro *distribution
name string
imageTypes map[string]distro.ImageType
imageTypeAliases map[string]string
}
func (a *architecture) Name() string {
return a.name
}
func (a *architecture) ListImageTypes() []string {
itNames := make([]string, 0, len(a.imageTypes))
for name := range a.imageTypes {
itNames = append(itNames, name)
}
sort.Strings(itNames)
return itNames
}
func (a *architecture) GetImageType(name string) (distro.ImageType, error) {
t, exists := a.imageTypes[name]
if !exists {
aliasForName, exists := a.imageTypeAliases[name]
if !exists {
return nil, errors.New("invalid image type: " + name)
}
t, exists = a.imageTypes[aliasForName]
if !exists {
panic(fmt.Sprintf("image type '%s' is an alias to a non-existing image type '%s'", name, aliasForName))
}
}
return t, nil
}
func (a *architecture) addImageTypes(platform platform.Platform, imageTypes ...imageType) {
if a.imageTypes == nil {
a.imageTypes = map[string]distro.ImageType{}
}
for idx := range imageTypes {
it := imageTypes[idx]
it.arch = a
it.platform = platform
a.imageTypes[it.name] = &it
for _, alias := range it.nameAliases {
if a.imageTypeAliases == nil {
a.imageTypeAliases = map[string]string{}
}
if existingAliasFor, exists := a.imageTypeAliases[alias]; exists {
panic(fmt.Sprintf("image type alias '%s' for '%s' is already defined for another image type '%s'", alias, it.name, existingAliasFor))
}
a.imageTypeAliases[alias] = it.name
}
}
}
func (a *architecture) Distro() distro.Distro {
return a.distro
}

View file

@ -1,596 +0,0 @@
package rhel9
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
var (
// Azure non-RHEL image type
azureImgType = imageType{
name: "vhd",
filename: "disk.vhd",
mimeType: "application/x-vhd",
packageSets: map[string]packageSetFunc{
osPkgsKey: azurePackageSet,
},
defaultImageConfig: defaultAzureImageConfig,
kernelOptions: defaultAzureKernelOptions,
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc"},
exports: []string{"vpc"},
basePartitionTables: defaultBasePartitionTables,
}
// Azure BYOS image type
azureByosImgType = imageType{
name: "vhd",
filename: "disk.vhd",
mimeType: "application/x-vhd",
packageSets: map[string]packageSetFunc{
osPkgsKey: azurePackageSet,
},
defaultImageConfig: defaultAzureByosImageConfig.InheritFrom(defaultAzureImageConfig),
kernelOptions: defaultAzureKernelOptions,
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc"},
exports: []string{"vpc"},
basePartitionTables: defaultBasePartitionTables,
}
// Azure RHUI image type
azureRhuiImgType = imageType{
name: "azure-rhui",
filename: "disk.vhd.xz",
mimeType: "application/xz",
compression: "xz",
packageSets: map[string]packageSetFunc{
osPkgsKey: azureRhuiPackageSet,
},
defaultImageConfig: defaultAzureRhuiImageConfig.InheritFrom(defaultAzureImageConfig),
kernelOptions: defaultAzureKernelOptions,
bootable: true,
defaultSize: 64 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vpc", "xz"},
exports: []string{"xz"},
basePartitionTables: azureRhuiBasePartitionTables,
}
)
// PACKAGE SETS
// Common Azure image package set
func azureCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"@Server",
"bzip2",
"cloud-init",
"cloud-utils-growpart",
"dracut-config-generic",
"efibootmgr",
"gdisk",
"hyperv-daemons",
"kernel-core",
"kernel-modules",
"kernel",
"langpacks-en",
"lvm2",
"NetworkManager",
"NetworkManager-cloud-setup",
"nvme-cli",
"patch",
"rng-tools",
"selinux-policy-targeted",
"uuid",
"WALinuxAgent",
"yum-utils",
},
Exclude: []string{
"aic94xx-firmware",
"alsa-firmware",
"alsa-lib",
"alsa-sof-firmware",
"alsa-tools-firmware",
"biosdevname",
"bolt",
"buildah",
"cockpit-podman",
"containernetworking-plugins",
"dnf-plugin-spacewalk",
"dracut-config-rescue",
"glibc-all-langpacks",
"iprutils",
"ivtv-firmware",
"iwl100-firmware",
"iwl1000-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"libertas-sd8686-firmware",
"libertas-sd8787-firmware",
"libertas-usb8388-firmware",
"NetworkManager-config-server",
"plymouth",
"podman",
"python3-dnf-plugin-spacewalk",
"python3-hwdata",
"python3-rhnlib",
"rhn-check",
"rhn-client-tools",
"rhn-setup",
"rhnlib",
"rhnsd",
"usb_modeswitch",
},
}.Append(distroSpecificPackageSet(t))
if t.arch.distro.isRHEL() {
ps.Append(rpmmd.PackageSet{
Include: []string{
"rhc",
},
})
}
return ps
}
// Azure BYOS image package set
func azurePackageSet(t *imageType) rpmmd.PackageSet {
return azureCommonPackageSet(t)
}
// Azure RHUI image package set
func azureRhuiPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"rhui-azure-rhel9",
},
}.Append(azureCommonPackageSet(t))
}
// PARTITION TABLES
var azureRhuiBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Size: 64 * common.GibiByte,
Partitions: []disk.Partition{
{
Size: 500 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.MebiByte,
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Type: disk.LVMPartitionGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 1 * common.GibiByte,
Name: "homelv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "home",
Mountpoint: "/home",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "tmplv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "tmp",
Mountpoint: "/tmp",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "usrlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "usr",
Mountpoint: "/usr",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "varlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "var",
Mountpoint: "/var",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Size: 64 * common.GibiByte,
Partitions: []disk.Partition{
{
Size: 500 * common.MebiByte,
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte,
Type: disk.FilesystemDataGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Type: disk.LVMPartitionGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 1 * common.GibiByte,
Name: "homelv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "home",
Mountpoint: "/home",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte,
Name: "tmplv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "tmp",
Mountpoint: "/tmp",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "usrlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "usr",
Mountpoint: "/usr",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 10 * common.GibiByte,
Name: "varlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "var",
Mountpoint: "/var",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
},
}
var defaultAzureKernelOptions = "ro console=tty1 console=ttyS0 earlyprintk=ttyS0 rootdelay=300"
var defaultAzureImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("Etc/UTC"),
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
X11Keymap: &osbuild.X11KeymapOptions{
Layouts: []string{"us"},
},
},
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel-core",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
},
},
EnabledServices: []string{
"firewalld",
"nm-cloud-setup.service",
"nm-cloud-setup.timer",
"sshd",
"waagent",
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
ClientAliveInterval: common.ToPtr(180),
},
},
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-amdgpu.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("amdgpu"),
},
},
{
Filename: "blacklist-floppy.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("floppy"),
},
},
{
Filename: "blacklist-nouveau.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("nouveau"),
osbuild.NewModprobeConfigCmdBlacklist("lbm-nouveau"),
},
},
},
CloudInit: []*osbuild.CloudInitStageOptions{
{
Filename: "10-azure-kvp.cfg",
Config: osbuild.CloudInitConfigFile{
Reporting: &osbuild.CloudInitConfigReporting{
Logging: &osbuild.CloudInitConfigReportingHandlers{
Type: "log",
},
Telemetry: &osbuild.CloudInitConfigReportingHandlers{
Type: "hyperv",
},
},
},
},
{
Filename: "91-azure_datasource.cfg",
Config: osbuild.CloudInitConfigFile{
Datasource: &osbuild.CloudInitConfigDatasource{
Azure: &osbuild.CloudInitConfigDatasourceAzure{
ApplyNetworkConfig: false,
},
},
DatasourceList: []string{
"Azure",
},
},
},
},
PwQuality: &osbuild.PwqualityConfStageOptions{
Config: osbuild.PwqualityConfConfig{
Minlen: common.ToPtr(6),
Minclass: common.ToPtr(3),
Dcredit: common.ToPtr(0),
Ucredit: common.ToPtr(0),
Lcredit: common.ToPtr(0),
Ocredit: common.ToPtr(0),
},
},
WAAgentConfig: &osbuild.WAAgentConfStageOptions{
Config: osbuild.WAAgentConfig{
RDFormat: common.ToPtr(false),
RDEnableSwap: common.ToPtr(false),
},
},
Grub2Config: &osbuild.GRUB2Config{
TerminalInput: []string{"serial", "console"},
TerminalOutput: []string{"serial", "console"},
Serial: "serial --speed=115200 --unit=0 --word=8 --parity=no --stop=1",
Timeout: 10,
},
UdevRules: &osbuild.UdevRulesStageOptions{
Filename: "/etc/udev/rules.d/68-azure-sriov-nm-unmanaged.rules",
Rules: osbuild.UdevRules{
osbuild.UdevRuleComment{
Comment: []string{
"Accelerated Networking on Azure exposes a new SRIOV interface to the VM.",
"This interface is transparently bonded to the synthetic interface,",
"so NetworkManager should just ignore any SRIOV interfaces.",
},
},
osbuild.NewUdevRule(
[]osbuild.UdevKV{
{K: "SUBSYSTEM", O: "==", V: "net"},
{K: "DRIVERS", O: "==", V: "hv_pci"},
{K: "ACTION", O: "==", V: "add"},
{K: "ENV", A: "NM_UNMANAGED", O: "=", V: "1"},
},
),
},
},
SystemdUnit: []*osbuild.SystemdUnitStageOptions{
{
Unit: "nm-cloud-setup.service",
Dropin: "10-rh-enable-for-azure.conf",
Config: osbuild.SystemdServiceUnitDropin{
Service: &osbuild.SystemdUnitServiceSection{
Environment: "NM_CLOUD_SETUP_AZURE=yes",
},
},
},
},
DefaultTarget: common.ToPtr("multi-user.target"),
}
// Diff of the default Image Config compare to the `defaultAzureImageConfig`
var defaultAzureByosImageConfig = &distro.ImageConfig{
GPGKeyFiles: []string{
"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
},
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// Don't disable RHSM redhat.repo management on the GCE
// image, which is BYOS and does not use RHUI for content.
// Otherwise subscribing the system manually after booting
// it would result in empty redhat.repo. Without RHUI, such
// system would have no way to get Red Hat content, but
// enable the repo management manually, which would be very
// confusing.
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
}
// Diff of the default Image Config compare to the `defaultAzureImageConfig`
var defaultAzureRhuiImageConfig = &distro.ImageConfig{
GPGKeyFiles: []string{
"/etc/pki/rpm-gpg/RPM-GPG-KEY-microsoft-azure-release",
"/etc/pki/rpm-gpg/RPM-GPG-KEY-redhat-release",
},
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{
SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
},
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
Rhsm: &osbuild.SubManConfigRHSMSection{
ManageRepos: common.ToPtr(false),
},
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
}

View file

@ -1,317 +0,0 @@
package rhel9
import (
"fmt"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
var (
tarImgType = imageType{
name: "tar",
filename: "root.tar.xz",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: func(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{"policycoreutils", "selinux-policy-targeted"},
Exclude: []string{"rng-tools"},
}
},
},
image: tarImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "archive"},
exports: []string{"archive"},
}
imageInstaller = imageType{
name: "image-installer",
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
osPkgsKey: bareMetalPackageSet,
installerPkgsKey: anacondaPackageSet,
},
rpmOstree: false,
bootISO: true,
bootable: true,
image: imageInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"anaconda-tree", "rootfs-image", "efiboot-tree", "os", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
}
)
func bareMetalPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"authselect-compat",
"chrony",
"cockpit-system",
"cockpit-ws",
"dhcp-client",
"dnf-utils",
"dosfstools",
"firewalld",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"lvm2",
"net-tools",
"nfs-utils",
"oddjob",
"oddjob-mkhomedir",
"policycoreutils",
"psmisc",
"python3-jsonschema",
"qemu-guest-agent",
"redhat-release",
"redhat-release-eula",
"rsync",
"tar",
"tcpdump",
},
}.Append(coreOsCommonPackageSet(t)).Append(distroBuildPackageSet(t))
// Ensure to not pull in subscription-manager on non-RHEL distro
if t.arch.distro.isRHEL() {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"subscription-manager-cockpit",
},
})
}
return ps
}
func installerPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"anaconda-dracut",
"curl",
"dracut-config-generic",
"dracut-network",
"hostname",
"iwl100-firmware",
"iwl1000-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"kernel",
"less",
"nfs-utils",
"openssh-clients",
"ostree",
"plymouth",
"prefixdevname",
"rng-tools",
"rpcbind",
"selinux-policy-targeted",
"systemd",
"tar",
"xfsprogs",
"xz",
},
}
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"biosdevname",
},
})
}
return ps
}
func anacondaPackageSet(t *imageType) rpmmd.PackageSet {
// common installer packages
ps := installerPackageSet(t)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"aajohan-comfortaa-fonts",
"abattis-cantarell-fonts",
"alsa-firmware",
"alsa-tools-firmware",
"anaconda",
"anaconda-dracut",
"anaconda-install-env-deps",
"anaconda-widgets",
"audit",
"bind-utils",
"bitmap-fangsongti-fonts",
"bzip2",
"cryptsetup",
"curl",
"dbus-x11",
"dejavu-sans-fonts",
"dejavu-sans-mono-fonts",
"device-mapper-persistent-data",
"dmidecode",
"dnf",
"dracut-config-generic",
"dracut-network",
"efibootmgr",
"ethtool",
"fcoe-utils",
"ftp",
"gdb-gdbserver",
"gdisk",
"glibc-all-langpacks",
"gnome-kiosk",
"google-noto-sans-cjk-ttc-fonts",
"grub2-tools",
"grub2-tools-extra",
"grub2-tools-minimal",
"grubby",
"gsettings-desktop-schemas",
"hdparm",
"hexedit",
"hostname",
"initscripts",
"ipmitool",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
"jomolhari-fonts",
"kacst-farsi-fonts",
"kacst-qurn-fonts",
"kbd",
"kbd-misc",
"kdump-anaconda-addon",
"kernel",
"khmeros-base-fonts",
"less",
"libblockdev-lvm-dbus",
"libibverbs",
"libreport-plugin-bugzilla",
"libreport-plugin-reportuploader",
"librsvg2",
"linux-firmware",
"lklug-fonts",
"lldpad",
"lohit-assamese-fonts",
"lohit-bengali-fonts",
"lohit-devanagari-fonts",
"lohit-gujarati-fonts",
"lohit-gurmukhi-fonts",
"lohit-kannada-fonts",
"lohit-odia-fonts",
"lohit-tamil-fonts",
"lohit-telugu-fonts",
"lsof",
"madan-fonts",
"mtr",
"mt-st",
"net-tools",
"nfs-utils",
"nmap-ncat",
"nm-connection-editor",
"nss-tools",
"openssh-clients",
"openssh-server",
"oscap-anaconda-addon",
"ostree",
"pciutils",
"perl-interpreter",
"pigz",
"plymouth",
"prefixdevname",
"python3-pyatspi",
"rdma-core",
"redhat-release-eula",
"rng-tools",
"rpcbind",
"rpm-ostree",
"rsync",
"rsyslog",
"selinux-policy-targeted",
"sg3_utils",
"sil-abyssinica-fonts",
"sil-padauk-fonts",
"sil-scheherazade-fonts",
"smartmontools",
"smc-meera-fonts",
"spice-vdagent",
"strace",
"systemd",
"tar",
"thai-scalable-waree-fonts",
"tigervnc-server-minimal",
"tigervnc-server-module",
"udisks2",
"udisks2-iscsi",
"usbutils",
"vim-minimal",
"volume_key",
"wget",
"xfsdump",
"xfsprogs",
"xorg-x11-drivers",
"xorg-x11-fonts-misc",
"xorg-x11-server-utils",
"xorg-x11-server-Xorg",
"xorg-x11-xauth",
"xz",
},
})
ps = ps.Append(anacondaBootPackageSet(t))
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"biosdevname",
"dmidecode",
"grub2-tools-efi",
"memtest86+",
},
})
case platform.ARCH_AARCH64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"dmidecode",
},
})
default:
panic(fmt.Sprintf("unsupported arch: %s", t.arch.Name()))
}
return ps
}

View file

@ -1,463 +0,0 @@
package rhel9
import (
"errors"
"fmt"
"sort"
"strings"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/runner"
)
var (
// rhel9 & cs9 share the same list
// of allowed profiles so a single
// allow list can be used
oscapProfileAllowList = []oscap.Profile{
oscap.AnssiBp28Enhanced,
oscap.AnssiBp28High,
oscap.AnssiBp28Intermediary,
oscap.AnssiBp28Minimal,
oscap.Cis,
oscap.CisServerL1,
oscap.CisWorkstationL1,
oscap.CisWorkstationL2,
oscap.Cui,
oscap.E8,
oscap.Hippa,
oscap.IsmO,
oscap.Ospp,
oscap.PciDss,
oscap.Stig,
oscap.StigGui,
}
)
type distribution struct {
name string
product string
osVersion string
releaseVersion string
modulePlatformID string
vendor string
ostreeRefTmpl string
isolabelTmpl string
runner runner.Runner
arches map[string]distro.Arch
defaultImageConfig *distro.ImageConfig
}
// CentOS- and RHEL-based OS image configuration defaults
var defaultDistroImageConfig = &distro.ImageConfig{
Timezone: common.ToPtr("America/New_York"),
Locale: common.ToPtr("C.UTF-8"),
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
UpdateDefault: true,
DefaultKernel: "kernel",
},
Network: &osbuild.SysconfigNetworkOptions{
Networking: true,
NoZeroConf: true,
},
},
},
}
func (d *distribution) Name() string {
return d.name
}
func (d *distribution) Releasever() string {
return d.releaseVersion
}
func (d *distribution) ModulePlatformID() string {
return d.modulePlatformID
}
func (d *distribution) OSTreeRef() string {
return d.ostreeRefTmpl
}
func (d *distribution) ListArches() []string {
archNames := make([]string, 0, len(d.arches))
for name := range d.arches {
archNames = append(archNames, name)
}
sort.Strings(archNames)
return archNames
}
func (d *distribution) GetArch(name string) (distro.Arch, error) {
arch, exists := d.arches[name]
if !exists {
return nil, errors.New("invalid architecture: " + name)
}
return arch, nil
}
func (d *distribution) addArches(arches ...architecture) {
if d.arches == nil {
d.arches = map[string]distro.Arch{}
}
// Do not make copies of architectures, as opposed to image types,
// because architecture definitions are not used by more than a single
// distro definition.
for idx := range arches {
d.arches[arches[idx].name] = &arches[idx]
}
}
func (d *distribution) isRHEL() bool {
return strings.HasPrefix(d.name, "rhel")
}
func (d *distribution) getDefaultImageConfig() *distro.ImageConfig {
return d.defaultImageConfig
}
func New() distro.Distro {
// default minor: create default minor version (current GA) and rename it
d := newDistro("rhel", 1)
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 newDistro(name string, minor int) *distribution {
var rd distribution
switch name {
case "rhel":
rd = distribution{
name: fmt.Sprintf("rhel-9%d", minor),
product: "Red Hat Enterprise Linux",
osVersion: fmt.Sprintf("9.%d", minor),
releaseVersion: "9",
modulePlatformID: "platform:el9",
vendor: "redhat",
ostreeRefTmpl: "rhel/9/%s/edge",
isolabelTmpl: fmt.Sprintf("RHEL-9-%d-0-BaseOS-%%s", minor),
runner: &runner.RHEL{Major: uint64(9), Minor: uint64(minor)},
defaultImageConfig: defaultDistroImageConfig,
}
case "centos":
rd = distribution{
name: "centos-9",
product: "CentOS Stream",
osVersion: "9-stream",
releaseVersion: "9",
modulePlatformID: "platform:el9",
vendor: "centos",
ostreeRefTmpl: "centos/9/%s/edge",
isolabelTmpl: "CentOS-Stream-9-BaseOS-%s",
runner: &runner.CentOS{Version: uint64(9)},
defaultImageConfig: defaultDistroImageConfig,
}
default:
panic(fmt.Sprintf("unknown distro name: %s", name))
}
// Architecture definitions
x86_64 := architecture{
name: platform.ARCH_X86_64.String(),
distro: &rd,
}
aarch64 := architecture{
name: platform.ARCH_AARCH64.String(),
distro: &rd,
}
ppc64le := architecture{
distro: &rd,
name: platform.ARCH_PPC64LE.String(),
}
s390x := architecture{
distro: &rd,
name: platform.ARCH_S390X.String(),
}
qcow2ImgType := mkQcow2ImgType(rd)
ociImgType := qcow2ImgType
ociImgType.name = "oci"
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "1.1",
},
},
qcow2ImgType,
ociImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
},
},
openstackImgType,
)
azureX64Platform := &platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VHD,
},
}
azureAarch64Platform := &platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VHD,
},
}
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_VMDK,
},
},
vmdkImgType,
)
x86_64.addImageTypes(
&platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_OVA,
},
},
ovaImgType,
)
ec2X86Platform := &platform.X86{
BIOS: true,
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
}
x86_64.addImageTypes(
ec2X86Platform,
mkAMIImgTypeX86_64(rd.osVersion, rd.isRHEL()),
)
gceX86Platform := &platform.X86{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_GCE,
},
}
x86_64.addImageTypes(
gceX86Platform,
mkGCEImageType(rd.isRHEL()),
)
x86_64.addImageTypes(
&platform.X86{
BasePlatform: platform.BasePlatform{
FirmwarePackages: []string{
"microcode_ctl", // ??
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6050-firmware",
},
},
BIOS: true,
UEFIVendor: rd.vendor,
},
edgeOCIImgType,
edgeCommitImgType,
edgeInstallerImgType,
edgeRawImgType,
imageInstaller,
edgeAMIImgType,
)
x86_64.addImageTypes(
&platform.X86{
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
BIOS: false,
UEFIVendor: rd.vendor,
},
edgeSimplifiedInstallerImgType,
)
x86_64.addImageTypes(
&platform.X86{},
tarImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
},
},
openstackImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{},
tarImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
BasePlatform: platform.BasePlatform{},
UEFIVendor: rd.vendor,
},
edgeCommitImgType,
edgeOCIImgType,
edgeInstallerImgType,
edgeSimplifiedInstallerImgType,
imageInstaller,
edgeAMIImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
UEFIVendor: rd.vendor,
},
edgeRawImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "1.1",
},
},
qcow2ImgType,
)
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
},
mkAMIImgTypeAarch64(rd.osVersion, rd.isRHEL()),
)
ppc64le.addImageTypes(
&platform.PPC64LE{
BIOS: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "1.1",
},
},
qcow2ImgType,
)
ppc64le.addImageTypes(
&platform.PPC64LE{},
tarImgType,
)
s390x.addImageTypes(
&platform.S390X{
Zipl: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_QCOW2,
QCOW2Compat: "1.1",
},
},
qcow2ImgType,
)
s390x.addImageTypes(
&platform.S390X{},
tarImgType,
)
if rd.isRHEL() {
// add azure to RHEL distro only
x86_64.addImageTypes(azureX64Platform, azureRhuiImgType, azureByosImgType)
aarch64.addImageTypes(azureAarch64Platform, azureRhuiImgType, azureByosImgType)
// keep the RHEL EC2 x86_64 images before 9.3 BIOS-only for backward compatibility
if common.VersionLessThan(rd.osVersion, "9.3") {
ec2X86Platform = &platform.X86{
BIOS: true,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
}
}
// add ec2 image types to RHEL distro only
x86_64.addImageTypes(ec2X86Platform, mkEc2ImgTypeX86_64(rd.osVersion, rd.isRHEL()), mkEc2HaImgTypeX86_64(rd.osVersion, rd.isRHEL()), mkEC2SapImgTypeX86_64(rd.osVersion, rd.isRHEL()))
aarch64.addImageTypes(
&platform.Aarch64{
UEFIVendor: rd.vendor,
BasePlatform: platform.BasePlatform{
ImageFormat: platform.FORMAT_RAW,
},
},
mkEC2ImgTypeAarch64(rd.osVersion, rd.isRHEL()),
)
// add GCE RHUI image to RHEL only
x86_64.addImageTypes(gceX86Platform, mkGCERHUIImageType(rd.isRHEL()))
} else {
x86_64.addImageTypes(azureX64Platform, azureImgType)
aarch64.addImageTypes(azureAarch64Platform, azureImgType)
}
rd.addArches(x86_64, aarch64, ppc64le, s390x)
return &rd
}

View file

@ -1,848 +0,0 @@
package rhel9_test
import (
"fmt"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distro/distro_test_common"
"github.com/osbuild/osbuild-composer/internal/distro/rhel9"
"github.com/osbuild/osbuild-composer/internal/platform"
)
type rhelFamilyDistro struct {
name string
distro distro.Distro
}
var rhelFamilyDistros = []rhelFamilyDistro{
{
name: "rhel",
distro: rhel9.New(),
},
}
func TestFilenameFromType(t *testing.T) {
type args struct {
outputFormat string
}
type wantResult struct {
filename string
mimeType string
wantErr bool
}
tests := []struct {
name string
args args
want wantResult
}{
{
name: "ami",
args: args{"ami"},
want: wantResult{
filename: "image.raw",
mimeType: "application/octet-stream",
},
},
{
name: "ec2",
args: args{"ec2"},
want: wantResult{
filename: "image.raw.xz",
mimeType: "application/xz",
},
},
{
name: "ec2-ha",
args: args{"ec2-ha"},
want: wantResult{
filename: "image.raw.xz",
mimeType: "application/xz",
},
},
{
name: "ec2-sap",
args: args{"ec2-sap"},
want: wantResult{
filename: "image.raw.xz",
mimeType: "application/xz",
},
},
{
name: "qcow2",
args: args{"qcow2"},
want: wantResult{
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
},
},
{
name: "openstack",
args: args{"openstack"},
want: wantResult{
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
},
},
{
name: "vhd",
args: args{"vhd"},
want: wantResult{
filename: "disk.vhd",
mimeType: "application/x-vhd",
},
},
{
name: "azure-rhui",
args: args{"azure-rhui"},
want: wantResult{
filename: "disk.vhd.xz",
mimeType: "application/xz",
},
},
{
name: "vmdk",
args: args{"vmdk"},
want: wantResult{
filename: "disk.vmdk",
mimeType: "application/x-vmdk",
},
},
{
name: "ova",
args: args{"ova"},
want: wantResult{
filename: "image.ova",
mimeType: "application/ovf",
},
},
{
name: "tar",
args: args{"tar"},
want: wantResult{
filename: "root.tar.xz",
mimeType: "application/x-tar",
},
},
{
name: "image-installer",
args: args{"image-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{
name: "edge-commit",
args: args{"edge-commit"},
want: wantResult{
filename: "commit.tar",
mimeType: "application/x-tar",
},
},
// Alias
{
name: "rhel-edge-commit",
args: args{"rhel-edge-commit"},
want: wantResult{
filename: "commit.tar",
mimeType: "application/x-tar",
},
},
{
name: "edge-container",
args: args{"edge-container"},
want: wantResult{
filename: "container.tar",
mimeType: "application/x-tar",
},
},
// Alias
{
name: "rhel-edge-container",
args: args{"rhel-edge-container"},
want: wantResult{
filename: "container.tar",
mimeType: "application/x-tar",
},
},
{
name: "edge-installer",
args: args{"edge-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
// Alias
{
name: "rhel-edge-installer",
args: args{"rhel-edge-installer"},
want: wantResult{
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
},
},
{
name: "gce",
args: args{"gce"},
want: wantResult{
filename: "image.tar.gz",
mimeType: "application/gzip",
},
},
{
name: "edge-ami",
args: args{"edge-ami"},
want: wantResult{
filename: "image.raw",
mimeType: "application/octet-stream",
},
},
{
name: "invalid-output-type",
args: args{"foobar"},
want: wantResult{wantErr: true},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dist := dist.distro
arch, _ := dist.GetArch("x86_64")
imgType, err := arch.GetImageType(tt.args.outputFormat)
if (err != nil) != tt.want.wantErr {
t.Errorf("Arch.GetImageType() error = %v, wantErr %v", err, tt.want.wantErr)
return
}
if !tt.want.wantErr {
gotFilename := imgType.Filename()
gotMIMEType := imgType.MIMEType()
if gotFilename != tt.want.filename {
t.Errorf("ImageType.Filename() got = %v, want %v", gotFilename, tt.want.filename)
}
if gotMIMEType != tt.want.mimeType {
t.Errorf("ImageType.MIMEType() got1 = %v, want %v", gotMIMEType, tt.want.mimeType)
}
}
})
}
})
}
}
func TestImageType_BuildPackages(t *testing.T) {
x8664BuildPackages := []string{
"dnf",
"dosfstools",
"e2fsprogs",
"grub2-efi-x64",
"grub2-pc",
"policycoreutils",
"shim-x64",
"systemd",
"tar",
"qemu-img",
"xz",
}
aarch64BuildPackages := []string{
"dnf",
"dosfstools",
"e2fsprogs",
"policycoreutils",
"qemu-img",
"systemd",
"tar",
"xz",
}
buildPackages := map[string][]string{
"x86_64": x8664BuildPackages,
"aarch64": aarch64BuildPackages,
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
d := dist.distro
for _, archLabel := range d.ListArches() {
archStruct, err := d.GetArch(archLabel)
if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) {
continue
}
for _, itLabel := range archStruct.ListImageTypes() {
itStruct, err := archStruct.GetImageType(itLabel)
if assert.NoErrorf(t, err, "d.GetArch(%v) returned err = %v; expected nil", archLabel, err) {
continue
}
manifest, _, err := itStruct.Manifest(&blueprint.Blueprint{}, distro.ImageOptions{}, nil, 0)
assert.NoError(t, err)
buildPkgs := manifest.GetPackageSetChains()["build"]
assert.NotNil(t, buildPkgs)
assert.Len(t, buildPkgs, 1)
assert.ElementsMatch(t, buildPackages[archLabel], buildPkgs[0].Include)
}
}
})
}
}
func TestImageType_Name(t *testing.T) {
imgMap := []struct {
arch string
imgNames []string
}{
{
arch: "x86_64",
imgNames: []string{
"qcow2",
"openstack",
"vhd",
"azure-rhui",
"vmdk",
"ova",
"ami",
"ec2",
"ec2-ha",
"ec2-sap",
"edge-commit",
"edge-container",
"edge-installer",
"gce",
"tar",
"image-installer",
},
},
{
arch: "aarch64",
imgNames: []string{
"qcow2",
"openstack",
"ami",
"ec2",
"edge-commit",
"edge-container",
"tar",
"image-installer",
"vhd",
"azure-rhui",
},
},
{
arch: "ppc64le",
imgNames: []string{
"qcow2",
"tar",
},
},
{
arch: "s390x",
imgNames: []string{
"qcow2",
"tar",
},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, mapping := range imgMap {
if mapping.arch == platform.ARCH_S390X.String() && dist.name == "centos" {
continue
}
arch, err := dist.distro.GetArch(mapping.arch)
if assert.NoError(t, err) {
for _, imgName := range mapping.imgNames {
if imgName == "edge-commit" && dist.name == "centos" {
continue
}
imgType, err := arch.GetImageType(imgName)
if assert.NoError(t, err) {
assert.Equalf(t, imgName, imgType.Name(), "arch: %s", mapping.arch)
}
}
}
}
})
}
}
func TestImageTypeAliases(t *testing.T) {
type args struct {
imageTypeAliases []string
}
type wantResult struct {
imageTypeName string
}
tests := []struct {
name string
args args
want wantResult
}{
{
name: "edge-commit aliases",
args: args{
imageTypeAliases: []string{"rhel-edge-commit"},
},
want: wantResult{
imageTypeName: "edge-commit",
},
},
{
name: "edge-container aliases",
args: args{
imageTypeAliases: []string{"rhel-edge-container"},
},
want: wantResult{
imageTypeName: "edge-container",
},
},
{
name: "edge-installer aliases",
args: args{
imageTypeAliases: []string{"rhel-edge-installer"},
},
want: wantResult{
imageTypeName: "edge-installer",
},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dist := dist.distro
for _, archName := range dist.ListArches() {
t.Run(archName, func(t *testing.T) {
arch, err := dist.GetArch(archName)
require.Nilf(t, err,
"failed to get architecture '%s', previously listed as supported for the distro '%s'",
archName, dist.Name())
// Test image type aliases only if the aliased image type is supported for the arch
if _, err = arch.GetImageType(tt.want.imageTypeName); err != nil {
t.Skipf("aliased image type '%s' is not supported for architecture '%s'",
tt.want.imageTypeName, archName)
}
for _, alias := range tt.args.imageTypeAliases {
t.Run(fmt.Sprintf("'%s' alias for image type '%s'", alias, tt.want.imageTypeName),
func(t *testing.T) {
gotImage, err := arch.GetImageType(alias)
require.Nilf(t, err, "arch.GetImageType() for image type alias '%s' failed: %v",
alias, err)
assert.Equalf(t, tt.want.imageTypeName, gotImage.Name(),
"got unexpected image type name for alias '%s'. got = %s, want = %s",
alias, tt.want.imageTypeName, gotImage.Name())
})
}
})
}
})
}
})
}
}
// Check that Manifest() function returns an error for unsupported
// configurations.
func TestDistro_ManifestError(t *testing.T) {
// Currently, the only unsupported configuration is OSTree commit types
// with Kernel boot options
r9distro := rhel9.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Kernel: &blueprint.KernelCustomization{
Append: "debug",
},
},
}
for _, archName := range r9distro.ListArches() {
arch, _ := r9distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
imgOpts := distro.ImageOptions{
Size: imgType.Size(0),
}
_, _, err := imgType.Manifest(&bp, imgOpts, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "kernel boot parameter customizations are not supported for ostree types")
} else if imgTypeName == "edge-raw-image" || imgTypeName == "edge-ami" {
assert.EqualError(t, err, fmt.Sprintf("\"%s\" images require specifying a URL from which to retrieve the OSTree commit", imgTypeName))
} else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" {
assert.EqualError(t, err, fmt.Sprintf("boot ISO image type \"%s\" requires specifying a URL from which to retrieve the OSTree commit", imgTypeName))
} else {
assert.NoError(t, err)
}
}
}
}
func TestArchitecture_ListImageTypes(t *testing.T) {
imgMap := []struct {
arch string
imgNames []string
rhelAdditionalImageTypes []string
}{
{
arch: "x86_64",
imgNames: []string{
"qcow2",
"openstack",
"vhd",
"azure-rhui",
"vmdk",
"ova",
"ami",
"ec2",
"ec2-ha",
"ec2-sap",
"edge-commit",
"edge-container",
"edge-installer",
"edge-raw-image",
"edge-simplified-installer",
"edge-ami",
"gce",
"gce-rhui",
"tar",
"image-installer",
"oci",
},
},
{
arch: "aarch64",
imgNames: []string{
"qcow2",
"openstack",
"ami",
"ec2",
"edge-commit",
"edge-container",
"edge-installer",
"edge-simplified-installer",
"edge-raw-image",
"edge-ami",
"tar",
"image-installer",
"vhd",
"azure-rhui",
},
},
{
arch: "ppc64le",
imgNames: []string{
"qcow2",
"tar",
},
},
{
arch: "s390x",
imgNames: []string{
"qcow2",
"tar",
},
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, mapping := range imgMap {
arch, err := dist.distro.GetArch(mapping.arch)
require.NoError(t, err)
imageTypes := arch.ListImageTypes()
var expectedImageTypes []string
expectedImageTypes = append(expectedImageTypes, mapping.imgNames...)
if dist.name == "rhel" {
expectedImageTypes = append(expectedImageTypes, mapping.rhelAdditionalImageTypes...)
}
require.ElementsMatch(t, expectedImageTypes, imageTypes)
}
})
}
}
func TestRhel9_ListArches(t *testing.T) {
arches := rhel9.New().ListArches()
assert.Equal(t, []string{"aarch64", "ppc64le", "s390x", "x86_64"}, arches)
}
func TestRhel9_GetArch(t *testing.T) {
arches := []struct {
name string
errorExpected bool
errorExpectedInCentos bool
}{
{
name: "x86_64",
},
{
name: "aarch64",
},
{
name: "ppc64le",
},
{
name: "s390x",
},
{
name: "foo-arch",
errorExpected: true,
},
}
for _, dist := range rhelFamilyDistros {
t.Run(dist.name, func(t *testing.T) {
for _, a := range arches {
actualArch, err := dist.distro.GetArch(a.name)
if a.errorExpected || (a.errorExpectedInCentos && dist.name == "centos") {
assert.Nil(t, actualArch)
assert.Error(t, err)
} else {
assert.Equal(t, a.name, actualArch.Name())
assert.NoError(t, err)
}
}
})
}
}
func TestRhel9_Name(t *testing.T) {
distro := rhel9.New()
assert.Equal(t, "rhel-9", distro.Name())
}
func TestRhel9_ModulePlatformID(t *testing.T) {
distro := rhel9.New()
assert.Equal(t, "platform:el9", distro.ModulePlatformID())
}
func TestRhel9_KernelOption(t *testing.T) {
distro_test_common.TestDistro_KernelOption(t, rhel9.New())
}
func TestRhel9_OSTreeOptions(t *testing.T) {
distro_test_common.TestDistro_OSTreeOptions(t, rhel9.New())
}
func TestDistro_CustomFileSystemManifestError(t *testing.T) {
r9distro := rhel9.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/etc",
},
},
},
}
for _, archName := range r9distro.ListArches() {
arch, _ := r9distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" || imgTypeName == "edge-ami" {
continue
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/etc\"]")
}
}
}
}
func TestDistro_TestRootMountPoint(t *testing.T) {
r9distro := rhel9.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/",
},
},
},
}
for _, archName := range r9distro.ListArches() {
arch, _ := r9distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" || imgTypeName == "edge-ami" {
continue
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_CustomFileSystemSubDirectories(t *testing.T) {
r9distro := rhel9.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/var/log",
},
{
MinSize: 1024,
Mountpoint: "/var/log/audit",
},
},
},
}
for _, archName := range r9distro.ListArches() {
arch, _ := r9distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if strings.HasPrefix(imgTypeName, "edge-") {
continue
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_MountpointsWithArbitraryDepthAllowed(t *testing.T) {
r9distro := rhel9.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/var/a",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b/c",
},
{
MinSize: 1024,
Mountpoint: "/var/a/b/c/d",
},
},
},
}
for _, archName := range r9distro.ListArches() {
arch, _ := r9distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if strings.HasPrefix(imgTypeName, "edge-") {
continue
} else {
assert.NoError(t, err)
}
}
}
}
func TestDistro_DirtyMountpointsNotAllowed(t *testing.T) {
r9distro := rhel9.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "//",
},
{
MinSize: 1024,
Mountpoint: "/var//",
},
{
MinSize: 1024,
Mountpoint: "/var//log/audit/",
},
},
},
}
for _, archName := range r9distro.ListArches() {
arch, _ := r9distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if strings.HasPrefix(imgTypeName, "edge-") {
continue
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"//\" \"/var//\" \"/var//log/audit/\"]")
}
}
}
}
func TestDistro_CustomFileSystemPatternMatching(t *testing.T) {
r9distro := rhel9.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/variable",
},
{
MinSize: 1024,
Mountpoint: "/variable/log/audit",
},
},
},
}
for _, archName := range r9distro.ListArches() {
arch, _ := r9distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" || imgTypeName == "edge-ami" {
continue
} else {
assert.EqualError(t, err, "The following custom mountpoints are not supported [\"/variable\" \"/variable/log/audit\"]")
}
}
}
}
func TestDistro_CustomUsrPartitionNotLargeEnough(t *testing.T) {
r9distro := rhel9.New()
bp := blueprint.Blueprint{
Customizations: &blueprint.Customizations{
Filesystem: []blueprint.FilesystemCustomization{
{
MinSize: 1024,
Mountpoint: "/usr",
},
},
},
}
for _, archName := range r9distro.ListArches() {
arch, _ := r9distro.GetArch(archName)
for _, imgTypeName := range arch.ListImageTypes() {
imgType, _ := arch.GetImageType(imgTypeName)
_, _, err := imgType.Manifest(&bp, distro.ImageOptions{}, nil, 0)
if imgTypeName == "edge-commit" || imgTypeName == "edge-container" {
assert.EqualError(t, err, "Custom mountpoints are not supported for ostree types")
} else if imgTypeName == "edge-installer" || imgTypeName == "edge-simplified-installer" || imgTypeName == "edge-raw-image" || imgTypeName == "edge-ami" {
continue
} else {
assert.NoError(t, err)
}
}
}
}

View file

@ -1,511 +0,0 @@
package rhel9
import (
"fmt"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
var (
// Image Definitions
edgeCommitImgType = imageType{
name: "edge-commit",
nameAliases: []string{"rhel-edge-commit"},
filename: "commit.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: edgeCommitPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: edgeServices,
},
rpmOstree: true,
image: edgeCommitImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "ostree-commit", "commit-archive"},
exports: []string{"commit-archive"},
}
edgeOCIImgType = imageType{
name: "edge-container",
nameAliases: []string{"rhel-edge-container"},
filename: "container.tar",
mimeType: "application/x-tar",
packageSets: map[string]packageSetFunc{
osPkgsKey: edgeCommitPackageSet,
containerPkgsKey: func(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{"nginx"}, // FIXME: this has no effect
}
},
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: edgeServices,
},
rpmOstree: true,
bootISO: false,
image: edgeContainerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "ostree-commit", "container-tree", "container"},
exports: []string{"container"},
}
edgeRawImgType = imageType{
name: "edge-raw-image",
nameAliases: []string{"rhel-edge-raw-image"},
filename: "image.raw.xz",
compression: "xz",
mimeType: "application/xz",
packageSets: nil,
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
},
defaultSize: 10 * common.GibiByte,
rpmOstree: true,
bootable: true,
bootISO: false,
image: edgeRawImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"ostree-deployment", "image", "xz"},
exports: []string{"xz"},
basePartitionTables: edgeBasePartitionTables,
}
edgeInstallerImgType = imageType{
name: "edge-installer",
nameAliases: []string{"rhel-edge-installer"},
filename: "installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
// TODO: non-arch-specific package set handling for installers
// This image type requires build packages for installers and
// ostree/edge. For now we only have x86-64 installer build
// package sets defined. When we add installer build package sets
// for other architectures, this will need to be moved to the
// architecture and the merging will happen in the PackageSets()
// method like the other sets.
installerPkgsKey: edgeInstallerPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
EnabledServices: edgeServices,
},
rpmOstree: true,
bootISO: true,
image: edgeInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"anaconda-tree", "rootfs-image", "efiboot-tree", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
}
edgeSimplifiedInstallerImgType = imageType{
name: "edge-simplified-installer",
nameAliases: []string{"rhel-edge-simplified-installer"},
filename: "simplified-installer.iso",
mimeType: "application/x-iso9660-image",
packageSets: map[string]packageSetFunc{
// TODO: non-arch-specific package set handling for installers
// This image type requires build packages for installers and
// ostree/edge. For now we only have x86-64 installer build
// package sets defined. When we add installer build package sets
// for other architectures, this will need to be moved to the
// architecture and the merging will happen in the PackageSets()
// method like the other sets.
installerPkgsKey: edgeSimplifiedInstallerPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
EnabledServices: edgeServices,
},
defaultSize: 10 * common.GibiByte,
rpmOstree: true,
bootable: true,
bootISO: true,
image: edgeSimplifiedInstallerImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"ostree-deployment", "image", "xz", "coi-tree", "efiboot-tree", "bootiso-tree", "bootiso"},
exports: []string{"bootiso"},
basePartitionTables: edgeBasePartitionTables,
}
edgeAMIImgType = imageType{
name: "edge-ami",
filename: "image.raw",
mimeType: "application/octet-stream",
packageSets: nil,
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
},
defaultSize: 10 * common.GibiByte,
rpmOstree: true,
bootable: true,
bootISO: false,
image: edgeRawImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"ostree-deployment", "image"},
exports: []string{"image"},
basePartitionTables: edgeBasePartitionTables,
environment: &environment.EC2{},
}
// Shared Services
edgeServices = []string{
// TODO(runcom): move fdo-client-linuxapp.service to presets?
"NetworkManager.service", "firewalld.service", "sshd.service", "fdo-client-linuxapp.service",
}
// Partition tables
edgeBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 1 * common.MebiByte, // 1MB
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: 127 * common.MebiByte, // 127 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 384 * common.MebiByte, // 384 MB
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 1,
FSTabPassNo: 1,
},
},
{
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LUKSContainer{
Label: "crypt_root",
Cipher: "cipher_null",
Passphrase: "osbuild",
PBKDF: disk.Argon2id{
Memory: 32,
Iterations: 4,
Parallelism: 1,
},
Clevis: &disk.ClevisBind{
Pin: "null",
Policy: "{}",
RemovePassphrase: true,
},
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 9 * 1024 * 1024 * 1024, // 9 GB
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 127 * common.MebiByte, // 127 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 384 * common.MebiByte, // 384 MB
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 1,
FSTabPassNo: 1,
},
},
{
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.LUKSContainer{
Label: "crypt_root",
Cipher: "cipher_null",
Passphrase: "osbuild",
PBKDF: disk.Argon2id{
Memory: 32,
Iterations: 4,
Parallelism: 1,
},
Clevis: &disk.ClevisBind{
Pin: "null",
Policy: "{}",
RemovePassphrase: true,
},
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "built with lvm2 and osbuild",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Size: 9 * 1024 * 1024 * 1024, // 9 GB
Name: "rootlv",
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
},
},
},
},
}
)
// Package Sets
// edge commit OS package set
func edgeCommitPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"redhat-release",
"glibc",
"glibc-minimal-langpack",
"nss-altfiles",
"dracut-config-generic",
"dracut-network",
"basesystem",
"bash",
"platform-python",
"shadow-utils",
"chrony",
"setup",
"shadow-utils",
"sudo",
"systemd",
"coreutils",
"util-linux",
"curl",
"vim-minimal",
"rpm",
"rpm-ostree",
"polkit",
"lvm2",
"cryptsetup",
"pinentry",
"e2fsprogs",
"dosfstools",
"keyutils",
"gnupg2",
"attr",
"xz",
"gzip",
"firewalld",
"iptables",
"NetworkManager",
"NetworkManager-wifi",
"NetworkManager-wwan",
"wpa_supplicant",
"dnsmasq",
"traceroute",
"hostname",
"iproute",
"iputils",
"openssh-clients",
"procps-ng",
"rootfiles",
"openssh-server",
"passwd",
"policycoreutils",
"policycoreutils-python-utils",
"selinux-policy-targeted",
"setools-console",
"less",
"tar",
"rsync",
"usbguard",
"bash-completion",
"tmux",
"ima-evm-utils",
"audit",
"podman",
"containernetworking-plugins", // required for cni networks but not a hard dependency of podman >= 4.2.0 (rhbz#2123210)
"container-selinux",
"skopeo",
"criu",
"slirp4netns",
"fuse-overlayfs",
"clevis",
"clevis-dracut",
"clevis-luks",
"greenboot",
"greenboot-default-health-checks",
"fdo-client",
"fdo-owner-cli",
"sos",
},
Exclude: []string{
"rng-tools",
},
}
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(x8664EdgeCommitPackageSet(t))
case platform.ARCH_AARCH64.String():
ps = ps.Append(aarch64EdgeCommitPackageSet(t))
}
if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || !common.VersionLessThan(t.arch.distro.osVersion, "9-stream") {
ps.Include = append(ps.Include, "ignition", "ignition-edge", "ssh-key-dir")
}
return ps
}
func x8664EdgeCommitPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"grub2",
"grub2-efi-x64",
"efibootmgr",
"shim-x64",
"microcode_ctl",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
},
}
}
func aarch64EdgeCommitPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"grub2-efi-aa64",
"efibootmgr",
"shim-aa64",
"iwl7260-firmware",
},
}
}
func edgeInstallerPackageSet(t *imageType) rpmmd.PackageSet {
return anacondaPackageSet(t)
}
func edgeSimplifiedInstallerPackageSet(t *imageType) rpmmd.PackageSet {
// common installer packages
ps := installerPackageSet(t)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"attr",
"basesystem",
"binutils",
"bsdtar",
"clevis-dracut",
"clevis-luks",
"cloud-utils-growpart",
"coreos-installer",
"coreos-installer-dracut",
"coreutils",
"device-mapper-multipath",
"dnsmasq",
"dosfstools",
"dracut-live",
"e2fsprogs",
"fcoe-utils",
"fdo-init",
"gzip",
"ima-evm-utils",
"iproute",
"iptables",
"iputils",
"iscsi-initiator-utils",
"keyutils",
"lldpad",
"lvm2",
"passwd",
"policycoreutils",
"policycoreutils-python-utils",
"procps-ng",
"redhat-logos",
"rootfiles",
"setools-console",
"sudo",
"traceroute",
"util-linux",
},
})
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(x8664EdgeCommitPackageSet(t))
case platform.ARCH_AARCH64.String():
ps = ps.Append(aarch64EdgeCommitPackageSet(t))
default:
panic(fmt.Sprintf("unsupported arch: %s", t.arch.Name()))
}
return ps
}

View file

@ -1,300 +0,0 @@
package rhel9
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
const gceKernelOptions = "net.ifnames=0 biosdevname=0 scsi_mod.use_blk_mq=Y console=ttyS0,38400n8d"
var (
gceImgType = imageType{
name: "gce",
filename: "image.tar.gz",
mimeType: "application/gzip",
packageSets: map[string]packageSetFunc{
osPkgsKey: gcePackageSet,
},
kernelOptions: gceKernelOptions,
bootable: true,
defaultSize: 20 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "archive"},
exports: []string{"archive"},
// TODO: the base partition table still contains the BIOS boot partition, but the image is UEFI-only
basePartitionTables: defaultBasePartitionTables,
}
gceRhuiImgType = imageType{
name: "gce-rhui",
filename: "image.tar.gz",
mimeType: "application/gzip",
packageSets: map[string]packageSetFunc{
osPkgsKey: gceRhuiPackageSet,
},
kernelOptions: gceKernelOptions,
bootable: true,
defaultSize: 20 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "archive"},
exports: []string{"archive"},
// TODO: the base partition table still contains the BIOS boot partition, but the image is UEFI-only
basePartitionTables: defaultBasePartitionTables,
}
)
func mkGCEImageType(rhsm bool) imageType {
it := gceImgType
it.defaultImageConfig = baseGCEImageConfig(rhsm)
return it
}
func mkGCERHUIImageType(rhsm bool) imageType {
it := gceRhuiImgType
it.defaultImageConfig = defaultGceRhuiImageConfig(rhsm)
return it
}
func baseGCEImageConfig(rhsm bool) *distro.ImageConfig {
ic := &distro.ImageConfig{
Timezone: common.ToPtr("UTC"),
TimeSynchronization: &osbuild.ChronyStageOptions{
Servers: []osbuild.ChronyConfigServer{{Hostname: "metadata.google.internal"}},
},
Firewall: &osbuild.FirewallStageOptions{
DefaultZone: "trusted",
},
EnabledServices: []string{
"sshd",
"rngd",
"dnf-automatic.timer",
},
DisabledServices: []string{
"sshd-keygen@",
"reboot.target",
},
DefaultTarget: common.ToPtr("multi-user.target"),
Locale: common.ToPtr("en_US.UTF-8"),
Keyboard: &osbuild.KeymapStageOptions{
Keymap: "us",
},
DNFConfig: []*osbuild.DNFConfigStageOptions{
{
Config: &osbuild.DNFConfig{
Main: &osbuild.DNFConfigMain{
IPResolve: "4",
},
},
},
},
DNFAutomaticConfig: &osbuild.DNFAutomaticConfigStageOptions{
Config: &osbuild.DNFAutomaticConfig{
Commands: &osbuild.DNFAutomaticConfigCommands{
ApplyUpdates: common.ToPtr(true),
UpgradeType: osbuild.DNFAutomaticUpgradeTypeSecurity,
},
},
},
YUMRepos: []*osbuild.YumReposStageOptions{
{
Filename: "google-cloud.repo",
Repos: []osbuild.YumRepository{
{
Id: "google-compute-engine",
Name: "Google Compute Engine",
BaseURLs: []string{"https://packages.cloud.google.com/yum/repos/google-compute-engine-el9-x86_64-stable"},
Enabled: common.ToPtr(true),
// TODO: enable GPG check once Google stops using SHA-1 in their keys
// https://issuetracker.google.com/issues/223626963
GPGCheck: common.ToPtr(false),
RepoGPGCheck: common.ToPtr(false),
GPGKey: []string{
"https://packages.cloud.google.com/yum/doc/yum-key.gpg",
"https://packages.cloud.google.com/yum/doc/rpm-package-key.gpg",
},
},
},
},
},
SshdConfig: &osbuild.SshdConfigStageOptions{
Config: osbuild.SshdConfigConfig{
PasswordAuthentication: common.ToPtr(false),
ClientAliveInterval: common.ToPtr(420),
PermitRootLogin: osbuild.PermitRootLoginValueNo,
},
},
Sysconfig: []*osbuild.SysconfigStageOptions{
{
Kernel: &osbuild.SysconfigKernelOptions{
DefaultKernel: "kernel-core",
UpdateDefault: true,
},
},
},
Modprobe: []*osbuild.ModprobeStageOptions{
{
Filename: "blacklist-floppy.conf",
Commands: osbuild.ModprobeConfigCmdList{
osbuild.NewModprobeConfigCmdBlacklist("floppy"),
},
},
},
GCPGuestAgentConfig: &osbuild.GcpGuestAgentConfigOptions{
ConfigScope: osbuild.GcpGuestAgentConfigScopeDistro,
Config: &osbuild.GcpGuestAgentConfig{
InstanceSetup: &osbuild.GcpGuestAgentConfigInstanceSetup{
SetBotoConfig: common.ToPtr(false),
},
},
},
}
if rhsm {
ic.RHSMConfig = map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// Don't disable RHSM redhat.repo management on the GCE
// image, which is BYOS and does not use RHUI for content.
// Otherwise subscribing the system manually after booting
// it would result in empty redhat.repo. Without RHUI, such
// system would have no way to get Red Hat content, but
// enable the repo management manually, which would be very
// confusing.
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
}
}
return ic
}
func defaultGceRhuiImageConfig(rhsm bool) *distro.ImageConfig {
ic := &distro.ImageConfig{
RHSMConfig: map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
Rhsm: &osbuild.SubManConfigRHSMSection{
ManageRepos: common.ToPtr(false),
},
},
},
subscription.RHSMConfigWithSubscription: {
SubMan: &osbuild.RHSMStageOptionsSubMan{
Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{
AutoRegistration: common.ToPtr(true),
},
// do not disable the redhat.repo management if the user
// explicitly request the system to be subscribed
},
},
},
}
return ic.InheritFrom(baseGCEImageConfig(rhsm))
}
func gceCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"langpacks-en", // not in Google's KS
"acpid",
"dhcp-client",
"dnf-automatic",
"net-tools",
//"openssh-server", included in core
"python3",
"rng-tools",
"tar",
"vim",
// GCE guest tools
"google-compute-engine",
"google-osconfig-agent",
"gce-disk-expand",
// Not explicitly included in GCP kickstart, but present on the image
// for time synchronization
"chrony",
"timedatex",
// EFI
"grub2-tools-efi",
"firewalld", // not pulled in any more as on RHEL-8
},
Exclude: []string{
"alsa-utils",
"b43-fwcutter",
"dmraid",
"eject",
"gpm",
"irqbalance",
"microcode_ctl",
"smartmontools",
"aic94xx-firmware",
"atmel-firmware",
"b43-openfwwf",
"bfa-firmware",
"ipw2100-firmware",
"ipw2200-firmware",
"ivtv-firmware",
"iwl100-firmware",
"iwl1000-firmware",
"iwl3945-firmware",
"iwl4965-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000-firmware",
"iwl6000g2a-firmware",
"iwl6050-firmware",
"kernel-firmware",
"libertas-usb8388-firmware",
"ql2100-firmware",
"ql2200-firmware",
"ql23xx-firmware",
"ql2400-firmware",
"ql2500-firmware",
"rt61pci-firmware",
"rt73usb-firmware",
"xorg-x11-drv-ati-firmware",
"zd1211-firmware",
// RHBZ#2075815
"qemu-guest-agent",
},
}.Append(coreOsCommonPackageSet(t)).Append(distroSpecificPackageSet(t))
// Some excluded packages are part of the @core group package set returned
// by coreOsCommonPackageSet(). Ensure that the conflicting packages are
// returned from the list of `Include` packages.
return ps.ResolveConflictsExclude()
}
// GCE BYOS image
func gcePackageSet(t *imageType) rpmmd.PackageSet {
return gceCommonPackageSet(t)
}
// GCE RHUI image
func gceRhuiPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"google-rhui-client-rhel9",
},
}.Append(gceCommonPackageSet(t))
}

View file

@ -1,627 +0,0 @@
package rhel9
import (
"fmt"
"math/rand"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/fdo"
"github.com/osbuild/osbuild-composer/internal/ignition"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/users"
"github.com/osbuild/osbuild-composer/internal/workload"
)
func osCustomizations(
t *imageType,
osPackageSet rpmmd.PackageSet,
options distro.ImageOptions,
containers []container.SourceSpec,
c *blueprint.Customizations,
) manifest.OSCustomizations {
imageConfig := t.getDefaultImageConfig()
osc := manifest.OSCustomizations{}
if t.bootable || t.rpmOstree {
osc.KernelName = c.GetKernel().Name
var kernelOptions []string
if t.kernelOptions != "" {
kernelOptions = append(kernelOptions, t.kernelOptions)
}
if bpKernel := c.GetKernel(); bpKernel.Append != "" {
kernelOptions = append(kernelOptions, bpKernel.Append)
}
osc.KernelOptionsAppend = kernelOptions
}
osc.ExtraBasePackages = osPackageSet.Include
osc.ExcludeBasePackages = osPackageSet.Exclude
osc.ExtraBaseRepos = osPackageSet.Repositories
osc.Containers = containers
osc.GPGKeyFiles = imageConfig.GPGKeyFiles
if imageConfig.ExcludeDocs != nil {
osc.ExcludeDocs = *imageConfig.ExcludeDocs
}
if !t.bootISO {
// don't put users and groups in the payload of an installer
// add them via kickstart instead
osc.Groups = users.GroupsFromBP(c.GetGroups())
osc.Users = users.UsersFromBP(c.GetUsers())
}
osc.EnabledServices = imageConfig.EnabledServices
osc.DisabledServices = imageConfig.DisabledServices
if imageConfig.DefaultTarget != nil {
osc.DefaultTarget = *imageConfig.DefaultTarget
}
osc.Firewall = imageConfig.Firewall
if fw := c.GetFirewall(); fw != nil {
options := osbuild.FirewallStageOptions{
Ports: fw.Ports,
}
if fw.Services != nil {
options.EnabledServices = fw.Services.Enabled
options.DisabledServices = fw.Services.Disabled
}
if fw.Zones != nil {
for _, z := range fw.Zones {
options.Zones = append(options.Zones, osbuild.FirewallZone{
Name: *z.Name,
Sources: z.Sources,
})
}
}
osc.Firewall = &options
}
language, keyboard := c.GetPrimaryLocale()
if language != nil {
osc.Language = *language
} else if imageConfig.Locale != nil {
osc.Language = *imageConfig.Locale
}
if keyboard != nil {
osc.Keyboard = keyboard
} else if imageConfig.Keyboard != nil {
osc.Keyboard = &imageConfig.Keyboard.Keymap
if imageConfig.Keyboard.X11Keymap != nil {
osc.X11KeymapLayouts = imageConfig.Keyboard.X11Keymap.Layouts
}
}
if hostname := c.GetHostname(); hostname != nil {
osc.Hostname = *hostname
}
timezone, ntpServers := c.GetTimezoneSettings()
if timezone != nil {
osc.Timezone = *timezone
} else if imageConfig.Timezone != nil {
osc.Timezone = *imageConfig.Timezone
}
if len(ntpServers) > 0 {
for _, server := range ntpServers {
osc.NTPServers = append(osc.NTPServers, osbuild.ChronyConfigServer{Hostname: server})
}
} else if imageConfig.TimeSynchronization != nil {
osc.NTPServers = imageConfig.TimeSynchronization.Servers
osc.LeapSecTZ = imageConfig.TimeSynchronization.LeapsecTz
}
// Relabel the tree, unless the `NoSElinux` flag is explicitly set to `true`
if imageConfig.NoSElinux == nil || imageConfig.NoSElinux != nil && !*imageConfig.NoSElinux {
osc.SElinux = "targeted"
}
if oscapConfig := c.GetOpenSCAP(); oscapConfig != nil {
if t.rpmOstree {
panic("unexpected oscap options for ostree image type")
}
var datastream = oscapConfig.DataStream
if datastream == "" {
datastream = oscap.DefaultRHEL9Datastream(t.arch.distro.isRHEL())
}
osc.OpenSCAPConfig = osbuild.NewOscapRemediationStageOptions(
osbuild.OscapConfig{
Datastream: datastream,
ProfileID: oscapConfig.ProfileID,
},
)
}
if t.arch.distro.isRHEL() && options.Facts != nil {
osc.FactAPIType = &options.Facts.APIType
}
var err error
osc.Directories, err = blueprint.DirectoryCustomizationsToFsNodeDirectories(c.GetDirectories())
if err != nil {
// In theory this should never happen, because the blueprint directory customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert directory customizations to fs node directories: %v", err))
}
osc.Files, err = blueprint.FileCustomizationsToFsNodeFiles(c.GetFiles())
if err != nil {
// In theory this should never happen, because the blueprint file customizations
// should have been validated before this point.
panic(fmt.Sprintf("failed to convert file customizations to fs node files: %v", err))
}
// set yum repos first, so it doesn't get overridden by
// imageConfig.YUMRepos
osc.YUMRepos = imageConfig.YUMRepos
customRepos, err := c.GetRepositories()
if err != nil {
// This shouldn't happen and since the repos
// should have already been validated
panic(fmt.Sprintf("failed to get custom repos: %v", err))
}
// This function returns a map of filename and corresponding yum repos
// and a list of fs node files for the inline gpg keys so we can save
// them to disk. This step also swaps the inline gpg key with the path
// to the file in the os file tree
yumRepos, gpgKeyFiles, err := blueprint.RepoCustomizationsToRepoConfigAndGPGKeyFiles(customRepos)
if err != nil {
panic(fmt.Sprintf("failed to convert inline gpgkeys to fs node files: %v", err))
}
// add the gpg key files to the list of files to be added to the tree
if len(gpgKeyFiles) > 0 {
osc.Files = append(osc.Files, gpgKeyFiles...)
}
for filename, repos := range yumRepos {
osc.YUMRepos = append(osc.YUMRepos, osbuild.NewYumReposStageOptions(filename, repos))
}
osc.ShellInit = imageConfig.ShellInit
osc.Grub2Config = imageConfig.Grub2Config
osc.Sysconfig = imageConfig.Sysconfig
osc.SystemdLogind = imageConfig.SystemdLogind
osc.CloudInit = imageConfig.CloudInit
osc.Modprobe = imageConfig.Modprobe
osc.DracutConf = imageConfig.DracutConf
osc.SystemdUnit = imageConfig.SystemdUnit
osc.Authselect = imageConfig.Authselect
osc.SELinuxConfig = imageConfig.SELinuxConfig
osc.Tuned = imageConfig.Tuned
osc.Tmpfilesd = imageConfig.Tmpfilesd
osc.PamLimitsConf = imageConfig.PamLimitsConf
osc.Sysctld = imageConfig.Sysctld
osc.DNFConfig = imageConfig.DNFConfig
osc.DNFAutomaticConfig = imageConfig.DNFAutomaticConfig
osc.SshdConfig = imageConfig.SshdConfig
osc.AuthConfig = imageConfig.Authconfig
osc.PwQuality = imageConfig.PwQuality
osc.RHSMConfig = imageConfig.RHSMConfig
osc.Subscription = options.Subscription
osc.WAAgentConfig = imageConfig.WAAgentConfig
osc.UdevRules = imageConfig.UdevRules
osc.GCPGuestAgentConfig = imageConfig.GCPGuestAgentConfig
return osc
}
func liveImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewLiveImage()
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.Environment = t.environment
img.Workload = workload
img.Compression = t.compression
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
return img, nil
}
func edgeCommitImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
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)
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.Environment = t.environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = t.arch.distro.osVersion
img.Filename = t.Filename()
if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || t.arch.distro.osVersion == "9-stream" {
img.OSCustomizations.EnabledServices = append(img.OSCustomizations.EnabledServices, "ignition-firstboot-complete.service", "coreos-ignition-write-issues.service")
}
img.Environment = t.environment
img.Workload = workload
img.OSVersion = t.arch.distro.osVersion
img.Filename = t.Filename()
return img, nil
}
func edgeContainerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
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.NewOSTreeContainer(commitRef)
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.ContainerLanguage = img.OSCustomizations.Language
img.Environment = t.environment
img.Workload = workload
img.OSTreeParent = parentCommit
img.OSVersion = t.arch.distro.osVersion
img.ExtraContainerPackages = packageSets[containerPkgsKey]
img.Filename = t.Filename()
if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || t.arch.distro.osVersion == "9-stream" {
img.OSCustomizations.EnabledServices = append(img.OSCustomizations.EnabledServices, "ignition-firstboot-complete.service", "coreos-ignition-write-issues.service")
}
return img, nil
}
func edgeInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
d := t.arch.distro
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
img := image.NewAnacondaOSTreeInstaller(commit)
img.Platform = t.platform
img.ExtraBasePackages = packageSets[installerPkgsKey]
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
img.SquashfsCompression = "xz"
img.AdditionalDracutModules = []string{
"nvdimm", // non-volatile DIMM firmware (provides nfit, cuse, and nd_e820)
"prefixdevname",
"prefixdevname-tools",
}
img.AdditionalDrivers = []string{"cuse", "ipmi_devintf", "ipmi_msghandler"}
if len(img.Users)+len(img.Groups) > 0 {
// only enable the users module if needed
img.AdditionalAnacondaModules = []string{"org.fedoraproject.Anaconda.Modules.Users"}
}
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.Variant = "edge"
img.OSName = "rhel"
img.OSVersion = d.osVersion
img.Release = fmt.Sprintf("%s %s", d.product, d.osVersion)
img.Filename = t.Filename()
return img, nil
}
func edgeRawImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
img := image.NewOSTreeRawImage(commit)
if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || t.arch.distro.osVersion == "9-stream" {
img.Ignition = true
}
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
// "rw" kernel option is required when /sysroot is mounted read-only to
// keep stateful parts of the filesystem writeable (/var/ and /etc)
img.KernelOptionsAppend = []string{"modprobe.blacklist=vc4"}
img.Keyboard = "us"
img.Locale = "C.UTF-8"
if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || t.arch.distro.osVersion == "9-stream" {
img.SysrootReadOnly = true
img.KernelOptionsAppend = append(img.KernelOptionsAppend, "rw")
}
img.Platform = t.platform
img.Workload = workload
img.Remote = ostree.Remote{
Name: "rhel-edge",
URL: options.OSTree.URL,
ContentURL: options.OSTree.ContentURL,
}
img.OSName = "redhat"
if bpIgnition := customizations.GetIgnition(); bpIgnition != nil && bpIgnition.FirstBoot != nil && bpIgnition.FirstBoot.ProvisioningURL != "" {
img.KernelOptionsAppend = append(img.KernelOptionsAppend, "ignition.config.url="+bpIgnition.FirstBoot.ProvisioningURL)
}
// 92+ only
if kopts := customizations.GetKernel(); kopts != nil && kopts.Append != "" {
img.KernelOptionsAppend = append(img.KernelOptionsAppend, kopts.Append)
}
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
img.PartitionTable = pt
img.Filename = t.Filename()
img.Compression = t.compression
return img, nil
}
func edgeSimplifiedInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
commit, err := makeOSTreePayloadCommit(options.OSTree, t.OSTreeRef())
if err != nil {
return nil, fmt.Errorf("%s: %s", t.Name(), err.Error())
}
rawImg := image.NewOSTreeRawImage(commit)
if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || t.arch.distro.osVersion == "9-stream" {
rawImg.Ignition = true
}
rawImg.Users = users.UsersFromBP(customizations.GetUsers())
rawImg.Groups = users.GroupsFromBP(customizations.GetGroups())
// "rw" kernel option is required when /sysroot is mounted read-only to
// keep stateful parts of the filesystem writeable (/var/ and /etc)
rawImg.KernelOptionsAppend = []string{"modprobe.blacklist=vc4"}
rawImg.Keyboard = "us"
rawImg.Locale = "C.UTF-8"
if !common.VersionLessThan(t.arch.distro.osVersion, "9.2") || t.arch.distro.osVersion == "9-stream" {
rawImg.SysrootReadOnly = true
rawImg.KernelOptionsAppend = append(rawImg.KernelOptionsAppend, "rw")
}
rawImg.Platform = t.platform
rawImg.Workload = workload
rawImg.Remote = ostree.Remote{
Name: "rhel-edge",
URL: options.OSTree.URL,
ContentURL: options.OSTree.ContentURL,
}
rawImg.OSName = "redhat"
// TODO: move generation into LiveImage
pt, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
rawImg.PartitionTable = pt
rawImg.Filename = t.Filename()
if bpIgnition := customizations.GetIgnition(); bpIgnition != nil && bpIgnition.FirstBoot != nil && bpIgnition.FirstBoot.ProvisioningURL != "" {
rawImg.KernelOptionsAppend = append(rawImg.KernelOptionsAppend, "ignition.config.url="+bpIgnition.FirstBoot.ProvisioningURL)
}
// 92+ only
if kopts := customizations.GetKernel(); kopts != nil && kopts.Append != "" {
rawImg.KernelOptionsAppend = append(rawImg.KernelOptionsAppend, kopts.Append)
}
img := image.NewOSTreeSimplifiedInstaller(rawImg, customizations.InstallationDevice)
img.ExtraBasePackages = packageSets[installerPkgsKey]
// img.Workload = workload
img.Platform = t.platform
img.Filename = t.Filename()
if bpFDO := customizations.GetFDO(); bpFDO != nil {
img.FDO = fdo.FromBP(*bpFDO)
}
// ignition configs from blueprint
if bpIgnition := customizations.GetIgnition(); bpIgnition != nil {
if bpIgnition.Embedded != nil {
var err error
img.IgnitionEmbedded, err = ignition.EmbeddedOptionsFromBP(*bpIgnition.Embedded)
if err != nil {
return nil, err
}
}
}
d := t.arch.distro
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.Variant = "edge"
img.OSName = "redhat"
img.OSVersion = d.osVersion
img.AdditionalDracutModules = []string{"prefixdevname", "prefixdevname-tools"}
return img, nil
}
func imageInstallerImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewAnacondaTarInstaller()
img.Platform = t.platform
img.Workload = workload
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.ExtraBasePackages = packageSets[installerPkgsKey]
img.Users = users.UsersFromBP(customizations.GetUsers())
img.Groups = users.GroupsFromBP(customizations.GetGroups())
img.AdditionalDracutModules = []string{
"nvdimm", // non-volatile DIMM firmware (provides nfit, cuse, and nd_e820)
"prefixdevname",
"prefixdevname-tools",
}
img.AdditionalDrivers = []string{"cuse", "ipmi_devintf", "ipmi_msghandler"}
img.AdditionalAnacondaModules = []string{"org.fedoraproject.Anaconda.Modules.Users"}
img.SquashfsCompression = "xz"
// put the kickstart file in the root of the iso
img.ISORootKickstart = true
d := t.arch.distro
img.ISOLabelTempl = d.isolabelTmpl
img.Product = d.product
img.OSName = "redhat"
img.OSVersion = d.osVersion
img.Release = fmt.Sprintf("%s %s", d.product, d.osVersion)
img.Filename = t.Filename()
return img, nil
}
func tarImage(workload workload.Workload,
t *imageType,
customizations *blueprint.Customizations,
options distro.ImageOptions,
packageSets map[string]rpmmd.PackageSet,
containers []container.SourceSpec,
rng *rand.Rand) (image.ImageKind, error) {
img := image.NewArchive()
img.Platform = t.platform
img.OSCustomizations = osCustomizations(t, packageSets[osPkgsKey], options, containers, customizations)
img.Environment = t.environment
img.Workload = workload
img.Filename = t.Filename()
return img, nil
}
// Create an ostree SourceSpec to define an ostree parent commit using the user
// options and the default ref for the image type. Additionally returns the
// ref to be used for the new commit to be created.
func makeOSTreeParentCommit(options *ostree.ImageOptions, defaultRef string) (*ostree.SourceSpec, string) {
commitRef := defaultRef
if options == nil {
// nothing to do
return nil, commitRef
}
if options.ImageRef != "" {
// user option overrides default commit ref
commitRef = options.ImageRef
}
var parentCommit *ostree.SourceSpec
if options.URL == "" {
// no parent
return nil, commitRef
}
// ostree URL specified: set source spec for parent commit
parentRef := options.ParentRef
if parentRef == "" {
// parent ref not set: use image ref
parentRef = commitRef
}
parentCommit = &ostree.SourceSpec{
URL: options.URL,
Ref: parentRef,
RHSM: options.RHSM,
}
return parentCommit, commitRef
}
// Create an ostree SourceSpec to define an ostree payload using the user options and the default ref for the image type.
func makeOSTreePayloadCommit(options *ostree.ImageOptions, defaultRef string) (ostree.SourceSpec, error) {
if options == nil || options.URL == "" {
// this should be caught by checkOptions() in distro, but it's good
// to guard against it here as well
return ostree.SourceSpec{}, fmt.Errorf("ostree commit URL required")
}
commitRef := defaultRef
if options.ImageRef != "" {
// user option overrides default commit ref
commitRef = options.ImageRef
}
return ostree.SourceSpec{
URL: options.URL,
Ref: commitRef,
RHSM: options.RHSM,
}, nil
}

View file

@ -1,429 +0,0 @@
package rhel9
import (
"fmt"
"log"
"math/rand"
"strings"
"golang.org/x/exp/slices"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/image"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/oscap"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/pathpolicy"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/workload"
)
const (
// package set names
// build package set name
buildPkgsKey = "build"
// main/common os image package set name
osPkgsKey = "os"
// container package set name
containerPkgsKey = "container"
// installer package set name
installerPkgsKey = "installer"
// blueprint package set name
blueprintPkgsKey = "blueprint"
)
type imageFunc func(workload workload.Workload, t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, packageSets map[string]rpmmd.PackageSet, containers []container.SourceSpec, rng *rand.Rand) (image.ImageKind, error)
type packageSetFunc func(t *imageType) rpmmd.PackageSet
type imageType struct {
arch *architecture
platform platform.Platform
environment environment.Environment
workload workload.Workload
name string
nameAliases []string
filename string
compression string // TODO: remove from image definition and make it a transport option
mimeType string
packageSets map[string]packageSetFunc
defaultImageConfig *distro.ImageConfig
kernelOptions string
defaultSize uint64
buildPipelines []string
payloadPipelines []string
exports []string
image imageFunc
// bootISO: installable ISO
bootISO bool
// rpmOstree: edge/ostree
rpmOstree bool
// bootable image
bootable bool
// List of valid arches for the image type
basePartitionTables distro.BasePartitionTableMap
}
func (t *imageType) Name() string {
return t.name
}
func (t *imageType) Arch() distro.Arch {
return t.arch
}
func (t *imageType) Filename() string {
return t.filename
}
func (t *imageType) MIMEType() string {
return t.mimeType
}
func (t *imageType) OSTreeRef() string {
d := t.arch.distro
if t.rpmOstree {
return fmt.Sprintf(d.ostreeRefTmpl, t.Arch().Name())
}
return ""
}
func (t *imageType) Size(size uint64) uint64 {
// Microsoft Azure requires vhd images to be rounded up to the nearest MB
if t.name == "vhd" && size%common.MebiByte != 0 {
size = (size/common.MebiByte + 1) * common.MebiByte
}
if size == 0 {
size = t.defaultSize
}
return size
}
func (t *imageType) BuildPipelines() []string {
return t.buildPipelines
}
func (t *imageType) PayloadPipelines() []string {
return t.payloadPipelines
}
func (t *imageType) PayloadPackageSets() []string {
return []string{blueprintPkgsKey}
}
func (t *imageType) PackageSetsChains() map[string][]string {
return nil
}
func (t *imageType) Exports() []string {
if len(t.exports) > 0 {
return t.exports
}
return []string{"assembler"}
}
func (t *imageType) BootMode() distro.BootMode {
if t.platform.GetUEFIVendor() != "" && t.platform.GetBIOSPlatform() != "" {
return distro.BOOT_HYBRID
} else if t.platform.GetUEFIVendor() != "" {
return distro.BOOT_UEFI
} else if t.platform.GetBIOSPlatform() != "" || t.platform.GetZiplSupport() {
return distro.BOOT_LEGACY
}
return distro.BOOT_NONE
}
func (t *imageType) getPartitionTable(
mountpoints []blueprint.FilesystemCustomization,
options distro.ImageOptions,
rng *rand.Rand,
) (*disk.PartitionTable, error) {
archName := t.arch.Name()
basePartitionTable, exists := t.basePartitionTables[archName]
if !exists {
return nil, fmt.Errorf("no partition table defined for architecture %q for image type %q", archName, t.Name())
}
imageSize := t.Size(options.Size)
lvmify := !t.rpmOstree
return disk.NewPartitionTable(&basePartitionTable, mountpoints, imageSize, lvmify, nil, rng)
}
func (t *imageType) getDefaultImageConfig() *distro.ImageConfig {
// ensure that image always returns non-nil default config
imageConfig := t.defaultImageConfig
if imageConfig == nil {
imageConfig = &distro.ImageConfig{}
}
return imageConfig.InheritFrom(t.arch.distro.getDefaultImageConfig())
}
func (t *imageType) PartitionType() string {
archName := t.arch.Name()
basePartitionTable, exists := t.basePartitionTables[archName]
if !exists {
return ""
}
return basePartitionTable.Type
}
func (t *imageType) Manifest(bp *blueprint.Blueprint,
options distro.ImageOptions,
repos []rpmmd.RepoConfig,
seed int64) (*manifest.Manifest, []string, error) {
warnings, err := t.checkOptions(bp, options)
if err != nil {
return nil, nil, err
}
// merge package sets that appear in the image type with the package sets
// of the same name from the distro and arch
staticPackageSets := make(map[string]rpmmd.PackageSet)
for name, getter := range t.packageSets {
staticPackageSets[name] = getter(t)
}
// amend with repository information and collect payload repos
payloadRepos := make([]rpmmd.RepoConfig, 0)
for _, repo := range repos {
if len(repo.PackageSets) > 0 {
// only apply the repo to the listed package sets
for _, psName := range repo.PackageSets {
if slices.Contains(t.PayloadPackageSets(), psName) {
payloadRepos = append(payloadRepos, repo)
}
ps := staticPackageSets[psName]
ps.Repositories = append(ps.Repositories, repo)
staticPackageSets[psName] = ps
}
}
}
w := t.workload
if w == nil {
cw := &workload.Custom{
BaseWorkload: workload.BaseWorkload{
Repos: payloadRepos,
},
Packages: bp.GetPackagesEx(false),
}
if services := bp.Customizations.GetServices(); services != nil {
cw.Services = services.Enabled
cw.DisabledServices = services.Disabled
}
w = cw
}
containerSources := make([]container.SourceSpec, len(bp.Containers))
for idx := range bp.Containers {
containerSources[idx] = container.SourceSpec(bp.Containers[idx])
}
source := rand.NewSource(seed)
// math/rand is good enough in this case
/* #nosec G404 */
rng := rand.New(source)
img, err := t.image(w, t, bp.Customizations, options, staticPackageSets, containerSources, rng)
if err != nil {
return nil, nil, err
}
mf := manifest.New()
mf.Distro = manifest.DISTRO_EL9
_, err = img.InstantiateManifest(&mf, repos, t.arch.distro.runner, rng)
if err != nil {
return nil, nil, err
}
return &mf, warnings, err
}
// checkOptions checks the validity and compatibility of options and customizations for the image type.
// Returns ([]string, error) where []string, if non-nil, will hold any generated warnings (e.g. deprecation notices).
func (t *imageType) checkOptions(bp *blueprint.Blueprint, options distro.ImageOptions) ([]string, error) {
customizations := bp.Customizations
// holds warnings (e.g. deprecation notices)
var warnings []string
if t.workload != nil {
// For now, if an image type defines its own workload, don't allow any
// user customizations.
// Soon we will have more workflows and each will define its allowed
// set of customizations. The current set of customizations defined in
// the blueprint spec corresponds to the Custom workflow.
if customizations != nil {
return warnings, fmt.Errorf("image type %q does not support customizations", t.name)
}
}
// we do not support embedding containers on ostree-derived images, only on commits themselves
if len(bp.Containers) > 0 && t.rpmOstree && (t.name != "edge-commit" && t.name != "edge-container") {
return warnings, fmt.Errorf("embedding containers is not supported for %s on %s", t.name, t.arch.distro.name)
}
ostreeURL := ""
if options.OSTree != nil {
if options.OSTree.ParentRef != "" && options.OSTree.URL == "" {
// specifying parent ref also requires URL
return nil, ostree.NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
}
ostreeURL = options.OSTree.URL
}
if t.bootISO && t.rpmOstree {
// ostree-based ISOs require a URL from which to pull a payload commit
if ostreeURL == "" {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name)
}
if t.name == "edge-simplified-installer" {
allowed := []string{"InstallationDevice", "FDO", "Ignition", "Kernel", "User", "Group"}
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, ", "))
}
if customizations.GetInstallationDevice() == "" {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying an installation device to install to", t.name)
}
// FDO is optional, but when specified has some restrictions
if customizations.GetFDO() != nil {
if customizations.GetFDO().ManufacturingServerURL == "" {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying FDO.ManufacturingServerURL configuration to install to when using FDO", t.name)
}
var diunSet int
if customizations.GetFDO().DiunPubKeyHash != "" {
diunSet++
}
if customizations.GetFDO().DiunPubKeyInsecure != "" {
diunSet++
}
if customizations.GetFDO().DiunPubKeyRootCerts != "" {
diunSet++
}
if diunSet != 1 {
return warnings, fmt.Errorf("boot ISO image type %q requires specifying one of [FDO.DiunPubKeyHash,FDO.DiunPubKeyInsecure,FDO.DiunPubKeyRootCerts] configuration to install to when using FDO", t.name)
}
}
// ignition is optional, we might be using FDO
if customizations.GetIgnition() != nil {
if customizations.GetIgnition().Embedded != nil && customizations.GetIgnition().FirstBoot != nil {
return warnings, fmt.Errorf("both ignition embedded and firstboot configurations found")
}
if customizations.GetIgnition().FirstBoot != nil && customizations.GetIgnition().FirstBoot.ProvisioningURL == "" {
return warnings, fmt.Errorf("ignition.firstboot requires a provisioning url")
}
}
} else if t.name == "edge-installer" {
allowed := []string{"User", "Group"}
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, ", "))
}
}
}
if t.name == "edge-raw-image" || t.name == "edge-ami" {
// ostree-based bootable images require a URL from which to pull a payload commit
if ostreeURL == "" {
return warnings, fmt.Errorf("%q images require specifying a URL from which to retrieve the OSTree commit", t.name)
}
allowed := []string{"Ignition", "Kernel", "User", "Group"}
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, ", "))
}
// TODO: consider additional checks, such as those in "edge-simplified-installer"
}
// warn that user & group customizations on edge-commit, edge-container are deprecated
// TODO(edge): directly error if these options are provided when rhel-9.5's time arrives
if t.name == "edge-commit" || t.name == "edge-container" {
if customizations.GetUsers() != nil {
w := fmt.Sprintf("Please note that user customizations on %q image type are deprecated and will be removed in the near future\n", t.name)
log.Print(w)
warnings = append(warnings, w)
}
if customizations.GetGroups() != nil {
w := fmt.Sprintf("Please note that group customizations on %q image type are deprecated and will be removed in the near future\n", t.name)
log.Print(w)
warnings = append(warnings, w)
}
}
if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.rpmOstree && t.name != "edge-raw-image" && t.name != "edge-simplified-installer" {
return warnings, fmt.Errorf("kernel boot parameter customizations are not supported for ostree types")
}
mountpoints := customizations.GetFilesystems()
if mountpoints != nil && t.rpmOstree {
return warnings, fmt.Errorf("Custom mountpoints are not supported for ostree types")
}
err := blueprint.CheckMountpointsPolicy(mountpoints, pathpolicy.MountpointPolicies)
if err != nil {
return warnings, err
}
if osc := customizations.GetOpenSCAP(); osc != nil {
if t.arch.distro.osVersion == "9.0" {
return warnings, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported os version: %s", t.arch.distro.osVersion))
}
if !oscap.IsProfileAllowed(osc.ProfileID, oscapProfileAllowList) {
return warnings, fmt.Errorf(fmt.Sprintf("OpenSCAP unsupported profile: %s", osc.ProfileID))
}
if t.rpmOstree {
return warnings, fmt.Errorf("OpenSCAP customizations are not supported for ostree types")
}
if osc.ProfileID == "" {
return warnings, fmt.Errorf("OpenSCAP profile cannot be empty")
}
}
// Check Directory/File Customizations are valid
dc := customizations.GetDirectories()
fc := customizations.GetFiles()
err = blueprint.ValidateDirFileCustomizations(dc, fc)
if err != nil {
return warnings, err
}
err = blueprint.CheckDirectoryCustomizationsPolicy(dc, pathpolicy.CustomDirectoriesPolicies)
if err != nil {
return warnings, err
}
err = blueprint.CheckFileCustomizationsPolicy(fc, pathpolicy.CustomFilesPolicies)
if err != nil {
return warnings, err
}
// check if repository customizations are valid
_, err = customizations.GetRepositories()
if err != nil {
return warnings, err
}
return warnings, nil
}

View file

@ -1,247 +0,0 @@
package rhel9
// This file defines package sets that are used by more than one image type.
import (
"fmt"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
// BUILD PACKAGE SETS
// distro-wide build package set
func distroBuildPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"dnf",
"dosfstools",
"e2fsprogs",
"glibc",
"lorax-templates-generic",
"lorax-templates-rhel",
"lvm2",
"policycoreutils",
"python3-iniparse",
"qemu-img",
"selinux-policy-targeted",
"systemd",
"tar",
"xfsprogs",
"xz",
},
}
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(x8664BuildPackageSet(t))
case platform.ARCH_PPC64LE.String():
ps = ps.Append(ppc64leBuildPackageSet(t))
}
return ps
}
// x86_64 build package set
func x8664BuildPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"grub2-pc",
},
}
}
// ppc64le build package set
func ppc64leBuildPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
"grub2-ppc64le",
"grub2-ppc64le-modules",
},
}
}
// installer boot package sets, needed for booting and
// also in the build host
func anacondaBootPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{}
grubCommon := rpmmd.PackageSet{
Include: []string{
"grub2-tools",
"grub2-tools-extra",
"grub2-tools-minimal",
},
}
efiCommon := rpmmd.PackageSet{
Include: []string{
"efibootmgr",
},
}
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(grubCommon)
ps = ps.Append(efiCommon)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"grub2-efi-x64",
"grub2-efi-x64-cdboot",
"grub2-pc",
"grub2-pc-modules",
"shim-x64",
"syslinux",
"syslinux-nonlinux",
},
})
case platform.ARCH_AARCH64.String():
ps = ps.Append(grubCommon)
ps = ps.Append(efiCommon)
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"grub2-efi-aa64-cdboot",
"grub2-efi-aa64",
"shim-aa64",
},
})
default:
panic(fmt.Sprintf("unsupported arch: %s", t.arch.Name()))
}
return ps
}
// OS package sets
// Replacement of the previously used @core package group
func coreOsCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"audit",
"basesystem",
"bash",
"coreutils",
"cronie",
"crypto-policies",
"crypto-policies-scripts",
"curl",
"dnf",
"yum",
"e2fsprogs",
"filesystem",
"glibc",
"grubby",
"hostname",
"iproute",
"iproute-tc",
"iputils",
"kbd",
"kexec-tools",
"less",
"logrotate",
"man-db",
"ncurses",
"openssh-clients",
"openssh-server",
"p11-kit",
"parted",
"passwd",
"policycoreutils",
"procps-ng",
"rootfiles",
"rpm",
"rpm-plugin-audit",
"rsyslog",
"selinux-policy-targeted",
"setup",
"shadow-utils",
"sssd-common",
"sssd-kcm",
"sudo",
"systemd",
"tuned",
"util-linux",
"vim-minimal",
"xfsprogs",
"authselect",
"prefixdevname",
"dnf-plugins-core",
"NetworkManager",
"NetworkManager-team",
"NetworkManager-tui",
"libsysfs",
"linux-firmware",
"lshw",
"lsscsi",
"kernel-tools",
"sg3_utils",
"sg3_utils-libs",
"python3-libselinux",
},
}
// Do not include this in the distroSpecificPackageSet for now,
// because it includes 'insights-client' which is not installed
// by default on all RHEL images (although it would probably make sense).
if t.arch.distro.isRHEL() {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"subscription-manager",
},
})
}
switch t.arch.Name() {
case platform.ARCH_X86_64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"irqbalance",
"microcode_ctl",
},
})
case platform.ARCH_AARCH64.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"irqbalance",
},
})
case platform.ARCH_PPC64LE.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"irqbalance",
"opal-prd",
"ppc64-diag-rtas",
"powerpc-utils-core",
"lsvpd",
},
})
case platform.ARCH_S390X.String():
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"s390utils-core",
},
})
}
return ps
}
// packages that are only in some (sub)-distributions
func distroSpecificPackageSet(t *imageType) rpmmd.PackageSet {
if t.arch.distro.isRHEL() {
return rpmmd.PackageSet{
Include: []string{
"insights-client",
},
}
}
return rpmmd.PackageSet{}
}

View file

@ -1,169 +0,0 @@
package rhel9
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/platform"
)
var defaultBasePartitionTables = distro.BasePartitionTableMap{
platform.ARCH_X86_64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 1 * common.MebiByte, // 1MB
Bootable: true,
Type: disk.BIOSBootPartitionGUID,
UUID: disk.BIOSBootPartitionUUID,
},
{
Size: 200 * common.MebiByte, // 200 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte, // 500 MB
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte, // 2GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_AARCH64.String(): disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: "gpt",
Partitions: []disk.Partition{
{
Size: 200 * common.MebiByte, // 200 MB
Type: disk.EFISystemPartitionGUID,
UUID: disk.EFISystemPartitionUUID,
Payload: &disk.Filesystem{
Type: "vfat",
UUID: disk.EFIFilesystemUUID,
Mountpoint: "/boot/efi",
Label: "EFI-SYSTEM",
FSTabOptions: "defaults,uid=0,gid=0,umask=077,shortname=winnt",
FSTabFreq: 0,
FSTabPassNo: 2,
},
},
{
Size: 500 * common.MebiByte, // 500 MB
Type: disk.XBootLDRPartitionGUID,
UUID: disk.FilesystemDataUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte, // 2GiB
Type: disk.FilesystemDataGUID,
UUID: disk.RootPartitionUUID,
Payload: &disk.Filesystem{
Type: "xfs",
Label: "root",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_PPC64LE.String(): disk.PartitionTable{
UUID: "0x14fc63d2",
Type: "dos",
Partitions: []disk.Partition{
{
Size: 4 * common.MebiByte,
Type: "41",
Bootable: true,
},
{
Size: 500 * common.MebiByte, // 500 MB
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte, // 2GiB
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
platform.ARCH_S390X.String(): disk.PartitionTable{
UUID: "0x14fc63d2",
Type: "dos",
Partitions: []disk.Partition{
{
Size: 500 * common.MebiByte, // 500 MB
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/boot",
Label: "boot",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
{
Size: 2 * common.GibiByte, // 2GiB
Bootable: true,
Payload: &disk.Filesystem{
Type: "xfs",
Mountpoint: "/",
FSTabOptions: "defaults",
FSTabFreq: 0,
FSTabPassNo: 0,
},
},
},
},
}

View file

@ -1,173 +0,0 @@
package rhel9
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/subscription"
)
var (
openstackImgType = imageType{
name: "openstack",
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
packageSets: map[string]packageSetFunc{
osPkgsKey: openstackCommonPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
},
kernelOptions: "ro net.ifnames=0",
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "qcow2"},
exports: []string{"qcow2"},
basePartitionTables: defaultBasePartitionTables,
}
)
func qcow2CommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"authselect-compat",
"chrony",
"cloud-init",
"cloud-utils-growpart",
"cockpit-system",
"cockpit-ws",
"dnf-utils",
"dosfstools",
"nfs-utils",
"oddjob",
"oddjob-mkhomedir",
"psmisc",
"python3-jsonschema",
"qemu-guest-agent",
"redhat-release",
"redhat-release-eula",
"rsync",
"tar",
"tcpdump",
},
Exclude: []string{
"aic94xx-firmware",
"alsa-firmware",
"alsa-lib",
"alsa-tools-firmware",
"biosdevname",
"dnf-plugin-spacewalk",
"fedora-release",
"fedora-repos",
"iprutils",
"ivtv-firmware",
"langpacks-*",
"langpacks-en",
"libertas-sd8787-firmware",
"nss",
"plymouth",
"rng-tools",
"udisks2",
},
}.Append(coreOsCommonPackageSet(t)).Append(distroSpecificPackageSet(t))
// Ensure to not pull in subscription-manager on non-RHEL distro
if t.arch.distro.isRHEL() {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
"subscription-manager-cockpit",
},
})
}
return ps
}
func openstackCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
// Defaults
"langpacks-en",
"firewalld",
// From the lorax kickstart
"cloud-init",
"qemu-guest-agent",
"spice-vdagent",
},
Exclude: []string{
"rng-tools",
},
}.Append(coreOsCommonPackageSet(t))
if t.arch.Name() == platform.ARCH_X86_64.String() {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
// packages below used to come from @core group and were not excluded
// they may not be needed at all, but kept them here to not need
// to exclude them instead in all other images
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl1000-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000g2a-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
},
})
}
return ps
}
func qcowImageConfig(d distribution) *distro.ImageConfig {
ic := &distro.ImageConfig{
DefaultTarget: common.ToPtr("multi-user.target"),
}
if d.isRHEL() {
ic.RHSMConfig = map[subscription.RHSMStatus]*osbuild.RHSMStageOptions{
subscription.RHSMConfigNoSubscription: {
DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{
ProductID: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{
Enabled: false,
},
},
},
}
}
return ic
}
func mkQcow2ImgType(d distribution) imageType {
it := imageType{
name: "qcow2",
filename: "disk.qcow2",
mimeType: "application/x-qemu-disk",
kernelOptions: "console=tty0 console=ttyS0,115200n8 no_timer_check net.ifnames=0",
packageSets: map[string]packageSetFunc{
osPkgsKey: qcow2CommonPackageSet,
},
bootable: true,
defaultSize: 10 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "qcow2"},
exports: []string{"qcow2"},
basePartitionTables: defaultBasePartitionTables,
}
it.defaultImageConfig = qcowImageConfig(d)
return it
}

View file

@ -1,176 +0,0 @@
package rhel9
import (
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
// sapImageConfig returns the SAP specific ImageConfig data
func sapImageConfig(osVersion string) *distro.ImageConfig {
return &distro.ImageConfig{
SELinuxConfig: &osbuild.SELinuxConfigStageOptions{
State: osbuild.SELinuxStatePermissive,
},
// RHBZ#1960617
Tuned: osbuild.NewTunedStageOptions("sap-hana"),
// RHBZ#1959979
Tmpfilesd: []*osbuild.TmpfilesdStageOptions{
osbuild.NewTmpfilesdStageOptions("sap.conf",
[]osbuild.TmpfilesdConfigLine{
{
Type: "x",
Path: "/tmp/.sap*",
},
{
Type: "x",
Path: "/tmp/.hdb*lock",
},
{
Type: "x",
Path: "/tmp/.trex*lock",
},
},
),
},
// RHBZ#1959963
PamLimitsConf: []*osbuild.PamLimitsConfStageOptions{
osbuild.NewPamLimitsConfStageOptions("99-sap.conf",
[]osbuild.PamLimitsConfigLine{
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNofile,
Value: osbuild.PamLimitsValueInt(1048576),
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@sapsys",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeHard,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
{
Domain: "@dba",
Type: osbuild.PamLimitsTypeSoft,
Item: osbuild.PamLimitsItemNproc,
Value: osbuild.PamLimitsValueUnlimited,
},
},
),
},
// RHBZ#1959962
Sysctld: []*osbuild.SysctldStageOptions{
osbuild.NewSysctldStageOptions("sap.conf",
[]osbuild.SysctldConfigLine{
{
Key: "kernel.pid_max",
Value: "4194304",
},
{
Key: "vm.max_map_count",
Value: "2147483647",
},
},
),
},
// E4S/EUS
DNFConfig: []*osbuild.DNFConfigStageOptions{
osbuild.NewDNFConfigStageOptions(
[]osbuild.DNFVariable{
{
Name: "releasever",
Value: osVersion,
},
},
nil,
),
},
}
}
func SapPackageSet(t *imageType) rpmmd.PackageSet {
return rpmmd.PackageSet{
Include: []string{
// RHBZ#2076763
"@Server",
// SAP System Roles
// https://access.redhat.com/sites/default/files/attachments/rhel_system_roles_for_sap_1.pdf
"ansible-core",
"rhel-system-roles-sap",
// RHBZ#1959813
"bind-utils",
"nfs-utils",
"tcsh",
// RHBZ#1959955
"uuidd",
// RHBZ#1959923
"cairo",
"expect",
"graphviz",
"gtk2",
"iptraf-ng",
"krb5-workstation",
"libaio",
"libatomic",
"libcanberra-gtk2",
"libicu",
"libtool-ltdl",
"lm_sensors",
"net-tools",
"numactl",
"PackageKit-gtk3-module",
"xorg-x11-xauth",
// RHBZ#1960617
"tuned-profiles-sap-hana",
// RHBZ#1961168
"libnsl",
},
Exclude: []string{
// COMPOSER-1829
"firewalld",
"iwl1000-firmware",
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000g2a-firmware",
"iwl6000g2b-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
},
}
}

View file

@ -1,89 +0,0 @@
package rhel9
import (
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
const vmdkKernelOptions = "ro net.ifnames=0"
var vmdkImgType = imageType{
name: "vmdk",
filename: "disk.vmdk",
mimeType: "application/x-vmdk",
packageSets: map[string]packageSetFunc{
osPkgsKey: vmdkCommonPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
},
kernelOptions: vmdkKernelOptions,
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vmdk"},
exports: []string{"vmdk"},
basePartitionTables: defaultBasePartitionTables,
}
var ovaImgType = imageType{
name: "ova",
filename: "image.ova",
mimeType: "application/ovf",
packageSets: map[string]packageSetFunc{
osPkgsKey: vmdkCommonPackageSet,
},
defaultImageConfig: &distro.ImageConfig{
Locale: common.ToPtr("en_US.UTF-8"),
},
kernelOptions: vmdkKernelOptions,
bootable: true,
defaultSize: 4 * common.GibiByte,
image: liveImage,
buildPipelines: []string{"build"},
payloadPipelines: []string{"os", "image", "vmdk", "ovf", "archive"},
exports: []string{"archive"},
basePartitionTables: defaultBasePartitionTables,
}
func vmdkCommonPackageSet(t *imageType) rpmmd.PackageSet {
ps := rpmmd.PackageSet{
Include: []string{
"chrony",
"cloud-init",
"firewalld",
"langpacks-en",
"open-vm-tools",
},
Exclude: []string{
"rng-tools",
},
}.Append(coreOsCommonPackageSet(t))
if t.arch.Name() == platform.ARCH_X86_64.String() {
ps = ps.Append(rpmmd.PackageSet{
Include: []string{
// packages below used to come from @core group and were not excluded
// they may not be needed at all, but kept them here to not need
// to exclude them instead in all other images
"iwl100-firmware",
"iwl105-firmware",
"iwl135-firmware",
"iwl1000-firmware",
"iwl2000-firmware",
"iwl2030-firmware",
"iwl3160-firmware",
"iwl5000-firmware",
"iwl5150-firmware",
"iwl6000g2a-firmware",
"iwl6050-firmware",
"iwl7260-firmware",
},
})
}
return ps
}

View file

@ -1,435 +0,0 @@
package test_distro
import (
"crypto/sha256"
"errors"
"fmt"
"sort"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/container"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distroregistry"
"github.com/osbuild/osbuild-composer/internal/manifest"
dnfjson_mock "github.com/osbuild/osbuild-composer/internal/mocks/dnfjson"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
const (
// package set names
// build package set name
buildPkgsKey = "build"
// main/common os image package set name
osPkgsKey = "os"
// blueprint package set name
blueprintPkgsKey = "blueprint"
)
type TestDistro struct {
name string
releasever string
modulePlatformID string
ostreeRef string
arches map[string]distro.Arch
}
type TestArch struct {
distribution *TestDistro
name string
imageTypes map[string]distro.ImageType
}
type TestImageType struct {
architecture *TestArch
name string
}
const (
TestDistroName = "test-distro"
TestDistro2Name = "test-distro-2"
TestDistroReleasever = "1"
TestDistro2Releasever = "2"
TestDistroModulePlatformID = "platform:test"
TestDistro2ModulePlatformID = "platform:test-2"
TestArchName = "test_arch"
TestArch2Name = "test_arch2"
TestArch3Name = "test_arch3"
TestImageTypeName = "test_type"
TestImageType2Name = "test_type2"
TestImageTypeOSTree = "test_ostree_type"
// added for cloudapi tests
TestImageTypeAmi = "ami"
TestImageTypeGce = "gce"
TestImageTypeVhd = "vhd"
TestImageTypeEdgeCommit = "rhel-edge-commit"
TestImageTypeEdgeInstaller = "rhel-edge-installer"
TestImageTypeImageInstaller = "image-installer"
TestImageTypeQcow2 = "qcow2"
TestImageTypeVmdk = "vmdk"
)
// TestDistro
func (d *TestDistro) Name() string {
return d.name
}
func (d *TestDistro) Releasever() string {
return d.releasever
}
func (d *TestDistro) ModulePlatformID() string {
return d.modulePlatformID
}
func (d *TestDistro) OSTreeRef() string {
return d.ostreeRef
}
func (d *TestDistro) ListArches() []string {
archs := make([]string, 0, len(d.arches))
for name := range d.arches {
archs = append(archs, name)
}
sort.Strings(archs)
return archs
}
func (d *TestDistro) GetArch(arch string) (distro.Arch, error) {
a, exists := d.arches[arch]
if !exists {
return nil, errors.New("invalid arch: " + arch)
}
return a, nil
}
func (d *TestDistro) addArches(arches ...*TestArch) {
if d.arches == nil {
d.arches = map[string]distro.Arch{}
}
for _, a := range arches {
a.distribution = d
d.arches[a.Name()] = a
}
}
// TestArch
func (a *TestArch) Name() string {
return a.name
}
func (a *TestArch) Distro() distro.Distro {
return a.distribution
}
func (a *TestArch) ListImageTypes() []string {
formats := make([]string, 0, len(a.imageTypes))
for name := range a.imageTypes {
formats = append(formats, name)
}
sort.Strings(formats)
return formats
}
func (a *TestArch) GetImageType(imageType string) (distro.ImageType, error) {
t, exists := a.imageTypes[imageType]
if !exists {
return nil, errors.New("invalid image type: " + imageType)
}
return t, nil
}
func (a *TestArch) addImageTypes(imageTypes ...TestImageType) {
if a.imageTypes == nil {
a.imageTypes = map[string]distro.ImageType{}
}
for idx := range imageTypes {
it := imageTypes[idx]
it.architecture = a
a.imageTypes[it.Name()] = &it
}
}
// TestImageType
func (t *TestImageType) Name() string {
return t.name
}
func (t *TestImageType) Arch() distro.Arch {
return t.architecture
}
func (t *TestImageType) Filename() string {
return "test.img"
}
func (t *TestImageType) MIMEType() string {
return "application/x-test"
}
func (t *TestImageType) OSTreeRef() string {
if t.name == TestImageTypeEdgeCommit || t.name == TestImageTypeEdgeInstaller || t.name == TestImageTypeOSTree {
return t.architecture.distribution.OSTreeRef()
}
return ""
}
func (t *TestImageType) Size(size uint64) uint64 {
return 0
}
func (t *TestImageType) PartitionType() string {
return ""
}
func (t *TestImageType) BootMode() distro.BootMode {
return distro.BOOT_HYBRID
}
func (t *TestImageType) BuildPipelines() []string {
return distro.BuildPipelinesFallback()
}
func (t *TestImageType) PayloadPipelines() []string {
return distro.PayloadPipelinesFallback()
}
func (t *TestImageType) PayloadPackageSets() []string {
return []string{blueprintPkgsKey}
}
func (t *TestImageType) PackageSetsChains() map[string][]string {
return map[string][]string{
osPkgsKey: {osPkgsKey, blueprintPkgsKey},
}
}
func (t *TestImageType) Exports() []string {
return distro.ExportsFallback()
}
func (t *TestImageType) Manifest(b *blueprint.Blueprint, options distro.ImageOptions, repos []rpmmd.RepoConfig, seed int64) (*manifest.Manifest, []string, error) {
var bpPkgs []string
if b != nil {
mountpoints := b.Customizations.GetFilesystems()
invalidMountpoints := []string{}
for _, m := range mountpoints {
if m.Mountpoint != "/" {
invalidMountpoints = append(invalidMountpoints, m.Mountpoint)
}
}
if len(invalidMountpoints) > 0 {
return nil, nil, fmt.Errorf("The following custom mountpoints are not supported %+q", invalidMountpoints)
}
bpPkgs = b.GetPackages()
}
var ostreeSources []ostree.SourceSpec
if defaultRef := t.OSTreeRef(); defaultRef != "" {
// ostree image type
ostreeSource := ostree.SourceSpec{ // init with default
Ref: defaultRef,
}
if ostreeOptions := options.OSTree; ostreeOptions != nil {
// handle the parameter combo error like we do in distros
if ostreeOptions.ParentRef != "" && ostreeOptions.URL == "" {
// specifying parent ref also requires URL
return nil, nil, ostree.NewParameterComboError("ostree parent ref specified, but no URL to retrieve it")
}
if ostreeOptions.ImageRef != "" { // override with ref from image options
ostreeSource.Ref = ostreeOptions.ImageRef
}
if ostreeOptions.ParentRef != "" { // override with parent ref
ostreeSource.Ref = ostreeOptions.ParentRef
}
// copy any other options that might be specified
ostreeSource.URL = options.OSTree.URL
ostreeSource.RHSM = options.OSTree.RHSM
}
ostreeSources = []ostree.SourceSpec{ostreeSource}
}
buildPackages := []rpmmd.PackageSet{{
Include: []string{
"dep-package1",
"dep-package2",
"dep-package3",
},
Repositories: repos,
}}
osPackages := []rpmmd.PackageSet{
{
Include: bpPkgs,
Repositories: repos,
},
{
Include: []string{
"dep-package1",
"dep-package2",
"dep-package3",
},
Repositories: repos,
},
}
m := &manifest.Manifest{}
manifest.NewContentTest(m, buildPkgsKey, buildPackages, nil, nil)
manifest.NewContentTest(m, osPkgsKey, osPackages, nil, ostreeSources)
return m, nil, nil
}
// newTestDistro returns a new instance of TestDistro with the
// given name and modulePlatformID.
//
// 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 {
td := TestDistro{
name: name,
releasever: releasever,
modulePlatformID: modulePlatformID,
ostreeRef: "test/13/x86_64/edge",
}
ta1 := TestArch{
name: TestArchName,
}
ta2 := TestArch{
name: TestArch2Name,
}
ta3 := TestArch{
name: TestArch3Name,
}
it1 := TestImageType{
name: TestImageTypeName,
}
it2 := TestImageType{
name: TestImageType2Name,
}
it3 := TestImageType{
name: TestImageTypeAmi,
}
it4 := TestImageType{
name: TestImageTypeVhd,
}
it5 := TestImageType{
name: TestImageTypeEdgeCommit,
}
it6 := TestImageType{
name: TestImageTypeEdgeInstaller,
}
it7 := TestImageType{
name: TestImageTypeImageInstaller,
}
it8 := TestImageType{
name: TestImageTypeQcow2,
}
it9 := TestImageType{
name: TestImageTypeVmdk,
}
it10 := TestImageType{
name: TestImageTypeGce,
}
it11 := TestImageType{
name: TestImageTypeOSTree,
}
ta1.addImageTypes(it1, it11)
ta2.addImageTypes(it1, it2)
ta3.addImageTypes(it3, it4, it5, it6, it7, it8, it9, it10)
td.addArches(&ta1, &ta2, &ta3)
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)
if err != nil {
panic(err)
}
// Override the host's architecture name with the test's name
registry.SetHostArchName(TestArchName)
return registry
}
// New2 returns new instance of TestDistro named "test-distro-2".
func New2() *TestDistro {
return newTestDistro(TestDistro2Name, TestDistro2ModulePlatformID, TestDistro2Releasever)
}
// ResolveContent transforms content source specs into resolved specs for serialization.
// For packages, it uses the dnfjson_mock.BaseDeps() every time, but retains
// the map keys from the input.
// For ostree commits it hashes the URL+Ref to create a checksum.
func ResolveContent(pkgs map[string][]rpmmd.PackageSet, containers map[string][]container.SourceSpec, commits map[string][]ostree.SourceSpec) (map[string][]rpmmd.PackageSpec, map[string][]container.Spec, map[string][]ostree.CommitSpec) {
pkgSpecs := make(map[string][]rpmmd.PackageSpec, len(pkgs))
for name := range pkgs {
pkgSpecs[name] = dnfjson_mock.BaseDeps()
}
containerSpecs := make(map[string][]container.Spec, len(containers))
for name := range containers {
containerSpecs[name] = make([]container.Spec, len(containers[name]))
for idx := range containers[name] {
containerSpecs[name][idx] = container.Spec{
Source: containers[name][idx].Source,
TLSVerify: containers[name][idx].TLSVerify,
LocalName: containers[name][idx].Name,
}
}
}
commitSpecs := make(map[string][]ostree.CommitSpec, len(commits))
for name := range commits {
commitSpecs[name] = make([]ostree.CommitSpec, len(commits[name]))
for idx := range commits[name] {
commitSpecs[name][idx] = ostree.CommitSpec{
Ref: commits[name][idx].Ref,
URL: commits[name][idx].URL,
Checksum: fmt.Sprintf("%x", sha256.Sum256([]byte(commits[name][idx].URL+commits[name][idx].Ref))),
}
fmt.Printf("Test distro spec: %+v\n", commitSpecs[name][idx])
}
}
return pkgSpecs, containerSpecs, commitSpecs
}

View file

@ -1,143 +0,0 @@
package distroregistry
import (
"fmt"
"sort"
"strings"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distro/fedora"
"github.com/osbuild/osbuild-composer/internal/distro/rhel7"
"github.com/osbuild/osbuild-composer/internal/distro/rhel8"
"github.com/osbuild/osbuild-composer/internal/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,
rhel7.New,
rhel8.New,
rhel8.NewRHEL84,
rhel8.NewRHEL85,
rhel8.NewRHEL86,
rhel8.NewRHEL87,
rhel8.NewRHEL88,
rhel8.NewRHEL89,
rhel8.NewCentos,
rhel9.New,
rhel9.NewRHEL90,
rhel9.NewRHEL91,
rhel9.NewRHEL92,
rhel9.NewRHEL93,
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: common.CurrentArch(),
}
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

@ -1,122 +0,0 @@
package distroregistry
import (
"fmt"
"testing"
"github.com/stretchr/testify/require"
"github.com/osbuild/osbuild-composer/internal/distro"
"github.com/osbuild/osbuild-composer/internal/distro/rhel8"
)
// Test that all distros are registered properly and that Registry.List() works.
func TestRegistry_List(t *testing.T) {
// build expected distros
var expected []string
for _, supportedDistro := range supportedDistros {
d := supportedDistro()
expected = append(expected, d.Name())
}
distros := NewDefault()
require.ElementsMatch(t, expected, distros.List(), "unexpected list of distros")
}
func TestRegistry_GetDistro(t *testing.T) {
distros := NewDefault()
t.Run("distro exists", func(t *testing.T) {
expectedDistro := rhel8.New()
require.Equal(t, expectedDistro.Name(), distros.GetDistro(expectedDistro.Name()).Name())
})
t.Run("distro doesn't exist", func(t *testing.T) {
require.Nil(t, distros.GetDistro("toucan-os"))
})
}
func TestRegistry_mangleHostDistroName(t *testing.T) {
type args struct {
name string
isBeta bool
isStream bool
}
tests := []struct {
name string
args args
want string
}{
{"fedora-33", args{"fedora-33", false, false}, "fedora-33"},
{"fedora-33 beta", args{"fedora-33", true, false}, "fedora-33-beta"},
{"fedora-33 stream", args{"fedora-33", false, true}, "fedora-33"},
{"fedora-33 beta stream", args{"fedora-33", true, true}, "fedora-33-beta"},
{"rhel-8", args{"rhel-8", false, false}, "rhel-8"},
{"rhel-8 beta", args{"rhel-8", true, false}, "rhel-8-beta"},
{"rhel-8 stream", args{"rhel-8", false, true}, "rhel-8"},
{"rhel-8 beta stream", args{"rhel-8", true, true}, "rhel-8-beta"},
{"rhel-84", args{"rhel-84", false, false}, "rhel-8"},
{"rhel-84 beta", args{"rhel-84", true, false}, "rhel-8-beta"},
{"rhel-84 stream", args{"rhel-84", false, true}, "rhel-8"},
{"rhel-84 beta stream", args{"rhel-84", true, true}, "rhel-8-beta"},
{"centos-8", args{"centos-8", false, false}, "centos-8"},
{"centos-8 beta", args{"centos-8", true, false}, "centos-8-beta"},
{"centos-8 stream", args{"centos-8", false, true}, "centos-stream-8"},
{"centos-8 beta stream", args{"centos-8", true, true}, "centos-8-beta"},
{"rhel-90", args{"rhel-90", false, false}, "rhel-90"},
{"rhel-90 beta", args{"rhel-90", true, false}, "rhel-90-beta"},
{"rhel-90 stream", args{"rhel-90", false, true}, "rhel-90"},
{"rhel-90 beta stream", args{"rhel-90", true, true}, "rhel-90-beta"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mangledName := mangleHostDistroName(tt.args.name, tt.args.isBeta, tt.args.isStream)
require.Equalf(
t,
tt.want,
mangledName,
"mangleHostDistroName() name:%s, isBeta:%s, isStream:%s =\nExpected: %s\nGot: %s\n",
tt.args.name,
tt.args.isBeta,
tt.args.isStream,
tt.want,
mangledName,
)
})
}
}
func TestRegistry_FromHost(t *testing.T) {
// expected distros
var distros []distro.Distro
for _, supportedDistro := range supportedDistros {
distros = append(distros, supportedDistro())
}
t.Run("host distro is nil", func(t *testing.T) {
registry, err := New(nil, distros...)
require.Nil(t, err)
require.NotNil(t, registry)
require.Nil(t, registry.FromHost())
})
t.Run("host distro not nil", func(t *testing.T) {
hostDistro := rhel8.New()
fmt.Println(hostDistro.Name())
registry, err := New(hostDistro, distros...)
require.Nil(t, err)
require.NotNil(t, registry)
gotDistro := registry.FromHost()
require.NotNil(t, gotDistro)
require.Equal(t, gotDistro.Name(), hostDistro.Name())
})
}

View file

@ -9,7 +9,7 @@ import (
"sync"
"time"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/gobwas/glob"
)

View file

@ -9,7 +9,7 @@ import (
"testing"
"time"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/stretchr/testify/assert"
)

View file

@ -25,8 +25,8 @@ import (
"strings"
"time"
"github.com/osbuild/osbuild-composer/internal/rhsm"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/images/pkg/rhsm"
"github.com/osbuild/images/pkg/rpmmd"
)
// BaseSolver defines the basic solver configuration without platform

View file

@ -8,9 +8,9 @@ import (
"strings"
"testing"
"github.com/osbuild/images/pkg/rpmmd"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/mocks/rpmrepo"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/stretchr/testify/assert"
)

View file

@ -1,6 +1,6 @@
package environment
import "github.com/osbuild/osbuild-composer/internal/rpmmd"
import "github.com/osbuild/images/pkg/rpmmd"
type Environment interface {
GetPackages() []string

View file

@ -1,127 +0,0 @@
package image
import (
"fmt"
"math/rand"
"github.com/osbuild/osbuild-composer/internal/artifact"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
"github.com/osbuild/osbuild-composer/internal/workload"
)
type AnacondaLiveInstaller struct {
Base
Platform platform.Platform
Environment environment.Environment
Workload workload.Workload
ExtraBasePackages rpmmd.PackageSet
ISOLabelTempl string
Product string
Variant string
OSName string
OSVersion string
Release string
Filename string
AdditionalKernelOpts []string
}
func NewAnacondaLiveInstaller() *AnacondaLiveInstaller {
return &AnacondaLiveInstaller{
Base: NewBase("live-installer"),
}
}
func (img *AnacondaLiveInstaller) InstantiateManifest(m *manifest.Manifest,
repos []rpmmd.RepoConfig,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuild(m, runner, repos)
buildPipeline.Checkpoint()
livePipeline := manifest.NewAnacondaInstaller(m,
manifest.AnacondaInstallerTypeLive,
buildPipeline,
img.Platform,
repos,
"kernel",
img.Product,
img.OSVersion)
livePipeline.ExtraPackages = img.ExtraBasePackages.Include
livePipeline.Variant = img.Variant
livePipeline.Biosdevname = (img.Platform.GetArch() == platform.ARCH_X86_64)
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(m, buildPipeline, livePipeline)
rootfsImagePipeline.Size = 8 * common.GibiByte
bootTreePipeline := manifest.NewEFIBootTree(m, buildPipeline, img.Product, img.OSVersion)
bootTreePipeline.Platform = img.Platform
bootTreePipeline.UEFIVendor = img.Platform.GetUEFIVendor()
bootTreePipeline.ISOLabel = isoLabel
kernelOpts := []string{
fmt.Sprintf("root=live:CDLABEL=%s", isoLabel),
"rd.live.image",
"quiet",
"rhgb",
}
kernelOpts = append(kernelOpts, img.AdditionalKernelOpts...)
bootTreePipeline.KernelOpts = kernelOpts
// enable ISOLinux on x86_64 only
isoLinuxEnabled := img.Platform.GetArch() == platform.ARCH_X86_64
isoTreePipeline := manifest.NewAnacondaInstallerISOTree(m,
buildPipeline,
livePipeline,
rootfsImagePipeline,
bootTreePipeline,
isoLabel)
isoTreePipeline.PartitionTable = rootfsPartitionTable
isoTreePipeline.Release = img.Release
isoTreePipeline.OSName = img.OSName
isoTreePipeline.KernelOpts = kernelOpts
isoTreePipeline.ISOLinux = isoLinuxEnabled
isoPipeline := manifest.NewISO(m, buildPipeline, isoTreePipeline, isoLabel)
isoPipeline.Filename = img.Filename
isoPipeline.ISOLinux = isoLinuxEnabled
artifact := isoPipeline.Export()
return artifact, nil
}

View file

@ -1,133 +0,0 @@
package image
import (
"fmt"
"math/rand"
"github.com/osbuild/osbuild-composer/internal/artifact"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
"github.com/osbuild/osbuild-composer/internal/users"
)
type AnacondaOSTreeInstaller struct {
Base
Platform platform.Platform
ExtraBasePackages rpmmd.PackageSet
Users []users.User
Groups []users.Group
SquashfsCompression string
ISOLabelTempl string
Product string
Variant string
OSName string
OSVersion string
Release string
Commit ostree.SourceSpec
Filename string
AdditionalDracutModules []string
AdditionalAnacondaModules []string
AdditionalDrivers []string
}
func NewAnacondaOSTreeInstaller(commit ostree.SourceSpec) *AnacondaOSTreeInstaller {
return &AnacondaOSTreeInstaller{
Base: NewBase("ostree-installer"),
Commit: commit,
}
}
func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest,
repos []rpmmd.RepoConfig,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuild(m, runner, repos)
buildPipeline.Checkpoint()
anacondaPipeline := manifest.NewAnacondaInstaller(m,
manifest.AnacondaInstallerTypePayload,
buildPipeline,
img.Platform,
repos,
"kernel",
img.Product,
img.OSVersion)
anacondaPipeline.ExtraPackages = img.ExtraBasePackages.Include
anacondaPipeline.ExtraRepos = img.ExtraBasePackages.Repositories
anacondaPipeline.Users = img.Users
anacondaPipeline.Groups = img.Groups
anacondaPipeline.Variant = img.Variant
anacondaPipeline.Biosdevname = (img.Platform.GetArch() == platform.ARCH_X86_64)
anacondaPipeline.Checkpoint()
anacondaPipeline.AdditionalDracutModules = img.AdditionalDracutModules
anacondaPipeline.AdditionalAnacondaModules = img.AdditionalAnacondaModules
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(m, buildPipeline, anacondaPipeline)
rootfsImagePipeline.Size = 4 * common.GibiByte
bootTreePipeline := manifest.NewEFIBootTree(m, 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)}
// enable ISOLinux on x86_64 only
isoLinuxEnabled := img.Platform.GetArch() == platform.ARCH_X86_64
isoTreePipeline := manifest.NewAnacondaInstallerISOTree(m,
buildPipeline,
anacondaPipeline,
rootfsImagePipeline,
bootTreePipeline,
isoLabel)
isoTreePipeline.PartitionTable = rootfsPartitionTable
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 = "/ostree/repo"
isoTreePipeline.OSTreeCommitSource = &img.Commit
isoTreePipeline.ISOLinux = isoLinuxEnabled
isoPipeline := manifest.NewISO(m, buildPipeline, isoTreePipeline, isoLabel)
isoPipeline.Filename = img.Filename
isoPipeline.ISOLinux = isoLinuxEnabled
artifact := isoPipeline.Export()
return artifact, nil
}

View file

@ -1,166 +0,0 @@
package image
import (
"fmt"
"math/rand"
"path/filepath"
"github.com/osbuild/osbuild-composer/internal/artifact"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
"github.com/osbuild/osbuild-composer/internal/users"
"github.com/osbuild/osbuild-composer/internal/workload"
)
const kspath = "/osbuild.ks"
type AnacondaTarInstaller struct {
Base
Platform platform.Platform
OSCustomizations manifest.OSCustomizations
Environment environment.Environment
Workload workload.Workload
ExtraBasePackages rpmmd.PackageSet
Users []users.User
Groups []users.Group
// If set, the kickstart file will be added to the bootiso-tree as
// /osbuild.ks, otherwise any kickstart options will be configured in the
// default /usr/share/anaconda/interactive-defaults.ks in the rootfs.
ISORootKickstart bool
SquashfsCompression string
ISOLabelTempl string
Product string
Variant string
OSName string
OSVersion string
Release string
Filename string
AdditionalKernelOpts []string
AdditionalAnacondaModules []string
AdditionalDracutModules []string
AdditionalDrivers []string
}
func NewAnacondaTarInstaller() *AnacondaTarInstaller {
return &AnacondaTarInstaller{
Base: NewBase("image-installer"),
}
}
func (img *AnacondaTarInstaller) InstantiateManifest(m *manifest.Manifest,
repos []rpmmd.RepoConfig,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuild(m, runner, repos)
buildPipeline.Checkpoint()
anacondaPipeline := manifest.NewAnacondaInstaller(m,
manifest.AnacondaInstallerTypePayload,
buildPipeline,
img.Platform,
repos,
"kernel",
img.Product,
img.OSVersion)
anacondaPipeline.ExtraPackages = img.ExtraBasePackages.Include
anacondaPipeline.ExtraRepos = img.ExtraBasePackages.Repositories
anacondaPipeline.Users = img.Users
anacondaPipeline.Groups = img.Groups
anacondaPipeline.Variant = img.Variant
anacondaPipeline.Biosdevname = (img.Platform.GetArch() == platform.ARCH_X86_64)
anacondaPipeline.AdditionalAnacondaModules = img.AdditionalAnacondaModules
anacondaPipeline.AdditionalDracutModules = img.AdditionalDracutModules
anacondaPipeline.AdditionalDrivers = img.AdditionalDrivers
tarPath := "/liveimg.tar.gz"
if !img.ISORootKickstart {
payloadPath := filepath.Join("/run/install/repo/", tarPath)
anacondaPipeline.InteractiveDefaults = manifest.NewAnacondaInteractiveDefaults(fmt.Sprintf("file://%s", payloadPath))
}
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(m, buildPipeline, anacondaPipeline)
rootfsImagePipeline.Size = 4 * common.GibiByte
bootTreePipeline := manifest.NewEFIBootTree(m, buildPipeline, img.Product, img.OSVersion)
bootTreePipeline.Platform = img.Platform
bootTreePipeline.UEFIVendor = img.Platform.GetUEFIVendor()
bootTreePipeline.ISOLabel = isoLabel
kernelOpts := []string{fmt.Sprintf("inst.stage2=hd:LABEL=%s", isoLabel)}
if img.ISORootKickstart {
kernelOpts = append(kernelOpts, fmt.Sprintf("inst.ks=hd:LABEL=%s:%s", isoLabel, kspath))
}
kernelOpts = append(kernelOpts, img.AdditionalKernelOpts...)
bootTreePipeline.KernelOpts = kernelOpts
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload
// enable ISOLinux on x86_64 only
isoLinuxEnabled := img.Platform.GetArch() == platform.ARCH_X86_64
isoTreePipeline := manifest.NewAnacondaInstallerISOTree(m,
buildPipeline,
anacondaPipeline,
rootfsImagePipeline,
bootTreePipeline,
isoLabel)
isoTreePipeline.PartitionTable = rootfsPartitionTable
isoTreePipeline.Release = img.Release
isoTreePipeline.OSName = img.OSName
isoTreePipeline.Users = img.Users
isoTreePipeline.Groups = img.Groups
isoTreePipeline.PayloadPath = tarPath
if img.ISORootKickstart {
isoTreePipeline.KSPath = kspath
}
isoTreePipeline.SquashfsCompression = img.SquashfsCompression
isoTreePipeline.OSPipeline = osPipeline
isoTreePipeline.KernelOpts = img.AdditionalKernelOpts
isoTreePipeline.ISOLinux = isoLinuxEnabled
isoPipeline := manifest.NewISO(m, buildPipeline, isoTreePipeline, isoLabel)
isoPipeline.Filename = img.Filename
isoPipeline.ISOLinux = isoLinuxEnabled
artifact := isoPipeline.Export()
return artifact, nil
}

View file

@ -1,47 +0,0 @@
package image
import (
"math/rand"
"github.com/osbuild/osbuild-composer/internal/artifact"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
"github.com/osbuild/osbuild-composer/internal/workload"
)
type Archive struct {
Base
Platform platform.Platform
OSCustomizations manifest.OSCustomizations
Environment environment.Environment
Workload workload.Workload
Filename string
}
func NewArchive() *Archive {
return &Archive{
Base: NewBase("archive"),
}
}
func (img *Archive) InstantiateManifest(m *manifest.Manifest,
repos []rpmmd.RepoConfig,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuild(m, runner, repos)
buildPipeline.Checkpoint()
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload
tarPipeline := manifest.NewTar(m, buildPipeline, &osPipeline.Base, "archive")
tarPipeline.Filename = img.Filename
artifact := tarPipeline.Export()
return artifact, nil
}

View file

@ -1,47 +0,0 @@
package image
import (
"math/rand"
"github.com/osbuild/osbuild-composer/internal/artifact"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
"github.com/osbuild/osbuild-composer/internal/workload"
)
type BaseContainer struct {
Base
Platform platform.Platform
OSCustomizations manifest.OSCustomizations
Environment environment.Environment
Workload workload.Workload
Filename string
}
func NewBaseContainer() *BaseContainer {
return &BaseContainer{
Base: NewBase("base-container"),
}
}
func (img *BaseContainer) InstantiateManifest(m *manifest.Manifest,
repos []rpmmd.RepoConfig,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuild(m, runner, repos)
buildPipeline.Checkpoint()
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload
ociPipeline := manifest.NewOCIContainer(m, buildPipeline, osPipeline)
ociPipeline.Filename = img.Filename
artifact := ociPipeline.Export()
return artifact, nil
}

View file

@ -1,29 +0,0 @@
package image
import (
"math/rand"
"github.com/osbuild/osbuild-composer/internal/artifact"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
)
type ImageKind interface {
Name() string
InstantiateManifest(m *manifest.Manifest, repos []rpmmd.RepoConfig, runner runner.Runner, rng *rand.Rand) (*artifact.Artifact, error)
}
type Base struct {
name string
}
func (img Base) Name() string {
return img.name
}
func NewBase(name string) Base {
return Base{
name: name,
}
}

View file

@ -1,133 +0,0 @@
package image
import (
"fmt"
"math/rand"
"github.com/osbuild/osbuild-composer/internal/artifact"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/environment"
"github.com/osbuild/osbuild-composer/internal/manifest"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/platform"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/runner"
"github.com/osbuild/osbuild-composer/internal/workload"
)
type LiveImage struct {
Base
Platform platform.Platform
PartitionTable *disk.PartitionTable
OSCustomizations manifest.OSCustomizations
Environment environment.Environment
Workload workload.Workload
Filename string
Compression string
ForceSize *bool
PartTool osbuild.PartTool
NoBLS bool
OSProduct string
OSVersion string
OSNick string
}
func NewLiveImage() *LiveImage {
return &LiveImage{
Base: NewBase("live-image"),
PartTool: osbuild.PTSfdisk,
}
}
func (img *LiveImage) InstantiateManifest(m *manifest.Manifest,
repos []rpmmd.RepoConfig,
runner runner.Runner,
rng *rand.Rand) (*artifact.Artifact, error) {
buildPipeline := manifest.NewBuild(m, runner, repos)
buildPipeline.Checkpoint()
osPipeline := manifest.NewOS(m, buildPipeline, img.Platform, repos)
osPipeline.PartitionTable = img.PartitionTable
osPipeline.OSCustomizations = img.OSCustomizations
osPipeline.Environment = img.Environment
osPipeline.Workload = img.Workload
osPipeline.NoBLS = img.NoBLS
osPipeline.OSProduct = img.OSProduct
osPipeline.OSVersion = img.OSVersion
osPipeline.OSNick = img.OSNick
imagePipeline := manifest.NewRawImage(m, buildPipeline, osPipeline)
imagePipeline.PartTool = img.PartTool
var artifact *artifact.Artifact
var artifactPipeline manifest.Pipeline
switch img.Platform.GetImageFormat() {
case platform.FORMAT_RAW:
if img.Compression == "" {
imagePipeline.Filename = img.Filename
}
artifactPipeline = imagePipeline
artifact = imagePipeline.Export()
case platform.FORMAT_QCOW2:
qcow2Pipeline := manifest.NewQCOW2(m, buildPipeline, imagePipeline)
if img.Compression == "" {
qcow2Pipeline.Filename = img.Filename
}
qcow2Pipeline.Compat = img.Platform.GetQCOW2Compat()
artifactPipeline = qcow2Pipeline
artifact = qcow2Pipeline.Export()
case platform.FORMAT_VHD:
vpcPipeline := manifest.NewVPC(m, buildPipeline, imagePipeline)
if img.Compression == "" {
vpcPipeline.Filename = img.Filename
}
vpcPipeline.ForceSize = img.ForceSize
artifactPipeline = vpcPipeline
artifact = vpcPipeline.Export()
case platform.FORMAT_VMDK:
vmdkPipeline := manifest.NewVMDK(m, buildPipeline, imagePipeline)
if img.Compression == "" {
vmdkPipeline.Filename = img.Filename
}
artifactPipeline = vmdkPipeline
artifact = vmdkPipeline.Export()
case platform.FORMAT_OVA:
vmdkPipeline := manifest.NewVMDK(m, buildPipeline, imagePipeline)
ovfPipeline := manifest.NewOVF(m, buildPipeline, vmdkPipeline)
artifactPipeline := manifest.NewTar(m, buildPipeline, ovfPipeline, "archive")
artifactPipeline.Format = osbuild.TarArchiveFormatOldgnu
artifactPipeline.RootNode = osbuild.TarRootNodeOmit
artifactPipeline.Filename = img.Filename
artifact = artifactPipeline.Export()
case platform.FORMAT_GCE:
// NOTE(akoutsou): temporary workaround; filename required for GCP
// TODO: define internal raw filename on image type
imagePipeline.Filename = "disk.raw"
archivePipeline := manifest.NewTar(m, buildPipeline, imagePipeline, "archive")
archivePipeline.Format = osbuild.TarArchiveFormatOldgnu
archivePipeline.RootNode = osbuild.TarRootNodeOmit
// these are required to successfully import the image to GCP
archivePipeline.ACLs = common.ToPtr(false)
archivePipeline.SELinux = common.ToPtr(false)
archivePipeline.Xattrs = common.ToPtr(false)
archivePipeline.Filename = img.Filename // filename extension will determine compression
default:
panic("invalid image format for image kind")
}
switch img.Compression {
case "xz":
xzPipeline := manifest.NewXZ(m, buildPipeline, artifactPipeline)
xzPipeline.Filename = img.Filename
artifact = xzPipeline.Export()
case "":
// do nothing
default:
// panic on unknown strings
panic(fmt.Sprintf("unsupported compression type %q", img.Compression))
}
return artifact, nil
}

Some files were not shown because too many files have changed in this diff Show more