66 "errors"
77 "fmt"
88 "io"
9- "os"
109 "path/filepath"
1110 "regexp"
1211 "strings"
@@ -22,7 +21,6 @@ import (
2221 scm_domain "github.com/boostsecurityio/poutine/providers/scm/domain"
2322 "github.com/boostsecurityio/poutine/scanner"
2423 "github.com/rs/zerolog/log"
25- "github.com/schollz/progressbar/v3"
2624)
2725
2826const TEMP_DIR_PREFIX = "poutine-*"
@@ -96,6 +94,14 @@ type Analyzer struct {
9694 Formatter Formatter
9795 Config * models.Config
9896 Opa * opa.Opa
97+ Observer ProgressObserver
98+ }
99+
100+ func (a * Analyzer ) observer () ProgressObserver {
101+ if a .Observer != nil {
102+ return a .Observer
103+ }
104+ return noopObserver {}
99105}
100106
101107func (a * Analyzer ) AnalyzeOrg (ctx context.Context , org string , numberOfGoroutines * int ) ([]* models.PackageInsights , error ) {
@@ -114,8 +120,9 @@ func (a *Analyzer) AnalyzeOrg(ctx context.Context, org string, numberOfGoroutine
114120 pkgsupplyClient := pkgsupply .NewStaticClient ()
115121 inventory := scanner .NewInventory (a .Opa , pkgsupplyClient , provider , providerVersion )
116122
123+ obs := a .observer ()
124+ obs .OnAnalysisStarted ("Discovering repositories" )
117125 log .Debug ().Msgf ("Starting repository analysis for organization: %s on %s" , org , provider )
118- bar := a .ProgressBar (0 , "Analyzing repositories" )
119126
120127 var reposWg sync.WaitGroup
121128 errChan := make (chan error , 1 )
@@ -137,23 +144,24 @@ func (a *Analyzer) AnalyzeOrg(ctx context.Context, org string, numberOfGoroutine
137144 }
138145 }()
139146
147+ discoveryCompleted := false
140148 for repoBatch := range orgReposBatches {
141149 if repoBatch .Err != nil {
142150 log .Error ().Err (repoBatch .Err ).Msg ("failed to fetch batch of repos, skipping batch" )
143151 continue
144152 }
145- if repoBatch .TotalCount != 0 {
146- bar .ChangeMax (repoBatch .TotalCount )
153+ if ! discoveryCompleted && repoBatch .TotalCount != 0 {
154+ discoveryCompleted = true
155+ obs .OnDiscoveryCompleted (org , repoBatch .TotalCount )
147156 }
148157
149158 for _ , repo := range repoBatch .Repositories {
150159 if a .Config .IgnoreForks && repo .GetIsFork () {
151- bar . ChangeMax ( repoBatch . TotalCount - 1 )
160+ obs . OnRepoSkipped ( repo . GetRepoIdentifier (), "fork" )
152161 continue
153162 }
154163 if repo .GetSize () == 0 {
155- bar .ChangeMax (repoBatch .TotalCount - 1 )
156- log .Info ().Str ("repo" , repo .GetRepoIdentifier ()).Msg ("Skipping empty repository" )
164+ obs .OnRepoSkipped (repo .GetRepoIdentifier (), "empty" )
157165 continue
158166 }
159167 if err := goRoutineLimitSem .Acquire (ctx , 1 ); err != nil {
@@ -166,22 +174,26 @@ func (a *Analyzer) AnalyzeOrg(ctx context.Context, org string, numberOfGoroutine
166174 defer goRoutineLimitSem .Release (1 )
167175 defer reposWg .Done ()
168176 repoNameWithOwner := repo .GetRepoIdentifier ()
177+ obs .OnRepoStarted (repoNameWithOwner )
169178 repoKey , err := a .cloneRepo (ctx , repo .BuildGitURL (a .ScmClient .GetProviderBaseURL ()), a .ScmClient .GetToken (), "HEAD" )
170179 if err != nil {
171180 log .Error ().Err (err ).Str ("repo" , repoNameWithOwner ).Msg ("failed to clone repo" )
181+ obs .OnRepoError (repoNameWithOwner , err )
172182 return
173183 }
174184 defer a .GitClient .Cleanup (repoKey )
175185
176186 pkg , err := a .GeneratePackageInsights (ctx , repoKey , repo , "HEAD" )
177187 if err != nil {
178188 log .Error ().Err (err ).Str ("repo" , repoNameWithOwner ).Msg ("failed to generate package insights" )
189+ obs .OnRepoError (repoNameWithOwner , err )
179190 return
180191 }
181192
182193 files , err := a .GitClient .ListFiles (repoKey , []string {".yml" , ".yaml" })
183194 if err != nil {
184195 log .Error ().Err (err ).Str ("repo" , repoNameWithOwner ).Msg ("failed to list files" )
196+ obs .OnRepoError (repoNameWithOwner , err )
185197 return
186198 }
187199
@@ -199,6 +211,7 @@ func (a *Analyzer) AnalyzeOrg(ctx context.Context, org string, numberOfGoroutine
199211 scannedPkg , err := inventory .ScanPackageScanner (ctx , * pkg , memScanner )
200212 if err != nil {
201213 log .Error ().Err (err ).Str ("repo" , repoNameWithOwner ).Msg ("failed to scan package" )
214+ obs .OnRepoError (repoNameWithOwner , err )
202215 return
203216 }
204217
@@ -208,7 +221,7 @@ func (a *Analyzer) AnalyzeOrg(ctx context.Context, org string, numberOfGoroutine
208221 log .Error ().Msg ("Context canceled while sending package to channel" )
209222 return
210223 }
211- _ = bar . Add ( 1 )
224+ obs . OnRepoCompleted ( repoNameWithOwner , scannedPkg )
212225 }(repo )
213226 }
214227 }
@@ -227,12 +240,12 @@ func (a *Analyzer) AnalyzeOrg(ctx context.Context, org string, numberOfGoroutine
227240 }
228241 }
229242
230- _ = bar .Finish ()
231-
243+ obs .OnFinalizeStarted (len (scannedPackages ))
232244 err = a .finalizeAnalysis (ctx , scannedPackages )
233245 if err != nil {
234246 return scannedPackages , err
235247 }
248+ obs .OnFinalizeCompleted ()
236249
237250 return scannedPackages , nil
238251}
@@ -259,27 +272,28 @@ func (a *Analyzer) AnalyzeStaleBranches(ctx context.Context, repoString string,
259272
260273 inventory := scanner .NewInventory (a .Opa , pkgsupplyClient , provider , providerVersion )
261274
275+ obs := a .observer ()
276+ obs .OnAnalysisStarted ("Cloning repository" )
262277 log .Debug ().Msgf ("Starting repository analysis for: %s/%s on %s" , org , repoName , provider )
263- bar := a .ProgressBar (3 , "Cloning repository" )
264- _ = bar .RenderBlank ()
265278
279+ obs .OnRepoStarted (repoString )
266280 repoUrl := repo .BuildGitURL (a .ScmClient .GetProviderBaseURL ())
267281 repoKey , err := a .fetchCone (ctx , repoUrl , a .ScmClient .GetToken (), "refs/heads/*:refs/remotes/origin/*" , ".github/workflows" )
268282 if err != nil {
283+ obs .OnRepoError (repoString , err )
269284 return nil , fmt .Errorf ("failed to fetch cone: %w" , err )
270285 }
271286 defer a .GitClient .Cleanup (repoKey )
272287
273- bar .Describe ("Listing unique workflows" )
274- _ = bar .Add (1 )
288+ obs .OnStepCompleted ("Listing unique workflows" )
275289
276290 workflows , err := a .GitClient .GetUniqWorkflowsBranches (ctx , repoKey )
277291 if err != nil {
292+ obs .OnRepoError (repoString , err )
278293 return nil , fmt .Errorf ("failed to get unique workflow: %w" , err )
279294 }
280295
281- bar .Describe ("Check which workflows match regex: " + regex .String ())
282- _ = bar .Add (1 )
296+ obs .OnStepCompleted ("Checking workflows match regex: " + regex .String ())
283297
284298 errChan := make (chan error , 1 )
285299 maxGoroutines := 5
@@ -340,13 +354,14 @@ func (a *Analyzer) AnalyzeStaleBranches(ctx context.Context, repoString string,
340354 close (filesChan )
341355 wgConsumer .Wait ()
342356 for err := range errChan {
357+ obs .OnRepoError (repoString , err )
343358 return nil , err
344359 }
345360
346- bar .Describe ("Scanning package" )
347- _ = bar .Add (1 )
361+ obs .OnStepCompleted ("Scanning package" )
348362 pkg , err := a .GeneratePackageInsights (ctx , repoKey , repo , "HEAD" )
349363 if err != nil {
364+ obs .OnRepoError (repoString , err )
350365 return nil , fmt .Errorf ("failed to generate package insight: %w" , err )
351366 }
352367
@@ -359,10 +374,13 @@ func (a *Analyzer) AnalyzeStaleBranches(ctx context.Context, repoString string,
359374
360375 scannedPackage , err := inventory .ScanPackageScanner (ctx , * pkg , & inventoryScanner )
361376 if err != nil {
377+ obs .OnRepoError (repoString , err )
362378 return nil , fmt .Errorf ("failed to scan package: %w" , err )
363379 }
364380
365- _ = bar .Finish ()
381+ obs .OnRepoCompleted (repoString , scannedPackage )
382+
383+ obs .OnFinalizeStarted (1 )
366384 if * expand {
367385 expanded := []results.Finding {}
368386 for _ , finding := range scannedPackage .FindingsResults .Findings {
@@ -400,8 +418,8 @@ func (a *Analyzer) AnalyzeStaleBranches(ctx context.Context, repoString string,
400418 if err := a .Formatter .FormatWithPath (ctx , []* models.PackageInsights {scannedPackage }, results ); err != nil {
401419 return nil , fmt .Errorf ("failed to finalize analysis of package: %w" , err )
402420 }
403-
404421 }
422+ obs .OnFinalizeCompleted ()
405423
406424 return scannedPackage , nil
407425}
@@ -427,26 +445,31 @@ func (a *Analyzer) AnalyzeRepo(ctx context.Context, repoString string, ref strin
427445 pkgsupplyClient := pkgsupply .NewStaticClient ()
428446 inventory := scanner .NewInventory (a .Opa , pkgsupplyClient , provider , providerVersion )
429447
448+ obs := a .observer ()
449+ obs .OnAnalysisStarted ("Cloning repository" )
430450 log .Debug ().Msgf ("Starting repository analysis for: %s/%s on %s" , org , repoName , provider )
431- bar := a .ProgressBar (2 , "Cloning repository" )
432- _ = bar .RenderBlank ()
433451
452+ obs .OnRepoStarted (repoString )
434453 repoKey , err := a .cloneRepo (ctx , repo .BuildGitURL (a .ScmClient .GetProviderBaseURL ()), a .ScmClient .GetToken (), ref )
435454 if err != nil {
455+ obs .OnRepoError (repoString , err )
436456 return nil , err
437457 }
438458 defer a .GitClient .Cleanup (repoKey )
439459
440- bar .Describe ("Analyzing repository" )
441- _ = bar .Add (1 )
460+ obs .OnStepCompleted ("Cloned repository" )
442461
443462 pkg , err := a .GeneratePackageInsights (ctx , repoKey , repo , ref )
444463 if err != nil {
464+ obs .OnRepoError (repoString , err )
445465 return nil , err
446466 }
447467
468+ obs .OnStepCompleted ("Generated package insights" )
469+
448470 files , err := a .GitClient .ListFiles (repoKey , []string {".yml" , ".yaml" })
449471 if err != nil {
472+ obs .OnRepoError (repoString , err )
450473 return nil , fmt .Errorf ("failed to list files: %w" , err )
451474 }
452475
@@ -463,14 +486,19 @@ func (a *Analyzer) AnalyzeRepo(ctx context.Context, repoString string, ref strin
463486
464487 scannedPackage , err := inventory .ScanPackageScanner (ctx , * pkg , memScanner )
465488 if err != nil {
489+ obs .OnRepoError (repoString , err )
466490 return nil , err
467491 }
468- _ = bar .Finish ()
469492
493+ obs .OnStepCompleted ("Scanned repository" )
494+ obs .OnRepoCompleted (repoString , scannedPackage )
495+
496+ obs .OnFinalizeStarted (1 )
470497 err = a .finalizeAnalysis (ctx , []* models.PackageInsights {scannedPackage })
471498 if err != nil {
472499 return nil , err
473500 }
501+ obs .OnFinalizeCompleted ()
474502
475503 return scannedPackage , nil
476504}
@@ -721,18 +749,3 @@ func (a *Analyzer) cloneRepo(ctx context.Context, gitURL string, token string, r
721749 }
722750 return key , nil
723751}
724-
725- func (a * Analyzer ) ProgressBar (maxValue int64 , description string ) * progressbar.ProgressBar {
726- if a .Config .Quiet {
727- return progressbar .DefaultSilent (maxValue , description )
728- } else {
729- return progressbar .NewOptions64 (
730- maxValue ,
731- progressbar .OptionSetDescription (description ),
732- progressbar .OptionShowCount (),
733- progressbar .OptionSetWriter (os .Stderr ),
734- progressbar .OptionClearOnFinish (),
735- )
736-
737- }
738- }
0 commit comments