From 040a4ef6a10e575372a69f53584dfdbbe91246bf Mon Sep 17 00:00:00 2001 From: Tom Gundersen Date: Sun, 26 Jun 2022 00:49:51 +0100 Subject: [PATCH] pipelines: add Fedora IoT installer pipelines --- internal/distro/fedora/pipelines.go | 135 ++++++--------------- internal/distro/fedora/stage_options.go | 153 ------------------------ internal/distro/pipelines/anaconda.go | 138 +++++++++++++++++++++ internal/distro/pipelines/iso.go | 50 ++++++++ internal/distro/pipelines/iso_tree.go | 111 +++++++++++++++++ 5 files changed, 334 insertions(+), 253 deletions(-) delete mode 100644 internal/distro/fedora/stage_options.go create mode 100644 internal/distro/pipelines/anaconda.go create mode 100644 internal/distro/pipelines/iso.go create mode 100644 internal/distro/pipelines/iso_tree.go diff --git a/internal/distro/fedora/pipelines.go b/internal/distro/fedora/pipelines.go index 311657a5b..b1730b64d 100644 --- a/internal/distro/fedora/pipelines.go +++ b/internal/distro/fedora/pipelines.go @@ -3,7 +3,6 @@ package fedora import ( "fmt" "math/rand" - "path" "github.com/osbuild/osbuild-composer/internal/blueprint" "github.com/osbuild/osbuild-composer/internal/disk" @@ -162,13 +161,6 @@ func ec2Pipelines(t *imageType, customizations *blueprint.Customizations, option return ec2CommonPipelines(t, customizations, options, repos, packageSetSpecs, rng, t.Filename()) } -//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 iotInstallerPipelines(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) @@ -181,21 +173,14 @@ func iotInstallerPipelines(t *imageType, customizations *blueprint.Customization d := t.arch.distro archName := t.Arch().Name() kernelVer := rpmmd.GetVerStrFromPackageSpecListPanic(installerPackages, "kernel") - ostreeRepoPath := "/ostree/repo" - payloadStages := ostreePayloadStages(options, ostreeRepoPath) - kickstartOptions, err := osbuild.NewKickstartStageOptions(kspath, "", customizations.GetUsers(), customizations.GetGroups(), makeISORootPath(ostreeRepoPath), options.OSTree.Ref, "fedora") - if err != nil { - return nil, err - } ksUsers := len(customizations.GetUsers())+len(customizations.GetGroups()) > 0 - pipelines = append(pipelines, *anacondaTreePipeline(repos, installerPackages, kernelVer, archName, d.product, d.osVersion, "IoT", ksUsers)) - 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)) + anacondaTreePipeline := anacondaTreePipeline(&buildPipeline, repos, installerPackages, kernelVer, archName, d.product, d.osVersion, "IoT", ksUsers) + isoTreePipeline := bootISOTreePipeline(&buildPipeline, &anacondaTreePipeline.Pipeline, options, kernelVer, archName, d.vendor, d.product, d.osVersion, isolabel, customizations.GetUsers(), customizations.GetGroups()) + isoPipeline := bootISOPipeline(&buildPipeline, &isoTreePipeline, t.Filename(), isolabel, false) - return pipelines, nil + return append(pipelines, anacondaTreePipeline.Serialize(), isoTreePipeline.Serialize(), isoPipeline.Serialize()), nil } func iotCorePipelines(t *imageType, customizations *blueprint.Customizations, options distro.ImageOptions, repos []rpmmd.RepoConfig, packageSetSpecs map[string][]rpmmd.PackageSpec) (*pipeline.BuildPipeline, *pipeline.OSPipeline, *pipeline.OSTreeCommitPipeline, error) { @@ -384,93 +369,43 @@ func containerPipeline(buildPipeline *pipeline.BuildPipeline, treePipeline *pipe 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 anacondaTreePipeline(buildPipeline *pipeline.BuildPipeline, repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, kernelVer, arch, product, osVersion, variant string, users bool) pipeline.AnacondaPipeline { + p := pipeline.NewAnacondaPipeline(buildPipeline) + p.Repos = repos + p.PackageSpecs = packages + p.Users = users + p.KernelVer = kernelVer + p.Arch = arch + p.Product = product + p.OSVersion = osVersion + p.Variant = variant + p.Biosdevname = (p.Arch == distro.X86_64ArchName) + return p } -func anacondaTreePipeline(repos []rpmmd.RepoConfig, packages []rpmmd.PackageSpec, kernelVer, arch, product, osVersion, variant string, users bool) *osbuild.Pipeline { - p := new(osbuild.Pipeline) - p.Name = "anaconda-tree" - p.Build = "name:build" - p.AddStage(osbuild.NewRPMStage(osbuild.NewRPMStageOptions(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(osbuild.NewAnacondaStageOptions(users))) - p.AddStage(osbuild.NewLoraxScriptStage(loraxScriptStageOptions(arch))) - p.AddStage(osbuild.NewDracutStage(dracutStageOptions(kernelVer, arch, []string{ - "anaconda", - "rdma", - "rngd", - "multipath", - "fcoe", - "fcoe-uefi", - "iscsi", - "lunmask", - "nfs", - }))) - p.AddStage(osbuild.NewSELinuxConfigStage(&osbuild.SELinuxConfigStageOptions{State: osbuild.SELinuxStatePermissive})) +func bootISOTreePipeline(buildPipeline *pipeline.BuildPipeline, treePipeline *pipeline.Pipeline, options distro.ImageOptions, kernelVer, arch, vendor, product, osVersion, isolabel string, users []blueprint.UserCustomization, groups []blueprint.GroupCustomization) pipeline.ISOTreePipeline { + p := pipeline.NewISOTreePipeline(buildPipeline, treePipeline) + p.KernelVer = kernelVer + p.Arch = arch + p.Release = "202010217.n.0" + p.Vendor = vendor + p.Product = product + p.OSName = "fedora" + p.OSVersion = osVersion + p.ISOLabel = isolabel + p.Users = users + p.Groups = groups + p.OSTreeRef = options.OSTree.Ref + p.OSTreeParent = options.OSTree.Parent 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})) - +func bootISOPipeline(buildPipeline *pipeline.BuildPipeline, treePipeline *pipeline.ISOTreePipeline, filename, isolabel string, isolinux bool) pipeline.ISOPipeline { + p := pipeline.NewISOPipeline(buildPipeline, treePipeline) + p.Filename = filename + p.ISOLabel = isolabel + p.ISOLinux = isolinux return p } diff --git a/internal/distro/fedora/stage_options.go b/internal/distro/fedora/stage_options.go deleted file mode 100644 index 27c59cdde..000000000 --- a/internal/distro/fedora/stage_options.go +++ /dev/null @@ -1,153 +0,0 @@ -package fedora - -import ( - "fmt" - - "github.com/osbuild/osbuild-composer/internal/distro" - osbuild "github.com/osbuild/osbuild-composer/internal/osbuild2" -) - -const ( - kspath = "/osbuild.ks" -) - -func buildStampStageOptions(arch, product, osVersion, variant string) *osbuild.BuildstampStageOptions { - return &osbuild.BuildstampStageOptions{ - Arch: arch, - Product: product, - Version: osVersion, - Variant: variant, - Final: true, - } -} - -func loraxScriptStageOptions(arch string) *osbuild.LoraxScriptStageOptions { - return &osbuild.LoraxScriptStageOptions{ - Path: "99-generic/runtime-postinstall.tmpl", - BaseArch: arch, - } -} - -func dracutStageOptions(kernelVer, arch string, additionalModules []string) *osbuild.DracutStageOptions { - kernel := []string{kernelVer} - modules := []string{ - "bash", - "systemd", - "fips", - "systemd-initrd", - "modsign", - "nss-softokn", - "i18n", - "convertfs", - "network-manager", - "network", - "ifcfg", - "url-lib", - "drm", - "plymouth", - "crypt", - "dm", - "dmsquash-live", - "kernel-modules", - "kernel-modules-extra", - "kernel-network-modules", - "livenet", - "lvm", - "mdraid", - "qemu", - "qemu-net", - "resume", - "rootfs-block", - "terminfo", - "udev-rules", - "dracut-systemd", - "pollcdrom", - "usrmount", - "base", - "fs-lib", - "img-lib", - "shutdown", - "uefi-lib", - } - - if arch == distro.X86_64ArchName { - modules = append(modules, "biosdevname") - } - - modules = append(modules, additionalModules...) - return &osbuild.DracutStageOptions{ - Kernel: kernel, - Modules: modules, - Install: []string{"/.buildstamp"}, - } -} - -func bootISOMonoStageOptions(kernelVer, arch, vendor, product, osVersion, isolabel string) *osbuild.BootISOMonoStageOptions { - comprOptions := new(osbuild.FSCompressionOptions) - if bcj := osbuild.BCJOption(arch); bcj != "" { - comprOptions.BCJ = bcj - } - var architectures []string - - if arch == distro.X86_64ArchName { - architectures = []string{"X64"} - } else if arch == distro.Aarch64ArchName { - architectures = []string{"AA64"} - } else { - panic("unsupported architecture") - } - - return &osbuild.BootISOMonoStageOptions{ - Product: osbuild.Product{ - Name: product, - Version: osVersion, - }, - ISOLabel: isolabel, - Kernel: kernelVer, - KernelOpts: fmt.Sprintf("inst.ks=hd:LABEL=%s:%s", isolabel, kspath), - EFI: osbuild.EFI{ - Architectures: architectures, - Vendor: vendor, - }, - ISOLinux: osbuild.ISOLinux{ - Enabled: arch == distro.X86_64ArchName, - Debug: false, - }, - Templates: "99-generic", - RootFS: osbuild.RootFS{ - Size: 9216, - Compression: osbuild.FSCompression{ - Method: "xz", - Options: comprOptions, - }, - }, - } -} - -func discinfoStageOptions(arch string) *osbuild.DiscinfoStageOptions { - return &osbuild.DiscinfoStageOptions{ - BaseArch: arch, - Release: "202010217.n.0", - } -} - -func xorrisofsStageOptions(filename, isolabel, arch string, isolinux bool) *osbuild.XorrisofsStageOptions { - options := &osbuild.XorrisofsStageOptions{ - Filename: filename, - VolID: fmt.Sprintf(isolabel, arch), - SysID: "LINUX", - EFI: "images/efiboot.img", - ISOLevel: 3, - } - - if isolinux { - options.Boot = &osbuild.XorrisofsBoot{ - Image: "isolinux/isolinux.bin", - Catalog: "isolinux/boot.cat", - } - - options.IsohybridMBR = "/usr/share/syslinux/isohdpfx.bin" - } - - return options -} diff --git a/internal/distro/pipelines/anaconda.go b/internal/distro/pipelines/anaconda.go new file mode 100644 index 000000000..067611b19 --- /dev/null +++ b/internal/distro/pipelines/anaconda.go @@ -0,0 +1,138 @@ +package pipeline + +import ( + "github.com/osbuild/osbuild-composer/internal/osbuild2" + "github.com/osbuild/osbuild-composer/internal/rpmmd" +) + +type AnacondaPipeline struct { + Pipeline + Repos []rpmmd.RepoConfig + PackageSpecs []rpmmd.PackageSpec + Users bool + KernelVer string + Arch string + Product string + OSVersion string + Variant string + Biosdevname bool +} + +func NewAnacondaPipeline(buildPipeline *BuildPipeline) AnacondaPipeline { + return AnacondaPipeline{ + Pipeline: New("anaconda-tree", &buildPipeline.Pipeline), + } +} + +func (p AnacondaPipeline) Serialize() osbuild2.Pipeline { + pipeline := p.Pipeline.Serialize() + + pipeline.AddStage(osbuild2.NewRPMStage(osbuild2.NewRPMStageOptions(p.Repos), osbuild2.NewRpmStageSourceFilesInputs(p.PackageSpecs))) + pipeline.AddStage(osbuild2.NewBuildstampStage(&osbuild2.BuildstampStageOptions{ + Arch: p.Arch, + Product: p.Product, + Version: p.OSVersion, + Variant: p.Variant, + Final: true, + })) + pipeline.AddStage(osbuild2.NewLocaleStage(&osbuild2.LocaleStageOptions{Language: "en_US.UTF-8"})) + + rootPassword := "" + rootUser := osbuild2.UsersStageOptionsUser{ + Password: &rootPassword, + } + + installUID := 0 + installGID := 0 + installHome := "/root" + installShell := "/usr/libexec/anaconda/run-anaconda" + installPassword := "" + installUser := osbuild2.UsersStageOptionsUser{ + UID: &installUID, + GID: &installGID, + Home: &installHome, + Shell: &installShell, + Password: &installPassword, + } + usersStageOptions := &osbuild2.UsersStageOptions{ + Users: map[string]osbuild2.UsersStageOptionsUser{ + "root": rootUser, + "install": installUser, + }, + } + + pipeline.AddStage(osbuild2.NewUsersStage(usersStageOptions)) + pipeline.AddStage(osbuild2.NewAnacondaStage(osbuild2.NewAnacondaStageOptions(p.Users))) + pipeline.AddStage(osbuild2.NewLoraxScriptStage(&osbuild2.LoraxScriptStageOptions{ + Path: "99-generic/runtime-postinstall.tmpl", + BaseArch: p.Arch, + })) + pipeline.AddStage(osbuild2.NewDracutStage(dracutStageOptions(p.KernelVer, p.Biosdevname, []string{ + "anaconda", + "rdma", + "rngd", + "multipath", + "fcoe", + "fcoe-uefi", + "iscsi", + "lunmask", + "nfs", + }))) + pipeline.AddStage(osbuild2.NewSELinuxConfigStage(&osbuild2.SELinuxConfigStageOptions{State: osbuild2.SELinuxStatePermissive})) + + return pipeline +} + +func dracutStageOptions(kernelVer string, biosdevname bool, additionalModules []string) *osbuild2.DracutStageOptions { + kernel := []string{kernelVer} + modules := []string{ + "bash", + "systemd", + "fips", + "systemd-initrd", + "modsign", + "nss-softokn", + "i18n", + "convertfs", + "network-manager", + "network", + "ifcfg", + "url-lib", + "drm", + "plymouth", + "crypt", + "dm", + "dmsquash-live", + "kernel-modules", + "kernel-modules-extra", + "kernel-network-modules", + "livenet", + "lvm", + "mdraid", + "qemu", + "qemu-net", + "resume", + "rootfs-block", + "terminfo", + "udev-rules", + "dracut-systemd", + "pollcdrom", + "usrmount", + "base", + "fs-lib", + "img-lib", + "shutdown", + "uefi-lib", + } + + if biosdevname { + modules = append(modules, "biosdevname") + } + + modules = append(modules, additionalModules...) + return &osbuild2.DracutStageOptions{ + Kernel: kernel, + Modules: modules, + Install: []string{"/.buildstamp"}, + } +} diff --git a/internal/distro/pipelines/iso.go b/internal/distro/pipelines/iso.go new file mode 100644 index 000000000..bad613133 --- /dev/null +++ b/internal/distro/pipelines/iso.go @@ -0,0 +1,50 @@ +package pipeline + +import ( + "github.com/osbuild/osbuild-composer/internal/osbuild2" +) + +type ISOPipeline struct { + Pipeline + treePipeline *ISOTreePipeline + Filename string + ISOLabel string + ISOLinux bool +} + +func NewISOPipeline(buildPipeline *BuildPipeline, treePipeline *ISOTreePipeline) ISOPipeline { + return ISOPipeline{ + Pipeline: New("bootiso", &buildPipeline.Pipeline), + treePipeline: treePipeline, + } +} + +func (p ISOPipeline) Serialize() osbuild2.Pipeline { + pipeline := p.Pipeline.Serialize() + + pipeline.AddStage(osbuild2.NewXorrisofsStage(xorrisofsStageOptions(p.Filename, p.ISOLabel, p.ISOLinux), osbuild2.NewXorrisofsStagePipelineTreeInputs(p.treePipeline.Name()))) + pipeline.AddStage(osbuild2.NewImplantisomd5Stage(&osbuild2.Implantisomd5StageOptions{Filename: p.Filename})) + + return pipeline +} + +func xorrisofsStageOptions(filename, isolabel string, isolinux bool) *osbuild2.XorrisofsStageOptions { + options := &osbuild2.XorrisofsStageOptions{ + Filename: filename, + VolID: isolabel, + SysID: "LINUX", + EFI: "images/efiboot.img", + ISOLevel: 3, + } + + if isolinux { + options.Boot = &osbuild2.XorrisofsBoot{ + Image: "isolinux/isolinux.bin", + Catalog: "isolinux/boot.cat", + } + + options.IsohybridMBR = "/usr/share/syslinux/isohdpfx.bin" + } + + return options +} diff --git a/internal/distro/pipelines/iso_tree.go b/internal/distro/pipelines/iso_tree.go new file mode 100644 index 000000000..0282e6377 --- /dev/null +++ b/internal/distro/pipelines/iso_tree.go @@ -0,0 +1,111 @@ +package pipeline + +import ( + "fmt" + "path" + + "github.com/osbuild/osbuild-composer/internal/blueprint" + "github.com/osbuild/osbuild-composer/internal/distro" + "github.com/osbuild/osbuild-composer/internal/osbuild2" +) + +type ISOTreePipeline struct { + Pipeline + treePipeline *Pipeline + KernelVer string + Arch string + Release string + Vendor string + Product string + OSName string + OSVersion string + ISOLabel string + Users []blueprint.UserCustomization + Groups []blueprint.GroupCustomization + OSTreeParent string + OSTreeRef string +} + +func NewISOTreePipeline(buildPipeline *BuildPipeline, treePipeline *Pipeline) ISOTreePipeline { + return ISOTreePipeline{ + Pipeline: New("bootiso-tree", &buildPipeline.Pipeline), + treePipeline: treePipeline, + } +} + +func (p ISOTreePipeline) Serialize() osbuild2.Pipeline { + pipeline := p.Pipeline.Serialize() + + kspath := "/osbuild.ks" + ostreeRepoPath := "/ostree/repo" + + pipeline.AddStage(osbuild2.NewBootISOMonoStage(bootISOMonoStageOptions(p.KernelVer, p.Arch, p.Vendor, p.Product, p.OSVersion, p.ISOLabel, kspath), osbuild2.NewBootISOMonoStagePipelineTreeInputs(p.treePipeline.Name()))) + + kickstartOptions, err := osbuild2.NewKickstartStageOptions(kspath, "", p.Users, p.Groups, makeISORootPath(ostreeRepoPath), p.OSTreeRef, p.OSName) + if err != nil { + panic("password encryption failed") + } + + pipeline.AddStage(osbuild2.NewKickstartStage(kickstartOptions)) + pipeline.AddStage(osbuild2.NewDiscinfoStage(&osbuild2.DiscinfoStageOptions{ + BaseArch: p.Arch, + Release: p.Release, + })) + + pipeline.AddStage(osbuild2.NewOSTreeInitStage(&osbuild2.OSTreeInitStageOptions{Path: ostreeRepoPath})) + pipeline.AddStage(osbuild2.NewOSTreePullStage( + &osbuild2.OSTreePullStageOptions{Repo: ostreeRepoPath}, + osbuild2.NewOstreePullStageInputs("org.osbuild.source", p.OSTreeParent, p.OSTreeRef), + )) + + return pipeline +} + +func bootISOMonoStageOptions(kernelVer, arch, vendor, product, osVersion, isolabel, kspath string) *osbuild2.BootISOMonoStageOptions { + comprOptions := new(osbuild2.FSCompressionOptions) + if bcj := osbuild2.BCJOption(arch); bcj != "" { + comprOptions.BCJ = bcj + } + var architectures []string + + if arch == distro.X86_64ArchName { + architectures = []string{"X64"} + } else if arch == distro.Aarch64ArchName { + architectures = []string{"AA64"} + } else { + panic("unsupported architecture") + } + + return &osbuild2.BootISOMonoStageOptions{ + Product: osbuild2.Product{ + Name: product, + Version: osVersion, + }, + ISOLabel: isolabel, + Kernel: kernelVer, + KernelOpts: fmt.Sprintf("inst.ks=hd:LABEL=%s:%s", isolabel, kspath), + EFI: osbuild2.EFI{ + Architectures: architectures, + Vendor: vendor, + }, + ISOLinux: osbuild2.ISOLinux{ + Enabled: arch == distro.X86_64ArchName, + Debug: false, + }, + Templates: "99-generic", + RootFS: osbuild2.RootFS{ + Size: 9216, + Compression: osbuild2.FSCompression{ + Method: "xz", + Options: comprOptions, + }, + }, + } +} + +//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) +}