github.com/anchore/syft@v1.4.2-0.20240516191711-1bec1fc5d397/syft/pkg/cataloger/golang/parse_go_mod.go (about) 1 package golang 2 3 import ( 4 "bufio" 5 "context" 6 "fmt" 7 "io" 8 "sort" 9 "strings" 10 11 "golang.org/x/mod/modfile" 12 13 "github.com/anchore/syft/internal" 14 "github.com/anchore/syft/internal/log" 15 "github.com/anchore/syft/syft/artifact" 16 "github.com/anchore/syft/syft/file" 17 "github.com/anchore/syft/syft/pkg" 18 "github.com/anchore/syft/syft/pkg/cataloger/generic" 19 ) 20 21 type goModCataloger struct { 22 licenses goLicenses 23 } 24 25 // parseGoModFile takes a go.mod and lists all packages discovered. 26 // 27 //nolint:funlen 28 func (c *goModCataloger) parseGoModFile(_ context.Context, resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 29 packages := make(map[string]pkg.Package) 30 31 contents, err := io.ReadAll(reader) 32 if err != nil { 33 return nil, nil, fmt.Errorf("failed to read go module: %w", err) 34 } 35 36 f, err := modfile.Parse(reader.RealPath, contents, nil) 37 if err != nil { 38 return nil, nil, fmt.Errorf("failed to parse go module: %w", err) 39 } 40 41 digests, err := parseGoSumFile(resolver, reader) 42 if err != nil { 43 log.Debugf("unable to get go.sum: %v", err) 44 } 45 46 for _, m := range f.Require { 47 licenses, err := c.licenses.getLicenses(resolver, m.Mod.Path, m.Mod.Version) 48 if err != nil { 49 log.Tracef("error getting licenses for package: %s %v", m.Mod.Path, err) 50 } 51 52 packages[m.Mod.Path] = pkg.Package{ 53 Name: m.Mod.Path, 54 Version: m.Mod.Version, 55 Licenses: pkg.NewLicenseSet(licenses...), 56 Locations: file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 57 PURL: packageURL(m.Mod.Path, m.Mod.Version), 58 Language: pkg.Go, 59 Type: pkg.GoModulePkg, 60 Metadata: pkg.GolangModuleEntry{ 61 H1Digest: digests[fmt.Sprintf("%s %s", m.Mod.Path, m.Mod.Version)], 62 }, 63 } 64 } 65 66 // remove any old packages and replace with new ones... 67 for _, m := range f.Replace { 68 licenses, err := c.licenses.getLicenses(resolver, m.New.Path, m.New.Version) 69 if err != nil { 70 log.Tracef("error getting licenses for package: %s %v", m.New.Path, err) 71 } 72 73 packages[m.New.Path] = pkg.Package{ 74 Name: m.New.Path, 75 Version: m.New.Version, 76 Licenses: pkg.NewLicenseSet(licenses...), 77 Locations: file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 78 PURL: packageURL(m.New.Path, m.New.Version), 79 Language: pkg.Go, 80 Type: pkg.GoModulePkg, 81 Metadata: pkg.GolangModuleEntry{ 82 H1Digest: digests[fmt.Sprintf("%s %s", m.New.Path, m.New.Version)], 83 }, 84 } 85 } 86 87 // remove any packages from the exclude fields 88 for _, m := range f.Exclude { 89 delete(packages, m.Mod.Path) 90 } 91 92 pkgsSlice := make([]pkg.Package, len(packages)) 93 idx := 0 94 for _, p := range packages { 95 p.SetID() 96 pkgsSlice[idx] = p 97 idx++ 98 } 99 100 sort.SliceStable(pkgsSlice, func(i, j int) bool { 101 return pkgsSlice[i].Name < pkgsSlice[j].Name 102 }) 103 104 return pkgsSlice, nil, nil 105 } 106 107 func parseGoSumFile(resolver file.Resolver, reader file.LocationReadCloser) (map[string]string, error) { 108 out := map[string]string{} 109 110 if resolver == nil { 111 return out, fmt.Errorf("no resolver provided") 112 } 113 114 goSumPath := strings.TrimSuffix(reader.Location.RealPath, ".mod") + ".sum" 115 goSumLocation := resolver.RelativeFileByPath(reader.Location, goSumPath) 116 if goSumLocation == nil { 117 return nil, fmt.Errorf("unable to resolve: %s", goSumPath) 118 } 119 contents, err := resolver.FileContentsByLocation(*goSumLocation) 120 if err != nil { 121 return nil, err 122 } 123 defer internal.CloseAndLogError(contents, goSumLocation.AccessPath) 124 125 // go.sum has the format like: 126 // github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 127 // github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= 128 // github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 129 scanner := bufio.NewScanner(contents) 130 // optionally, resize scanner's capacity for lines over 64K, see next example 131 for scanner.Scan() { 132 line := scanner.Text() 133 parts := strings.Split(line, " ") 134 if len(parts) < 3 { 135 continue 136 } 137 nameVersion := fmt.Sprintf("%s %s", parts[0], parts[1]) 138 hash := parts[2] 139 out[nameVersion] = hash 140 } 141 142 return out, nil 143 }