Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
83 changes: 77 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,74 @@ 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 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 kernel package changes (would require kernel/initramfs updates)
if c.OS != nil && c.OS.Packages.Install != nil {
for _, pkg := range c.OS.Packages.Install {
if len(pkg) >= 6 && pkg[:6] == "kernel" {
incompatibleConfigs = append(incompatibleConfigs,
fmt.Sprintf("os.packages.install: '%s' would modify kernel files", pkg))
break
}
}
}

if c.OS != nil && c.OS.Packages.Remove != nil {
for _, pkg := range c.OS.Packages.Remove {
if len(pkg) >= 6 && pkg[:6] == "kernel" {
incompatibleConfigs = append(incompatibleConfigs,
fmt.Sprintf("os.packages.remove: '%s' would modify kernel files", pkg))
break
}
}
}

if c.OS != nil && c.OS.Packages.Update != nil {
for _, pkg := range c.OS.Packages.Update {
if len(pkg) >= 6 && pkg[:6] == "kernel" {
incompatibleConfigs = append(incompatibleConfigs,
fmt.Sprintf("os.packages.update: '%s' would modify kernel files", pkg))
break
}
}
}

// 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)
}

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