github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/preflight/text_results.go (about) 1 /* 2 Copyright (C) 2022-2023 ApeCloud Co., Ltd 3 4 This file is part of KubeBlocks project 5 6 This program is free software: you can redistribute it and/or modify 7 it under the terms of the GNU Affero General Public License as published by 8 the Free Software Foundation, either version 3 of the License, or 9 (at your option) any later version. 10 11 This program is distributed in the hope that it will be useful 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU Affero General Public License for more details. 15 16 You should have received a copy of the GNU Affero General Public License 17 along with this program. If not, see <http://www.gnu.org/licenses/>. 18 */ 19 20 package preflight 21 22 import ( 23 "encoding/json" 24 "fmt" 25 "io" 26 "strings" 27 28 "github.com/1aal/kubeblocks/pkg/cli/spinner" 29 30 "github.com/pkg/errors" 31 analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze" 32 "gopkg.in/yaml.v2" 33 34 "github.com/1aal/kubeblocks/pkg/cli/printer" 35 ) 36 37 const ( 38 FailMessage = "Failed items were found. Please resolve the failed items and try again." 39 ) 40 41 type TextResultOutput struct { 42 Title string `json:"title" yaml:"title"` 43 Message string `json:"message" yaml:"message"` 44 URI string `json:"uri,omitempty" yaml:"uri,omitempty"` 45 Strict bool `json:"strict,omitempty" yaml:"strict,omitempty"` 46 } 47 48 func NewTextResultOutput(title, message, uri string) TextResultOutput { 49 return TextResultOutput{ 50 Title: title, 51 Message: message, 52 URI: uri, 53 } 54 } 55 56 type TextOutput struct { 57 Pass []TextResultOutput `json:"pass,omitempty" yaml:"pass,omitempty"` 58 Warn []TextResultOutput `json:"warn,omitempty" yaml:"warn,omitempty"` 59 Fail []TextResultOutput `json:"fail,omitempty" yaml:"fail,omitempty"` 60 } 61 62 func NewTextOutput() TextOutput { 63 return TextOutput{ 64 Pass: []TextResultOutput{}, 65 Warn: []TextResultOutput{}, 66 Fail: []TextResultOutput{}, 67 } 68 } 69 70 // ShowTextResults shadows interactive mode, and exports results by customized format 71 func ShowTextResults(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult, format string, verbose bool, out io.Writer) error { 72 switch format { 73 case "json": 74 return showTextResultsJSON(preflightName, analyzeResults, verbose, out) 75 case "yaml": 76 return showStdoutResultsYAML(preflightName, analyzeResults, verbose, out) 77 case "kbcli": 78 return showResultsKBCli(preflightName, analyzeResults, verbose, out) 79 default: 80 return errors.Errorf("unknown output format: %q", format) 81 } 82 } 83 84 func showResultsKBCli(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult, verbose bool, out io.Writer) error { 85 var ( 86 allMsg string 87 all = make([]string, 0) 88 spinnerDone = func(s spinner.Interface) { 89 s.SetFinalMsg(allMsg) 90 s.Done("") 91 fmt.Fprintln(out) 92 } 93 showResults = func(results []TextResultOutput) { 94 for _, result := range results { 95 all = append(all, fmt.Sprintf("- %s", result.Message)) 96 } 97 } 98 ) 99 msg := fmt.Sprintf("%-50s", "Kubernetes cluster preflight") 100 s := spinner.New(out, spinner.WithMessage(msg)) 101 data := showStdoutResultsStructured(preflightName, analyzeResults, verbose) 102 isFailed := false 103 104 if verbose { 105 if len(data.Pass) > 0 { 106 all = append(all, fmt.Sprint(printer.BoldGreen("Pass"))) 107 showResults(data.Pass) 108 } 109 } 110 if len(data.Warn) > 0 { 111 all = append(all, fmt.Sprint(printer.BoldYellow("Warn"))) 112 showResults(data.Warn) 113 } 114 if len(data.Fail) > 0 { 115 all = append(all, fmt.Sprint(printer.BoldRed("Fail"))) 116 showResults(data.Fail) 117 isFailed = true 118 } 119 allMsg = fmt.Sprintf(" %s", strings.Join(all, "\n ")) 120 s.SetFinalMsg(suffixMsg(allMsg)) 121 if isFailed { 122 s.Fail() 123 spinnerDone(s) 124 return errors.New(FailMessage) 125 } 126 s.Success() 127 spinnerDone(s) 128 return nil 129 } 130 131 func suffixMsg(msg string) string { 132 return fmt.Sprintf("%-50s", msg) 133 } 134 135 func showTextResultsJSON(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult, verbose bool, out io.Writer) error { 136 output := showStdoutResultsStructured(preflightName, analyzeResults, verbose) 137 b, err := json.MarshalIndent(output, "", " ") 138 if err != nil { 139 return errors.Wrap(err, "failed to marshal results as json") 140 } 141 142 fmt.Fprintf(out, "%s\n", b) 143 if len(output.Fail) > 0 { 144 return errors.New(FailMessage) 145 } 146 return nil 147 } 148 149 func showStdoutResultsYAML(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult, verbose bool, out io.Writer) error { 150 data := showStdoutResultsStructured(preflightName, analyzeResults, verbose) 151 if len(data.Pass) > 0 { 152 fmt.Fprintln(out, printer.BoldGreen("Pass items")) 153 if b, err := yaml.Marshal(data.Pass); err != nil { 154 return errors.Wrap(err, "failed to marshal results as yaml") 155 } else { 156 fmt.Fprintf(out, "%s", b) 157 } 158 } 159 if len(data.Warn) > 0 { 160 fmt.Fprintln(out, printer.BoldYellow("Warn items")) 161 if b, err := yaml.Marshal(data.Warn); err != nil { 162 return errors.Wrap(err, "failed to marshal results as yaml") 163 } else { 164 fmt.Fprintf(out, "%s", b) 165 } 166 } 167 if len(data.Fail) > 0 { 168 fmt.Fprintln(out, printer.BoldRed("Pass items")) 169 if b, err := yaml.Marshal(data.Fail); err != nil { 170 return errors.Wrap(err, "failed to marshal results as yaml") 171 } else { 172 fmt.Fprintf(out, "%s", b) 173 } 174 return errors.New(FailMessage) 175 } 176 return nil 177 } 178 179 // showStdoutResultsStructured is Used by KBCLI, JSON and YAML outputs 180 func showStdoutResultsStructured(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult, verbose bool) TextOutput { 181 output := NewTextOutput() 182 for _, analyzeResult := range analyzeResults { 183 resultOutput := NewTextResultOutput(analyzeResult.Title, analyzeResult.Message, analyzeResult.URI) 184 if analyzeResult.Strict { 185 resultOutput.Strict = analyzeResult.Strict 186 } 187 switch { 188 case analyzeResult.IsPass: 189 if verbose { 190 output.Pass = append(output.Pass, resultOutput) 191 } 192 case analyzeResult.IsWarn: 193 output.Warn = append(output.Warn, resultOutput) 194 case analyzeResult.IsFail: 195 output.Fail = append(output.Fail, resultOutput) 196 } 197 } 198 return output 199 }