@@ -14,6 +14,8 @@ import (
14
14
15
15
"slices"
16
16
17
+ "github.com/gitpod-io/leeway/pkg/leeway/cache"
18
+
17
19
"github.com/anchore/clio"
18
20
"github.com/anchore/grype/grype"
19
21
"github.com/anchore/grype/grype/db/v6/distribution"
@@ -361,9 +363,38 @@ func ScanPackageForVulnerabilities(p *Package, buildctx *buildContext, sbomFile
361
363
return nil
362
364
}
363
365
366
+ // ScanPackageSBOM scans a package's SBOM for vulnerabilities and exports results to the specified directory
367
+ // This is an exported wrapper around ScanAllPackagesForVulnerabilities for use by the sbom scan command
368
+ // If withDependencies is true, it will also scan all dependencies of the package
369
+ func ScanPackageSBOM (p * Package , reporter Reporter , localCache cache.LocalCache , outputDir string , withDependencies bool ) error {
370
+ // Create a minimal buildContext with just the required fields
371
+ ctx := & buildContext {
372
+ buildOptions : buildOptions {
373
+ Reporter : reporter ,
374
+ LocalCache : localCache ,
375
+ },
376
+ }
377
+
378
+ // If we need to scan dependencies as well
379
+ if withDependencies {
380
+ // Get all dependencies
381
+ deps := getAllDependencies (p )
382
+ log .Infof ("Scanning %s and %d dependencies for vulnerabilities" , p .FullName (), len (deps ))
383
+
384
+ // Add the package itself to the list
385
+ packagesToScan := append ([]* Package {p }, deps ... )
386
+
387
+ // Call the existing function with all packages and the custom output directory
388
+ return ScanAllPackagesForVulnerabilities (ctx , packagesToScan , outputDir )
389
+ }
390
+
391
+ // Just scan the single package
392
+ return ScanAllPackagesForVulnerabilities (ctx , []* Package {p }, outputDir )
393
+ }
394
+
364
395
// ScanAllPackagesForVulnerabilities scans all packages for vulnerabilities
365
396
// This function is called after the build process completes
366
- func ScanAllPackagesForVulnerabilities (buildctx * buildContext , packages []* Package ) error {
397
+ func ScanAllPackagesForVulnerabilities (buildctx * buildContext , packages []* Package , customOutputDir ... string ) error {
367
398
// Skip if no packages to scan
368
399
if len (packages ) == 0 {
369
400
return nil
@@ -382,14 +413,29 @@ func ScanAllPackagesForVulnerabilities(buildctx *buildContext, packages []*Packa
382
413
continue
383
414
}
384
415
385
- // Get the location for this package's vulnerability reports
386
- reportLocation := GetVulnerabilityReportLocation (p , timestamp )
387
-
388
- // Create the directory for this package's vulnerability reports
389
- if err := os .MkdirAll (reportLocation .PackageDir , 0755 ); err != nil {
390
- errMsg := fmt .Sprintf ("failed to create vulnerability reports directory for package %s: %s" , p .FullName (), err )
391
- buildctx .Reporter .PackageBuildLog (p , true , []byte (errMsg + "\n " ))
392
- return xerrors .Errorf (errMsg )
416
+ // Determine the output directory
417
+ var outputDir string
418
+ if len (customOutputDir ) > 0 && customOutputDir [0 ] != "" {
419
+ // Use custom output directory if provided
420
+ outputDir = customOutputDir [0 ]
421
+
422
+ // Create the output directory if it doesn't exist
423
+ if err := os .MkdirAll (outputDir , 0755 ); err != nil {
424
+ errMsg := fmt .Sprintf ("failed to create output directory %s: %s" , outputDir , err )
425
+ buildctx .Reporter .PackageBuildLog (p , true , []byte (errMsg + "\n " ))
426
+ return xerrors .Errorf (errMsg )
427
+ }
428
+ } else {
429
+ // Use default timestamp-based directory structure
430
+ reportLocation := GetVulnerabilityReportLocation (p , timestamp )
431
+ outputDir = reportLocation .PackageDir
432
+
433
+ // Create the directory for this package's vulnerability reports
434
+ if err := os .MkdirAll (outputDir , 0755 ); err != nil {
435
+ errMsg := fmt .Sprintf ("failed to create vulnerability reports directory for package %s: %s" , p .FullName (), err )
436
+ buildctx .Reporter .PackageBuildLog (p , true , []byte (errMsg + "\n " ))
437
+ return xerrors .Errorf (errMsg )
438
+ }
393
439
}
394
440
395
441
// Find the SBOM file for this package
@@ -458,14 +504,14 @@ func ScanAllPackagesForVulnerabilities(buildctx *buildContext, packages []*Packa
458
504
sbomFile = tempFileName
459
505
460
506
// Scan the package for vulnerabilities
461
- if err := ScanPackageForVulnerabilities (p , buildctx , sbomFile , reportLocation . PackageDir ); err != nil {
507
+ if err := ScanPackageForVulnerabilities (p , buildctx , sbomFile , outputDir ); err != nil {
462
508
buildctx .Reporter .PackageBuildLog (p , false , fmt .Appendf (nil , "Failed to scan package %s for vulnerabilities: %s\n " , p .FullName (), err .Error ()))
463
509
// Add to failed packages
464
510
failedPackages = append (failedPackages , p .FullName ())
465
511
continue
466
512
}
467
513
468
- buildctx .Reporter .PackageBuildLog (p , false , fmt .Appendf (nil , "Vulnerability scan completed for package %s (reports: %s)\n " , p .FullName (), reportLocation . PackageDir ))
514
+ buildctx .Reporter .PackageBuildLog (p , false , fmt .Appendf (nil , "Vulnerability scan completed for package %s (reports: %s)\n " , p .FullName (), outputDir ))
469
515
}
470
516
471
517
// Return error if any packages failed due to vulnerabilities
@@ -675,6 +721,33 @@ func loadVulnerabilityDB(p *Package, buildctx *buildContext) (vulnerability.Prov
675
721
return provider , status , nil
676
722
}
677
723
724
+ // Helper function to get all dependencies of a package
725
+ func getAllDependencies (pkg * Package ) []* Package {
726
+ // Use a map to avoid duplicates
727
+ depsMap := make (map [string ]* Package )
728
+
729
+ // Recursively collect dependencies
730
+ var collectDeps func (p * Package )
731
+ collectDeps = func (p * Package ) {
732
+ for _ , dep := range p .GetDependencies () {
733
+ if _ , exists := depsMap [dep .FullName ()]; ! exists {
734
+ depsMap [dep .FullName ()] = dep
735
+ collectDeps (dep )
736
+ }
737
+ }
738
+ }
739
+
740
+ collectDeps (pkg )
741
+
742
+ // Convert map to slice
743
+ deps := make ([]* Package , 0 , len (depsMap ))
744
+ for _ , dep := range depsMap {
745
+ deps = append (deps , dep )
746
+ }
747
+
748
+ return deps
749
+ }
750
+
678
751
// ErrNoSBOMFile is returned when no SBOM file is found in a cached archive
679
752
var ErrNoSBOMFile = fmt .Errorf ("no SBOM file found" )
680
753
0 commit comments