github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/dotnet/parse_packages_lock.go (about) 1 package dotnet 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "slices" 8 "sort" 9 10 "github.com/anchore/packageurl-go" 11 "github.com/anchore/syft/internal/log" 12 "github.com/anchore/syft/internal/relationship" 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 var _ generic.Parser = parseDotnetPackagesLock 20 21 type dotnetPackagesLock struct { 22 Version int `json:"version"` 23 Dependencies map[string]map[string]dotnetPackagesLockDep `json:"dependencies"` 24 } 25 26 type dotnetPackagesLockDep struct { 27 Type string `json:"type"` 28 Requested string `json:"requested"` 29 Resolved string `json:"resolved"` 30 ContentHash string `json:"contentHash"` 31 Dependencies map[string]string `json:"dependencies,omitempty"` 32 } 33 34 func parseDotnetPackagesLock(_ context.Context, _ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { //nolint:funlen 35 var pkgs []pkg.Package 36 var pkgMap = make(map[string]pkg.Package) 37 var relationships []artifact.Relationship 38 39 dec := json.NewDecoder(reader) 40 41 // unmarshal file 42 var lockFile dotnetPackagesLock 43 if err := dec.Decode(&lockFile); err != nil { 44 return nil, nil, fmt.Errorf("failed to parse packages.lock.json file: %w", err) 45 } 46 47 // collect all deps here 48 allDependencies := make(map[string]dotnetPackagesLockDep) 49 50 var names []string 51 for _, dependencies := range lockFile.Dependencies { 52 for name, dep := range dependencies { 53 depNameVersion := createNameAndVersion(name, dep.Resolved) 54 55 if slices.Contains(names, depNameVersion) { 56 continue 57 } 58 59 names = append(names, depNameVersion) 60 allDependencies[depNameVersion] = dep 61 } 62 } 63 64 // sort the names so that the order of the packages is deterministic 65 sort.Strings(names) 66 67 // create artifact for each pkg 68 for _, nameVersion := range names { 69 name, _ := extractNameAndVersion(nameVersion) 70 71 dep := allDependencies[nameVersion] 72 dotnetPkg := newDotnetPackagesLockPackage(name, dep, reader.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) 73 if dotnetPkg != nil { 74 pkgs = append(pkgs, *dotnetPkg) 75 pkgMap[nameVersion] = *dotnetPkg 76 } 77 } 78 79 // fill up relationships 80 for depNameVersion, dep := range allDependencies { 81 parentPkg, ok := pkgMap[depNameVersion] 82 if !ok { 83 log.Debugf("package \"%s\" not found in map of all pacakges", depNameVersion) 84 continue 85 } 86 87 for childDepName, childDepVersion := range dep.Dependencies { 88 childDepNameVersion := createNameAndVersion(childDepName, childDepVersion) 89 90 // try and find pkg for dependency with exact name and version 91 childPkg, ok := pkgMap[childDepNameVersion] 92 if !ok { 93 // no exact match found, lets match on name only, lockfile will contain other version of pkg 94 cpkg, ok := findPkgByName(childDepName, pkgMap) 95 if !ok { 96 log.Debugf("dependency \"%s\" of package \"%s\" not found in map of all packages", childDepNameVersion, depNameVersion) 97 continue 98 } 99 100 childPkg = *cpkg 101 } 102 103 rel := artifact.Relationship{ 104 From: parentPkg, 105 To: childPkg, 106 Type: artifact.DependencyOfRelationship, 107 } 108 relationships = append(relationships, rel) 109 } 110 } 111 112 // sort the relationships for deterministic output 113 relationship.Sort(relationships) 114 115 return pkgs, relationships, nil 116 } 117 118 func newDotnetPackagesLockPackage(name string, dep dotnetPackagesLockDep, locations ...file.Location) *pkg.Package { 119 metadata := pkg.DotnetPackagesLockEntry{ 120 Name: name, 121 Version: dep.Resolved, 122 ContentHash: dep.ContentHash, 123 Type: dep.Type, 124 } 125 126 p := &pkg.Package{ 127 Name: name, 128 Version: dep.Resolved, 129 Type: pkg.DotnetPkg, 130 Metadata: metadata, 131 Locations: file.NewLocationSet(locations...), 132 Language: pkg.Dotnet, 133 PURL: packagesLockPackageURL(name, dep.Resolved), 134 } 135 136 p.SetID() 137 138 return p 139 } 140 141 func packagesLockPackageURL(name, version string) string { 142 var qualifiers packageurl.Qualifiers 143 144 return packageurl.NewPackageURL( 145 packageurl.TypeNuget, // See explanation in syft/pkg/cataloger/dotnet/package.go as to why this was chosen. 146 "", 147 name, 148 version, 149 qualifiers, 150 "", 151 ).ToString() 152 } 153 154 func findPkgByName(pkgName string, pkgMap map[string]pkg.Package) (*pkg.Package, bool) { 155 for pkgNameVersion, pkg := range pkgMap { 156 name, _ := extractNameAndVersion(pkgNameVersion) 157 if name == pkgName { 158 return &pkg, true 159 } 160 } 161 162 return nil, false 163 }