@@ -14,6 +14,8 @@ import (
1414
1515 "slices"
1616
17+ "github.com/gitpod-io/leeway/pkg/leeway/cache"
18+
1719 "github.com/anchore/clio"
1820 "github.com/anchore/grype/grype"
1921 "github.com/anchore/grype/grype/db/v6/distribution"
@@ -361,9 +363,38 @@ func ScanPackageForVulnerabilities(p *Package, buildctx *buildContext, sbomFile
361363 return nil
362364}
363365
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+
364395// ScanAllPackagesForVulnerabilities scans all packages for vulnerabilities
365396// 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 {
367398 // Skip if no packages to scan
368399 if len (packages ) == 0 {
369400 return nil
@@ -382,14 +413,29 @@ func ScanAllPackagesForVulnerabilities(buildctx *buildContext, packages []*Packa
382413 continue
383414 }
384415
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+ }
393439 }
394440
395441 // Find the SBOM file for this package
@@ -458,14 +504,14 @@ func ScanAllPackagesForVulnerabilities(buildctx *buildContext, packages []*Packa
458504 sbomFile = tempFileName
459505
460506 // 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 {
462508 buildctx .Reporter .PackageBuildLog (p , false , fmt .Appendf (nil , "Failed to scan package %s for vulnerabilities: %s\n " , p .FullName (), err .Error ()))
463509 // Add to failed packages
464510 failedPackages = append (failedPackages , p .FullName ())
465511 continue
466512 }
467513
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 ))
469515 }
470516
471517 // Return error if any packages failed due to vulnerabilities
@@ -675,6 +721,33 @@ func loadVulnerabilityDB(p *Package, buildctx *buildContext) (vulnerability.Prov
675721 return provider , status , nil
676722}
677723
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+
678751// ErrNoSBOMFile is returned when no SBOM file is found in a cached archive
679752var ErrNoSBOMFile = fmt .Errorf ("no SBOM file found" )
680753
0 commit comments