From 4742b8bf0b1b16f915c763da4bb8b376cf4fb91a Mon Sep 17 00:00:00 2001 From: Chris Gunn Date: Thu, 14 Dec 2023 16:03:38 -0800 Subject: [PATCH] Image Customizer: Add support for kernel command-line (#6881) --- .../imagecustomizer/docs/configuration.md | 20 +++ toolkit/tools/imagecustomizerapi/config.go | 5 + .../tools/imagecustomizerapi/config_test.go | 69 +++++++++++ .../imagecustomizerapi/kernelcommandline.go | 34 ++++++ .../tools/imagecustomizerapi/systemconfig.go | 6 + .../imagecustomizerapi/systemconfig_test.go | 12 ++ .../pkg/imagecustomizerlib/customizeboot.go | 66 ++++++++++ .../customizepartitionsfilecopy.go | 6 +- .../pkg/imagecustomizerlib/customizeutils.go | 6 + .../pkg/imagecustomizerlib/imagecustomizer.go | 9 +- .../imagecustomizer_test.go | 114 +++++++++++++++--- .../pkg/imagecustomizerlib/imageutils.go | 57 +++++---- .../testdata/commandline-config.yaml | 3 + .../testdata/partitions-config.yaml | 3 + .../pkg/imagecustomizerlib/typeConversion.go | 8 ++ 15 files changed, 369 insertions(+), 49 deletions(-) create mode 100644 toolkit/tools/imagecustomizerapi/kernelcommandline.go create mode 100644 toolkit/tools/pkg/imagecustomizerlib/customizeboot.go create mode 100644 toolkit/tools/pkg/imagecustomizerlib/testdata/commandline-config.yaml diff --git a/toolkit/tools/imagecustomizer/docs/configuration.md b/toolkit/tools/imagecustomizer/docs/configuration.md index 59a4d6fa77..357910805e 100644 --- a/toolkit/tools/imagecustomizer/docs/configuration.md +++ b/toolkit/tools/imagecustomizer/docs/configuration.md @@ -179,6 +179,21 @@ SystemConfig: Permissions: "664" ``` +## KernelCommandLine type + +Options for configuring the kernel. + +### ExtraCommandLine + +Additional Linux kernel command line options to add to the image. + +If the partitions are customized, then the `grub.cfg` file will be reset to handle the +new partition layout. +So, any existing ExtraCommandLine value in the base image will be replaced. + +If the partitions are not customized, then the `ExtraCommandLine` value will be appended +to the existing `grub.cfg` file. + ## Module type Options for configuring a kernel module. @@ -461,6 +476,11 @@ SystemConfig: Hostname: example-image ``` +### KernelCommandLine [[KernelCommandLine](#kernelcommandline-type)] + +Specifies extra kernel command line options, as well as other configuration values +relating to the kernel. + ### UpdateBaseImagePackages [bool] Updates the packages that exist in the base image. diff --git a/toolkit/tools/imagecustomizerapi/config.go b/toolkit/tools/imagecustomizerapi/config.go index f387de301b..4756736385 100644 --- a/toolkit/tools/imagecustomizerapi/config.go +++ b/toolkit/tools/imagecustomizerapi/config.go @@ -39,11 +39,16 @@ func (c *Config) IsValid() error { hasDisks := c.Disks != nil hasBootType := c.SystemConfig.BootType != BootTypeUnset + hasPartitionSettings := len(c.SystemConfig.PartitionSettings) > 0 if hasDisks != hasBootType { return fmt.Errorf("SystemConfig.BootType and Disks must be specified together") } + if hasPartitionSettings && !hasDisks { + return fmt.Errorf("the Disks and SystemConfig.BootType values must also be specified if SystemConfig.PartitionSettings is specified") + } + // Ensure the correct partitions exist to support the specified the boot type. switch c.SystemConfig.BootType { case BootTypeEfi: diff --git a/toolkit/tools/imagecustomizerapi/config_test.go b/toolkit/tools/imagecustomizerapi/config_test.go index 2b0a201797..d36ce42a70 100644 --- a/toolkit/tools/imagecustomizerapi/config_test.go +++ b/toolkit/tools/imagecustomizerapi/config_test.go @@ -262,3 +262,72 @@ func TestConfigIsValidInvalidPartitionId(t *testing.T) { assert.ErrorContains(t, err, "partition") assert.ErrorContains(t, err, "ID") } + +func TestConfigIsValidPartitionSettingsMissingDisks(t *testing.T) { + config := &Config{ + SystemConfig: SystemConfig{ + Hostname: "test", + PartitionSettings: []PartitionSetting{ + { + ID: "esp", + MountPoint: "/boot/efi", + }, + }, + }, + } + err := config.IsValid() + assert.Error(t, err) + assert.ErrorContains(t, err, "Disks") + assert.ErrorContains(t, err, "BootType") + assert.ErrorContains(t, err, "PartitionSettings") +} + +func TestConfigIsValidBootTypeMissingDisks(t *testing.T) { + config := &Config{ + SystemConfig: SystemConfig{ + Hostname: "test", + BootType: BootTypeEfi, + KernelCommandLine: KernelCommandLine{ + ExtraCommandLine: "console=ttyS0", + }, + }, + } + err := config.IsValid() + assert.Error(t, err) + assert.ErrorContains(t, err, "SystemConfig.BootType and Disks must be specified together") +} + +func TestConfigIsValidKernelCLI(t *testing.T) { + config := &Config{ + Disks: &[]Disk{{ + PartitionTableType: "gpt", + MaxSize: 2, + Partitions: []Partition{ + { + ID: "esp", + FsType: "fat32", + Start: 1, + Flags: []PartitionFlag{ + "esp", + "boot", + }, + }, + }, + }}, + SystemConfig: SystemConfig{ + BootType: "efi", + Hostname: "test", + PartitionSettings: []PartitionSetting{ + { + ID: "esp", + MountPoint: "/boot/efi", + }, + }, + KernelCommandLine: KernelCommandLine{ + ExtraCommandLine: "console=ttyS0", + }, + }, + } + err := config.IsValid() + assert.NoError(t, err) +} diff --git a/toolkit/tools/imagecustomizerapi/kernelcommandline.go b/toolkit/tools/imagecustomizerapi/kernelcommandline.go new file mode 100644 index 0000000000..8222f670b5 --- /dev/null +++ b/toolkit/tools/imagecustomizerapi/kernelcommandline.go @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package imagecustomizerapi + +import ( + "fmt" + "strings" +) + +type KernelCommandLine struct { + // Extra kernel command line args. + ExtraCommandLine string `yaml:"ExtraCommandLine"` +} + +func (s *KernelCommandLine) IsValid() error { + err := commandLineIsValid(s.ExtraCommandLine, "ExtraCommandLine") + if err != nil { + return err + } + + return nil +} + +func commandLineIsValid(commandLine string, fieldName string) error { + // Disallow special characters to avoid breaking the grub.cfg file. + // In addition, disallow the "`" character, since it is used as the sed escape character by + // `installutils.setGrubCfgAdditionalCmdLine()`. + if strings.ContainsAny(commandLine, "\n'\"\\$`") { + return fmt.Errorf("the %s value contains invalid characters", fieldName) + } + + return nil +} diff --git a/toolkit/tools/imagecustomizerapi/systemconfig.go b/toolkit/tools/imagecustomizerapi/systemconfig.go index e2ed6bedef..41e74856d5 100644 --- a/toolkit/tools/imagecustomizerapi/systemconfig.go +++ b/toolkit/tools/imagecustomizerapi/systemconfig.go @@ -21,6 +21,7 @@ type SystemConfig struct { PackagesRemove []string `yaml:"PackagesRemove"` PackageListsUpdate []string `yaml:"PackageListsUpdate"` PackagesUpdate []string `yaml:"PackagesUpdate"` + KernelCommandLine KernelCommandLine `yaml:"KernelCommandLine"` AdditionalFiles map[string]FileConfigList `yaml:"AdditionalFiles"` PartitionSettings []PartitionSetting `yaml:"PartitionSettings"` PostInstallScripts []Script `yaml:"PostInstallScripts"` @@ -44,6 +45,11 @@ func (s *SystemConfig) IsValid() error { } } + err = s.KernelCommandLine.IsValid() + if err != nil { + return fmt.Errorf("invalid KernelCommandLine: %w", err) + } + for sourcePath, fileConfigList := range s.AdditionalFiles { err = fileConfigList.IsValid() if err != nil { diff --git a/toolkit/tools/imagecustomizerapi/systemconfig_test.go b/toolkit/tools/imagecustomizerapi/systemconfig_test.go index cada72e43e..0a662c6471 100644 --- a/toolkit/tools/imagecustomizerapi/systemconfig_test.go +++ b/toolkit/tools/imagecustomizerapi/systemconfig_test.go @@ -41,3 +41,15 @@ func TestSystemConfigIsValidDuplicatePartitionID(t *testing.T) { assert.Error(t, err) assert.ErrorContains(t, err, "duplicate PartitionSettings ID") } + +func TestSystemConfigIsValidKernelCommandLineInvalidChars(t *testing.T) { + value := SystemConfig{ + KernelCommandLine: KernelCommandLine{ + ExtraCommandLine: "example=\"example\"", + }, + } + + err := value.IsValid() + assert.Error(t, err) + assert.ErrorContains(t, err, "ExtraCommandLine") +} diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizeboot.go b/toolkit/tools/pkg/imagecustomizerlib/customizeboot.go new file mode 100644 index 0000000000..730d70cecf --- /dev/null +++ b/toolkit/tools/pkg/imagecustomizerlib/customizeboot.go @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +package imagecustomizerlib + +import ( + "fmt" + "os" + "path/filepath" + "regexp" + + "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/logger" + "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/safechroot" +) + +var ( + linuxCommandLineRegex = regexp.MustCompile(`\tlinux .* (\$kernelopts)`) +) + +func handleKernelCommandLine(extraCommandLine string, imageChroot *safechroot.Chroot, partitionsCustomized bool) error { + var err error + + if partitionsCustomized { + // ExtraCommandLine was handled when the new image was created and the grub.cfg file was regenerated from + // scatch. + return nil + } + + if extraCommandLine == "" { + // Nothing to do. + return nil + } + + logger.Log.Infof("Setting KernelCommandLine.ExtraCommandLine") + + grub2ConfigFilePath := filepath.Join(imageChroot.RootDir(), "/boot/grub2/grub.cfg") + + // Read the existing grub.cfg file. + grub2ConfigFileBytes, err := os.ReadFile(grub2ConfigFilePath) + if err != nil { + return fmt.Errorf("failed to read existing grub2 config file: %w", err) + } + + grub2ConfigFile := string(grub2ConfigFileBytes) + + // Find the point where the new command line arguments should be added. + match := linuxCommandLineRegex.FindStringSubmatchIndex(grub2ConfigFile) + if match == nil { + return fmt.Errorf("failed to find Linux kernel command line params in grub2 config file") + } + + // Get the location of "$kernelopts". + // Note: regexp returns index pairs. So, [2] is the start index of the 1st group. + insertIndex := match[2] + + // Insert new command line arguments. + newGrub2ConfigFile := grub2ConfigFile[:insertIndex] + extraCommandLine + " " + grub2ConfigFile[insertIndex:] + + // Update grub.cfg file. + err = os.WriteFile(grub2ConfigFilePath, []byte(newGrub2ConfigFile), 0) + if err != nil { + return fmt.Errorf("failed to write new grub2 config file: %w", err) + } + + return nil +} diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizepartitionsfilecopy.go b/toolkit/tools/pkg/imagecustomizerlib/customizepartitionsfilecopy.go index e7bb64e091..9e739f405f 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/customizepartitionsfilecopy.go +++ b/toolkit/tools/pkg/imagecustomizerlib/customizepartitionsfilecopy.go @@ -9,7 +9,6 @@ import ( "path/filepath" "github.com/microsoft/CBL-Mariner/toolkit/tools/imagecustomizerapi" - "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/logger" "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/safechroot" "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/shell" ) @@ -30,7 +29,7 @@ func customizePartitionsUsingFileCopy(buildDir string, baseConfigPath string, co } newImageConnection, err := createNewImage(newBuildImageFile, diskConfig, config.SystemConfig.PartitionSettings, - config.SystemConfig.BootType, buildDir, "newimageroot", installOSFunc) + config.SystemConfig.BootType, config.SystemConfig.KernelCommandLine, buildDir, "newimageroot", installOSFunc) if err != nil { return err } @@ -82,8 +81,7 @@ func copyFilesIntoNewDiskHelper(existingImageChroot *safechroot.Chroot, newImage copyArgs = append(copyArgs, fullFileName) } - err = shell.ExecuteLiveWithCallback(func(...interface{}) {}, logger.Log.Warn, false, - "cp", copyArgs...) + err = shell.ExecuteLiveWithErr(1, "cp", copyArgs...) if err != nil { return fmt.Errorf("failed to copy files:\n%w", err) } diff --git a/toolkit/tools/pkg/imagecustomizerlib/customizeutils.go b/toolkit/tools/pkg/imagecustomizerlib/customizeutils.go index f91a67741f..6dfb336669 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/customizeutils.go +++ b/toolkit/tools/pkg/imagecustomizerlib/customizeutils.go @@ -83,6 +83,12 @@ func doCustomizations(buildDir string, baseConfigPath string, config *imagecusto return err } + err = handleKernelCommandLine(config.SystemConfig.KernelCommandLine.ExtraCommandLine, imageChroot, + partitionsCustomized) + if err != nil { + return fmt.Errorf("failed to add extra kernel command line: %w", err) + } + err = runScripts(baseConfigPath, config.SystemConfig.FinalizeImageScripts, imageChroot) if err != nil { return err diff --git a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go index 1e98cdc640..039fbaade3 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go +++ b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer.go @@ -138,9 +138,16 @@ func toQemuImageFormat(imageFormat string) (string, error) { func validateConfig(baseConfigPath string, config *imagecustomizerapi.Config, rpmsSources []string, useBaseImageRpmRepos bool, ) error { + // Note: This IsValid() check does duplicate the one in UnmarshalYamlFile(). + // But it is useful for functions that call CustomizeImage() directly. For example, test code. + err := config.IsValid() + if err != nil { + return err + } + partitionsCustomized := hasPartitionCustomizations(config) - err := validateSystemConfig(baseConfigPath, &config.SystemConfig, rpmsSources, useBaseImageRpmRepos, + err = validateSystemConfig(baseConfigPath, &config.SystemConfig, rpmsSources, useBaseImageRpmRepos, partitionsCustomized) if err != nil { return err diff --git a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer_test.go b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer_test.go index ed59509774..cc9b970d25 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer_test.go +++ b/toolkit/tools/pkg/imagecustomizerlib/imagecustomizer_test.go @@ -8,16 +8,20 @@ import ( "fmt" "os" "path/filepath" + "regexp" "testing" "github.com/microsoft/CBL-Mariner/toolkit/tools/imagecustomizerapi" "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/buildpipeline" "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/ptrutils" "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/safechroot" - "github.com/microsoft/CBL-Mariner/toolkit/tools/internal/safeloopback" "github.com/stretchr/testify/assert" ) +const ( + testImageRootDirName = "testimageroot" +) + func TestCustomizeImageEmptyConfig(t *testing.T) { var err error @@ -88,34 +92,43 @@ func TestCustomizeImageCopyFiles(t *testing.T) { checkFileType(t, outImageFilePath, "raw") // Mount the output disk image so that its contents can be checked. - loopback, err := safeloopback.NewLoopback(outImageFilePath) + imageConnection, err := reconnectToFakeEfiImage(buildDir, outImageFilePath) if !assert.NoError(t, err) { return } - defer loopback.Close() + defer imageConnection.Close() - // Create partition mount config. - // Note: The assigned loopback device might be different from the one assigned when `createFakeEfiImage` ran. - bootPartitionDevPath := fmt.Sprintf("%sp1", loopback.DevicePath()) - osPartitionDevPath := fmt.Sprintf("%sp2", loopback.DevicePath()) + // Check the contents of the copied file. + file_contents, err := os.ReadFile(filepath.Join(imageConnection.Chroot().RootDir(), "a.txt")) + assert.NoError(t, err) + assert.Equal(t, "abcdefg\n", string(file_contents)) +} + +func reconnectToFakeEfiImage(buildDir string, imageFilePath string) (*ImageConnection, error) { + imageConnection := NewImageConnection() + err := imageConnection.ConnectLoopback(imageFilePath) + if err != nil { + imageConnection.Close() + return nil, err + } + + rootDir := filepath.Join(buildDir, testImageRootDirName) + + bootPartitionDevPath := fmt.Sprintf("%sp1", imageConnection.Loopback().DevicePath()) + osPartitionDevPath := fmt.Sprintf("%sp2", imageConnection.Loopback().DevicePath()) - newMountDirectories := []string{} mountPoints := []*safechroot.MountPoint{ safechroot.NewPreDefaultsMountPoint(osPartitionDevPath, "/", "ext4", 0, ""), safechroot.NewMountPoint(bootPartitionDevPath, "/boot/efi", "vfat", 0, ""), } - imageChroot := safechroot.NewChroot(filepath.Join(buildDir, "imageroot"), false) - err = imageChroot.Initialize("", newMountDirectories, mountPoints) - if !assert.NoError(t, err) { - return + err = imageConnection.ConnectChroot(rootDir, false, []string{}, mountPoints) + if err != nil { + imageConnection.Close() + return nil, err } - defer imageChroot.Close(false) - // Check the contents of the copied file. - file_contents, err := os.ReadFile(filepath.Join(imageChroot.RootDir(), "a.txt")) - assert.NoError(t, err) - assert.Equal(t, "abcdefg\n", string(file_contents)) + return imageConnection, nil } func TestValidateConfigValidAdditionalFiles(t *testing.T) { @@ -189,6 +202,69 @@ func TestValidateConfigScriptNonExecutable(t *testing.T) { assert.Error(t, err) } +func TestCustomizeImageKernelCommandLineAdd(t *testing.T) { + var err error + + if testing.Short() { + t.Skip("Short mode enabled") + } + + if !buildpipeline.IsRegularBuild() { + t.Skip("loopback block device not available") + } + + if os.Geteuid() != 0 { + t.Skip("Test must be run as root because it uses a chroot") + } + + buildDir := filepath.Join(tmpDir, "TestCustomizeImageKernelCommandLine") + outImageFilePath := filepath.Join(buildDir, "image.vhd") + + // Create fake disk. + diskFilePath, err := createFakeEfiImage(buildDir) + if !assert.NoError(t, err) { + return + } + + // Customize image. + config := &imagecustomizerapi.Config{ + SystemConfig: imagecustomizerapi.SystemConfig{ + KernelCommandLine: imagecustomizerapi.KernelCommandLine{ + ExtraCommandLine: "console=tty0 console=ttyS0", + }, + }, + } + + err = CustomizeImage(buildDir, buildDir, config, diskFilePath, nil, outImageFilePath, "raw", false) + if !assert.NoError(t, err) { + return + } + + // Mount the output disk image so that its contents can be checked. + imageConnection, err := reconnectToFakeEfiImage(buildDir, outImageFilePath) + if !assert.NoError(t, err) { + return + } + defer imageConnection.Close() + + // Read the grub.cfg file. + grub2ConfigFilePath := filepath.Join(imageConnection.Chroot().RootDir(), "/boot/grub2/grub.cfg") + + grub2ConfigFile, err := os.ReadFile(grub2ConfigFilePath) + if !assert.NoError(t, err) { + return + } + + t.Logf("%s", grub2ConfigFile) + + linuxCommandLineRegex, err := regexp.Compile(`linux .* console=tty0 console=ttyS0 `) + if !assert.NoError(t, err) { + return + } + + assert.True(t, linuxCommandLineRegex.Match(grub2ConfigFile)) +} + func createFakeEfiImage(buildDir string) (string, error) { var err error @@ -241,8 +317,8 @@ func createFakeEfiImage(buildDir string) (string, error) { return nil } - imageConnection, err := createNewImage(rawDisk, diskConfig, partitionSettings, "efi", buildDir, "imageroot", - installOS) + imageConnection, err := createNewImage(rawDisk, diskConfig, partitionSettings, "efi", + imagecustomizerapi.KernelCommandLine{}, buildDir, testImageRootDirName, installOS) if err != nil { return "", err } diff --git a/toolkit/tools/pkg/imagecustomizerlib/imageutils.go b/toolkit/tools/pkg/imagecustomizerlib/imageutils.go index ae573e6db2..4280359813 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/imageutils.go +++ b/toolkit/tools/pkg/imagecustomizerlib/imageutils.go @@ -58,13 +58,14 @@ func connectToExistingImageHelper(imageConnection *ImageConnection, imageFilePat } func createNewImage(filename string, diskConfig imagecustomizerapi.Disk, - partitionSettings []imagecustomizerapi.PartitionSetting, bootType imagecustomizerapi.BootType, buildDir string, - chrootDirName string, installOS installOSFunc, + partitionSettings []imagecustomizerapi.PartitionSetting, bootType imagecustomizerapi.BootType, + kernelCommandLine imagecustomizerapi.KernelCommandLine, buildDir string, chrootDirName string, + installOS installOSFunc, ) (*ImageConnection, error) { imageConnection := &ImageConnection{} - err := createNewImageHelper(imageConnection, filename, diskConfig, partitionSettings, bootType, buildDir, - chrootDirName, installOS, + err := createNewImageHelper(imageConnection, filename, diskConfig, partitionSettings, bootType, kernelCommandLine, + buildDir, chrootDirName, installOS, ) if err != nil { imageConnection.Close() @@ -75,8 +76,9 @@ func createNewImage(filename string, diskConfig imagecustomizerapi.Disk, } func createNewImageHelper(imageConnection *ImageConnection, filename string, diskConfig imagecustomizerapi.Disk, - partitionSettings []imagecustomizerapi.PartitionSetting, bootType imagecustomizerapi.BootType, buildDir string, - chrootDirName string, installOS installOSFunc, + partitionSettings []imagecustomizerapi.PartitionSetting, bootType imagecustomizerapi.BootType, + kernelCommandLine imagecustomizerapi.KernelCommandLine, buildDir string, chrootDirName string, + installOS installOSFunc, ) error { // Convert config to image config types, so that the imager's utils can be used. imagerBootType, err := bootTypeToImager(bootType) @@ -94,13 +96,18 @@ func createNewImageHelper(imageConnection *ImageConnection, filename string, dis return err } + imagerKernelCommandLine, err := kernelCommandLineToImager(kernelCommandLine) + if err != nil { + return err + } + // Sort the partitions so that they are mounted in the correct oder. sort.Slice(imagerPartitionSettings, func(i, j int) bool { return imagerPartitionSettings[i].MountPoint < imagerPartitionSettings[j].MountPoint }) // Create imager boilerplate. - mountPointMap, err := createImageBoilerplate(imageConnection, filename, buildDir, chrootDirName, imagerDiskConfig, + mountPointMap, tmpFstabFile, err := createImageBoilerplate(imageConnection, filename, buildDir, chrootDirName, imagerDiskConfig, imagerPartitionSettings) if err != nil { return err @@ -112,9 +119,17 @@ func createNewImageHelper(imageConnection *ImageConnection, filename string, dis return err } + // Move the fstab file into the image. + imageFstabFilePath := filepath.Join(imageConnection.Chroot().RootDir(), "etc/fstab") + + err = file.Move(tmpFstabFile, imageFstabFilePath) + if err != nil { + return fmt.Errorf("failed to move fstab into new image:\n%w", err) + } + // Configure the boot loader. err = installutils.ConfigureDiskBootloader(imagerBootType, false, false, imagerPartitionSettings, - configuration.KernelCommandLine{}, imageConnection.Chroot(), imageConnection.Loopback().DevicePath(), + imagerKernelCommandLine, imageConnection.Chroot(), imageConnection.Loopback().DevicePath(), mountPointMap, diskutils.EncryptedRootDevice{}, diskutils.VerityDevice{}) if err != nil { return fmt.Errorf("failed to install bootloader:\n%w", err) @@ -125,17 +140,17 @@ func createNewImageHelper(imageConnection *ImageConnection, filename string, dis func createImageBoilerplate(imageConnection *ImageConnection, filename string, buildDir string, chrootDirName string, imagerDiskConfig configuration.Disk, imagerPartitionSettings []configuration.PartitionSetting, -) (map[string]string, error) { +) (map[string]string, string, error) { // Create raw disk image file. err := diskutils.CreateSparseDisk(filename, imagerDiskConfig.MaxSize, 0o644) if err != nil { - return nil, fmt.Errorf("failed to create empty disk file (%s):\n%w", filename, err) + return nil, "", fmt.Errorf("failed to create empty disk file (%s):\n%w", filename, err) } // Connect raw disk image file. err = imageConnection.ConnectLoopback(filename) if err != nil { - return nil, err + return nil, "", err } // Set up partitions. @@ -143,13 +158,13 @@ func createImageBoilerplate(imageConnection *ImageConnection, filename string, b imageConnection.Loopback().DevicePath(), imagerDiskConfig, configuration.RootEncryption{}, configuration.ReadOnlyVerityRoot{}) if err != nil { - return nil, fmt.Errorf("failed to create partitions on disk (%s):\n%w", imageConnection.Loopback().DevicePath(), err) + return nil, "", fmt.Errorf("failed to create partitions on disk (%s):\n%w", imageConnection.Loopback().DevicePath(), err) } // Read the disk partitions. diskPartitions, err := diskutils.GetDiskPartitions(imageConnection.Loopback().DevicePath()) if err != nil { - return nil, err + return nil, "", err } // Create the fstab file. @@ -166,13 +181,13 @@ func createImageBoilerplate(imageConnection *ImageConnection, filename string, b mountPointToMountArgsMap, partIDToDevPathMap, partIDToFsTypeMap, false, /*hidepidEnabled*/ ) if err != nil { - return nil, fmt.Errorf("failed to write temp fstab file:\n%w", err) + return nil, "", fmt.Errorf("failed to write temp fstab file:\n%w", err) } // Read back the fstab file. mountPoints, err := findMountsFromFstabFile(tmpFstabFile, diskPartitions) if err != nil { - return nil, err + return nil, "", err } // Create chroot environment. @@ -180,16 +195,8 @@ func createImageBoilerplate(imageConnection *ImageConnection, filename string, b err = imageConnection.ConnectChroot(imageChrootDir, false, nil, mountPoints) if err != nil { - return nil, err + return nil, "", err } - // Move the fstab file into the image. - imageFstabFilePath := filepath.Join(imageConnection.Chroot().RootDir(), "etc/fstab") - - err = file.Move(tmpFstabFile, imageFstabFilePath) - if err != nil { - return nil, fmt.Errorf("failed to move fstab into new image:\n%w", err) - } - - return mountPointMap, nil + return mountPointMap, tmpFstabFile, nil } diff --git a/toolkit/tools/pkg/imagecustomizerlib/testdata/commandline-config.yaml b/toolkit/tools/pkg/imagecustomizerlib/testdata/commandline-config.yaml new file mode 100644 index 0000000000..9f72db0a5f --- /dev/null +++ b/toolkit/tools/pkg/imagecustomizerlib/testdata/commandline-config.yaml @@ -0,0 +1,3 @@ +SystemConfig: + KernelCommandLine: + ExtraCommandLine: console=tty0 console=ttyS0 diff --git a/toolkit/tools/pkg/imagecustomizerlib/testdata/partitions-config.yaml b/toolkit/tools/pkg/imagecustomizerlib/testdata/partitions-config.yaml index 922612591a..b2a3415294 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/testdata/partitions-config.yaml +++ b/toolkit/tools/pkg/imagecustomizerlib/testdata/partitions-config.yaml @@ -39,3 +39,6 @@ SystemConfig: - ID: var MountPoint: /var + + KernelCommandLine: + ExtraCommandLine: console=tty0 console=ttyS0 diff --git a/toolkit/tools/pkg/imagecustomizerlib/typeConversion.go b/toolkit/tools/pkg/imagecustomizerlib/typeConversion.go index 00305b536a..dd25028ad2 100644 --- a/toolkit/tools/pkg/imagecustomizerlib/typeConversion.go +++ b/toolkit/tools/pkg/imagecustomizerlib/typeConversion.go @@ -161,3 +161,11 @@ func mountIdentifierTypeToImager(mountIdentifierType imagecustomizerapi.MountIde return "", fmt.Errorf("unknwon MountIdentifierType value (%s)", mountIdentifierType) } } + +func kernelCommandLineToImager(kernelCommandLine imagecustomizerapi.KernelCommandLine, +) (configuration.KernelCommandLine, error) { + imagerKernelCommandLine := configuration.KernelCommandLine{ + ExtraCommandLine: kernelCommandLine.ExtraCommandLine, + } + return imagerKernelCommandLine, nil +}