From aa7fe628472f8efd79bd14111255496482d53a0e Mon Sep 17 00:00:00 2001 From: David Cassany Viladomat Date: Tue, 1 Feb 2022 14:17:28 +0100 Subject: [PATCH] Add --reboot and --poweroff flags (#66) Signed-off-by: David Cassany --- cmd/install.go | 8 ++++++-- pkg/action/action_test.go | 4 ++++ pkg/action/install.go | 11 +++++++++-- pkg/types/v1/config.go | 33 +++++++++++++++++++++++++++++---- pkg/utils/common.go | 16 ++++++++++++++++ pkg/utils/utils_test.go | 21 +++++++++++++++++++++ 6 files changed, 85 insertions(+), 8 deletions(-) diff --git a/cmd/install.go b/cmd/install.go index e2100433d..9c87b1043 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -46,7 +46,10 @@ var installCmd = &cobra.Command{ // Note that vars with ELEMENTAL in front and that match entries in the config (only one level deep) are overwritten automatically cfg.Target = args[0] - cfg.DigestSetup() + err = cfg.DigestSetup() + if err != nil { + return err + } cmd.SilenceUsage = true cfg.Logger.Infof("Install called") @@ -75,9 +78,10 @@ func init() { installCmd.Flags().BoolP("force-efi", "", false, "Forces an EFI installation") installCmd.Flags().BoolP("force-gpt", "", false, "Forces a GPT partition table") installCmd.Flags().BoolP("strict", "", false, "Enable strict check of hooks (They need to exit with 0)") - installCmd.Flags().BoolP("poweroff", "", false, "Shutdown the system after install") installCmd.Flags().BoolP("tty", "", false, "Add named tty to grub") installCmd.Flags().BoolP("force", "", false, "Force install") + installCmd.Flags().BoolP("reboot", "", false, "Reboot the system after install") + installCmd.Flags().BoolP("poweroff", "", false, "Shutdown the system after install") viper.BindPFlags(installCmd.Flags()) diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index d87ed737f..1d60668c7 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -125,7 +125,9 @@ var _ = Describe("Actions", func() { config.ActiveImage.Size = activeSize config.ActiveImage.RootTree = activeTree config.ActiveImage.MountPoint = activeMount + config.Reboot = true Expect(install.Run()).To(BeNil()) + Expect(runner.IncludesCmds([][]string{{"reboot", "-f"}})) }) It("Successfully installs despite hooks failure", func() { @@ -134,7 +136,9 @@ var _ = Describe("Actions", func() { config.ActiveImage.Size = activeSize config.ActiveImage.RootTree = activeTree config.ActiveImage.MountPoint = activeMount + config.PowerOff = true Expect(install.Run()).To(BeNil()) + Expect(runner.IncludesCmds([][]string{{"poweroff", "-f"}})) }) It("Successfully installs from ISO", func() { diff --git a/pkg/action/install.go b/pkg/action/install.go index 1fbd658de..ed878cdcb 100644 --- a/pkg/action/install.go +++ b/pkg/action/install.go @@ -190,7 +190,14 @@ func (i InstallAction) Run() (err error) { if err != nil { return err } - // profit! - // TODO poweroff or reboot or nothing + + // Reboot, poweroff or nothing + if i.Config.Reboot { + i.Config.Logger.Infof("Rebooting in 5 seconds") + return utils.Reboot(i.Config.Runner, 5) + } else if i.Config.PowerOff { + i.Config.Logger.Infof("Shutting down in 5 seconds") + return utils.Shutdown(i.Config.Runner, 5) + } return err } diff --git a/pkg/types/v1/config.go b/pkg/types/v1/config.go index d1e060e75..2c816437c 100644 --- a/pkg/types/v1/config.go +++ b/pkg/types/v1/config.go @@ -17,6 +17,7 @@ limitations under the License. package v1 import ( + "errors" "fmt" dockTypes "github.com/docker/docker/api/types" "github.com/mudler/luet/pkg/api/core/context" @@ -189,6 +190,8 @@ type RunConfig struct { NoVerify bool `yaml:"no-verify,omitempty" mapstructure:"no-verify"` CloudInitPaths string `yaml:"CLOUD_INIT_PATHS,omitempty" mapstructure:"CLOUD_INIT_PATHS"` GrubDefEntry string `yaml:"GRUB_ENTRY_NAME,omitempty" mapstructure:"GRUB_ENTRY_NAME"` + Reboot bool `yaml:"reboot,omitempty" mapstructure:"reboot"` + PowerOff bool `yaml:"poweroff,omitempty" mapstructure:"poweroff"` // Internally used to track stuff around PartTable string BootFlag string @@ -330,13 +333,30 @@ func (r *RunConfig) setupStyle() { r.Partitions = append(r.Partitions, part) } +// sanityChecks just verifies some potential inconsistencies on configuration +func (r RunConfig) sanityChecks() error { + err := errors.New("Invalid options") + if r.Reboot && r.PowerOff { + r.Logger.Errorf("'reboot' and 'poweroff' are mutually exclusive options") + return err + } + + if r.CosignPubKey != "" && !r.Cosign { + r.Logger.Errorf("'cosign-key' requires 'cosing' option to be enabled") + return err + } + + if r.Cosign && r.CosignPubKey == "" { + r.Logger.Warnf("No 'cosign-key' option set, keyless cosign verification is experimental") + } + + return nil +} + // setupLuet will initialize Luet interface if required func (r *RunConfig) setupLuet() { if r.DockerImg != "" { plugins := []string{} - if r.Cosign && r.CosignPubKey == "" { - r.Logger.Warnf("Keyless cosign verification is experimental, consider setting a public key") - } if !r.NoVerify { plugins = append(plugins, cnst.LuetMtreePlugin) } @@ -345,9 +365,14 @@ func (r *RunConfig) setupLuet() { } // DigestSetup will gather what partition table and bootflag we need for the current system -func (r *RunConfig) DigestSetup() { +func (r *RunConfig) DigestSetup() error { + err := r.sanityChecks() + if err != nil { + return err + } r.setupStyle() r.setupLuet() + return nil } // BuildConfig represents the config we need for building isos, raw images, artifacts diff --git a/pkg/utils/common.go b/pkg/utils/common.go index 0bd1a6636..a51e3a2ad 100644 --- a/pkg/utils/common.go +++ b/pkg/utils/common.go @@ -118,6 +118,22 @@ func SyncData(source string, target string, excludes ...string) error { return task.Run() } +// Reboot reboots the system afater the given delay (in seconds) time passed. +func Reboot(runner v1.Runner, delay time.Duration) error { + time.Sleep(delay * time.Second) + _, err := runner.Run("reboot", "-f") + return err +} + +// Shutdown halts the system afater the given delay (in seconds) time passed. +func Shutdown(runner v1.Runner, delay time.Duration) error { + time.Sleep(delay * time.Second) + _, err := runner.Run("poweroff", "-f") + return err +} + +// CosignVerify runs a cosign validation for the give image and given public key. If no +// key is provided then it attempts a keyless validation (experimental feature). func CosignVerify(fs afero.Fs, runner v1.Runner, image string, publicKey string, debug bool) (string, error) { args := []string{} diff --git a/pkg/utils/utils_test.go b/pkg/utils/utils_test.go index 6b9d7f5a3..000571ba6 100644 --- a/pkg/utils/utils_test.go +++ b/pkg/utils/utils_test.go @@ -31,6 +31,7 @@ import ( "io/ioutil" "os" "testing" + "time" ) func getNamesFromListFiles(list []os.FileInfo) []string { @@ -197,6 +198,26 @@ var _ = Describe("Utils", func() { Expect(err).NotTo(BeNil()) }) }) + Context("Reboot and shutdown", func() { + var runner *v1mock.TestRunnerV2 + BeforeEach(func() { + runner = v1mock.NewTestRunnerV2() + }) + It("reboots", func() { + start := time.Now() + utils.Reboot(runner, 2) + duration := time.Since(start) + Expect(runner.CmdsMatch([][]string{{"reboot", "-f"}})).To(BeNil()) + Expect(duration.Seconds() >= 2).To(BeTrue()) + }) + It("shuts down", func() { + start := time.Now() + utils.Shutdown(runner, 3) + duration := time.Since(start) + Expect(runner.CmdsMatch([][]string{{"poweroff", "-f"}})).To(BeNil()) + Expect(duration.Seconds() >= 3).To(BeTrue()) + }) + }) Context("CopyFile", func() { It("Copies source to target", func() { fs.Create("/some/file")