Skip to content

Commit b3d3740

Browse files
authored
Report package snapshot time API as unsupported for Ubuntu. (#782)
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.
1 parent fd1accc commit b3d3740

11 files changed

Lines changed: 223 additions & 28 deletions

toolkit/tools/pkg/imagecustomizerlib/configvalidation.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@ var (
4848
ErrAdditionalDirsSourceIsFile = NewImageCustomizerError("Validation:AdditionalDirsSourceIsFile", "additionalDirs source exists but is a file")
4949
ErrAdditionalDirsSourceNotFound = NewImageCustomizerError("Validation:AdditionalDirsSourceNotFound", "additionalDirs source does not exist")
5050
ErrInvalidPackageSnapshotTime = NewImageCustomizerError("Validation:InvalidPackageSnapshotTime", "invalid command-line option '--package-snapshot-time'")
51-
ErrUnsupportedFedoraFeature = NewImageCustomizerError("Validation:UnsupportedFedoraFeature", "unsupported feature for Fedora images")
5251
ErrInvalidOutputSelinuxPolicyPathArg = NewImageCustomizerError("Validation:InvalidOutputSelinuxPolicyPathArg", "invalid command-line option '--output-selinux-policy-path'")
5352
ErrOutputSelinuxPolicyPathIsFileArg = NewImageCustomizerError("Validation:OutputSelinuxPolicyPathIsFileArg", "path exists but is a file")
5453
ErrInvalidOutputSelinuxPolicyPathConfig = NewImageCustomizerError("Validation:InvalidOutputSelinuxPolicyPathConfig", "invalid config file property 'output.selinuxPolicyPath'")

toolkit/tools/pkg/imagecustomizerlib/convertimage_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ func testConvertImageRawToCosi(t *testing.T, baseImageInfo testBaseImageInfo) {
123123
if baseImageInfo.Distro == baseImageDistroUbuntu {
124124
// This check should be removed once bootloader hard-reset support is added for Ubuntu.
125125
// It will fail once this support is added.
126-
assert.ErrorContains(t, err, "bootloader hard-reset is not supported for Ubuntu images")
126+
assert.ErrorIs(t, err, ErrUbuntuUnsupportedBootloaderHardReset)
127127
return
128128
}
129129
if !assert.NoError(t, err) {
@@ -197,7 +197,7 @@ func testConvertImageRawToCosiWithCompression(t *testing.T, baseImageInfo testBa
197197
if baseImageInfo.Distro == baseImageDistroUbuntu {
198198
// This check should be removed once bootloader hard-reset support is added for Ubuntu.
199199
// It will fail once this support is added.
200-
assert.ErrorContains(t, err, "bootloader hard-reset is not supported for Ubuntu images")
200+
assert.ErrorIs(t, err, ErrUbuntuUnsupportedBootloaderHardReset)
201201
return
202202
}
203203
if !assert.NoError(t, err) {

toolkit/tools/pkg/imagecustomizerlib/distrohandler.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ const (
2727
var (
2828
ErrUnsupportedDistroVersion = NewImageCustomizerError("Validation:UnsupportedDistroVersion", "base image has unsupported distro version")
2929
ErrUnsupportedDistroVersionSuffix = fmt.Sprintf("preview feature '%s' may be specified to use unsupported versions", imagecustomizerapi.PreviewFeatureUnsupportedDistroVersion)
30+
31+
ErrUnsupportedDistroApi = NewImageCustomizerError("Validation:UnsupportedDistroApi", "unsupported API for distro")
32+
ErrUnsupportedPackageSnapshotTime = NewImageCustomizerError("Validation:UnsupportedPackageSnapshotTime", "package snapshot time API is not supported")
33+
ErrUnsupportedRpmSources = NewImageCustomizerError("Validation:UnsupportedRpmSources", "RPM sources API is not supported")
3034
)
3135

3236
// DistroHandler represents the interface for distribution-specific configuration

toolkit/tools/pkg/imagecustomizerlib/distrohandler_acl.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ func (d *aclDistroHandler) ValidateConfig(rc *ResolvedConfig) error {
5858
}
5959
}
6060

61+
err := d.checkForUnsupportedApis(rc)
62+
if err != nil {
63+
return fmt.Errorf("%w (distro='%s', versionid='%s'):\n%w", ErrUnsupportedDistroApi, d.targetOs.Distro,
64+
d.targetOs.VersionId, err)
65+
}
66+
67+
return nil
68+
}
69+
70+
func (d *aclDistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error {
6171
if rc.Storage.CustomizePartitions() {
6272
return fmt.Errorf("storage repartitioning is not yet supported for ACL")
6373
}

toolkit/tools/pkg/imagecustomizerlib/distrohandler_azurelinux4.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,24 @@ func (d *azureLinux4DistroHandler) ValidateConfig(rc *ResolvedConfig) error {
6060
}
6161
}
6262

63+
err := d.checkForUnsupportedApis(rc)
64+
if err != nil {
65+
return fmt.Errorf("%w (distro='%s', versionid='%s'):\n%w", ErrUnsupportedDistroApi, d.targetOs.Distro,
66+
d.targetOs.VersionId, err)
67+
}
68+
69+
return nil
70+
}
71+
72+
func (d *azureLinux4DistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error {
73+
if rc.HasPackageSnapshotTime() {
74+
return ErrUnsupportedPackageSnapshotTime
75+
}
76+
6377
switch rc.OutputImageFormat {
6478
case imagecustomizerapi.ImageFormatTypeIso, imagecustomizerapi.ImageFormatTypePxeDir,
6579
imagecustomizerapi.ImageFormatTypePxeTar:
66-
return fmt.Errorf("ISO and PXE output formats are not supported for Azure Linux 4.0")
80+
return fmt.Errorf("ISO and PXE output formats are not supported yet for Azure Linux 4.0 images")
6781
}
6882

6983
return nil

toolkit/tools/pkg/imagecustomizerlib/distrohandler_fedora.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,18 @@ func (d *fedoraDistroHandler) ValidateConfig(rc *ResolvedConfig) error {
6666
}
6767
}
6868

69+
err := d.checkForUnsupportedApis(rc)
70+
if err != nil {
71+
return fmt.Errorf("%w (distro='%s', versionid='%s'):\n%w", ErrUnsupportedDistroApi, d.targetOs.Distro,
72+
d.targetOs.VersionId, err)
73+
}
74+
75+
return nil
76+
}
77+
78+
func (d *fedoraDistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error {
6979
if rc.HasPackageSnapshotTime() {
70-
return fmt.Errorf("Package snapshotting API not supported for Fedora:\n%w", ErrUnsupportedFedoraFeature)
80+
return ErrUnsupportedPackageSnapshotTime
7181
}
7282

7383
return nil

toolkit/tools/pkg/imagecustomizerlib/distrohandler_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package imagecustomizerlib
66
import (
77
"os"
88
"path/filepath"
9+
"slices"
910
"testing"
1011

1112
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagecustomizerapi"
@@ -143,3 +144,94 @@ func TestAclValidateConfigPackageOpsRequireToolsDir(t *testing.T) {
143144
err = handler.ValidateConfig(rc)
144145
assert.NoError(t, err)
145146
}
147+
148+
func TestCustomizeImageUnsupportedPackageSnapshotTime(t *testing.T) {
149+
for _, baseImageInfo := range slices.Concat([]testBaseImageInfo{testBaseImageAzl4CoreEfi}, baseImageUbuntuAll) {
150+
t.Run(baseImageInfo.Name, func(t *testing.T) {
151+
testCustomizeImageUnsupportedPackageSnapshotTimeHelper(t, baseImageInfo)
152+
})
153+
}
154+
}
155+
156+
func testCustomizeImageUnsupportedPackageSnapshotTimeHelper(t *testing.T, baseImageInfo testBaseImageInfo) {
157+
baseImage := checkSkipForCustomizeImage(t, baseImageInfo)
158+
159+
testTmpDir := filepath.Join(tmpDir, "TestCustomizeImageUnsupportedPackageSnapshotTime_"+baseImageInfo.Name)
160+
defer os.RemoveAll(testTmpDir)
161+
162+
buildDir := filepath.Join(testTmpDir, "build")
163+
164+
options := ImageCustomizerOptions{
165+
BuildDir: buildDir,
166+
InputImageFile: baseImage,
167+
OutputImageFile: "./out/image.vhdx",
168+
OutputImageFormat: "vhdx",
169+
UseBaseImageRpmRepos: true,
170+
PreviewFeatures: baseImageInfo.PreviewFeatures,
171+
}
172+
173+
config := &imagecustomizerapi.Config{
174+
PreviewFeatures: []imagecustomizerapi.PreviewFeature{imagecustomizerapi.PreviewFeaturePackageSnapshotTime},
175+
OS: &imagecustomizerapi.OS{},
176+
}
177+
178+
options.PackageSnapshotTime = "2025-01-01"
179+
180+
err := CustomizeImage(t.Context(), testTmpDir, config, options)
181+
assert.ErrorIs(t, err, ErrUnsupportedPackageSnapshotTime)
182+
assert.ErrorIs(t, err, ErrUnsupportedDistroApi)
183+
184+
options.PackageSnapshotTime = ""
185+
config.OS.Packages.SnapshotTime = "2025-01-01"
186+
187+
err = CustomizeImage(t.Context(), testTmpDir, config, options)
188+
assert.ErrorIs(t, err, ErrUnsupportedPackageSnapshotTime)
189+
assert.ErrorIs(t, err, ErrUnsupportedDistroApi)
190+
}
191+
192+
func TestCustomizeImageUnsupportedRpmSources(t *testing.T) {
193+
for _, baseImageInfo := range baseImageUbuntuAll {
194+
t.Run(baseImageInfo.Name, func(t *testing.T) {
195+
testCustomizeImageUnsupportedRpmSourcesHelper(t, baseImageInfo)
196+
})
197+
}
198+
}
199+
200+
func testCustomizeImageUnsupportedRpmSourcesHelper(t *testing.T, baseImageInfo testBaseImageInfo) {
201+
baseImage := checkSkipForCustomizeImage(t, baseImageInfo)
202+
203+
testTmpDir := filepath.Join(tmpDir, "TestCustomizeImageUnsupportedRpmSources_"+baseImageInfo.Name)
204+
defer os.RemoveAll(testTmpDir)
205+
206+
buildDir := filepath.Join(testTmpDir, "build")
207+
208+
err := os.MkdirAll(testTmpDir, os.ModePerm)
209+
if !assert.NoError(t, err) {
210+
return
211+
}
212+
213+
repoFile := filepath.Join(testTmpDir, "a.repo")
214+
err = os.WriteFile(repoFile, []byte{}, os.ModePerm)
215+
if !assert.NoError(t, err) {
216+
return
217+
}
218+
219+
options := ImageCustomizerOptions{
220+
BuildDir: buildDir,
221+
InputImageFile: baseImage,
222+
OutputImageFile: "./out/image.vhdx",
223+
OutputImageFormat: "vhdx",
224+
UseBaseImageRpmRepos: true,
225+
PreviewFeatures: baseImageInfo.PreviewFeatures,
226+
}
227+
228+
config := &imagecustomizerapi.Config{
229+
OS: &imagecustomizerapi.OS{},
230+
}
231+
232+
options.RpmsSources = []string{repoFile}
233+
234+
err = CustomizeImage(t.Context(), testTmpDir, config, options)
235+
assert.ErrorIs(t, err, ErrUnsupportedRpmSources)
236+
assert.ErrorIs(t, err, ErrUnsupportedDistroApi)
237+
}

toolkit/tools/pkg/imagecustomizerlib/distrohandler_ubuntu.go

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ const (
3131
grubEfiPackageDebianArm64 = "grub-efi-arm64"
3232
)
3333

34+
var (
35+
ErrUbuntuUnsupportedBootloaderHardReset = NewImageCustomizerError("Validation:UbuntuUnsupportedBootloaderHardReset", "bootloader hard-reset API is not supported yet for Ubuntu images")
36+
ErrUbuntuUnsupportedDisableBaseImageRepos = NewImageCustomizerError("Validation:UbuntuUnsupportedDisableBaseImageRepos", "disabling base image package repositories is not supported yet for Ubuntu images")
37+
)
38+
3439
func newUbuntuDistroHandler(targetOs targetos.TargetOs) *ubuntuDistroHandler {
3540
logger.Log.Debugf("Distro handler: Ubuntu (distro='%s', versionid='%s')", targetOs.Distro, targetOs.VersionId)
3641

@@ -59,37 +64,45 @@ func (d *ubuntuDistroHandler) ValidateConfig(rc *ResolvedConfig) error {
5964
}
6065
}
6166

62-
// Check if Ubuntu is being used with bootloader hard-reset.
63-
// Ubuntu bootloader config logic is not yet fully implemented.
64-
if rc.BootLoader.ResetType == imagecustomizerapi.ResetBootLoaderTypeHard {
65-
return ErrUbuntuBootLoaderHardReset
67+
err := d.checkForUnsupportedApis(rc)
68+
if err != nil {
69+
return fmt.Errorf("%w (distro='%s', versionid='%s'):\n%w", ErrUnsupportedDistroApi, d.targetOs.Distro,
70+
d.targetOs.VersionId, err)
6671
}
6772

6873
return nil
6974
}
7075

71-
// ManagePackages handles the complete package management workflow for Ubuntu
72-
func (d *ubuntuDistroHandler) ManagePackages(ctx context.Context, buildDir string, baseConfigPath string,
73-
config *imagecustomizerapi.OS, imageChroot *safechroot.Chroot, toolsChroot *safechroot.Chroot,
74-
rpmsSources []string, useBaseImageRpmRepos bool, snapshotTime imagecustomizerapi.PackageSnapshotTime,
75-
) error {
76-
if len(rpmsSources) > 0 {
77-
return fmt.Errorf("RPM sources are not supported for Ubuntu images:\n%w", ErrUnsupportedUbuntuFeature)
76+
func (d *ubuntuDistroHandler) checkForUnsupportedApis(rc *ResolvedConfig) error {
77+
// Check if Ubuntu is being used with bootloader hard-reset.
78+
// Ubuntu bootloader config logic is not yet fully implemented.
79+
if rc.BootLoader.ResetType == imagecustomizerapi.ResetBootLoaderTypeHard {
80+
return ErrUbuntuUnsupportedBootloaderHardReset
81+
}
82+
83+
if len(rc.Options.RpmsSources) > 0 {
84+
return ErrUnsupportedRpmSources
7885
}
7986

8087
// UseBaseImageRpmRepos defaults to true and is only false when the user explicitly
8188
// passes --disable-base-image-rpm-repos. Ubuntu does not use RPM repos, so disabling
8289
// them is not meaningful and likely indicates a configuration mistake.
83-
if !useBaseImageRpmRepos {
84-
return fmt.Errorf("Disabling base image RPM repositories is not supported for Ubuntu images:\n%w",
85-
ErrUnsupportedUbuntuFeature)
90+
if !rc.Options.UseBaseImageRpmRepos {
91+
return ErrUbuntuUnsupportedDisableBaseImageRepos
8692
}
8793

88-
if config.Packages.SnapshotTime != "" {
89-
return fmt.Errorf("package snapshotTime is not yet supported for Ubuntu images:\n%w",
90-
ErrUnsupportedUbuntuFeature)
94+
if rc.HasPackageSnapshotTime() {
95+
return ErrUnsupportedPackageSnapshotTime
9196
}
9297

98+
return nil
99+
}
100+
101+
// ManagePackages handles the complete package management workflow for Ubuntu
102+
func (d *ubuntuDistroHandler) ManagePackages(ctx context.Context, buildDir string, baseConfigPath string,
103+
config *imagecustomizerapi.OS, imageChroot *safechroot.Chroot, toolsChroot *safechroot.Chroot,
104+
rpmsSources []string, useBaseImageRpmRepos bool, snapshotTime imagecustomizerapi.PackageSnapshotTime,
105+
) error {
93106
return managePackagesDeb(ctx, config, imageChroot)
94107
}
95108

@@ -100,8 +113,7 @@ func (d *ubuntuDistroHandler) IsPackageInstalled(imageChroot safechroot.ChrootIn
100113

101114
func (d *ubuntuDistroHandler) GetPackageInformation(imageChroot *safechroot.Chroot, packageName string,
102115
) (*PackageVersionInformation, error) {
103-
return nil, fmt.Errorf("Getting package information is not supported yet for Ubuntu images:\n%w",
104-
ErrUnsupportedUbuntuFeature)
116+
return nil, fmt.Errorf("getting package information is not supported yet for Ubuntu images")
105117
}
106118

107119
func (d *ubuntuDistroHandler) GetAllPackagesFromChroot(imageChroot safechroot.ChrootInterface) ([]OsPackage, error) {
@@ -194,7 +206,7 @@ func (d *ubuntuDistroHandler) ConfigureDiskBootLoader(imageConnection *imageconn
194206
selinuxConfig imagecustomizerapi.SELinux, kernelCommandLine imagecustomizerapi.KernelCommandLine,
195207
currentSELinuxMode imagecustomizerapi.SELinuxMode, newImage bool,
196208
) error {
197-
return ErrUbuntuBootLoaderHardReset
209+
return ErrUbuntuUnsupportedBootloaderHardReset
198210
}
199211

200212
func (d *ubuntuDistroHandler) ReadGrubConfigLinuxArgs(bootDir string) (map[string][]grubConfigLinuxArg, error) {
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
package imagecustomizerlib
5+
6+
import (
7+
"os"
8+
"path/filepath"
9+
"testing"
10+
11+
"github.com/microsoft/azure-linux-image-tools/toolkit/tools/imagecustomizerapi"
12+
"github.com/stretchr/testify/assert"
13+
)
14+
15+
func TestCustomizeImageUbuntuUnsupportedAPIs(t *testing.T) {
16+
for _, baseImageInfo := range baseImageUbuntuAll {
17+
t.Run(baseImageInfo.Name, func(t *testing.T) {
18+
testCustomizeImageUbuntuUnsupportedAPIsHelper(t, baseImageInfo)
19+
})
20+
}
21+
}
22+
23+
func testCustomizeImageUbuntuUnsupportedAPIsHelper(t *testing.T, baseImageInfo testBaseImageInfo) {
24+
baseImage := checkSkipForCustomizeImage(t, baseImageInfo)
25+
26+
testTmpDir := filepath.Join(tmpDir, "TestCustomizeImageUbuntuUnsupportedAPIs_"+baseImageInfo.Name)
27+
defer os.RemoveAll(testTmpDir)
28+
29+
buildDir := filepath.Join(testTmpDir, "build")
30+
31+
options := ImageCustomizerOptions{
32+
BuildDir: buildDir,
33+
InputImageFile: baseImage,
34+
OutputImageFile: "./out/image.vhdx",
35+
OutputImageFormat: "vhdx",
36+
UseBaseImageRpmRepos: true,
37+
PreviewFeatures: baseImageInfo.PreviewFeatures,
38+
}
39+
40+
config := &imagecustomizerapi.Config{
41+
OS: &imagecustomizerapi.OS{},
42+
}
43+
44+
config.OS.BootLoader.ResetType = imagecustomizerapi.ResetBootLoaderTypeHard
45+
46+
err := CustomizeImage(t.Context(), testTmpDir, config, options)
47+
assert.ErrorIs(t, err, ErrUbuntuUnsupportedBootloaderHardReset)
48+
assert.ErrorIs(t, err, ErrUnsupportedDistroApi)
49+
50+
config.OS.BootLoader.ResetType = imagecustomizerapi.ResetBootLoaderTypeDefault
51+
options.UseBaseImageRpmRepos = false
52+
53+
err = CustomizeImage(t.Context(), testTmpDir, config, options)
54+
assert.ErrorIs(t, err, ErrUbuntuUnsupportedDisableBaseImageRepos)
55+
assert.ErrorIs(t, err, ErrUnsupportedDistroApi)
56+
}

toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ var (
4242
ErrFedoraPreviewFeatureRequired = NewImageCustomizerError("Validation:FedoraPreviewFeatureRequired", fmt.Sprintf("preview feature '%s' required to customize Fedora base image", imagecustomizerapi.PreviewFeatureFedora))
4343
ErrUbuntuPreviewFeatureRequired = NewImageCustomizerError("Validation:UbuntuPreviewFeatureRequired", fmt.Sprintf("preview feature '%s' required to customize Ubuntu base image", imagecustomizerapi.PreviewFeatureUbuntu))
4444
ErrAzureContainerLinuxPreviewFeatureRequired = NewImageCustomizerError("Validation:AzureContainerLinuxPreviewFeatureRequired", fmt.Sprintf("preview feature '%s' required to customize Azure Container Linux base image", imagecustomizerapi.PreviewFeatureAzureContainerLinux))
45-
ErrUbuntuBootLoaderHardReset = NewImageCustomizerError("Validation:UbuntuBootLoaderHardReset", "bootloader hard-reset is not supported for Ubuntu images")
46-
ErrUnsupportedUbuntuFeature = NewImageCustomizerError("Validation:UnsupportedUbuntuFeature", "unsupported feature for Ubuntu images")
4745
ErrInputImageOciPreviewRequired = NewImageCustomizerError("Validation:InputImageOciPreviewRequired", fmt.Sprintf("preview feature '%s' required to specify OCI input image", imagecustomizerapi.PreviewFeatureInputImageOci))
4846
ErrConvertUnsupportedInputFormat = NewImageCustomizerError("Validation:ConvertUnsupportedInputFormat", "input image format is not supported")
4947
ErrConvertBuildDirRequired = NewImageCustomizerError("Validation:ConvertBuildDirRequired", "build directory is required for cosi and baremetal-image output formats")

0 commit comments

Comments
 (0)