github.com/wolfi-dev/wolfictl@v0.16.11/pkg/cli/internal/builds/builds.go (about) 1 package builds 2 3 import ( 4 "context" 5 "fmt" 6 "io/fs" 7 "path/filepath" 8 "slices" 9 10 goapk "github.com/chainguard-dev/go-apk/pkg/apk" 11 "github.com/wolfi-dev/wolfictl/pkg/apk" 12 "github.com/wolfi-dev/wolfictl/pkg/scan" 13 ) 14 15 // Find walks the given filesystem and returns a map of build groups, where each 16 // group is keyed by the origin package name, and contains the origin package 17 // and its subpackages. Find expects the filesystem to be laid out just like 18 // Melange outputs built APKs, starting with a "packages" directory containing 19 // subdirectories for each architecture, and APK files within those 20 // subdirectories. 21 func Find(fsys fs.FS, architectures []string) (map[string]BuildGroup, error) { 22 buildsByOrigin := make(map[string]BuildGroup) 23 24 err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { 25 if err != nil { 26 return err 27 } 28 29 if path == "." { 30 return nil 31 } 32 33 if d.IsDir() { 34 if !slices.Contains(architectures, d.Name()) { 35 // This is not an arch directory we care about 36 return fs.SkipDir 37 } 38 39 return nil 40 } 41 42 // This is a file 43 44 if filepath.Ext(d.Name()) != ".apk" { 45 // Not an APK file 46 return nil 47 } 48 49 // This is an APK file 50 51 f, err := fsys.Open(path) 52 if err != nil { 53 return fmt.Errorf("failed to open file %q: %w", path, err) 54 } 55 pkginfo, err := apk.PKGINFOFromAPK(f) 56 if err != nil { 57 return fmt.Errorf("failed to parse APK file %q: %w", path, err) 58 } 59 fileinfo, err := f.Stat() 60 if err != nil { 61 return fmt.Errorf("failed to stat file %q: %w", path, err) 62 } 63 _ = f.Close() // done with the file! 64 65 // Add to the build group 66 67 built := newBuiltPackage(fileinfo, pkginfo, path) 68 k := built.buildGroupKey() 69 70 if _, ok := buildsByOrigin[k]; !ok { 71 buildsByOrigin[k] = BuildGroup{ 72 Fsys: fsys, 73 } 74 } 75 bg := buildsByOrigin[k] 76 77 if built.PkgInfo.Name == built.PkgInfo.Origin { 78 // This is a top-level package 79 bg.Origin = built 80 } else { 81 // This is a subpackage 82 bg.Subpackages = append(bg.Subpackages, built) 83 } 84 85 buildsByOrigin[k] = bg 86 87 return nil 88 }) 89 if err != nil { 90 return nil, fmt.Errorf("failed to walk packages dir: %w", err) 91 } 92 93 return buildsByOrigin, nil 94 } 95 96 // Package describes a built (e.g. by Melange) APK package file that resides on 97 // a filesystem. 98 type Package struct { 99 // FsysPath is the path to the package file on the filesystem. 100 FsysPath string 101 102 // FileInfo is the file info of the package file. 103 FileInfo fs.FileInfo 104 105 // PkgInfo is the parsed package information (found in an APK's PKGINFO file). 106 PkgInfo *goapk.Package 107 } 108 109 func (p Package) buildGroupKey() string { 110 // Construct a key for use in build group maps, using the origin name, (full) 111 // version string, and architecture. 112 return fmt.Sprintf( 113 "%s-%s-%s", 114 p.PkgInfo.Origin, 115 p.PkgInfo.Version, 116 p.PkgInfo.Arch, 117 ) 118 } 119 120 func newBuiltPackage(fi fs.FileInfo, p *goapk.Package, fsysPath string) Package { 121 return Package{ 122 FsysPath: fsysPath, 123 FileInfo: fi, 124 PkgInfo: p, 125 } 126 } 127 128 // BuildGroup describes a set of Packages that were produced as a result of a 129 // Melange build of a package definition, which includes the origin package and 130 // 0-n subpackages as well. 131 type BuildGroup struct { 132 // Fsys is the filesystem where the build group was found. 133 Fsys fs.FS 134 135 Origin Package 136 Subpackages []Package 137 } 138 139 // Scan uses the provided scan.Scanner to scan the APKs in the build group for 140 // vulnerabilities. It returns a slice of scan results, one for each APK in the 141 // build group. The first slice member is the result for the origin APK, and the 142 // rest are for the subpackages. 143 func (bg BuildGroup) Scan(ctx context.Context, scanner *scan.Scanner, distroID string) ([]scan.Result, error) { 144 fsys := bg.Fsys 145 146 targets := append([]Package{bg.Origin}, bg.Subpackages...) 147 148 var results []scan.Result 149 150 for _, target := range targets { 151 // TODO: Run these scans in parallel to restore friendship with Jon. 152 153 apkfile, err := fsys.Open(target.FsysPath) 154 if err != nil { 155 return nil, fmt.Errorf("failed to open APK %q: %w", target.FsysPath, err) 156 } 157 158 result, err := scanner.ScanAPK(ctx, apkfile, distroID) 159 if err != nil { 160 return nil, fmt.Errorf("failed to scan APK %q: %w", target.FsysPath, err) 161 } 162 163 results = append(results, *result) 164 } 165 166 return results, nil 167 }