debian-forge-composer/internal/distro/rhel86/pipelines.go
Achilleas Koutsou 129536dad0 distro/rhel86,rhel90: don't write keys for ostree commits in user stage
Writing the key to the user home directory has no effect for ostree
commits.  Instead we write them using a fist-boot service.
In certain situations (e.g., when building an upgrade commit against an
existing parent), the user's home directory might not exist during the
build and the user key creation fails in the users stage, so let's
remove it entirely to avoid the issue.
2022-02-23 11:08:24 +01:00

1046 lines
38 KiB
Go

package rhel86
import (
"fmt"
"math/rand"
"path"
"path/filepath"
"github.com/osbuild/osbuild-composer/internal/blueprint"
"github.com/osbuild/osbuild-composer/internal/common"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/distro"
osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2"
"github.com/osbuild/osbuild-composer/internal/rpmmd"
)
func qcow2Pipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, partitionTable)
if err != nil {
return nil, err
}
pipelines = append(pipelines, *treePipeline)
diskfile := "disk.img"
kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name)
imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, partitionTable, t.arch, kernelVer)
pipelines = append(pipelines, *imagePipeline)
qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, "qcow2", "0.10")
pipelines = append(pipelines, *qemuPipeline)
return pipelines, nil
}
func prependKernelCmdlineStage(pipeline *osbuild.Pipeline, t *imageType, pt *disk.PartitionTable) *osbuild.Pipeline {
if t.arch.name == distro.S390xArchName {
rootFs := pt.FindMountable("/")
if rootFs == nil {
panic("s390x image must have a root filesystem, this is a programming error")
}
kernelStage := osbuild.NewKernelCmdlineStage(osbuild.NewKernelCmdlineStageOptions(rootFs.GetFSSpec().UUID, t.kernelOptions))
pipeline.Stages = append([]*osbuild.Stage{kernelStage}, pipeline.Stages...)
}
return pipeline
}
func vhdPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, partitionTable)
if err != nil {
return nil, err
}
pipelines = append(pipelines, *treePipeline)
diskfile := "disk.img"
kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name)
imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, partitionTable, t.arch, kernelVer)
pipelines = append(pipelines, *imagePipeline)
if err != nil {
return nil, err
}
qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, "vpc", "")
pipelines = append(pipelines, *qemuPipeline)
return pipelines, nil
}
func vmdkPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, partitionTable)
if err != nil {
return nil, err
}
pipelines = append(pipelines, *treePipeline)
diskfile := "disk.img"
kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name)
imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, partitionTable, t.arch, kernelVer)
pipelines = append(pipelines, *imagePipeline)
if err != nil {
return nil, err
}
qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, "vmdk", "")
pipelines = append(pipelines, *qemuPipeline)
return pipelines, nil
}
func openstackPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, partitionTable)
if err != nil {
return nil, err
}
pipelines = append(pipelines, *treePipeline)
diskfile := "disk.img"
kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name)
imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, partitionTable, t.arch, kernelVer)
pipelines = append(pipelines, *imagePipeline)
if err != nil {
return nil, err
}
qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename, "qcow2", "")
pipelines = append(pipelines, *qemuPipeline)
return pipelines, nil
}
func ec2CommonPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions,
repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec,
rng *rand.Rand, diskfile string) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
partitionTable, err := t.getPartitionTable(customizations.GetFilesystems(), options, rng)
if err != nil {
return nil, err
}
treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, partitionTable)
if err != nil {
return nil, err
}
pipelines = append(pipelines, *treePipeline)
kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name)
imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, partitionTable, t.arch, kernelVer)
pipelines = append(pipelines, *imagePipeline)
return pipelines, nil
}
// ec2Pipelines returns pipelines which produce uncompressed EC2 images which are expected to use RHSM for content
func ec2Pipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
return ec2CommonPipelines(t, customizations, options, repos, packageSetSpecs, rng, t.Filename())
}
// rhelEc2Pipelines returns pipelines which produce XZ-compressed EC2 images which are expected to use RHUI for content
func rhelEc2Pipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
rawImageFilename := "image.raw"
pipelines, err := ec2CommonPipelines(t, customizations, options, repos, packageSetSpecs, rng, rawImageFilename)
if err != nil {
return nil, err
}
lastPipeline := pipelines[len(pipelines)-1]
pipelines = append(pipelines, *xzArchivePipeline(lastPipeline.Name, rawImageFilename, t.Filename()))
return pipelines, nil
}
func tarPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, nil)
if err != nil {
return nil, err
}
pipelines = append(pipelines, *treePipeline)
tarPipeline := osbuild.Pipeline{
Name: "root-tar",
Build: "name:build",
}
tarPipeline.AddStage(tarStage("os", "root.tar.xz"))
pipelines = append(pipelines, tarPipeline)
return pipelines, nil
}
//makeISORootPath return a path that can be used to address files and folders in
//the root of the iso
func makeISORootPath(p string) string {
fullpath := path.Join("/run/install/repo", p)
return fmt.Sprintf("file://%s", fullpath)
}
func edgeInstallerPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
installerPackages := packageSetSpecs[installerPkgsKey]
d := t.arch.distro
archName := t.arch.name
kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(installerPackages, "kernel")
ostreeRepoPath := "/ostree/repo"
payloadStages := ostreePayloadStages(options, ostreeRepoPath)
kickstartOptions := ostreeKickstartStageOptions(makeISORootPath(ostreeRepoPath), options.OSTree.Ref)
pipelines = append(pipelines, *anacondaTreePipeline(repos, installerPackages, kernelVer, archName, d.product, d.osVersion, "edge"))
isolabel := fmt.Sprintf(d.isolabelTmpl, archName)
pipelines = append(pipelines, *bootISOTreePipeline(kernelVer, archName, d.vendor, d.product, d.osVersion, isolabel, kickstartOptions, payloadStages))
pipelines = append(pipelines, *bootISOPipeline(t.Filename(), d.isolabelTmpl, archName, false))
return pipelines, nil
}
func tarInstallerPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, nil)
if err != nil {
return nil, err
}
pipelines = append(pipelines, *treePipeline)
kernelPkg := new(rpmmd.PackageSpec)
installerPackages := packageSetSpecs[installerPkgsKey]
for _, pkg := range installerPackages {
if pkg.Name == "kernel" {
// Implicit memory alasing doesn't couse any bug in this case
/* #nosec G601 */
kernelPkg = &pkg
break
}
}
if kernelPkg == nil {
return nil, fmt.Errorf("kernel package not found in installer package set")
}
kernelVer := fmt.Sprintf("%s-%s.%s", kernelPkg.Version, kernelPkg.Release, kernelPkg.Arch)
tarPath := "/liveimg.tar"
tarPayloadStages := []*osbuild.Stage{tarStage("os", tarPath)}
kickstartOptions := tarKickstartStageOptions(makeISORootPath(tarPath))
archName := t.arch.name
d := t.arch.distro
pipelines = append(pipelines, *anacondaTreePipeline(repos, installerPackages, kernelVer, archName, d.product, d.osVersion, "BaseOS"))
isolabel := fmt.Sprintf(d.isolabelTmpl, archName)
pipelines = append(pipelines, *bootISOTreePipeline(kernelVer, archName, d.vendor, d.product, d.osVersion, isolabel, kickstartOptions, tarPayloadStages))
pipelines = append(pipelines, *bootISOPipeline(t.Filename(), d.isolabelTmpl, t.Arch().Name(), true))
return pipelines, nil
}
func edgeCorePipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
treePipeline, err := osPipeline(t, repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, nil)
if err != nil {
return nil, err
}
pipelines = append(pipelines, *treePipeline)
pipelines = append(pipelines, *ostreeCommitPipeline(options, t.arch.distro.osVersion))
return pipelines, nil
}
func edgeCommitPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines, err := edgeCorePipelines(t, customizations, options, repos, packageSetSpecs)
if err != nil {
return nil, err
}
tarPipeline := osbuild.Pipeline{
Name: "commit-archive",
Build: "name:build",
}
tarPipeline.AddStage(tarStage("ostree-commit", t.Filename()))
pipelines = append(pipelines, tarPipeline)
return pipelines, nil
}
func edgeContainerPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines, err := edgeCorePipelines(t, customizations, options, repos, packageSetSpecs)
if err != nil {
return nil, err
}
nginxConfigPath := "/etc/nginx.conf"
httpPort := "8080"
pipelines = append(pipelines, *containerTreePipeline(repos, packageSetSpecs[containerPkgsKey], options, customizations, nginxConfigPath, httpPort))
pipelines = append(pipelines, *containerPipeline(t, nginxConfigPath, httpPort))
return pipelines, nil
}
func edgeImagePipelines(t *imageType, filename string, options distro.ImageOptions, rng *rand.Rand) ([]osbuild.Pipeline, string, error) {
pipelines := make([]osbuild.Pipeline, 0)
ostreeRepoPath := "/ostree/repo"
imgName := "image.raw"
partitionTable, err := t.getPartitionTable(nil, options, rng)
if err != nil {
return nil, "", err
}
// prepare ostree deployment tree
treePipeline := ostreeDeployPipeline(t, partitionTable, ostreeRepoPath, nil, "", rng, options)
pipelines = append(pipelines, *treePipeline)
// make raw image from tree
imagePipeline := liveImagePipeline(treePipeline.Name, imgName, partitionTable, t.arch, "")
pipelines = append(pipelines, *imagePipeline)
// compress image
xzPipeline := xzArchivePipeline(imagePipeline.Name, imgName, filename)
pipelines = append(pipelines, *xzPipeline)
return pipelines, xzPipeline.Name, nil
}
func edgeRawImagePipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
imgName := t.filename
// create the raw image
imagePipelines, _, err := edgeImagePipelines(t, imgName, options, rng)
if err != nil {
return nil, err
}
pipelines = append(pipelines, imagePipelines...)
return pipelines, nil
}
func buildPipeline(repos []rpmmd.RepoConfig, buildPackageSpecs []rpmmd.PackageSpec, runner string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "build"
p.Runner = runner
p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), osbuild.NewRpmStageSourceFilesInputs(buildPackageSpecs)))
p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(true)))
return p
}
func osPipeline(t *imageType,
repos []rpmmd.RepoConfig,
packages []rpmmd.PackageSpec,
bpPackages []rpmmd.PackageSpec,
c *blueprint.Customizations,
options distro.ImageOptions,
pt *disk.PartitionTable) (*osbuild.Pipeline, error) {
imageConfig := t.getDefaultImageConfig()
p := new(osbuild.Pipeline)
if t.rpmOstree {
p.Name = "ostree-tree"
} else {
p.Name = "os"
}
p.Build = "name:build"
packages = append(packages, bpPackages...)
if t.rpmOstree && options.OSTree.Parent != "" && options.OSTree.URL != "" {
p.AddStage(osbuild.NewOSTreePasswdStage("org.osbuild.source", options.OSTree.Parent))
}
p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), osbuild.NewRpmStageSourceFilesInputs(packages)))
// If the /boot is on a separate partition, the prefix for the BLS stage must be ""
if pt == nil || pt.FindMountable("/boot") == nil {
p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{}))
} else {
p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{Prefix: common.StringToPtr("")}))
}
language, keyboard := c.GetPrimaryLocale()
if language != nil {
p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language}))
} else {
p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: imageConfig.Locale}))
}
if keyboard != nil {
p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard}))
} else if imageConfig.Keyboard != nil {
p.AddStage(osbuild.NewKeymapStage(imageConfig.Keyboard))
}
if hostname := c.GetHostname(); hostname != nil {
p.AddStage(osbuild.NewHostnameStage(&osbuild.HostnameStageOptions{Hostname: *hostname}))
}
timezone, ntpServers := c.GetTimezoneSettings()
if timezone != nil {
p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: *timezone}))
} else {
p.AddStage(osbuild.NewTimezoneStage(&osbuild.TimezoneStageOptions{Zone: imageConfig.Timezone}))
}
if len(ntpServers) > 0 {
p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers}))
} else if imageConfig.TimeSynchronization != nil {
p.AddStage(osbuild.NewChronyStage(imageConfig.TimeSynchronization))
}
if groups := c.GetGroups(); len(groups) > 0 {
p.AddStage(osbuild.NewGroupsStage(groupStageOptions(groups)))
}
if users := c.GetUsers(); len(users) > 0 {
userOptions, err := userStageOptions(users)
if err != nil {
return nil, err
}
if t.rpmOstree {
// for ostree, writing the key during user creation is redundant
// and can cause issues so create users without keys and write them
// on first boot
userOptionsSansKeys := new(osbuild.UsersStageOptions)
userOptionsSansKeys.Users = make(map[string]osbuild.UsersStageOptionsUser, len(userOptions.Users))
for name, options := range userOptions.Users {
userOptionsSansKeys.Users[name] = osbuild.UsersStageOptionsUser{
UID: options.UID,
GID: options.GID,
Groups: options.Groups,
Description: options.Description,
Home: options.Home,
Shell: options.Shell,
Password: options.Password,
Key: nil,
}
}
p.AddStage(osbuild.NewUsersStage(userOptionsSansKeys))
p.AddStage(osbuild.NewFirstBootStage(usersFirstBootOptions(userOptions)))
} else {
p.AddStage(osbuild.NewUsersStage(userOptions))
}
}
if services := c.GetServices(); services != nil || imageConfig.EnabledServices != nil ||
imageConfig.DisabledServices != nil || imageConfig.DefaultTarget != "" {
p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(
imageConfig.EnabledServices,
imageConfig.DisabledServices,
services,
imageConfig.DefaultTarget,
)))
}
if firewall := c.GetFirewall(); firewall != nil {
p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall)))
}
for _, sysconfigConfig := range imageConfig.Sysconfig {
p.AddStage(osbuild.NewSysconfigStage(sysconfigConfig))
}
if t.arch.distro.isRHEL() {
if options.Subscription != nil {
commands := []string{
fmt.Sprintf("/usr/sbin/subscription-manager register --org=%s --activationkey=%s --serverurl %s --baseurl %s", options.Subscription.Organization, options.Subscription.ActivationKey, options.Subscription.ServerUrl, options.Subscription.BaseUrl),
}
if options.Subscription.Insights {
commands = append(commands, "/usr/bin/insights-client --register")
}
p.AddStage(osbuild.NewFirstBootStage(&osbuild.FirstBootStageOptions{
Commands: commands,
WaitForNetwork: true,
}))
if rhsmConfig, exists := imageConfig.RHSMConfig[distro.RHSMConfigWithSubscription]; exists {
p.AddStage(osbuild.NewRHSMStage(rhsmConfig))
}
} else {
if rhsmConfig, exists := imageConfig.RHSMConfig[distro.RHSMConfigNoSubscription]; exists {
p.AddStage(osbuild.NewRHSMStage(rhsmConfig))
}
}
}
for _, systemdLogindConfig := range imageConfig.SystemdLogind {
p.AddStage(osbuild.NewSystemdLogindStage(systemdLogindConfig))
}
for _, cloudInitConfig := range imageConfig.CloudInit {
p.AddStage(osbuild.NewCloudInitStage(cloudInitConfig))
}
for _, modprobeConfig := range imageConfig.Modprobe {
p.AddStage(osbuild.NewModprobeStage(modprobeConfig))
}
for _, dracutConfConfig := range imageConfig.DracutConf {
p.AddStage(osbuild.NewDracutConfStage(dracutConfConfig))
}
for _, systemdUnitConfig := range imageConfig.SystemdUnit {
p.AddStage(osbuild.NewSystemdUnitStage(systemdUnitConfig))
}
if authselectConfig := imageConfig.Authselect; authselectConfig != nil {
p.AddStage(osbuild.NewAuthselectStage(authselectConfig))
}
if seLinuxConfig := imageConfig.SELinuxConfig; seLinuxConfig != nil {
p.AddStage(osbuild.NewSELinuxConfigStage(seLinuxConfig))
}
if tunedConfig := imageConfig.Tuned; tunedConfig != nil {
p.AddStage(osbuild.NewTunedStage(tunedConfig))
}
for _, tmpfilesdConfig := range imageConfig.Tmpfilesd {
p.AddStage(osbuild.NewTmpfilesdStage(tmpfilesdConfig))
}
for _, pamLimitsConfConfig := range imageConfig.PamLimitsConf {
p.AddStage(osbuild.NewPamLimitsConfStage(pamLimitsConfConfig))
}
for _, sysctldConfig := range imageConfig.Sysctld {
p.AddStage(osbuild.NewSysctldStage(sysctldConfig))
}
for _, dnfConfig := range imageConfig.DNFConfig {
p.AddStage(osbuild.NewDNFConfigStage(dnfConfig))
}
if sshdConfig := imageConfig.SshdConfig; sshdConfig != nil {
p.AddStage((osbuild.NewSshdConfigStage(sshdConfig)))
}
if pt != nil {
p = prependKernelCmdlineStage(p, t, pt)
p.AddStage(osbuild.NewFSTabStage(osbuild.NewFSTabStageOptions(pt)))
kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(bpPackages, c.GetKernel().Name)
p.AddStage(bootloaderConfigStage(t, *pt, c.GetKernel(), kernelVer, false, false))
}
p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false)))
if t.rpmOstree {
p.AddStage(osbuild.NewOSTreePrepTreeStage(&osbuild.OSTreePrepTreeStageOptions{
EtcGroupMembers: []string{
// NOTE: We may want to make this configurable.
"wheel", "docker",
},
}))
}
return p, nil
}
func ostreeCommitPipeline(options distro.ImageOptions, osVersion string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "ostree-commit"
p.Build = "name:build"
p.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: "/repo"}))
commitStageInput := new(osbuild.OSTreeCommitStageInput)
commitStageInput.Type = "org.osbuild.tree"
commitStageInput.Origin = "org.osbuild.pipeline"
commitStageInput.References = osbuild.OSTreeCommitStageReferences{"name:ostree-tree"}
p.AddStage(osbuild.NewOSTreeCommitStage(
&osbuild.OSTreeCommitStageOptions{
Ref: options.OSTree.Ref,
OSVersion: osVersion,
Parent: options.OSTree.Parent,
},
&osbuild.OSTreeCommitStageInputs{Tree: commitStageInput}),
)
return p
}
func tarStage(source, filename string) *osbuild.Stage {
tree := new(osbuild.TarStageInput)
tree.Type = "org.osbuild.tree"
tree.Origin = "org.osbuild.pipeline"
tree.References = []string{"name:" + source}
return osbuild.NewTarStage(&osbuild.TarStageOptions{Filename: filename}, &osbuild.TarStageInputs{Tree: tree})
}
func containerTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, options distro.ImageOptions, c *blueprint.Customizations, nginxConfigPath, listenPort string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "container-tree"
p.Build = "name:build"
p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), osbuild.NewRpmStageSourceFilesInputs(packages)))
language, _ := c.GetPrimaryLocale()
if language != nil {
p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language}))
} else {
p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US"}))
}
htmlRoot := "/usr/share/nginx/html"
repoPath := filepath.Join(htmlRoot, "repo")
p.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: repoPath}))
p.AddStage(osbuild.NewOSTreePullStage(
&osbuild.OSTreePullStageOptions{Repo: repoPath},
osbuild.NewOstreePullStageInputs("org.osbuild.pipeline", "name:ostree-commit", options.OSTree.Ref),
))
// make nginx log directory world writeable, otherwise nginx can't start in
// an unprivileged container
p.AddStage(osbuild.NewChmodStage(chmodStageOptions("/var/log/nginx", "o+w", true)))
p.AddStage(osbuild.NewNginxConfigStage(nginxConfigStageOptions(nginxConfigPath, htmlRoot, listenPort)))
return p
}
func containerPipeline(t *imageType, nginxConfigPath, listenPort string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "container"
p.Build = "name:build"
options := &osbuild.OCIArchiveStageOptions{
Architecture: t.arch.Name(),
Filename: t.Filename(),
Config: &osbuild.OCIArchiveConfig{
Cmd: []string{"nginx", "-c", nginxConfigPath},
ExposedPorts: []string{listenPort},
},
}
baseInput := new(osbuild.OCIArchiveStageInput)
baseInput.Type = "org.osbuild.tree"
baseInput.Origin = "org.osbuild.pipeline"
baseInput.References = []string{"name:container-tree"}
inputs := &osbuild.OCIArchiveStageInputs{Base: baseInput}
p.AddStage(osbuild.NewOCIArchiveStage(options, inputs))
return p
}
func ostreePayloadStages(options distro.ImageOptions, ostreeRepoPath string) []*osbuild.Stage {
stages := make([]*osbuild.Stage, 0)
// ostree commit payload
stages = append(stages, osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: ostreeRepoPath}))
stages = append(stages, osbuild.NewOSTreePullStage(
&osbuild.OSTreePullStageOptions{Repo: ostreeRepoPath},
osbuild.NewOstreePullStageInputs("org.osbuild.source", options.OSTree.Parent, options.OSTree.Ref),
))
return stages
}
func edgeSimplifiedInstallerPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) {
pipelines := make([]osbuild.Pipeline, 0)
pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey], t.arch.distro.runner))
installerPackages := packageSetSpecs[installerPkgsKey]
kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(installerPackages, "kernel")
imgName := "disk.img.xz"
installDevice := customizations.GetInstallationDevice()
// create the raw image
imagePipelines, imgPipelineName, err := edgeImagePipelines(t, imgName, options, rng)
if err != nil {
return nil, err
}
pipelines = append(pipelines, imagePipelines...)
// create boot ISO with raw image
d := t.arch.distro
archName := t.arch.name
installerTreePipeline := simplifiedInstallerTreePipeline(repos, installerPackages, kernelVer, archName, d.product, d.osVersion, "edge")
isolabel := fmt.Sprintf(d.isolabelTmpl, archName)
efibootTreePipeline := simplifiedInstallerEFIBootTreePipeline(installDevice, kernelVer, archName, d.vendor, d.product, d.osVersion, isolabel)
bootISOTreePipeline := simplifiedInstallerBootISOTreePipeline(imgPipelineName, kernelVer)
pipelines = append(pipelines, *installerTreePipeline, *efibootTreePipeline, *bootISOTreePipeline)
pipelines = append(pipelines, *bootISOPipeline(t.Filename(), d.isolabelTmpl, t.Arch().Name(), false))
return pipelines, nil
}
func simplifiedInstallerBootISOTreePipeline(archivePipelineName, kver string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "bootiso-tree"
p.Build = "name:build"
p.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: "input://file/disk.img.xz",
To: "tree:///disk.img.xz",
},
},
},
osbuild.NewFilesInputs(osbuild.NewFilesInputReferencesPipeline(archivePipelineName, "disk.img.xz")),
))
p.AddStage(osbuild.NewMkdirStage(
&osbuild.MkdirStageOptions{
Paths: []osbuild.Path{
{
Path: "images",
},
{
Path: "images/pxeboot",
},
},
},
))
pt := disk.PartitionTable{
Size: 20971520,
SectorSize: 512,
Partitions: []disk.Partition{
{
Start: 0,
Size: 20971520,
Payload: &disk.Filesystem{
Type: "vfat",
Mountpoint: "/",
},
},
},
}
filename := "images/efiboot.img"
loopback := osbuild.NewLoopbackDevice(&osbuild.LoopbackDeviceOptions{Filename: filename})
p.AddStage(osbuild.NewTruncateStage(&osbuild.TruncateStageOptions{Filename: filename, Size: fmt.Sprintf("%d", pt.Size)}))
for _, stage := range osbuild.GenMkfsStages(&pt, loopback) {
p.AddStage(stage)
}
inputName := "root-tree"
copyInputs := osbuild.NewCopyStagePipelineTreeInputs(inputName, "efiboot-tree")
copyOptions, copyDevices, copyMounts := osbuild.GenCopyFSTreeOptions(inputName, "efiboot-tree", filename, &pt)
p.AddStage(osbuild.NewCopyStage(copyOptions, copyInputs, copyDevices, copyMounts))
inputName = "coi"
copyInputs = osbuild.NewCopyStagePipelineTreeInputs(inputName, "coi-tree")
p.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/boot/vmlinuz-%s", inputName, kver),
To: "tree:///images/pxeboot/vmlinuz",
},
{
From: fmt.Sprintf("input://%s/boot/initramfs-%s.img", inputName, kver),
To: "tree:///images/pxeboot/initrd.img",
},
},
},
copyInputs,
))
inputName = "efi-tree"
copyInputs = osbuild.NewCopyStagePipelineTreeInputs(inputName, "efiboot-tree")
p.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/EFI", inputName),
To: "tree:///",
},
},
},
copyInputs,
))
return p
}
func simplifiedInstallerEFIBootTreePipeline(installDevice, kernelVer, arch, vendor, product, osVersion, isolabel string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "efiboot-tree"
p.Build = "name:build"
p.AddStage(osbuild.NewGrubISOStage(grubISOStageOptions(installDevice, kernelVer, arch, vendor, product, osVersion, isolabel)))
return p
}
func simplifiedInstallerTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, kernelVer, arch, product, osVersion, variant string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "coi-tree"
p.Build = "name:build"
p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), osbuild.NewRpmStageSourceFilesInputs(packages)))
p.AddStage(osbuild.NewBuildstampStage(buildStampStageOptions(arch, product, osVersion, variant)))
p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"}))
p.AddStage(osbuild.NewDracutStage(dracutStageOptions(kernelVer, arch, []string{"coreos-installer"})))
return p
}
func ostreeDeployPipeline(
t *imageType,
pt *disk.PartitionTable,
repoPath string,
kernel *blueprint.KernelCustomization,
kernelVer string,
rng *rand.Rand,
options distro.ImageOptions,
) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "image-tree"
p.Build = "name:build"
osname := "redhat"
remote := "rhel-edge"
p.AddStage(osbuild.OSTreeInitFsStage())
p.AddStage(osbuild.NewOSTreePullStage(
&osbuild.OSTreePullStageOptions{Repo: repoPath, Remote: remote},
osbuild.NewOstreePullStageInputs("org.osbuild.source", options.OSTree.Parent, options.OSTree.Ref),
))
p.AddStage(osbuild.NewOSTreeOsInitStage(
&osbuild.OSTreeOsInitStageOptions{
OSName: osname,
},
))
p.AddStage(osbuild.NewOSTreeConfigStage(ostreeConfigStageOptions(repoPath, true)))
p.AddStage(osbuild.NewMkdirStage(efiMkdirStageOptions()))
kernelOpts := []string{
"console=tty0",
"console=ttyS0",
}
kernelOpts = append(kernelOpts, osbuild.GenImageKernelOptions(pt)...)
p.AddStage(osbuild.NewOSTreeDeployStage(
&osbuild.OSTreeDeployStageOptions{
OsName: osname,
Ref: options.OSTree.Ref,
Remote: remote,
Mounts: []string{"/boot", "/boot/efi"},
Rootfs: osbuild.Rootfs{
Label: "root",
},
KernelOpts: kernelOpts,
},
))
if options.OSTree.URL != "" {
p.AddStage(osbuild.NewOSTreeRemotesStage(
&osbuild.OSTreeRemotesStageOptions{
Repo: "/ostree/repo",
Remotes: []osbuild.OSTreeRemote{
{
Name: remote,
URL: options.OSTree.URL,
},
},
},
))
}
p.AddStage(osbuild.NewOSTreeFillvarStage(
&osbuild.OSTreeFillvarStageOptions{
Deployment: osbuild.OSTreeDeployment{
OSName: osname,
Ref: options.OSTree.Ref,
},
},
))
fstabOptions := osbuild.NewFSTabStageOptions(pt)
fstabOptions.OSTree = &osbuild.OSTreeFstab{
Deployment: osbuild.OSTreeDeployment{
OSName: osname,
Ref: options.OSTree.Ref,
},
}
p.AddStage(osbuild.NewFSTabStage(fstabOptions))
// TODO: Add users?
p.AddStage(bootloaderConfigStage(t, *pt, kernel, kernelVer, true, true))
p.AddStage(osbuild.NewOSTreeSelinuxStage(
&osbuild.OSTreeSelinuxStageOptions{
Deployment: osbuild.OSTreeDeployment{
OSName: osname,
Ref: options.OSTree.Ref,
},
},
))
return p
}
func anacondaTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, kernelVer, arch, product, osVersion, variant string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "anaconda-tree"
p.Build = "name:build"
p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), osbuild.NewRpmStageSourceFilesInputs(packages)))
p.AddStage(osbuild.NewBuildstampStage(buildStampStageOptions(arch, product, osVersion, variant)))
p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"}))
rootPassword := ""
rootUser := osbuild.UsersStageOptionsUser{
Password: &rootPassword,
}
installUID := 0
installGID := 0
installHome := "/root"
installShell := "/usr/libexec/anaconda/run-anaconda"
installPassword := ""
installUser := osbuild.UsersStageOptionsUser{
UID: &installUID,
GID: &installGID,
Home: &installHome,
Shell: &installShell,
Password: &installPassword,
}
usersStageOptions := &osbuild.UsersStageOptions{
Users: map[string]osbuild.UsersStageOptionsUser{
"root": rootUser,
"install": installUser,
},
}
p.AddStage(osbuild.NewUsersStage(usersStageOptions))
p.AddStage(osbuild.NewAnacondaStage(anacondaStageOptions()))
p.AddStage(osbuild.NewLoraxScriptStage(loraxScriptStageOptions(arch)))
p.AddStage(osbuild.NewDracutStage(dracutStageOptions(kernelVer, arch, []string{
"anaconda",
"rdma",
"rngd",
"multipath",
"fcoe",
"fcoe-uefi",
"iscsi",
"lunmask",
"nfs",
})))
return p
}
func bootISOTreePipeline(kernelVer, arch, vendor, product, osVersion, isolabel string, ksOptions *osbuild.KickstartStageOptions, payloadStages []*osbuild.Stage) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "bootiso-tree"
p.Build = "name:build"
p.AddStage(osbuild.NewBootISOMonoStage(bootISOMonoStageOptions(kernelVer, arch, vendor, product, osVersion, isolabel), osbuild.NewBootISOMonoStagePipelineTreeInputs("anaconda-tree")))
p.AddStage(osbuild.NewKickstartStage(ksOptions))
p.AddStage(osbuild.NewDiscinfoStage(discinfoStageOptions(arch)))
for _, stage := range payloadStages {
p.AddStage(stage)
}
return p
}
func bootISOPipeline(filename, isolabel, arch string, isolinux bool) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "bootiso"
p.Build = "name:build"
p.AddStage(osbuild.NewXorrisofsStage(xorrisofsStageOptions(filename, isolabel, arch, isolinux), osbuild.NewXorrisofsStagePipelineTreeInputs("bootiso-tree")))
p.AddStage(osbuild.NewImplantisomd5Stage(&osbuild.Implantisomd5StageOptions{Filename: filename}))
return p
}
func liveImagePipeline(inputPipelineName string, outputFilename string, pt *disk.PartitionTable, arch *architecture, kernelVer string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "image"
p.Build = "name:build"
for _, stage := range osbuild.GenImagePrepareStages(pt, outputFilename) {
p.AddStage(stage)
}
inputName := "root-tree"
copyOptions, copyDevices, copyMounts := osbuild.GenCopyFSTreeOptions(inputName, inputPipelineName, outputFilename, pt)
copyInputs := osbuild.NewCopyStagePipelineTreeInputs(inputName, inputPipelineName)
p.AddStage(osbuild.NewCopyStage(copyOptions, copyInputs, copyDevices, copyMounts))
for _, stage := range osbuild.GenImageFinishStages(pt, outputFilename) {
p.AddStage(stage)
}
loopback := osbuild.NewLoopbackDevice(&osbuild.LoopbackDeviceOptions{Filename: outputFilename})
p.AddStage(bootloaderInstStage(outputFilename, pt, arch, kernelVer, copyDevices, copyMounts, loopback))
return p
}
func xzArchivePipeline(inputPipelineName, inputFilename, outputFilename string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = "archive"
p.Build = "name:build"
p.AddStage(osbuild.NewXzStage(
osbuild.NewXzStageOptions(outputFilename),
osbuild.NewFilesInputs(osbuild.NewFilesInputReferencesPipeline(inputPipelineName, inputFilename)),
))
return p
}
func qemuPipeline(inputPipelineName, inputFilename, outputFilename, format, qcow2Compat string) *osbuild.Pipeline {
p := new(osbuild.Pipeline)
p.Name = format
p.Build = "name:build"
qemuStage := osbuild.NewQEMUStage(qemuStageOptions(outputFilename, format, qcow2Compat), osbuild.NewQemuStagePipelineFilesInputs(inputPipelineName, inputFilename))
p.AddStage(qemuStage)
return p
}
func bootloaderConfigStage(t *imageType, partitionTable disk.PartitionTable, kernel *blueprint.KernelCustomization, kernelVer string, install, greenboot bool) *osbuild.Stage {
if t.arch.name == distro.S390xArchName {
return osbuild.NewZiplStage(new(osbuild.ZiplStageOptions))
}
kernelOptions := t.kernelOptions
uefi := t.supportsUEFI()
legacy := t.arch.legacy
options := osbuild.NewGrub2StageOptions(&partitionTable, kernelOptions, kernel, kernelVer, uefi, legacy, t.arch.distro.vendor, install)
options.Greenboot = greenboot
// before unifying the org.osbuild.grub2 stage option generator, we didn't
// set the following for RHEL 8.5, so we need to revert here to maintain
// the old behaviour
if uefi {
options.UEFI.Unified = false
}
return osbuild.NewGRUB2Stage(options)
}
func bootloaderInstStage(filename string, pt *disk.PartitionTable, arch *architecture, kernelVer string, devices *osbuild.Devices, mounts *osbuild.Mounts, disk *osbuild.Device) *osbuild.Stage {
platform := arch.legacy
if platform != "" {
return osbuild.NewGrub2InstStage(osbuild.NewGrub2InstStageOption(filename, pt, platform))
}
if arch.name == distro.S390xArchName {
return osbuild.NewZiplInstStage(osbuild.NewZiplInstStageOptions(kernelVer, pt), disk, devices, mounts)
}
return nil
}