Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: osbuild/images
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v0.128.0
Choose a base ref
...
head repository: osbuild/images
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v0.129.0
Choose a head ref
  • 8 commits
  • 17 files changed
  • 3 contributors

Commits on Mar 27, 2025

  1. disk: add luks/lvm payload loading support via json/yaml

    This commit adds support to load payloads for LUKS/LVM via
    json/yaml. This is needed for PR#1339 where we extract the
    partition tables into YAML. We have nested LUKS/LVM partitions
    in e.g. the iot simplified installer.
    
    This sadly involves some duplicated code in the Partition,
    LUKSContainer, LVMLogicalVolume `UnmarshalJSON()` code.
    
    Ideally we would write somethign like:
    ```go
    func payloadUnmarshaller[T any](data []byte) (*T, error) {
    	type alias T
    	var withoutPayload struct {
    		alias
    		Payload     json.RawMessage `json:"payload"`
    		PayloadType string          `json:"payload_type"`
    	}
    	if err := jsonUnmarshalStrict(data, &withoutPayload); err != nil {
    		return nil, fmt.Errorf("cannot unmarshal %q: %w", data, err)
    	}
    	t := T(withoutPayload.alias)
    
    	payload, err := unmarshalJSONPayload(data)
    	t.Payload = payload
    	return payload, err
    }
    
    func (lv *LVMLogicalVolume) UnmarshalJSON(data []byte) (err error) {
    	*lv, err = payloadUnmarshaller[*LVMLogicalVolume](data)
    	return err
    }
    ```
    but GO does not let us and sadly its pretty fundamental:
    ```
    ./lvm.go:248:13: cannot use a type parameter as RHS in type declaration
    ```
    and without type aliases in the custom unmarshaler we run into
    an infinite recursion. So unless go supports this we will have to
    live with the duplication.
    mvo5 authored and supakeen committed Mar 27, 2025
    Copy the full SHA
    2c41be1 View commit details
  2. defs: extract splitDistroNameVer() helper

    This helper can split name,version from a distro name like
    `centos-stream-10`. Note that we cannot use distroidparser
    here (which would be preferable) because of import cycles.
    mvo5 authored and supakeen committed Mar 27, 2025
    Copy the full SHA
    1c8c5a6 View commit details
  3. distro/defs: add defs.PartitionTable() helper

    The new `defs.PartitionTable()` can load the base partition table
    configuration from YAML and add some json tags to the disk
    package for minimal partition table loading.
    mvo5 authored and supakeen committed Mar 27, 2025

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature.
    Copy the full SHA
    3cc50c1 View commit details
  4. distro: move fedora partition tables into YAML

    This commit moves all partition table handling for fedora out into
    the `distro.yaml`.
    mvo5 authored and supakeen committed Mar 27, 2025
    Copy the full SHA
    5d69f6e View commit details
  5. defs: add partition_table_override to distro.yaml

    With the most recent changes for the `iot-raw` imagetype
    in fedora we need a way to conditionally override parts
    of the partition table.
    
    This commit adds this support via a simple:
    ```yaml
        partition_table_override:
          condition:
            version_greater_or_equal:
              "42":
                - select_partition: 2
                  fstab_options: "defaults,ro"
    ```
    It currently supports `size` (we will need this for RHEL) and
    `fstab_options`. It is sadly not generic (we can look into
    this using reflection) and a bit ad-hoc, i.e. the selector
    is currently very primitive and will fail for nested payloads
    (like overrides for a FS inside LUKS). We have no need for
    the nesting yet though and if it comes up we could invent a
    notion like (`select_partiton: 2:1` or similar /if/ we need
    it).
    mvo5 authored and supakeen committed Mar 27, 2025
    Copy the full SHA
    dcacef3 View commit details
  6. fedora: use partition_table_override to tweak iot_raw_image

    This is doing the equivalent of
    #1352
    mvo5 authored and supakeen committed Mar 27, 2025
    Copy the full SHA
    06808df View commit details

Commits on Mar 28, 2025

  1. osbuild: create .mount and .swap units in /etc/systemd

    Create .mount and .swap units, which replace /etc/fstab, under
    /etc/systemd instead of /usr/lib/systemd.
    
    This is needed for ostree-based images.
    It's also probably more correct to put the files in /etc in general.
    achilleas-k committed Mar 28, 2025
    Copy the full SHA
    4394425 View commit details
  2. schutzfile: Update osbuild dependency commit ID

    schutzbot authored and achilleas-k committed Mar 28, 2025
    Copy the full SHA
    3ff9568 View commit details
8 changes: 4 additions & 4 deletions Schutzfile
Original file line number Diff line number Diff line change
@@ -9,28 +9,28 @@
"centos-9": {
"dependencies": {
"osbuild": {
"commit": "e93cd75e5b23bc16f19eb032226f19e0957ddfb6"
"commit": "1668c78cfb63c927b1b2073fc06f1b3351d3418b"
}
}
},
"centos-10": {
"dependencies": {
"osbuild": {
"commit": "e93cd75e5b23bc16f19eb032226f19e0957ddfb6"
"commit": "1668c78cfb63c927b1b2073fc06f1b3351d3418b"
}
}
},
"fedora-40": {
"dependencies": {
"osbuild": {
"commit": "e93cd75e5b23bc16f19eb032226f19e0957ddfb6"
"commit": "1668c78cfb63c927b1b2073fc06f1b3351d3418b"
}
}
},
"fedora-41": {
"dependencies": {
"osbuild": {
"commit": "e93cd75e5b23bc16f19eb032226f19e0957ddfb6"
"commit": "1668c78cfb63c927b1b2073fc06f1b3351d3418b"
}
},
"repos": [
3 changes: 1 addition & 2 deletions cmd/otk/osbuild-gen-partition-table/main_test.go
Original file line number Diff line number Diff line change
@@ -224,8 +224,7 @@ var expectedSimplePartOutput = `{
"size": 1048576,
"type": "21686148-6449-6E6F-744E-656564454649",
"bootable": true,
"uuid": "FAC7F1FB-3E8D-4137-A512-961DE09A5549",
"payload_type": "no-payload"
"uuid": "FAC7F1FB-3E8D-4137-A512-961DE09A5549"
},
{
"start": 10097152,
26 changes: 24 additions & 2 deletions pkg/disk/luks.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package disk

import (
"encoding/json"
"fmt"
"math/rand"
"reflect"
@@ -29,7 +30,7 @@ type ClevisBind struct {

// If enabled, the passphrase will be removed from the LUKS device at the
// end of the build (using the org.osbuild.luks2.remove-key stage).
RemovePassphrase bool
RemovePassphrase bool `json:"remove_passphrase"`
}

// LUKSContainer represents a LUKS encrypted volume.
@@ -47,7 +48,7 @@ type LUKSContainer struct {
// Parameters for binding the LUKS device.
Clevis *ClevisBind

Payload Entity
Payload Entity `json:"payload,omitempty"`
}

func init() {
@@ -131,3 +132,24 @@ func (lc *LUKSContainer) minSize(size uint64) uint64 {
}
return minSize
}

func (lc *LUKSContainer) UnmarshalJSON(data []byte) (err error) {
// keep in sync with lvm.go,partition.go,luks.go
type alias LUKSContainer
var withoutPayload struct {
alias
Payload json.RawMessage `json:"payload"`
PayloadType string `json:"payload_type"`
}
if err := jsonUnmarshalStrict(data, &withoutPayload); err != nil {
return fmt.Errorf("cannot unmarshal %q: %w", data, err)
}
*lc = LUKSContainer(withoutPayload.alias)

lc.Payload, err = unmarshalJSONPayload(data)
return err
}

func (lc *LUKSContainer) UnmarshalYAML(unmarshal func(any) error) error {
return unmarshalYAMLviaJSON(lc, unmarshal)
}
34 changes: 33 additions & 1 deletion pkg/disk/lvm.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package disk

import (
"encoding/json"
"fmt"
"reflect"
"strings"
@@ -16,7 +17,7 @@ type LVMVolumeGroup struct {
Name string
Description string

LogicalVolumes []LVMLogicalVolume
LogicalVolumes []LVMLogicalVolume `json:"logical_volumes,omitempty"`
}

func init() {
@@ -174,6 +175,16 @@ func (vg *LVMVolumeGroup) minSize(size uint64) uint64 {
return vg.AlignUp(size)
}

func (vg *LVMVolumeGroup) UnmarshalJSON(data []byte) error {
type alias LVMVolumeGroup
var tmp alias
if err := json.Unmarshal(data, &tmp); err != nil {
return err
}
*vg = LVMVolumeGroup(tmp)
return nil
}

type LVMLogicalVolume struct {
Name string
Size uint64
@@ -232,3 +243,24 @@ func lvname(path string) string {
path = strings.TrimLeft(path, "/")
return strings.ReplaceAll(path, "/", "_") + "lv"
}

func (lv *LVMLogicalVolume) UnmarshalJSON(data []byte) (err error) {
// keep in sync with lvm.go,partition.go,luks.go
type alias LVMLogicalVolume
var withoutPayload struct {
alias
Payload json.RawMessage `json:"payload"`
PayloadType string `json:"payload_type"`
}
if err := jsonUnmarshalStrict(data, &withoutPayload); err != nil {
return fmt.Errorf("cannot unmarshal %q: %w", data, err)
}
*lv = LVMLogicalVolume(withoutPayload.alias)

lv.Payload, err = unmarshalJSONPayload(data)
return err
}

func (lv *LVMLogicalVolume) UnmarshalYAML(unmarshal func(any) error) error {
return unmarshalYAMLviaJSON(lv, unmarshal)
}
41 changes: 13 additions & 28 deletions pkg/disk/partition.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package disk

import (
"bytes"
"encoding/json"
"fmt"
"reflect"
)

type Partition struct {
@@ -105,7 +103,7 @@ func (p *Partition) IsPReP() bool {
func (p *Partition) MarshalJSON() ([]byte, error) {
type partAlias Partition

entityName := "no-payload"
var entityName string
if p.Payload != nil {
entityName = p.Payload.EntityName()
}
@@ -121,36 +119,23 @@ func (p *Partition) MarshalJSON() ([]byte, error) {
return json.Marshal(partWithPayloadType)
}

func (p *Partition) UnmarshalJSON(data []byte) error {
type partAlias Partition
var partWithoutPayload struct {
partAlias
func (p *Partition) UnmarshalJSON(data []byte) (err error) {
// keep in sync with lvm.go,partition.go,luks.go
type alias Partition
var withoutPayload struct {
alias
Payload json.RawMessage `json:"payload"`
PayloadType string `json:"payload_type,omitempty"`
}

dec := json.NewDecoder(bytes.NewBuffer(data))
if err := dec.Decode(&partWithoutPayload); err != nil {
return fmt.Errorf("cannot build partition from %q: %w", data, err)
PayloadType string `json:"payload_type"`
}
*p = Partition(partWithoutPayload.partAlias)
// no payload, e.g. bios partiton
if partWithoutPayload.PayloadType == "no-payload" {
return nil
if err := jsonUnmarshalStrict(data, &withoutPayload); err != nil {
return fmt.Errorf("cannot unmarshal %q: %w", data, err)
}
*p = Partition(withoutPayload.alias)

entType := payloadEntityMap[partWithoutPayload.PayloadType]
if entType == nil {
return fmt.Errorf("cannot build partition from %q: unknown payload %q", data, partWithoutPayload.PayloadType)
}
entValP := reflect.New(entType).Elem().Addr()
ent := entValP.Interface()
if err := json.Unmarshal(partWithoutPayload.Payload, &ent); err != nil {
return err
}
p.Payload = ent.(PayloadEntity)
return nil
p.Payload, err = unmarshalJSONPayload(data)
return err
}

func (t *Partition) UnmarshalYAML(unmarshal func(any) error) error {
return unmarshalYAMLviaJSON(t, unmarshal)
}
2 changes: 1 addition & 1 deletion pkg/disk/partition_table.go
Original file line number Diff line number Diff line change
@@ -27,7 +27,7 @@ type PartitionTable struct {
// Extra space at the end of the partition table (sectors)
ExtraPadding uint64 `json:"extra_padding,omitempty"`
// Starting offset of the first partition in the table (Mb)
StartOffset uint64 `json:"start_offset,omitempty"`
StartOffset uint64 `json:"start_offset,omitempty" yaml:"start_offset,omitempty"`
}

type PartitioningMode string
77 changes: 76 additions & 1 deletion pkg/disk/partition_table_yaml_test.go
Original file line number Diff line number Diff line change
@@ -39,7 +39,6 @@ partition_table:
bootable: true
type: *bios_boot_partition_guid
uuid: *bios_boot_partition_uuid
payload_type: "no-payload"
- &default_partition_table_part_efi
size: 209_715_200 # 200 MiB
type: *efi_system_partition_guid
@@ -118,3 +117,79 @@ partition_table:
}
assert.Equal(t, expected, ptWrapper.PartitionTable)
}

func TestPartitionTableUnmarshalYAMLwithLUKS(t *testing.T) {
inputYAML := `
partition_table:
uuid: "D209C89E-EA5E-4FBD-B161-B461CCE297E0"
type: "gpt"
partitions:
- size: 1_048_576 # 1 MiB
bootable: true
- payload_type: "luks"
size: 987654321
payload:
label: "crypt_root"
cipher: "cipher_null"
passphrase: "osbuild"
pbkdf:
iterations: 4
clevis:
pin: "null"
payload_type: "lvm"
payload:
name: "rootvg"
description: "bla"
logical_volumes:
- size: 123456789
name: "rootlv"
payload_type: "filesystem"
payload:
type: "ext4"
mountpoint: "/"
`
var ptWrapper struct {
PartitionTable disk.PartitionTable `yaml:"partition_table"`
}

err := yaml.Unmarshal([]byte(inputYAML), &ptWrapper)
require.NoError(t, err)
expected := disk.PartitionTable{
UUID: "D209C89E-EA5E-4FBD-B161-B461CCE297E0",
Type: 2,
Partitions: []disk.Partition{
{
Size: 1048576,
Bootable: true,
}, {
Size: 987654321,
Payload: &disk.LUKSContainer{
Label: "crypt_root",
Cipher: "cipher_null",
Passphrase: "osbuild",
PBKDF: disk.Argon2id{
Iterations: 4,
},
Clevis: &disk.ClevisBind{
Pin: "null",
},
Payload: &disk.LVMVolumeGroup{
Name: "rootvg",
Description: "bla",
LogicalVolumes: []disk.LVMLogicalVolume{
{
Name: "rootlv",
Size: 123456789,
Payload: &disk.Filesystem{
Type: "ext4",
Mountpoint: "/",
},
},
},
},
},
},
},
}
assert.Equal(t, expected, ptWrapper.PartitionTable)
}
10 changes: 8 additions & 2 deletions pkg/disk/partition_test.go
Original file line number Diff line number Diff line change
@@ -28,8 +28,14 @@ func TestMarshalUnmarshalSimple(t *testing.T) {

func TestMarshalUnmarshalSad(t *testing.T) {
var part disk.Partition
err := json.Unmarshal([]byte(`{"randon": "json"}`), &part)
assert.ErrorContains(t, err, `cannot build partition from "{`)
err := json.Unmarshal([]byte(`{"random": "json"}`), &part)
assert.ErrorContains(t, err, `json: unknown field "random"`)
}

func TestMarshalUnmarshalPayloadButNoPayloadTypeSad(t *testing.T) {
var part disk.Partition
err := json.Unmarshal([]byte(`{"payload": "some-payload-but-no-payload-type-field"}`), &part)
assert.ErrorContains(t, err, `cannot build payload: empty payload type but payload is`)
}

func TestMarshalUnmarshalPartitionHappy(t *testing.T) {
34 changes: 34 additions & 0 deletions pkg/disk/unmarshal.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package disk

import (
"bytes"
"encoding/json"
"fmt"
"reflect"
)

// unmarshalYAMLviaJSON unmarshals via the JSON interface, this avoids code
@@ -22,3 +24,35 @@ func unmarshalYAMLviaJSON(u json.Unmarshaler, unmarshal func(any) error) error {
}
return nil
}

func unmarshalJSONPayload(data []byte) (PayloadEntity, error) {
var payload struct {
Payload json.RawMessage `json:"payload"`
PayloadType string `json:"payload_type,omitempty"`
}
if err := json.Unmarshal(data, &payload); err != nil {
return nil, fmt.Errorf("cannot peek payload: %w", err)
}
if payload.PayloadType == "" {
if len(payload.Payload) > 0 {
return nil, fmt.Errorf("cannot build payload: empty payload type but payload is: %q", payload.Payload)
}
return nil, nil
}
entType := payloadEntityMap[payload.PayloadType]
if entType == nil {
return nil, fmt.Errorf("cannot build payload from %q: unknown payload type %q", data, payload.PayloadType)
}
entValP := reflect.New(entType).Elem().Addr()
ent := entValP.Interface()
if err := jsonUnmarshalStrict(payload.Payload, &ent); err != nil {
return nil, fmt.Errorf("cannot decode payload for %q: %w", data, err)
}
return ent.(PayloadEntity), nil
}

func jsonUnmarshalStrict(data []byte, v any) error {
dec := json.NewDecoder(bytes.NewBuffer(data))
dec.DisallowUnknownFields()
return dec.Decode(&v)
}
Loading