Skip to content

Commit 0315d58

Browse files
committed
Image Customizer: Support multiple kernels for ISP/PXE flow.
1 parent 8e564b3 commit 0315d58

15 files changed

+450
-166
lines changed

test/vmtests/vmtests/test_min_change.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def test_min_change_efi_azl2_iso_output(
283283
close_list: List[Closeable],
284284
) -> None:
285285
azl_release = 2
286-
config_path = TEST_CONFIGS_DIR.joinpath("iso-bootstrap-vm.yaml")
286+
config_path = TEST_CONFIGS_DIR.joinpath("iso-bootstrap-vm-azl2.yaml")
287287
output_format = "iso"
288288

289289
run_min_change_test(
@@ -377,7 +377,7 @@ def test_min_change_legacy_azl2_iso_output(
377377
close_list: List[Closeable],
378378
) -> None:
379379
azl_release = 2
380-
config_path = TEST_CONFIGS_DIR.joinpath("iso-bootstrap-vm.yaml")
380+
config_path = TEST_CONFIGS_DIR.joinpath("iso-bootstrap-vm-azl2.yaml")
381381
output_format = "iso"
382382

383383
run_min_change_test(

toolkit/tools/pkg/imagecustomizerlib/grubcfgutils.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,8 @@ func FindNonRecoveryLinuxLine(inputGrubCfgContent string) ([]grub.Line, error) {
192192
return linuxLines, nil
193193
}
194194

195-
// Overrides the path of the kernel binary of all the linux commands within a grub config file.
195+
// Overrides the path of the kernel binary/initrd image in all the linux
196+
// commands within a grub config file.
196197
func setLinuxOrInitrdPathAll(inputGrubCfgContent string, commandName string, filePath string, allowMultiple bool) (outputGrubCfgContent string, oldFilePaths []string, err error) {
197198
quotedFilePath := grub.QuoteString(filePath)
198199

@@ -218,6 +219,34 @@ func setLinuxOrInitrdPathAll(inputGrubCfgContent string, commandName string, fil
218219
return outputGrubCfgContent, oldFilePaths, nil
219220
}
220221

222+
// Prefixes the path of the kernel binary/initrd image in all the linux
223+
// commands within a grub config file.
224+
func prependLinuxOrInitrdPathAll(inputGrubCfgContent string, commandName string, prefix string, allowMultiple bool) (outputGrubCfgContent string, oldFilePaths []string, err error) {
225+
lines, err := findLinuxOrInitrdLineAll(inputGrubCfgContent, commandName, allowMultiple)
226+
if err != nil {
227+
return "", nil, err
228+
}
229+
230+
outputGrubCfgContent = inputGrubCfgContent
231+
// loop from last to first so that the captured locations from
232+
// findGrubCommandAll are not invalidated as reconstructing
233+
// outputGrubCfgContent.
234+
for i := len(lines) - 1; i >= 0; i-- {
235+
line := lines[i]
236+
linuxFilePathToken := line.Tokens[1]
237+
start := linuxFilePathToken.Loc.Start.Index
238+
end := linuxFilePathToken.Loc.End.Index
239+
240+
oldFilePath := inputGrubCfgContent[start:end]
241+
oldFilePaths = append(oldFilePaths, oldFilePath)
242+
if !strings.HasPrefix(oldFilePath, prefix) {
243+
outputGrubCfgContent = outputGrubCfgContent[:start] + prefix + outputGrubCfgContent[start:]
244+
}
245+
}
246+
247+
return outputGrubCfgContent, oldFilePaths, nil
248+
}
249+
221250
// Overrides the path of the kernel binary of the linux command within a grub config file.
222251
func setLinuxPath(inputGrubCfgContent string, linuxPath string) (outputGrubCfgContent string, oldKernelPath string, err error) {
223252
outputGrubCfgContent, oldKernelPaths, err := setLinuxOrInitrdPathAll(inputGrubCfgContent, linuxCommand, linuxPath, false /*allowMultiple*/)

toolkit/tools/pkg/imagecustomizerlib/liveosisoartifactstore.go

Lines changed: 89 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ package imagecustomizerlib
55

66
import (
77
"fmt"
8-
"io/fs"
98
"os"
109
"path/filepath"
1110
"regexp"
@@ -29,13 +28,22 @@ const (
2928
savedConfigsFileNamePath = "/" + savedConfigsDir + "/" + savedConfigsFileName
3029
)
3130

31+
var (
32+
kernelVersionRegEx = regexp.MustCompile(`\b(\d+\.\d+\.\d+\.\d+-\d+\.(azl|cm)\d)\b`)
33+
)
34+
3235
type IsoInfoStore struct {
33-
kernelVersion string
3436
seLinuxMode imagecustomizerapi.SELinuxMode
3537
dracutPackageInfo *PackageVersionInformation
3638
selinuxPolicyPackageInfo *PackageVersionInformation
3739
}
3840

41+
type KernelBootFiles struct {
42+
vmlinuzPath string
43+
initrdImagePath string
44+
otherFiles []string
45+
}
46+
3947
type IsoFilesStore struct {
4048
artifactsDir string
4149
bootEfiPath string
@@ -44,8 +52,8 @@ type IsoFilesStore struct {
4452
isoGrubCfgPath string
4553
pxeGrubCfgPath string
4654
savedConfigsFilePath string
47-
vmlinuzPath string
48-
initrdImagePath string
55+
kernelBootFiles map[string]*KernelBootFiles // kernel-version -> KernelBootFiles
56+
initrdImagePath string // non-kernel specific
4957
squashfsImagePath string
5058
additionalFiles map[string]string // local-build-path -> iso-media-path
5159
}
@@ -82,53 +90,60 @@ func containsGrubNoPrefix(filePaths []string) (bool, error) {
8290
return false, nil
8391
}
8492

85-
func findKernelVersion(imageRootDir string) (kernelVersion string, err error) {
86-
const kernelModulesDir = "/usr/lib/modules"
93+
func getSELinuxMode(imageChroot *safechroot.Chroot) (imagecustomizerapi.SELinuxMode, error) {
94+
bootCustomizer, err := NewBootCustomizer(imageChroot)
95+
if err != nil {
96+
return imagecustomizerapi.SELinuxModeDefault, err
97+
}
8798

88-
kernelParentPath := filepath.Join(imageRootDir, kernelModulesDir)
89-
kernelDirs, err := os.ReadDir(kernelParentPath)
99+
imageSELinuxMode, err := bootCustomizer.GetSELinuxMode(imageChroot)
90100
if err != nil {
91-
return "", fmt.Errorf("failed to enumerate kernels under (%s):\n%w", kernelParentPath, err)
101+
return imagecustomizerapi.SELinuxModeDefault, fmt.Errorf("failed to get current SELinux mode:\n%w", err)
92102
}
93103

94-
// Filter out directories that are empty.
95-
// Some versions of Azure Linux 2.0 don't cleanup properly when the kernel package is uninstalled.
96-
filteredKernelDirs := []fs.DirEntry(nil)
97-
for _, kernelDir := range kernelDirs {
98-
kernelPath := filepath.Join(kernelParentPath, kernelDir.Name())
99-
empty, err := file.IsDirEmpty(kernelPath)
100-
if err != nil {
101-
return "", err
102-
}
104+
return imageSELinuxMode, nil
105+
}
103106

104-
if !empty {
105-
filteredKernelDirs = append(filteredKernelDirs, kernelDir)
106-
}
107+
func getKernelVersions(filesStore *IsoFilesStore) []string {
108+
var kernelVersions []string
109+
for k := range filesStore.kernelBootFiles {
110+
kernelVersions = append(kernelVersions, k)
107111
}
108112

109-
if len(filteredKernelDirs) == 0 {
110-
return "", fmt.Errorf("did not find any kernels installed under (%s)", kernelModulesDir)
111-
}
112-
if len(filteredKernelDirs) > 1 {
113-
return "", fmt.Errorf("unsupported scenario: found more than one kernel under (%s)", kernelModulesDir)
114-
}
115-
kernelVersion = filteredKernelDirs[0].Name()
116-
logger.Log.Debugf("Found installed kernel version (%s)", kernelVersion)
117-
return kernelVersion, nil
113+
return kernelVersions
118114
}
119115

120-
func getSELinuxMode(imageChroot *safechroot.Chroot) (imagecustomizerapi.SELinuxMode, error) {
121-
bootCustomizer, err := NewBootCustomizer(imageChroot)
122-
if err != nil {
123-
return imagecustomizerapi.SELinuxModeDefault, err
116+
func storeIfKernelSpecificFile(filesStore *IsoFilesStore, targetPath string) bool {
117+
scheduleAdditionalFile := true
118+
119+
baseFileName := filepath.Base(targetPath)
120+
121+
matches := kernelVersionRegEx.FindStringSubmatch(baseFileName)
122+
if len(matches) <= 1 {
123+
return scheduleAdditionalFile
124124
}
125125

126-
imageSELinuxMode, err := bootCustomizer.GetSELinuxMode(imageChroot)
127-
if err != nil {
128-
return imagecustomizerapi.SELinuxModeDefault, fmt.Errorf("failed to get current SELinux mode:\n%w", err)
126+
kernelVersion := matches[1]
127+
128+
// Ensure we have an entry in the map for it
129+
kernelBootFiles, exists := filesStore.kernelBootFiles[kernelVersion]
130+
if !exists {
131+
kernelBootFiles = &KernelBootFiles{}
132+
filesStore.kernelBootFiles[kernelVersion] = kernelBootFiles
129133
}
130134

131-
return imageSELinuxMode, nil
135+
if strings.HasPrefix(baseFileName, vmLinuzPrefix) {
136+
kernelBootFiles.vmlinuzPath = targetPath
137+
scheduleAdditionalFile = false
138+
} else if strings.HasPrefix(baseFileName, initramfsPrefix) || strings.HasPrefix(baseFileName, initrdPrefix) {
139+
kernelBootFiles.initrdImagePath = targetPath
140+
scheduleAdditionalFile = false
141+
} else {
142+
kernelBootFiles.otherFiles = append(kernelBootFiles.otherFiles, targetPath)
143+
scheduleAdditionalFile = false
144+
}
145+
146+
return scheduleAdditionalFile
132147
}
133148

134149
func createIsoFilesStoreFromMountedImage(inputArtifactsStore *IsoArtifactsStore, imageRootDir string, storeDir string) (filesStore *IsoFilesStore, err error) {
@@ -138,18 +153,13 @@ func createIsoFilesStoreFromMountedImage(inputArtifactsStore *IsoArtifactsStore,
138153
artifactsDir: artifactsDir,
139154
savedConfigsFilePath: filepath.Join(artifactsDir, savedConfigsDir, savedConfigsFileName),
140155
additionalFiles: make(map[string]string),
156+
kernelBootFiles: make(map[string]*KernelBootFiles),
141157
}
142158

143159
// the following files will be re-created - no need to copy them only to
144160
// have them overwritten.
145161
var exclusions []*regexp.Regexp
146162
//
147-
// We will generate a new initrd later. So, we do not copy the initrd.img
148-
// that comes in the input full disk image.
149-
//
150-
exclusions = append(exclusions, regexp.MustCompile(`/boot/initrd\.img.*`))
151-
exclusions = append(exclusions, regexp.MustCompile(`/boot/initramfs-.*\.img.*`))
152-
//
153163
// On full disk images (generated by Mariner toolkit), there are two
154164
// grub.cfg files:
155165
// - <boot partition>/boot/grub2/grub.cfg:
@@ -195,6 +205,13 @@ func createIsoFilesStoreFromMountedImage(inputArtifactsStore *IsoArtifactsStore,
195205
relativeFilePath := strings.TrimPrefix(sourcePath, imageRootDir)
196206
targetPath := strings.Replace(sourcePath, imageRootDir, filesStore.artifactsDir, -1)
197207

208+
// `scheduleAdditionalFile` indicates whether the file being processed
209+
// now should be captured in the general 'additional' files collection
210+
// or it has been captured in a data structure specific field
211+
// (like filesStore.isoGrubCfgPath) and it will be handled from there.
212+
// 'additional files' is a collection of all the files that need to be
213+
// included in the final output, but we do not have particular interest
214+
// in them (i.e. user files that we do not manipulate - but copy as-is).
198215
scheduleAdditionalFile := true
199216

200217
_, bootFilesConfig, err := getBootArchConfig()
@@ -237,12 +254,11 @@ func createIsoFilesStoreFromMountedImage(inputArtifactsStore *IsoArtifactsStore,
237254
// We will place the pxe grub config next to the iso grub config.
238255
filesStore.pxeGrubCfgPath = filepath.Join(filepath.Dir(filesStore.isoGrubCfgPath), pxeGrubCfg)
239256
scheduleAdditionalFile = false
240-
}
241-
242-
if strings.HasPrefix(filepath.Base(targetPath), vmLinuzPrefix) {
243-
targetPath = filepath.Join(filepath.Dir(targetPath), "vmlinuz")
244-
filesStore.vmlinuzPath = targetPath
257+
case isoInitrdPath:
258+
filesStore.initrdImagePath = targetPath
245259
scheduleAdditionalFile = false
260+
default:
261+
scheduleAdditionalFile = storeIfKernelSpecificFile(filesStore, targetPath)
246262
}
247263

248264
err = file.NewFileCopyBuilder(sourcePath, targetPath).
@@ -280,7 +296,7 @@ func createIsoFilesStoreFromMountedImage(inputArtifactsStore *IsoArtifactsStore,
280296
// already. This also ensures that no file from the input iso overwrites
281297
// a newer version that has just been created.
282298

283-
// Copy the addition files from the input iso store
299+
// Copy the additional files from the input iso store
284300
for inputSourceFile, inputTargetFile := range inputArtifactsStore.files.additionalFiles {
285301
found := false
286302
for _, targetFile := range filesStore.additionalFiles {
@@ -327,15 +343,9 @@ func createIsoFilesStoreFromMountedImage(inputArtifactsStore *IsoArtifactsStore,
327343
func createIsoInfoStoreFromMountedImage(imageRootDir string) (infoStore *IsoInfoStore, err error) {
328344
infoStore = &IsoInfoStore{}
329345

330-
kernelVersion, err := findKernelVersion(imageRootDir)
331-
if err != nil {
332-
return nil, fmt.Errorf("failed to determine kernel version from (%s):\n%w", imageRootDir, err)
333-
}
334-
infoStore.kernelVersion = kernelVersion
335-
336346
chroot := safechroot.NewChroot(imageRootDir, true /*isExistingDir*/)
337347
if chroot == nil {
338-
return nil, fmt.Errorf("failed to create a new chroot object for (%s).", imageRootDir)
348+
return nil, fmt.Errorf("failed to create a new chroot object for (%s)", imageRootDir)
339349
}
340350
defer chroot.Close(true /*leaveOnDisk*/)
341351

@@ -387,6 +397,7 @@ func createIsoFilesStoreFromIsoImage(isoImageFile, storeDir string) (filesStore
387397
}
388398

389399
filesStore.additionalFiles = make(map[string]string)
400+
filesStore.kernelBootFiles = make(map[string]*KernelBootFiles)
390401

391402
_, bootFilesConfig, err := getBootArchConfig()
392403
if err != nil {
@@ -429,12 +440,11 @@ func createIsoFilesStoreFromIsoImage(isoImageFile, storeDir string) (filesStore
429440
case savedConfigsFileNamePath:
430441
filesStore.savedConfigsFilePath = isoFile
431442
scheduleAdditionalFile = false
432-
case isoKernelPath:
433-
filesStore.vmlinuzPath = isoFile
434-
scheduleAdditionalFile = false
435443
case isoBootImagePath:
436444
filesStore.isoBootImagePath = isoFile
437445
scheduleAdditionalFile = false
446+
default:
447+
scheduleAdditionalFile = storeIfKernelSpecificFile(filesStore, isoFile)
438448
}
439449

440450
if scheduleAdditionalFile {
@@ -457,7 +467,6 @@ func createIsoInfoStoreFromIsoImage(savedConfigFile string) (infoStore *IsoInfoS
457467
// since we will not expand the rootfs and inspect its contents to get
458468
// such information.
459469
infoStore = &IsoInfoStore{
460-
kernelVersion: savedConfigs.OS.KernelVersion,
461470
dracutPackageInfo: savedConfigs.OS.DracutPackageInfo,
462471
selinuxPolicyPackageInfo: savedConfigs.OS.SELinuxPolicyPackageInfo,
463472
}
@@ -524,6 +533,19 @@ func fileExistsToString(filePath string) string {
524533
return "!e " + filePath
525534
}
526535

536+
func dumpKernelFiles(kernelBootFiles *KernelBootFiles) {
537+
if kernelBootFiles == nil {
538+
logger.Log.Debugf("-- -- not defined")
539+
return
540+
}
541+
542+
logger.Log.Debugf("-- -- vmlinuzPath = %s", fileExistsToString(kernelBootFiles.vmlinuzPath))
543+
logger.Log.Debugf("-- -- initrdImagePath = %s", fileExistsToString(kernelBootFiles.initrdImagePath))
544+
for _, otherFile := range kernelBootFiles.otherFiles {
545+
logger.Log.Debugf("-- -- otherFile = %s", fileExistsToString(otherFile))
546+
}
547+
}
548+
527549
func dumpFilesStore(filesStore *IsoFilesStore) {
528550
logger.Log.Debugf("Files Store")
529551
if filesStore == nil {
@@ -537,7 +559,12 @@ func dumpFilesStore(filesStore *IsoFilesStore) {
537559
logger.Log.Debugf("-- isoGrubCfgPath = %s", fileExistsToString(filesStore.isoGrubCfgPath))
538560
logger.Log.Debugf("-- pxeGrubCfgPath = %s", fileExistsToString(filesStore.pxeGrubCfgPath))
539561
logger.Log.Debugf("-- savedConfigsFilePath = %s", fileExistsToString(filesStore.savedConfigsFilePath))
540-
logger.Log.Debugf("-- vmlinuzPath = %s", fileExistsToString(filesStore.vmlinuzPath))
562+
logger.Log.Debugf("-- kernel file groups")
563+
for key, value := range filesStore.kernelBootFiles {
564+
logger.Log.Debugf("-- -- version = %s", key)
565+
dumpKernelFiles(value)
566+
}
567+
541568
logger.Log.Debugf("-- initrdImagePath = %s", fileExistsToString(filesStore.initrdImagePath))
542569
logger.Log.Debugf("-- squashfsImagePath = %s", fileExistsToString(filesStore.squashfsImagePath))
543570
logger.Log.Debugf("-- additionalFiles =")
@@ -552,7 +579,6 @@ func dumpInfoStore(infoStore *IsoInfoStore) {
552579
logger.Log.Debugf("-- not defined")
553580
return
554581
}
555-
logger.Log.Debugf("-- kernelVersion = %s", infoStore.kernelVersion)
556582
logger.Log.Debugf("-- seLinuxMode = %s", infoStore.seLinuxMode)
557583
if infoStore.dracutPackageInfo != nil {
558584
logger.Log.Debugf("-- dracut package info = %s", infoStore.dracutPackageInfo.getFullVersionString())

0 commit comments

Comments
 (0)