package rhel84 import ( "encoding/json" "fmt" "math/rand" "path/filepath" "strings" "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/blueprint" "github.com/osbuild/osbuild-composer/internal/rpmmd" ) const ( kspath = "/usr/share/anaconda/interactive-defaults.ks" ) type pipelinesFunc func(t *imageTypeS2, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec, rng *rand.Rand) ([]osbuild.Pipeline, error) type imageTypeS2 struct { arch *architecture name string filename string mimeType string packageSets map[string]rpmmd.PackageSet enabledServices []string disabledServices []string defaultTarget string kernelOptions string bootable bool bootISO bool rpmOstree bool defaultSize uint64 buildPipelines []string payloadPipelines []string exports []string partitionTableGenerator func(imageSize uint64, arch distro.Arch, rng *rand.Rand) disk.PartitionTable pipelines pipelinesFunc } func (t *imageTypeS2) Arch() distro.Arch { return t.arch } func (t *imageTypeS2) Name() string { return t.name } func (t *imageTypeS2) Filename() string { return t.filename } func (t *imageTypeS2) MIMEType() string { return t.mimeType } func (t *imageTypeS2) OSTreeRef() string { if t.rpmOstree { return fmt.Sprintf(ostreeRef, t.arch.name) } return "" } func (t *imageTypeS2) Size(size uint64) uint64 { const MegaByte = 1024 * 1024 // Microsoft Azure requires vhd images to be rounded up to the nearest MB if t.name == "vhd" && size%MegaByte != 0 { size = (size/MegaByte + 1) * MegaByte } if size == 0 { size = t.defaultSize } return size } func (t *imageTypeS2) PartitionType() string { return "" } func (t *imageTypeS2) Packages(bp blueprint.Blueprint) ([]string, []string) { packages := append(t.packageSets["packages"].Include, bp.GetPackages()...) timezone, _ := bp.Customizations.GetTimezoneSettings() if timezone != nil { packages = append(packages, "chrony") } // copy the list of excluded packages from the image type // and subtract any packages found in the blueprint (this // will not handle the issue with dependencies present in // the list of excluded packages, but it will create a // possibility of a workaround at least) excludedPackages := append([]string(nil), t.packageSets["packages"].Exclude...) for _, pkg := range bp.GetPackages() { // removePackage is fine if the package doesn't exist excludedPackages = removePackage(excludedPackages, pkg) } return packages, excludedPackages } func (t *imageTypeS2) BuildPackages() []string { buildPackages := append(t.arch.distro.buildPackages, t.arch.buildPackages...) if t.rpmOstree { buildPackages = append(buildPackages, "rpm-ostree") } if t.bootISO { buildPackages = append(buildPackages, t.packageSets["build"].Include...) } return buildPackages } func (t *imageTypeS2) PackageSets(bp blueprint.Blueprint) map[string]rpmmd.PackageSet { sets := map[string]rpmmd.PackageSet{ "build-packages": { Include: t.BuildPackages(), }, } for name, pkgSet := range t.packageSets { if name == "packages" { // treat base packages separately to combine with blueprint packages := new(rpmmd.PackageSet) packages.Include, packages.Exclude = t.Packages(bp) sets[name] = *packages continue } sets[name] = pkgSet } return sets } func (t *imageTypeS2) BuildPipelines() []string { return t.buildPipelines } func (t *imageTypeS2) PayloadPipelines() []string { return t.payloadPipelines } func (t *imageTypeS2) PayloadPackageSets() []string { return []string{"packages"} } func (t *imageTypeS2) Exports() []string { return t.exports } func (t *imageTypeS2) Manifest(c *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSpecSets map[string][]rpmmd.PackageSpec, seed int64) (distro.Manifest, error) { if err := t.checkOptions(c, options); err != nil { return distro.Manifest{}, err } source := rand.NewSource(seed) // math/rand is good enough in this case /* #nosec G404 */ rng := rand.New(source) pipelines, err := t.pipelines(t, c, options, repos, packageSpecSets, rng) if err != nil { return distro.Manifest{}, err } // flatten spec sets for sources allPackageSpecs := make([]rpmmd.PackageSpec, 0) for _, specs := range packageSpecSets { allPackageSpecs = append(allPackageSpecs, specs...) } var commits []ostreeCommit if t.bootISO && options.OSTree.Parent != "" && options.OSTree.URL != "" { commit := ostreeCommit{Checksum: options.OSTree.Parent, URL: options.OSTree.URL} commits = []ostreeCommit{commit} } return json.Marshal( osbuild.Manifest{ Version: "2", Pipelines: pipelines, Sources: t.sources(allPackageSpecs, commits), }, ) } // local type for ostree commit metadata used to define commit sources type ostreeCommit struct { Checksum string URL string } func (t *imageTypeS2) sources(packages []rpmmd.PackageSpec, ostreeCommits []ostreeCommit) osbuild.Sources { sources := osbuild.Sources{} curl := &osbuild.CurlSource{ Items: make(map[string]osbuild.CurlSourceItem), } for _, pkg := range packages { item := new(osbuild.URLWithSecrets) item.URL = pkg.RemoteLocation if pkg.Secrets == "org.osbuild.rhsm" { item.Secrets = &osbuild.URLSecrets{ Name: "org.osbuild.rhsm", } } curl.Items[pkg.Checksum] = item } if len(curl.Items) > 0 { sources["org.osbuild.curl"] = curl } ostree := &osbuild.OSTreeSource{ Items: make(map[string]osbuild.OSTreeSourceItem), } for _, commit := range ostreeCommits { item := new(osbuild.OSTreeSourceItem) item.Remote.URL = commit.URL ostree.Items[commit.Checksum] = *item } if len(ostree.Items) > 0 { sources["org.osbuild.ostree"] = ostree } return sources } func edgePipelines(t *imageTypeS2, 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, *t.buildPipeline(repos, packageSetSpecs["build-packages"])) if t.bootISO { var kernelPkg *rpmmd.PackageSpec installerPackages := packageSetSpecs["installer"] for idx := range installerPackages { pkg := installerPackages[idx] if pkg.Name == "kernel" { kernelPkg = &pkg break } } if kernelPkg == nil { panic("kernel package not found in installer package set; this is a programming error") } kernelVer := fmt.Sprintf("%s-%s.%s", kernelPkg.Version, kernelPkg.Release, kernelPkg.Arch) anacondaPipeline, err := t.anacondaTreePipeline(repos, customizations, packageSetSpecs["installer"], options, kernelVer) if err != nil { return nil, err } pipelines = append(pipelines, *anacondaPipeline) pipelines = append(pipelines, *t.bootISOTreePipeline(kernelVer)) pipelines = append(pipelines, *t.bootISOPipeline()) } else { treePipeline, err := t.ostreeTreePipeline(repos, packageSetSpecs["packages"], customizations) if err != nil { return nil, err } pipelines = append(pipelines, *treePipeline) pipelines = append(pipelines, *t.ostreeCommitPipeline(options)) pipelines = append(pipelines, *t.containerTreePipeline(repos, packageSetSpecs["container"], options, customizations)) pipelines = append(pipelines, *t.containerPipeline()) } return pipelines, nil } func (t *imageTypeS2) buildPipeline(repos []rpmmd.RepoConfig, buildPackageSpecs []rpmmd.PackageSpec) *osbuild.Pipeline { p := new(osbuild.Pipeline) p.Name = "build" p.Runner = "org.osbuild.rhel84" p.AddStage(osbuild.NewRPMStage(t.rpmStageOptions(repos), osbuild.NewRpmStageSourceFilesInputs(buildPackageSpecs))) p.AddStage(osbuild.NewSELinuxStage(t.selinuxStageOptions())) return p } func (t *imageTypeS2) ostreeTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, c *blueprint.Customizations) (*osbuild.Pipeline, error) { p := new(osbuild.Pipeline) p.Name = "ostree-tree" p.Build = "name:build" p.AddStage(osbuild.NewRPMStage(t.rpmStageOptions(repos), osbuild.NewRpmStageSourceFilesInputs(packages))) 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(osbuild.NewGroupsStageOptions(groups))) } if userOptions, err := osbuild.NewUsersStageOptions(c.GetUsers(), false); err != nil { return nil, err } else if userOptions != nil { // 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, err := osbuild.NewUsersStageOptions(c.GetUsers(), true) if err != nil { return nil, err } p.AddStage(osbuild.NewUsersStage(userOptionsSansKeys)) p.AddStage(osbuild.NewFirstBootStage(t.usersFirstBootOptions(userOptions))) } if services := c.GetServices(); services != nil || t.enabledServices != nil || t.disabledServices != nil || t.defaultTarget != "" { p.AddStage(osbuild.NewSystemdStage(t.systemdStageOptions(t.enabledServices, t.disabledServices, services, t.defaultTarget))) } if firewall := c.GetFirewall(); firewall != nil { p.AddStage(osbuild.NewFirewallStage(t.firewallStageOptions(firewall))) } if !t.bootISO { p.AddStage(osbuild.NewSELinuxStage(t.selinuxStageOptions())) } // 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, }, })) p.AddStage(osbuild.NewOSTreePrepTreeStage(&osbuild.OSTreePrepTreeStageOptions{ EtcGroupMembers: []string{ // NOTE: We may want to make this configurable. "wheel", "docker", }, })) return p, nil } func (t *imageTypeS2) 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: "8.4", // NOTE: Set on image type? Parent: options.OSTree.Parent, }, &osbuild.OSTreeCommitStageInputs{Tree: commitStageInput}), ) return p } func (t *imageTypeS2) 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(t.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"})) } p.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: "/var/www/html/repo"})) p.AddStage(osbuild.NewOSTreePullStage( &osbuild.OSTreePullStageOptions{Repo: "/var/www/html/repo"}, osbuild.NewOstreePullStageInputs("org.osbuild.pipeline", "name:ostree-commit", options.OSTree.Ref), )) return p } func (t *imageTypeS2) containerPipeline() *osbuild.Pipeline { p := new(osbuild.Pipeline) // NOTE(akoutsou) 1to2t: final pipeline should always be named "assembler" p.Name = "assembler" 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 (t *imageTypeS2) anacondaTreePipeline(repos []rpmmd.RepoConfig, customizations *blueprint.Customizations, packages []rpmmd.PackageSpec, options distro.ImageOptions, kernelVer string) (*osbuild.Pipeline, error) { ostreeRepoPath := "/ostree/repo" p := new(osbuild.Pipeline) p.Name = "anaconda-tree" p.Build = "name:build" p.AddStage(osbuild.NewRPMStage(t.rpmStageOptions(repos), osbuild.NewRpmStageSourceFilesInputs(packages))) p.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: ostreeRepoPath})) p.AddStage(osbuild.NewOSTreePullStage( &osbuild.OSTreePullStageOptions{Repo: ostreeRepoPath}, osbuild.NewOstreePullStageInputs("org.osbuild.source", options.OSTree.Parent, options.OSTree.Ref), )) p.AddStage(osbuild.NewBuildstampStage(t.buildStampStageOptions())) 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)) nUsers := len(customizations.GetUsers())+len(customizations.GetGroups()) > 0 p.AddStage(osbuild.NewAnacondaStage(osbuild.NewAnacondaStageOptions(nUsers))) p.AddStage(osbuild.NewLoraxScriptStage(t.loraxScriptStageOptions())) p.AddStage(osbuild.NewDracutStage(t.dracutStageOptions(kernelVer))) kickstartOptions, err := osbuild.NewKickstartStageOptions(kspath, "", customizations.GetUsers(), customizations.GetGroups(), fmt.Sprintf("file://%s", ostreeRepoPath), options.OSTree.Ref) if err != nil { return nil, err } p.AddStage(osbuild.NewKickstartStage(kickstartOptions)) return p, nil } func (t *imageTypeS2) bootISOTreePipeline(kernelVer string) *osbuild.Pipeline { p := new(osbuild.Pipeline) p.Name = "bootiso-tree" p.Build = "name:build" p.AddStage(osbuild.NewBootISOMonoStage(t.bootISOMonoStageOptions(kernelVer), osbuild.NewBootISOMonoStagePipelineTreeInputs("anaconda-tree"))) p.AddStage(osbuild.NewDiscinfoStage(t.discinfoStageOptions())) return p } func (t *imageTypeS2) bootISOPipeline() *osbuild.Pipeline { p := new(osbuild.Pipeline) // NOTE(akoutsou) 1to2t: final pipeline should always be named "assembler" p.Name = "assembler" p.Build = "name:build" p.AddStage(osbuild.NewXorrisofsStage(t.xorrisofsStageOptions(), osbuild.NewXorrisofsStagePipelineTreeInputs("bootiso-tree"))) p.AddStage(osbuild.NewImplantisomd5Stage(&osbuild.Implantisomd5StageOptions{Filename: t.Filename()})) return p } func (t *imageTypeS2) rpmStageOptions(repos []rpmmd.RepoConfig) *osbuild.RPMStageOptions { var gpgKeys []string for _, repo := range repos { if repo.GPGKey == "" { continue } gpgKeys = append(gpgKeys, repo.GPGKey) } return &osbuild.RPMStageOptions{ GPGKeys: gpgKeys, Exclude: &osbuild.Exclude{ // NOTE: Make configurable? Docs: true, }, } } func (t *imageTypeS2) selinuxStageOptions() *osbuild.SELinuxStageOptions { options := &osbuild.SELinuxStageOptions{ FileContexts: "etc/selinux/targeted/contexts/files/file_contexts", } if t.bootISO { options.Labels = map[string]string{ "/usr/bin/cp": "system_u:object_r:install_exec_t:s0", "/usr/bin/tar": "system_u:object_r:install_exec_t:s0", } } return options } func (t *imageTypeS2) usersFirstBootOptions(usersStageOptions *osbuild.UsersStageOptions) *osbuild.FirstBootStageOptions { cmds := make([]string, 0, 3*len(usersStageOptions.Users)+1) // workaround for creating authorized_keys file for user varhome := filepath.Join("/var", "home") for name, user := range usersStageOptions.Users { if user.Key != nil { sshdir := filepath.Join(varhome, name, ".ssh") cmds = append(cmds, fmt.Sprintf("mkdir -p %s", sshdir)) cmds = append(cmds, fmt.Sprintf("sh -c 'echo %q >> %q'", *user.Key, filepath.Join(sshdir, "authorized_keys"))) cmds = append(cmds, fmt.Sprintf("chown %s:%s -Rc %s", name, name, sshdir)) } } cmds = append(cmds, fmt.Sprintf("restorecon -rvF %s", varhome)) options := &osbuild.FirstBootStageOptions{ Commands: cmds, WaitForNetwork: false, } return options } func (t *imageTypeS2) firewallStageOptions(firewall *blueprint.FirewallCustomization) *osbuild.FirewallStageOptions { options := osbuild.FirewallStageOptions{ Ports: firewall.Ports, } if firewall.Services != nil { options.EnabledServices = firewall.Services.Enabled options.DisabledServices = firewall.Services.Disabled } return &options } func (t *imageTypeS2) systemdStageOptions(enabledServices, disabledServices []string, s *blueprint.ServicesCustomization, target string) *osbuild.SystemdStageOptions { if s != nil { enabledServices = append(enabledServices, s.Enabled...) disabledServices = append(disabledServices, s.Disabled...) } return &osbuild.SystemdStageOptions{ EnabledServices: enabledServices, DisabledServices: disabledServices, DefaultTarget: target, } } func (t *imageTypeS2) buildStampStageOptions() *osbuild.BuildstampStageOptions { return &osbuild.BuildstampStageOptions{ Arch: t.Arch().Name(), Product: "Red Hat Enterprise Linux", Version: "8.4", Variant: "edge", Final: true, } } func (t *imageTypeS2) loraxScriptStageOptions() *osbuild.LoraxScriptStageOptions { return &osbuild.LoraxScriptStageOptions{ Path: "99-generic/runtime-postinstall.tmpl", BaseArch: t.Arch().Name(), } } func (t *imageTypeS2) dracutStageOptions(kernelVer string) *osbuild.DracutStageOptions { kernel := []string{kernelVer} modules := []string{ "bash", "systemd", "fips", "systemd-initrd", "modsign", "nss-softokn", "rdma", "rngd", "i18n", "convertfs", "network-manager", "network", "ifcfg", "url-lib", "drm", "plymouth", "prefixdevname", "prefixdevname-tools", "anaconda", "crypt", "dm", "dmsquash-live", "kernel-modules", "kernel-modules-extra", "kernel-network-modules", "livenet", "lvm", "mdraid", "multipath", "qemu", "qemu-net", "fcoe", "fcoe-uefi", "iscsi", "lunmask", "nfs", "resume", "rootfs-block", "terminfo", "udev-rules", "biosdevname", "dracut-systemd", "pollcdrom", "usrmount", "base", "fs-lib", "img-lib", "shutdown", "uefi-lib", } return &osbuild.DracutStageOptions{ Kernel: kernel, Modules: modules, Install: []string{"/.buildstamp"}, } } func (t *imageTypeS2) bootISOMonoStageOptions(kernelVer string) *osbuild.BootISOMonoStageOptions { return &osbuild.BootISOMonoStageOptions{ Product: osbuild.Product{ Name: "Red Hat Enterprise Linux", Version: "8.4", }, ISOLabel: fmt.Sprintf("RHEL-8-4-0-BaseOS-%s", t.Arch().Name()), Kernel: kernelVer, EFI: osbuild.EFI{ Architectures: []string{ "IA32", "X64", }, Vendor: "redhat", }, ISOLinux: osbuild.ISOLinux{ Enabled: true, Debug: false, }, Templates: "80-rhel", RootFS: osbuild.RootFS{ Size: 4096, Compression: osbuild.FSCompression{ Method: "xz", Options: &osbuild.FSCompressionOptions{ // TODO: based on image arch BCJ: "x86", }, }, }, } } func (t *imageTypeS2) discinfoStageOptions() *osbuild.DiscinfoStageOptions { return &osbuild.DiscinfoStageOptions{ BaseArch: t.Arch().Name(), Release: "202010217.n.0", } } func (t *imageTypeS2) xorrisofsStageOptions() *osbuild.XorrisofsStageOptions { return &osbuild.XorrisofsStageOptions{ Filename: t.Filename(), VolID: fmt.Sprintf("RHEL-8-4-0-BaseOS-%s", t.Arch().Name()), SysID: "LINUX", Boot: &osbuild.XorrisofsBoot{ Image: "isolinux/isolinux.bin", Catalog: "isolinux/boot.cat", }, EFI: "images/efiboot.img", IsohybridMBR: "/usr/share/syslinux/isohdpfx.bin", } } func (t *imageTypeS2) checkOptions(customizations *blueprint.Customizations, options distro.ImageOptions) error { if t.bootISO { if options.OSTree.Parent == "" { return fmt.Errorf("boot ISO image type %q requires specifying a URL from which to retrieve the OSTree commit", t.name) } if t.name == "rhel-edge-installer" { allowed := []string{"User", "Group"} if err := customizations.CheckAllowed(allowed...); err != nil { return fmt.Errorf("unsupported blueprint customizations found for boot ISO image type %q: (allowed: %s)", t.name, strings.Join(allowed, ", ")) } } } if kernelOpts := customizations.GetKernel(); kernelOpts.Append != "" && t.rpmOstree { return fmt.Errorf("kernel boot parameter customizations are not supported for ostree types") } mountpoints := customizations.GetFilesystems() if mountpoints != nil && t.rpmOstree { return fmt.Errorf("Custom mountpoints are not supported for ostree types") } invalidMountpoints := []string{} for _, m := range mountpoints { if m.Mountpoint != "/" { invalidMountpoints = append(invalidMountpoints, m.Mountpoint) } } if len(invalidMountpoints) > 0 { return fmt.Errorf("The following custom mountpoints are not supported %+q", invalidMountpoints) } return nil } func (t *imageTypeS2) prependKernelCmdlineStage(pipeline *osbuild.Pipeline, pt *disk.PartitionTable) *osbuild.Pipeline { if t.arch.name == distro.S390xArchName { rootFs := pt.FindMountable("/") if rootFs == nil { panic("s390x image must have a root partition, 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 (t *imageTypeS2) getPartitionTable(options distro.ImageOptions, rng *rand.Rand) (*disk.PartitionTable, error) { basePartitionTable := t.partitionTableGenerator(options.Size, t.Arch(), rng) pt, err := disk.NewPartitionTable(&basePartitionTable, nil, options.Size, false, rng) return pt, err }