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  }