@@ -11,9 +11,10 @@ import (
1111 "io"
1212 "os"
1313 "os/exec"
14- "strconv "
14+ "os/signal "
1515 "strings"
1616 "sync"
17+ "syscall"
1718 "time"
1819
1920 "github.com/google/uuid"
@@ -221,71 +222,6 @@ func runWorkflowAndProcessData(engine workflow.Engine, logger *zerolog.Logger, n
221222 return err
222223}
223224
224- func sendAnalytics (analytics analytics.Analytics , debugLogger * zerolog.Logger ) {
225- debugLogger .Print ("Sending Analytics" )
226-
227- analytics .SetApiUrl (globalConfiguration .GetString (configuration .API_URL ))
228-
229- res , err := analytics .Send ()
230- if err != nil {
231- debugLogger .Err (err ).Msg ("Failed to send Analytics" )
232- return
233- }
234- defer func () { _ = res .Body .Close () }()
235-
236- successfullySend := 200 <= res .StatusCode && res .StatusCode < 300
237- if successfullySend {
238- debugLogger .Print ("Analytics successfully send" )
239- } else {
240- var details string
241- if res != nil {
242- details = res .Status
243- }
244-
245- debugLogger .Print ("Failed to send Analytics:" , details )
246- }
247- }
248-
249- func sendInstrumentation (eng workflow.Engine , instrumentor analytics.InstrumentationCollector , logger * zerolog.Logger ) {
250- // Avoid duplicate data to be sent for IDE integrations that use the CLI
251- if ! shallSendInstrumentation (eng .GetConfiguration (), instrumentor ) {
252- logger .Print ("This CLI call is not instrumented!" )
253- return
254- }
255-
256- // add temporary static nodejs binary flag, remove once linuxstatic is official
257- staticNodeJsBinaryBool , parseErr := strconv .ParseBool (constants .StaticNodeJsBinary )
258- if parseErr != nil {
259- logger .Print ("Failed to parse staticNodeJsBinary:" , parseErr )
260- } else {
261- // the legacycli:: prefix is added to maintain compatibility with our monitoring dashboard
262- instrumentor .AddExtension ("legacycli::static-nodejs-binary" , staticNodeJsBinaryBool )
263- }
264-
265- logger .Print ("Sending Instrumentation" )
266- data , err := analytics .GetV2InstrumentationObject (instrumentor , analytics .WithLogger (logger ))
267- if err != nil {
268- logger .Err (err ).Msg ("Failed to derive data object" )
269- }
270-
271- v2InstrumentationData := utils .ValueOf (json .Marshal (data ))
272- localConfiguration := globalConfiguration .Clone ()
273- // the report analytics workflow needs --experimental to run
274- // we pass the flag here so that we report at every interaction
275- localConfiguration .Set (configuration .FLAG_EXPERIMENTAL , true )
276- localConfiguration .Set ("inputData" , string (v2InstrumentationData ))
277- _ , err = eng .InvokeWithConfig (
278- localworkflows .WORKFLOWID_REPORT_ANALYTICS ,
279- localConfiguration ,
280- )
281-
282- if err != nil {
283- logger .Err (err ).Msg ("Failed to send Instrumentation" )
284- } else {
285- logger .Print ("Instrumentation successfully sent" )
286- }
287- }
288-
289225func help (_ * cobra.Command , _ []string ) error {
290226 helpProvided = true
291227 args := utils .RemoveSimilar (os .Args [1 :], "--" ) // remove all double dash arguments to avoid issues with the help command
@@ -548,11 +484,51 @@ func initExtensions(engine workflow.Engine, config configuration.Configuration)
548484 }
549485}
550486
487+ // tearDown handles sending analytics and instrumentation
488+ // It is used both for normal exit and signal-triggered exit
489+ func tearDown (ctx context.Context , err error , errorList []error , startTime time.Time , ua networking.UserAgentInfo , cliAnalytics analytics.Analytics , networkAccess networking.NetworkAccess ) int {
490+ if err != nil {
491+ errorList , err = processError (err , errorList )
492+
493+ for _ , tempError := range errorList {
494+ if tempError != nil {
495+ cliAnalytics .AddError (tempError )
496+ }
497+ }
498+ }
499+
500+ exitCode := cliv2 .DeriveExitCode (err )
501+ globalLogger .Printf ("Deriving Exit Code %d (cause: %v)" , exitCode , err )
502+
503+ displayError (err , globalEngine .GetUserInterface (), globalConfiguration , ctx )
504+
505+ updateInstrumentationDataBeforeSending (cliAnalytics , startTime , ua , exitCode )
506+
507+ if ! globalConfiguration .GetBool (configuration .ANALYTICS_DISABLED ) {
508+ sendAnalytics (cliAnalytics , globalLogger )
509+ }
510+ sendInstrumentation (globalEngine , cliAnalytics .GetInstrumentation (), globalLogger )
511+
512+ // cleanup resources in use
513+ // WARNING: deferred actions will execute AFTER cleanup; only defer if not impacted by this
514+ if _ , cleanupErr := globalEngine .Invoke (basic_workflows .WORKFLOWID_GLOBAL_CLEANUP ); cleanupErr != nil {
515+ globalLogger .Printf ("Failed to cleanup %v" , cleanupErr )
516+ }
517+
518+ if globalConfiguration .GetBool (configuration .DEBUG ) {
519+ writeLogFooter (exitCode , errorList , globalConfiguration , networkAccess )
520+ }
521+
522+ return exitCode
523+ }
524+
551525func MainWithErrorCode () int {
552526 initDebugBuild ()
553527
554528 errorList := []error {}
555529 errorListMutex := sync.Mutex {}
530+ var tearDownOnce sync.Once
531+ var finalExitCode int
556532
557533 startTime := time .Now ()
558534 var err error
@@ -656,6 +632,29 @@ func MainWithErrorCode() int {
656632 cliAnalytics .GetInstrumentation ().SetStage (instrumentation .DetermineStage (cliAnalytics .IsCiEnvironment ()))
657633 cliAnalytics .GetInstrumentation ().SetStatus (analytics .Success )
658634
635+ // prepare for signal handling
636+ signalChan := make (chan os.Signal , 1 )
637+
638+ if globalConfiguration .GetBool (configuration .PREVIEW_FEATURES_ENABLED ) {
639+ // Set up signal handling to send instrumentation on premature termination
640+ signal .Notify (signalChan , syscall .SIGINT , syscall .SIGTERM )
641+ go func () {
642+ sig := <- signalChan
643+ globalLogger .Printf ("Received signal %v, attempting to send instrumentation before exit" , sig )
644+
645+ tearDownOnce .Do (func () {
646+ signalError := cli .NewTerminatedBySignalError (fmt .Sprintf ("Signal: %v" , sig ))
647+
648+ errorListMutex .Lock ()
649+ errorListCopy := append ([]error {}, errorList ... )
650+ errorListMutex .Unlock ()
651+
652+ finalExitCode = tearDown (ctx , signalError , errorListCopy , startTime , ua , cliAnalytics , networkAccess )
653+ })
654+ os .Exit (finalExitCode )
655+ }()
656+ }
657+
659658 setTimeout (globalConfiguration , func () {
660659 os .Exit (constants .SNYK_EXIT_CODE_EX_UNAVAILABLE )
661660 })
@@ -681,40 +680,20 @@ func MainWithErrorCode() int {
681680 // ignore
682681 }
683682
684- if err != nil {
685- errorList , err = processError (err , errorList )
686-
687- for _ , tempError := range errorList {
688- if tempError != nil {
689- cliAnalytics .AddError (tempError )
690- }
691- }
683+ if globalConfiguration .GetBool (configuration .PREVIEW_FEATURES_ENABLED ) {
684+ // Stop signal handling before cleanup to prevent race conditions
685+ signal .Stop (signalChan )
692686 }
693687
694- displayError (err , globalEngine .GetUserInterface (), globalConfiguration , ctx )
695-
696- exitCode := cliv2 .DeriveExitCode (err )
697- globalLogger .Printf ("Deriving Exit Code %d (cause: %v)" , exitCode , err )
698-
699- updateInstrumentationDataBeforeSending (cliAnalytics , startTime , ua , exitCode )
700-
701- if ! globalConfiguration .GetBool (configuration .ANALYTICS_DISABLED ) {
702- sendAnalytics (cliAnalytics , globalLogger )
703- }
704- sendInstrumentation (globalEngine , cliAnalytics .GetInstrumentation (), globalLogger )
705-
706- // cleanup resources in use
707- // WARNING: deferred actions will execute AFTER cleanup; only defer if not impacted by this
708- _ , err = globalEngine .Invoke (basic_workflows .WORKFLOWID_GLOBAL_CLEANUP )
709- if err != nil {
710- globalLogger .Printf ("Failed to cleanup %v" , err )
711- }
688+ tearDownOnce .Do (func () {
689+ errorListMutex .Lock ()
690+ errorListCopy := append ([]error {}, errorList ... )
691+ errorListMutex .Unlock ()
712692
713- if debugEnabled {
714- writeLogFooter (exitCode , errorList , globalConfiguration , networkAccess )
715- }
693+ finalExitCode = tearDown (ctx , err , errorListCopy , startTime , ua , cliAnalytics , networkAccess )
694+ })
716695
717- return exitCode
696+ return finalExitCode
718697}
719698
720699func processError (err error , errorList []error ) ([]error , error ) {
0 commit comments