diff --git a/.github/workflows/build_and_test_x86.yaml b/.github/workflows/build_and_test_x86.yaml index 4dd143ffade..2ce6adff64d 100644 --- a/.github/workflows/build_and_test_x86.yaml +++ b/.github/workflows/build_and_test_x86.yaml @@ -95,6 +95,10 @@ jobs: sudo rm -rf /usr/local/lib/android # will release about 10 GB if you don't need Android sudo rm -rf /usr/share/dotnet # will release about 20GB if you don't need .NET sudo df -h + - if: ${{ steps.cache-check.outputs.cache-hit != 'true' }} + name: Build toolkit + run: | + make build - if: ${{ steps.cache-check.outputs.cache-hit != 'true' }} name: Install to disk run: | diff --git a/pkg/action/build-disk.go b/pkg/action/build-disk.go index c8bd463459c..556184ef23e 100644 --- a/pkg/action/build-disk.go +++ b/pkg/action/build-disk.go @@ -178,7 +178,7 @@ func (b *BuildDiskAction) BuildDiskRun() (err error) { //nolint:gocyclo } // Install grub - err = b.bootloader.InstallConfig(activeRoot, b.roots[constants.StatePartName]) + err = b.bootloader.InstallConfig(activeRoot, b.roots[constants.EfiPartName]) if err != nil { b.cfg.Logger.Errorf("failed installing grub configuration: %s", err.Error()) return err @@ -199,7 +199,7 @@ func (b *BuildDiskAction) BuildDiskRun() (err error) { //nolint:gocyclo grubVars := b.spec.GetGrubLabels() err = b.bootloader.SetPersistentVariables( - filepath.Join(b.roots[constants.StatePartName], constants.GrubOEMEnv), + filepath.Join(b.roots[constants.EfiPartName], constants.GrubOEMEnv), grubVars, ) if err != nil { @@ -208,7 +208,7 @@ func (b *BuildDiskAction) BuildDiskRun() (err error) { //nolint:gocyclo } err = b.bootloader.InstallEFI( - activeRoot, b.roots[constants.StatePartName], + activeRoot, b.roots[constants.EfiPartName], b.roots[constants.EfiPartName], b.spec.Partitions.State.FilesystemLabel, ) if err != nil { @@ -217,7 +217,7 @@ func (b *BuildDiskAction) BuildDiskRun() (err error) { //nolint:gocyclo } // Rebrand - err = b.bootloader.SetDefaultEntry(b.roots[constants.StatePartName], activeRoot, b.spec.GrubDefEntry) + err = b.bootloader.SetDefaultEntry(b.roots[constants.EfiPartName], activeRoot, b.spec.GrubDefEntry) if err != nil { return elementalError.NewFromError(err, elementalError.SetDefaultGrubEntry) } diff --git a/pkg/action/build-iso.go b/pkg/action/build-iso.go index b53f7b29909..d4acec12eeb 100644 --- a/pkg/action/build-iso.go +++ b/pkg/action/build-iso.go @@ -30,7 +30,7 @@ import ( ) const ( - grubPrefixDir = "/boot/grub2" + grubPrefixDir = "/EFI/BOOT" isoBootCatalog = "/boot/boot.catalog" ) @@ -73,7 +73,7 @@ func NewBuildISOAction(cfg *v1.BuildConfig, spec *v1.LiveISO, opts ...BuildISOAc } if b.bootloader == nil { - b.bootloader = bootloader.NewGrub(&cfg.Config, bootloader.WithGrubPrefix(grubPrefixDir)) + b.bootloader = bootloader.NewGrub(&cfg.Config) } return b diff --git a/pkg/action/install.go b/pkg/action/install.go index 4c6438352c7..e0912f145ea 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -197,8 +197,8 @@ func (i InstallAction) Run() (err error) { // Install grub err = i.bootloader.Install( cnst.WorkingImgDir, - i.spec.Partitions.State.MountPoint, - i.spec.Partitions.State.FilesystemLabel, + i.spec.Partitions.EFI.MountPoint, + i.spec.Partitions.EFI.FilesystemLabel, ) if err != nil { return elementalError.NewFromError(err, elementalError.InstallGrub) @@ -221,7 +221,7 @@ func (i InstallAction) Run() (err error) { grubVars := i.spec.GetGrubLabels() err = i.bootloader.SetPersistentVariables( - filepath.Join(i.spec.Partitions.State.MountPoint, cnst.GrubOEMEnv), + filepath.Join(i.spec.Partitions.EFI.MountPoint, cnst.GrubOEMEnv), grubVars, ) if err != nil { @@ -231,7 +231,7 @@ func (i InstallAction) Run() (err error) { // Installation rebrand (only grub for now) err = i.bootloader.SetDefaultEntry( - i.spec.Partitions.State.MountPoint, + i.spec.Partitions.EFI.MountPoint, cnst.WorkingImgDir, i.spec.GrubDefEntry, ) diff --git a/pkg/action/install_test.go b/pkg/action/install_test.go index cedccae4306..1210f179015 100644 --- a/pkg/action/install_test.go +++ b/pkg/action/install_test.go @@ -169,7 +169,7 @@ var _ = Describe("Install action tests", func() { { Name: "device1", FilesystemLabel: "COS_GRUB", - Type: "ext4", + Type: "vfat", }, { Name: "device2", @@ -388,7 +388,7 @@ var _ = Describe("Install action tests", func() { spec.GrubDefEntry = "cOS" bootloader.ErrorSetDefaultEntry = true Expect(installer.Run()).NotTo(BeNil()) - Expect(runner.MatchMilestones([][]string{{"grub2-editenv", filepath.Join(constants.StateDir, constants.GrubOEMEnv)}})) + Expect(runner.MatchMilestones([][]string{{"grub2-editenv", filepath.Join(constants.EfiDir, constants.GrubOEMEnv)}})) }) }) }) diff --git a/pkg/action/reset.go b/pkg/action/reset.go index 8eb43ba899b..10fa90eaf7a 100644 --- a/pkg/action/reset.go +++ b/pkg/action/reset.go @@ -205,8 +205,8 @@ func (r ResetAction) Run() (err error) { // install grub err = r.bootloader.Install( cnst.WorkingImgDir, - r.spec.Partitions.State.MountPoint, - r.spec.Partitions.State.FilesystemLabel, + r.spec.Partitions.EFI.MountPoint, + r.spec.Partitions.EFI.FilesystemLabel, ) if err != nil { @@ -242,7 +242,7 @@ func (r ResetAction) Run() (err error) { grubVars := r.spec.GetGrubLabels() err = r.bootloader.SetPersistentVariables( - filepath.Join(r.spec.Partitions.State.MountPoint, cnst.GrubOEMEnv), + filepath.Join(r.spec.Partitions.EFI.MountPoint, cnst.GrubOEMEnv), grubVars, ) if err != nil { @@ -252,7 +252,7 @@ func (r ResetAction) Run() (err error) { // installation rebrand (only grub for now) err = r.bootloader.SetDefaultEntry( - r.spec.Partitions.State.MountPoint, + r.spec.Partitions.EFI.MountPoint, cnst.WorkingImgDir, r.spec.GrubDefEntry, ) diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 5e6f028d21a..d59fea0bf9c 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -182,6 +182,11 @@ func (u *UpgradeAction) Run() (err error) { return elementalError.NewFromError(err, elementalError.MountRecoveryPartition) } cleanup.Push(umount) + umount, err = elemental.MountRWPartition(u.config.Config, u.spec.Partitions.EFI) + if err != nil { + return elementalError.NewFromError(err, elementalError.MountPartitions) + } + cleanup.Push(umount) // Cleanup transition image file before leaving cleanup.Push(func() error { return u.remove(upgradeImg.File) }) @@ -253,7 +258,7 @@ func (u *UpgradeAction) Run() (err error) { grubVars := u.spec.GetGrubLabels() err = u.bootloader.SetPersistentVariables( - filepath.Join(u.spec.Partitions.State.MountPoint, constants.GrubOEMEnv), + filepath.Join(u.spec.Partitions.EFI.MountPoint, constants.GrubOEMEnv), grubVars, ) if err != nil { @@ -265,7 +270,7 @@ func (u *UpgradeAction) Run() (err error) { if !u.spec.RecoveryUpgrade { u.Info("rebranding") - err = u.bootloader.SetDefaultEntry(u.spec.Partitions.State.MountPoint, constants.WorkingImgDir, u.spec.GrubDefEntry) + err = u.bootloader.SetDefaultEntry(u.spec.Partitions.EFI.MountPoint, constants.WorkingImgDir, u.spec.GrubDefEntry) if err != nil { u.Error("failed setting default entry") return elementalError.NewFromError(err, elementalError.SetDefaultGrubEntry) diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 5d0bc1ca620..16ef9816a7e 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -98,6 +98,7 @@ var _ = Describe("Runtime Actions", func() { logger.SetLevel(logrus.DebugLevel) // Create paths used by tests + utils.MkdirAll(fs, constants.EfiDir, constants.DirPerm) utils.MkdirAll(fs, fmt.Sprintf("%s/cOS", constants.RunningStateDir), constants.DirPerm) utils.MkdirAll(fs, fmt.Sprintf("%s/cOS", constants.LiveDir), constants.DirPerm) @@ -107,7 +108,8 @@ var _ = Describe("Runtime Actions", func() { { Name: "device1", FilesystemLabel: "COS_GRUB", - Type: "ext4", + Type: "vfat", + MountPoint: constants.EfiDir, }, { Name: "device2", @@ -337,7 +339,7 @@ var _ = Describe("Runtime Actions", func() { err := upgrade.Run() Expect(err).ToNot(HaveOccurred()) - actualBytes, err := fs.ReadFile(filepath.Join(constants.RunningStateDir, "grub_oem_env")) + actualBytes, err := fs.ReadFile(filepath.Join(constants.EfiDir, "grub_oem_env")) Expect(err).To(BeNil()) expected := map[string]string{ diff --git a/pkg/bootloader/bootloader_suite_test.go b/pkg/bootloader/bootloader_suite_test.go index 6257be87340..e192d5e21f6 100644 --- a/pkg/bootloader/bootloader_suite_test.go +++ b/pkg/bootloader/bootloader_suite_test.go @@ -25,5 +25,5 @@ import ( func TestTypes(t *testing.T) { RegisterFailHandler(Fail) - RunSpecs(t, "booloader type test suite") + RunSpecs(t, "bootloader type test suite") } diff --git a/pkg/bootloader/grub.go b/pkg/bootloader/grub.go index 063f2528192..dbb261187de 100644 --- a/pkg/bootloader/grub.go +++ b/pkg/bootloader/grub.go @@ -27,16 +27,16 @@ import ( "github.com/rancher/elemental-toolkit/pkg/utils" efilib "github.com/canonical/go-efilib" + eleefi "github.com/rancher/elemental-toolkit/pkg/efi" ) const ( - grubPrefix = "/grub2" grubCfgFile = "grub.cfg" +) - grubEFICfgTmpl = "search --no-floppy --label --set=root %s" + - "\nset prefix=($root)%s" + - "\nconfigfile $prefix/" + grubCfgFile +var ( + defaultGrubPrefixes = []string{"/EFI/ELEMENTAL", "/EFI/BOOT"} ) func getGModulePatterns(module string) []string { @@ -57,7 +57,7 @@ type Grub struct { grubEfiImg string mokMngr string - grubPrefix string + grubPrefixes []string configFile string elementalCfg string disableBootEntry bool @@ -80,8 +80,8 @@ func NewGrub(cfg *v1.Config, opts ...GrubOptions) *Grub { logger: cfg.Logger, runner: cfg.Runner, platform: cfg.Platform, - grubPrefix: grubPrefix, configFile: grubCfgFile, + grubPrefixes: defaultGrubPrefixes, elementalCfg: filepath.Join(constants.GrubCfgPath, constants.GrubCfg), clearBootEntry: true, secureBoot: secureBoot, @@ -107,7 +107,7 @@ func WithSecureBoot(secureboot bool) func(g *Grub) error { func WithGrubPrefix(prefix string) func(g *Grub) error { return func(g *Grub) error { - g.grubPrefix = prefix + g.grubPrefixes = append(g.grubPrefixes, prefix) return nil } } @@ -174,16 +174,18 @@ func (g *Grub) installModules(rootDir, bootDir string, modules ...string) error if err != nil { return err } - for _, module := range modules { - fileWriteName := filepath.Join(bootDir, g.grubPrefix, fmt.Sprintf("%s-efi", g.platform.Arch), filepath.Base(module)) - g.logger.Debugf("Copying %s to %s", module, fileWriteName) - err = utils.MkdirAll(g.fs, filepath.Dir(fileWriteName), constants.DirPerm) - if err != nil { - return fmt.Errorf("error creating destination folder: %v", err) - } - err = utils.CopyFile(g.fs, module, fileWriteName) - if err != nil { - return fmt.Errorf("error copying %s to %s: %s", module, fileWriteName, err.Error()) + for _, grubPrefix := range g.grubPrefixes { + for _, module := range modules { + fileWriteName := filepath.Join(bootDir, grubPrefix, fmt.Sprintf("%s-efi", g.platform.Arch), filepath.Base(module)) + g.logger.Debugf("Copying %s to %s", module, fileWriteName) + err = utils.MkdirAll(g.fs, filepath.Dir(fileWriteName), constants.DirPerm) + if err != nil { + return fmt.Errorf("error creating destination folder: %v", err) + } + err = utils.CopyFile(g.fs, module, fileWriteName) + if err != nil { + return fmt.Errorf("error copying %s to %s: %s", module, fileWriteName, err.Error()) + } } } return nil @@ -208,15 +210,15 @@ func (g *Grub) InstallEFI(rootDir, bootDir, efiDir, deviceLabel string) error { return nil } -func (g *Grub) InstallEFIFallbackBinaries(rootDir, efiDir, deviceLabel string) error { - return g.installEFIPartitionBinaries(rootDir, efiDir, constants.FallbackEFIPath, deviceLabel) +func (g *Grub) InstallEFIFallbackBinaries(rootDir, efiDir, _ string) error { + return g.installEFIPartitionBinaries(rootDir, efiDir, constants.FallbackEFIPath) } -func (g *Grub) InstallEFIElementalBinaries(rootDir, efiDir, deviceLabel string) error { - return g.installEFIPartitionBinaries(rootDir, efiDir, constants.EntryEFIPath, deviceLabel) +func (g *Grub) InstallEFIElementalBinaries(rootDir, efiDir, _ string) error { + return g.installEFIPartitionBinaries(rootDir, efiDir, constants.EntryEFIPath) } -func (g *Grub) installEFIPartitionBinaries(rootDir, efiDir, efiPath, deviceLabel string) error { +func (g *Grub) installEFIPartitionBinaries(rootDir, efiDir, efiPath string) error { err := g.findEFIImages(rootDir) if err != nil { return err @@ -274,12 +276,6 @@ func (g *Grub) installEFIPartitionBinaries(rootDir, efiDir, efiPath, deviceLabel return fmt.Errorf("failed copying %s to %s: %s", g.grubEfiImg, installPath, err.Error()) } - grubCfgContent := []byte(fmt.Sprintf(grubEFICfgTmpl, deviceLabel, g.grubPrefix)) - err = g.fs.WriteFile(filepath.Join(efiDir, efiPath, grubCfgFile), grubCfgContent, constants.FilePerm) - if err != nil { - return fmt.Errorf("error writing %s: %s", filepath.Join(efiDir, efiPath, grubCfgFile), err) - } - return nil } @@ -402,8 +398,8 @@ func (g *Grub) SetDefaultEntry(partMountPoint, imgMountPoint, defaultEntry strin } // Install installs grub into the device, copy the config file and add any extra TTY to grub -func (g *Grub) Install(rootDir, bootDir, stateLabel string) (err error) { - err = g.InstallEFI(rootDir, bootDir, constants.EfiDir, stateLabel) +func (g *Grub) Install(rootDir, bootDir, deviceLabel string) (err error) { + err = g.InstallEFI(rootDir, bootDir, constants.EfiDir, deviceLabel) if err != nil { return err } @@ -422,24 +418,29 @@ func (g *Grub) Install(rootDir, bootDir, stateLabel string) (err error) { return g.InstallConfig(rootDir, bootDir) } -// InstallConfig installs grub configuraton files to the expected location. rootDir is the root -// of the OS image, bootDir is the folder grub read the configuration from, usually state partition mountpoint +// InstallConfig installs grub configuraton files to the expected location. +// rootDir is the root of the OS image, bootDir is the folder grub read the +// configuration from, usually EFI partition mountpoint func (g Grub) InstallConfig(rootDir, bootDir string) error { - grubFile := filepath.Join(rootDir, g.elementalCfg) - dstGrubFile := filepath.Join(bootDir, g.grubPrefix, g.configFile) + for _, path := range []string{constants.FallbackEFIPath, constants.EntryEFIPath} { + grubFile := filepath.Join(rootDir, g.elementalCfg) + dstGrubFile := filepath.Join(bootDir, path, g.configFile) - g.logger.Infof("Using grub config file %s", grubFile) + g.logger.Infof("Using grub config file %s", grubFile) - // Create Needed dir under state partition to store the grub.cfg and any needed modules - err := utils.MkdirAll(g.fs, filepath.Join(bootDir, g.grubPrefix), constants.DirPerm) - if err != nil { - return fmt.Errorf("error creating grub dir: %s", err) - } + // Create Needed dir under state partition to store the grub.cfg and any needed modules + err := utils.MkdirAll(g.fs, filepath.Join(bootDir, path), constants.DirPerm) + if err != nil { + return fmt.Errorf("error creating grub dir: %s", err) + } - g.logger.Infof("Copying grub config file from %s to %s", grubFile, dstGrubFile) - err = utils.CopyFile(g.fs, grubFile, dstGrubFile) - if err != nil { - g.logger.Errorf("Failed copying grub config file: %s", err) + g.logger.Infof("Copying grub config file from %s to %s", grubFile, dstGrubFile) + err = utils.CopyFile(g.fs, grubFile, dstGrubFile) + if err != nil { + g.logger.Errorf("Failed copying grub config file: %s", err) + return err + } } - return err + + return nil } diff --git a/pkg/bootloader/grub_test.go b/pkg/bootloader/grub_test.go index 58f4dd21cca..a8d54edda03 100644 --- a/pkg/bootloader/grub_test.go +++ b/pkg/bootloader/grub_test.go @@ -24,6 +24,7 @@ import ( efi "github.com/canonical/go-efilib" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + "github.com/rancher/elemental-toolkit/cmd" "github.com/rancher/elemental-toolkit/pkg/bootloader" "github.com/rancher/elemental-toolkit/pkg/config" @@ -104,6 +105,7 @@ var _ = Describe("Booloader", Label("bootloader", "grub"), func() { config.WithLogger(logger), config.WithRunner(runner), config.WithFs(fs), + config.WithPlatform("linux/amd64"), ) }) @@ -111,15 +113,21 @@ var _ = Describe("Booloader", Label("bootloader", "grub"), func() { grub = bootloader.NewGrub(cfg, bootloader.WithGrubDisableBootEntry(true)) Expect(grub.Install(rootDir, bootDir, "DEVICE_LABEL")).To(Succeed()) - // Check everything is copied in boot directory - data, err := fs.ReadFile(fmt.Sprintf("%s/grub2/grub.cfg", bootDir)) + // Check grub config in EFI directory + data, err := fs.ReadFile(filepath.Join(bootDir, "/EFI/BOOT/grub.cfg")) + Expect(err).To(BeNil()) + Expect(data).To(Equal(grubCfg)) + + data, err = fs.ReadFile(filepath.Join(bootDir, "/EFI/ELEMENTAL/grub.cfg")) Expect(err).To(BeNil()) Expect(data).To(Equal(grubCfg)) - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/loopback.mod", bootDir)) + + // Check everything is copied in boot directory + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/loopback.mod")) Expect(err).To(BeNil()) - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/xzio.mod", bootDir)) + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/xzio.mod")) Expect(err).To(BeNil()) - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/squash4.mod", bootDir)) + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/squash4.mod")) Expect(err).To(BeNil()) // Check everything is copied in EFI directory @@ -137,19 +145,16 @@ var _ = Describe("Booloader", Label("bootloader", "grub"), func() { Expect(err).To(BeNil()) }) - It("installs just fine without sercure boot", func() { + It("installs just fine without secure boot", func() { grub = bootloader.NewGrub(cfg, bootloader.WithGrubDisableBootEntry(true), bootloader.WithSecureBoot(false)) Expect(grub.Install(rootDir, bootDir, "DEVICE_LABEL")).To(Succeed()) // Check everything is copied in boot directory - data, err := fs.ReadFile(fmt.Sprintf("%s/grub2/grub.cfg", bootDir)) + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/loopback.mod")) Expect(err).To(BeNil()) - Expect(data).To(Equal(grubCfg)) - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/loopback.mod", bootDir)) + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/xzio.mod")) Expect(err).To(BeNil()) - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/xzio.mod", bootDir)) - Expect(err).To(BeNil()) - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/squash4.mod", bootDir)) + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/squash4.mod")) Expect(err).To(BeNil()) // Check secureboot files are NOT there @@ -167,6 +172,15 @@ var _ = Describe("Booloader", Label("bootloader", "grub"), func() { Expect(err).To(BeNil()) _, err = fs.Stat(filepath.Join(constants.EfiDir, "EFI/ELEMENTAL/grub.efi")) Expect(err).To(BeNil()) + + // Check grub config in EFI directory + data, err := fs.ReadFile(filepath.Join(bootDir, "EFI/BOOT/grub.cfg")) + Expect(err).To(BeNil()) + Expect(data).To(Equal(grubCfg)) + + data, err = fs.ReadFile(filepath.Join(bootDir, "EFI/ELEMENTAL/grub.cfg")) + Expect(err).To(BeNil()) + Expect(data).To(Equal(grubCfg)) }) It("fails to install if squash4.mod is missing", func() { @@ -196,7 +210,7 @@ var _ = Describe("Booloader", Label("bootloader", "grub"), func() { Expect(grub.InstallConfig(rootDir, bootDir)).To(Succeed()) // Check everything is copied in boot directory - data, err := fs.ReadFile(fmt.Sprintf("%s/grub2/grub.cfg", bootDir)) + data, err := fs.ReadFile(filepath.Join(bootDir, "EFI/ELEMENTAL/grub.cfg")) Expect(err).To(BeNil()) Expect(data).To(Equal(grubCfg)) }) @@ -218,11 +232,11 @@ var _ = Describe("Booloader", Label("bootloader", "grub"), func() { Expect(grub.InstallEFI(rootDir, bootDir, efiDir, "DEVICE_LABEL")).To(Succeed()) // Check everything is copied in boot directory - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/loopback.mod", bootDir)) + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/loopback.mod")) Expect(err).To(BeNil()) - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/xzio.mod", bootDir)) + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/xzio.mod")) Expect(err).To(BeNil()) - _, err = fs.Stat(fmt.Sprintf("%s/grub2/x86_64-efi/squash4.mod", bootDir)) + _, err = fs.Stat(filepath.Join(bootDir, "EFI/BOOT/x86_64-efi/squash4.mod")) Expect(err).To(BeNil()) // Check everything is copied in EFI directory diff --git a/pkg/config/config.go b/pkg/config/config.go index 401f67099ce..f92fa348ec3 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -317,6 +317,9 @@ func NewInstallElementalPartitions() v1.ElementalPartitions { MountPoint: constants.PersistentDir, Flags: []string{}, } + + _ = partitions.SetFirmwarePartitions(v1.EFI, v1.GPT) + return partitions } diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go index ba95277dfcf..fdb9799001a 100644 --- a/pkg/config/config_test.go +++ b/pkg/config/config_test.go @@ -134,12 +134,6 @@ var _ = Describe("Types", Label("types", "config"), func() { Expect(spec.Recovery.Source.Value()).To(Equal(spec.Active.File)) Expect(spec.PartTable).To(Equal(v1.GPT)) - // No firmware partitions added yet - Expect(spec.Partitions.EFI).To(BeNil()) - - // Adding firmware partitions - err = spec.Partitions.SetFirmwarePartitions(spec.Firmware, spec.PartTable) - Expect(err).ShouldNot(HaveOccurred()) Expect(spec.Partitions.EFI).NotTo(BeNil()) }) It("sets installation defaults without being on installation media", Label("install"), func() { diff --git a/pkg/elemental/elemental.go b/pkg/elemental/elemental.go index 9098aaf1daf..0a3abc645ee 100644 --- a/pkg/elemental/elemental.go +++ b/pkg/elemental/elemental.go @@ -171,14 +171,14 @@ func MountRWPartition(c v1.Config, part *v1.Partition) (umount func() error, err if mnt, _ := IsMounted(c, part); mnt { err = MountPartition(c, part, "remount", "rw") if err != nil { - c.Logger.Errorf("failed mounting %s partition: %v", part.Name, err) + c.Logger.Errorf("Failed mounting %s partition: %s", part.Name, err.Error()) return nil, err } umount = func() error { return MountPartition(c, part, "remount", "ro") } } else { err = MountPartition(c, part, "rw") if err != nil { - c.Logger.Error("failed mounting %s partition: %v", part.Name, err) + c.Logger.Errorf("Failed mounting %s partition: %s", part.Name, err.Error()) return nil, err } umount = func() error { return UnmountPartition(c, part) } diff --git a/pkg/elemental/elemental_test.go b/pkg/elemental/elemental_test.go index 1963a02e5d9..61586fc7999 100644 --- a/pkg/elemental/elemental_test.go +++ b/pkg/elemental/elemental_test.go @@ -178,6 +178,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { _, err = fs.Create("/some/device") Expect(err).ToNot(HaveOccurred()) + parts.EFI.Path = "/dev/device1" parts.OEM.Path = "/dev/device2" parts.Recovery.Path = "/dev/device3" parts.State.Path = "/dev/device4" @@ -188,7 +189,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { err := elemental.MountPartitions(*config, parts.PartitionsByMountPoint(false)) Expect(err).To(BeNil()) lst, _ := mounter.List() - Expect(len(lst)).To(Equal(4)) + Expect(len(lst)).To(Equal(5)) }) It("Mounts disk partitions excluding recovery", func() { @@ -223,6 +224,7 @@ var _ = Describe("Elemental", Label("elemental"), func() { _, err = fs.Create("/some/device") Expect(err).ToNot(HaveOccurred()) + parts.EFI.Path = "/dev/device1" parts.OEM.Path = "/dev/device2" parts.Recovery.Path = "/dev/device3" parts.State.Path = "/dev/device4" diff --git a/pkg/features/embedded/cloud-config-defaults/system/oem/03_branding.yaml b/pkg/features/embedded/cloud-config-defaults/system/oem/03_branding.yaml index 11746a3a084..d8206fc5b6b 100644 --- a/pkg/features/embedded/cloud-config-defaults/system/oem/03_branding.yaml +++ b/pkg/features/embedded/cloud-config-defaults/system/oem/03_branding.yaml @@ -9,7 +9,7 @@ name: "Branding" stages: initramfs: - name: "Branding" - if: '[ ! -f "/run/elemental/recovery_mode" ]' + if: '[ -f "/run/elemental/active_mode" ]' hostname: "elemental" files: - path: /etc/issue @@ -27,6 +27,25 @@ stages: permissions: 0644 owner: 0 group: 0 + - name: "Branding" + if: '[ -f "/run/elemental/passive_mode" ]' + hostname: "elemental" + files: + - path: /etc/issue + content: | + .-----. + | .-. | + | |.| | + | `-' | + `-----' + + Welcome to \S (passive)! + IP address \4 + Login with user: root, password: cos + There might be an issue with the active partition, booted in passive. + permissions: 0644 + owner: 0 + group: 0 - name: "Branding recovery" if: '[ -f "/run/elemental/recovery_mode" ]' hostname: "elemental" diff --git a/pkg/features/embedded/cloud-config-essentials/system/oem/08_boot_assessment.yaml b/pkg/features/embedded/cloud-config-essentials/system/oem/08_boot_assessment.yaml index 954114aea6c..347f2632e06 100644 --- a/pkg/features/embedded/cloud-config-essentials/system/oem/08_boot_assessment.yaml +++ b/pkg/features/embedded/cloud-config-essentials/system/oem/08_boot_assessment.yaml @@ -21,10 +21,10 @@ stages: cat /proc/cmdline | grep -q "active" commands: - | - mount -o rw,remount /run/initramfs/elemental-state - grub2-editenv /run/initramfs/elemental-state/boot_assessment set enable_boot_assessment= - grub2-editenv /run/initramfs/elemental-state/boot_assessment set boot_assessment_tentative= - mount -o ro,remount /run/initramfs/elemental-state + mount -o rw,remount /run/elemental/efi + grub2-editenv /run/elemental/efi/boot_assessment set enable_boot_assessment= + grub2-editenv /run/elemental/efi/boot_assessment set boot_assessment_tentative= + mount -o ro,remount /run/elemental/efi - name: "Create upgrade failure sentinel if necessary" if: | cat /proc/cmdline | grep -q "upgrade_failure" @@ -35,30 +35,29 @@ stages: owner: 0 group: 0 after-install: - # After install, reset, and upgrade, we install additional GRUB configuration for boot assessment into COS_STATE. + # After install, reset, and upgrade, we install additional GRUB configuration for boot assessment into COS_GRUB. - - &statemount - name: "Mount state" + - &efimount + name: "Mount efi" commands: - | - STATEDIR=/tmp/mnt/STATE - STATE=$(blkid -L COS_STATE || true) - mkdir -p $STATEDIR || true - mount ${STATE} $STATEDIR + EFIDIR=/tmp/mnt/EFI + EFI=$(blkid -L COS_GRUB || true) + mkdir -p $EFIDIR || true + mount ${EFI} $EFIDIR # Here we hook the boot assessment configuration to 'grubcustom' # we do that selectively in order to just "append" eventual other configuration provided. # XXX: maybe we should just write to /grubcustom and override any other custom grub? - &customhook name: "Hook boot assessment grub configuration" if: | - ! grep -q "grub_boot_assessment" /tmp/mnt/STATE/grubcustom + ! grep -q "grub_boot_assessment" /tmp/mnt/EFI/grubcustom commands: - | - cat << 'EOF' >> /tmp/mnt/STATE/grubcustom + cat << 'EOF' >> /tmp/mnt/EFI/grubcustom set bootfile="/grub_boot_assessment" - search --no-floppy --file --set=bootfile_loc "${bootfile}" - if [ "${bootfile_loc}" ]; then - source "(${bootfile_loc})${bootfile}" + if [ "${bootfile}" ]; then + source "${bootfile}" fi EOF # Overrides the active cmdline by adding "rd.emergency=reboot", "rd.shell=0" and "panic=5" @@ -72,10 +71,10 @@ stages: - &bootgrub name: "Add boot assessment grub configuration" files: - - path: "/tmp/mnt/STATE/grub_boot_assessment" + - path: "/tmp/mnt/EFI/grub_boot_assessment" owner: 0 group: 0 - permsisions: 0600 + permissions: 0600 content: | set extra_active_cmdline="rd.emergency=reboot rd.shell=0 panic=5 systemd.crash_reboot systemd.crash_shell=0" set boot_assessment_file="/boot_assessment" @@ -93,28 +92,33 @@ stages: fi fi fi - - &stateumount - name: "umount state" + - &efiumount + name: "umount EFI" commands: - | - umount /tmp/mnt/STATE + umount /tmp/mnt/EFI # Here we do enable boot assessment for the next bootup. # Similarly, we could trigger boot assessment in other cases after-upgrade: - - <<: *statemount + - <<: *efimount - name: "Set upgrade sentinel" commands: - | - grub2-editenv /tmp/mnt/STATE/boot_assessment set enable_boot_assessment=yes + grub2-editenv /tmp/mnt/EFI/boot_assessment set enable_boot_assessment=yes # We do re-install hooks here if needed to track upgrades of boot assessment - <<: *customhook - <<: *bootgrub - - <<: *stateumount + - <<: *efiumount after-reset: - - <<: *statemount + - <<: *efimount + - name: "Remove GRUB sentinels" + commands: + - | + grub2-editenv /tmp/mnt/EFI/boot_assessment set enable_boot_assessment= + grub2-editenv /tmp/mnt/EFI/boot_assessment set boot_assessment_tentative= # Reset completely restores COS_STATE, so we re-inject ourselves - <<: *customhook - <<: *bootgrub - - <<: *stateumount + - <<: *efiumount diff --git a/pkg/features/embedded/grub-config/etc/elemental/grub.cfg b/pkg/features/embedded/grub-config/etc/elemental/grub.cfg index 4d1ed6a88d4..a02afa377f7 100644 --- a/pkg/features/embedded/grub-config/etc/elemental/grub.cfg +++ b/pkg/features/embedded/grub-config/etc/elemental/grub.cfg @@ -46,6 +46,7 @@ insmod squash4 set loopdev="loop0" menuentry "${display_name}" --id cos { + search --no-floppy --label --set=root $state_label # label is kept around for backward compatibility set label=${active_label} set img=/cOS/active.img @@ -58,6 +59,7 @@ menuentry "${display_name}" --id cos { } menuentry "${display_name} (fallback)" --id fallback { + search --no-floppy --label --set=root $state_label # label is kept around for backward compatibility set label=${passive_label} set img=/cOS/passive.img diff --git a/pkg/types/v1/bootloader.go b/pkg/types/v1/bootloader.go index d1ffd5eb7e6..5e9edee53b1 100644 --- a/pkg/types/v1/bootloader.go +++ b/pkg/types/v1/bootloader.go @@ -17,7 +17,7 @@ limitations under the License. package v1 type Bootloader interface { - Install(rootDir, bootDir, stateLabel string) (err error) + Install(rootDir, bootDir, deviceLabel string) (err error) InstallConfig(rootDir, bootDir string) error DoEFIEntries(shimName, efiDir string) error InstallEFI(rootDir, bootDir, efiDir, deviceLabel string) error diff --git a/pkg/types/v1/config_test.go b/pkg/types/v1/config_test.go index 63defa849ee..fb8355ee806 100644 --- a/pkg/types/v1/config_test.go +++ b/pkg/types/v1/config_test.go @@ -380,7 +380,7 @@ var _ = Describe("Types", Label("types", "config"), func() { }) Describe("sanitize", func() { It("runs method", func() { - Expect(spec.Partitions.EFI).To(BeNil()) + Expect(spec.Partitions.EFI).ToNot(BeNil()) Expect(spec.Active.Source.IsEmpty()).To(BeTrue()) // Creates firmware partitions diff --git a/tests/fallback/fallback_test.go b/tests/fallback/fallback_test.go index 3aca45e67c1..47735e67d75 100644 --- a/tests/fallback/fallback_test.go +++ b/tests/fallback/fallback_test.go @@ -32,10 +32,10 @@ var _ = Describe("Elemental booting fallback tests", func() { var s *sut.SUT bootAssessmentInstalled := func() { // Auto assessment was installed - out, _ := s.Command("sudo cat /run/initramfs/elemental-state/grubcustom") - Expect(out).To(ContainSubstring("bootfile_loc")) + out, _ := s.Command("sudo cat /run/elemental/efi/grubcustom") + Expect(out).To(ContainSubstring("bootfile")) - out, _ = s.Command("sudo cat /run/initramfs/elemental-state/grub_boot_assessment") + out, _ = s.Command("sudo cat /run/elemental/efi/grub_boot_assessment") Expect(out).To(ContainSubstring("boot_assessment_file")) cmdline, _ := s.Command("sudo cat /proc/cmdline") @@ -69,7 +69,7 @@ var _ = Describe("Elemental booting fallback tests", func() { Expect(err).ToNot(HaveOccurred(), out) Expect(out).Should(ContainSubstring("Upgrade completed")) - out, _ = s.Command("sudo cat /run/initramfs/elemental-state/boot_assessment") + out, _ = s.Command("sudo cat /run/elemental/efi/boot_assessment") Expect(out).To(ContainSubstring("enable_boot_assessment=yes")) // Break the upgrade diff --git a/tests/recovery/recovery_test.go b/tests/recovery/recovery_test.go index 130e92dfe9b..2e84ad61f3f 100644 --- a/tests/recovery/recovery_test.go +++ b/tests/recovery/recovery_test.go @@ -50,7 +50,7 @@ var _ = Describe("Elemental Recovery upgrade tests", func() { Expect(s.ChangeBoot(sut.Recovery)).To(Succeed()) s.Reboot() - Expect(s.BootFrom()).To(Equal(sut.Recovery)) + s.EventuallyBootedFrom(sut.Recovery) By(fmt.Sprintf("upgrading to %s", comm.UpgradeImage())) @@ -66,7 +66,7 @@ var _ = Describe("Elemental Recovery upgrade tests", func() { Expect(err).ToNot(HaveOccurred()) s.Reboot() - ExpectWithOffset(1, s.BootFrom()).To(Equal(sut.Active)) + s.EventuallyBootedFrom(sut.Active) upgradedVersion := s.GetOSRelease("TIMESTAMP") Expect(upgradedVersion).ToNot(Equal(currentVersion)) @@ -94,11 +94,11 @@ var _ = Describe("Elemental Recovery upgrade tests", func() { // TODO: verify state.yaml matches expectations s.Reboot() - Expect(s.BootFrom()).To(Equal(sut.Recovery)) + s.EventuallyBootedFrom(sut.Recovery) By("rebooting back to active") s.Reboot() - Expect(s.BootFrom()).To(Equal(sut.Active)) + s.EventuallyBootedFrom(sut.Active) }) }) }) diff --git a/tests/upgrade/upgrade_test.go b/tests/upgrade/upgrade_test.go index f62b3db94e5..d54ddd57eb6 100644 --- a/tests/upgrade/upgrade_test.go +++ b/tests/upgrade/upgrade_test.go @@ -32,7 +32,7 @@ var _ = Describe("Elemental Feature tests", func() { BeforeEach(func() { s = sut.NewSUT() s.EventuallyConnects() - Expect(s.BootFrom()).To(Equal(sut.Active)) + s.EventuallyBootedFrom(sut.Active) }) Context("After install", func() { @@ -51,7 +51,7 @@ var _ = Describe("Elemental Feature tests", func() { Expect(out).Should(ContainSubstring("Upgrade completed")) s.Reboot() - Expect(s.BootFrom()).To(Equal(sut.Active)) + s.EventuallyBootedFrom(sut.Active) currentVersion := s.GetOSRelease("TIMESTAMP") Expect(currentVersion).NotTo(Equal(originalVersion)) diff --git a/tests/vm/sut.go b/tests/vm/sut.go index b3cc1d5e550..d5fd2c69688 100644 --- a/tests/vm/sut.go +++ b/tests/vm/sut.go @@ -188,7 +188,7 @@ func (s *SUT) Reset() { err := s.ChangeBootOnce(Recovery) Expect(err).ToNot(HaveOccurred()) s.Reboot() - Expect(s.BootFrom()).To(Equal(Recovery)) + s.EventuallyBootedFrom(Recovery) } By("Running elemental reset") @@ -220,6 +220,17 @@ func (s *SUT) BootFrom() string { } } +func (s *SUT) EventuallyBootedFrom(image string) { + Eventually(func() error { + actual := s.BootFrom() + if actual != image { + return fmt.Errorf("Expected boot from %s, actual %s", image, actual) + } + + return nil + }, time.Duration(60)*time.Second, time.Duration(10)*time.Second).ShouldNot(HaveOccurred()) +} + func (s *SUT) GetOSRelease(ss string) string { out, err := s.Command(fmt.Sprintf("source /etc/os-release && echo $%s", ss)) Expect(err).ToNot(HaveOccurred())