package rhel85 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/disk" "github.com/osbuild/osbuild-composer/internal/distro" "github.com/osbuild/osbuild-composer/internal/osbuild2" 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])) treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) if err != nil { return nil, err } partitionTable := defaultPartitionTable(options, t.arch, rng) treePipeline = prependKernelCmdlineStage(treePipeline, t, &partitionTable) if options.Subscription == nil { // RHSM DNF plugins should be by default disabled on RHEL Guest KVM images treePipeline.AddStage(osbuild.NewRHSMStage(&osbuild.RHSMStageOptions{ DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{ ProductID: &osbuild.RHSMStageOptionsDnfPlugin{ Enabled: false, }, SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{ Enabled: false, }, }, })) } treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) pipelines = append(pipelines, *treePipeline) diskfile := "disk.img" 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 == s390xArchName { rootPartition := pt.RootPartition() if rootPartition == nil { panic("s390x image must have a root partition, this is a programming error") } kernelStage := osbuild.NewKernelCmdlineStage(kernelCmdlineStageOptions(rootPartition.Filesystem.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])) treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) if err != nil { return nil, err } partitionTable := defaultPartitionTable(options, t.arch, rng) treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) pipelines = append(pipelines, *treePipeline) diskfile := "disk.img" 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])) treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) if err != nil { return nil, err } partitionTable := defaultPartitionTable(options, t.arch, rng) treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) pipelines = append(pipelines, *treePipeline) diskfile := "disk.img" 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])) treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) if err != nil { return nil, err } partitionTable := defaultPartitionTable(options, t.arch, rng) treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) pipelines = append(pipelines, *treePipeline) diskfile := "disk.img" 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 } // ec2BaseTreePipeline returns the base OS pipeline common for all EC2 image types. // // The expectation is that specific EC2 image types can extend the returned pipeline // by appending additional stages. // // The argument `withRHUI` should be set to `true` only if the image package set includes RHUI client packages. // // Note: the caller of this function has to append the `osbuild.NewSELinuxStage(selinuxStageOptions(false))` stage // as the last one to the returned pipeline. The stage is not appended on purpose, to allow caller to append // any additional stages to the pipeline, but before the SELinuxStage, which must be always the last one. func ec2BaseTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, defaultTarget string, withRHUI bool, pt *disk.PartitionTable) (*osbuild.Pipeline, error) { p := new(osbuild.Pipeline) p.Name = "os" p.Build = "name:build" packages = append(packages, bpPackages...) p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) // If the /boot is on a separate partition, the prefix for the BLS stage must be "" if pt.BootPartition() == 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: "en_US.UTF-8"})) } if keyboard != nil { p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *keyboard})) } else { p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{ Keymap: "us", X11Keymap: &osbuild.X11KeymapOptions{ Layouts: []string{"us"}, }, })) } 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: "UTC"})) } if len(ntpServers) > 0 { p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) } else { p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{ Servers: []osbuild.ChronyConfigServer{ { Hostname: "169.254.169.123", Prefer: common.BoolToPtr(true), Iburst: common.BoolToPtr(true), Minpoll: common.IntToPtr(4), Maxpoll: common.IntToPtr(4), }, }, // empty string will remove any occurrences of the option from the configuration LeapsecTz: common.StringToPtr(""), })) } 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 } p.AddStage(osbuild.NewUsersStage(userOptions)) } if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) } if firewall := c.GetFirewall(); firewall != nil { p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) } p.AddStage(osbuild.NewSystemdLogindStage(&osbuild.SystemdLogindStageOptions{ Filename: "00-getty-fixes.conf", Config: osbuild.SystemdLogindConfigDropin{ Login: osbuild.SystemdLogindConfigLoginSection{ NAutoVTs: common.IntToPtr(0), }, }, })) p.AddStage(osbuild.NewSysconfigStage(&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.BoolToPtr(true), Type: osbuild.IfcfgTypeEthernet, UserCtl: common.BoolToPtr(true), PeerDNS: common.BoolToPtr(true), IPv6Init: common.BoolToPtr(false), }, }, }, })) p.AddStage(osbuild.NewCloudInitStage(&osbuild.CloudInitStageOptions{ Filename: "00-rhel-default-user.cfg", Config: osbuild.CloudInitConfigFile{ SystemInfo: &osbuild.CloudInitConfigSystemInfo{ DefaultUser: &osbuild.CloudInitConfigDefaultUser{ Name: "ec2-user", }, }, }, })) p.AddStage(osbuild.NewModprobeStage(&osbuild.ModprobeStageOptions{ Filename: "blacklist-nouveau.conf", Commands: osbuild.ModprobeConfigCmdList{ osbuild.NewModprobeConfigCmdBlacklist("nouveau"), }, })) p.AddStage(osbuild.NewDracutConfStage(&osbuild.DracutConfStageOptions{ Filename: "sgdisk.conf", Config: osbuild.DracutConfigFile{ Install: []string{"sgdisk"}, }, })) // RHBZ#1822853 p.AddStage(osbuild.NewSystemdUnitStage(&osbuild.SystemdUnitStageOptions{ Unit: "nm-cloud-setup.service", Dropin: "10-rh-enable-for-ec2.conf", Config: osbuild.SystemdServiceUnitDropin{ Service: &osbuild.SystemdUnitServiceSection{ Environment: "NM_CLOUD_SETUP_EC2=yes", }, }, })) p.AddStage(osbuild.NewAuthselectStage(&osbuild.AuthselectStageOptions{ Profile: "sssd", })) if options.Subscription != nil { commands := []string{ fmt.Sprintf("/usr/sbin/subscription-manager register --org=%d --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, })) } else { rhsmStageOptions := &osbuild.RHSMStageOptions{ DnfPlugins: &osbuild.RHSMStageOptionsDnfPlugins{ ProductID: &osbuild.RHSMStageOptionsDnfPlugin{ Enabled: false, }, SubscriptionManager: &osbuild.RHSMStageOptionsDnfPlugin{ Enabled: false, }, }, // RHBZ#1932802 SubMan: &osbuild.RHSMStageOptionsSubMan{ Rhsmcertd: &osbuild.SubManConfigRHSMCERTDSection{ AutoRegistration: common.BoolToPtr(true), }, }, } // Disable RHSM redhat.repo management only if the image uses 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. // RHBZ#1932802 if withRHUI { rhsmStageOptions.SubMan.Rhsm = &osbuild.SubManConfigRHSMSection{ ManageRepos: common.BoolToPtr(false), } } p.AddStage(osbuild.NewRHSMStage(rhsmStageOptions)) } return p, nil } func ec2X86_64BaseTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, defaultTarget string, withRHUI bool, pt *disk.PartitionTable) (*osbuild.Pipeline, error) { treePipeline, err := ec2BaseTreePipeline(repos, packages, bpPackages, c, options, enabledServices, disabledServices, defaultTarget, withRHUI, pt) if err != nil { return nil, err } // EC2 x86_64-specific stages treePipeline.AddStage(osbuild.NewDracutConfStage(&osbuild.DracutConfStageOptions{ Filename: "xen.conf", Config: osbuild.DracutConfigFile{ AddDrivers: []string{ "xen-netfront", "xen-blkfront", }, }, })) return treePipeline, nil } func ec2CommonPipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand, withRHUI bool, diskfile string) ([]osbuild.Pipeline, error) { pipelines := make([]osbuild.Pipeline, 0) pipelines = append(pipelines, *buildPipeline(repos, packageSetSpecs[buildPkgsKey])) partitionTable := ec2PartitionTable(options, t.arch, rng) var treePipeline *osbuild.Pipeline var err error switch arch := t.arch.Name(); arch { // rhel-ec2-x86_64, rhel-ha-ec2 case x86_64ArchName: treePipeline, err = ec2X86_64BaseTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, withRHUI, &partitionTable) // rhel-ec2-aarch64 case aarch64ArchName: treePipeline, err = ec2BaseTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget, withRHUI, &partitionTable) default: return nil, fmt.Errorf("ec2CommonPipelines: unsupported image architecture: %q", arch) } if err != nil { return nil, err } treePipeline.AddStage(osbuild.NewFSTabStage(partitionTable.FSTabStageOptionsV2())) kernelVer := kernelVerStr(packageSetSpecs[blueprintPkgsKey], customizations.GetKernel().Name, t.Arch().Name()) treePipeline.AddStage(bootloaderConfigStage(t, partitionTable, customizations.GetKernel(), kernelVer)) // The last stage must be the SELinux stage treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) pipelines = append(pipelines, *treePipeline) 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, false, 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, true, 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])) treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) if err != nil { return nil, err } treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) 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 } 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])) installerPackages := packageSetSpecs[installerPkgsKey] kernelVer := kernelVerStr(installerPackages, "kernel", t.Arch().Name()) ostreeRepoPath := "/ostree/repo" pipelines = append(pipelines, *anacondaTreePipeline(repos, installerPackages, kernelVer, t.Arch().Name(), ostreePayloadStages(options, ostreeRepoPath))) pipelines = append(pipelines, *bootISOTreePipeline(kernelVer, t.Arch().Name(), ostreeKickstartStageOptions(fmt.Sprintf("file://%s", ostreeRepoPath), options.OSTree.Ref))) pipelines = append(pipelines, *bootISOPipeline(t.Filename(), t.Arch().Name())) 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])) treePipeline, err := osPipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) if err != nil { return nil, err } treePipeline.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) pipelines = append(pipelines, *treePipeline) kernelPkg := new(rpmmd.PackageSpec) installerPackages := packageSetSpecs[installerPkgsKey] for _, pkg := range installerPackages { if pkg.Name == "kernel" { 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)} pipelines = append(pipelines, *anacondaTreePipeline(repos, installerPackages, kernelVer, t.Arch().Name(), tarPayloadStages)) pipelines = append(pipelines, *bootISOTreePipeline(kernelVer, t.Arch().Name(), tarKickstartStageOptions(fmt.Sprintf("file://%s", tarPath)))) pipelines = append(pipelines, *bootISOPipeline(t.Filename(), t.Arch().Name())) 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])) treePipeline, err := ostreeTreePipeline(repos, packageSetSpecs[osPkgsKey], packageSetSpecs[blueprintPkgsKey], customizations, options, t.enabledServices, t.disabledServices, t.defaultTarget) if err != nil { return nil, err } pipelines = append(pipelines, *treePipeline) pipelines = append(pipelines, *ostreeCommitPipeline(options)) 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 } pipelines = append(pipelines, *containerTreePipeline(repos, packageSetSpecs[containerPkgsKey], options, customizations)) pipelines = append(pipelines, *containerPipeline(t)) return pipelines, nil } func buildPipeline(repos []rpmmd.RepoConfig, buildPackageSpecs []rpmmd.PackageSpec) *osbuild.Pipeline { p := new(osbuild.Pipeline) p.Name = "build" p.Runner = "org.osbuild.rhel85" p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(buildPackageSpecs))) p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(true))) return p } func osPipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, defaultTarget string) (*osbuild.Pipeline, error) { p := new(osbuild.Pipeline) p.Name = "os" p.Build = "name:build" packages = append(packages, bpPackages...) p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{})) language, keyboard := c.GetPrimaryLocale() if language != nil { p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) } else { p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) } if keyboard != nil { p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *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: "America/New_York"})) } if len(ntpServers) > 0 { p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) } 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 } p.AddStage(osbuild.NewUsersStage(userOptions)) } if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) } if firewall := c.GetFirewall(); firewall != nil { p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) } // These are the current defaults for the sysconfig stage. This can be changed to be image type exclusive if different configs are needed. p.AddStage(osbuild.NewSysconfigStage(&osbuild.SysconfigStageOptions{ Kernel: osbuild.SysconfigKernelOptions{ UpdateDefault: true, DefaultKernel: "kernel", }, Network: osbuild.SysconfigNetworkOptions{ Networking: true, NoZeroConf: true, }, })) if options.Subscription != nil { commands := []string{ fmt.Sprintf("/usr/sbin/subscription-manager register --org=%d --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, }, )) } return p, nil } func ostreeTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, bpPackages []rpmmd.PackageSpec, c *blueprint.Customizations, options distro.ImageOptions, enabledServices, disabledServices []string, defaultTarget string) (*osbuild.Pipeline, error) { p := new(osbuild.Pipeline) p.Name = "ostree-tree" p.Build = "name:build" packages = append(packages, bpPackages...) p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) p.AddStage(osbuild.NewFixBLSStage(&osbuild.FixBLSStageOptions{})) language, keyboard := c.GetPrimaryLocale() if language != nil { p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: *language})) } else { p.AddStage(osbuild.NewLocaleStage(&osbuild.LocaleStageOptions{Language: "en_US.UTF-8"})) } if keyboard != nil { p.AddStage(osbuild.NewKeymapStage(&osbuild.KeymapStageOptions{Keymap: *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: "America/New_York"})) } if len(ntpServers) > 0 { p.AddStage(osbuild.NewChronyStage(&osbuild.ChronyStageOptions{Timeservers: ntpServers})) } 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 } p.AddStage(osbuild.NewUsersStage(userOptions)) p.AddStage(osbuild.NewFirstBootStage(usersFirstBootOptions(userOptions))) } if services := c.GetServices(); services != nil || enabledServices != nil || disabledServices != nil || defaultTarget != "" { p.AddStage(osbuild.NewSystemdStage(systemdStageOptions(enabledServices, disabledServices, services, defaultTarget))) } if firewall := c.GetFirewall(); firewall != nil { p.AddStage(osbuild.NewFirewallStage(firewallStageOptions(firewall))) } // These are the current defaults for the sysconfig stage. This can be changed to be image type exclusive if different configs are needed. p.AddStage(osbuild.NewSysconfigStage(&osbuild.SysconfigStageOptions{ Kernel: osbuild.SysconfigKernelOptions{ UpdateDefault: true, DefaultKernel: "kernel", }, Network: osbuild.SysconfigNetworkOptions{ Networking: true, NoZeroConf: true, }, })) if options.Subscription != nil { commands := []string{ fmt.Sprintf("/usr/sbin/subscription-manager register --org=%d --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, }, )) } p.AddStage(osbuild.NewSELinuxStage(selinuxStageOptions(false))) 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) *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) *osbuild.Pipeline { p := new(osbuild.Pipeline) p.Name = "container-tree" p.Build = "name:build" p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(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"})) } p.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: "/var/www/html/repo"})) p.AddStage(osbuild.NewOSTreePullStage( &osbuild.OSTreePullStageOptions{Repo: "/var/www/html/repo"}, ostreePullStageInputs("org.osbuild.pipeline", "name:ostree-commit", options.OSTree.Ref), )) return p } func containerPipeline(t *imageType) *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{"httpd", "-D", "FOREGROUND"}, ExposedPorts: []string{"80"}, }, } 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}, ostreePullStageInputs("org.osbuild.source", options.OSTree.Parent, options.OSTree.Ref), )) return stages } func anacondaTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, kernelVer string, arch string, payloadStages []*osbuild.Stage) *osbuild.Pipeline { p := new(osbuild.Pipeline) p.Name = "anaconda-tree" p.Build = "name:build" p.AddStage(osbuild.NewRPMStage(rpmStageOptions(repos), rpmStageInputs(packages))) for _, stage := range payloadStages { p.AddStage(stage) } p.AddStage(osbuild.NewBuildstampStage(buildStampStageOptions(arch))) 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))) return p } func bootISOTreePipeline(kernelVer string, arch string, ksOptions *osbuild.KickstartStageOptions) *osbuild.Pipeline { p := new(osbuild.Pipeline) p.Name = "bootiso-tree" p.Build = "name:build" p.AddStage(osbuild.NewBootISOMonoStage(bootISOMonoStageOptions(kernelVer, arch), bootISOMonoStageInputs())) p.AddStage(osbuild.NewKickstartStage(ksOptions)) p.AddStage(osbuild.NewDiscinfoStage(discinfoStageOptions(arch))) return p } func bootISOPipeline(filename string, arch string) *osbuild.Pipeline { p := new(osbuild.Pipeline) p.Name = "bootiso" p.Build = "name:build" p.AddStage(osbuild.NewXorrisofsStage(xorrisofsStageOptions(filename, arch), xorrisofsStageInputs())) 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" loopback := osbuild.NewLoopbackDevice(&osbuild.LoopbackDeviceOptions{Filename: outputFilename}) p.AddStage(osbuild.NewTruncateStage(&osbuild.TruncateStageOptions{Filename: outputFilename, Size: fmt.Sprintf("%d", pt.Size)})) sfOptions, sfDevices := sfdiskStageOptions(pt, loopback) p.AddStage(osbuild.NewSfdiskStage(sfOptions, sfDevices)) for _, stage := range mkfsStages(pt, loopback) { p.AddStage(stage) } inputName := "root-tree" copyOptions, copyDevices, copyMounts := copyFSTreeOptions(inputName, inputPipelineName, pt, loopback) copyInputs := copyPipelineTreeInputs(inputName, inputPipelineName) p.AddStage(osbuild.NewCopyStage(copyOptions, copyInputs, copyDevices, copyMounts)) 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 } // mkfsStages generates a list of org.osbuild.mkfs.* stages based on a // partition table description for a single device node func mkfsStages(pt *disk.PartitionTable, device *osbuild.Device) []*osbuild2.Stage { stages := make([]*osbuild2.Stage, 0, len(pt.Partitions)) // assume loopback device for simplicity since it's the only one currently supported // panic if the conversion fails devOptions, ok := device.Options.(*osbuild.LoopbackDeviceOptions) if !ok { panic("mkfsStages: failed to convert device options to loopback options") } for _, p := range pt.Partitions { if p.Filesystem == nil { // no filesystem for partition (e.g., BIOS boot) continue } var stage *osbuild.Stage stageDevice := osbuild.NewLoopbackDevice( &osbuild.LoopbackDeviceOptions{ Filename: devOptions.Filename, Start: p.Start, Size: p.Size, }, ) switch p.Filesystem.Type { case "xfs": options := &osbuild.MkfsXfsStageOptions{ UUID: p.Filesystem.UUID, Label: p.Filesystem.Label, } devices := &osbuild.MkfsXfsStageDevices{Device: *stageDevice} stage = osbuild.NewMkfsXfsStage(options, devices) case "vfat": options := &osbuild.MkfsFATStageOptions{ VolID: strings.Replace(p.Filesystem.UUID, "-", "", -1), } devices := &osbuild.MkfsFATStageDevices{Device: *stageDevice} stage = osbuild.NewMkfsFATStage(options, devices) case "btrfs": options := &osbuild.MkfsBtrfsStageOptions{ UUID: p.Filesystem.UUID, Label: p.Filesystem.Label, } devices := &osbuild.MkfsBtrfsStageDevices{Device: *stageDevice} stage = osbuild.NewMkfsBtrfsStage(options, devices) case "ext4": options := &osbuild.MkfsExt4StageOptions{ UUID: p.Filesystem.UUID, Label: p.Filesystem.Label, } devices := &osbuild.MkfsExt4StageDevices{Device: *stageDevice} stage = osbuild.NewMkfsExt4Stage(options, devices) default: panic("unknown fs type " + p.Type) } stages = append(stages, stage) } return stages } 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), qemuStageInputs(inputPipelineName, inputFilename)) p.AddStage(qemuStage) return p } func bootloaderConfigStage(t *imageType, partitionTable disk.PartitionTable, kernel *blueprint.KernelCustomization, kernelVer string) *osbuild.Stage { if t.arch.name == s390xArchName { return osbuild.NewZiplStage(new(osbuild.ZiplStageOptions)) } kernelOptions := t.kernelOptions uefi := t.supportsUEFI() legacy := t.arch.legacy return osbuild.NewGRUB2Stage(grub2StageOptions(partitionTable.RootPartition(), partitionTable.BootPartition(), kernelOptions, kernel, kernelVer, uefi, legacy)) } func bootloaderInstStage(filename string, pt *disk.PartitionTable, arch *architecture, kernelVer string, devices *osbuild.CopyStageDevices, mounts *osbuild.CopyStageMounts, disk *osbuild.Device) *osbuild.Stage { platform := arch.legacy if platform != "" { return osbuild.NewGrub2InstStage(grub2InstStageOptions(filename, pt, platform)) } if arch.name == s390xArchName { devmap := map[string]osbuild.Device(*devices) devmap["disk"] = *disk ziplDevices := osbuild.CopyStageDevices(devmap) return osbuild.NewZiplInstStage(ziplInstStageOptions(kernelVer, pt), &ziplDevices, mounts) } return nil } func kernelVerStr(pkgs []rpmmd.PackageSpec, kernelName, arch string) string { kernelPkg := new(rpmmd.PackageSpec) for _, pkg := range pkgs { if pkg.Name == kernelName { kernelPkg = &pkg break } } if kernelPkg == nil { panic(fmt.Sprintf("kernel package %q not found", kernelName)) } return fmt.Sprintf("%s-%s.%s", kernelPkg.Version, kernelPkg.Release, kernelPkg.Arch) }