mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 18:29:53 +00:00
make runPreflight and preflight cli flags public (#769)
This commit is contained in:
@@ -1,294 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
ui "github.com/replicatedhq/termui/v3"
|
||||
"github.com/replicatedhq/termui/v3/widgets"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||
)
|
||||
|
||||
var (
|
||||
selectedResult = 0
|
||||
table = widgets.NewTable()
|
||||
isShowingSaved = false
|
||||
)
|
||||
|
||||
func showInteractiveResults(preflightName string, outputPath string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
if err := ui.Init(); err != nil {
|
||||
return errors.Wrap(err, "failed to create terminal ui")
|
||||
}
|
||||
defer ui.Close()
|
||||
|
||||
drawUI(preflightName, analyzeResults)
|
||||
|
||||
uiEvents := ui.PollEvents()
|
||||
for {
|
||||
select {
|
||||
case e := <-uiEvents:
|
||||
switch e.ID {
|
||||
case "<C-c>":
|
||||
return nil
|
||||
case "q":
|
||||
if isShowingSaved == true {
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
case "s":
|
||||
filename, err := save(preflightName, outputPath, analyzeResults)
|
||||
if err != nil {
|
||||
// show
|
||||
} else {
|
||||
showSaved(filename)
|
||||
go func() {
|
||||
time.Sleep(time.Second * 5)
|
||||
isShowingSaved = false
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
}()
|
||||
}
|
||||
case "<Resize>":
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
case "<Down>":
|
||||
if selectedResult < len(analyzeResults)-1 {
|
||||
selectedResult++
|
||||
} else {
|
||||
selectedResult = 0
|
||||
table.SelectedRow = 0
|
||||
}
|
||||
table.ScrollDown()
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
case "<Up>":
|
||||
if selectedResult > 0 {
|
||||
selectedResult--
|
||||
} else {
|
||||
selectedResult = len(analyzeResults) - 1
|
||||
table.SelectedRow = len(analyzeResults)
|
||||
}
|
||||
table.ScrollUp()
|
||||
ui.Clear()
|
||||
drawUI(preflightName, analyzeResults)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawUI(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
drawGrid(analyzeResults)
|
||||
drawHeader(preflightName)
|
||||
drawFooter()
|
||||
}
|
||||
|
||||
func drawGrid(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
drawPreflightTable(analyzeResults)
|
||||
drawDetails(analyzeResults[selectedResult])
|
||||
}
|
||||
|
||||
func drawHeader(preflightName string) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = fmt.Sprintf("%s Preflight Checks", util.AppName(preflightName))
|
||||
title.TextStyle.Fg = ui.ColorWhite
|
||||
title.TextStyle.Bg = ui.ColorClear
|
||||
title.TextStyle.Modifier = ui.ModifierBold
|
||||
title.Border = false
|
||||
|
||||
left := termWidth/2 - 2*len(title.Text)/3
|
||||
right := termWidth/2 + (termWidth/2 - left)
|
||||
|
||||
title.SetRect(left, 0, right, 1)
|
||||
ui.Render(title)
|
||||
}
|
||||
|
||||
func drawFooter() {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
instructions := widgets.NewParagraph()
|
||||
instructions.Text = "[q] quit [s] save [↑][↓] scroll"
|
||||
instructions.Border = false
|
||||
|
||||
left := 0
|
||||
right := termWidth
|
||||
top := termHeight - 1
|
||||
bottom := termHeight
|
||||
|
||||
instructions.SetRect(left, top, right, bottom)
|
||||
ui.Render(instructions)
|
||||
}
|
||||
|
||||
func drawPreflightTable(analyzeResults []*analyzerunner.AnalyzeResult) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
table.SetRect(0, 3, termWidth/2, termHeight-6)
|
||||
table.FillRow = true
|
||||
table.Border = true
|
||||
table.Rows = [][]string{}
|
||||
table.ColumnWidths = []int{termWidth}
|
||||
|
||||
for i, analyzeResult := range analyzeResults {
|
||||
title := analyzeResult.Title
|
||||
if analyzeResult.Strict {
|
||||
title = title + fmt.Sprintf(" (Strict: %t)", analyzeResult.Strict)
|
||||
}
|
||||
if analyzeResult.IsPass {
|
||||
title = fmt.Sprintf("✔ %s", title)
|
||||
} else if analyzeResult.IsWarn {
|
||||
title = fmt.Sprintf("⚠️ %s", title)
|
||||
} else if analyzeResult.IsFail {
|
||||
title = fmt.Sprintf("✘ %s", title)
|
||||
}
|
||||
table.Rows = append(table.Rows, []string{
|
||||
title,
|
||||
})
|
||||
|
||||
if analyzeResult.IsPass {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorGreen, ui.ColorClear)
|
||||
}
|
||||
} else if analyzeResult.IsWarn {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorYellow, ui.ColorClear)
|
||||
}
|
||||
} else if analyzeResult.IsFail {
|
||||
if i == selectedResult {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierReverse)
|
||||
} else {
|
||||
table.RowStyles[i] = ui.NewStyle(ui.ColorRed, ui.ColorClear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui.Render(table)
|
||||
}
|
||||
|
||||
func drawDetails(analysisResult *analyzerunner.AnalyzeResult) {
|
||||
termWidth, _ := ui.TerminalDimensions()
|
||||
|
||||
currentTop := 4
|
||||
title := widgets.NewParagraph()
|
||||
title.Text = analysisResult.Title
|
||||
title.Border = false
|
||||
if analysisResult.IsPass {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorGreen, ui.ColorClear, ui.ModifierBold)
|
||||
} else if analysisResult.IsWarn {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorYellow, ui.ColorClear, ui.ModifierBold)
|
||||
} else if analysisResult.IsFail {
|
||||
title.TextStyle = ui.NewStyle(ui.ColorRed, ui.ColorClear, ui.ModifierBold)
|
||||
}
|
||||
height := estimateNumberOfLines(title.Text, termWidth/2)
|
||||
title.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(title)
|
||||
currentTop = currentTop + height + 1
|
||||
|
||||
message := widgets.NewParagraph()
|
||||
message.Text = analysisResult.Message
|
||||
message.Border = false
|
||||
height = estimateNumberOfLines(message.Text, termWidth/2) + 2
|
||||
message.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(message)
|
||||
currentTop = currentTop + height + 1
|
||||
|
||||
if analysisResult.URI != "" {
|
||||
uri := widgets.NewParagraph()
|
||||
uri.Text = fmt.Sprintf("For more information: %s", analysisResult.URI)
|
||||
uri.Border = false
|
||||
height = estimateNumberOfLines(uri.Text, termWidth/2)
|
||||
uri.SetRect(termWidth/2, currentTop, termWidth, currentTop+height)
|
||||
ui.Render(uri)
|
||||
currentTop = currentTop + height + 1
|
||||
}
|
||||
}
|
||||
|
||||
func estimateNumberOfLines(text string, width int) int {
|
||||
lines := len(text)/width + 1
|
||||
return lines
|
||||
}
|
||||
|
||||
func save(preflightName string, outputPath string, analyzeResults []*analyzerunner.AnalyzeResult) (string, error) {
|
||||
filename := ""
|
||||
if outputPath != "" {
|
||||
// use override output path
|
||||
overridePath, err := convert.ValidateOutputPath(outputPath)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "override output file path")
|
||||
}
|
||||
filename = overridePath
|
||||
} else {
|
||||
// use default output path
|
||||
filename = fmt.Sprintf("%s-results-%s.txt", preflightName, time.Now().Format("2006-01-02T15_04_05"))
|
||||
}
|
||||
|
||||
_, err := os.Stat(filename)
|
||||
if err == nil {
|
||||
os.Remove(filename)
|
||||
}
|
||||
|
||||
results := fmt.Sprintf("%s Preflight Checks\n\n", util.AppName(preflightName))
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
result := ""
|
||||
|
||||
if analyzeResult.IsPass {
|
||||
result = "Check PASS\n"
|
||||
} else if analyzeResult.IsWarn {
|
||||
result = "Check WARN\n"
|
||||
} else if analyzeResult.IsFail {
|
||||
result = "Check FAIL\n"
|
||||
}
|
||||
|
||||
result = result + fmt.Sprintf("Title: %s\n", analyzeResult.Title)
|
||||
result = result + fmt.Sprintf("Message: %s\n", analyzeResult.Message)
|
||||
|
||||
if analyzeResult.URI != "" {
|
||||
result = result + fmt.Sprintf("URI: %s\n", analyzeResult.URI)
|
||||
}
|
||||
|
||||
if analyzeResult.Strict {
|
||||
result = result + fmt.Sprintf("Strict: %t\n", analyzeResult.Strict)
|
||||
}
|
||||
|
||||
result = result + "\n------------\n"
|
||||
|
||||
results = results + result
|
||||
}
|
||||
|
||||
if err := ioutil.WriteFile(filename, []byte(results), 0644); err != nil {
|
||||
return "", errors.Wrap(err, "failed to save preflight results")
|
||||
}
|
||||
|
||||
return filename, nil
|
||||
}
|
||||
|
||||
func showSaved(filename string) {
|
||||
termWidth, termHeight := ui.TerminalDimensions()
|
||||
|
||||
savedMessage := widgets.NewParagraph()
|
||||
savedMessage.Text = fmt.Sprintf("Preflight results saved to\n\n%s", filename)
|
||||
savedMessage.WrapText = true
|
||||
savedMessage.Border = true
|
||||
|
||||
left := termWidth/2 - 20
|
||||
right := termWidth/2 + 20
|
||||
top := termHeight/2 - 4
|
||||
bottom := termHeight/2 + 4
|
||||
|
||||
savedMessage.SetRect(left, top, right, bottom)
|
||||
ui.Render(savedMessage)
|
||||
|
||||
isShowingSaved = true
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"k8s.io/klog/v2"
|
||||
@@ -29,24 +30,14 @@ that a cluster meets the requirements to run an application.`,
|
||||
},
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
v := viper.GetViper()
|
||||
return runPreflights(v, args[0])
|
||||
return preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args[0])
|
||||
},
|
||||
}
|
||||
|
||||
cobra.OnInitialize(initConfig)
|
||||
|
||||
cmd.AddCommand(VersionCmd())
|
||||
|
||||
cmd.Flags().Bool("interactive", true, "interactive preflights")
|
||||
cmd.Flags().String("format", "human", "output format, one of human, json, yaml. only used when interactive is set to false")
|
||||
cmd.Flags().String("collector-image", "", "the full name of the collector image to use")
|
||||
cmd.Flags().String("collector-pullpolicy", "", "the pull policy of the collector image")
|
||||
cmd.Flags().Bool("collect-without-permissions", true, "always run preflight checks even if some require permissions that preflight does not have")
|
||||
cmd.Flags().String("selector", "", "selector (label query) to filter remote collection nodes on.")
|
||||
cmd.Flags().String("since-time", "", "force pod logs collectors to return logs after a specific date (RFC3339)")
|
||||
cmd.Flags().String("since", "", "force pod logs collectors to return logs newer than a relative duration like 5s, 2m, or 3h.")
|
||||
cmd.Flags().StringP("output", "o", "", "specify the output file path for the preflight checks")
|
||||
cmd.Flags().Bool("debug", false, "enable debug logging")
|
||||
preflight.AddFlags(cmd.PersistentFlags())
|
||||
|
||||
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
|
||||
|
||||
|
||||
@@ -1,373 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
cursor "github.com/ahmetalpbalkan/go-cursor"
|
||||
"github.com/fatih/color"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/replicatedhq/troubleshoot/cmd/util"
|
||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||
troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/docrewrite"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/oci"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/specs"
|
||||
"github.com/spf13/viper"
|
||||
spin "github.com/tj/go-spin"
|
||||
"golang.org/x/sync/errgroup"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
)
|
||||
|
||||
func runPreflights(v *viper.Viper, arg string) error {
|
||||
if v.GetBool("interactive") {
|
||||
fmt.Print(cursor.Hide())
|
||||
defer fmt.Print(cursor.Show())
|
||||
}
|
||||
|
||||
go func() {
|
||||
signalChan := make(chan os.Signal, 1)
|
||||
signal.Notify(signalChan, os.Interrupt)
|
||||
<-signalChan
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
var preflightContent []byte
|
||||
var err error
|
||||
if strings.HasPrefix(arg, "secret/") {
|
||||
// format secret/namespace-name/secret-name
|
||||
pathParts := strings.Split(arg, "/")
|
||||
if len(pathParts) != 3 {
|
||||
return errors.Errorf("path %s must have 3 components", arg)
|
||||
}
|
||||
|
||||
spec, err := specs.LoadFromSecret(pathParts[1], pathParts[2], "preflight-spec")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get spec from secret")
|
||||
}
|
||||
|
||||
preflightContent = spec
|
||||
} else if _, err = os.Stat(arg); err == nil {
|
||||
b, err := ioutil.ReadFile(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = b
|
||||
} else {
|
||||
u, err := url.Parse(arg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if u.Scheme == "oci" {
|
||||
content, err := oci.PullPreflightFromOCI(arg)
|
||||
if err != nil {
|
||||
if err == oci.ErrNoRelease {
|
||||
return errors.Errorf("no release found for %s.\nCheck the oci:// uri for errors or contact the application vendor for support.", arg)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = content
|
||||
} else {
|
||||
if !util.IsURL(arg) {
|
||||
return fmt.Errorf("%s is not a URL and was not found (err %s)", arg, err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", arg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Replicated_Preflight/v1beta2")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preflightContent = body
|
||||
}
|
||||
}
|
||||
|
||||
preflightContent, err = docrewrite.ConvertToV1Beta2(preflightContent)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to convert to v1beta2")
|
||||
}
|
||||
|
||||
troubleshootclientsetscheme.AddToScheme(scheme.Scheme)
|
||||
decode := scheme.Codecs.UniversalDeserializer().Decode
|
||||
obj, _, err := decode([]byte(preflightContent), nil, nil)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to parse %s", arg)
|
||||
}
|
||||
|
||||
var collectResults []preflight.CollectResult
|
||||
preflightSpecName := ""
|
||||
|
||||
progressCh := make(chan interface{})
|
||||
defer close(progressCh)
|
||||
|
||||
ctx, stopProgressCollection := context.WithCancel(context.Background())
|
||||
// make sure we shut down progress collection goroutines if an error occurs
|
||||
defer stopProgressCollection()
|
||||
progressCollection, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
if v.GetBool("interactive") {
|
||||
progressCollection.Go(collectInteractiveProgress(ctx, progressCh))
|
||||
} else {
|
||||
progressCollection.Go(collectNonInteractiveProgess(ctx, progressCh))
|
||||
}
|
||||
|
||||
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
|
||||
r, err := collectInCluster(preflightSpec, progressCh)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect in cluster")
|
||||
}
|
||||
collectResults = append(collectResults, *r)
|
||||
preflightSpecName = preflightSpec.Name
|
||||
} else if hostPreflightSpec, ok := obj.(*troubleshootv1beta2.HostPreflight); ok {
|
||||
if len(hostPreflightSpec.Spec.Collectors) > 0 {
|
||||
r, err := collectHost(hostPreflightSpec, progressCh)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect from host")
|
||||
}
|
||||
collectResults = append(collectResults, *r)
|
||||
}
|
||||
if len(hostPreflightSpec.Spec.RemoteCollectors) > 0 {
|
||||
r, err := collectRemote(hostPreflightSpec, progressCh)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to collect remotely")
|
||||
}
|
||||
collectResults = append(collectResults, *r)
|
||||
}
|
||||
preflightSpecName = hostPreflightSpec.Name
|
||||
}
|
||||
|
||||
if collectResults == nil {
|
||||
return errors.New("no results")
|
||||
}
|
||||
|
||||
analyzeResults := []*analyzer.AnalyzeResult{}
|
||||
for _, res := range collectResults {
|
||||
analyzeResults = append(analyzeResults, res.Analyze()...)
|
||||
}
|
||||
|
||||
if preflightSpec, ok := obj.(*troubleshootv1beta2.Preflight); ok {
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
err := uploadResults(preflightSpec.Spec.UploadResultsTo, analyzeResults)
|
||||
if err != nil {
|
||||
progressCh <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stopProgressCollection()
|
||||
progressCollection.Wait()
|
||||
|
||||
if v.GetBool("interactive") {
|
||||
if len(analyzeResults) == 0 {
|
||||
return errors.New("no data has been collected")
|
||||
}
|
||||
return showInteractiveResults(preflightSpecName, v.GetString("output"), analyzeResults)
|
||||
}
|
||||
|
||||
return showStdoutResults(v.GetString("format"), preflightSpecName, analyzeResults)
|
||||
}
|
||||
|
||||
func collectInteractiveProgress(ctx context.Context, progressCh <-chan interface{}) func() error {
|
||||
return func() error {
|
||||
spinner := spin.New()
|
||||
lastMsg := ""
|
||||
|
||||
errorTxt := color.New(color.FgHiRed)
|
||||
infoTxt := color.New(color.FgCyan)
|
||||
|
||||
for {
|
||||
select {
|
||||
case msg := <-progressCh:
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
errorTxt.Printf("%s\r * %v\n", cursor.ClearEntireLine(), msg)
|
||||
case string:
|
||||
if lastMsg == msg {
|
||||
break
|
||||
}
|
||||
lastMsg = msg
|
||||
infoTxt.Printf("%s\r * %s\n", cursor.ClearEntireLine(), msg)
|
||||
|
||||
}
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
fmt.Printf("\r %s %s ", color.CyanString("Running Preflight Checks"), spinner.Next())
|
||||
case <-ctx.Done():
|
||||
fmt.Printf("\r%s\r", cursor.ClearEntireLine())
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectNonInteractiveProgess(ctx context.Context, progressCh <-chan interface{}) func() error {
|
||||
return func() error {
|
||||
for {
|
||||
select {
|
||||
case msg := <-progressCh:
|
||||
switch msg := msg.(type) {
|
||||
case error:
|
||||
fmt.Fprintf(os.Stderr, "error - %v\n", msg)
|
||||
case string:
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg)
|
||||
case preflight.CollectProgress:
|
||||
fmt.Fprintf(os.Stderr, "%s\n", msg.String())
|
||||
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func collectInCluster(preflightSpec *troubleshootv1beta2.Preflight, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
v := viper.GetViper()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
collectOpts := preflight.CollectOpts{
|
||||
Namespace: v.GetString("namespace"),
|
||||
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
||||
ProgressChan: progressCh,
|
||||
KubernetesRestConfig: restConfig,
|
||||
}
|
||||
|
||||
if v.GetString("since") != "" || v.GetString("since-time") != "" {
|
||||
err := parseTimeFlags(v, preflightSpec.Spec.Collectors)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
collectResults, err := preflight.Collect(collectOpts, preflightSpec)
|
||||
if err != nil {
|
||||
if !collectResults.IsRBACAllowed() {
|
||||
if preflightSpec.Spec.UploadResultsTo != "" {
|
||||
clusterCollectResults := collectResults.(preflight.ClusterCollectResult)
|
||||
err := uploadErrors(preflightSpec.Spec.UploadResultsTo, clusterCollectResults.Collectors)
|
||||
if err != nil {
|
||||
progressCh <- err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
func collectRemote(preflightSpec *troubleshootv1beta2.HostPreflight, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
v := viper.GetViper()
|
||||
|
||||
restConfig, err := k8sutil.GetRESTConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to convert kube flags to rest config")
|
||||
}
|
||||
|
||||
labelSelector, err := labels.Parse(v.GetString("selector"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "unable to parse selector")
|
||||
}
|
||||
|
||||
namespace := v.GetString("namespace")
|
||||
if namespace == "" {
|
||||
namespace = "default"
|
||||
}
|
||||
|
||||
timeout := v.GetDuration("request-timeout")
|
||||
if timeout == 0 {
|
||||
timeout = 30 * time.Second
|
||||
}
|
||||
|
||||
collectOpts := preflight.CollectOpts{
|
||||
Namespace: namespace,
|
||||
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
||||
ProgressChan: progressCh,
|
||||
KubernetesRestConfig: restConfig,
|
||||
Image: v.GetString("collector-image"),
|
||||
PullPolicy: v.GetString("collector-pullpolicy"),
|
||||
LabelSelector: labelSelector.String(),
|
||||
Timeout: timeout,
|
||||
}
|
||||
|
||||
collectResults, err := preflight.CollectRemote(collectOpts, preflightSpec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to collect from remote")
|
||||
}
|
||||
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
func collectHost(hostPreflightSpec *troubleshootv1beta2.HostPreflight, progressCh chan interface{}) (*preflight.CollectResult, error) {
|
||||
collectOpts := preflight.CollectOpts{
|
||||
ProgressChan: progressCh,
|
||||
}
|
||||
|
||||
collectResults, err := preflight.CollectHost(collectOpts, hostPreflightSpec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to collect from host")
|
||||
}
|
||||
|
||||
return &collectResults, nil
|
||||
}
|
||||
|
||||
func parseTimeFlags(v *viper.Viper, collectors []*troubleshootv1beta2.Collect) error {
|
||||
var (
|
||||
sinceTime time.Time
|
||||
err error
|
||||
)
|
||||
if v.GetString("since-time") != "" {
|
||||
if v.GetString("since") != "" {
|
||||
return errors.Errorf("at most one of `sinceTime` or `since` may be specified")
|
||||
}
|
||||
sinceTime, err = time.Parse(time.RFC3339, v.GetString("since-time"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since-time flag")
|
||||
}
|
||||
} else {
|
||||
parsedDuration, err := time.ParseDuration(v.GetString("since"))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "unable to parse --since flag")
|
||||
}
|
||||
now := time.Now()
|
||||
sinceTime = now.Add(0 - parsedDuration)
|
||||
}
|
||||
for _, collector := range collectors {
|
||||
if collector.Logs != nil {
|
||||
if collector.Logs.Limits == nil {
|
||||
collector.Logs.Limits = new(troubleshootv1beta2.LogLimits)
|
||||
}
|
||||
collector.Logs.Limits.SinceTime = metav1.NewTime(sinceTime)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
)
|
||||
|
||||
func showStdoutResults(format string, preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
if format == "human" {
|
||||
return showStdoutResultsHuman(preflightName, analyzeResults)
|
||||
} else if format == "json" {
|
||||
return showStdoutResultsJSON(preflightName, analyzeResults)
|
||||
}
|
||||
|
||||
return errors.Errorf("unknown output format: %q", format)
|
||||
}
|
||||
|
||||
func showStdoutResultsHuman(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
fmt.Println("")
|
||||
var failed bool
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
testResultfailed := outputResult(analyzeResult)
|
||||
if testResultfailed {
|
||||
failed = true
|
||||
}
|
||||
}
|
||||
if failed {
|
||||
fmt.Printf("--- FAIL %s\n", preflightName)
|
||||
fmt.Println("FAILED")
|
||||
} else {
|
||||
fmt.Printf("--- PASS %s\n", preflightName)
|
||||
fmt.Println("PASS")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func showStdoutResultsJSON(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
type ResultOutput struct {
|
||||
Title string `json:"title"`
|
||||
Message string `json:"message"`
|
||||
URI string `json:"uri,omitempty"`
|
||||
Strict bool `json:"strict,omitempty"`
|
||||
}
|
||||
type Output struct {
|
||||
Pass []ResultOutput `json:"pass,omitempty"`
|
||||
Warn []ResultOutput `json:"warn,omitempty"`
|
||||
Fail []ResultOutput `json:"fail,omitempty"`
|
||||
}
|
||||
|
||||
output := Output{
|
||||
Pass: []ResultOutput{},
|
||||
Warn: []ResultOutput{},
|
||||
Fail: []ResultOutput{},
|
||||
}
|
||||
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
resultOutput := ResultOutput{
|
||||
Title: analyzeResult.Title,
|
||||
Message: analyzeResult.Message,
|
||||
URI: analyzeResult.URI,
|
||||
}
|
||||
|
||||
if analyzeResult.Strict {
|
||||
resultOutput.Strict = analyzeResult.Strict
|
||||
}
|
||||
|
||||
if analyzeResult.IsPass {
|
||||
output.Pass = append(output.Pass, resultOutput)
|
||||
} else if analyzeResult.IsWarn {
|
||||
output.Warn = append(output.Warn, resultOutput)
|
||||
} else if analyzeResult.IsFail {
|
||||
output.Fail = append(output.Fail, resultOutput)
|
||||
}
|
||||
}
|
||||
|
||||
b, err := json.MarshalIndent(output, "", " ")
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal results")
|
||||
}
|
||||
|
||||
fmt.Printf("%s\n", b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func outputResult(analyzeResult *analyzerunner.AnalyzeResult) bool {
|
||||
if analyzeResult.IsPass {
|
||||
fmt.Printf(" --- PASS %s\n", analyzeResult.Title)
|
||||
fmt.Printf(" --- %s\n", analyzeResult.Message)
|
||||
} else if analyzeResult.IsWarn {
|
||||
fmt.Printf(" --- WARN: %s\n", analyzeResult.Title)
|
||||
fmt.Printf(" --- %s\n", analyzeResult.Message)
|
||||
} else if analyzeResult.IsFail {
|
||||
fmt.Printf(" --- FAIL: %s\n", analyzeResult.Title)
|
||||
fmt.Printf(" --- %s\n", analyzeResult.Message)
|
||||
}
|
||||
|
||||
if analyzeResult.Strict {
|
||||
fmt.Printf(" --- Strict: %t\n", analyzeResult.Strict)
|
||||
}
|
||||
|
||||
if analyzeResult.IsFail {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||
"github.com/replicatedhq/troubleshoot/pkg/preflight"
|
||||
)
|
||||
|
||||
func uploadResults(uri string, analyzeResults []*analyzerunner.AnalyzeResult) error {
|
||||
uploadPreflightResults := &preflight.UploadPreflightResults{
|
||||
Results: []*preflight.UploadPreflightResult{},
|
||||
}
|
||||
for _, analyzeResult := range analyzeResults {
|
||||
uploadPreflightResult := &preflight.UploadPreflightResult{
|
||||
Strict: analyzeResult.Strict,
|
||||
IsFail: analyzeResult.IsFail,
|
||||
IsWarn: analyzeResult.IsWarn,
|
||||
IsPass: analyzeResult.IsPass,
|
||||
Title: analyzeResult.Title,
|
||||
Message: analyzeResult.Message,
|
||||
URI: analyzeResult.URI,
|
||||
}
|
||||
|
||||
uploadPreflightResults.Results = append(uploadPreflightResults.Results, uploadPreflightResult)
|
||||
}
|
||||
|
||||
return upload(uri, uploadPreflightResults)
|
||||
}
|
||||
|
||||
func uploadErrors(uri string, collectors []collect.Collector) error {
|
||||
errors := []*preflight.UploadPreflightError{}
|
||||
for _, collector := range collectors {
|
||||
for _, e := range collector.GetRBACErrors() {
|
||||
errors = append(errors, &preflight.UploadPreflightError{
|
||||
Error: e.Error(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
results := &preflight.UploadPreflightResults{
|
||||
Errors: errors,
|
||||
}
|
||||
|
||||
return upload(uri, results)
|
||||
}
|
||||
|
||||
func upload(uri string, payload *preflight.UploadPreflightResults) error {
|
||||
b, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to marshal payload")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("POST", uri, bytes.NewBuffer(b))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to create request")
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
client := http.DefaultClient
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to execute request")
|
||||
}
|
||||
|
||||
if resp.StatusCode > 290 {
|
||||
return errors.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user