From 28914f6a39c5b052bbc7a0d3e21cb6b4d804addc Mon Sep 17 00:00:00 2001 From: Chris Gunn Date: Tue, 16 Jun 2026 13:27:28 -0700 Subject: [PATCH 1/4] Report package snapshot time API as unsupported for Ubuntu. When customizing an Ubuntu distro, report a validation error if the user tries to use the package snapshot time API since this functionality isn't supported. Also, refactor the unsupported API validation in the distro handlers so that they are more consistent. This includes ensuring that all long- term unsupported APIs have proper error objects. --- .../imagecustomizerlib/configvalidation.go | 1 - .../imagecustomizerlib/convertimage_test.go | 4 +- .../pkg/imagecustomizerlib/distrohandler.go | 4 + .../imagecustomizerlib/distrohandler_acl.go | 10 ++ .../distrohandler_azurelinux4.go | 16 +++- .../distrohandler_fedora.go | 12 ++- .../imagecustomizerlib/distrohandler_test.go | 92 +++++++++++++++++++ .../distrohandler_ubuntu.go | 44 +++++---- .../pkg/imagecustomizerlib/imagecustomizer.go | 2 - .../liveosisobuilder_test.go | 2 +- 10 files changed, 161 insertions(+), 26 deletions(-) diff --git a/toolkit/tools/pkg/imagecustomizerlib/configvalidation.go b/toolkit/tools/pkg/imagecustomizerlib/configvalidation.go index eac2c84970..15b85b35a8 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/configvalidation.go +++ b/toolkit/tools/pkg/imagecustomizerlib/configvalidation.go @@ -48,7 +48,6 @@ var ( ErrAdditionalDirsSourceIsFile = NewImageCustomizerError("Validation:AdditionalDirsSourceIsFile", "additionalDirs source exists but is a file") ErrAdditionalDirsSourceNotFound = NewImageCustomizerError("Validation:AdditionalDirsSourceNotFound", "additionalDirs source does not exist") ErrInvalidPackageSnapshotTime = NewImageCustomizerError("Validation:InvalidPackageSnapshotTime", "invalid command-line option '--package-snapshot-time'") - ErrUnsupportedFedoraFeature = NewImageCustomizerError("Validation:UnsupportedFedoraFeature", "unsupported feature for Fedora images") ErrInvalidOutputSelinuxPolicyPathArg = NewImageCustomizerError("Validation:InvalidOutputSelinuxPolicyPathArg", "invalid command-line option '--output-selinux-policy-path'") ErrOutputSelinuxPolicyPathIsFileArg = NewImageCustomizerError("Validation:OutputSelinuxPolicyPathIsFileArg", "path exists but is a file") ErrInvalidOutputSelinuxPolicyPathConfig = NewImageCustomizerError("Validation:InvalidOutputSelinuxPolicyPathConfig", "invalid config file property 'output.selinuxPolicyPath'") diff --git a/toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go b/toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go index c832a3091f..8f79782b95 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go +++ b/toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go @@ -123,7 +123,7 @@ func testConvertImageRawToCosi(t *testing.T, baseImageInfo testBaseImageInfo) { if baseImageInfo.Distro == baseImageDistroUbuntu { // This check should be removed once bootloader hard-reset support is added for Ubuntu. // It will fail once this support is added. - assert.ErrorContains(t, err, "bootloader hard-reset is not supported for Ubuntu images") + assert.ErrorContains(t, err, "bootloader hard-reset API is not yet supported for Ubuntu images") return } if !assert.NoError(t, err) { @@ -197,7 +197,7 @@ func testConvertImageRawToCosiWithCompression(t *testing.T, baseImageInfo testBa if baseImageInfo.Distro == baseImageDistroUbuntu { // This check should be removed once bootloader hard-reset support is added for Ubuntu. // It will fail once this support is added. - assert.ErrorContains(t, err, "bootloader hard-reset is not supported for Ubuntu images") + assert.ErrorContains(t, err, "bootloader hard-reset API is not yet supported for Ubuntu images") return } if !assert.NoError(t, err) { diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler.go index 85be4cb021..ab304bcd07 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler.go @@ -27,6 +27,10 @@ const ( var ( ErrUnsupportedDistroVersion = NewImageCustomizerError("Validation:UnsupportedDistroVersion", "base image has unsupported distro version") ErrUnsupportedDistroVersionSuffix = fmt.Sprintf("preview feature '%s' may be specified to use unsupported versions", imagecustomizerapi.PreviewFeatureUnsupportedDistroVersion) + + ErrUnsupportedDistroApi = NewImageCustomizerError("Validation:UnsupportedDistroApi", "unsupported API for distro") + ErrUnsupportedPackageSnapshotTime = NewImageCustomizerError("Validation:UnsupportedPackageSnapshotTime", "package snapshot time API is not supported") + ErrUnsupportedRpmSources = NewImageCustomizerError("Validation:UnsupportedRpmSources", "RPM sources API is not supported") ) // DistroHandler represents the interface for distribution-specific configuration diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_acl.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_acl.go index dea83e0834..818f1f9d3c 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_acl.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_acl.go @@ -58,6 +58,16 @@ func (d *aclDistroHandler) ValidateConfig(rc *ResolvedConfig) error { } } + err := d.checkForUnsupportedApis(rc) + if err != nil { + return fmt.Errorf("%w (distro='%s', versionid='%s'):\n%w", ErrUnsupportedDistroApi, d.targetOs.Distro, + d.targetOs.VersionId, err) + } + + return nil +} + +func (d *aclDistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error { if rc.Storage.CustomizePartitions() { return fmt.Errorf("storage repartitioning is not yet supported for ACL") } diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux4.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux4.go index f6e371acd8..9c0fe32eb3 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux4.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux4.go @@ -60,10 +60,24 @@ func (d *azureLinux4DistroHandler) ValidateConfig(rc *ResolvedConfig) error { } } + err := d.checkForUnsupportedApis(rc) + if err != nil { + return fmt.Errorf("%w (distro='%s', versionid='%s'):\n%w", ErrUnsupportedDistroApi, d.targetOs.Distro, + d.targetOs.VersionId, err) + } + + return nil +} + +func (d *azureLinux4DistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error { + if rc.HasPackageSnapshotTime() { + return ErrUnsupportedPackageSnapshotTime + } + switch rc.OutputImageFormat { case imagecustomizerapi.ImageFormatTypeIso, imagecustomizerapi.ImageFormatTypePxeDir, imagecustomizerapi.ImageFormatTypePxeTar: - return fmt.Errorf("ISO and PXE output formats are not supported for Azure Linux 4.0") + return fmt.Errorf("ISO and PXE output formats are not supported yet for Azure Linux 4.0 images") } return nil diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go index eac2bde3ea..e7819f463f 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go @@ -66,8 +66,18 @@ func (d *fedoraDistroHandler) ValidateConfig(rc *ResolvedConfig) error { } } + err := d.checkForUnsupportedApis(rc) + if err != nil { + return fmt.Errorf("%w (distro='%s', versionid='%s'):\n%w", ErrUnsupportedDistroApi, d.targetOs.Distro, + d.targetOs.VersionId, err) + } + + return nil +} + +func (d *fedoraDistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error { if rc.HasPackageSnapshotTime() { - return fmt.Errorf("Package snapshotting API not supported for Fedora:\n%w", ErrUnsupportedFedoraFeature) + return ErrUnsupportedPackageSnapshotTime } return nil diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go index 6910d0c588..10fb396ee2 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go @@ -6,6 +6,7 @@ package imagecustomizerlib import ( "os" "path/filepath" + "slices" "testing" "github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagecustomizerapi" @@ -143,3 +144,94 @@ func TestAclValidateConfigPackageOpsRequireToolsDir(t *testing.T) { err = handler.ValidateConfig(rc) assert.NoError(t, err) } + +func TestCustomizeImageUnsupportedPackageSnapshotTime(t *testing.T) { + for _, baseImageInfo := range slices.Concat([]testBaseImageInfo{testBaseImageAzl4CoreEfi}, baseImageUbuntuAll) { + t.Run(baseImageInfo.Name, func(t *testing.T) { + testCustomizeImageUnsupportedPackageSnapshotTimeHelper(t, baseImageInfo) + }) + } +} + +func testCustomizeImageUnsupportedPackageSnapshotTimeHelper(t *testing.T, baseImageInfo testBaseImageInfo) { + baseImage := checkSkipForCustomizeImage(t, baseImageInfo) + + testTmpDir := filepath.Join(tmpDir, "TestCustomizeImageUnsupportedPackageSnapshotTime_"+baseImageInfo.Name) + defer os.RemoveAll(testTmpDir) + + buildDir := filepath.Join(testTmpDir, "build") + + options := ImageCustomizerOptions{ + BuildDir: buildDir, + InputImageFile: baseImage, + OutputImageFile: "./out/image.vhdx", + OutputImageFormat: "vhdx", + UseBaseImageRpmRepos: true, + PreviewFeatures: baseImageInfo.PreviewFeatures, + } + + config := &imagecustomizerapi.Config{ + PreviewFeatures: []imagecustomizerapi.PreviewFeature{imagecustomizerapi.PreviewFeaturePackageSnapshotTime}, + OS: &imagecustomizerapi.OS{}, + } + + options.PackageSnapshotTime = "2025-01-01" + + err := CustomizeImage(t.Context(), testTmpDir, config, options) + assert.ErrorIs(t, err, ErrUnsupportedPackageSnapshotTime) + assert.ErrorIs(t, err, ErrUnsupportedDistroApi) + + options.PackageSnapshotTime = "" + config.OS.Packages.SnapshotTime = "2025-01-01" + + err = CustomizeImage(t.Context(), testTmpDir, config, options) + assert.ErrorIs(t, err, ErrUnsupportedPackageSnapshotTime) + assert.ErrorIs(t, err, ErrUnsupportedDistroApi) +} + +func TestCustomizeImageUnsupportedRpmSources(t *testing.T) { + for _, baseImageInfo := range slices.Concat(baseImageUbuntuAll) { + t.Run(baseImageInfo.Name, func(t *testing.T) { + testCustomizeImageUnsupportedRpmSourcesHelper(t, baseImageInfo) + }) + } +} + +func testCustomizeImageUnsupportedRpmSourcesHelper(t *testing.T, baseImageInfo testBaseImageInfo) { + baseImage := checkSkipForCustomizeImage(t, baseImageInfo) + + testTmpDir := filepath.Join(tmpDir, "TestCustomizeImageUnsupportedRpmSources_"+baseImageInfo.Name) + defer os.RemoveAll(testTmpDir) + + buildDir := filepath.Join(testTmpDir, "build") + + err := os.MkdirAll(testTmpDir, os.ModePerm) + if !assert.NoError(t, err) { + return + } + + repoFile := filepath.Join(testTmpDir, "a.repo") + err = os.WriteFile(repoFile, []byte{}, os.ModePerm) + if !assert.NoError(t, err) { + return + } + + options := ImageCustomizerOptions{ + BuildDir: buildDir, + InputImageFile: baseImage, + OutputImageFile: "./out/image.vhdx", + OutputImageFormat: "vhdx", + UseBaseImageRpmRepos: true, + PreviewFeatures: baseImageInfo.PreviewFeatures, + } + + config := &imagecustomizerapi.Config{ + OS: &imagecustomizerapi.OS{}, + } + + options.RpmsSources = []string{repoFile} + + err = CustomizeImage(t.Context(), testTmpDir, config, options) + assert.ErrorIs(t, err, ErrUnsupportedRpmSources) + assert.ErrorIs(t, err, ErrUnsupportedDistroApi) +} diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go index dbc9610595..0fd02411f3 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go @@ -59,37 +59,45 @@ func (d *ubuntuDistroHandler) ValidateConfig(rc *ResolvedConfig) error { } } - // Check if Ubuntu is being used with bootloader hard-reset. - // Ubuntu bootloader config logic is not yet fully implemented. - if rc.BootLoader.ResetType == imagecustomizerapi.ResetBootLoaderTypeHard { - return ErrUbuntuBootLoaderHardReset + err := d.checkForUnsupportedApis(rc) + if err != nil { + return fmt.Errorf("%w (distro='%s', versionid='%s'):\n%w", ErrUnsupportedDistroApi, d.targetOs.Distro, + d.targetOs.VersionId, err) } return nil } -// ManagePackages handles the complete package management workflow for Ubuntu -func (d *ubuntuDistroHandler) ManagePackages(ctx context.Context, buildDir string, baseConfigPath string, - config *imagecustomizerapi.OS, imageChroot *safechroot.Chroot, toolsChroot *safechroot.Chroot, - rpmsSources []string, useBaseImageRpmRepos bool, snapshotTime imagecustomizerapi.PackageSnapshotTime, -) error { - if len(rpmsSources) > 0 { - return fmt.Errorf("RPM sources are not supported for Ubuntu images:\n%w", ErrUnsupportedUbuntuFeature) +func (d *ubuntuDistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error { + // Check if Ubuntu is being used with bootloader hard-reset. + // Ubuntu bootloader config logic is not yet fully implemented. + if rc.BootLoader.ResetType == imagecustomizerapi.ResetBootLoaderTypeHard { + return fmt.Errorf("bootloader hard-reset API is not yet supported for Ubuntu images") + } + + if len(rc.Options.RpmsSources) > 0 { + return ErrUnsupportedRpmSources } // UseBaseImageRpmRepos defaults to true and is only false when the user explicitly // passes --disable-base-image-rpm-repos. Ubuntu does not use RPM repos, so disabling // them is not meaningful and likely indicates a configuration mistake. - if !useBaseImageRpmRepos { - return fmt.Errorf("Disabling base image RPM repositories is not supported for Ubuntu images:\n%w", - ErrUnsupportedUbuntuFeature) + if !rc.Options.UseBaseImageRpmRepos { + return fmt.Errorf("disabling base image package repositories is not supported yet for Ubuntu images") } - if config.Packages.SnapshotTime != "" { - return fmt.Errorf("package snapshotTime is not yet supported for Ubuntu images:\n%w", - ErrUnsupportedUbuntuFeature) + if rc.HasPackageSnapshotTime() { + return ErrUnsupportedPackageSnapshotTime } + return nil +} + +// ManagePackages handles the complete package management workflow for Ubuntu +func (d *ubuntuDistroHandler) ManagePackages(ctx context.Context, buildDir string, baseConfigPath string, + config *imagecustomizerapi.OS, imageChroot *safechroot.Chroot, toolsChroot *safechroot.Chroot, + rpmsSources []string, useBaseImageRpmRepos bool, snapshotTime imagecustomizerapi.PackageSnapshotTime, +) error { return managePackagesDeb(ctx, config, imageChroot) } @@ -194,7 +202,7 @@ func (d *ubuntuDistroHandler) ConfigureDiskBootLoader(imageConnection *imageconn selinuxConfig imagecustomizerapi.SELinux, kernelCommandLine imagecustomizerapi.KernelCommandLine, currentSELinuxMode imagecustomizerapi.SELinuxMode, newImage bool, ) error { - return ErrUbuntuBootLoaderHardReset + return fmt.Errorf("bootloader hard-reset API is not yet supported for Ubuntu images") } func (d *ubuntuDistroHandler) ReadGrubConfigLinuxArgs(bootDir string) (map[string][]grubConfigLinuxArg, error) { diff --git a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go index b2a5ab2a54..6552ddf023 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go +++ b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go @@ -42,8 +42,6 @@ var ( ErrFedoraPreviewFeatureRequired = NewImageCustomizerError("Validation:FedoraPreviewFeatureRequired", fmt.Sprintf("preview feature '%s' required to customize Fedora base image", imagecustomizerapi.PreviewFeatureFedora)) ErrUbuntuPreviewFeatureRequired = NewImageCustomizerError("Validation:UbuntuPreviewFeatureRequired", fmt.Sprintf("preview feature '%s' required to customize Ubuntu base image", imagecustomizerapi.PreviewFeatureUbuntu)) ErrAzureContainerLinuxPreviewFeatureRequired = NewImageCustomizerError("Validation:AzureContainerLinuxPreviewFeatureRequired", fmt.Sprintf("preview feature '%s' required to customize Azure Container Linux base image", imagecustomizerapi.PreviewFeatureAzureContainerLinux)) - ErrUbuntuBootLoaderHardReset = NewImageCustomizerError("Validation:UbuntuBootLoaderHardReset", "bootloader hard-reset is not supported for Ubuntu images") - ErrUnsupportedUbuntuFeature = NewImageCustomizerError("Validation:UnsupportedUbuntuFeature", "unsupported feature for Ubuntu images") ErrInputImageOciPreviewRequired = NewImageCustomizerError("Validation:InputImageOciPreviewRequired", fmt.Sprintf("preview feature '%s' required to specify OCI input image", imagecustomizerapi.PreviewFeatureInputImageOci)) ErrConvertUnsupportedInputFormat = NewImageCustomizerError("Validation:ConvertUnsupportedInputFormat", "input image format is not supported") ErrConvertBuildDirRequired = NewImageCustomizerError("Validation:ConvertBuildDirRequired", "build directory is required for cosi and baremetal-image output formats") diff --git a/toolkit/tools/pkg/imagecustomizerlib/liveosisobuilder_test.go b/toolkit/tools/pkg/imagecustomizerlib/liveosisobuilder_test.go index ad67a7b330..b4d827e745 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/liveosisobuilder_test.go +++ b/toolkit/tools/pkg/imagecustomizerlib/liveosisobuilder_test.go @@ -29,7 +29,7 @@ import ( // Azure Linux distro handler when ISO or PXE output is requested with // an Azure Linux 4.0 base image. ISO and PXE outputs are not supported // on Azure Linux 4.0; LiveOS tests expect this error in that case. -const azl4IsoPxeUnsupportedError = "ISO and PXE output formats are not supported for Azure Linux 4.0" +const azl4IsoPxeUnsupportedError = "ISO and PXE output formats are not supported yet for Azure Linux 4.0 images" func createConfig(t *testing.T, baseImageVersion string, fileNames, kernelParameter string, initramfsType imagecustomizerapi.InitramfsImageType, bootstrapFileUrl string, enlargeDisk, enableOsConfig, bootstrapPrereqs, twoKernels bool, kdumpBootFiles imagecustomizerapi.KdumpBootFilesType, From ee230275f0085fba0535ed2e3da5dd5208c04375 Mon Sep 17 00:00:00 2001 From: Chris Gunn Date: Thu, 18 Jun 2026 14:31:13 -0700 Subject: [PATCH 2/4] Feedback updates --- .../imagecustomizerlib/distrohandler_test.go | 2 +- .../distrohandler_ubuntu.go | 11 +++- .../distrohandler_ubuntu_test.go | 56 +++++++++++++++++++ 3 files changed, 65 insertions(+), 4 deletions(-) create mode 100644 toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu_test.go diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go index 10fb396ee2..fdaa078c16 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go @@ -190,7 +190,7 @@ func testCustomizeImageUnsupportedPackageSnapshotTimeHelper(t *testing.T, baseIm } func TestCustomizeImageUnsupportedRpmSources(t *testing.T) { - for _, baseImageInfo := range slices.Concat(baseImageUbuntuAll) { + for _, baseImageInfo := range baseImageUbuntuAll { t.Run(baseImageInfo.Name, func(t *testing.T) { testCustomizeImageUnsupportedRpmSourcesHelper(t, baseImageInfo) }) diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go index 0fd02411f3..9c4919f5d5 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go @@ -31,6 +31,11 @@ const ( grubEfiPackageDebianArm64 = "grub-efi-arm64" ) +var ( + ErrUbuntuUnsupportedBootloaderHardReset = NewImageCustomizerError("Validation:UbuntuUnsupportedBootloaderHardReset", "bootloader hard-reset API is not supported yet for Ubuntu images") + ErrUbuntuUnsupportedDisableBaseImageRepos = NewImageCustomizerError("Validation:UbuntuUnsupportedDisableBaseImageRepos", "disabling base image package repositories is not supported yet for Ubuntu images") +) + func newUbuntuDistroHandler(targetOs targetos.TargetOs) *ubuntuDistroHandler { logger.Log.Debugf("Distro handler: Ubuntu (distro='%s', versionid='%s')", targetOs.Distro, targetOs.VersionId) @@ -72,7 +77,7 @@ func (d *ubuntuDistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error // Check if Ubuntu is being used with bootloader hard-reset. // Ubuntu bootloader config logic is not yet fully implemented. if rc.BootLoader.ResetType == imagecustomizerapi.ResetBootLoaderTypeHard { - return fmt.Errorf("bootloader hard-reset API is not yet supported for Ubuntu images") + return ErrUbuntuUnsupportedBootloaderHardReset } if len(rc.Options.RpmsSources) > 0 { @@ -83,7 +88,7 @@ func (d *ubuntuDistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error // passes --disable-base-image-rpm-repos. Ubuntu does not use RPM repos, so disabling // them is not meaningful and likely indicates a configuration mistake. if !rc.Options.UseBaseImageRpmRepos { - return fmt.Errorf("disabling base image package repositories is not supported yet for Ubuntu images") + return ErrUbuntuUnsupportedDisableBaseImageRepos } if rc.HasPackageSnapshotTime() { @@ -202,7 +207,7 @@ func (d *ubuntuDistroHandler) ConfigureDiskBootLoader(imageConnection *imageconn selinuxConfig imagecustomizerapi.SELinux, kernelCommandLine imagecustomizerapi.KernelCommandLine, currentSELinuxMode imagecustomizerapi.SELinuxMode, newImage bool, ) error { - return fmt.Errorf("bootloader hard-reset API is not yet supported for Ubuntu images") + return ErrUbuntuUnsupportedBootloaderHardReset } func (d *ubuntuDistroHandler) ReadGrubConfigLinuxArgs(bootDir string) (map[string][]grubConfigLinuxArg, error) { diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu_test.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu_test.go new file mode 100644 index 0000000000..22f27ff08d --- /dev/null +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu_test.go @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package imagecustomizerlib + +import ( + "os" + "path/filepath" + "testing" + + "github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagecustomizerapi" + "github.com/stretchr/testify/assert" +) + +func TestCustomizeImageUbuntuUnsupportedAPIs(t *testing.T) { + for _, baseImageInfo := range baseImageUbuntuAll { + t.Run(baseImageInfo.Name, func(t *testing.T) { + testCustomizeImageUbuntuUnsupportedAPIsHelper(t, baseImageInfo) + }) + } +} + +func testCustomizeImageUbuntuUnsupportedAPIsHelper(t *testing.T, baseImageInfo testBaseImageInfo) { + baseImage := checkSkipForCustomizeImage(t, baseImageInfo) + + testTmpDir := filepath.Join(tmpDir, "TestCustomizeImageUbuntuUnsupportedAPIs_"+baseImageInfo.Name) + defer os.RemoveAll(testTmpDir) + + buildDir := filepath.Join(testTmpDir, "build") + + options := ImageCustomizerOptions{ + BuildDir: buildDir, + InputImageFile: baseImage, + OutputImageFile: "./out/image.vhdx", + OutputImageFormat: "vhdx", + UseBaseImageRpmRepos: true, + PreviewFeatures: baseImageInfo.PreviewFeatures, + } + + config := &imagecustomizerapi.Config{ + OS: &imagecustomizerapi.OS{}, + } + + config.OS.BootLoader.ResetType = imagecustomizerapi.ResetBootLoaderTypeHard + + err := CustomizeImage(t.Context(), testTmpDir, config, options) + assert.ErrorIs(t, err, ErrUbuntuUnsupportedBootloaderHardReset) + assert.ErrorIs(t, err, ErrUnsupportedDistroApi) + + config.OS.BootLoader.ResetType = imagecustomizerapi.ResetBootLoaderTypeDefault + options.UseBaseImageRpmRepos = false + + err = CustomizeImage(t.Context(), testTmpDir, config, options) + assert.ErrorIs(t, err, ErrUbuntuUnsupportedDisableBaseImageRepos) + assert.ErrorIs(t, err, ErrUnsupportedDistroApi) +} From 2eaf6deba17bbfe73ed638a27c069a7bfd1d98de Mon Sep 17 00:00:00 2001 From: Chris Gunn Date: Thu, 18 Jun 2026 17:35:00 -0700 Subject: [PATCH 3/4] Merge fix --- toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go index 9c4919f5d5..70d72155d0 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go +++ b/toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go @@ -113,8 +113,7 @@ func (d *ubuntuDistroHandler) IsPackageInstalled(imageChroot safechroot.ChrootIn func (d *ubuntuDistroHandler) GetPackageInformation(imageChroot *safechroot.Chroot, packageName string, ) (*PackageVersionInformation, error) { - return nil, fmt.Errorf("Getting package information is not supported yet for Ubuntu images:\n%w", - ErrUnsupportedUbuntuFeature) + return nil, fmt.Errorf("getting package information is not supported yet for Ubuntu images") } func (d *ubuntuDistroHandler) GetAllPackagesFromChroot(imageChroot safechroot.ChrootInterface) ([]OsPackage, error) { From 6d5cde1ab06cda15f7e8ee5c15838ad8f75989d2 Mon Sep 17 00:00:00 2001 From: Chris Gunn Date: Fri, 19 Jun 2026 11:24:28 -0700 Subject: [PATCH 4/4] Feedback updates --- toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go b/toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go index 8f79782b95..4e935b6298 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go +++ b/toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go @@ -123,7 +123,7 @@ func testConvertImageRawToCosi(t *testing.T, baseImageInfo testBaseImageInfo) { if baseImageInfo.Distro == baseImageDistroUbuntu { // This check should be removed once bootloader hard-reset support is added for Ubuntu. // It will fail once this support is added. - assert.ErrorContains(t, err, "bootloader hard-reset API is not yet supported for Ubuntu images") + assert.ErrorIs(t, err, ErrUbuntuUnsupportedBootloaderHardReset) return } if !assert.NoError(t, err) { @@ -197,7 +197,7 @@ func testConvertImageRawToCosiWithCompression(t *testing.T, baseImageInfo testBa if baseImageInfo.Distro == baseImageDistroUbuntu { // This check should be removed once bootloader hard-reset support is added for Ubuntu. // It will fail once this support is added. - assert.ErrorContains(t, err, "bootloader hard-reset API is not yet supported for Ubuntu images") + assert.ErrorIs(t, err, ErrUbuntuUnsupportedBootloaderHardReset) return } if !assert.NoError(t, err) {