Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/imagecustomizer/api/configuration/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ os:
- [options](./module.md#options-mapstring-string)
- [overlays](./os.md#overlays-overlay) ([overlay type](./overlay.md))
- [uki](./os.md#uki-uki) ([uki type](./uki.md))
- [kernels](./uki.md#kernels)
- [mode](./uki.md#mode-string)
- [imageHistory](./os.md#imagehistory-string)
- [scripts](./config.md#scripts-scripts) ([scripts type](./scripts.md))
- [postCustomization](./scripts.md#postcustomization-script) ([script type](./script.md))
Expand Down
88 changes: 69 additions & 19 deletions docs/imagecustomizer/api/configuration/uki.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,47 +25,97 @@ os:
bootLoader:
resetType: hard-reset
uki:
kernels: auto
mode: create
previewFeatures:
- uki
```

Added in v0.8.

## kernels
## mode [string]

Specifies which kernels to produce UKIs for.
Specifies how to handle UKI creation or preservation.

The value can either contain:
Required.

- The string `"auto"`
- A list of kernel version strings.
Supported values:

When `"auto"` is specified, the tool automatically searches for all the
installed kernels and produces UKIs for all the found kernels.
- `create`: Create UKI files for all installed kernels. When used with a base image that
already has UKIs, the new UKIs will be generated and override the old ones.

If a list of kernel versions is provided, then the tool will only produce UKIs
for the kernels specified.
- `passthrough`: Preserve existing UKI files without modification.

The kernel versions must match the regex: `^\d+\.\d+\.\d+(\.\d+)?(-[\w\-\.]+)?$`.
Examples of valid kernel formats: `6.6.51.1-5.azl3`, `5.10.120-4.custom`, `4.18.0-80.el8`.
Example (creating UKIs):

Example:
```yaml
os:
bootLoader:
resetType: hard-reset
uki:
mode: create

kernelCommandLine:
extraCommandLine:
- rd.info

previewFeatures:
- uki
```

Example (passthrough mode):

```yaml
# Customize an existing UKI image without regenerating UKI files.
# This preserves the existing kernel, initramfs, and cmdline in the UKI.
os:
uki:
kernels: auto
mode: passthrough

# You can still perform OS customizations:
packages:
install:
- nginx
- vim

additionalFiles:
- path: /etc/app-config.txt
content: |
Application configuration

previewFeatures:
- uki
```

Example:
Example (re-customizing UKI with verity):

```yaml
# Recustomize an existing UKI+verity image with updated verity hashes.
storage:
reinitializeVerity: all

os:
bootloader:
resetType: hard-reset

uki:
kernels:
- 6.6.51.1-5.azl3
- 5.10.120-4.custom
mode: create

kernelCommandLine:
extraCommandLine:
- rd.info

packages:
install:
- openssh-server

additionalFiles:
- path: /etc/uki-recustomization.txt
content: |
UKI recustomization with verity refresh

previewFeatures:
- uki
- reinitialize-verity
```

Added in v0.8.
Added in v1.2.0
67 changes: 61 additions & 6 deletions toolkit/tools/imagecustomizerapi/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ func (c *Config) IsValid() (err error) {
return fmt.Errorf("the 'uki' preview feature must be enabled to use 'os.uki'")
}

// Temporary limitation: We currently require 'os.bootloader.reset' to be 'hard-reset' when 'os.uki' is enabled.
// In the future, as we design and develop the bootloader further, this hard-reset limitation may be lifted.
if c.OS.BootLoader.ResetType != ResetBootLoaderTypeHard {
return fmt.Errorf(
"'os.bootloader.reset' must be '%s' when 'os.uki' is enabled", ResetBootLoaderTypeHard,
)
// Validate passthrough mode compatibility
if c.OS.Uki.Mode == UkiModePassthrough {
err = c.validateUkiPassthroughMode()
if err != nil {
return err
}
}
}

Expand Down Expand Up @@ -154,3 +154,58 @@ func (c *Config) IsValid() (err error) {
func (c *Config) CustomizePartitions() bool {
return c.Storage.CustomizePartitions()
}

func (c *Config) validateUkiPassthroughMode() error {
var incompatibleConfigs []string

// Check for bootloader hard-reset (modifies bootloader configuration)
if c.OS != nil && c.OS.BootLoader.ResetType == ResetBootLoaderTypeHard {
incompatibleConfigs = append(incompatibleConfigs,
"os.bootloader.resetType: hard-reset modifies bootloader configuration")
}

// Check for SELinux mode changes (modifies kernel command line)
if c.OS != nil && c.OS.SELinux.Mode != SELinuxModeDefault {
incompatibleConfigs = append(incompatibleConfigs,
"os.selinux.mode: changes SELinux mode which modifies kernel command line embedded in UKI")
}

// Check for kernel command line modifications
if c.OS != nil && len(c.OS.KernelCommandLine.ExtraCommandLine) > 0 {
incompatibleConfigs = append(incompatibleConfigs,
"os.kernelCommandLine.extraCommandLine: modifies kernel command line embedded in UKI")
}

// Check for verity configuration (modifies initramfs and kernel cmdline)
if len(c.Storage.Verity) > 0 {
incompatibleConfigs = append(incompatibleConfigs,
"storage.verity: adds verity devices which modifies initramfs and kernel cmdline")
}

// Check for verity reinitialization (modifies initramfs and kernel cmdline)
if c.Storage.ReinitializeVerity == ReinitializeVerityTypeAll {
incompatibleConfigs = append(incompatibleConfigs,
"storage.reinitializeVerity: reinitializes verity which modifies initramfs and kernel cmdline")
}

// Check for overlay configurations (might trigger initramfs regeneration)
if c.OS != nil && c.OS.Overlays != nil && len(*c.OS.Overlays) > 0 {
incompatibleConfigs = append(incompatibleConfigs,
"os.overlays: overlay configuration might trigger initramfs regeneration")
}

if len(incompatibleConfigs) > 0 {
errorMsg := "UKI passthrough mode is incompatible with the following configurations:\n"
for _, cfg := range incompatibleConfigs {
errorMsg += fmt.Sprintf(" - %s\n", cfg)
}
errorMsg += "\nPassthrough mode preserves existing UKIs without modification.\n"
errorMsg += "To make these changes, use mode: create to regenerate UKIs, or remove the incompatible configurations."
return fmt.Errorf("%s", errorMsg)
}

// Note: Kernel package modifications are validated at runtime by checking /boot
// for new kernel binaries after package operations. This is more reliable than
// checking package names statically.
return nil
}
28 changes: 2 additions & 26 deletions toolkit/tools/imagecustomizerapi/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,7 @@ func TestConfigIsValidWithPreviewFeaturesAndUki(t *testing.T) {
ResetType: "hard-reset",
},
Uki: &Uki{
Kernels: UkiKernels{
Auto: false,
Kernels: []string{"6.6.51.1-5.azl3"},
},
Mode: UkiModeCreate,
},
},
PreviewFeatures: []PreviewFeature{"uki"},
Expand All @@ -183,10 +180,7 @@ func TestConfigIsValidWithMissingUkiPreviewFeature(t *testing.T) {
ResetType: "hard-reset",
},
Uki: &Uki{
Kernels: UkiKernels{
Auto: false,
Kernels: []string{"6.6.51.1-5.azl3"},
},
Mode: UkiModeCreate,
},
},
PreviewFeatures: []PreviewFeature{},
Expand All @@ -197,24 +191,6 @@ func TestConfigIsValidWithMissingUkiPreviewFeature(t *testing.T) {
assert.ErrorContains(t, err, "the 'uki' preview feature must be enabled to use 'os.uki'")
}

func TestConfigIsValidWithUkiAndMissingHardReset(t *testing.T) {
config := &Config{
OS: &OS{
Uki: &Uki{
Kernels: UkiKernels{
Auto: true,
Kernels: nil,
},
},
},
PreviewFeatures: []PreviewFeature{"uki"},
}

err := config.IsValid()
assert.Error(t, err)
assert.ErrorContains(t, err, "'os.bootloader.reset' must be 'hard-reset' when 'os.uki' is enabled")
}

func TestConfigIsValidWithInvalidBootType(t *testing.T) {
config := &Config{
Storage: Storage{
Expand Down
30 changes: 9 additions & 21 deletions toolkit/tools/imagecustomizerapi/os_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,66 +235,54 @@ func TestOSValidWithUki(t *testing.T) {
ResetType: ResetBootLoaderTypeHard,
},
Uki: &Uki{
Kernels: UkiKernels{
Auto: false,
Kernels: []string{"6.6.51.1-5.azl3"},
},
Mode: UkiModeCreate,
},
}

err := os.IsValid()
assert.NoError(t, err)
}

func TestOSValidUkiAutoMode(t *testing.T) {
func TestOSValidUkiPassthroughMode(t *testing.T) {
os := OS{
BootLoader: BootLoader{
ResetType: ResetBootLoaderTypeHard,
},
Uki: &Uki{
Kernels: UkiKernels{
Auto: true,
},
Mode: UkiModePassthrough,
},
}

err := os.IsValid()
assert.NoError(t, err)
}

func TestOSInvalidUkiInvalidKernels(t *testing.T) {
func TestOSInvalidUkiInvalidMode(t *testing.T) {
os := OS{
BootLoader: BootLoader{
ResetType: ResetBootLoaderTypeHard,
},
Uki: &Uki{
Kernels: UkiKernels{
Auto: false,
Kernels: []string{"invalid-kernel-version"},
},
Mode: UkiMode("invalid-mode"),
},
}

err := os.IsValid()
assert.Error(t, err)
assert.ErrorContains(t, err, "invalid uki")
assert.ErrorContains(t, err, "invalid kernel version at index 0:")
assert.ErrorContains(t, err, "invalid kernel version format (invalid-kernel-version)")
assert.ErrorContains(t, err, "invalid uki mode value (invalid-mode)")
}

func TestOSInvalidUkiEmptyKernels(t *testing.T) {
func TestOSValidUkiUnspecifiedMode(t *testing.T) {
os := OS{
BootLoader: BootLoader{
ResetType: ResetBootLoaderTypeHard,
},
Uki: &Uki{
Kernels: UkiKernels{
Auto: false,
Kernels: []string{},
},
Mode: UkiModeUnspecified,
},
}

err := os.IsValid()
assert.ErrorContains(t, err, "must specify either 'auto' or a non-empty list of kernel names")
assert.NoError(t, err)
}
20 changes: 2 additions & 18 deletions toolkit/tools/imagecustomizerapi/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -662,29 +662,13 @@
},
"Uki": {
"properties": {
"kernels": {
"$ref": "#/$defs/UkiKernels"
"mode": {
"type": "string"
}
},
"additionalProperties": false,
"type": "object"
},
"UkiKernels": {
"oneOf": [
{
"type": "string",
"enum": [
"auto"
]
},
{
"items": {
"type": "string"
},
"type": "array"
}
]
},
"User": {
"properties": {
"name": {
Expand Down
6 changes: 3 additions & 3 deletions toolkit/tools/imagecustomizerapi/uki.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ import (
)

type Uki struct {
Kernels UkiKernels `yaml:"kernels" json:"kernels"`
Mode UkiMode `yaml:"mode" json:"mode"`
}

func (u *Uki) IsValid() error {
err := u.Kernels.IsValid()
err := u.Mode.IsValid()
if err != nil {
return fmt.Errorf("invalid uki kernels:\n%w", err)
return fmt.Errorf("invalid uki mode:\n%w", err)
}

return nil
Expand Down
Loading
Loading