osbuild2: new stages and osbuild features

Stages:
- org.osbuild.copy
- org.osbuild.truncate
- org.osbuild.sfdisk
- org.osbuild.qemu
- org.osbuild.mkfs.btrfs
- org.osbuild.mkfs.ext4
- org.osbuild.mkfs.fat
- org.osbuild.mkfs.xfs
- org.osbuild.grub2.inst

Stages can now have devices and mounts in addition to options and
inputs.

Devices:
- org.osbuild.loopback

Mounts:
- org.osbuild.btrfs
- org.osbuild.ext4
- org.osbuild.fat
- org.osbuild.xfs
This commit is contained in:
Achilleas Koutsou 2021-07-06 12:38:34 +02:00 committed by Ondřej Budai
parent 4cf26bb628
commit e85fc3b48c
17 changed files with 510 additions and 0 deletions

View file

@ -0,0 +1,9 @@
package osbuild2
func NewBtrfsMount(source, target string) *Mount {
return &Mount{
Type: "org.osbuild.btrfs",
Source: source,
Target: target,
}
}

View file

@ -0,0 +1,46 @@
package osbuild2
// Stage to copy items from inputs to mount points or the tree. Multiple items
// can be copied. The source and destination is a URL.
type CopyStageOptions struct {
Paths []CopyStagePath `json:"paths"`
}
type CopyStagePath struct {
From string `json:"from"`
To string `json:"to"`
}
func (CopyStageOptions) isStageOptions() {}
type CopyStageInputs map[string]CopyStageInput
type CopyStageInput struct {
inputCommon
References CopyStageReferences `json:"references"`
}
func (CopyStageInputs) isStageInputs() {}
type CopyStageReferences []string
func (CopyStageReferences) isReferences() {}
type CopyStageDevices map[string]Device
func (CopyStageDevices) isStageDevices() {}
type CopyStageMounts map[string]Mount
func (CopyStageMounts) isStageMounts() {}
func NewCopyStage(options *CopyStageOptions, inputs *CopyStageInputs, devices *CopyStageDevices, mounts *CopyStageMounts) *Stage {
return &Stage{
Type: "org.osbuild.copy",
Options: options,
Inputs: inputs,
Devices: devices,
Mounts: mounts,
}
}

View file

@ -0,0 +1,14 @@
package osbuild2
type Devices interface {
isStageDevices()
}
type Device struct {
Type string `json:"type"`
Options DeviceOptions `json:"options"`
}
type DeviceOptions interface {
isDeviceOptions()
}

View file

@ -0,0 +1,9 @@
package osbuild2
func NewExt4Mount(source, target string) *Mount {
return &Mount{
Type: "org.osbuild.ext4",
Source: source,
Target: target,
}
}

View file

@ -0,0 +1,9 @@
package osbuild2
func NewFATMount(source, target string) *Mount {
return &Mount{
Type: "org.osbuild.fat",
Source: source,
Target: target,
}
}

View file

@ -0,0 +1,94 @@
package osbuild2
import (
"encoding/json"
"fmt"
)
// Install the grub2 boot loader for non-UEFI systems or hybrid boot
type Grub2InstStageOptions struct {
// Filename of the disk image
Filename string `json:"filename"`
// Platform of the target system
Platform string `json:"platform"`
Location uint64 `json:"location,omitempty"`
// How to obtain the GRUB core image
Core CoreMkImage `json:"core"`
// Location of grub config
Prefix PrefixPartition `json:"prefix"`
// Sector size (in bytes)
SectorSize *uint64 `json:"sector-size,omitempty"`
}
func (Grub2InstStageOptions) isStageOptions() {}
// Generate the core image via grub-mkimage
type CoreMkImage struct {
Type string `json:"type"`
PartLabel string `json:"partlabel"`
Filesystem string `json:"filesystem"`
}
// Grub2 config on a specific partition, e.g. (,gpt3)/boot
type PrefixPartition struct {
Type string `json:"type"`
PartLabel string `json:"partlabel"`
// The partition number, starting at zero
Number uint `json:"number"`
// Location of the grub config inside the partition
Path string `json:"path"`
}
func NewGrub2InstStage(options *Grub2InstStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.grub2.inst",
Options: options,
}
}
// alias for custom marshaller
type grub2instStageOptions Grub2InstStageOptions
func (options Grub2InstStageOptions) MarshalJSON() ([]byte, error) {
g2options := grub2instStageOptions(options)
valueIn := func(v string, options []string) bool {
for _, o := range options {
if v == o {
return true
}
}
return false
}
// verify enum values
if g2options.Core.Type != "mkimage" {
return nil, fmt.Errorf("org.osbuild.grub2.inst: invalid value %q for core.type", g2options.Core.Type)
}
if !valueIn(g2options.Core.PartLabel, []string{"gpt", "dos"}) {
return nil, fmt.Errorf("org.osbuild.grub2.inst: invalid value %q for core.partlabel", g2options.Core.PartLabel)
}
if !valueIn(g2options.Core.Filesystem, []string{"ext4", "xfs", "btrfs"}) {
return nil, fmt.Errorf("org.osbuild.grub2.inst: invalid value %q for core.filesystem", g2options.Core.Filesystem)
}
if g2options.Prefix.Type != "partition" {
return nil, fmt.Errorf("org.osbuild.grub2.inst: invalid value %q for prefix.type", g2options.Prefix.Type)
}
if !valueIn(g2options.Prefix.PartLabel, []string{"gpt", "dos"}) {
return nil, fmt.Errorf("org.osbuild.grub2.inst: invalid value %q for core.partlabel", g2options.Core.PartLabel)
}
return json.Marshal(g2options)
}

View file

@ -0,0 +1,26 @@
package osbuild2
// Expose a file (or part of it) as a device node
type LoopbackDeviceOptions struct {
// File to associate with the loopback device
Filename string `json:"filename"`
// Start of the data segment
Start uint64 `json:"start,omitempty"`
// Size limit of the data segment (in sectors)
Size uint64 `json:"size,omitempty"`
// Sector size (in bytes)
SectorSize *uint64 `json:"sector-size,omitempty"`
}
func (LoopbackDeviceOptions) isDeviceOptions() {}
func NewLoopbackDevice(options *LoopbackDeviceOptions) *Device {
return &Device{
Type: "org.osbuild.loopback",
Options: options,
}
}

View file

@ -0,0 +1,22 @@
package osbuild2
type MkfsBtrfsStageOptions struct {
UUID string `json:"uuid"`
Label string `json:"label,omitempty"`
}
func (MkfsBtrfsStageOptions) isStageOptions() {}
type MkfsBtrfsStageDevices struct {
Device Device `json:"device"`
}
func (MkfsBtrfsStageDevices) isStageDevices() {}
func NewMkfsBtrfsStage(options *MkfsBtrfsStageOptions, devices *MkfsBtrfsStageDevices) *Stage {
return &Stage{
Type: "org.osbuild.mkfs.btrfs",
Options: options,
Devices: devices,
}
}

View file

@ -0,0 +1,22 @@
package osbuild2
type MkfsExt4StageOptions struct {
UUID string `json:"uuid"`
Label string `json:"label,omitempty"`
}
func (MkfsExt4StageOptions) isStageOptions() {}
type MkfsExt4StageDevices struct {
Device Device `json:"device"`
}
func (MkfsExt4StageDevices) isStageDevices() {}
func NewMkfsExt4Stage(options *MkfsExt4StageOptions, devices *MkfsExt4StageDevices) *Stage {
return &Stage{
Type: "org.osbuild.mkfs.ext4",
Options: options,
Devices: devices,
}
}

View file

@ -0,0 +1,23 @@
package osbuild2
type MkfsFATStageOptions struct {
VolID string `json:"volid"`
Label string `json:"label,omitempty"`
FATSize *int `json:"fat-size,omitempty"`
}
func (MkfsFATStageOptions) isStageOptions() {}
type MkfsFATStageDevices struct {
Device Device `json:"device"`
}
func (MkfsFATStageDevices) isStageDevices() {}
func NewMkfsFATStage(options *MkfsFATStageOptions, devices *MkfsFATStageDevices) *Stage {
return &Stage{
Type: "org.osbuild.mkfs.fat",
Options: options,
Devices: devices,
}
}

View file

@ -0,0 +1,22 @@
package osbuild2
type MkfsXfsStageOptions struct {
UUID string `json:"uuid"`
Label string `json:"label,omitempty"`
}
func (MkfsXfsStageOptions) isStageOptions() {}
type MkfsXfsStageDevices struct {
Device Device `json:"device"`
}
func (MkfsXfsStageDevices) isStageDevices() {}
func NewMkfsXfsStage(options *MkfsXfsStageOptions, devices *MkfsXfsStageDevices) *Stage {
return &Stage{
Type: "org.osbuild.mkfs.xfs",
Options: options,
Devices: devices,
}
}

View file

@ -0,0 +1,16 @@
package osbuild2
type Mounts interface {
isStageMounts()
}
type Mount struct {
Type string `json:"type"`
Source string `json:"source"`
Target string `json:"target"`
Options *MountOptions `json:"options,omitempty"`
}
type MountOptions interface {
isMountOptions()
}

View file

@ -0,0 +1,85 @@
package osbuild2
import (
"encoding/json"
"fmt"
)
// Convert a disk image to a different format.
//
// Some formats support format-specific options:
// qcow2: The compatibility version can be specified via 'compat'
type QEMUStageOptions struct {
// Filename for resulting image
Filename string `json:"filename"`
// Image format and options
Format QEMUFormatOptions `json:"format"`
}
type QEMUFormatOptions interface {
isQEMUFormatOptions()
}
type Qcow2Options struct {
// The type of the format must be 'qcow2'
Type string `json:"type"`
// The qcow2-compatibility-version to use
Compat string `json:"compat"`
}
func (Qcow2Options) isQEMUFormatOptions() {}
func (QEMUStageOptions) isStageOptions() {}
type QEMUStageInputs struct {
Image *QEMUStageInput `json:"image"`
}
func (QEMUStageInputs) isStageInputs() {}
type QEMUStageInput struct {
inputCommon
References QEMUStageReferences `json:"references"`
}
func (QEMUStageInput) isStageInput() {}
type QEMUStageReferences map[string]QEMUFile
func (QEMUStageReferences) isReferences() {}
type QEMUFile struct {
Metadata FileMetadata `json:"metadata,omitempty"`
File string `json:"file,omitempty"`
}
type FileMetadata map[string]interface{}
// NewQEMUStage creates a new QEMU Stage object.
func NewQEMUStage(options *QEMUStageOptions, inputs *QEMUStageInputs) *Stage {
return &Stage{
Type: "org.osbuild.qemu",
Options: options,
Inputs: inputs,
}
}
// alias for custom marshaller
type qemuStageOptions QEMUStageOptions
// Custom marshaller for validating
func (options QEMUStageOptions) MarshalJSON() ([]byte, error) {
switch o := options.Format.(type) {
case Qcow2Options:
if o.Type != "qcow2" {
return nil, fmt.Errorf("invalid format type %q for qcow2 options", o.Type)
}
default:
return nil, fmt.Errorf("unknown format options in QEMU stage: %#v", options.Format)
}
return json.Marshal(qemuStageOptions(options))
}

View file

@ -0,0 +1,51 @@
package osbuild2
// Partition a target using sfdisk(8)
type SfdiskStageOptions struct {
// The type of the partition table
Label string `json:"label"`
// UUID for the disk image's partition table
UUID string `json:"uuid"`
// Partition layout
Partitions []Partition `json:"partitions,omitempty"`
}
func (SfdiskStageOptions) isStageOptions() {}
// Description of a partition
type Partition struct {
// Mark the partition as bootable (dos)
Bootable bool `json:"bootable,omitempty"`
// The partition name (GPT)
Name string `json:"name,omitempty"`
// The size of the partition
Size uint64 `json:"size,omitempty"`
// The start offset of the partition
Start uint64 `json:"start,omitempty"`
// The partition type (UUID or identifier)
Type string `json:"type,omitempty"`
// UUID of the partition (GPT)
UUID string `json:"uuid,omitempty"`
}
type SfdiskStageDevices struct {
Device Device `json:"device"`
}
func (SfdiskStageDevices) isStageDevices() {}
func NewSfdiskStage(options *SfdiskStageOptions, devices *SfdiskStageDevices) *Stage {
return &Stage{
Type: "org.osbuild.sfdisk",
Options: options,
Devices: devices,
}
}

View file

@ -14,6 +14,8 @@ type Stage struct {
Inputs Inputs `json:"inputs,omitempty"`
Options StageOptions `json:"options,omitempty"`
Devices Devices `json:"devices,omitempty"`
Mounts Mounts `json:"mounts,omitempty"`
}
// Collection of Inputs for a Stage
@ -55,6 +57,8 @@ type rawStage struct {
Type string `json:"type"`
Options json.RawMessage `json:"options"`
Inputs json.RawMessage `json:"inputs"`
Devices json.RawMessage `json:"devices"`
Mounts json.RawMessage `json:"mounts"`
}
// UnmarshalJSON unmarshals JSON into a Stage object. Each type of stage has
@ -66,6 +70,8 @@ func (stage *Stage) UnmarshalJSON(data []byte) error {
}
var options StageOptions
var inputs Inputs
var devices Devices
var mounts Mounts
switch rawStage.Type {
case "org.osbuild.authselect":
options = new(AuthselectStageOptions)
@ -129,6 +135,31 @@ func (stage *Stage) UnmarshalJSON(data []byte) error {
options = new(OSTreeInitStageOptions)
case "org.osbuild.ostree.preptree":
options = new(OSTreePrepTreeStageOptions)
case "org.osbuild.truncate":
options = new(TruncateStageOptions)
case "org.osbuild.sfdisk":
options = new(SfdiskStageOptions)
devices = new(SfdiskStageDevices)
case "org.osbuild.copy":
options = new(CopyStageOptions)
inputs = new(CopyStageInputs)
devices = new(CopyStageDevices)
mounts = new(CopyStageMounts)
case "org.osbuild.mkfs.btrfs":
options = new(MkfsBtrfsStageOptions)
devices = new(MkfsBtrfsStageDevices)
case "org.osbuild.mkfs.ext4":
options = new(MkfsExt4StageOptions)
devices = new(MkfsExt4StageDevices)
case "org.osbuild.mkfs.fat":
options = new(MkfsFATStageOptions)
devices = new(MkfsFATStageDevices)
case "org.osbuild.mkfs.xfs":
options = new(MkfsXfsStageOptions)
devices = new(MkfsXfsStageDevices)
case "org.osbuild.qemu":
options = new(QEMUStageOptions)
inputs = new(QEMUStageInputs)
default:
return fmt.Errorf("unexpected stage type: %s", rawStage.Type)
}
@ -144,6 +175,8 @@ func (stage *Stage) UnmarshalJSON(data []byte) error {
stage.Type = rawStage.Type
stage.Options = options
stage.Inputs = inputs
stage.Devices = devices
stage.Mounts = mounts
return nil
}

View file

@ -0,0 +1,20 @@
package osbuild2
// Create, shrink, or extend a file
type TruncateStageOptions struct {
// Image filename
Filename string `json:"filename"`
// Desired size
Size string `json:"size"`
}
func (TruncateStageOptions) isStageOptions() {}
func NewTruncateStage(options *TruncateStageOptions) *Stage {
return &Stage{
Type: "org.osbuild.truncate",
Options: options,
}
}

View file

@ -0,0 +1,9 @@
package osbuild2
func NewXfsMount(source, target string) *Mount {
return &Mount{
Type: "org.osbuild.xfs",
Source: source,
Target: target,
}
}