diff --git a/pkg/image/anaconda_container_installer.go b/pkg/image/anaconda_container_installer.go new file mode 100644 index 0000000000..c3814de2a8 --- /dev/null +++ b/pkg/image/anaconda_container_installer.go @@ -0,0 +1,149 @@ +package image + +import ( + "fmt" + "math/rand" + + "github.com/osbuild/images/internal/common" + "github.com/osbuild/images/pkg/arch" + "github.com/osbuild/images/pkg/artifact" + "github.com/osbuild/images/pkg/container" + "github.com/osbuild/images/pkg/customizations/users" + "github.com/osbuild/images/pkg/disk" + "github.com/osbuild/images/pkg/manifest" + "github.com/osbuild/images/pkg/platform" + "github.com/osbuild/images/pkg/rpmmd" + "github.com/osbuild/images/pkg/runner" +) + +type AnacondaContainerInstaller struct { + Base + Platform platform.Platform + ExtraBasePackages rpmmd.PackageSet + Users []users.User + Groups []users.Group + + SquashfsCompression string + + ISOLabelTempl string + Product string + Variant string + OSName string + Ref string + OSVersion string + Release string + + ContainerSource container.SourceSpec + + Filename string + + AdditionalDracutModules []string + AdditionalAnacondaModules []string + AdditionalDrivers []string + FIPS bool +} + +func NewAnacondaContainerInstaller(container container.SourceSpec, ref string) *AnacondaContainerInstaller { + return &AnacondaContainerInstaller{ + Base: NewBase("container-installer"), + ContainerSource: container, + Ref: ref, + } +} + +func (img *AnacondaContainerInstaller) InstantiateManifest(m *manifest.Manifest, + repos []rpmmd.RepoConfig, + runner runner.Runner, + rng *rand.Rand) (*artifact.Artifact, error) { + buildPipeline := manifest.NewBuild(m, runner, repos, &manifest.BuildOptions{ContainerBuildable: true}) + buildPipeline.Checkpoint() + + anacondaPipeline := manifest.NewAnacondaInstaller(m, + manifest.AnacondaInstallerTypePayload, + buildPipeline, + img.Platform, + repos, + "kernel", + img.Product, + img.OSVersion) + + // This is only built with ELN for now + anacondaPipeline.UseRHELLoraxTemplates = true + + anacondaPipeline.ExtraPackages = img.ExtraBasePackages.Include + anacondaPipeline.ExcludePackages = img.ExtraBasePackages.Exclude + anacondaPipeline.ExtraRepos = img.ExtraBasePackages.Repositories + anacondaPipeline.Users = img.Users + anacondaPipeline.Groups = img.Groups + anacondaPipeline.Variant = img.Variant + anacondaPipeline.Biosdevname = (img.Platform.GetArch() == arch.ARCH_X86_64) + anacondaPipeline.Checkpoint() + anacondaPipeline.AdditionalDracutModules = img.AdditionalDracutModules + anacondaPipeline.AdditionalAnacondaModules = img.AdditionalAnacondaModules + if img.FIPS { + anacondaPipeline.AdditionalAnacondaModules = append( + anacondaPipeline.AdditionalAnacondaModules, + "org.fedoraproject.Anaconda.Modules.Security", + ) + } + anacondaPipeline.AdditionalDrivers = img.AdditionalDrivers + + rootfsPartitionTable := &disk.PartitionTable{ + Size: 20 * common.MebiByte, + Partitions: []disk.Partition{ + { + Start: 0, + Size: 20 * common.MebiByte, + Payload: &disk.Filesystem{ + Type: "vfat", + Mountpoint: "/", + UUID: disk.NewVolIDFromRand(rng), + }, + }, + }, + } + + // TODO: replace isoLabelTmpl with more high-level properties + isoLabel := fmt.Sprintf(img.ISOLabelTempl, img.Platform.GetArch()) + + rootfsImagePipeline := manifest.NewISORootfsImg(buildPipeline, anacondaPipeline) + rootfsImagePipeline.Size = 4 * common.GibiByte + + bootTreePipeline := manifest.NewEFIBootTree(m, buildPipeline, img.Product, img.OSVersion) + bootTreePipeline.Platform = img.Platform + bootTreePipeline.UEFIVendor = img.Platform.GetUEFIVendor() + bootTreePipeline.ISOLabel = isoLabel + bootTreePipeline.KernelOpts = []string{fmt.Sprintf("inst.stage2=hd:LABEL=%s", isoLabel), fmt.Sprintf("inst.ks=hd:LABEL=%s:%s", isoLabel, kspath)} + if img.FIPS { + bootTreePipeline.KernelOpts = append(bootTreePipeline.KernelOpts, "fips=1") + } + + // enable ISOLinux on x86_64 only + isoLinuxEnabled := img.Platform.GetArch() == arch.ARCH_X86_64 + + isoTreePipeline := manifest.NewAnacondaInstallerISOTree(buildPipeline, anacondaPipeline, rootfsImagePipeline, bootTreePipeline) + isoTreePipeline.PartitionTable = rootfsPartitionTable + isoTreePipeline.Release = img.Release + isoTreePipeline.OSName = img.OSName + isoTreePipeline.Users = img.Users + isoTreePipeline.Groups = img.Groups + + isoTreePipeline.SquashfsCompression = img.SquashfsCompression + + // For ostree installers, always put the kickstart file in the root of the ISO + isoTreePipeline.KSPath = kspath + isoTreePipeline.PayloadPath = "/container" + + isoTreePipeline.ContainerSource = &img.ContainerSource + isoTreePipeline.ISOLinux = isoLinuxEnabled + if img.FIPS { + isoTreePipeline.KernelOpts = append(isoTreePipeline.KernelOpts, "fips=1") + } + + isoPipeline := manifest.NewISO(buildPipeline, isoTreePipeline, isoLabel) + isoPipeline.SetFilename(img.Filename) + isoPipeline.ISOLinux = isoLinuxEnabled + artifact := isoPipeline.Export() + + return artifact, nil +} diff --git a/pkg/image/anaconda_ostree_installer.go b/pkg/image/anaconda_ostree_installer.go index bea9c13a7c..4f3557e442 100644 --- a/pkg/image/anaconda_ostree_installer.go +++ b/pkg/image/anaconda_ostree_installer.go @@ -128,7 +128,7 @@ func (img *AnacondaOSTreeInstaller) InstantiateManifest(m *manifest.Manifest, isoTreePipeline.KSPath = kspath isoTreePipeline.PayloadPath = "/ostree/repo" - isoTreePipeline.OSTreeCommitSource = &img.Commit + isoTreePipeline.CommitSource = &img.Commit isoTreePipeline.ISOLinux = isoLinuxEnabled if img.FIPS { isoTreePipeline.KernelOpts = append(isoTreePipeline.KernelOpts, "fips=1") diff --git a/pkg/manifest/anaconda_installer.go b/pkg/manifest/anaconda_installer.go index 958f77b24c..05c8d5b089 100644 --- a/pkg/manifest/anaconda_installer.go +++ b/pkg/manifest/anaconda_installer.go @@ -72,6 +72,9 @@ type AnacondaInstaller struct { AdditionalDrivers []string Files []*fsnode.File + + // Temporary + UseRHELLoraxTemplates bool } func NewAnacondaInstaller(m *Manifest, @@ -135,8 +138,18 @@ func (p *AnacondaInstaller) getBuildPackages(Distro) []string { packages := p.anacondaBootPackageSet() packages = append(packages, "rpm", - "lorax-templates-generic", ) + + if p.UseRHELLoraxTemplates { + packages = append(packages, + "lorax-templates-rhel", + ) + } else { + packages = append(packages, + "lorax-templates-generic", + ) + } + return packages } @@ -269,9 +282,17 @@ func (p *AnacondaInstaller) serialize() osbuild.Pipeline { } if p.Type == AnacondaInstallerTypePayload { + var LoraxPath string + + if p.UseRHELLoraxTemplates { + LoraxPath = "80-rhel/runtime-postinstall.tmpl" + } else { + LoraxPath = "99-generic/runtime-postinstall.tmpl" + } + pipeline.AddStage(osbuild.NewAnacondaStage(osbuild.NewAnacondaStageOptions(p.AdditionalAnacondaModules))) pipeline.AddStage(osbuild.NewLoraxScriptStage(&osbuild.LoraxScriptStageOptions{ - Path: "99-generic/runtime-postinstall.tmpl", + Path: LoraxPath, BaseArch: p.platform.GetArch().String(), })) } diff --git a/pkg/manifest/anaconda_installer_iso_tree.go b/pkg/manifest/anaconda_installer_iso_tree.go index cb209de4de..10225c221b 100644 --- a/pkg/manifest/anaconda_installer_iso_tree.go +++ b/pkg/manifest/anaconda_installer_iso_tree.go @@ -37,17 +37,20 @@ type AnacondaInstallerISOTree struct { // Anaconda pipeline. KSPath string - // The path where the payload (tarball or ostree repo) will be stored. + // The path where the payload (tarball, ostree repo, or container) will be stored. PayloadPath string isoLabel string SquashfsCompression string - OSPipeline *OS - OSTreeCommitSource *ostree.SourceSpec + OSPipeline *OS - ostreeCommitSpec *ostree.CommitSpec + CommitSource *ostree.SourceSpec + commitSpec *ostree.CommitSpec + + ContainerSource *container.SourceSpec + containerSpec *container.Spec KernelOpts []string @@ -74,21 +77,27 @@ func NewAnacondaInstallerISOTree(buildPipeline *Build, anacondaPipeline *Anacond return p } -func (p *AnacondaInstallerISOTree) getOSTreeCommitSources() []ostree.SourceSpec { - if p.OSTreeCommitSource == nil { +func (p *AnacondaInstallerISOTree) getOSTreeCommits() []ostree.CommitSpec { + if p.commitSpec == nil { return nil } + return []ostree.CommitSpec{*p.commitSpec} +} - return []ostree.SourceSpec{ - *p.OSTreeCommitSource, +func (p *AnacondaInstallerISOTree) getContainerSpecs() []container.Spec { + if p.containerSpec == nil { + return []container.Spec{} } + return []container.Spec{*p.containerSpec} } -func (p *AnacondaInstallerISOTree) getOSTreeCommits() []ostree.CommitSpec { - if p.ostreeCommitSpec == nil { - return nil +func (p *AnacondaInstallerISOTree) getContainerSources() []container.SourceSpec { + if p.ContainerSource == nil { + return []container.SourceSpec{} + } + return []container.SourceSpec{ + *p.ContainerSource, } - return []ostree.CommitSpec{*p.ostreeCommitSpec} } func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string { @@ -96,10 +105,14 @@ func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string { "squashfs-tools", } - if p.OSTreeCommitSource != nil { + if p.CommitSource != nil { packages = append(packages, "rpm-ostree") } + if p.ContainerSource != nil { + packages = append(packages, "skopeo") + } + if p.OSPipeline != nil { packages = append(packages, "tar") } @@ -107,36 +120,18 @@ func (p *AnacondaInstallerISOTree) getBuildPackages(_ Distro) []string { return packages } -func (p *AnacondaInstallerISOTree) serializeStart(_ []rpmmd.PackageSpec, _ []container.Spec, commits []ostree.CommitSpec) { - if len(commits) == 0 { - // nothing to do - return - } - - if len(commits) > 1 { - panic("pipeline supports at most one ostree commit") +func (p *AnacondaInstallerISOTree) serializeStart(_ []rpmmd.PackageSpec, containers []container.Spec, commits []ostree.CommitSpec) { + if p.commitSpec != nil || p.containerSpec != nil { + panic("double call to serializeStart()") } - - p.ostreeCommitSpec = &commits[0] } func (p *AnacondaInstallerISOTree) serializeEnd() { - p.ostreeCommitSpec = nil + p.commitSpec = nil + p.containerSpec = nil } func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline { - // If the anaconda pipeline is a payload then we need one of two payload types - if p.anacondaPipeline.Type == AnacondaInstallerTypePayload { - if p.ostreeCommitSpec == nil && p.OSPipeline == nil { - panic("missing ostree or ospipeline parameters in ISO tree pipeline") - } - - // But not both payloads - if p.ostreeCommitSpec != nil && p.OSPipeline != nil { - panic("got both ostree and ospipeline parameters in ISO tree pipeline") - } - } - pipeline := p.Base.serialize() kernelOpts := []string{} @@ -263,12 +258,12 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline { copyInputs, )) - if p.ostreeCommitSpec != nil { + if p.commitSpec != nil { // Set up the payload ostree repo pipeline.AddStage(osbuild.NewOSTreeInitStage(&osbuild.OSTreeInitStageOptions{Path: p.PayloadPath})) pipeline.AddStage(osbuild.NewOSTreePullStage( &osbuild.OSTreePullStageOptions{Repo: p.PayloadPath}, - osbuild.NewOstreePullStageInputs("org.osbuild.source", p.ostreeCommitSpec.Checksum, p.ostreeCommitSpec.Ref), + osbuild.NewOstreePullStageInputs("org.osbuild.source", p.commitSpec.Checksum, p.commitSpec.Ref), )) // Configure the kickstart file with the payload and any user options @@ -277,7 +272,7 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline { p.Users, p.Groups, makeISORootPath(p.PayloadPath), - p.ostreeCommitSpec.Ref, + p.commitSpec.Ref, p.OSName) if err != nil { @@ -287,6 +282,40 @@ func (p *AnacondaInstallerISOTree) serialize() osbuild.Pipeline { pipeline.AddStage(osbuild.NewKickstartStage(kickstartOptions)) } + if p.containerSpec != nil { + images := osbuild.NewContainersInputForSources([]container.Spec{*p.containerSpec}) + manifests := osbuild.NewFilesInputForManifestLists([]container.Spec{*p.containerSpec}) + + pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{ + Paths: []osbuild.MkdirStagePath{ + { + Path: p.PayloadPath, + }, + }, + })) + + // copy the container in + pipeline.AddStage(osbuild.NewSkopeoStageWithOCI( + p.PayloadPath, + images, + manifests)) + + kickstartOptions, err := osbuild.NewKickstartStageOptionsWithOSTreeContainer( + p.KSPath, + p.Users, + p.Groups, + path.Join("/run/install/repo", p.PayloadPath), + "oci", + "", + "") + + if err != nil { + panic("failed to create kickstartstage options") + } + + pipeline.AddStage(osbuild.NewKickstartStage(kickstartOptions)) + } + if p.OSPipeline != nil { // Create the payload tarball pipeline.AddStage(osbuild.NewTarStage(&osbuild.TarStageOptions{Filename: p.PayloadPath}, p.OSPipeline.name))