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  }