From 1cdacaaaff992a4eecefe5fa6be6e8bf518d0c31 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Mon, 18 Sep 2023 19:47:39 +0100 Subject: [PATCH 01/13] feat: Log preflight results if the prevent deployment --- cmd/kots/cli/install.go | 64 ++++++++++++++++++++++++++++++++++------- pkg/kurl/kurl_nodes.go | 3 +- 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index 73d26ca746..ab64a34116 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -6,7 +6,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "mime/multipart" "net/http" "net/url" @@ -47,6 +46,10 @@ import ( "k8s.io/client-go/kubernetes" ) +var client = &http.Client{ + Timeout: time.Second * 30, +} + func InstallCmd() *cobra.Command { cmd := &cobra.Command{ Use: "install [upstream uri]", @@ -321,7 +324,7 @@ func InstallCmd() *cobra.Command { log.ActionWithoutSpinner("Extracting airgap bundle") - airgapRootDir, err := ioutil.TempDir("", "kotsadm-airgap") + airgapRootDir, err := os.MkdirTemp("", "kotsadm-airgap") if err != nil { return errors.Wrap(err, "failed to create temp dir") } @@ -446,7 +449,13 @@ func InstallCmd() *cobra.Command { log.ActionWithSpinner("Waiting for preflight checks to complete") if err := ValidatePreflightStatus(deployOptions, authSlug, apiEndpoint); err != nil { log.FinishSpinnerWithError() - return errors.Wrap(err, "failed to validate preflight results") + perr := preflightError{} + if errors.As(err, &perr) { + cmd.SilenceErrors = true + log.Info(fmt.Sprintf("\n%s", err.Error())) + os.Exit(1) + } + return err } log.FinishSpinner() case storetypes.VersionPendingConfig: @@ -692,7 +701,7 @@ func getIngressConfig(v *viper.Viper) (*kotsv1beta1.IngressConfig, error) { ingressConfig := kotsv1beta1.IngressConfig{} if ingressConfigPath != "" { - content, err := ioutil.ReadFile(ingressConfigPath) + content, err := os.ReadFile(ingressConfigPath) if err != nil { return nil, errors.Wrap(err, "failed to read ingress service config file") } @@ -719,7 +728,7 @@ func getIdentityConfig(v *viper.Viper) (*kotsv1beta1.IdentityConfig, error) { identityConfig := kotsv1beta1.IdentityConfig{} if identityConfigPath != "" { - content, err := ioutil.ReadFile(identityConfigPath) + content, err := os.ReadFile(identityConfigPath) if err != nil { return nil, errors.Wrap(err, "failed to read identity service config file") } @@ -910,7 +919,7 @@ func getAutomatedInstallStatus(url string, authSlug string) (*kotsstore.TaskStat return nil, errors.Errorf("unexpected status code %d", resp.StatusCode) } - b, err := ioutil.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(err, "failed to read response body") } @@ -964,7 +973,7 @@ func getPreflightResponse(url string, authSlug string) (*handlers.GetPreflightRe newReq.Header.Add("Content-Type", "application/json") newReq.Header.Add("Authorization", authSlug) - resp, err := http.DefaultClient.Do(newReq) + resp, err := client.Do(newReq) if err != nil { return nil, errors.Wrap(err, "failed to execute request") } @@ -974,7 +983,7 @@ func getPreflightResponse(url string, authSlug string) (*handlers.GetPreflightRe return nil, errors.Errorf("unexpected status code %d", resp.StatusCode) } - b, err := ioutil.ReadAll(resp.Body) + b, err := io.ReadAll(resp.Body) if err != nil { return nil, errors.Wrap(err, "failed to read response body") } @@ -1005,6 +1014,16 @@ func checkPreflightsComplete(response *handlers.GetPreflightResultResponse) (boo return true, nil } +type preflightError struct { + Msg string `json:"msg"` +} + +func (e preflightError) Error() string { + return e.Msg +} + +func (e preflightError) Unwrap() error { return fmt.Errorf(e.Msg) } + func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, error) { if response.PreflightResult.Result == "" { return false, nil @@ -1016,8 +1035,25 @@ func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, return false, errors.Wrap(err, fmt.Sprintf("failed to unmarshal upload preflight results from response: %v", response.PreflightResult.Result)) } + status := func(r *preflight.UploadPreflightResult) string { + if r.IsFail { + return "FAIL" + } + if r.IsWarn { + return "WARN" + } + if r.IsPass { + return "PASS" + } + // We should never get here + return "UNKNOWN" + } + + var s strings.Builder + s.WriteString("\nPreflight results (status: title)\n") var isWarn, isFail bool for _, result := range results.Results { + s.WriteString(fmt.Sprintf("%s - %q\n", status(result), result.Title)) if result.IsWarn { isWarn = true } @@ -1027,14 +1063,20 @@ func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, } if isWarn && isFail { - return false, errors.New("There are preflight check failures and warnings for the application. The app was not deployed.") + return false, preflightError{ + Msg: fmt.Sprintf("There are preflight check failures and warnings for the application. The app was not deployed. %s", s.String()), + } } if isWarn { - return false, errors.New("There are preflight check warnings for the application. The app was not deployed.") + return false, preflightError{ + Msg: fmt.Sprintf("There are preflight check warnings for the application. The app was not deployed. %s", s.String()), + } } if isFail { - return false, errors.New("There are preflight check failures for the application. The app was not deployed.") + return false, preflightError{ + Msg: fmt.Sprintf("There are preflight check failures for the application. The app was not deployed. %s", s.String()), + } } return true, nil diff --git a/pkg/kurl/kurl_nodes.go b/pkg/kurl/kurl_nodes.go index bf85770c19..b70158e29d 100644 --- a/pkg/kurl/kurl_nodes.go +++ b/pkg/kurl/kurl_nodes.go @@ -8,6 +8,7 @@ import ( "io/ioutil" "math" "net/http" + "os" "strconv" "time" @@ -140,7 +141,7 @@ func getNodeMetrics(nodeIP string) (*statsv1alpha1.Summary, error) { port := 10255 // only use mutual TLS if client cert exists - _, err := ioutil.ReadFile("/etc/kubernetes/pki/kubelet/client.crt") + _, err := os.ReadFile("/etc/kubernetes/pki/kubelet/client.crt") if err == nil { cert, err := tls.LoadX509KeyPair("/etc/kubernetes/pki/kubelet/client.crt", "/etc/kubernetes/pki/kubelet/client.key") if err != nil { From d72c667a13a295c3258a0d58cf43c0d83dbfecc4 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Mon, 18 Sep 2023 19:49:53 +0100 Subject: [PATCH 02/13] Remove header string --- cmd/kots/cli/install.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index ab64a34116..302099b4d0 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -1050,7 +1050,6 @@ func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, } var s strings.Builder - s.WriteString("\nPreflight results (status: title)\n") var isWarn, isFail bool for _, result := range results.Results { s.WriteString(fmt.Sprintf("%s - %q\n", status(result), result.Title)) From 2f05a4856d186e785228f9a36630c19b54197eda Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Tue, 19 Sep 2023 14:25:25 +0100 Subject: [PATCH 03/13] Remove deprecated ioutil.ReadFile --- pkg/kotsutil/troubleshoot.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/kotsutil/troubleshoot.go b/pkg/kotsutil/troubleshoot.go index 9f93a4d8a8..67a4d08fc8 100644 --- a/pkg/kotsutil/troubleshoot.go +++ b/pkg/kotsutil/troubleshoot.go @@ -2,7 +2,6 @@ package kotsutil import ( "context" - "io/ioutil" "os" "path/filepath" @@ -22,7 +21,7 @@ func LoadTSKindsFromPath(dir string) (*troubleshootloader.TroubleshootKinds, err return nil } - contents, err := ioutil.ReadFile(path) + contents, err := os.ReadFile(path) if err != nil { return errors.Wrap(err, "failed to read file") } From 4450d4a85391f390c6bc099667bb3a57573e31d4 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Tue, 19 Sep 2023 17:10:12 +0100 Subject: [PATCH 04/13] Improved logging for both the cli and kotsadm install logs --- cmd/kots/cli/install.go | 54 +++++++++++++++++++---------------- pkg/handlers/preflight.go | 2 +- pkg/preflight/execute.go | 13 ++++++--- pkg/preflight/preflight.go | 35 +++++++++++++++++++++-- pkg/reporting/distribution.go | 7 ++++- 5 files changed, 77 insertions(+), 34 deletions(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index 302099b4d0..b1a855d61f 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -429,6 +429,7 @@ func InstallCmd() *cobra.Command { case err := <-errChan: if err != nil { log.Error(err) + // TODO: Why is this a negative exit code? os.Exit(-1) } case <-stopCh: @@ -447,17 +448,22 @@ func InstallCmd() *cobra.Command { switch status { case storetypes.VersionPendingPreflight: log.ActionWithSpinner("Waiting for preflight checks to complete") + log.FinishSpinner() if err := ValidatePreflightStatus(deployOptions, authSlug, apiEndpoint); err != nil { - log.FinishSpinnerWithError() + log.Errorf("Preflight checks contain warnings or errors. The application was not deployed") perr := preflightError{} if errors.As(err, &perr) { cmd.SilenceErrors = true - log.Info(fmt.Sprintf("\n%s", err.Error())) + var s strings.Builder + s.WriteString("\nPreflight check results (state - title - message)") + for _, result := range perr.Results { + s.WriteString(fmt.Sprintf("\n%s - %q - %q", preflightState(result), result.Title, result.Message)) + } + log.Info(s.String()) os.Exit(1) } return err } - log.FinishSpinner() case storetypes.VersionPendingConfig: log.ActionWithoutSpinnerWarning("Additional app configuration is required. Please login to the Admin Console to continue", nil) } @@ -1015,14 +1021,14 @@ func checkPreflightsComplete(response *handlers.GetPreflightResultResponse) (boo } type preflightError struct { - Msg string `json:"msg"` + Results []*preflight.UploadPreflightResult } func (e preflightError) Error() string { - return e.Msg + return "preflight checks have warnings or errors" } -func (e preflightError) Unwrap() error { return fmt.Errorf(e.Msg) } +func (e preflightError) Unwrap() error { return fmt.Errorf("preflight checks have warnings or errors") } func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, error) { if response.PreflightResult.Result == "" { @@ -1035,24 +1041,8 @@ func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, return false, errors.Wrap(err, fmt.Sprintf("failed to unmarshal upload preflight results from response: %v", response.PreflightResult.Result)) } - status := func(r *preflight.UploadPreflightResult) string { - if r.IsFail { - return "FAIL" - } - if r.IsWarn { - return "WARN" - } - if r.IsPass { - return "PASS" - } - // We should never get here - return "UNKNOWN" - } - - var s strings.Builder var isWarn, isFail bool for _, result := range results.Results { - s.WriteString(fmt.Sprintf("%s - %q\n", status(result), result.Title)) if result.IsWarn { isWarn = true } @@ -1063,20 +1053,34 @@ func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, if isWarn && isFail { return false, preflightError{ - Msg: fmt.Sprintf("There are preflight check failures and warnings for the application. The app was not deployed. %s", s.String()), + Results: results.Results, } } if isWarn { return false, preflightError{ - Msg: fmt.Sprintf("There are preflight check warnings for the application. The app was not deployed. %s", s.String()), + Results: results.Results, } } if isFail { return false, preflightError{ - Msg: fmt.Sprintf("There are preflight check failures for the application. The app was not deployed. %s", s.String()), + Results: results.Results, } } return true, nil } + +func preflightState(r *preflight.UploadPreflightResult) string { + if r.IsFail { + return "FAIL" + } + if r.IsWarn { + return "WARN" + } + if r.IsPass { + return "PASS" + } + // We should never get here + return "UNKNOWN" +} diff --git a/pkg/handlers/preflight.go b/pkg/handlers/preflight.go index fc9cd17c98..5138d6b8af 100644 --- a/pkg/handlers/preflight.go +++ b/pkg/handlers/preflight.go @@ -201,7 +201,7 @@ func (h *Handler) StartPreflightChecks(w http.ResponseWriter, r *http.Request) { return } - archiveDir, err := ioutil.TempDir("", "kotsadm") + archiveDir, err := os.MkdirTemp("", "kotsadm") if err != nil { logger.Error(errors.Wrap(err, "failed to create temp dir")) w.WriteHeader(http.StatusInternalServerError) diff --git a/pkg/preflight/execute.go b/pkg/preflight/execute.go index 0d1a9b9472..697a068475 100644 --- a/pkg/preflight/execute.go +++ b/pkg/preflight/execute.go @@ -45,7 +45,7 @@ func setPreflightResult(appID string, sequence int64, preflightResults *types.Pr // execute will execute the preflights using spec in preflightSpec. // This spec should be rendered, no template functions remaining func execute(appID string, sequence int64, preflightSpec *troubleshootv1beta2.Preflight, ignorePermissionErrors bool) (*types.PreflightResults, error) { - logger.Debug("executing preflight checks", + logger.Info("executing preflight checks", zap.String("appID", appID), zap.Int64("sequence", sequence)) @@ -65,7 +65,12 @@ func execute(appID string, sequence int64, preflightSpec *troubleshootv1beta2.Pr if err, ok := msg.(error); ok { logger.Errorf("error while running preflights: %v", err) } else { - logger.Infof("preflight progress: %v", msg) + switch m := msg.(type) { + case preflight.CollectProgress: + logger.Infof("preflight progress: %s", m.String()) + default: + logger.Infof("preflight progress: %+v", msg) + } } progress, ok := msg.(preflight.CollectProgress) @@ -121,7 +126,7 @@ func execute(appID string, sequence int64, preflightSpec *troubleshootv1beta2.Pr KubernetesRestConfig: restConfig, } - logger.Debug("preflight collect phase") + logger.Info("preflight collect phase") collectResults, err := troubleshootpreflight.Collect(collectOpts, preflightSpec) if err != nil && !isPermissionsError(err) { preflightRunError = err @@ -147,7 +152,7 @@ func execute(appID string, sequence int64, preflightSpec *troubleshootv1beta2.Pr } uploadPreflightResults.Errors = rbacErrors } else { - logger.Debug("preflight analyze phase") + logger.Info("preflight analyze phase") analyzeResults := collectResults.Analyze() // the typescript api added some flair to this result diff --git a/pkg/preflight/preflight.go b/pkg/preflight/preflight.go index fddf9ea7df..ef0b8a83e5 100644 --- a/pkg/preflight/preflight.go +++ b/pkg/preflight/preflight.go @@ -160,13 +160,27 @@ func Run(appID string, appSlug string, sequence int64, isAirgap bool, archiveDir preflight.Spec.Collectors = collectors go func() { - logger.Debug("preflight checks beginning") + logger.Info("preflight checks beginning") uploadPreflightResults, err := execute(appID, sequence, preflight, ignoreRBAC) if err != nil { logger.Error(errors.Wrap(err, "failed to run preflight checks")) return } - logger.Debug("preflight checks completed") + + // Log the preflight results if there are any warnings or errors + // The app may not get installed so we need to see this info for debugging + if GetPreflightState(uploadPreflightResults) != "pass" { + // TODO: Are there conditions when the application gets installed? + logger.Warnf("Preflight checks completed with warnings or errors. The application may not get installed") + for _, result := range uploadPreflightResults.Results { + if result == nil { + continue + } + logger.Infof("preflight state=%s title=%q message=%q", preflightState(*result), result.Title, result.Message) + } + } else { + logger.Info("preflight checks completed") + } go func() { err := reporting.GetReporter().SubmitAppInfo(appID) // send app and preflight info when preflights finish @@ -194,7 +208,7 @@ func Run(appID string, appSlug string, sequence int64, isAirgap bool, archiveDir // preflight reporting if isDeployed { if err := reporting.WaitAndReportPreflightChecks(appID, sequence, false, false); err != nil { - logger.Debugf("failed to send preflights data to replicated app: %v", err) + logger.Errorf("failed to send preflights data to replicated app: %v", err) return } } @@ -216,6 +230,21 @@ func Run(appID string, appSlug string, sequence int64, isAirgap bool, archiveDir return nil } +func preflightState(p troubleshootpreflight.UploadPreflightResult) string { + if p.IsFail { + return "fail" + } + + if p.IsWarn { + return "warn" + } + + if p.IsPass { + return "pass" + } + return "unknown" +} + // maybeDeployFirstVersion will deploy the first version if preflight checks pass func maybeDeployFirstVersion(appID string, sequence int64, preflightResults *types.PreflightResults) (bool, error) { if sequence != 0 { diff --git a/pkg/reporting/distribution.go b/pkg/reporting/distribution.go index eb91f41c25..8d2d8c6124 100644 --- a/pkg/reporting/distribution.go +++ b/pkg/reporting/distribution.go @@ -53,7 +53,12 @@ func distributionFromServerGroupAndResources(clientset kubernetes.Interface) Dis func distributionFromProviderId(clientset kubernetes.Interface) Distribution { nodes, err := clientset.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{}) - logger.Infof("Found %d nodes", len(nodes.Items)) + nodeCount := len(nodes.Items) + if nodeCount > 1 { + logger.Infof("Found %d nodes", nodeCount) + } else { + logger.Infof("Found %d node", nodeCount) + } if err != nil { logger.Infof("got error listing node: %v", err.Error()) } From 2c2132da875914871b5d1aff94ef2808c7ef28db Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 21 Sep 2023 18:01:47 +0100 Subject: [PATCH 05/13] Changes as per PR comments --- cmd/kots/cli/install.go | 25 +++---------------------- cmd/kots/cli/set-config.go | 3 ++- pkg/preflight/preflight.go | 5 ++--- pkg/print/config.go | 25 +++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 26 deletions(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index b1a855d61f..4582dc4d25 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -32,6 +32,7 @@ import ( "github.com/replicatedhq/kots/pkg/kurl" "github.com/replicatedhq/kots/pkg/logger" "github.com/replicatedhq/kots/pkg/metrics" + "github.com/replicatedhq/kots/pkg/print" "github.com/replicatedhq/kots/pkg/pull" "github.com/replicatedhq/kots/pkg/replicatedapp" "github.com/replicatedhq/kots/pkg/store/kotsstore" @@ -448,18 +449,12 @@ func InstallCmd() *cobra.Command { switch status { case storetypes.VersionPendingPreflight: log.ActionWithSpinner("Waiting for preflight checks to complete") - log.FinishSpinner() if err := ValidatePreflightStatus(deployOptions, authSlug, apiEndpoint); err != nil { + log.FinishSpinner() log.Errorf("Preflight checks contain warnings or errors. The application was not deployed") perr := preflightError{} if errors.As(err, &perr) { - cmd.SilenceErrors = true - var s strings.Builder - s.WriteString("\nPreflight check results (state - title - message)") - for _, result := range perr.Results { - s.WriteString(fmt.Sprintf("\n%s - %q - %q", preflightState(result), result.Title, result.Message)) - } - log.Info(s.String()) + print.PreflightErrors(log, perr.Results) os.Exit(1) } return err @@ -1070,17 +1065,3 @@ func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, return true, nil } - -func preflightState(r *preflight.UploadPreflightResult) string { - if r.IsFail { - return "FAIL" - } - if r.IsWarn { - return "WARN" - } - if r.IsPass { - return "PASS" - } - // We should never get here - return "UNKNOWN" -} diff --git a/cmd/kots/cli/set-config.go b/cmd/kots/cli/set-config.go index c07b7c738c..800d7931bd 100644 --- a/cmd/kots/cli/set-config.go +++ b/cmd/kots/cli/set-config.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "io" "io/ioutil" "net/http" "net/url" @@ -127,7 +128,7 @@ func SetConfigCmd() *cobra.Command { } defer resp.Body.Close() - respBody, err := ioutil.ReadAll(resp.Body) + respBody, err := io.ReadAll(resp.Body) if err != nil { log.FinishSpinnerWithError() return errors.Wrap(err, "failed to read server response") diff --git a/pkg/preflight/preflight.go b/pkg/preflight/preflight.go index ef0b8a83e5..f94440dc59 100644 --- a/pkg/preflight/preflight.go +++ b/pkg/preflight/preflight.go @@ -170,8 +170,7 @@ func Run(appID string, appSlug string, sequence int64, isAirgap bool, archiveDir // Log the preflight results if there are any warnings or errors // The app may not get installed so we need to see this info for debugging if GetPreflightState(uploadPreflightResults) != "pass" { - // TODO: Are there conditions when the application gets installed? - logger.Warnf("Preflight checks completed with warnings or errors. The application may not get installed") + logger.Warnf("Preflight checks completed with warnings or errors. The application will not get deployed") for _, result := range uploadPreflightResults.Results { if result == nil { continue @@ -208,7 +207,7 @@ func Run(appID string, appSlug string, sequence int64, isAirgap bool, archiveDir // preflight reporting if isDeployed { if err := reporting.WaitAndReportPreflightChecks(appID, sequence, false, false); err != nil { - logger.Errorf("failed to send preflights data to replicated app: %v", err) + logger.Debugf("failed to send preflights data to replicated app: %v", err) return } } diff --git a/pkg/print/config.go b/pkg/print/config.go index adadbbe4cf..e499ff2ef8 100644 --- a/pkg/print/config.go +++ b/pkg/print/config.go @@ -6,6 +6,7 @@ import ( configtypes "github.com/replicatedhq/kots/pkg/kotsadmconfig/types" "github.com/replicatedhq/kots/pkg/logger" + "github.com/replicatedhq/troubleshoot/pkg/preflight" ) func ConfigValidationErrors(log *logger.CLILogger, groupValidationErrors []configtypes.ConfigGroupValidationError) { @@ -26,3 +27,27 @@ func ConfigValidationErrors(log *logger.CLILogger, groupValidationErrors []confi log.FinishSpinnerWithError() log.Errorf(sb.String()) } + +func PreflightErrors(log *logger.CLILogger, results []*preflight.UploadPreflightResult) { + var s strings.Builder + s.WriteString("\nPreflight check results (state - title - message)") + for _, result := range results { + s.WriteString(fmt.Sprintf("\n%s - %q - %q", preflightState(result), result.Title, result.Message)) + } + log.Info(s.String()) + // log.Errorf(sb.String()) +} + +func preflightState(r *preflight.UploadPreflightResult) string { + if r.IsFail { + return "FAIL" + } + if r.IsWarn { + return "WARN" + } + if r.IsPass { + return "PASS" + } + // We should never get here + return "UNKNOWN" +} From 90917d4fbf9dcdd51e548b34a08db38c57e23253 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 21 Sep 2023 18:05:50 +0100 Subject: [PATCH 06/13] Update failing tests --- cmd/kots/cli/install_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/kots/cli/install_test.go b/cmd/kots/cli/install_test.go index e44e43a1e5..19ad35b4c3 100644 --- a/cmd/kots/cli/install_test.go +++ b/cmd/kots/cli/install_test.go @@ -286,9 +286,9 @@ var _ = Describe("Install", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(expectedErr)) }, - Entry("warnings and failures", true, true, "There are preflight check failures and warnings for the application"), - Entry("warnings only", false, true, "There are preflight check warnings for the application"), - Entry("failures only", true, false, "There are preflight check failures for the application"), + Entry("warnings and failures", true, true, "Preflight checks have warnings or errors"), + Entry("warnings only", false, true, "Preflight checks have warnings or errors"), + Entry("failures only", true, false, "Preflight checks have warnings or errors"), ) It("does not return an error if there are no warnings and failures", func() { From bf41bf3ec76288fe7fd237cd12c7d2bb0546be2a Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 22 Sep 2023 10:05:07 +0100 Subject: [PATCH 07/13] Reinstate previous error messages of preflight failures --- cmd/kots/cli/install.go | 8 ++++++-- cmd/kots/cli/install_test.go | 14 +++++++------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index 4582dc4d25..697f248ac8 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -1016,14 +1016,15 @@ func checkPreflightsComplete(response *handlers.GetPreflightResultResponse) (boo } type preflightError struct { + Msg string Results []*preflight.UploadPreflightResult } func (e preflightError) Error() string { - return "preflight checks have warnings or errors" + return e.Msg } -func (e preflightError) Unwrap() error { return fmt.Errorf("preflight checks have warnings or errors") } +func (e preflightError) Unwrap() error { return fmt.Errorf(e.Msg) } func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, error) { if response.PreflightResult.Result == "" { @@ -1048,17 +1049,20 @@ func checkPreflightResults(response *handlers.GetPreflightResultResponse) (bool, if isWarn && isFail { return false, preflightError{ + Msg: "There are preflight check failures and warnings for the application. The app was not deployed.", Results: results.Results, } } if isWarn { return false, preflightError{ + Msg: "There are preflight check warnings for the application. The app was not deployed.", Results: results.Results, } } if isFail { return false, preflightError{ + Msg: "There are preflight check failures for the application. The app was not deployed.", Results: results.Results, } } diff --git a/cmd/kots/cli/install_test.go b/cmd/kots/cli/install_test.go index 19ad35b4c3..7ff8e06adc 100644 --- a/cmd/kots/cli/install_test.go +++ b/cmd/kots/cli/install_test.go @@ -223,7 +223,7 @@ var _ = Describe("Install", func() { }) It("returns an error if the preflight collection results cannot be parsed", func() { - invalidPreflightCollection, err := json.Marshal(handlers.GetPreflightResultResponse{ + invalidPreflightCollection, _ := json.Marshal(handlers.GetPreflightResultResponse{ PreflightProgress: `{invalid: json}`, PreflightResult: preflighttypes.PreflightResult{}, }) @@ -239,13 +239,13 @@ var _ = Describe("Install", func() { ), ) - err = ValidatePreflightStatus(validDeployOptions, authSlug, server.URL()) + err := ValidatePreflightStatus(validDeployOptions, authSlug, server.URL()) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to unmarshal collect progress for preflights")) }) It("returns an error if the upload preflight results are invalid", func() { - invalidUploadPreflightResponse, err := json.Marshal(handlers.GetPreflightResultResponse{ + invalidUploadPreflightResponse, _ := json.Marshal(handlers.GetPreflightResultResponse{ PreflightProgress: "", PreflightResult: preflighttypes.PreflightResult{ Result: "{invalid: json}", @@ -262,7 +262,7 @@ var _ = Describe("Install", func() { ), ) - err = ValidatePreflightStatus(validDeployOptions, authSlug, server.URL()) + err := ValidatePreflightStatus(validDeployOptions, authSlug, server.URL()) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring("failed to unmarshal upload preflight results")) }) @@ -286,9 +286,9 @@ var _ = Describe("Install", func() { Expect(err).To(HaveOccurred()) Expect(err.Error()).To(ContainSubstring(expectedErr)) }, - Entry("warnings and failures", true, true, "Preflight checks have warnings or errors"), - Entry("warnings only", false, true, "Preflight checks have warnings or errors"), - Entry("failures only", true, false, "Preflight checks have warnings or errors"), + Entry("warnings and failures", true, true, "There are preflight check failures and warnings for the application"), + Entry("warnings only", false, true, "There are preflight check warnings for the application"), + Entry("failures only", true, false, "There are preflight check failures for the application"), ) It("does not return an error if there are no warnings and failures", func() { From ea089abc2aa87c41978639965152737f33376818 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 22 Sep 2023 11:19:13 +0100 Subject: [PATCH 08/13] Remove unnecessary comment --- pkg/print/config.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/print/config.go b/pkg/print/config.go index e499ff2ef8..619c8ef0d6 100644 --- a/pkg/print/config.go +++ b/pkg/print/config.go @@ -35,7 +35,6 @@ func PreflightErrors(log *logger.CLILogger, results []*preflight.UploadPreflight s.WriteString(fmt.Sprintf("\n%s - %q - %q", preflightState(result), result.Title, result.Message)) } log.Info(s.String()) - // log.Errorf(sb.String()) } func preflightState(r *preflight.UploadPreflightResult) string { From 2b3501bd193a20bedeb3d98b6a610011980ae865 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 22 Sep 2023 17:46:25 +0100 Subject: [PATCH 09/13] Update cmd/kots/cli/install.go Co-authored-by: Craig O'Donnell --- cmd/kots/cli/install.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index 697f248ac8..549b18e8a3 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -450,7 +450,7 @@ func InstallCmd() *cobra.Command { case storetypes.VersionPendingPreflight: log.ActionWithSpinner("Waiting for preflight checks to complete") if err := ValidatePreflightStatus(deployOptions, authSlug, apiEndpoint); err != nil { - log.FinishSpinner() + log.FinishSpinnerWithError() log.Errorf("Preflight checks contain warnings or errors. The application was not deployed") perr := preflightError{} if errors.As(err, &perr) { From cadc62a9b2d98f0058d17337559f417378a34838 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Fri, 22 Sep 2023 17:50:10 +0100 Subject: [PATCH 10/13] Changes as per PR comments --- cmd/kots/cli/install.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index 549b18e8a3..71cd1a6d0d 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -459,6 +459,7 @@ func InstallCmd() *cobra.Command { } return err } + log.FinishSpinner() case storetypes.VersionPendingConfig: log.ActionWithoutSpinnerWarning("Additional app configuration is required. Please login to the Admin Console to continue", nil) } From 2924058abea89a3d9d8b3da52c7b590852012ced Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Mon, 25 Sep 2023 10:26:37 +0100 Subject: [PATCH 11/13] Silence cobra errors instead of premature exit --- cmd/kots/cli/install.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index 71cd1a6d0d..94061849b5 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -455,7 +455,7 @@ func InstallCmd() *cobra.Command { perr := preflightError{} if errors.As(err, &perr) { print.PreflightErrors(log, perr.Results) - os.Exit(1) + cmd.SilenceErrors = true // Stop Cobra from printing the error, we print it ourselves } return err } From c1f3cfe6957304d0330492bf12f3d0f43aaa88a7 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Tue, 26 Sep 2023 12:00:30 +0100 Subject: [PATCH 12/13] Changes from PR comments * Use tabwriter to log preflight check results * DRY code --- cmd/kots/cli/install.go | 8 +++++--- pkg/preflight/execute.go | 8 ++++---- pkg/preflight/preflight.go | 13 +++++++++++-- pkg/print/config.go | 31 +++++++++++-------------------- 4 files changed, 31 insertions(+), 29 deletions(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index 94061849b5..3fb564bfa1 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -450,12 +450,14 @@ func InstallCmd() *cobra.Command { case storetypes.VersionPendingPreflight: log.ActionWithSpinner("Waiting for preflight checks to complete") if err := ValidatePreflightStatus(deployOptions, authSlug, apiEndpoint); err != nil { - log.FinishSpinnerWithError() - log.Errorf("Preflight checks contain warnings or errors. The application was not deployed") perr := preflightError{} if errors.As(err, &perr) { + log.FinishSpinner() // We succeeded waiting for the results. Don't finish with an error + log.Errorf("Preflight checks contain warnings or errors. The application was not deployed") print.PreflightErrors(log, perr.Results) - cmd.SilenceErrors = true // Stop Cobra from printing the error, we print it ourselves + cmd.SilenceErrors = true // Stop Cobra from printing the error, we format the message ourselves + } else { + log.FinishSpinnerWithError() } return err } diff --git a/pkg/preflight/execute.go b/pkg/preflight/execute.go index 697a068475..4b73d70648 100644 --- a/pkg/preflight/execute.go +++ b/pkg/preflight/execute.go @@ -66,10 +66,10 @@ func execute(appID string, sequence int64, preflightSpec *troubleshootv1beta2.Pr logger.Errorf("error while running preflights: %v", err) } else { switch m := msg.(type) { - case preflight.CollectProgress: - logger.Infof("preflight progress: %s", m.String()) - default: - logger.Infof("preflight progress: %+v", msg) + case preflight.CollectProgress: + logger.Infof("preflight progress: %s", m.String()) + default: + logger.Infof("preflight progress: %+v", msg) } } diff --git a/pkg/preflight/preflight.go b/pkg/preflight/preflight.go index f94440dc59..edcfea6e4e 100644 --- a/pkg/preflight/preflight.go +++ b/pkg/preflight/preflight.go @@ -175,7 +175,7 @@ func Run(appID string, appSlug string, sequence int64, isAirgap bool, archiveDir if result == nil { continue } - logger.Infof("preflight state=%s title=%q message=%q", preflightState(*result), result.Title, result.Message) + logger.Infof("preflight state=%s title=%q message=%q", GetPreflightCheckState(result), result.Title, result.Message) } } else { logger.Info("preflight checks completed") @@ -229,7 +229,12 @@ func Run(appID string, appSlug string, sequence int64, isAirgap bool, archiveDir return nil } -func preflightState(p troubleshootpreflight.UploadPreflightResult) string { +// GetPreflightCheckState returns the state of a single preflight check result +func GetPreflightCheckState(p *troubleshootpreflight.UploadPreflightResult) string { + if p == nil { + return "unknown" + } + if p.IsFail { return "fail" } @@ -275,6 +280,10 @@ func maybeDeployFirstVersion(appID string, sequence int64, preflightResults *typ return true, nil } +// GetPreflightState returns a single state based on checking all +// preflight checks results. If there are any errors, the state is fail. +// If there are no errors and any warnings, the state is warn. +// Otherwise, the state is pass. func GetPreflightState(preflightResults *types.PreflightResults) string { if len(preflightResults.Errors) > 0 { return "fail" diff --git a/pkg/print/config.go b/pkg/print/config.go index 619c8ef0d6..3507dcc75a 100644 --- a/pkg/print/config.go +++ b/pkg/print/config.go @@ -6,7 +6,8 @@ import ( configtypes "github.com/replicatedhq/kots/pkg/kotsadmconfig/types" "github.com/replicatedhq/kots/pkg/logger" - "github.com/replicatedhq/troubleshoot/pkg/preflight" + "github.com/replicatedhq/kots/pkg/preflight" + tsPreflight "github.com/replicatedhq/troubleshoot/pkg/preflight" ) func ConfigValidationErrors(log *logger.CLILogger, groupValidationErrors []configtypes.ConfigGroupValidationError) { @@ -28,25 +29,15 @@ func ConfigValidationErrors(log *logger.CLILogger, groupValidationErrors []confi log.Errorf(sb.String()) } -func PreflightErrors(log *logger.CLILogger, results []*preflight.UploadPreflightResult) { - var s strings.Builder - s.WriteString("\nPreflight check results (state - title - message)") - for _, result := range results { - s.WriteString(fmt.Sprintf("\n%s - %q - %q", preflightState(result), result.Title, result.Message)) - } - log.Info(s.String()) -} +func PreflightErrors(log *logger.CLILogger, results []*tsPreflight.UploadPreflightResult) { + w := NewTabWriter() + defer w.Flush() -func preflightState(r *preflight.UploadPreflightResult) string { - if r.IsFail { - return "FAIL" - } - if r.IsWarn { - return "WARN" - } - if r.IsPass { - return "PASS" + fmt.Fprintf(w, "\n") + fmtColumns := "%s\t%s\t%s\n" + fmt.Fprintf(w, fmtColumns, "STATE", "TITLE", "MESSAGE") + for _, result := range results { + fmt.Fprintf(w, fmtColumns, strings.ToUpper(preflight.GetPreflightCheckState(result)), result.Title, result.Message) } - // We should never get here - return "UNKNOWN" + fmt.Fprintf(w, "\n") } From 43bdb6686c86219eff49a8c3237aae727ecf6003 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Tue, 26 Sep 2023 12:04:02 +0100 Subject: [PATCH 13/13] Print error message to terminal --- cmd/kots/cli/install.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kots/cli/install.go b/cmd/kots/cli/install.go index 3fb564bfa1..84170c7dbc 100644 --- a/cmd/kots/cli/install.go +++ b/cmd/kots/cli/install.go @@ -453,7 +453,7 @@ func InstallCmd() *cobra.Command { perr := preflightError{} if errors.As(err, &perr) { log.FinishSpinner() // We succeeded waiting for the results. Don't finish with an error - log.Errorf("Preflight checks contain warnings or errors. The application was not deployed") + log.Errorf(perr.Msg) print.PreflightErrors(log, perr.Results) cmd.SilenceErrors = true // Stop Cobra from printing the error, we format the message ourselves } else {