Skip to content

Commit

Permalink
fix(v2): report preflight failures (#1774)
Browse files Browse the repository at this point in the history
* fix(v2): report preflight failures

* f

* f

* f

* f
  • Loading branch information
emosbaugh authored Jan 31, 2025
1 parent f1e76f2 commit 6e7bddf
Show file tree
Hide file tree
Showing 25 changed files with 321 additions and 221 deletions.
9 changes: 4 additions & 5 deletions cmd/installer/cli/adminconsole_resetpassword.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"errors"
"fmt"
"os"

Expand All @@ -13,10 +14,8 @@ import (

func AdminConsoleResetPasswordCmd(ctx context.Context, name string) *cobra.Command {
cmd := &cobra.Command{
Use: "reset-password",
Short: fmt.Sprintf("Reset the %s Admin Console password", name),
SilenceErrors: true,
SilenceUsage: true,
Use: "reset-password",
Short: fmt.Sprintf("Reset the %s Admin Console password", name),
PreRunE: func(cmd *cobra.Command, args []string) error {
if os.Getuid() != 0 {
return fmt.Errorf("reset-password command must be run as root")
Expand All @@ -34,7 +33,7 @@ func AdminConsoleResetPasswordCmd(ctx context.Context, name string) *cobra.Comma

password := args[0]
if !validateAdminConsolePassword(password, password) {
return ErrNothingElseToAdd
return NewErrorNothingElseToAdd(errors.New("password is not valid"))
}

if err := kotscli.ResetPassword(password); err != nil {
Expand Down
36 changes: 18 additions & 18 deletions cmd/installer/cli/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,8 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command {
)

cmd := &cobra.Command{
Use: "install",
Short: fmt.Sprintf("Install %s", name),
SilenceErrors: true,
SilenceUsage: true,
Use: "install",
Short: fmt.Sprintf("Install %s", name),
PreRunE: func(cmd *cobra.Command, args []string) error {
if os.Getuid() != 0 {
return fmt.Errorf("install command must be run as root")
Expand Down Expand Up @@ -134,7 +132,8 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command {
logrus.Warnf("You downloaded an air gap bundle but didn't provide it with --airgap-bundle.")
logrus.Warnf("If you continue, the installation will not use an air gap bundle and will connect to the internet.")
if !prompts.New().Confirm("Do you want to proceed with an online installation?", false) {
return ErrNothingElseToAdd
// TODO: send aborted metrics event
return NewErrorNothingElseToAdd(errors.New("user aborted: air gap bundle downloaded but flag not provided"))
}
}

Expand Down Expand Up @@ -170,7 +169,7 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command {

if !isAirgap {
if err := maybePromptForAppUpdate(cmd.Context(), prompts.New(), license, assumeYes); err != nil {
if errors.Is(err, ErrNothingElseToAdd) {
if errors.As(err, &ErrorNothingElseToAdd{}) {
metrics.ReportApplyFinished(cmd.Context(), licenseFile, nil, err)
return err
}
Expand Down Expand Up @@ -231,8 +230,8 @@ func InstallCmd(ctx context.Context, name string) *cobra.Command {

if err := RunHostPreflights(cmd, applier, replicatedAPIURL, proxyRegistryURL, isAirgap, proxy, cidrCfg, nil, assumeYes); err != nil {
metrics.ReportApplyFinished(cmd.Context(), licenseFile, nil, err)
if err == ErrPreflightsHaveFail {
return ErrNothingElseToAdd
if errors.Is(err, preflights.ErrPreflightsHaveFail) {
return NewErrorNothingElseToAdd(err)
}
return err
}
Expand Down Expand Up @@ -395,7 +394,8 @@ func maybePromptForAppUpdate(ctx context.Context, prompt prompts.Prompt, license

text := fmt.Sprintf("Do you want to continue installing %s anyway?", channelRelease.VersionLabel)
if !prompt.Confirm(text, true) {
return ErrNothingElseToAdd
// TODO: send aborted metrics event
return NewErrorNothingElseToAdd(errors.New("user aborted: app not up-to-date"))
}

logrus.Debug("User confirmed prompt to continue installing out-of-date release")
Expand Down Expand Up @@ -441,11 +441,11 @@ const minAdminPasswordLength = 6

func validateAdminConsolePassword(password, passwordCheck string) bool {
if password != passwordCheck {
logrus.Info("Passwords don't match. Please try again.")
logrus.Errorf("Passwords don't match. Please try again.")
return false
}
if len(password) < minAdminPasswordLength {
logrus.Infof("Passwords must have more than %d characters. Please try again.", minAdminPasswordLength)
logrus.Errorf("Password must have more than %d characters. Please try again.", minAdminPasswordLength)
return false
}
return true
Expand Down Expand Up @@ -1108,11 +1108,11 @@ func runHostPreflights(cmd *cobra.Command, hpf *v1beta2.HostPreflightSpec, proxy
}
if ignoreHostPreflightsFlag {
if assumeYes {
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, *output, true, cmd.CalledAs())
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, metrics.ClusterID(), *output, true, cmd.CalledAs())
return nil
}
if prompts.New().Confirm("Are you sure you want to ignore these failures and continue installing?", false) {
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, *output, true, cmd.CalledAs())
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, metrics.ClusterID(), *output, true, cmd.CalledAs())
return nil // user continued after host preflights failed
}
}
Expand All @@ -1122,8 +1122,8 @@ func runHostPreflights(cmd *cobra.Command, hpf *v1beta2.HostPreflightSpec, proxy
} else {
logrus.Info("Please address this issue and try again.")
}
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, *output, false, cmd.CalledAs())
return ErrPreflightsHaveFail
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, metrics.ClusterID(), *output, false, cmd.CalledAs())
return preflights.ErrPreflightsHaveFail
}

// Warnings found
Expand All @@ -1138,16 +1138,16 @@ func runHostPreflights(cmd *cobra.Command, hpf *v1beta2.HostPreflightSpec, proxy
// so we just print the warnings and continue
pb.Close()
output.PrintTableWithoutInfo()
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, *output, true, cmd.CalledAs())
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, metrics.ClusterID(), *output, true, cmd.CalledAs())
return nil
}
pb.Close()
output.PrintTableWithoutInfo()
if prompts.New().Confirm("Do you want to continue?", false) {
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, *output, true, cmd.CalledAs())
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, metrics.ClusterID(), *output, true, cmd.CalledAs())
return nil
}
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, *output, false, cmd.CalledAs())
metrics.ReportPreflightsFailed(cmd.Context(), replicatedAPIURL, metrics.ClusterID(), *output, false, cmd.CalledAs())
return fmt.Errorf("user aborted")
}

Expand Down
66 changes: 38 additions & 28 deletions cmd/installer/cli/install2.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,9 @@ func Install2Cmd(ctx context.Context, name string) *cobra.Command {
var flags Install2CmdFlags

cmd := &cobra.Command{
Use: "install2",
Short: fmt.Sprintf("Experimental installer for %s", name),
Hidden: true,
SilenceUsage: true,
SilenceErrors: true,
Use: "install2",
Short: fmt.Sprintf("Experimental installer for %s", name),
Hidden: true,
PreRunE: func(cmd *cobra.Command, args []string) error {
if err := preRunInstall2(cmd, &flags); err != nil {
return err
Expand All @@ -89,12 +87,14 @@ func Install2Cmd(ctx context.Context, name string) *cobra.Command {
runtimeconfig.Cleanup()
},
RunE: func(cmd *cobra.Command, args []string) error {
metrics.ReportInstallationStarted(ctx, flags.license)
if err := runInstall2(cmd.Context(), name, flags); err != nil {
metrics.ReportInstallationFailed(ctx, flags.license, err)
clusterID := metrics.ClusterID()
metricsReporter := NewInstallReporter(flags.license, clusterID, cmd.CalledAs())
metricsReporter.ReportInstallationStarted(ctx)
if err := runInstall2(cmd.Context(), name, flags, metricsReporter); err != nil {
metricsReporter.ReportInstallationFailed(ctx, err)
return err
}
metrics.ReportInstallationSucceeded(ctx, flags.license)
metricsReporter.ReportInstallationSucceeded(ctx)
return nil
},
}
Expand Down Expand Up @@ -217,11 +217,15 @@ func preRunInstall2(cmd *cobra.Command, flags *Install2CmdFlags) error {
return nil
}

func runInstall2(ctx context.Context, name string, flags Install2CmdFlags) error {
func runInstall2(ctx context.Context, name string, flags Install2CmdFlags, metricsReporter preflights.MetricsReporter) error {
if err := runInstallVerifyAndPrompt(ctx, name, &flags); err != nil {
return err
}

if err := ensureAdminConsolePassword(&flags); err != nil {
return err
}

logrus.Debugf("materializing binaries")
if err := materializeFiles(flags.airgapBundle); err != nil {
return fmt.Errorf("unable to materialize files: %w", err)
Expand All @@ -243,9 +247,12 @@ func runInstall2(ctx context.Context, name string, flags Install2CmdFlags) error
return fmt.Errorf("unable to configure network manager: %w", err)
}

logrus.Debugf("running host preflights")
if err := runInstallPreflights(ctx, flags); err != nil {
return fmt.Errorf("unable to run preflights: %w", err)
logrus.Debugf("running install preflights")
if err := runInstallPreflights(ctx, flags, metricsReporter); err != nil {
if errors.Is(err, preflights.ErrPreflightsHaveFail) {
return NewErrorNothingElseToAdd(err)
}
return fmt.Errorf("unable to run install preflights: %w", err)
}

k0sCfg, err := installAndStartCluster(ctx, flags.networkInterface, flags.airgapBundle, flags.proxy, flags.cidrCfg, flags.overrides, nil)
Expand Down Expand Up @@ -339,7 +346,7 @@ func runInstallVerifyAndPrompt(ctx context.Context, name string, flags *Install2
logrus.Debugf("checking license matches")
license, err := getLicenseFromFilepath(flags.licenseFile)
if err != nil {
return err // do not return the metricErr, as we want the user to see the error message without a prefix
return err
}
if flags.isAirgap {
logrus.Debugf("checking airgap bundle matches binary")
Expand All @@ -350,11 +357,11 @@ func runInstallVerifyAndPrompt(ctx context.Context, name string, flags *Install2

if !flags.isAirgap {
if err := maybePromptForAppUpdate(ctx, prompts.New(), license, flags.assumeYes); err != nil {
if errors.Is(err, ErrNothingElseToAdd) {
if errors.As(err, &ErrorNothingElseToAdd{}) {
return err
}
// If we get an error other than ErrNothingElseToAdd, we warn and continue as
// this check is not critical.
// If we get an error other than ErrorNothingElseToAdd, we warn and continue as this
// check is not critical.
logrus.Debugf("WARNING: Failed to check for newer app versions: %v", err)
}
}
Expand All @@ -363,14 +370,14 @@ func runInstallVerifyAndPrompt(ctx context.Context, name string, flags *Install2
return err
}

if flags.adminConsolePassword != "" {
if !validateAdminConsolePassword(flags.adminConsolePassword, flags.adminConsolePassword) {
return fmt.Errorf("unable to set the Admin Console password")
}
} else {
return nil
}

func ensureAdminConsolePassword(flags *Install2CmdFlags) error {
if flags.adminConsolePassword == "" {
// no password was provided
if flags.assumeYes {
logrus.Infof("The Admin Console password is set to %s", "password")
logrus.Infof("The Admin Console password is set to %q", "password")
flags.adminConsolePassword = "password"
} else {
maxTries := 3
Expand All @@ -380,13 +387,15 @@ func runInstallVerifyAndPrompt(ctx context.Context, name string, flags *Install2

if validateAdminConsolePassword(promptA, promptB) {
flags.adminConsolePassword = promptA
break
return nil
}
}
return NewErrorNothingElseToAdd(errors.New("password is not valid"))
}
}
if flags.adminConsolePassword == "" {
return fmt.Errorf("no admin console password")

if !validateAdminConsolePassword(flags.adminConsolePassword, flags.adminConsolePassword) {
return NewErrorNothingElseToAdd(errors.New("password is not valid"))
}

return nil
Expand Down Expand Up @@ -483,7 +492,8 @@ func verifyChannelRelease(cmdName string, isAirgap bool, assumeYes bool) error {
logrus.Warnf("You downloaded an air gap bundle but didn't provide it with --airgap-bundle.")
logrus.Warnf("If you continue, the %s will not use an air gap bundle and will connect to the internet.", cmdName)
if !prompts.New().Confirm(fmt.Sprintf("Do you want to proceed with an online %s?", cmdName), false) {
return ErrNothingElseToAdd
// TODO: send aborted metrics event
return NewErrorNothingElseToAdd(errors.New("user aborted: air gap bundle downloaded but flag not provided"))
}
}
return nil
Expand All @@ -499,7 +509,7 @@ func verifyNoInstallation(name string, cmdName string) error {
logrus.Infof("If you want to %s, you need to remove the existing installation first.", cmdName)
logrus.Infof("You can do this by running the following command:")
logrus.Infof("\n sudo ./%s reset\n", name)
return ErrNothingElseToAdd
return NewErrorNothingElseToAdd(errors.New("previous installation detected"))
}
return nil
}
Expand Down
27 changes: 9 additions & 18 deletions cmd/installer/cli/install_runpreflights.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

import (
"context"
"errors"
"fmt"

"github.com/replicatedhq/embedded-cluster/pkg/configutils"
Expand All @@ -11,17 +12,6 @@ import (
"github.com/spf13/cobra"
)

// ErrNothingElseToAdd is an error returned when there is nothing else to add to the
// screen. This is useful when we want to exit an error from a function here but
// don't want to print anything else (possibly because we have already printed the
// necessary data to the screen).
var ErrNothingElseToAdd = fmt.Errorf("")

// ErrPreflightsHaveFail is an error returned when we managed to execute the
// host preflights but they contain failures. We use this to differentiate the
// way we provide user feedback.
var ErrPreflightsHaveFail = fmt.Errorf("host preflight failures detected")

func InstallRunPreflightsCmd(ctx context.Context, name string) *cobra.Command {
var flags Install2CmdFlags

Expand Down Expand Up @@ -71,17 +61,20 @@ func runInstallRunPreflights(ctx context.Context, name string, flags Install2Cmd
return fmt.Errorf("unable to configure sysctl: %w", err)
}

logrus.Debugf("running host preflights")
if err := runInstallPreflights(ctx, flags); err != nil {
return err
logrus.Debugf("running install preflights")
if err := runInstallPreflights(ctx, flags, nil); err != nil {
if errors.Is(err, preflights.ErrPreflightsHaveFail) {
return NewErrorNothingElseToAdd(err)
}
return fmt.Errorf("unable to run install preflights: %w", err)
}

logrus.Info("Host preflights completed successfully")

return nil
}

func runInstallPreflights(ctx context.Context, flags Install2CmdFlags) error {
func runInstallPreflights(ctx context.Context, flags Install2CmdFlags, metricsReported preflights.MetricsReporter) error {
var replicatedAPIURL, proxyRegistryURL string
if flags.license != nil {
replicatedAPIURL = flags.license.Spec.Endpoint
Expand All @@ -100,10 +93,8 @@ func runInstallPreflights(ctx context.Context, flags Install2CmdFlags) error {
SkipHostPreflights: flags.skipHostPreflights,
IgnoreHostPreflights: flags.ignoreHostPreflights,
AssumeYes: flags.assumeYes,
MetricsReporter: metricsReported,
}); err != nil {
if err == preflights.ErrPreflightsHaveFail {
return ErrNothingElseToAdd
}
return err
}

Expand Down
4 changes: 2 additions & 2 deletions cmd/installer/cli/install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,9 +301,9 @@ func Test_maybePromptForAppUpdate(t *testing.T) {
}

if tt.isErrNothingElseToAdd {
assert.Equal(t, ErrNothingElseToAdd, err)
assert.ErrorAs(t, err, &ErrorNothingElseToAdd{})
} else {
assert.NotEqual(t, ErrNothingElseToAdd, err)
assert.NotErrorAs(t, err, &ErrorNothingElseToAdd{})
}
})
}
Expand Down
Loading

0 comments on commit 6e7bddf

Please sign in to comment.