debian-forge-composer/internal/manifest/iso_tree.go
Tomáš Hozza 30c3ea791e osbuild/mkdir: rename Path struct to MkdirStagePath
The plain `Path` name was a bit unfortunate, since it was specific to
the `mkdir` stage, but it was used outside of the `osbuild` package as
`osbuild.Path` which was making a wrong impression of it being a generic
path structure. This is not true.

Rename the structure to contain the stage name.

Signed-off-by: Tomáš Hozza <thozza@redhat.com>
2023-01-19 11:15:33 +01:00

267 lines
7.6 KiB
Go

package manifest
import (
"fmt"
"path"
"github.com/osbuild/osbuild-composer/internal/disk"
"github.com/osbuild/osbuild-composer/internal/osbuild"
"github.com/osbuild/osbuild-composer/internal/ostree"
"github.com/osbuild/osbuild-composer/internal/users"
)
// An AnacondaISOTree represents a tree containing the anaconda installer,
// configuration in terms of a kickstart file, as well as an embedded
// payload to be installed, this payload can either be an ostree
// CommitSpec or OSPipeline for an OS.
type AnacondaISOTree struct {
Base
// TODO: review optional and mandatory fields and their meaning
OSName string
Release string
Users []users.User
Groups []users.Group
PartitionTable *disk.PartitionTable
anacondaPipeline *Anaconda
rootfsPipeline *ISORootfsImg
bootTreePipeline *EFIBootTree
// The location of the kickstart file, if it will be added to the
// bootiso-tree.
// Otherwise, it should be defined in the interactive defaults of the
// Anaconda pipeline.
KSPath string
// The path where the payload (tarball or ostree repo) will be stored.
PayloadPath string
isoLabel string
SquashfsCompression string
OSPipeline *OS
OSTree *ostree.CommitSpec
KernelOpts []string
// Enable ISOLinux stage
ISOLinux bool
}
func NewAnacondaISOTree(m *Manifest,
buildPipeline *Build,
anacondaPipeline *Anaconda,
rootfsPipeline *ISORootfsImg,
bootTreePipeline *EFIBootTree,
isoLabel string) *AnacondaISOTree {
p := &AnacondaISOTree{
Base: NewBase(m, "bootiso-tree", buildPipeline),
anacondaPipeline: anacondaPipeline,
rootfsPipeline: rootfsPipeline,
bootTreePipeline: bootTreePipeline,
isoLabel: isoLabel,
}
buildPipeline.addDependent(p)
if anacondaPipeline.Base.manifest != m {
panic("anaconda pipeline from different manifest")
}
m.addPipeline(p)
return p
}
// Return the ostree commit URL and checksum that will be included in this
func (p *AnacondaISOTree) getOSTreeCommits() []ostree.CommitSpec {
if p.OSTree == nil {
return nil
}
return []ostree.CommitSpec{
*p.OSTree,
}
}
func (p *AnacondaISOTree) getBuildPackages() []string {
packages := []string{
"squashfs-tools",
}
if p.OSTree != nil {
packages = append(packages, "rpm-ostree")
}
if p.OSPipeline != nil {
packages = append(packages, "tar")
}
return packages
}
func (p *AnacondaISOTree) serialize() osbuild.Pipeline {
// We need one of two payloads
if p.OSTree == nil && p.OSPipeline == nil {
panic("missing ostree or ospipeline parameters in ISO tree pipeline")
}
// But not both payloads
if p.OSTree != nil && p.OSPipeline != nil {
panic("got both ostree and ospipeline parameters in ISO tree pipeline")
}
pipeline := p.Base.serialize()
kernelOpts := []string{}
kernelOpts = append(kernelOpts, fmt.Sprintf("inst.stage2=hd:LABEL=%s", p.isoLabel))
if p.KSPath != "" {
kernelOpts = append(kernelOpts, fmt.Sprintf("inst.ks=hd:LABEL=%s:%s", p.isoLabel, p.KSPath))
}
if len(p.KernelOpts) > 0 {
kernelOpts = append(kernelOpts, p.KernelOpts...)
}
pipeline.AddStage(osbuild.NewMkdirStage(&osbuild.MkdirStageOptions{
Paths: []osbuild.MkdirStagePath{
{
Path: "images",
},
{
Path: "images/pxeboot",
},
},
}))
inputName := "tree"
copyStageOptions := &osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/boot/vmlinuz-%s", inputName, p.anacondaPipeline.kernelVer),
To: "tree:///images/pxeboot/vmlinuz",
},
{
From: fmt.Sprintf("input://%s/boot/initramfs-%s.img", inputName, p.anacondaPipeline.kernelVer),
To: "tree:///images/pxeboot/initrd.img",
},
},
}
copyStageInputs := osbuild.NewPipelineTreeInputs(inputName, p.anacondaPipeline.Name())
copyStage := osbuild.NewCopyStageSimple(copyStageOptions, copyStageInputs)
pipeline.AddStage(copyStage)
squashfsOptions := osbuild.SquashfsStageOptions{
Filename: "images/install.img",
}
if p.SquashfsCompression != "" {
squashfsOptions.Compression.Method = p.SquashfsCompression
} else {
// default to xz if not specified
squashfsOptions.Compression.Method = "xz"
}
if squashfsOptions.Compression.Method == "xz" {
squashfsOptions.Compression.Options = &osbuild.FSCompressionOptions{
BCJ: osbuild.BCJOption(p.anacondaPipeline.platform.GetArch().String()),
}
}
squashfsStage := osbuild.NewSquashfsStage(&squashfsOptions, p.rootfsPipeline.Name())
pipeline.AddStage(squashfsStage)
if p.ISOLinux {
isoLinuxOptions := &osbuild.ISOLinuxStageOptions{
Product: osbuild.ISOLinuxProduct{
Name: p.anacondaPipeline.product,
Version: p.anacondaPipeline.version,
},
Kernel: osbuild.ISOLinuxKernel{
Dir: "/images/pxeboot",
Opts: kernelOpts,
},
}
isoLinuxStage := osbuild.NewISOLinuxStage(isoLinuxOptions, p.anacondaPipeline.Name())
pipeline.AddStage(isoLinuxStage)
}
filename := "images/efiboot.img"
pipeline.AddStage(osbuild.NewTruncateStage(&osbuild.TruncateStageOptions{
Filename: filename,
Size: fmt.Sprintf("%d", p.PartitionTable.Size),
}))
efibootDevice := osbuild.NewLoopbackDevice(&osbuild.LoopbackDeviceOptions{Filename: filename})
for _, stage := range osbuild.GenMkfsStages(p.PartitionTable, efibootDevice) {
pipeline.AddStage(stage)
}
inputName = "root-tree"
copyInputs := osbuild.NewPipelineTreeInputs(inputName, p.bootTreePipeline.Name())
copyOptions, copyDevices, copyMounts := osbuild.GenCopyFSTreeOptions(inputName, p.bootTreePipeline.Name(), filename, p.PartitionTable)
pipeline.AddStage(osbuild.NewCopyStage(copyOptions, copyInputs, copyDevices, copyMounts))
copyInputs = osbuild.NewPipelineTreeInputs(inputName, p.bootTreePipeline.Name())
pipeline.AddStage(osbuild.NewCopyStageSimple(
&osbuild.CopyStageOptions{
Paths: []osbuild.CopyStagePath{
{
From: fmt.Sprintf("input://%s/EFI", inputName),
To: "tree:///",
},
},
},
copyInputs,
))
if p.OSTree != 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.OSTree.Checksum, p.OSTree.Ref),
))
// Configure the kickstart file with the payload and any user options
kickstartOptions, err := osbuild.NewKickstartStageOptions(p.KSPath, "", p.Users, p.Groups, makeISORootPath(p.PayloadPath), p.OSTree.Ref, p.OSName)
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))
// If the KSPath is set, we need to add the kickstart stage to this (bootiso-tree) pipeline.
// If it's not specified here, it should have been added to the InteractiveDefaults in the anaconda-tree.
if p.KSPath != "" {
kickstartOptions, err := osbuild.NewKickstartStageOptions(p.KSPath, makeISORootPath(p.PayloadPath), p.Users, p.Groups, "", "", p.OSName)
if err != nil {
panic("failed to create kickstartstage options")
}
pipeline.AddStage(osbuild.NewKickstartStage(kickstartOptions))
}
}
pipeline.AddStage(osbuild.NewDiscinfoStage(&osbuild.DiscinfoStageOptions{
BaseArch: p.anacondaPipeline.platform.GetArch().String(),
Release: p.Release,
}))
return pipeline
}
// 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)
}