From 0a2ed01a46deebf98e12812316f48d1c47a6c51f Mon Sep 17 00:00:00 2001 From: Craig O'Donnell Date: Thu, 17 Feb 2022 17:28:28 -0500 Subject: [PATCH] improvement: added --output flag for preflight and support bundle (#538) * improvement: added --output flag for preflight and support bundle * improvement: added datetime to preflight default file name --- cmd/preflight/cli/interactive_results.go | 22 +++++++++++++++++----- cmd/preflight/cli/root.go | 1 + cmd/preflight/cli/run.go | 2 +- cmd/troubleshoot/cli/root.go | 1 + cmd/troubleshoot/cli/run.go | 1 + pkg/convert/output.go | 19 +++++++++++++++++++ pkg/supportbundle/supportbundle.go | 19 ++++++++++++++++--- 7 files changed, 56 insertions(+), 9 deletions(-) create mode 100644 pkg/convert/output.go diff --git a/cmd/preflight/cli/interactive_results.go b/cmd/preflight/cli/interactive_results.go index 1bb0f321..9098e117 100644 --- a/cmd/preflight/cli/interactive_results.go +++ b/cmd/preflight/cli/interactive_results.go @@ -4,7 +4,6 @@ import ( "fmt" "io/ioutil" "os" - "path" "time" "github.com/pkg/errors" @@ -12,6 +11,7 @@ import ( "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 ( @@ -20,7 +20,7 @@ var ( isShowingSaved = false ) -func showInteractiveResults(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) error { +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") } @@ -44,7 +44,7 @@ func showInteractiveResults(preflightName string, analyzeResults []*analyzerunne return nil } case "s": - filename, err := save(preflightName, analyzeResults) + filename, err := save(preflightName, outputPath, analyzeResults) if err != nil { // show } else { @@ -217,8 +217,20 @@ func estimateNumberOfLines(text string, width int) int { return lines } -func save(preflightName string, analyzeResults []*analyzerunner.AnalyzeResult) (string, error) { - filename := path.Join(util.HomeDir(), fmt.Sprintf("%s-results.txt", preflightName)) +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) diff --git a/cmd/preflight/cli/root.go b/cmd/preflight/cli/root.go index bd229164..39215337 100644 --- a/cmd/preflight/cli/root.go +++ b/cmd/preflight/cli/root.go @@ -38,6 +38,7 @@ that a cluster meets the requirements to run an application.`, 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") viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) diff --git a/cmd/preflight/cli/run.go b/cmd/preflight/cli/run.go index 2d2a0514..dc2c2b06 100644 --- a/cmd/preflight/cli/run.go +++ b/cmd/preflight/cli/run.go @@ -167,7 +167,7 @@ func runPreflights(v *viper.Viper, arg string) error { if len(analyzeResults) == 0 { return errors.New("no data has been collected") } - return showInteractiveResults(preflightSpecName, analyzeResults) + return showInteractiveResults(preflightSpecName, v.GetString("output"), analyzeResults) } return showStdoutResults(v.GetString("format"), preflightSpecName, analyzeResults) diff --git a/cmd/troubleshoot/cli/root.go b/cmd/troubleshoot/cli/root.go index 5e5db1a8..5698d341 100644 --- a/cmd/troubleshoot/cli/root.go +++ b/cmd/troubleshoot/cli/root.go @@ -42,6 +42,7 @@ from a server that can be used to assist when troubleshooting a Kubernetes clust cmd.Flags().Bool("collect-without-permissions", true, "always generate a support bundle, even if it some require additional permissions") 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 support bundle") // hidden in favor of the `insecure-skip-tls-verify` flag cmd.Flags().Bool("allow-insecure-connections", false, "when set, do not verify TLS certs when retrieving spec and reporting results") diff --git a/cmd/troubleshoot/cli/run.go b/cmd/troubleshoot/cli/run.go index 0662eb96..19ce0e92 100644 --- a/cmd/troubleshoot/cli/run.go +++ b/cmd/troubleshoot/cli/run.go @@ -181,6 +181,7 @@ func runTroubleshoot(v *viper.Viper, arg string) error { Namespace: v.GetString("namespace"), ProgressChan: progressChan, SinceTime: sinceTime, + OutputPath: v.GetString("output"), Redact: v.GetBool("redact"), FromCLI: true, } diff --git a/pkg/convert/output.go b/pkg/convert/output.go new file mode 100644 index 00000000..3b84774e --- /dev/null +++ b/pkg/convert/output.go @@ -0,0 +1,19 @@ +package convert + +import ( + "os" + "path/filepath" +) + +// ValidateOutputPath takes an output file path and returns it as an absolute path. +// It returns an error if the absolute path cannot be determined or if the parent directory does not exist. +func ValidateOutputPath(outputPath string) (string, error) { + outputPath, err := filepath.Abs(outputPath) + if err != nil { + return "", err + } + if _, err := os.Stat(filepath.Dir(outputPath)); err != nil { + return "", err + } + return outputPath, nil +} diff --git a/pkg/supportbundle/supportbundle.go b/pkg/supportbundle/supportbundle.go index 8300591e..d3ad1ce0 100644 --- a/pkg/supportbundle/supportbundle.go +++ b/pkg/supportbundle/supportbundle.go @@ -15,6 +15,7 @@ import ( analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze" troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2" "github.com/replicatedhq/troubleshoot/pkg/collect" + "github.com/replicatedhq/troubleshoot/pkg/convert" "k8s.io/client-go/rest" ) @@ -26,6 +27,7 @@ type SupportBundleCreateOpts struct { Namespace string ProgressChan chan interface{} SinceTime *time.Time + OutputPath string Redact bool FromCLI bool } @@ -57,9 +59,20 @@ func CollectSupportBundleFromSpec(spec *troubleshootv1beta2.SupportBundleSpec, a } defer os.RemoveAll(tmpDir) - basename := fmt.Sprintf("support-bundle-%s", time.Now().Format("2006-01-02T15_04_05")) - if !opts.FromCLI { - basename = filepath.Join(os.TempDir(), basename) + basename := "" + if opts.OutputPath != "" { + // use override output path + overridePath, err := convert.ValidateOutputPath(opts.OutputPath) + if err != nil { + return nil, errors.Wrap(err, "override output file path") + } + basename = strings.TrimSuffix(overridePath, ".tar.gz") + } else { + // use default output path + basename = fmt.Sprintf("support-bundle-%s", time.Now().Format("2006-01-02T15_04_05")) + if !opts.FromCLI { + basename = filepath.Join(os.TempDir(), basename) + } } filename, err := findFileName(basename, "tar.gz")