github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/golang/parse_go_mod.go (about) 1 package golang 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "sort" 8 "strings" 9 10 "golang.org/x/mod/modfile" 11 12 "github.com/anchore/syft/internal/log" 13 "github.com/anchore/syft/syft/artifact" 14 "github.com/anchore/syft/syft/file" 15 "github.com/anchore/syft/syft/pkg" 16 "github.com/anchore/syft/syft/pkg/cataloger/generic" 17 ) 18 19 type goModCataloger struct { 20 licenses goLicenses 21 } 22 23 // parseGoModFile takes a go.mod and lists all packages discovered. 24 // 25 //nolint:funlen 26 func (c *goModCataloger) parseGoModFile(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { 27 packages := make(map[string]pkg.Package) 28 29 contents, err := io.ReadAll(reader) 30 if err != nil { 31 return nil, nil, fmt.Errorf("failed to read go module: %w", err) 32 } 33 34 f, err := modfile.Parse(reader.RealPath, contents, nil) 35 if err != nil { 36 return nil, nil, fmt.Errorf("failed to parse go module: %w", err) 37 } 38 39 digests, err := parseGoSumFile(resolver, reader) 40 if err != nil { 41 log.Debugf("unable to get go.sum: %v", err) 42 } 43 44 for _, m := range f.Require { 45 licenses, err := c.licenses.getLicenses(resolver, m.Mod.Path, m.Mod.Version) 46 if err != nil { 47 log.Tracef("error getting licenses for package: %s %v", m.Mod.Path, err) 48 } 49 50 packages[m.Mod.Path] = pkg.Package{ 51 Name: m.Mod.Path, 52 Version: m.Mod.Version, 53 Licenses: pkg.NewLicenseSet(licenses...), 54 Locations: file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 55 PURL: packageURL(m.Mod.Path, m.Mod.Version), 56 Language: pkg.Go, 57 Type: pkg.GoModulePkg, 58 MetadataType: pkg.GolangModMetadataType, 59 Metadata: pkg.GolangModMetadata{ 60 H1Digest: digests[fmt.Sprintf("%s %s", m.Mod.Path, m.Mod.Version)], 61 }, 62 } 63 } 64 65 // remove any old packages and replace with new ones... 66 for _, m := range f.Replace { 67 licenses, err := c.licenses.getLicenses(resolver, m.New.Path, m.New.Version) 68 if err != nil { 69 log.Tracef("error getting licenses for package: %s %v", m.New.Path, err) 70 } 71 72 packages[m.New.Path] = pkg.Package{ 73 Name: m.New.Path, 74 Version: m.New.Version, 75 Licenses: pkg.NewLicenseSet(licenses...), 76 Locations: file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), 77 PURL: packageURL(m.New.Path, m.New.Version), 78 Language: pkg.Go, 79 Type: pkg.GoModulePkg, 80 MetadataType: pkg.GolangModMetadataType, 81 Metadata: pkg.GolangModMetadata{ 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 124 // go.sum has the format like: 125 // github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 126 // github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= 127 // github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= 128 scanner := bufio.NewScanner(contents) 129 // optionally, resize scanner's capacity for lines over 64K, see next example 130 for scanner.Scan() { 131 line := scanner.Text() 132 parts := strings.Split(line, " ") 133 if len(parts) < 3 { 134 continue 135 } 136 nameVersion := fmt.Sprintf("%s %s", parts[0], parts[1]) 137 hash := parts[2] 138 out[nameVersion] = hash 139 } 140 141 return out, nil 142 }