From d0f0710c78653dd6d52b5df31c17f788d918aea1 Mon Sep 17 00:00:00 2001 From: Itxaka Date: Fri, 25 Apr 2025 10:43:21 +0200 Subject: [PATCH] Use grub binaries and libs from rootfs (#760) --- internal/agent/hooks/finish.go | 6 +- internal/agent/hooks/gruboptions.go | 44 ++++++++++---- internal/agent/hooks/hook.go | 7 +-- pkg/action/bootentries.go | 2 +- pkg/action/bootentries_test.go | 2 +- pkg/action/install_test.go | 23 +++++++- pkg/action/reset_test.go | 3 +- pkg/elemental/elemental.go | 2 +- pkg/elemental/elemental_test.go | 6 +- pkg/utils/common.go | 24 ++++++-- pkg/utils/grub.go | 92 +++++++++++++++++++++++++---- pkg/utils/utils_test.go | 54 ++++++++++++++++- 12 files changed, 220 insertions(+), 45 deletions(-) diff --git a/internal/agent/hooks/finish.go b/internal/agent/hooks/finish.go index 739f3b5..4f478df 100644 --- a/internal/agent/hooks/finish.go +++ b/internal/agent/hooks/finish.go @@ -43,7 +43,11 @@ func (k Finish) Run(c config.Config, spec v1.Spec) error { c.Logger.Logger.Info().Msg("Finished encrypt hook") } - // Now that we have everything encrypted and ready if needed + // Now that we have everything encrypted and ready to mount if needed + err = GrubPostInstallOptions{}.Run(c, spec) + if err != nil { + return err + } err = BundlePostInstall{}.Run(c, spec) if err != nil { c.Logger.Logger.Warn().Err(err).Msg("could not copy run bundles post install") diff --git a/internal/agent/hooks/gruboptions.go b/internal/agent/hooks/gruboptions.go index 21b07ce..e727b3c 100644 --- a/internal/agent/hooks/gruboptions.go +++ b/internal/agent/hooks/gruboptions.go @@ -1,11 +1,13 @@ package hook import ( + "github.com/kairos-io/kairos-agent/v2/pkg/config" + cnst "github.com/kairos-io/kairos-agent/v2/pkg/constants" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" - "strings" - - config "github.com/kairos-io/kairos-agent/v2/pkg/config" - "github.com/kairos-io/kairos-sdk/system" + "github.com/kairos-io/kairos-agent/v2/pkg/utils" + "github.com/kairos-io/kairos-sdk/machine" + "github.com/kairos-io/kairos-sdk/state" + "path/filepath" ) type GrubOptions struct{} @@ -16,9 +18,9 @@ func (b GrubOptions) Run(c config.Config, _ v1.Spec) error { } c.Logger.Logger.Debug().Msg("Running GrubOptions hook") c.Logger.Debugf("Setting grub options: %s", c.Install.GrubOptions) - err := system.Apply(system.SetGRUBOptions(c.Install.GrubOptions)) - if err != nil && !strings.Contains(err.Error(), "0 errors occurred") { - c.Logger.Logger.Error().Err(err).Msg("Failed to set grub options") + err := grubOptions(c, c.Install.GrubOptions) + if err != nil { + return err } c.Logger.Logger.Debug().Msg("Finish GrubOptions hook") return nil @@ -31,10 +33,32 @@ func (b GrubPostInstallOptions) Run(c config.Config, _ v1.Spec) error { return nil } c.Logger.Logger.Debug().Msg("Running GrubOptions hook") - err := system.Apply(system.SetGRUBOptions(c.GrubOptions)) + c.Logger.Debugf("Setting grub options: %s", c.GrubOptions) + err := grubOptions(c, c.GrubOptions) + if err != nil { + return err + } + c.Logger.Logger.Debug().Msg("Finish GrubOptions hook") + return nil +} + +// grubOptions sets the grub options in the grubenv file +// It mounts the OEM partition if not already mounted +// If its mounted but RO, it remounts it as RW +func grubOptions(c config.Config, opts map[string]string) error { + runtime, err := state.NewRuntime() + if err != nil { + return err + } + if !runtime.OEM.Mounted { + err = machine.Mount(cnst.OEMLabel, cnst.OEMPath) + defer func() { + _ = machine.Umount(cnst.OEMPath) + }() + } + err = utils.SetPersistentVariables(filepath.Join(runtime.OEM.MountPoint, "grubenv"), opts, &c) if err != nil { c.Logger.Logger.Error().Err(err).Msg("Failed to set grub options") } - c.Logger.Logger.Debug().Msg("Running GrubOptions hook") - return nil + return err } diff --git a/internal/agent/hooks/hook.go b/internal/agent/hooks/hook.go index 416586e..8bc457c 100644 --- a/internal/agent/hooks/hook.go +++ b/internal/agent/hooks/hook.go @@ -2,7 +2,7 @@ package hook import ( "fmt" - config "github.com/kairos-io/kairos-agent/v2/pkg/config" + "github.com/kairos-io/kairos-agent/v2/pkg/config" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" "github.com/kairos-io/kairos-sdk/utils" "strings" @@ -15,8 +15,7 @@ type Interface interface { // FinishInstall is a list of hooks that run when the install process is finished completely. // Its mean for options that are not related to the install process itself var FinishInstall = []Interface{ - &GrubOptions{}, // Set custom GRUB options in OEM partition - &Lifecycle{}, // Handles poweroff/reboot by config options + &Lifecycle{}, // Handles poweroff/reboot by config options } // FinishReset is a list of hooks that run when the reset process is finished completely. @@ -46,7 +45,7 @@ var FinishUKIInstall = []Interface{ // PostInstall is a list of hooks that run after the install process has run. // Runs things that need to be done before we run other post install stages like // encrypting partitions, copying the install logs or installing bundles -// Most of this options are optional so they are not run by default unless specified int he config +// Most of this options are optional so they are not run by default unless specified in the config var PostInstall = []Interface{ &Finish{}, } diff --git a/pkg/action/bootentries.go b/pkg/action/bootentries.go index 0a8bc24..bcb0ca0 100644 --- a/pkg/action/bootentries.go +++ b/pkg/action/bootentries.go @@ -60,7 +60,7 @@ func selectBootEntryGrub(cfg *config.Config, entry string) error { vars := map[string]string{ "next_entry": entry, } - err = utils.SetPersistentVariables("/oem/grubenv", vars, cfg.Fs) + err = utils.SetPersistentVariables("/oem/grubenv", vars, cfg) if err != nil { cfg.Logger.Errorf("could not set default boot entry: %s\n", err) return err diff --git a/pkg/action/bootentries_test.go b/pkg/action/bootentries_test.go index ca8a8db..7a66cb5 100644 --- a/pkg/action/bootentries_test.go +++ b/pkg/action/bootentries_test.go @@ -694,7 +694,7 @@ var _ = Describe("Bootentries tests", Label("bootentry"), func() { err = SelectBootEntry(config, "kairos") Expect(err).ToNot(HaveOccurred()) Expect(memLog.String()).To(ContainSubstring("Default boot entry set to kairos")) - variables, err := utils.ReadPersistentVariables("/oem/grubenv", fs) + variables, err := utils.ReadPersistentVariables("/oem/grubenv", config) Expect(err).ToNot(HaveOccurred()) Expect(variables["next_entry"]).To(Equal("kairos")) }) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index 80d044a..030c489 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -28,7 +28,6 @@ import ( agentConfig "github.com/kairos-io/kairos-agent/v2/pkg/config" "github.com/kairos-io/kairos-agent/v2/pkg/constants" v1 "github.com/kairos-io/kairos-agent/v2/pkg/types/v1" - "github.com/kairos-io/kairos-agent/v2/pkg/utils" fsutils "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" v1mock "github.com/kairos-io/kairos-agent/v2/tests/mocks" "github.com/kairos-io/kairos-sdk/collector" @@ -150,6 +149,16 @@ var _ = Describe("Install action tests", func() { Expect(err).To(BeNil()) _, err = fs.Create(grubCfg) Expect(err).To(BeNil()) + // Create fake grub dir in rootfs and fake grub binaries + err = fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "sbin"), constants.DirPerm) + Expect(err).To(BeNil()) + f, err := fs.Create(filepath.Join(spec.Active.MountPoint, "sbin", "grub2-install")) + Expect(err).To(BeNil()) + Expect(f.Chmod(0755)).ToNot(HaveOccurred()) + err = fsutils.MkdirAll(fs, filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc"), constants.DirPerm) + Expect(err).To(BeNil()) + _, err = fs.Create(filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc", "modinfo.sh")) + Expect(err).To(BeNil()) mainDisk := sdkTypes.Disk{ Name: "device", @@ -349,9 +358,10 @@ var _ = Describe("Install action tests", func() { Expect(cl.WasGetCalledWith("http://my.config.org")).To(BeTrue()) }) - It("Fails on grub2-install errors", Label("grub"), func() { + It("Fails to find grub2-install", Label("grub"), func() { spec.Target = device - cmdFail = utils.FindCommand("grub2-install", []string{"grub2-install", "grub-install"}) + err := config.Fs.Remove(filepath.Join(spec.Active.MountPoint, "sbin", "grub2-install")) + Expect(err).To(BeNil()) Expect(installer.Run()).NotTo(BeNil()) Expect(runner.MatchMilestones([][]string{{"grub2-install"}})) }) @@ -362,5 +372,12 @@ var _ = Describe("Install action tests", func() { Expect(installer.Run()).NotTo(BeNil()) Expect(runner.MatchMilestones([][]string{{"tune2fs", "-L", constants.PassiveLabel}})) }) + It("Fails if there is no grub2 artifacts", Label("grub"), func() { + spec.Target = device + err := config.Fs.Remove(filepath.Join(spec.Active.MountPoint, "usr", "lib", "grub", "i386-pc", "modinfo.sh")) + Expect(err).To(BeNil()) + Expect(installer.Run()).NotTo(BeNil()) + Expect(runner.MatchMilestones([][]string{{"grub2-install"}})) + }) }) }) diff --git a/pkg/action/reset_test.go b/pkg/action/reset_test.go index 9ac0f97..5f2185c 100644 --- a/pkg/action/reset_test.go +++ b/pkg/action/reset_test.go @@ -130,7 +130,8 @@ var _ = Describe("Reset action tests", func() { ghwTest.AddDisk(mainDisk) ghwTest.CreateDevices() - fs.Create(constants.EfiDevice) + Expect(fsutils.MkdirAll(fs, constants.EfiDevice, constants.DirPerm)).ToNot(HaveOccurred()) + bootedFrom = constants.SystemLabel runner.SideEffect = func(cmd string, args ...string) ([]byte, error) { if cmd == cmdFail { diff --git a/pkg/elemental/elemental.go b/pkg/elemental/elemental.go index 6e7863e..cc2937a 100644 --- a/pkg/elemental/elemental.go +++ b/pkg/elemental/elemental.go @@ -584,7 +584,7 @@ func (e Elemental) SetDefaultGrubEntry(partMountPoint string, imgMountPoint stri return utils.SetPersistentVariables( filepath.Join(partMountPoint, cnst.GrubOEMEnv), map[string]string{"default_menu_entry": defaultEntry}, - e.config.Fs, + e.config, ) } diff --git a/pkg/elemental/elemental_test.go b/pkg/elemental/elemental_test.go index d553445..526bcfa 100644 --- a/pkg/elemental/elemental_test.go +++ b/pkg/elemental/elemental_test.go @@ -848,7 +848,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { el := elemental.NewElemental(config) Expect(config.Fs.Mkdir("/tmp", cnst.DirPerm)).To(BeNil()) Expect(el.SetDefaultGrubEntry("/tmp", "/imgMountpoint", "dio")).To(BeNil()) - varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config.Fs) + varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config) Expect(err).To(BeNil()) Expect(varsParsed["default_menu_entry"]).To(Equal("dio")) }) @@ -856,7 +856,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { el := elemental.NewElemental(config) Expect(config.Fs.Mkdir("/mountpoint", cnst.DirPerm)).To(BeNil()) Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil()) - _, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config.Fs) + _, err := utils.ReadPersistentVariables(filepath.Join("/tmp", cnst.GrubOEMEnv), config) // Because it didnt do anything due to the entry being empty, the file should not be there Expect(err).ToNot(BeNil()) _, err = config.Fs.Stat(filepath.Join("/tmp", cnst.GrubOEMEnv)) @@ -871,7 +871,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { el := elemental.NewElemental(config) Expect(el.SetDefaultGrubEntry("/mountpoint", "/imgMountPoint", "")).To(BeNil()) - varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/mountpoint", cnst.GrubOEMEnv), config.Fs) + varsParsed, err := utils.ReadPersistentVariables(filepath.Join("/mountpoint", cnst.GrubOEMEnv), config) Expect(err).To(BeNil()) Expect(varsParsed["default_menu_entry"]).To(Equal("test")) diff --git a/pkg/utils/common.go b/pkg/utils/common.go index 5fe67c0..d727bd2 100644 --- a/pkg/utils/common.go +++ b/pkg/utils/common.go @@ -503,11 +503,27 @@ func CalcFileChecksum(fs v1.FS, fileName string) (string, error) { // FindCommand will search for the command(s) in the options given to find the current command // If it cant find it returns the default value give. Useful for the same binaries with different names across OS -func FindCommand(defaultPath string, options []string) string { +func FindCommand(fs v1.FS, defaultPath string, options []string) string { for _, p := range options { - path, err := exec.LookPath(p) - if err == nil { - return path + // If its a full path, check if it exists directly + if strings.Contains(p, "/") { + d, err := fs.Stat(p) + if err != nil { + continue + } + if d.IsDir() { + continue + } + m := d.Mode() + // Check if its executable + if m&0111 != 0 { + return p + } + } else { + path, err := exec.LookPath(p) + if err == nil { + return path + } } } diff --git a/pkg/utils/grub.go b/pkg/utils/grub.go index 90b2da6..891a7ce 100644 --- a/pkg/utils/grub.go +++ b/pkg/utils/grub.go @@ -24,6 +24,7 @@ import ( "github.com/kairos-io/kairos-agent/v2/pkg/utils/fs" "github.com/kairos-io/kairos-sdk/utils" "io/fs" + "os" "path/filepath" "sort" "strings" @@ -61,16 +62,38 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, if !efi { g.config.Logger.Info("Installing GRUB..") + // Find where in the rootDir the grub2 files for i386-pc are + // Use the modinfo.sh as a marker + grubdir = findGrubDir(g.config.Fs, rootDir) + if grubdir == "" { + g.config.Logger.Logger.Error().Str("path", rootDir).Msg("Failed to find grub dir") + return fmt.Errorf("failed to find grub dir under %s", rootDir) + } + grubargs = append( grubargs, - fmt.Sprintf("--root-directory=%s", rootDir), + fmt.Sprintf("--directory=%s", grubdir), fmt.Sprintf("--boot-directory=%s", bootDir), "--target=i386-pc", target, ) g.config.Logger.Debugf("Running grub with the following args: %s", grubargs) - out, err := g.config.Runner.Run(FindCommand("grub2-install", []string{"grub2-install", "grub-install"}), grubargs...) + + grubBin := FindCommand(g.config.Fs, "", []string{ + filepath.Join(rootDir, "/usr/sbin/", "grub2-install"), + filepath.Join(rootDir, "/usr/bin/", "grub2-install"), + filepath.Join(rootDir, "/sbin/", "grub2-install"), + filepath.Join(rootDir, "/usr/sbin/", "grub-install"), + filepath.Join(rootDir, "/usr/bin/", "grub-install"), + filepath.Join(rootDir, "/sbin/", "grub-install"), + }) + g.config.Logger.Logger.Debug().Str("command", grubBin).Msg("Found grub binary") + if grubBin == "" { + g.config.Logger.Logger.Error().Str("path", rootDir).Msg("Grub binary not found in path") + return fmt.Errorf("grub binary not found in path") + } + out, err := g.config.Runner.Run(grubBin, grubargs...) if err != nil { g.config.Logger.Errorf(string(out)) return err @@ -257,20 +280,62 @@ func (g Grub) Install(target, rootDir, bootDir, grubConf, tty string, efi bool, return nil } -// SetPersistentVariables sets the given vars into the given grubEnvFile for grub to read them -func SetPersistentVariables(grubEnvFile string, vars map[string]string, fs v1.FS) error { +// findGrubDir will find the grub dir under the dir given if possible by searching for the modinfo.sh +// And it will return the full dir path where the modinfo.sh is contained +func findGrubDir(vfs v1.FS, dir string) string { + var foundPath string + _ = fsutils.WalkDirFs(vfs, dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.Name() == "modinfo.sh" && strings.Contains(path, "i386-pc") { + // We found the grub dir, return it + foundPath = filepath.Dir(path) + return nil + } + return nil + }) + + return foundPath + +} + +func SetPersistentVariables(grubEnvFile string, vars map[string]string, c *agentConfig.Config) error { var b bytes.Buffer + // Write header b.WriteString("# GRUB Environment Block\n") - keys := make([]string, 0, len(vars)) - for k := range vars { + // First we need to read the existing values from the grubenv file if they exist + finalVars, err := ReadPersistentVariables(grubEnvFile, c) + if err != nil { + if !os.IsNotExist(err) { + return fmt.Errorf("error reading existing grubenv file %s: %s", grubEnvFile, err) + } + } + // Check if we have a nil var + if len(finalVars) != 0 { + c.Logger.Logger.Debug().Interface("existingVars", finalVars).Msg("Existing grubenv variables") + } + + // Merge the existing vars with the new ones + // existing vars will be overridden by the new ones from vars if they match + for key, newValue := range vars { + if oldValue, exists := finalVars[key]; exists { + c.Logger.Logger.Warn().Str("key", key).Str("oldValue", oldValue).Str("newValue", newValue).Msg("Overriding existing grubenv variable") + } + finalVars[key] = newValue + } + + keys := make([]string, 0, len(finalVars)) + + for k := range finalVars { keys = append(keys, k) } sort.Strings(keys) for _, k := range keys { - if len(vars[k]) > 0 { - b.WriteString(fmt.Sprintf("%s=%s\n", k, vars[k])) + if len(finalVars[k]) > 0 { + b.WriteString(fmt.Sprintf("%s=%s\n", k, finalVars[k])) } } @@ -280,7 +345,7 @@ func SetPersistentVariables(grubEnvFile string, vars map[string]string, fs v1.FS for i := 0; i < toBeFilled; i++ { b.WriteByte('#') } - return fs.WriteFile(grubEnvFile, b.Bytes(), cnst.FilePerm) + return c.Fs.WriteFile(grubEnvFile, b.Bytes(), cnst.FilePerm) } // copyGrubFonts will try to finds and copy the needed grub fonts into the system @@ -397,11 +462,12 @@ func (g Grub) copyGrub() error { } // ReadPersistentVariables will read a grub env file and parse the values -func ReadPersistentVariables(grubEnvFile string, fs v1.FS) (map[string]string, error) { +func ReadPersistentVariables(grubEnvFile string, c *agentConfig.Config) (map[string]string, error) { vars := make(map[string]string) - f, err := fs.ReadFile(grubEnvFile) + + f, err := c.Fs.ReadFile(grubEnvFile) if err != nil { - return nil, err + return vars, err } for _, a := range strings.Split(string(f), "\n") { // comment or fillup, so skip @@ -412,7 +478,7 @@ func ReadPersistentVariables(grubEnvFile string, fs v1.FS) (map[string]string, e if len(splitted) == 2 { vars[splitted[0]] = splitted[1] } else { - return nil, fmt.Errorf("invalid format for %s", a) + return vars, fmt.Errorf("invalid format for %s", a) } } return vars, nil diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index ef130e7..de31419 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -691,6 +691,17 @@ var _ = Describe("Utils", Label("utils"), func() { err = fs.WriteFile(filepath.Join(rootDir, constants.GrubConf), []byte("console=tty1"), 0644) Expect(err).ShouldNot(HaveOccurred()) + // Create fake grub dir in rootfs and fake grub binaries + err = fsutils.MkdirAll(fs, filepath.Join(rootDir, "sbin"), constants.DirPerm) + Expect(err).To(BeNil()) + f, err := fs.Create(filepath.Join(rootDir, "sbin", "grub2-install")) + Expect(err).To(BeNil()) + Expect(f.Chmod(0755)).ToNot(HaveOccurred()) + err = fsutils.MkdirAll(fs, filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc"), constants.DirPerm) + Expect(err).To(BeNil()) + _, err = fs.Create(filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc", "modinfo.sh")) + Expect(err).To(BeNil()) + }) It("installs with default values", func() { grub := utils.NewGrub(config) @@ -741,6 +752,18 @@ var _ = Describe("Utils", Label("utils"), func() { Expect(err).To(BeNil()) }) + It("fails with bios if no grub2-install file exists", func() { + Expect(fs.RemoveAll(filepath.Join(rootDir, "sbin", "grub2-install"))).ToNot(HaveOccurred()) + grub := utils.NewGrub(config) + err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", false, "") + Expect(err).To(HaveOccurred()) + }) + It("fails with bios if no modules files exists", func() { + Expect(fs.RemoveAll(filepath.Join(rootDir, "usr", "lib", "grub", "i386-pc"))).ToNot(HaveOccurred()) + grub := utils.NewGrub(config) + err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", false, "") + Expect(err).To(HaveOccurred()) + }) It("fails with efi if no modules files exist", Label("efi"), func() { grub := utils.NewGrub(config) err := grub.Install(target, rootDir, bootDir, constants.GrubConf, "", true, "") @@ -804,9 +827,9 @@ var _ = Describe("Utils", Label("utils"), func() { defer os.Remove(temp.Name()) Expect(utils.SetPersistentVariables( temp.Name(), map[string]string{"key1": "value1", "key2": "value2"}, - config.Fs, + config, )).To(BeNil()) - readVars, err := utils.ReadPersistentVariables(temp.Name(), config.Fs) + readVars, err := utils.ReadPersistentVariables(temp.Name(), config) Expect(err).To(BeNil()) Expect(readVars["key1"]).To(Equal("value1")) Expect(readVars["key2"]).To(Equal("value2")) @@ -814,10 +837,35 @@ var _ = Describe("Utils", Label("utils"), func() { It("Fails setting variables", func() { e := utils.SetPersistentVariables( "badfilenopath", map[string]string{"key1": "value1"}, - config.Fs, + config, ) Expect(e).NotTo(BeNil()) }) + It("respects existing variables", func() { + temp, err := os.CreateTemp("", "grub-*") + Expect(err).ShouldNot(HaveOccurred()) + defer os.Remove(temp.Name()) + Expect(utils.SetPersistentVariables( + temp.Name(), map[string]string{"key1": "value1", "key2": "value2"}, + config, + )).To(BeNil()) + readVars, err := utils.ReadPersistentVariables(temp.Name(), config) + Expect(err).To(BeNil()) + Expect(readVars["key1"]).To(Equal("value1")) + Expect(readVars["key2"]).To(Equal("value2")) + + // Now we do it again with a different value + Expect(utils.SetPersistentVariables( + temp.Name(), map[string]string{"key1": "value3", "key3": "value4"}, + config, + )).To(BeNil()) + // Now there should be an extra key and key1 should be updated + readVars, err = utils.ReadPersistentVariables(temp.Name(), config) + Expect(err).To(BeNil()) + Expect(readVars["key1"]).To(Equal("value3")) + Expect(readVars["key2"]).To(Equal("value2")) + Expect(readVars["key3"]).To(Equal("value4")) + }) }) }) Describe("CreateSquashFS", Label("CreateSquashFS"), func() {