distro/rhel85: add image and qcow pipelines
Live image pipeline: Creates the live image in a file through a loopback device. Stages: - truncate: create the file to hold the image - sfdisk: partition the device - mkfs.fat, mkfs.xfs: create the filesystems - copy: copy the tree from the previous pipeline (the OS pipeline) into the directories where the partitions are mounted - grub2.inst: install the bootloader QEMU pipeline: Convert the live image from the previous pipeline to a qcow2 image.
This commit is contained in:
parent
6debb62758
commit
dec74dba32
5 changed files with 338 additions and 1 deletions
|
|
@ -3,9 +3,12 @@ package rhel85
|
|||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"strings"
|
||||
|
||||
"github.com/osbuild/osbuild-composer/internal/blueprint"
|
||||
"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"
|
||||
)
|
||||
|
|
@ -48,6 +51,13 @@ func qcow2Pipelines(t *imageType, customizations *blueprint.Customizations, opti
|
|||
treePipeline.AddStage(osbuild.NewGRUB2Stage(grub2StageOptions(&partitionTable, t.kernelOptions, customizations.GetKernel(), packageSetSpecs["packages"], t.arch.uefi, t.arch.legacy)))
|
||||
pipelines = append(pipelines, *treePipeline)
|
||||
|
||||
diskfile := "disk.img"
|
||||
imagePipeline := liveImagePipeline(treePipeline.Name, diskfile, &partitionTable, t.arch.legacy)
|
||||
pipelines = append(pipelines, *imagePipeline)
|
||||
|
||||
qemuPipeline := qemuPipeline(imagePipeline.Name, diskfile, t.filename)
|
||||
pipelines = append(pipelines, *qemuPipeline)
|
||||
|
||||
return pipelines, nil
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +373,7 @@ func tarStage(source, filename string) *osbuild.Stage {
|
|||
tree := new(osbuild.TarStageInput)
|
||||
tree.Type = "org.osbuild.tree"
|
||||
tree.Origin = "org.osbuild.pipeline"
|
||||
tree.References = []string{fmt.Sprintf("name:%s", source)}
|
||||
tree.References = []string{"name:" + source}
|
||||
return osbuild.NewTarStage(&osbuild.TarStageOptions{Filename: filename}, &osbuild.TarStageInputs{Tree: tree})
|
||||
}
|
||||
|
||||
|
|
@ -485,3 +495,98 @@ func bootISOPipeline(filename string, arch string) *osbuild.Pipeline {
|
|||
|
||||
return p
|
||||
}
|
||||
|
||||
func liveImagePipeline(inputPipelineName string, outputFilename string, pt *disk.PartitionTable, platform 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(osbuild.NewGrub2InstStage(grub2InstStageOptions(outputFilename, pt, 2, platform)))
|
||||
|
||||
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 string, inputFilename, outputFilename string) *osbuild.Pipeline {
|
||||
p := new(osbuild.Pipeline)
|
||||
p.Name = "qcow2"
|
||||
p.Build = "name:build"
|
||||
|
||||
qemuStage := osbuild.NewQEMUStage(qemuStageOptions(outputFilename), qemuStageInputs(inputPipelineName, inputFilename))
|
||||
p.AddStage(qemuStage)
|
||||
return p
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,3 +49,26 @@ func xorrisofsStageInputs() *osbuild.XorrisofsStageInputs {
|
|||
input.References = osbuild.XorrisofsStageReferences{"name:bootiso-tree"}
|
||||
return &osbuild.XorrisofsStageInputs{Tree: input}
|
||||
}
|
||||
|
||||
func copyPipelineTreeInputs(name, inputPipeline string) *osbuild.CopyStageInputs {
|
||||
inputName := "root-tree"
|
||||
treeInput := osbuild.CopyStageInput{}
|
||||
treeInput.Type = "org.osbuild.tree"
|
||||
treeInput.Origin = "org.osbuild.pipeline"
|
||||
treeInput.References = []string{"name:" + inputPipeline}
|
||||
return &osbuild.CopyStageInputs{inputName: treeInput}
|
||||
}
|
||||
|
||||
func qemuStageInputs(stage, file string) *osbuild.QEMUStageInputs {
|
||||
stageKey := "name:" + stage
|
||||
ref := map[string]osbuild.QEMUFile{
|
||||
stageKey: {
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
input := new(osbuild.QEMUStageInput)
|
||||
input.Type = "org.osbuild.files"
|
||||
input.Origin = "org.osbuild.pipeline"
|
||||
input.References = ref
|
||||
return &osbuild.QEMUStageInputs{Image: input}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -350,3 +350,127 @@ func grub2StageOptions(pt *disk.PartitionTable, kernelOptions string, kernel *bl
|
|||
|
||||
return &stageOptions
|
||||
}
|
||||
|
||||
// sfdiskStageOptions creates the options and devices properties for an
|
||||
// org.osbuild.sfdisk stage based on a partition table description
|
||||
func sfdiskStageOptions(pt *disk.PartitionTable, device *osbuild.Device) (*osbuild.SfdiskStageOptions, *osbuild.SfdiskStageDevices) {
|
||||
stageDevices := &osbuild.SfdiskStageDevices{
|
||||
Device: *device,
|
||||
}
|
||||
|
||||
partitions := make([]osbuild.Partition, len(pt.Partitions))
|
||||
for idx, p := range pt.Partitions {
|
||||
partitions[idx] = osbuild.Partition{
|
||||
Bootable: p.Bootable,
|
||||
Size: p.Size,
|
||||
Start: p.Start,
|
||||
Type: p.Type,
|
||||
UUID: p.UUID,
|
||||
}
|
||||
}
|
||||
stageOptions := &osbuild.SfdiskStageOptions{
|
||||
Label: pt.Type,
|
||||
UUID: pt.UUID,
|
||||
Partitions: partitions,
|
||||
}
|
||||
|
||||
return stageOptions, stageDevices
|
||||
}
|
||||
|
||||
// copyFSTreeOptions creates the options, inputs, devices, and mounts properties
|
||||
// for an org.osbuild.copy stage for a given source tree using a partition
|
||||
// table description to define the mounts
|
||||
func copyFSTreeOptions(inputName, inputPipeline string, pt *disk.PartitionTable, device *osbuild.Device) (
|
||||
*osbuild.CopyStageOptions,
|
||||
*osbuild.CopyStageDevices,
|
||||
*osbuild.CopyStageMounts,
|
||||
) {
|
||||
// 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("copyStageOptions: failed to convert device options to loopback options")
|
||||
}
|
||||
|
||||
devices := make(map[string]osbuild.Device, len(pt.Partitions))
|
||||
mounts := make(map[string]osbuild.Mount, len(pt.Partitions))
|
||||
for _, p := range pt.Partitions {
|
||||
if p.Filesystem == nil {
|
||||
// no filesystem for partition (e.g., BIOS boot)
|
||||
continue
|
||||
}
|
||||
name := filepath.Base(p.Filesystem.Mountpoint)
|
||||
if name == "/" {
|
||||
name = "root"
|
||||
}
|
||||
devices[name] = *osbuild.NewLoopbackDevice(
|
||||
&osbuild.LoopbackDeviceOptions{
|
||||
Filename: devOptions.Filename,
|
||||
Start: p.Start,
|
||||
Size: p.Size,
|
||||
},
|
||||
)
|
||||
var mount *osbuild.Mount
|
||||
switch p.Filesystem.Type {
|
||||
case "xfs":
|
||||
mount = osbuild.NewXfsMount(name, p.Filesystem.Mountpoint)
|
||||
case "vfat":
|
||||
mount = osbuild.NewFATMount(name, p.Filesystem.Mountpoint)
|
||||
case "ext4":
|
||||
mount = osbuild.NewExt4Mount(name, p.Filesystem.Mountpoint)
|
||||
case "btrfs":
|
||||
mount = osbuild.NewBtrfsMount(name, p.Filesystem.Mountpoint)
|
||||
default:
|
||||
panic("unknown fs type " + p.Type)
|
||||
}
|
||||
mounts[name] = *mount
|
||||
}
|
||||
|
||||
stageMounts := osbuild.CopyStageMounts(mounts)
|
||||
stageDevices := osbuild.CopyStageDevices(devices)
|
||||
|
||||
options := osbuild.CopyStageOptions{
|
||||
Paths: []osbuild.CopyStagePath{
|
||||
{
|
||||
From: fmt.Sprintf("input://%s/", inputName),
|
||||
To: "mount://root/",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return &options, &stageDevices, &stageMounts
|
||||
}
|
||||
|
||||
func grub2InstStageOptions(filename string, pt *disk.PartitionTable, platform string) *osbuild.Grub2InstStageOptions {
|
||||
bootPartIndex := findBootPartition(pt)
|
||||
core := osbuild.CoreMkImage{
|
||||
Type: "mkimage",
|
||||
PartLabel: pt.Type,
|
||||
Filesystem: pt.Partitions[bootPartIndex].Filesystem.Type,
|
||||
}
|
||||
|
||||
prefix := osbuild.PrefixPartition{
|
||||
Type: "partition",
|
||||
PartLabel: pt.Type,
|
||||
Number: bootPartIndex,
|
||||
Path: "/boot/grub2",
|
||||
}
|
||||
|
||||
return &osbuild.Grub2InstStageOptions{
|
||||
Filename: filename,
|
||||
Platform: platform,
|
||||
Location: pt.Partitions[0].Start,
|
||||
Core: core,
|
||||
Prefix: prefix,
|
||||
}
|
||||
}
|
||||
|
||||
func qemuStageOptions(filename string) *osbuild.QEMUStageOptions {
|
||||
return &osbuild.QEMUStageOptions{
|
||||
Filename: filename,
|
||||
Format: osbuild.Qcow2Options{
|
||||
Type: "qcow2",
|
||||
Compat: "0.10",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
48
internal/osbuild2/qemu_stage_test.go
Normal file
48
internal/osbuild2/qemu_stage_test.go
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package osbuild2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewQemukStage(t *testing.T) {
|
||||
|
||||
formatOptionsList := []QEMUFormatOptions{
|
||||
Qcow2Options{
|
||||
Type: "qcow2",
|
||||
Compat: "0.10",
|
||||
},
|
||||
VPCOptions{
|
||||
Type: "vpc",
|
||||
},
|
||||
VMDKOptions{
|
||||
Type: "vmdk",
|
||||
},
|
||||
}
|
||||
|
||||
input := new(QEMUStageInput)
|
||||
input.Type = "org.osbuild.files"
|
||||
input.Origin = "org.osbuild.pipeline"
|
||||
input.References = map[string]QEMUFile{
|
||||
"name:stage": {
|
||||
File: "img.raw",
|
||||
},
|
||||
}
|
||||
inputs := QEMUStageInputs{Image: input}
|
||||
|
||||
for _, format := range formatOptionsList {
|
||||
options := QEMUStageOptions{
|
||||
Filename: "img.out",
|
||||
Format: format,
|
||||
}
|
||||
expectedStage := &Stage{
|
||||
Type: "org.osbuild.qemu",
|
||||
Options: &options,
|
||||
Inputs: &inputs,
|
||||
}
|
||||
|
||||
actualStage := NewQEMUStage(&options, &inputs)
|
||||
assert.Equal(t, expectedStage, actualStage)
|
||||
}
|
||||
}
|
||||
37
internal/osbuild2/sfdisk_stage_test.go
Normal file
37
internal/osbuild2/sfdisk_stage_test.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package osbuild2
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewSfdiskStage(t *testing.T) {
|
||||
|
||||
partition := Partition{
|
||||
Bootable: true,
|
||||
Name: "root",
|
||||
Size: 2097152,
|
||||
Start: 0,
|
||||
Type: "C12A7328-F81F-11D2-BA4B-00A0C93EC93B",
|
||||
UUID: "68B2905B-DF3E-4FB3-80FA-49D1E773AA33",
|
||||
}
|
||||
|
||||
options := SfdiskStageOptions{
|
||||
Label: "gpt",
|
||||
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
|
||||
Partitions: []Partition{partition},
|
||||
}
|
||||
|
||||
device := NewLoopbackDevice(&LoopbackDeviceOptions{Filename: "disk.raw"})
|
||||
devices := SfdiskStageDevices{*device}
|
||||
|
||||
expectedStage := &Stage{
|
||||
Type: "org.osbuild.sfdisk",
|
||||
Options: &options,
|
||||
Devices: &devices,
|
||||
}
|
||||
|
||||
actualStage := NewSfdiskStage(&options, &devices)
|
||||
assert.Equal(t, expectedStage, actualStage)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue