Skip to content
Merged
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
11 changes: 10 additions & 1 deletion toolkit/tools/internal/osinfo/osinfo.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package osinfo

import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
Expand All @@ -10,7 +12,14 @@ import (
func GetDistroAndVersion(rootDir string) (string, string) {
output, err := os.ReadFile(filepath.Join(rootDir, "etc/os-release"))
if err != nil {
return "Unknown Distro", "Unknown Version"
if !errors.Is(err, fs.ErrNotExist) {
return "Unknown Distro", "Unknown Version"
}
// Fall back to /usr/lib/os-release per the os-release(5) spec.
output, err = os.ReadFile(filepath.Join(rootDir, "usr/lib/os-release"))
if err != nil {
return "Unknown Distro", "Unknown Version"
}
}

lines := strings.Split(string(output), "\n")
Expand Down
53 changes: 39 additions & 14 deletions toolkit/tools/internal/targetos/targetos.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,37 @@
package targetos

import (
"errors"
"fmt"
"io/fs"
"path/filepath"
"strings"

"github.com/microsoft/azure-linux-image-tools/toolkit/tools/internal/envfile"
)

type TargetOs string

const (
TargetOsAzureLinux2 TargetOs = "azl2"
TargetOsAzureLinux3 TargetOs = "azl3"
TargetOsFedora42 TargetOs = "fedora42"
TargetOsUbuntu2204 TargetOs = "ubuntu2204"
TargetOsUbuntu2404 TargetOs = "ubuntu2404"
TargetOsAzureLinux2 TargetOs = "azl2"
TargetOsAzureLinux3 TargetOs = "azl3"
TargetOsAzureContainerLinux3 TargetOs = "acl3"
TargetOsFedora42 TargetOs = "fedora42"
TargetOsUbuntu2204 TargetOs = "ubuntu2204"
TargetOsUbuntu2404 TargetOs = "ubuntu2404"
)

func GetInstalledTargetOs(rootfs string) (TargetOs, error) {
// Try /etc/os-release first, then fall back to /usr/lib/os-release.
fields, err := envfile.ParseEnvFile(filepath.Join(rootfs, "etc/os-release"))
if err != nil {
return "", fmt.Errorf("failed to read /etc/os-release file:\n%w", err)
if !errors.Is(err, fs.ErrNotExist) {
return "", fmt.Errorf("failed to read /etc/os-release:\n%w", err)
}
fields, err = envfile.ParseEnvFile(filepath.Join(rootfs, "usr/lib/os-release"))
Comment thread
liulanze marked this conversation as resolved.
if err != nil {
return "", fmt.Errorf("failed to read os-release (tried /etc/os-release and /usr/lib/os-release):\n%w", err)
}
}

distroId := fields["ID"]
Expand All @@ -36,16 +47,30 @@ func GetInstalledTargetOs(rootfs string) (TargetOs, error) {
return TargetOsAzureLinux2, nil

default:
return "", fmt.Errorf("unknown VERSION_ID (%s) for CBL-Mariner in /etc/os-release", versionId)
return "", fmt.Errorf("unknown VERSION_ID (%s) for CBL-Mariner in os-release", versionId)
Comment thread
liulanze marked this conversation as resolved.
Comment thread
liulanze marked this conversation as resolved.
}

case "azurelinux":
switch versionId {
case "3.0":
return TargetOsAzureLinux3, nil
variantId := fields["VARIANT_ID"]

switch variantId {
case "azurecontainerlinux":
// ACL currently sets VERSION_ID to the full version string (e.g.
// "3.0.20260421") Accept any version that starts with "3."
if !strings.HasPrefix(versionId, "3.") {
return "", fmt.Errorf("unknown VERSION_ID (%s) for Azure Container Linux in os-release", versionId)
}
return TargetOsAzureContainerLinux3, nil

default:
return "", fmt.Errorf("unknown VERSION_ID (%s) for Azure Linux in /etc/os-release", versionId)
// Standard Azure Linux (or unknown variant — treat as standard).
switch versionId {
case "3.0":
return TargetOsAzureLinux3, nil

default:
return "", fmt.Errorf("unknown VERSION_ID (%s) for Azure Linux in os-release", versionId)
}
}

case "fedora":
Expand All @@ -54,7 +79,7 @@ func GetInstalledTargetOs(rootfs string) (TargetOs, error) {
return TargetOsFedora42, nil

default:
return "", fmt.Errorf("unknown VERSION_ID (%s) for Fedora in /etc/os-release", versionId)
return "", fmt.Errorf("unknown VERSION_ID (%s) for Fedora in os-release", versionId)
}

case "ubuntu":
Expand All @@ -66,10 +91,10 @@ func GetInstalledTargetOs(rootfs string) (TargetOs, error) {
return TargetOsUbuntu2404, nil

default:
return "", fmt.Errorf("unknown VERSION_ID (%s) for Ubuntu in /etc/os-release", versionId)
return "", fmt.Errorf("unknown VERSION_ID (%s) for Ubuntu in os-release", versionId)
}

default:
return "", fmt.Errorf("unknown ID (%s) in /etc/os-release", distroId)
return "", fmt.Errorf("unknown ID (%s) in os-release", distroId)
}
}
14 changes: 7 additions & 7 deletions toolkit/tools/pkg/imagecustomizerlib/bootcustomizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func NewBootCustomizer(imageChroot safechroot.ChrootInterface, uki *imagecustomi
}

// Determine boot configuration type
bootConfigType, err := determineBootConfigType(grubCfgContent, imageChroot)
bootConfigType, err := determineBootConfigType(grubCfgContent, imageChroot, distroHandler)
if err != nil {
return nil, err
}
Expand All @@ -93,16 +93,16 @@ func NewBootCustomizer(imageChroot safechroot.ChrootInterface, uki *imagecustomi
return b, nil
}

func determineBootConfigType(grubCfgContent string, imageChroot safechroot.ChrootInterface) (bootConfigType, error) {
// If grub.cfg doesn't exist, check for UKI
func determineBootConfigType(grubCfgContent string, imageChroot safechroot.ChrootInterface, distroHandler DistroHandler) (bootConfigType, error) {
// If grub.cfg doesn't exist, check for UKI using the distro-specific ESP path.
if grubCfgContent == "" {
hasUkis, err := baseImageHasUkis(imageChroot.(*safechroot.Chroot))
if err == nil && hasUkis {
espDir := filepath.Join(imageChroot.RootDir(), distroHandler.GetEspDir())
ukiFiles, err := getUkiFiles(espDir)
if err == nil && len(ukiFiles) > 0 {
// UKI images without grub.cfg are in passthrough mode (grub.cfg not regenerated)
// For UKI create mode, grub.cfg is regenerated during kernel extraction, so it would exist
return bootConfigTypeUki, nil
}
// No grub.cfg and no UKIs - this is an error
return "", ErrBootNoConfigFound
}

Expand Down Expand Up @@ -183,7 +183,7 @@ func (b *BootCustomizer) getSELinuxModeFromCmdline(buildDir string, imageChroot
}

case bootConfigTypeUki:
espDir := filepath.Join(imageChroot.RootDir(), EspDir)
espDir := filepath.Join(imageChroot.RootDir(), b.distroHandler.GetEspDir())

kernelToArgs, err := extractKernelCmdlineFromUkiEfis(espDir, buildDir)
if err != nil {
Expand Down
8 changes: 4 additions & 4 deletions toolkit/tools/pkg/imagecustomizerlib/cosicommon.go
Original file line number Diff line number Diff line change
Expand Up @@ -395,7 +395,7 @@ func extractCosiBootMetadata(buildDirAbs string, imageConnection *imageconnectio
}

// If no config entries, try extracting standalone UKI .efi entries
ukiEntries, err := extractUkiEntriesIfPresent(chrootDir, buildDirAbs)
ukiEntries, err := extractUkiEntriesIfPresent(chrootDir, buildDirAbs, distroHandler)
if err != nil {
return nil, fmt.Errorf("error extracting UKI standalone entries:\n%w", err)
}
Expand All @@ -419,8 +419,8 @@ func extractCosiBootMetadata(buildDirAbs string, imageConnection *imageconnectio
}
}

func extractUkiEntriesIfPresent(chrootDir, buildDir string) ([]SystemDBootEntry, error) {
espDir := filepath.Join(chrootDir, EspDir)
func extractUkiEntriesIfPresent(chrootDir, buildDir string, distroHandler DistroHandler) ([]SystemDBootEntry, error) {
espDir := filepath.Join(chrootDir, distroHandler.GetEspDir())

cmdlines, err := extractKernelCmdlineFromUkiEfis(espDir, buildDir)
if err != nil {
Expand All @@ -429,7 +429,7 @@ func extractUkiEntriesIfPresent(chrootDir, buildDir string) ([]SystemDBootEntry,

var entries []SystemDBootEntry
for kernelName, cmdline := range cmdlines {
efiPath := filepath.Join("/boot/efi/EFI/Linux", fmt.Sprintf("%s.efi", kernelName))
efiPath := filepath.Join("/", distroHandler.GetEspDir(), "EFI/Linux", fmt.Sprintf("%s.efi", kernelName))
kernelVersion, err := getKernelVersion(kernelName)
if err != nil {
return nil, fmt.Errorf("invalid kernel name in UKI file (%s):\n%w", kernelName, err)
Expand Down
6 changes: 3 additions & 3 deletions toolkit/tools/pkg/imagecustomizerlib/customizeos.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,14 +43,14 @@ func doOsCustomizations(ctx context.Context, rc *ResolvedConfig, imageConnection
// mode, we skip extraction to preserve existing UKIs.
if rc.Uki != nil && rc.Uki.Mode == imagecustomizerapi.UkiModeCreate {
// Check if base image has UKIs to determine if extraction is needed
hasUkis, err := baseImageHasUkis(imageChroot)
hasUkis, err := baseImageHasUkis(imageChroot, distroHandler)
if err != nil {
return err
}

if hasUkis {
// Base image has UKIs and mode is create - extract for re-customization
err = extractKernelAndInitramfsFromUkis(ctx, imageChroot, rc.BuildDirAbs)
err = extractKernelAndInitramfsFromUkis(ctx, imageChroot, rc.BuildDirAbs, distroHandler)
if err != nil {
return err
}
Expand All @@ -65,7 +65,7 @@ func doOsCustomizations(ctx context.Context, rc *ResolvedConfig, imageConnection
return fmt.Errorf("failed to create UKI build directory:\n%w", err)
}

err = extractAndSaveUkiCmdline(rc.BuildDirAbs, imageChroot)
err = extractAndSaveUkiCmdline(rc.BuildDirAbs, imageChroot, distroHandler)
if err != nil {
return fmt.Errorf("failed to extract UKI cmdline for modify mode:\n%w", err)
}
Expand Down
44 changes: 24 additions & 20 deletions toolkit/tools/pkg/imagecustomizerlib/customizeuki.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ type UkiKernelInfo struct {
Initramfs string `json:"initramfs,omitempty"` // Optional: empty in modify mode
}

func baseImageHasUkis(imageChroot *safechroot.Chroot) (bool, error) {
espDir := filepath.Join(imageChroot.RootDir(), EspDir)
func baseImageHasUkis(imageChroot *safechroot.Chroot, distroHandler DistroHandler) (bool, error) {
espDir := filepath.Join(imageChroot.RootDir(), distroHandler.GetEspDir())
ukiFiles, err := getUkiFiles(espDir)
if err != nil {
return false, fmt.Errorf("failed to check for UKI files:\n%w", err)
Expand Down Expand Up @@ -112,8 +112,8 @@ func baseImageHasUkiAddons(espPath string) (bool, error) {
// - mode: create: Extract and regenerate UKIs
// - mode: passthrough: Preserve existing UKIs without modification
// - mode: modify: Check for addon, modify addon only (preserve main UKI)
func validateUkiMode(imageConnection *imageconnection.ImageConnection, uki *imagecustomizerapi.Uki) error {
hasUkis, err := baseImageHasUkis(imageConnection.Chroot())
func validateUkiMode(imageConnection *imageconnection.ImageConnection, uki *imagecustomizerapi.Uki, distroHandler DistroHandler) error {
hasUkis, err := baseImageHasUkis(imageConnection.Chroot(), distroHandler)
if err != nil {
return err
}
Expand Down Expand Up @@ -157,7 +157,7 @@ func validateUkiMode(imageConnection *imageconnection.ImageConnection, uki *imag

// For modify mode, validate that base image has UKI addons
if uki.Mode == imagecustomizerapi.UkiModeModify {
espDir := filepath.Join(imageConnection.Chroot().RootDir(), EspDir)
espDir := filepath.Join(imageConnection.Chroot().RootDir(), distroHandler.GetEspDir())
hasAddons, err := baseImageHasUkiAddons(espDir)
if err != nil {
return fmt.Errorf("failed to check for UKI addons:\n%w", err)
Expand All @@ -173,8 +173,8 @@ func validateUkiMode(imageConnection *imageconnection.ImageConnection, uki *imag
}

// extractAndSaveUkiCmdline extracts the kernel cmdline from existing UKI addons and saves them to uki-kernel-info.json.
func extractAndSaveUkiCmdline(buildDir string, imageChroot *safechroot.Chroot) error {
espDir := filepath.Join(imageChroot.RootDir(), EspDir)
func extractAndSaveUkiCmdline(buildDir string, imageChroot *safechroot.Chroot, distroHandler DistroHandler) error {
espDir := filepath.Join(imageChroot.RootDir(), distroHandler.GetEspDir())
ukiFiles, err := getUkiFiles(espDir)
if err != nil {
return fmt.Errorf("failed to get UKI files:\n%w", err)
Expand Down Expand Up @@ -307,13 +307,13 @@ func prepareUkiHelper(ctx context.Context, buildDir string, uki *imagecustomizer
}

// Extract kernel command line arguments from either grub.cfg or UKI.
espDir := filepath.Join(imageChroot.RootDir(), EspDir)
espDir := filepath.Join(imageChroot.RootDir(), distroHandler.GetEspDir())
kernelToArgs, err := extractKernelToArgs(espDir, bootDir, buildDir)
if err != nil {
return fmt.Errorf("%w:\n%w", ErrUKIKernelCmdlineExtract, err)
}

err = cleanBootDirectory(imageChroot)
err = cleanBootDirectory(imageChroot, distroHandler)
if err != nil {
return fmt.Errorf("%w:\n%w", ErrUKICleanBootDir, err)
}
Expand Down Expand Up @@ -965,13 +965,17 @@ func getKernelNameFromUki(ukiPath string) (string, error) {
fileName := filepath.Base(ukiPath)

matches := ukiNamePattern.FindStringSubmatch(fileName)
if len(matches) != 2 {
return "", fmt.Errorf("invalid UKI file name: (%s)", fileName)
if len(matches) == 2 {
// Standard UKI naming: vmlinuz-<version>.efi → vmlinuz-<version>
return "vmlinuz-" + matches[1], nil
}

// Reconstruct kernel name (vmlinuz-<version>, e.g., vmlinuz-6.6.51.1-5.azl3)
kernelName := "vmlinuz-" + matches[1]
return kernelName, nil
// Non-standard UKI naming (e.g., acl.efi): use filename without .efi extension
if strings.HasSuffix(fileName, ".efi") {
return strings.TrimSuffix(fileName, ".efi"), nil
Comment thread
liulanze marked this conversation as resolved.
}
Comment thread
liulanze marked this conversation as resolved.

Comment thread
liulanze marked this conversation as resolved.
return "", fmt.Errorf("invalid UKI file name: (%s)", fileName)
}

func extractSectionFromUkiWithObjcopy(ukiPath string, sectionName string, outputPath string, buildDir string) error {
Expand All @@ -998,22 +1002,22 @@ func extractSectionFromUkiWithObjcopy(ukiPath string, sectionName string, output
return nil
}

func extractKernelAndInitramfsFromUkis(ctx context.Context, imageChroot *safechroot.Chroot, buildDir string) error {
err := extractKernelAndInitramfsFromUkisHelper(ctx, imageChroot, buildDir)
func extractKernelAndInitramfsFromUkis(ctx context.Context, imageChroot *safechroot.Chroot, buildDir string, distroHandler DistroHandler) error {
err := extractKernelAndInitramfsFromUkisHelper(ctx, imageChroot, buildDir, distroHandler)
if err != nil {
return fmt.Errorf("%w:\n%w", ErrUKIExtractComponents, err)
}

return nil
}

func extractKernelAndInitramfsFromUkisHelper(ctx context.Context, imageChroot *safechroot.Chroot, buildDir string) error {
func extractKernelAndInitramfsFromUkisHelper(ctx context.Context, imageChroot *safechroot.Chroot, buildDir string, distroHandler DistroHandler) error {
logger.Log.Infof("Extracting kernel and initramfs from existing UKIs for re-customization")

_, span := otel.GetTracerProvider().Tracer(OtelTracerName).Start(ctx, "extract_kernel_initramfs_from_ukis")
defer span.End()

espDir := filepath.Join(imageChroot.RootDir(), EspDir)
espDir := filepath.Join(imageChroot.RootDir(), distroHandler.GetEspDir())
ukiFiles, err := getUkiFiles(espDir)
if err != nil {
return err
Expand Down Expand Up @@ -1121,9 +1125,9 @@ func cleanUkiDirectory(ukiOutputDir string) error {
return nil
}

func cleanBootDirectory(imageChroot *safechroot.Chroot) error {
func cleanBootDirectory(imageChroot *safechroot.Chroot, distroHandler DistroHandler) error {
bootPath := filepath.Join(imageChroot.RootDir(), BootDir)
espPath := filepath.Join(imageChroot.RootDir(), EspDir)
espPath := filepath.Join(imageChroot.RootDir(), distroHandler.GetEspDir())

dirEntries, err := os.ReadDir(bootPath)
if err != nil {
Expand Down
42 changes: 42 additions & 0 deletions toolkit/tools/pkg/imagecustomizerlib/customizeuki_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -566,3 +566,45 @@ func getUkiAddonFiles(espPath string) ([]string, error) {

return addonFiles, nil
}

func TestGetKernelNameFromUki(t *testing.T) {
tests := []struct {
name string
ukiPath string
expected string
expectError bool
}{
{
name: "standard vmlinuz naming",
ukiPath: "/boot/efi/EFI/Linux/vmlinuz-6.6.51.1-5.azl3.efi",
expected: "vmlinuz-6.6.51.1-5.azl3",
},
{
name: "non-standard naming (ACL)",
ukiPath: "/boot/EFI/Linux/acl.efi",
expected: "acl",
},
{
name: "non-standard naming with path",
ukiPath: "/some/path/custom-kernel.efi",
expected: "custom-kernel",
},
{
name: "no .efi extension",
ukiPath: "/boot/EFI/Linux/vmlinuz-6.6.51",
expectError: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := getKernelNameFromUki(tt.ukiPath)
if tt.expectError {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, result)
}
})
}
}
Loading
Loading