11package preflight
22
33import (
4+ "bytes"
45 "context"
6+ "encoding/json"
57 "fmt"
68 "os"
79 "os/signal"
10+ "path/filepath"
811 "time"
912
1013 cursor "github.com/ahmetalpbalkan/go-cursor"
@@ -13,15 +16,19 @@ import (
1316 "github.com/replicatedhq/troubleshoot/internal/util"
1417 analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
1518 troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
19+ "github.com/replicatedhq/troubleshoot/pkg/collect"
1620 "github.com/replicatedhq/troubleshoot/pkg/constants"
21+ "github.com/replicatedhq/troubleshoot/pkg/convert"
1722 "github.com/replicatedhq/troubleshoot/pkg/k8sutil"
1823 "github.com/replicatedhq/troubleshoot/pkg/types"
24+ "github.com/replicatedhq/troubleshoot/pkg/version"
1925 "github.com/spf13/viper"
2026 spin "github.com/tj/go-spin"
2127 "go.opentelemetry.io/otel"
2228 "golang.org/x/sync/errgroup"
2329 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2430 "k8s.io/apimachinery/pkg/labels"
31+ "k8s.io/klog/v2"
2532)
2633
2734func RunPreflights (interactive bool , output string , format string , args []string ) error {
@@ -77,6 +84,21 @@ func RunPreflights(interactive bool, output string, format string, args []string
7784 var uploadCollectResults []CollectResult
7885 preflightSpecName := ""
7986
87+ // Create a temporary directory to save the preflight bundle
88+ tmpDir , err := os .MkdirTemp ("" , "preflightbundle-" )
89+ if err != nil {
90+ return errors .Wrap (err , "create temp dir for preflightbundle" )
91+ }
92+ defer os .RemoveAll (tmpDir )
93+ bundleFileName := fmt .Sprintf ("preflightbundle-%s" , time .Now ().Format ("2006-01-02T15_04_05" ))
94+ bundlePath := filepath .Join (tmpDir , bundleFileName )
95+ if err := os .MkdirAll (bundlePath , 0777 ); err != nil {
96+ return errors .Wrap (err , "failed to create preflight bundle dir" )
97+ }
98+
99+ archivePath := fmt .Sprintf ("%s.tar.gz" , bundleFileName )
100+ klog .V (2 ).Infof ("Preflight data collected in temporary directory: %s" , tmpDir )
101+
80102 progressCh := make (chan interface {})
81103 defer close (progressCh )
82104
@@ -92,12 +114,21 @@ func RunPreflights(interactive bool, output string, format string, args []string
92114 }
93115
94116 uploadResultsMap := make (map [string ][]CollectResult )
117+ collectorResults := collect .NewResult ()
118+ analyzers := []* troubleshootv1beta2.Analyze {}
119+ hostAnalyzers := []* troubleshootv1beta2.HostAnalyze {}
95120
96121 for _ , spec := range specs .PreflightsV1Beta2 {
97- r , err := collectInCluster (ctx , & spec , progressCh )
122+ r , err := collectInCluster (ctx , & spec , progressCh , bundlePath )
98123 if err != nil {
99124 return types .NewExitCodeError (constants .EXIT_CODE_CATCH_ALL , errors .Wrap (err , "failed to collect in cluster" ))
100125 }
126+ collectorResult , ok := (* r ).(ClusterCollectResult )
127+ if ! ok {
128+ return errors .Errorf ("unexpected result type: %T" , collectResults )
129+ }
130+ collectorResults .AddResult (collect .CollectorResult (collectorResult .AllCollectedData ))
131+
101132 if spec .Spec .UploadResultsTo != "" {
102133 uploadResultsMap [spec .Spec .UploadResultsTo ] = append (uploadResultsMap [spec .Spec .UploadResultsTo ], * r )
103134 uploadCollectResults = append (collectResults , * r )
@@ -106,33 +137,54 @@ func RunPreflights(interactive bool, output string, format string, args []string
106137 }
107138 // TODO: This spec name will be overwritten by the next spec. Is this intentional?
108139 preflightSpecName = spec .Name
140+ analyzers = append (analyzers , spec .Spec .Analyzers ... )
109141 }
110142
111143 for _ , spec := range specs .HostPreflightsV1Beta2 {
112144 if len (spec .Spec .Collectors ) > 0 {
113- r , err := collectHost (ctx , & spec , progressCh )
145+ r , err := collectHost (ctx , & spec , progressCh , bundlePath )
114146 if err != nil {
115147 return types .NewExitCodeError (constants .EXIT_CODE_CATCH_ALL , errors .Wrap (err , "failed to collect from host" ))
116148 }
117149 collectResults = append (collectResults , * r )
150+ collectorResult , ok := (* r ).(HostCollectResult )
151+ if ! ok {
152+ return errors .Errorf ("unexpected result type: %T" , collectResults )
153+ }
154+ collectorResults .AddResult (collect .CollectorResult (collectorResult .AllCollectedData ))
118155 }
119156 if len (spec .Spec .RemoteCollectors ) > 0 {
120157 r , err := collectRemote (ctx , & spec , progressCh )
121158 if err != nil {
122159 return types .NewExitCodeError (constants .EXIT_CODE_CATCH_ALL , errors .Wrap (err , "failed to collect remotely" ))
123160 }
124161 collectResults = append (collectResults , * r )
162+ collectorResult , ok := (* r ).(RemoteCollectResult )
163+ if ! ok {
164+ return errors .Errorf ("unexpected result type: %T" , collectResults )
165+ }
166+ collectorResults .AddResult (collect .CollectorResult (collectorResult .AllCollectedData ))
125167 }
126168 preflightSpecName = spec .Name
169+ hostAnalyzers = append (hostAnalyzers , spec .Spec .Analyzers ... )
127170 }
128171
129172 if len (collectResults ) == 0 && len (uploadCollectResults ) == 0 {
130173 return types .NewExitCodeError (constants .EXIT_CODE_CATCH_ALL , errors .New ("no data was collected" ))
131174 }
132175
133- analyzeResults := []* analyzer.AnalyzeResult {}
134- for _ , res := range collectResults {
135- analyzeResults = append (analyzeResults , res .Analyze ()... )
176+ err = saveTSVersionToBundle (collectorResults , bundlePath )
177+ if err != nil {
178+ return errors .Wrap (err , "failed to save version file" )
179+ }
180+
181+ analyzeResults , err := analyzer .AnalyzeLocal (ctx , bundlePath , analyzers , hostAnalyzers )
182+ if err != nil {
183+ return errors .Wrap (err , "failed to analyze support bundle" )
184+ }
185+ err = saveAnalysisResultsToBundle (collectorResults , analyzeResults , bundlePath )
186+ if err != nil {
187+ return errors .Wrap (err , "failed to save analysis results to bundle" )
136188 }
137189
138190 uploadAnalyzeResultsMap := make (map [string ][]* analyzer.AnalyzeResult )
@@ -150,6 +202,12 @@ func RunPreflights(interactive bool, output string, format string, args []string
150202 }
151203 }
152204
205+ // Archive preflight bundle
206+ if err := collectorResults .ArchiveBundle (bundlePath , archivePath ); err != nil {
207+ return errors .Wrapf (err , "failed to create %s archive" , archivePath )
208+ }
209+ defer fmt .Fprintf (os .Stderr , "\n Saving preflight bundle to %s\n " , archivePath )
210+
153211 stopProgressCollection ()
154212 progressCollection .Wait ()
155213
@@ -176,6 +234,37 @@ func RunPreflights(interactive bool, output string, format string, args []string
176234 return types .NewExitCodeError (exitCode , errors .New ("preflights failed with warnings or errors" ))
177235}
178236
237+ func saveAnalysisResultsToBundle (
238+ results collect.CollectorResult , analyzeResults []* analyzer.AnalyzeResult , bundlePath string ,
239+ ) error {
240+ data := convert .FromAnalyzerResult (analyzeResults )
241+ analysis , err := json .MarshalIndent (data , "" , " " )
242+ if err != nil {
243+ return err
244+ }
245+
246+ err = results .SaveResult (bundlePath , "analysis.json" , bytes .NewBuffer (analysis ))
247+ if err != nil {
248+ return err
249+ }
250+
251+ return nil
252+ }
253+
254+ func saveTSVersionToBundle (results collect.CollectorResult , bundlePath string ) error {
255+ version , err := version .GetVersionFile ()
256+ if err != nil {
257+ return err
258+ }
259+
260+ err = results .SaveResult (bundlePath , constants .VERSION_FILENAME , bytes .NewBuffer ([]byte (version )))
261+ if err != nil {
262+ return err
263+ }
264+
265+ return nil
266+ }
267+
179268// Determine if any preflight checks passed vs failed vs warned
180269// If all checks passed: 0
181270// If 1 or more checks failed: 3
@@ -250,7 +339,9 @@ func collectNonInteractiveProgess(ctx context.Context, progressCh <-chan interfa
250339 }
251340}
252341
253- func collectInCluster (ctx context.Context , preflightSpec * troubleshootv1beta2.Preflight , progressCh chan interface {}) (* CollectResult , error ) {
342+ func collectInCluster (
343+ ctx context.Context , preflightSpec * troubleshootv1beta2.Preflight , progressCh chan interface {}, bundlePath string ,
344+ ) (* CollectResult , error ) {
254345 v := viper .GetViper ()
255346
256347 restConfig , err := k8sutil .GetRESTConfig ()
@@ -263,6 +354,7 @@ func collectInCluster(ctx context.Context, preflightSpec *troubleshootv1beta2.Pr
263354 IgnorePermissionErrors : v .GetBool ("collect-without-permissions" ),
264355 ProgressChan : progressCh ,
265356 KubernetesRestConfig : restConfig ,
357+ BundlePath : bundlePath ,
266358 }
267359
268360 if v .GetString ("since" ) != "" || v .GetString ("since-time" ) != "" {
@@ -289,7 +381,7 @@ func collectInCluster(ctx context.Context, preflightSpec *troubleshootv1beta2.Pr
289381 return & collectResults , nil
290382}
291383
292- func collectRemote (ctx context.Context , preflightSpec * troubleshootv1beta2.HostPreflight , progressCh chan interface {}) (* CollectResult , error ) {
384+ func collectRemote (_ context.Context , preflightSpec * troubleshootv1beta2.HostPreflight , progressCh chan interface {}) (* CollectResult , error ) {
293385 v := viper .GetViper ()
294386
295387 restConfig , err := k8sutil .GetRESTConfig ()
@@ -331,9 +423,12 @@ func collectRemote(ctx context.Context, preflightSpec *troubleshootv1beta2.HostP
331423 return & collectResults , nil
332424}
333425
334- func collectHost (ctx context.Context , hostPreflightSpec * troubleshootv1beta2.HostPreflight , progressCh chan interface {}) (* CollectResult , error ) {
426+ func collectHost (
427+ _ context.Context , hostPreflightSpec * troubleshootv1beta2.HostPreflight , progressCh chan interface {}, bundlePath string ,
428+ ) (* CollectResult , error ) {
335429 collectOpts := CollectOpts {
336430 ProgressChan : progressCh ,
431+ BundlePath : bundlePath ,
337432 }
338433
339434 collectResults , err := CollectHost (collectOpts , hostPreflightSpec )
0 commit comments