@@ -16,13 +16,15 @@ import (
1616 "github.com/pkg/errors"
1717 "github.com/replicatedhq/troubleshoot/cmd/util"
1818 analyzer "github.com/replicatedhq/troubleshoot/pkg/analyze"
19+ analyzerunner "github.com/replicatedhq/troubleshoot/pkg/analyze"
1920 troubleshootv1beta2 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta2"
2021 troubleshootclientsetscheme "github.com/replicatedhq/troubleshoot/pkg/client/troubleshootclientset/scheme"
2122 "github.com/replicatedhq/troubleshoot/pkg/constants"
2223 "github.com/replicatedhq/troubleshoot/pkg/docrewrite"
2324 "github.com/replicatedhq/troubleshoot/pkg/k8sutil"
2425 "github.com/replicatedhq/troubleshoot/pkg/oci"
2526 "github.com/replicatedhq/troubleshoot/pkg/specs"
27+ "github.com/replicatedhq/troubleshoot/pkg/types"
2628 "github.com/spf13/viper"
2729 spin "github.com/tj/go-spin"
2830 "go.opentelemetry.io/otel"
@@ -47,7 +49,8 @@ func RunPreflights(interactive bool, output string, format string, args []string
4749 signalChan := make (chan os.Signal , 1 )
4850 signal .Notify (signalChan , os .Interrupt )
4951 <- signalChan
50- os .Exit (0 )
52+ // exiting due to a signal shouldn't be considered successful
53+ os .Exit (1 )
5154 }()
5255
5356 var preflightContent []byte
@@ -61,64 +64,66 @@ func RunPreflights(interactive bool, output string, format string, args []string
6164 // format secret/namespace-name/secret-name
6265 pathParts := strings .Split (v , "/" )
6366 if len (pathParts ) != 3 {
64- return errors .Errorf ("path %s must have 3 components" , v )
67+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , errors .Errorf ("path %s must have 3 components" , v ) )
6568 }
6669
6770 spec , err := specs .LoadFromSecret (pathParts [1 ], pathParts [2 ], "preflight-spec" )
6871 if err != nil {
69- return errors .Wrap (err , "failed to get spec from secret" )
72+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , errors .Wrap (err , "failed to get spec from secret" ) )
7073 }
7174
7275 preflightContent = spec
7376 } else if _ , err = os .Stat (v ); err == nil {
7477 b , err := os .ReadFile (v )
7578 if err != nil {
76- return err
79+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , err )
7780 }
7881
7982 preflightContent = b
8083 } else if v == "-" {
8184 b , err := io .ReadAll (os .Stdin )
8285 if err != nil {
83- return err
86+ return types . NewExitCodeError ( constants . EXIT_CODE_CATCH_ALL , err )
8487 }
8588 preflightContent = b
8689 } else {
8790 u , err := url .Parse (v )
8891 if err != nil {
89- return err
92+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , err )
9093 }
9194
9295 if u .Scheme == "oci" {
9396 content , err := oci .PullPreflightFromOCI (v )
9497 if err != nil {
9598 if err == oci .ErrNoRelease {
96- return errors .Errorf ("no release found for %s.\n Check the oci:// uri for errors or contact the application vendor for support." , v )
99+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , errors .Errorf ("no release found for %s.\n Check the oci:// uri for errors or contact the application vendor for support." , v ) )
97100 }
98101
99- return err
102+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , err )
100103 }
101104
102105 preflightContent = content
103106 } else {
104107 if ! util .IsURL (v ) {
105- return fmt .Errorf ("%s is not a URL and was not found (err %s)" , v , err )
108+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , fmt .Errorf ("%s is not a URL and was not found (err %s)" , v , err ) )
106109 }
107110
108111 req , err := http .NewRequest ("GET" , v , nil )
109112 if err != nil {
110- return err
113+ // exit code: should this be catch all or spec issues...?
114+ return types .NewExitCodeError (constants .EXIT_CODE_CATCH_ALL , err )
111115 }
112116 req .Header .Set ("User-Agent" , "Replicated_Preflight/v1beta2" )
113117 resp , err := http .DefaultClient .Do (req )
114118 if err != nil {
115- return err
119+ // exit code: should this be catch all or spec issues...?
120+ return types .NewExitCodeError (constants .EXIT_CODE_CATCH_ALL , err )
116121 }
117122 defer resp .Body .Close ()
118123
119124 body , err := io .ReadAll (resp .Body )
120125 if err != nil {
121- return err
126+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , err )
122127 }
123128
124129 preflightContent = body
@@ -137,7 +142,7 @@ func RunPreflights(interactive bool, output string, format string, args []string
137142
138143 err := yaml .Unmarshal ([]byte (doc ), & parsedDocHead )
139144 if err != nil {
140- return errors .Wrap (err , "failed to parse yaml" )
145+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , errors .Wrap (err , "failed to parse yaml" ) )
141146 }
142147
143148 if parsedDocHead .Kind != "Preflight" {
@@ -146,14 +151,14 @@ func RunPreflights(interactive bool, output string, format string, args []string
146151
147152 preflightContent , err = docrewrite .ConvertToV1Beta2 ([]byte (doc ))
148153 if err != nil {
149- return errors .Wrap (err , "failed to convert to v1beta2" )
154+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , errors .Wrap (err , "failed to convert to v1beta2" ) )
150155 }
151156
152157 troubleshootclientsetscheme .AddToScheme (scheme .Scheme )
153158 decode := scheme .Codecs .UniversalDeserializer ().Decode
154159 obj , _ , err := decode ([]byte (preflightContent ), nil , nil )
155160 if err != nil {
156- return errors .Wrapf (err , "failed to parse %s" , v )
161+ return types . NewExitCodeError ( constants . EXIT_CODE_SPEC_ISSUES , errors .Wrapf (err , "failed to parse %s" , v ) )
157162 }
158163
159164 if spec , ok := obj .(* troubleshootv1beta2.Preflight ); ok {
@@ -192,7 +197,7 @@ func RunPreflights(interactive bool, output string, format string, args []string
192197 if preflightSpec != nil {
193198 r , err := collectInCluster (ctx , preflightSpec , progressCh )
194199 if err != nil {
195- return errors .Wrap (err , "failed to collect in cluster" )
200+ return types . NewExitCodeError ( constants . EXIT_CODE_CATCH_ALL , errors .Wrap (err , "failed to collect in cluster" ) )
196201 }
197202 collectResults = append (collectResults , * r )
198203 preflightSpecName = preflightSpec .Name
@@ -201,7 +206,7 @@ func RunPreflights(interactive bool, output string, format string, args []string
201206 for _ , spec := range uploadResultSpecs {
202207 r , err := collectInCluster (ctx , spec , progressCh )
203208 if err != nil {
204- return errors .Wrap (err , "failed to collect in cluster" )
209+ return types . NewExitCodeError ( constants . EXIT_CODE_CATCH_ALL , errors .Wrap (err , "failed to collect in cluster" ) )
205210 }
206211 uploadResultsMap [spec .Spec .UploadResultsTo ] = append (uploadResultsMap [spec .Spec .UploadResultsTo ], * r )
207212 uploadCollectResults = append (collectResults , * r )
@@ -212,22 +217,22 @@ func RunPreflights(interactive bool, output string, format string, args []string
212217 if len (hostPreflightSpec .Spec .Collectors ) > 0 {
213218 r , err := collectHost (ctx , hostPreflightSpec , progressCh )
214219 if err != nil {
215- return errors .Wrap (err , "failed to collect from host" )
220+ return types . NewExitCodeError ( constants . EXIT_CODE_CATCH_ALL , errors .Wrap (err , "failed to collect from host" ) )
216221 }
217222 collectResults = append (collectResults , * r )
218223 }
219224 if len (hostPreflightSpec .Spec .RemoteCollectors ) > 0 {
220225 r , err := collectRemote (ctx , hostPreflightSpec , progressCh )
221226 if err != nil {
222- return errors .Wrap (err , "failed to collect remotely" )
227+ return types . NewExitCodeError ( constants . EXIT_CODE_CATCH_ALL , errors .Wrap (err , "failed to collect remotely" ) )
223228 }
224229 collectResults = append (collectResults , * r )
225230 }
226231 preflightSpecName = hostPreflightSpec .Name
227232 }
228233
229234 if collectResults == nil && uploadCollectResults == nil {
230- return errors .New ("no results" )
235+ return types . NewExitCodeError ( constants . EXIT_CODE_CATCH_ALL , errors .New ("no results" ) )
231236 }
232237
233238 analyzeResults := []* analyzer.AnalyzeResult {}
@@ -255,14 +260,48 @@ func RunPreflights(interactive bool, output string, format string, args []string
255260 stopProgressCollection ()
256261 progressCollection .Wait ()
257262
263+ if len (analyzeResults ) == 0 {
264+ return types .NewExitCodeError (constants .EXIT_CODE_CATCH_ALL , errors .New ("no data has been collected" ))
265+ }
266+
258267 if interactive {
259- if len (analyzeResults ) == 0 {
260- return errors .New ("no data has been collected" )
268+ err = showInteractiveResults (preflightSpecName , output , analyzeResults )
269+ } else {
270+ err = showTextResults (format , preflightSpecName , output , analyzeResults )
271+ }
272+
273+ if err != nil {
274+ return err
275+ }
276+
277+ exitCode := checkOutcomesToExitCode (analyzeResults )
278+
279+ if exitCode == 0 {
280+ return nil
281+ }
282+
283+ return types .NewExitCodeError (exitCode , errors .New ("preflights failed with warnings or errors" ))
284+ }
285+
286+ // Determine if any preflight checks passed vs failed vs warned
287+ // If all checks passed: 0
288+ // If 1 or more checks failed: 3
289+ // If no checks failed, but 1 or more warn: 4
290+ func checkOutcomesToExitCode (analyzeResults []* analyzerunner.AnalyzeResult ) int {
291+ // Assume pass until they don't
292+ exitCode := 0
293+
294+ for _ , analyzeResult := range analyzeResults {
295+ if analyzeResult .IsWarn {
296+ exitCode = constants .EXIT_CODE_WARN
297+ } else if analyzeResult .IsFail {
298+ exitCode = constants .EXIT_CODE_FAIL
299+ // No need to check further, a fail is a fail
300+ return exitCode
261301 }
262- return showInteractiveResults (preflightSpecName , output , analyzeResults )
263302 }
264303
265- return showTextResults ( format , preflightSpecName , output , analyzeResults )
304+ return exitCode
266305}
267306
268307func collectInteractiveProgress (ctx context.Context , progressCh <- chan interface {}) func () error {
0 commit comments