mirror of
https://github.com/replicatedhq/troubleshoot.git
synced 2026-02-14 10:19:54 +00:00
feat: Optionally save preflight bundles to disk (#1612)
* feat: Optionally save preflight bundles to disk Signed-off-by: Evans Mungai <evans@replicated.com> * Add e2e test of saving preflight bundle Signed-off-by: Evans Mungai <evans@replicated.com> * Update cli docs Signed-off-by: Evans Mungai <evans@replicated.com> * Expose GetVersionFile function publicly Signed-off-by: Evans Mungai <evans@replicated.com> * Store analysis.json file in preflight bundle Signed-off-by: Evans Mungai <evans@replicated.com> * Run go fmt when running lint fixers Signed-off-by: Evans Mungai <evans@replicated.com> * Always generate a preflight bundle in CLI Signed-off-by: Evans Mungai <evans@replicated.com> * Print saving bundle message to stderr Signed-off-by: Evans Mungai <evans@replicated.com> * Revert changes in docs directory Signed-off-by: Evans Mungai <evans@replicated.com> * Use NewResult constructor Signed-off-by: Evans Mungai <evans@replicated.com> * Log always when preflight bundle is saved to disk Signed-off-by: Evans Mungai <evans@replicated.com> --------- Signed-off-by: Evans Mungai <evans@replicated.com>
This commit is contained in:
2
Makefile
2
Makefile
@@ -239,7 +239,7 @@ scan:
|
|||||||
lint:
|
lint:
|
||||||
golangci-lint run --new -c .golangci.yaml ${BUILDPATHS}
|
golangci-lint run --new -c .golangci.yaml ${BUILDPATHS}
|
||||||
|
|
||||||
.PHONY: lint-and-fix
|
.PHONY: fmt lint-and-fix
|
||||||
lint-and-fix:
|
lint-and-fix:
|
||||||
golangci-lint run --new --fix -c .golangci.yaml ${BUILDPATHS}
|
golangci-lint run --new --fix -c .golangci.yaml ${BUILDPATHS}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ that a cluster meets the requirements to run an application.`,
|
|||||||
|
|
||||||
err = preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args)
|
err = preflight.RunPreflights(v.GetBool("interactive"), v.GetString("output"), v.GetString("format"), args)
|
||||||
if !v.GetBool("dry-run") && (v.GetBool("debug") || v.IsSet("v")) {
|
if !v.GetBool("dry-run") && (v.GetBool("debug") || v.IsSet("v")) {
|
||||||
fmt.Printf("\n%s", traces.GetExporterInstance().GetSummary())
|
fmt.Fprintf(os.Stderr, "\n%s", traces.GetExporterInstance().GetSummary())
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ For more information on redactors visit https://troubleshoot.sh/docs/redact/
|
|||||||
if output == "" {
|
if output == "" {
|
||||||
output = fmt.Sprintf("redacted-support-bundle-%s.tar.gz", time.Now().Format("2006-01-02T15_04_05"))
|
output = fmt.Sprintf("redacted-support-bundle-%s.tar.gz", time.Now().Format("2006-01-02T15_04_05"))
|
||||||
}
|
}
|
||||||
err = collectorResult.ArchiveSupportBundle(bundleDir, output)
|
err = collectorResult.ArchiveBundle(bundleDir, output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to create support bundle archive")
|
return errors.Wrap(err, "failed to create support bundle archive")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ If no arguments are provided, specs are automatically loaded from the cluster by
|
|||||||
|
|
||||||
err = runTroubleshoot(v, args)
|
err = runTroubleshoot(v, args)
|
||||||
if !v.IsSet("dry-run") && (v.GetBool("debug") || v.IsSet("v")) {
|
if !v.IsSet("dry-run") && (v.GetBool("debug") || v.IsSet("v")) {
|
||||||
fmt.Printf("\n%s", traces.GetExporterInstance().GetSummary())
|
fmt.Fprintf(os.Stderr, "\n%s", traces.GetExporterInstance().GetSummary())
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -270,7 +270,14 @@ func (r CollectorResult) CloseWriter(bundlePath string, relativePath string, wri
|
|||||||
return errors.Errorf("cannot close writer of type %T", writer)
|
return errors.Errorf("cannot close writer of type %T", writer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ArchiveSupportBundle creates an archive of the files in the bundle directory
|
||||||
|
// Deprecated: Use better named ArchiveBundle since this method is used to archive any directory
|
||||||
func (r CollectorResult) ArchiveSupportBundle(bundlePath string, outputFilename string) error {
|
func (r CollectorResult) ArchiveSupportBundle(bundlePath string, outputFilename string) error {
|
||||||
|
return r.ArchiveBundle(bundlePath, outputFilename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArchiveBundle creates an archive of the files in the bundle directory
|
||||||
|
func (r CollectorResult) ArchiveBundle(bundlePath string, outputFilename string) error {
|
||||||
fileWriter, err := os.Create(outputFilename)
|
fileWriter, err := os.Create(outputFilename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to create output file")
|
return errors.Wrap(err, "failed to create output file")
|
||||||
@@ -404,5 +411,5 @@ func CollectorResultFromBundle(bundleDir string) (CollectorResult, error) {
|
|||||||
// Deprecated: Remove in a future version (v1.0)
|
// Deprecated: Remove in a future version (v1.0)
|
||||||
func TarSupportBundleDir(bundlePath string, input CollectorResult, outputFilename string) error {
|
func TarSupportBundleDir(bundlePath string, input CollectorResult, outputFilename string) error {
|
||||||
// Is this used anywhere external anyway?
|
// Is this used anywhere external anyway?
|
||||||
return input.ArchiveSupportBundle(bundlePath, outputFilename)
|
return input.ArchiveBundle(bundlePath, outputFilename)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const (
|
|||||||
LIB_TRACER_NAME = "github.com/replicatedhq/troubleshoot"
|
LIB_TRACER_NAME = "github.com/replicatedhq/troubleshoot"
|
||||||
TROUBLESHOOT_ROOT_SPAN_NAME = "ReplicatedTroubleshootRootSpan"
|
TROUBLESHOOT_ROOT_SPAN_NAME = "ReplicatedTroubleshootRootSpan"
|
||||||
EXCLUDED = "excluded"
|
EXCLUDED = "excluded"
|
||||||
|
ANALYSIS_FILENAME = "analysis.json"
|
||||||
|
|
||||||
// Cluster Resources Collector Directories
|
// Cluster Resources Collector Directories
|
||||||
CLUSTER_RESOURCES_DIR = "cluster-resources"
|
CLUSTER_RESOURCES_DIR = "cluster-resources"
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ type CollectOpts struct {
|
|||||||
LabelSelector string
|
LabelSelector string
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
ProgressChan chan interface{}
|
ProgressChan chan interface{}
|
||||||
|
|
||||||
|
// Optional path to the bundle directory to store the collected data
|
||||||
|
BundlePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CollectProgress struct {
|
type CollectProgress struct {
|
||||||
@@ -96,7 +99,7 @@ func CollectHost(opts CollectOpts, p *troubleshootv1beta2.HostPreflight) (Collec
|
|||||||
func CollectHostWithContext(
|
func CollectHostWithContext(
|
||||||
ctx context.Context, opts CollectOpts, p *troubleshootv1beta2.HostPreflight,
|
ctx context.Context, opts CollectOpts, p *troubleshootv1beta2.HostPreflight,
|
||||||
) (CollectResult, error) {
|
) (CollectResult, error) {
|
||||||
collectSpecs := make([]*troubleshootv1beta2.HostCollect, 0, 0)
|
collectSpecs := make([]*troubleshootv1beta2.HostCollect, 0)
|
||||||
if p != nil && p.Spec.Collectors != nil {
|
if p != nil && p.Spec.Collectors != nil {
|
||||||
collectSpecs = append(collectSpecs, p.Spec.Collectors...)
|
collectSpecs = append(collectSpecs, p.Spec.Collectors...)
|
||||||
}
|
}
|
||||||
@@ -105,7 +108,7 @@ func CollectHostWithContext(
|
|||||||
|
|
||||||
var collectors []collect.HostCollector
|
var collectors []collect.HostCollector
|
||||||
for _, desiredCollector := range collectSpecs {
|
for _, desiredCollector := range collectSpecs {
|
||||||
collector, ok := collect.GetHostCollector(desiredCollector, "")
|
collector, ok := collect.GetHostCollector(desiredCollector, opts.BundlePath)
|
||||||
if ok {
|
if ok {
|
||||||
collectors = append(collectors, collector)
|
collectors = append(collectors, collector)
|
||||||
}
|
}
|
||||||
@@ -140,6 +143,7 @@ func CollectHostWithContext(
|
|||||||
span.End()
|
span.End()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The values of map entries will contain the collected data in bytes if the data was not stored to disk
|
||||||
collectResult.AllCollectedData = allCollectedData
|
collectResult.AllCollectedData = allCollectedData
|
||||||
|
|
||||||
return collectResult, nil
|
return collectResult, nil
|
||||||
@@ -154,7 +158,7 @@ func CollectWithContext(ctx context.Context, opts CollectOpts, p *troubleshootv1
|
|||||||
var allCollectors []collect.Collector
|
var allCollectors []collect.Collector
|
||||||
var foundForbidden bool
|
var foundForbidden bool
|
||||||
|
|
||||||
collectSpecs := make([]*troubleshootv1beta2.Collect, 0, 0)
|
collectSpecs := make([]*troubleshootv1beta2.Collect, 0)
|
||||||
if p != nil && p.Spec.Collectors != nil {
|
if p != nil && p.Spec.Collectors != nil {
|
||||||
collectSpecs = append(collectSpecs, p.Spec.Collectors...)
|
collectSpecs = append(collectSpecs, p.Spec.Collectors...)
|
||||||
}
|
}
|
||||||
@@ -180,7 +184,7 @@ func CollectWithContext(ctx context.Context, opts CollectOpts, p *troubleshootv1
|
|||||||
allCollectedData := make(map[string][]byte)
|
allCollectedData := make(map[string][]byte)
|
||||||
|
|
||||||
for _, desiredCollector := range collectSpecs {
|
for _, desiredCollector := range collectSpecs {
|
||||||
if collectorInterface, ok := collect.GetCollector(desiredCollector, "", opts.Namespace, opts.KubernetesRestConfig, k8sClient, nil); ok {
|
if collectorInterface, ok := collect.GetCollector(desiredCollector, opts.BundlePath, opts.Namespace, opts.KubernetesRestConfig, k8sClient, nil); ok {
|
||||||
if collector, ok := collectorInterface.(collect.Collector); ok {
|
if collector, ok := collectorInterface.(collect.Collector); ok {
|
||||||
err := collector.CheckRBAC(ctx, collector, desiredCollector, opts.KubernetesRestConfig, opts.Namespace)
|
err := collector.CheckRBAC(ctx, collector, desiredCollector, opts.KubernetesRestConfig, opts.Namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -305,6 +309,7 @@ func CollectWithContext(ctx context.Context, opts CollectOpts, p *troubleshootv1
|
|||||||
span.End()
|
span.End()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The values of map entries will contain the collected data in bytes if the data was not stored to disk
|
||||||
collectResult.AllCollectedData = allCollectedData
|
collectResult.AllCollectedData = allCollectedData
|
||||||
|
|
||||||
return collectResult, nil
|
return collectResult, nil
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
package preflight
|
package preflight
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
cursor "github.com/ahmetalpbalkan/go-cursor"
|
cursor "github.com/ahmetalpbalkan/go-cursor"
|
||||||
@@ -13,15 +16,19 @@ import (
|
|||||||
"github.com/replicatedhq/troubleshoot/internal/util"
|
"github.com/replicatedhq/troubleshoot/internal/util"
|
||||||
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
|
||||||
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||||
|
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||||
|
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||||
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
"github.com/replicatedhq/troubleshoot/pkg/k8sutil"
|
||||||
"github.com/replicatedhq/troubleshoot/pkg/types"
|
"github.com/replicatedhq/troubleshoot/pkg/types"
|
||||||
|
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
spin "github.com/tj/go-spin"
|
spin "github.com/tj/go-spin"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func RunPreflights(interactive bool, output string, format string, args []string) error {
|
func RunPreflights(interactive bool, output string, format string, args []string) error {
|
||||||
@@ -77,6 +84,21 @@ func RunPreflights(interactive bool, output string, format string, args []string
|
|||||||
var uploadCollectResults []CollectResult
|
var uploadCollectResults []CollectResult
|
||||||
preflightSpecName := ""
|
preflightSpecName := ""
|
||||||
|
|
||||||
|
// Create a temporary directory to save the preflight bundle
|
||||||
|
tmpDir, err := os.MkdirTemp("", "preflightbundle-")
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "create temp dir for preflightbundle")
|
||||||
|
}
|
||||||
|
defer os.RemoveAll(tmpDir)
|
||||||
|
bundleFileName := fmt.Sprintf("preflightbundle-%s", time.Now().Format("2006-01-02T15_04_05"))
|
||||||
|
bundlePath := filepath.Join(tmpDir, bundleFileName)
|
||||||
|
if err := os.MkdirAll(bundlePath, 0777); err != nil {
|
||||||
|
return errors.Wrap(err, "failed to create preflight bundle dir")
|
||||||
|
}
|
||||||
|
|
||||||
|
archivePath := fmt.Sprintf("%s.tar.gz", bundleFileName)
|
||||||
|
klog.V(2).Infof("Preflight data collected in temporary directory: %s", tmpDir)
|
||||||
|
|
||||||
progressCh := make(chan interface{})
|
progressCh := make(chan interface{})
|
||||||
defer close(progressCh)
|
defer close(progressCh)
|
||||||
|
|
||||||
@@ -92,12 +114,21 @@ func RunPreflights(interactive bool, output string, format string, args []string
|
|||||||
}
|
}
|
||||||
|
|
||||||
uploadResultsMap := make(map[string][]CollectResult)
|
uploadResultsMap := make(map[string][]CollectResult)
|
||||||
|
collectorResults := collect.NewResult()
|
||||||
|
analyzers := []*troubleshootv1beta2.Analyze{}
|
||||||
|
hostAnalyzers := []*troubleshootv1beta2.HostAnalyze{}
|
||||||
|
|
||||||
for _, spec := range specs.PreflightsV1Beta2 {
|
for _, spec := range specs.PreflightsV1Beta2 {
|
||||||
r, err := collectInCluster(ctx, &spec, progressCh)
|
r, err := collectInCluster(ctx, &spec, progressCh, bundlePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.Wrap(err, "failed to collect in cluster"))
|
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.Wrap(err, "failed to collect in cluster"))
|
||||||
}
|
}
|
||||||
|
collectorResult, ok := (*r).(ClusterCollectResult)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("unexpected result type: %T", collectResults)
|
||||||
|
}
|
||||||
|
collectorResults.AddResult(collect.CollectorResult(collectorResult.AllCollectedData))
|
||||||
|
|
||||||
if spec.Spec.UploadResultsTo != "" {
|
if spec.Spec.UploadResultsTo != "" {
|
||||||
uploadResultsMap[spec.Spec.UploadResultsTo] = append(uploadResultsMap[spec.Spec.UploadResultsTo], *r)
|
uploadResultsMap[spec.Spec.UploadResultsTo] = append(uploadResultsMap[spec.Spec.UploadResultsTo], *r)
|
||||||
uploadCollectResults = append(collectResults, *r)
|
uploadCollectResults = append(collectResults, *r)
|
||||||
@@ -106,15 +137,21 @@ func RunPreflights(interactive bool, output string, format string, args []string
|
|||||||
}
|
}
|
||||||
// TODO: This spec name will be overwritten by the next spec. Is this intentional?
|
// TODO: This spec name will be overwritten by the next spec. Is this intentional?
|
||||||
preflightSpecName = spec.Name
|
preflightSpecName = spec.Name
|
||||||
|
analyzers = append(analyzers, spec.Spec.Analyzers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, spec := range specs.HostPreflightsV1Beta2 {
|
for _, spec := range specs.HostPreflightsV1Beta2 {
|
||||||
if len(spec.Spec.Collectors) > 0 {
|
if len(spec.Spec.Collectors) > 0 {
|
||||||
r, err := collectHost(ctx, &spec, progressCh)
|
r, err := collectHost(ctx, &spec, progressCh, bundlePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.Wrap(err, "failed to collect from host"))
|
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.Wrap(err, "failed to collect from host"))
|
||||||
}
|
}
|
||||||
collectResults = append(collectResults, *r)
|
collectResults = append(collectResults, *r)
|
||||||
|
collectorResult, ok := (*r).(HostCollectResult)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("unexpected result type: %T", collectResults)
|
||||||
|
}
|
||||||
|
collectorResults.AddResult(collect.CollectorResult(collectorResult.AllCollectedData))
|
||||||
}
|
}
|
||||||
if len(spec.Spec.RemoteCollectors) > 0 {
|
if len(spec.Spec.RemoteCollectors) > 0 {
|
||||||
r, err := collectRemote(ctx, &spec, progressCh)
|
r, err := collectRemote(ctx, &spec, progressCh)
|
||||||
@@ -122,17 +159,32 @@ func RunPreflights(interactive bool, output string, format string, args []string
|
|||||||
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.Wrap(err, "failed to collect remotely"))
|
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.Wrap(err, "failed to collect remotely"))
|
||||||
}
|
}
|
||||||
collectResults = append(collectResults, *r)
|
collectResults = append(collectResults, *r)
|
||||||
|
collectorResult, ok := (*r).(RemoteCollectResult)
|
||||||
|
if !ok {
|
||||||
|
return errors.Errorf("unexpected result type: %T", collectResults)
|
||||||
|
}
|
||||||
|
collectorResults.AddResult(collect.CollectorResult(collectorResult.AllCollectedData))
|
||||||
}
|
}
|
||||||
preflightSpecName = spec.Name
|
preflightSpecName = spec.Name
|
||||||
|
hostAnalyzers = append(hostAnalyzers, spec.Spec.Analyzers...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(collectResults) == 0 && len(uploadCollectResults) == 0 {
|
if len(collectResults) == 0 && len(uploadCollectResults) == 0 {
|
||||||
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.New("no data was collected"))
|
return types.NewExitCodeError(constants.EXIT_CODE_CATCH_ALL, errors.New("no data was collected"))
|
||||||
}
|
}
|
||||||
|
|
||||||
analyzeResults := []*analyzer.AnalyzeResult{}
|
err = saveTSVersionToBundle(collectorResults, bundlePath)
|
||||||
for _, res := range collectResults {
|
if err != nil {
|
||||||
analyzeResults = append(analyzeResults, res.Analyze()...)
|
return errors.Wrap(err, "failed to save version file")
|
||||||
|
}
|
||||||
|
|
||||||
|
analyzeResults, err := analyzer.AnalyzeLocal(ctx, bundlePath, analyzers, hostAnalyzers)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to analyze support bundle")
|
||||||
|
}
|
||||||
|
err = saveAnalysisResultsToBundle(collectorResults, analyzeResults, bundlePath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "failed to save analysis results to bundle")
|
||||||
}
|
}
|
||||||
|
|
||||||
uploadAnalyzeResultsMap := make(map[string][]*analyzer.AnalyzeResult)
|
uploadAnalyzeResultsMap := make(map[string][]*analyzer.AnalyzeResult)
|
||||||
@@ -150,6 +202,12 @@ func RunPreflights(interactive bool, output string, format string, args []string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Archive preflight bundle
|
||||||
|
if err := collectorResults.ArchiveBundle(bundlePath, archivePath); err != nil {
|
||||||
|
return errors.Wrapf(err, "failed to create %s archive", archivePath)
|
||||||
|
}
|
||||||
|
defer fmt.Fprintf(os.Stderr, "\nSaving preflight bundle to %s\n", archivePath)
|
||||||
|
|
||||||
stopProgressCollection()
|
stopProgressCollection()
|
||||||
progressCollection.Wait()
|
progressCollection.Wait()
|
||||||
|
|
||||||
@@ -176,6 +234,37 @@ func RunPreflights(interactive bool, output string, format string, args []string
|
|||||||
return types.NewExitCodeError(exitCode, errors.New("preflights failed with warnings or errors"))
|
return types.NewExitCodeError(exitCode, errors.New("preflights failed with warnings or errors"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func saveAnalysisResultsToBundle(
|
||||||
|
results collect.CollectorResult, analyzeResults []*analyzer.AnalyzeResult, bundlePath string,
|
||||||
|
) error {
|
||||||
|
data := convert.FromAnalyzerResult(analyzeResults)
|
||||||
|
analysis, err := json.MarshalIndent(data, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = results.SaveResult(bundlePath, "analysis.json", bytes.NewBuffer(analysis))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveTSVersionToBundle(results collect.CollectorResult, bundlePath string) error {
|
||||||
|
version, err := version.GetVersionFile()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = results.SaveResult(bundlePath, constants.VERSION_FILENAME, bytes.NewBuffer([]byte(version)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Determine if any preflight checks passed vs failed vs warned
|
// Determine if any preflight checks passed vs failed vs warned
|
||||||
// If all checks passed: 0
|
// If all checks passed: 0
|
||||||
// If 1 or more checks failed: 3
|
// If 1 or more checks failed: 3
|
||||||
@@ -250,7 +339,9 @@ func collectNonInteractiveProgess(ctx context.Context, progressCh <-chan interfa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectInCluster(ctx context.Context, preflightSpec *troubleshootv1beta2.Preflight, progressCh chan interface{}) (*CollectResult, error) {
|
func collectInCluster(
|
||||||
|
ctx context.Context, preflightSpec *troubleshootv1beta2.Preflight, progressCh chan interface{}, bundlePath string,
|
||||||
|
) (*CollectResult, error) {
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
|
|
||||||
restConfig, err := k8sutil.GetRESTConfig()
|
restConfig, err := k8sutil.GetRESTConfig()
|
||||||
@@ -263,6 +354,7 @@ func collectInCluster(ctx context.Context, preflightSpec *troubleshootv1beta2.Pr
|
|||||||
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
IgnorePermissionErrors: v.GetBool("collect-without-permissions"),
|
||||||
ProgressChan: progressCh,
|
ProgressChan: progressCh,
|
||||||
KubernetesRestConfig: restConfig,
|
KubernetesRestConfig: restConfig,
|
||||||
|
BundlePath: bundlePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
if v.GetString("since") != "" || v.GetString("since-time") != "" {
|
if v.GetString("since") != "" || v.GetString("since-time") != "" {
|
||||||
@@ -289,7 +381,7 @@ func collectInCluster(ctx context.Context, preflightSpec *troubleshootv1beta2.Pr
|
|||||||
return &collectResults, nil
|
return &collectResults, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectRemote(ctx context.Context, preflightSpec *troubleshootv1beta2.HostPreflight, progressCh chan interface{}) (*CollectResult, error) {
|
func collectRemote(_ context.Context, preflightSpec *troubleshootv1beta2.HostPreflight, progressCh chan interface{}) (*CollectResult, error) {
|
||||||
v := viper.GetViper()
|
v := viper.GetViper()
|
||||||
|
|
||||||
restConfig, err := k8sutil.GetRESTConfig()
|
restConfig, err := k8sutil.GetRESTConfig()
|
||||||
@@ -331,9 +423,12 @@ func collectRemote(ctx context.Context, preflightSpec *troubleshootv1beta2.HostP
|
|||||||
return &collectResults, nil
|
return &collectResults, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func collectHost(ctx context.Context, hostPreflightSpec *troubleshootv1beta2.HostPreflight, progressCh chan interface{}) (*CollectResult, error) {
|
func collectHost(
|
||||||
|
_ context.Context, hostPreflightSpec *troubleshootv1beta2.HostPreflight, progressCh chan interface{}, bundlePath string,
|
||||||
|
) (*CollectResult, error) {
|
||||||
collectOpts := CollectOpts{
|
collectOpts := CollectOpts{
|
||||||
ProgressChan: progressCh,
|
ProgressChan: progressCh,
|
||||||
|
BundlePath: bundlePath,
|
||||||
}
|
}
|
||||||
|
|
||||||
collectResults, err := CollectHost(collectOpts, hostPreflightSpec)
|
collectResults, err := CollectHost(collectOpts, hostPreflightSpec)
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"go.opentelemetry.io/otel/attribute"
|
"go.opentelemetry.io/otel/attribute"
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
"k8s.io/client-go/kubernetes"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -221,24 +220,6 @@ func findFileName(basename, extension string) (string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getVersionFile() (io.Reader, error) {
|
|
||||||
version := troubleshootv1beta2.SupportBundleVersion{
|
|
||||||
ApiVersion: "troubleshoot.sh/v1beta2",
|
|
||||||
Kind: "SupportBundle",
|
|
||||||
Spec: troubleshootv1beta2.SupportBundleVersionSpec{
|
|
||||||
VersionNumber: version.Version(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
b, err := yaml.Marshal(version)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Wrap(err, "failed to marshal version data")
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes.NewBuffer(b), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
const AnalysisFilename = "analysis.json"
|
|
||||||
|
|
||||||
func getAnalysisFile(analyzeResults []*analyze.AnalyzeResult) (io.Reader, error) {
|
func getAnalysisFile(analyzeResults []*analyze.AnalyzeResult) (io.Reader, error) {
|
||||||
data := convert.FromAnalyzerResult(analyzeResults)
|
data := convert.FromAnalyzerResult(analyzeResults)
|
||||||
analysis, err := json.MarshalIndent(data, "", " ")
|
analysis, err := json.MarshalIndent(data, "", " ")
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
"github.com/replicatedhq/troubleshoot/pkg/collect"
|
||||||
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
"github.com/replicatedhq/troubleshoot/pkg/constants"
|
||||||
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
"github.com/replicatedhq/troubleshoot/pkg/convert"
|
||||||
|
"github.com/replicatedhq/troubleshoot/pkg/version"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
@@ -144,12 +145,12 @@ func CollectSupportBundleFromSpec(
|
|||||||
return nil, fmt.Errorf("failed to generate support bundle")
|
return nil, fmt.Errorf("failed to generate support bundle")
|
||||||
}
|
}
|
||||||
|
|
||||||
version, err := getVersionFile()
|
version, err := version.GetVersionFile()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to get version file")
|
return nil, errors.Wrap(err, "failed to get version file")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = result.SaveResult(bundlePath, constants.VERSION_FILENAME, version)
|
err = result.SaveResult(bundlePath, constants.VERSION_FILENAME, bytes.NewBuffer([]byte(version)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to write version")
|
return nil, errors.Wrap(err, "failed to write version")
|
||||||
}
|
}
|
||||||
@@ -172,7 +173,7 @@ func CollectSupportBundleFromSpec(
|
|||||||
return nil, errors.Wrap(err, "failed to get analysis file")
|
return nil, errors.Wrap(err, "failed to get analysis file")
|
||||||
}
|
}
|
||||||
|
|
||||||
err = result.SaveResult(bundlePath, AnalysisFilename, analysis)
|
err = result.SaveResult(bundlePath, constants.ANALYSIS_FILENAME, analysis)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "failed to write analysis")
|
return nil, errors.Wrap(err, "failed to write analysis")
|
||||||
}
|
}
|
||||||
@@ -188,7 +189,7 @@ func CollectSupportBundleFromSpec(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Archive Support Bundle
|
// Archive Support Bundle
|
||||||
if err := result.ArchiveSupportBundle(bundlePath, filename); err != nil {
|
if err := result.ArchiveBundle(bundlePath, filename); err != nil {
|
||||||
return nil, errors.Wrap(err, "create bundle file")
|
return nil, errors.Wrap(err, "create bundle file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,10 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -96,3 +100,21 @@ func getGoInfo() GoInfo {
|
|||||||
func GetUserAgent() string {
|
func GetUserAgent() string {
|
||||||
return fmt.Sprintf("Replicated_Troubleshoot/%s", Version())
|
return fmt.Sprintf("Replicated_Troubleshoot/%s", Version())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetVersionFile() (string, error) {
|
||||||
|
// TODO: Should this type be agnostic to the tool?
|
||||||
|
// i.e should it be a TroubleshootVersion instead?
|
||||||
|
version := troubleshootv1beta2.SupportBundleVersion{
|
||||||
|
ApiVersion: "troubleshoot.sh/v1beta2",
|
||||||
|
Kind: "SupportBundle",
|
||||||
|
Spec: troubleshootv1beta2.SupportBundleVersionSpec{
|
||||||
|
VersionNumber: Version(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b, err := yaml.Marshal(version)
|
||||||
|
if err != nil {
|
||||||
|
return "", errors.Wrap(err, "failed to marshal version data")
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,10 +2,21 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
PREFLIGHT_BIN=$(pwd)/bin/preflight
|
||||||
|
|
||||||
tmpdir="$(mktemp -d)"
|
tmpdir="$(mktemp -d)"
|
||||||
|
trap cleanup SIGHUP SIGINT SIGTERM EXIT
|
||||||
|
cleanup() {
|
||||||
|
rm -rf $tmpdir
|
||||||
|
}
|
||||||
|
|
||||||
|
reset_tmp() {
|
||||||
|
rm -rf "$tmpdir"
|
||||||
|
tmpdir="$(mktemp -d)"
|
||||||
|
}
|
||||||
|
|
||||||
echo -e "\n========= Running preflights from e2e spec and checking results ========="
|
echo -e "\n========= Running preflights from e2e spec and checking results ========="
|
||||||
./bin/preflight --debug --interactive=false --format=json examples/preflight/e2e.yaml > "$tmpdir/result.json"
|
$PREFLIGHT_BIN --debug --interactive=false --format=json examples/preflight/e2e.yaml > "$tmpdir/result.json"
|
||||||
if [ $? -ne 0 ]; then
|
if [ $? -ne 0 ]; then
|
||||||
echo "preflight command failed"
|
echo "preflight command failed"
|
||||||
exit $EXIT_STATUS
|
exit $EXIT_STATUS
|
||||||
@@ -35,13 +46,34 @@ EXIT_STATUS=1
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "\n========= Running preflights from stdin using e2e spec ========="
|
echo -e "\n========= Running preflights from stdin using e2e spec ========="
|
||||||
cat examples/preflight/e2e.yaml | ./bin/preflight --debug --interactive=false --format=json - > "$tmpdir/result.json"
|
cat examples/preflight/e2e.yaml | $PREFLIGHT_BIN --debug --interactive=false --format=json - > "$tmpdir/result.json"
|
||||||
EXIT_STATUS=$?
|
EXIT_STATUS=$?
|
||||||
if [ $EXIT_STATUS -ne 0 ]; then
|
if [ $EXIT_STATUS -ne 0 ]; then
|
||||||
echo "preflight command failed"
|
echo "preflight command failed"
|
||||||
exit $EXIT_STATUS
|
exit $EXIT_STATUS
|
||||||
fi
|
fi
|
||||||
|
|
||||||
rm -rf "$tmpdir"
|
echo -e "\n========= Running preflights and storing bundle in current working directory ========="
|
||||||
|
E2E_PREFLIGHT=$(pwd)/examples/preflight/e2e.yaml
|
||||||
|
|
||||||
|
# We need a clean slate
|
||||||
|
reset_tmp
|
||||||
|
pushd $tmpdir >/dev/null
|
||||||
|
echo $E2E_PREFLIGHT
|
||||||
|
cat $E2E_PREFLIGHT | $PREFLIGHT_BIN --debug --interactive=false -
|
||||||
|
EXIT_STATUS=$?
|
||||||
|
popd >/dev/null
|
||||||
|
|
||||||
|
if [ $EXIT_STATUS -ne 0 ]; then
|
||||||
|
echo "preflight command failed"
|
||||||
|
exit $EXIT_STATUS
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ls $tmpdir/preflightbundle-*.tar.gz; then
|
||||||
|
echo "preflight bundle exists"
|
||||||
|
else
|
||||||
|
echo "Failed to find collected preflight bundle"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
exit $EXIT_STATUS
|
exit $EXIT_STATUS
|
||||||
|
|||||||
Reference in New Issue
Block a user