Skip to content

Commit

Permalink
cli: Add exit-codes to install command (rancher#374)
Browse files Browse the repository at this point in the history
cli: Add exit-codes to install command

Fixes rancher#365

* Refactor power-action for upgrade and install because of gocyclo lint
* Refactor build-disk to use regular error

Signed-off-by: Fredrik Lönnegren <[email protected]>
  • Loading branch information
frelon authored Nov 15, 2022
1 parent 3b2e929 commit 1ad9686
Show file tree
Hide file tree
Showing 12 changed files with 160 additions and 95 deletions.
1 change: 0 additions & 1 deletion cmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,6 @@ func ReadUpgradeSpec(r *v1.RunConfig, flags *pflag.FlagSet) (*v1.UpgradeSpec, er
err = vp.Unmarshal(upgrade, setDecoder, decodeHook)
if err != nil {
r.Logger.Warnf("error unmarshalling UpgradeSpec: %s", err)
return nil, err
}
err = upgrade.Sanitize()
r.Logger.Debugf("Loaded upgrade UpgradeSpec: %s", litter.Sdump(upgrade))
Expand Down
16 changes: 7 additions & 9 deletions cmd/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package cmd

import (
"errors"
"os/exec"

"github.com/spf13/cobra"
Expand All @@ -26,6 +25,7 @@ import (

"github.com/rancher/elemental-cli/cmd/config"
"github.com/rancher/elemental-cli/pkg/action"
elementalError "github.com/rancher/elemental-cli/pkg/error"
v1 "github.com/rancher/elemental-cli/pkg/types/v1"
)

Expand Down Expand Up @@ -53,10 +53,12 @@ func NewInstallCmd(root *cobra.Command, addCheckRoot bool) *cobra.Command {
cfg, err := config.ReadConfigRun(viper.GetString("config-dir"), cmd.Flags(), mounter)
if err != nil {
cfg.Logger.Errorf("Error reading config: %s\n", err)
return elementalError.NewFromError(err, elementalError.ReadingRunConfig)
}

if err := validateInstallUpgradeFlags(cfg.Logger, cmd.Flags()); err != nil {
return err
cfg.Logger.Errorf("Error reading install/upgrade flags: %s\n", err)
return elementalError.NewFromError(err, elementalError.ReadingInstallUpgradeFlags)
}

// Manage deprecated flags
Expand All @@ -70,24 +72,20 @@ func NewInstallCmd(root *cobra.Command, addCheckRoot bool) *cobra.Command {
spec, err := config.ReadInstallSpec(cfg, cmd.Flags())
if err != nil {
cfg.Logger.Errorf("invalid install command setup %v", err)
return err
return elementalError.NewFromError(err, elementalError.ReadingSpecConfig)
}

if len(args) == 1 {
spec.Target = args[0]
}

if spec.Target == "" {
return errors.New("at least a target device must be supplied")
return elementalError.New("at least a target device must be supplied", elementalError.InvalidTarget)
}

cfg.Logger.Infof("Install called")
install := action.NewInstallAction(cfg, spec)
err = install.Run()
if err != nil {
return err
}
return nil
return install.Run()
},
}
firmType := newEnumFlag([]string{v1.EFI, v1.BIOS}, v1.EFI)
Expand Down
11 changes: 11 additions & 0 deletions cmd/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/spf13/viper"

elementalError "github.com/rancher/elemental-cli/pkg/error"
)

var _ = Describe("Install", Label("install", "cmd"), func() {
Expand All @@ -47,12 +49,21 @@ var _ = Describe("Install", Label("install", "cmd"), func() {
Expect(err).ToNot(BeNil())
Expect(buf.String()).To(ContainSubstring("Usage:"))
Expect(err.Error()).To(ContainSubstring("flags docker-image, directory and system are mutually exclusive"))
Expect(err.(*elementalError.ElementalError)).ToNot(BeNil())
Expect(err.(*elementalError.ElementalError).ExitCode()).To(Equal(elementalError.ReadingInstallUpgradeFlags))
})
It("Errors out if no installation source is defined", Label("args"), func() {
_, _, err := executeCommandC(rootCmd, "install")
Expect(err).ToNot(BeNil())
Expect(buf.String()).To(ContainSubstring("undefined system source to install"))
})
It("Errors out if no installation target is defined", Label("args"), func() {
_, _, err := executeCommandC(rootCmd, "install", "--directory", "dir")
Expect(err).ToNot(BeNil())
Expect(buf.String()).To(ContainSubstring("at least a target device must be supplied"))
Expect(err.(*elementalError.ElementalError)).ToNot(BeNil())
Expect(err.(*elementalError.ElementalError).ExitCode()).To(Equal(elementalError.InvalidTarget))
})
It("Errors out setting reboot and poweroff at the same time", Label("flags"), func() {
_, _, err := executeCommandC(rootCmd, "install", "--reboot", "--poweroff", "/dev/whatever")
Expect(err).ToNot(BeNil())
Expand Down
2 changes: 1 addition & 1 deletion cmd/upgrade.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ func NewUpgradeCmd(root *cobra.Command, addCheckRoot bool) *cobra.Command {
spec, err := config.ReadUpgradeSpec(cfg, cmd.Flags())
if err != nil {
cfg.Logger.Errorf("Invalid upgrade command setup %v", err)
return elementalError.NewFromError(err, elementalError.ReadingRunConfig)
return elementalError.NewFromError(err, elementalError.ReadingSpecConfig)
}

cfg.Logger.Infof("Upgrade called")
Expand Down
15 changes: 13 additions & 2 deletions docs/elemental_exit-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
| 30 | Error truncating a file|
| 31 | Error reading the run config|
| 32 | Error reading the install/upgrade flags|
| 33 | Error reading the upgrade config|
| 33 | Error reading the config for the command|
| 34 | Error mounting state partition|
| 35 | Error mounting recovery partition|
| 36 | Error during before-upgrade hook|
Expand All @@ -37,8 +37,19 @@
| 41 | Error occurred during cleanup|
| 42 | Error occurred trying to reboot|
| 43 | Error occurred trying to shutdown|
| 44 | Error occurred when unmounting image|
| 44 | Error occurred when labeling partition|
| 44 | Error occurred when unmounting image|
| 45 | Error setting default grub entry|
| 46 | Error occurred during selinux relabeling|
| 47 | Error invalid device specified|
| 48 | Error deploying image to file|
| 49 | Error installing GRUB|
| 50 | Error during before-install hook|
| 51 | Error during after-install hook|
| 52 | Error during after-install-chroot hook|
| 53 | Error during file download|
| 54 | Error mounting partitions|
| 55 | Error deactivating active devices|
| 56 | Error during device partitioning|
| 57 | Device already contains an install|
| 255 | Unknown error|
60 changes: 28 additions & 32 deletions pkg/action/build-disk.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const (
GB = 1024 * MB
)

func BuildDiskRun(cfg *v1.BuildConfig, spec *v1.RawDiskArchEntry, imgType string, oemLabel string, recoveryLabel string, output string) (elementalError *eleError.ElementalError) {
func BuildDiskRun(cfg *v1.BuildConfig, spec *v1.RawDiskArchEntry, imgType string, oemLabel string, recoveryLabel string, output string) error {
cfg.Logger.Infof("Building disk image type %s for arch %s", imgType, cfg.Arch)

if len(spec.Packages) == 0 {
Expand Down Expand Up @@ -105,31 +105,31 @@ func BuildDiskRun(cfg *v1.BuildConfig, spec *v1.RawDiskArchEntry, imgType string
}

// Create rootfs.part
elementalError = CreatePart(
err = CreatePart(
cfg,
rootfsPart,
filepath.Join(baseDir, "root"),
recoveryLabel,
constants.LinuxImgFs,
2048*MB,
)
if elementalError != nil {
cfg.Logger.Error(elementalError)
return elementalError
if err != nil {
cfg.Logger.Error(err)
return err
}

// create EFI part
elementalError = CreatePart(
err = CreatePart(
cfg,
efiPart,
"",
constants.EfiLabel,
constants.EfiFs,
20*MB,
)
if elementalError != nil {
cfg.Logger.Error(elementalError)
return elementalError
if err != nil {
cfg.Logger.Error(err)
return err
}
// copy files to efi with mcopy
_, err = cfg.Runner.Run("mcopy", "-s", "-i", efiPart, filepath.Join(baseDir, "efi", "EFI"), "::EFI")
Expand All @@ -144,54 +144,50 @@ func BuildDiskRun(cfg *v1.BuildConfig, spec *v1.RawDiskArchEntry, imgType string
if err != nil {
return eleError.NewFromError(err, eleError.CopyFile)
}
elementalError = CreatePart(
err = CreatePart(
cfg,
oemPart,
filepath.Join(baseDir, "oem"),
oemLabel,
constants.LinuxImgFs,
64*MB,
)
if elementalError != nil {
cfg.Logger.Error(elementalError)
return elementalError
if err != nil {
cfg.Logger.Error(err)
return err
}

// Create final image
elementalError = CreateFinalImage(cfg, output, efiPart, oemPart, rootfsPart)
if elementalError != nil {
cfg.Logger.Error(elementalError)
return elementalError
err = CreateFinalImage(cfg, output, efiPart, oemPart, rootfsPart)
if err != nil {
cfg.Logger.Error(err)
return err
}

switch imgType {
case "raw":
// Nothing to do here
cfg.Logger.Infof("Done! Image created at %s", output)
case "azure":
elementalError = Raw2Azure(output, cfg.Fs, cfg.Logger, false)
if elementalError != nil {
return elementalError
err = Raw2Azure(output, cfg.Fs, cfg.Logger, false)
if err != nil {
return err
}
cfg.Logger.Infof("Done! Image created at %s", fmt.Sprintf("%s.vhd", output))
case "gce":
elementalError = Raw2Gce(output, cfg.Fs, cfg.Logger, false)
if elementalError != nil {
return elementalError
err = Raw2Gce(output, cfg.Fs, cfg.Logger, false)
if err != nil {
return err
}
cfg.Logger.Infof("Done! Image created at %s", fmt.Sprintf("%s.tar.gz", output))
}

// Workaround for the cleanup still using the normal error
if err != nil {
elementalError = eleError.NewFromError(err, eleError.Unknown)
}
return elementalError
return eleError.NewFromError(err, eleError.Unknown)
}

// Raw2Gce transforms an image from RAW format into GCE format
// THIS REMOVES THE SOURCE IMAGE BY DEFAULT
func Raw2Gce(source string, fs v1.FS, logger v1.Logger, keepOldImage bool) *eleError.ElementalError {
func Raw2Gce(source string, fs v1.FS, logger v1.Logger, keepOldImage bool) error {
// The RAW image file must have a size in an increment of 1 GB. For example, the file must be either 10 GB or 11 GB but not 10.5 GB.
// The disk image filename must be disk.raw.
// The compressed file must be a .tar.gz file that uses gzip compression and the --format=oldgnu option for the tar utility.
Expand Down Expand Up @@ -263,7 +259,7 @@ func Raw2Gce(source string, fs v1.FS, logger v1.Logger, keepOldImage bool) *eleE

// Raw2Azure transforms an image from RAW format into Azure format
// THIS REMOVES THE SOURCE IMAGE BY DEFAULT
func Raw2Azure(source string, fs v1.FS, logger v1.Logger, keepOldImage bool) *eleError.ElementalError {
func Raw2Azure(source string, fs v1.FS, logger v1.Logger, keepOldImage bool) error {
// All VHDs on Azure must have a virtual size aligned to 1 MB (1024 × 1024 bytes)
// The Hyper-V virtual hard disk (VHDX) format isn't supported in Azure, only fixed VHD
logger.Info("Transforming raw image into azure format")
Expand Down Expand Up @@ -301,7 +297,7 @@ func Raw2Azure(source string, fs v1.FS, logger v1.Logger, keepOldImage bool) *el

// CreateFinalImage creates the final image by truncating the image with the proper sizes, concatenating the contents of the
// given parts and creating the partition table on the image
func CreateFinalImage(c *v1.BuildConfig, img string, parts ...string) *eleError.ElementalError {
func CreateFinalImage(c *v1.BuildConfig, img string, parts ...string) error {
err := utils.MkdirAll(c.Fs, filepath.Dir(img), constants.DirPerm)
if err != nil {
return eleError.NewFromError(err, eleError.CreateDir)
Expand Down Expand Up @@ -377,7 +373,7 @@ func CreateFinalImage(c *v1.BuildConfig, img string, parts ...string) *eleError.

// CreatePart creates, truncates, and formats an img.part file. if rootDir is passed it will use that as the rootdir for
// the part creation, thus copying the contents into the newly created part file
func CreatePart(c *v1.BuildConfig, img string, rootDir string, label string, fs string, size int64) *eleError.ElementalError {
func CreatePart(c *v1.BuildConfig, img string, rootDir string, label string, fs string, size int64) error {
err := utils.MkdirAll(c.Fs, filepath.Dir(img), constants.DirPerm)
if err != nil {
return eleError.NewFromError(err, eleError.CreateDir)
Expand Down
6 changes: 4 additions & 2 deletions pkg/action/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -562,14 +562,16 @@ var _ = Describe("Runtime Actions", func() {
err := action.BuildDiskRun(cfg, rawDisk.X86_64, "raw", "OEM", "REC", "disk.raw")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring(fmt.Sprintf("no packages in the config for arch %s", cfg.Arch)))
Expect(err.ExitCode()).To(Equal(eleError.NoPackagesForArch))
Expect(err.(*eleError.ElementalError)).ToNot(BeNil())
Expect(err.(*eleError.ElementalError).ExitCode()).To(Equal(eleError.NoPackagesForArch))
})
It("Fails if config has no repos", func() {
cfg.Repos = []v1.Repository{}
err := action.BuildDiskRun(cfg, rawDisk.X86_64, "raw", "OEM", "REC", "disk.raw")
Expect(err).To(HaveOccurred())
Expect(err.Error()).To(ContainSubstring("no repositories configured"))
Expect(err.ExitCode()).To(Equal(eleError.NoReposConfigured))
Expect(err.(*eleError.ElementalError)).ToNot(BeNil())
Expect(err.(*eleError.ElementalError).ExitCode()).To(Equal(eleError.NoReposConfigured))
})
})
})
28 changes: 27 additions & 1 deletion pkg/action/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ limitations under the License.
package action

import (
"github.com/sirupsen/logrus"

elementalError "github.com/rancher/elemental-cli/pkg/error"
v1 "github.com/rancher/elemental-cli/pkg/types/v1"
"github.com/rancher/elemental-cli/pkg/utils"
"github.com/sirupsen/logrus"
)

// Hook is RunStage wrapper that only adds logic to ignore errors
Expand All @@ -43,3 +45,27 @@ func ChrootHook(config *v1.Config, hook string, strict bool, chrootDir string, b
}
return utils.ChrootedCallback(config, chrootDir, bindMounts, callback)
}

// PowerAction executes a power-action (Reboot/PowerOff) after completed
// install or upgrade and returns any encountered error.
func PowerAction(cfg *v1.RunConfig) error {
// Reboot, poweroff or nothing
var (
err error
code int
)

if cfg.Reboot {
cfg.Logger.Infof("Rebooting in 5 seconds")
if err = utils.Reboot(cfg.Runner, 5); err != nil {
code = elementalError.Reboot
}
} else if cfg.PowerOff {
cfg.Logger.Infof("Shutting down in 5 seconds")
if err = utils.Shutdown(cfg.Runner, 5); err != nil {
code = elementalError.PowerOff
}
}

return elementalError.NewFromError(err, code)
}
Loading

0 comments on commit 1ad9686

Please sign in to comment.