diff --git a/syft/pkg/cataloger/dotnet/package.go b/syft/pkg/cataloger/dotnet/package.go index c8cb261a6..a7b3b209f 100644 --- a/syft/pkg/cataloger/dotnet/package.go +++ b/syft/pkg/cataloger/dotnet/package.go @@ -1,6 +1,8 @@ package dotnet import ( + "fmt" + "regexp" "strings" "github.com/anchore/packageurl-go" @@ -9,13 +11,7 @@ import ( ) func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations ...file.Location) *pkg.Package { - if lib.Type != "package" { - return nil - } - - fields := strings.Split(nameVersion, "/") - name := fields[0] - version := fields[1] + name, version := extractNameAndVersion(nameVersion) m := pkg.DotnetDepsMetadata{ Name: name, @@ -41,6 +37,27 @@ func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations . return p } +func getDepsJSONFilePrefix(p string) string { + r := regexp.MustCompile(`([^\/]+)\.deps\.json$`) + match := r.FindStringSubmatch(p) + if len(match) > 1 { + return match[1] + } + return "" +} + +func extractNameAndVersion(nameVersion string) (name, version string) { + fields := strings.Split(nameVersion, "/") + name = fields[0] + version = fields[1] + return +} + +func createNameAndVersion(name, version string) (nameVersion string) { + nameVersion = fmt.Sprintf("%s/%s", name, version) + return +} + func packageURL(m pkg.DotnetDepsMetadata) string { var qualifiers packageurl.Qualifiers diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go b/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go index 2c7e1cf0b..e99811ac8 100644 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go +++ b/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go @@ -5,6 +5,7 @@ import ( "fmt" "sort" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" @@ -13,8 +14,18 @@ import ( var _ generic.Parser = parseDotnetDeps +type dotnetRuntimeTarget struct { + Name string `json:"name"` +} + +type dotnetDepsTarget struct { + Dependencies map[string]string `json:"dependencies"` + Runtime map[string]struct{} `json:"runtime"` +} type dotnetDeps struct { - Libraries map[string]dotnetDepsLibrary `json:"libraries"` + RuntimeTarget dotnetRuntimeTarget `json:"runtimeTarget"` + Targets map[string]map[string]dotnetDepsTarget `json:"targets"` + Libraries map[string]dotnetDepsLibrary `json:"libraries"` } type dotnetDepsLibrary struct { @@ -24,27 +35,55 @@ type dotnetDepsLibrary struct { HashPath string `json:"hashPath"` } +//nolint:funlen func parseDotnetDeps(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package + var pkgMap = make(map[string]pkg.Package) + var relationships []artifact.Relationship dec := json.NewDecoder(reader) - var p dotnetDeps - if err := dec.Decode(&p); err != nil { + var depsDoc dotnetDeps + if err := dec.Decode(&depsDoc); err != nil { return nil, nil, fmt.Errorf("failed to parse deps.json file: %w", err) } - var names []string + rootName := getDepsJSONFilePrefix(reader.AccessPath()) + if rootName == "" { + return nil, nil, fmt.Errorf("unable to determine root package name from deps.json file: %s", reader.AccessPath()) + } + var rootPkg *pkg.Package + for nameVersion, lib := range depsDoc.Libraries { + name, _ := extractNameAndVersion(nameVersion) + if lib.Type == "project" && name == rootName { + rootPkg = newDotnetDepsPackage( + nameVersion, + lib, + reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ) + } + } + if rootPkg == nil { + return nil, nil, fmt.Errorf("unable to determine root package from deps.json file: %s", reader.AccessPath()) + } + pkgs = append(pkgs, *rootPkg) + pkgMap[createNameAndVersion(rootPkg.Name, rootPkg.Version)] = *rootPkg - for nameVersion := range p.Libraries { + var names []string + for nameVersion := range depsDoc.Libraries { names = append(names, nameVersion) } - // sort the names so that the order of the packages is deterministic sort.Strings(names) for _, nameVersion := range names { - lib := p.Libraries[nameVersion] + // skip the root package + name, version := extractNameAndVersion(nameVersion) + if name == rootPkg.Name && version == rootPkg.Version { + continue + } + + lib := depsDoc.Libraries[nameVersion] dotnetPkg := newDotnetDepsPackage( nameVersion, lib, @@ -53,8 +92,36 @@ func parseDotnetDeps(_ file.Resolver, _ *generic.Environment, reader file.Locati if dotnetPkg != nil { pkgs = append(pkgs, *dotnetPkg) + pkgMap[nameVersion] = *dotnetPkg } } - return pkgs, nil, nil + for pkgNameVersion, target := range depsDoc.Targets[depsDoc.RuntimeTarget.Name] { + for depName, depVersion := range target.Dependencies { + depNameVersion := createNameAndVersion(depName, depVersion) + depPkg, ok := pkgMap[depNameVersion] + if !ok { + log.Debug("unable to find package in map", depNameVersion) + continue + } + p, ok := pkgMap[pkgNameVersion] + if !ok { + log.Debug("unable to find package in map", pkgNameVersion) + continue + } + rel := artifact.Relationship{ + From: depPkg, + To: p, + Type: artifact.DependencyOfRelationship, + } + relationships = append(relationships, rel) + } + } + + // sort the relationships for deterministic output + // TODO: ideally this would be replaced with artifact.SortRelationships when one exists and is type agnostic. + // this will only consider package-to-package relationships. + pkg.SortRelationships(relationships) + + return pkgs, relationships, nil } diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go b/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go index b85353744..b71850100 100644 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go +++ b/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go @@ -12,202 +12,363 @@ import ( func TestParseDotnetDeps(t *testing.T) { fixture := "test-fixtures/TestLibrary.deps.json" fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture)) - expected := []pkg.Package{ - { - Name: "AWSSDK.Core", - Version: "3.7.10.6", - PURL: "pkg:nuget/AWSSDK.Core@3.7.10.6", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "AWSSDK.Core", - Version: "3.7.10.6", - Sha512: "sha512-kHBB+QmosVaG6DpngXQ8OlLVVNMzltNITfsRr68Z90qO7dSqJ2EHNd8dtBU1u3AQQLqqFHOY0lfmbpexeH6Pew==", - Path: "awssdk.core/3.7.10.6", - HashPath: "awssdk.core.3.7.10.6.nupkg.sha512", - }, + rootPkg := pkg.Package{ + Name: "TestLibrary", + Version: "1.0.0", + PURL: "pkg:nuget/TestLibrary@1.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "TestLibrary", + Version: "1.0.0", }, - { - Name: "Microsoft.Extensions.DependencyInjection.Abstractions", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Microsoft.Extensions.DependencyInjection.Abstractions", - Version: "6.0.0", - Sha512: "sha512-xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==", - Path: "microsoft.extensions.dependencyinjection.abstractions/6.0.0", - HashPath: "microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512", - }, + } + testCommon := pkg.Package{ + Name: "TestCommon", + Version: "1.0.0", + PURL: "pkg:nuget/TestCommon@1.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "TestCommon", + Version: "1.0.0", }, - { - Name: "Microsoft.Extensions.DependencyInjection", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Microsoft.Extensions.DependencyInjection", - Version: "6.0.0", - Sha512: "sha512-k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", - Path: "microsoft.extensions.dependencyinjection/6.0.0", - HashPath: "microsoft.extensions.dependencyinjection.6.0.0.nupkg.sha512", - }, + } + awssdkcore := pkg.Package{ + Name: "AWSSDK.Core", + Version: "3.7.10.6", + PURL: "pkg:nuget/AWSSDK.Core@3.7.10.6", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "AWSSDK.Core", + Version: "3.7.10.6", + Sha512: "sha512-kHBB+QmosVaG6DpngXQ8OlLVVNMzltNITfsRr68Z90qO7dSqJ2EHNd8dtBU1u3AQQLqqFHOY0lfmbpexeH6Pew==", + Path: "awssdk.core/3.7.10.6", + HashPath: "awssdk.core.3.7.10.6.nupkg.sha512", }, - { - Name: "Microsoft.Extensions.Logging.Abstractions", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.Logging.Abstractions@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Microsoft.Extensions.Logging.Abstractions", - Version: "6.0.0", - Sha512: "sha512-/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==", - Path: "microsoft.extensions.logging.abstractions/6.0.0", - HashPath: "microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512", - }, + } + msftDependencyInjectionAbstractions := pkg.Package{ + Name: "Microsoft.Extensions.DependencyInjection.Abstractions", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection.Abstractions@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Microsoft.Extensions.DependencyInjection.Abstractions", + Version: "6.0.0", + Sha512: "sha512-xlzi2IYREJH3/m6+lUrQlujzX8wDitm4QGnUu6kUXTQAWPuZY8i+ticFJbzfqaetLA6KR/rO6Ew/HuYD+bxifg==", + Path: "microsoft.extensions.dependencyinjection.abstractions/6.0.0", + HashPath: "microsoft.extensions.dependencyinjection.abstractions.6.0.0.nupkg.sha512", }, - { - Name: "Microsoft.Extensions.Logging", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.Logging@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Microsoft.Extensions.Logging", - Version: "6.0.0", - Sha512: "sha512-eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", - Path: "microsoft.extensions.logging/6.0.0", - HashPath: "microsoft.extensions.logging.6.0.0.nupkg.sha512", - }, + } + msftDependencyInjection := pkg.Package{ + Name: "Microsoft.Extensions.DependencyInjection", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.DependencyInjection@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Microsoft.Extensions.DependencyInjection", + Version: "6.0.0", + Sha512: "sha512-k6PWQMuoBDGGHOQTtyois2u4AwyVcIwL2LaSLlTZQm2CYcJ1pxbt6jfAnpWmzENA/wfrYRI/X9DTLoUkE4AsLw==", + Path: "microsoft.extensions.dependencyinjection/6.0.0", + HashPath: "microsoft.extensions.dependencyinjection.6.0.0.nupkg.sha512", }, - - { - Name: "Microsoft.Extensions.Options", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.Options@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Microsoft.Extensions.Options", - Version: "6.0.0", - Sha512: "sha512-dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", - Path: "microsoft.extensions.options/6.0.0", - HashPath: "microsoft.extensions.options.6.0.0.nupkg.sha512", - }, + } + msftLoggingAbstractions := pkg.Package{ + Name: "Microsoft.Extensions.Logging.Abstractions", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.Logging.Abstractions@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Microsoft.Extensions.Logging.Abstractions", + Version: "6.0.0", + Sha512: "sha512-/HggWBbTwy8TgebGSX5DBZ24ndhzi93sHUBDvP1IxbZD7FDokYzdAr6+vbWGjw2XAfR2EJ1sfKUotpjHnFWPxA==", + Path: "microsoft.extensions.logging.abstractions/6.0.0", + HashPath: "microsoft.extensions.logging.abstractions.6.0.0.nupkg.sha512", }, - { - Name: "Microsoft.Extensions.Primitives", - Version: "6.0.0", - PURL: "pkg:nuget/Microsoft.Extensions.Primitives@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Microsoft.Extensions.Primitives", - Version: "6.0.0", - Sha512: "sha512-9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", - Path: "microsoft.extensions.primitives/6.0.0", - HashPath: "microsoft.extensions.primitives.6.0.0.nupkg.sha512", - }, + } + msftExtensionsLogging := pkg.Package{ + Name: "Microsoft.Extensions.Logging", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.Logging@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Microsoft.Extensions.Logging", + Version: "6.0.0", + Sha512: "sha512-eIbyj40QDg1NDz0HBW0S5f3wrLVnKWnDJ/JtZ+yJDFnDj90VoPuoPmFkeaXrtu+0cKm5GRAwoDf+dBWXK0TUdg==", + Path: "microsoft.extensions.logging/6.0.0", + HashPath: "microsoft.extensions.logging.6.0.0.nupkg.sha512", }, - { - Name: "Newtonsoft.Json", - Version: "13.0.1", - PURL: "pkg:nuget/Newtonsoft.Json@13.0.1", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Newtonsoft.Json", - Version: "13.0.1", - Sha512: "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==", - Path: "newtonsoft.json/13.0.1", - HashPath: "newtonsoft.json.13.0.1.nupkg.sha512", - }, + } + msftExtensionsOptions := pkg.Package{ + Name: "Microsoft.Extensions.Options", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.Options@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Microsoft.Extensions.Options", + Version: "6.0.0", + Sha512: "sha512-dzXN0+V1AyjOe2xcJ86Qbo233KHuLEY0njf/P2Kw8SfJU+d45HNS2ctJdnEnrWbM9Ye2eFgaC5Mj9otRMU6IsQ==", + Path: "microsoft.extensions.options/6.0.0", + HashPath: "microsoft.extensions.options.6.0.0.nupkg.sha512", }, - { - Name: "Serilog.Sinks.Console", - Version: "4.0.1", - PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Serilog.Sinks.Console", - Version: "4.0.1", - Sha512: "sha512-apLOvSJQLlIbKlbx+Y2UDHSP05kJsV7mou+fvJoRGs/iR+jC22r8cuFVMjjfVxz/AD4B2UCltFhE1naRLXwKNw==", - Path: "serilog.sinks.console/4.0.1", - HashPath: "serilog.sinks.console.4.0.1.nupkg.sha512", - }, + } + msftExtensionsPrimitives := pkg.Package{ + Name: "Microsoft.Extensions.Primitives", + Version: "6.0.0", + PURL: "pkg:nuget/Microsoft.Extensions.Primitives@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Microsoft.Extensions.Primitives", + Version: "6.0.0", + Sha512: "sha512-9+PnzmQFfEFNR9J2aDTfJGGupShHjOuGw4VUv+JB044biSHrnmCIMD+mJHmb2H7YryrfBEXDurxQ47gJZdCKNQ==", + Path: "microsoft.extensions.primitives/6.0.0", + HashPath: "microsoft.extensions.primitives.6.0.0.nupkg.sha512", }, - { - Name: "Serilog", - Version: "2.10.0", - PURL: "pkg:nuget/Serilog@2.10.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "Serilog", - Version: "2.10.0", - Sha512: "sha512-+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==", - Path: "serilog/2.10.0", - HashPath: "serilog.2.10.0.nupkg.sha512", - }, + } + newtonsoftJson := pkg.Package{ + Name: "Newtonsoft.Json", + Version: "13.0.1", + PURL: "pkg:nuget/Newtonsoft.Json@13.0.1", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Newtonsoft.Json", + Version: "13.0.1", + Sha512: "sha512-ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==", + Path: "newtonsoft.json/13.0.1", + HashPath: "newtonsoft.json.13.0.1.nupkg.sha512", }, - { - Name: "System.Diagnostics.DiagnosticSource", - Version: "6.0.0", - PURL: "pkg:nuget/System.Diagnostics.DiagnosticSource@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "System.Diagnostics.DiagnosticSource", - Version: "6.0.0", - Sha512: "sha512-frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==", - Path: "system.diagnostics.diagnosticsource/6.0.0", - HashPath: "system.diagnostics.diagnosticsource.6.0.0.nupkg.sha512", - }, + } + serilogSinksConsole := pkg.Package{ + Name: "Serilog.Sinks.Console", + Version: "4.0.1", + PURL: "pkg:nuget/Serilog.Sinks.Console@4.0.1", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Serilog.Sinks.Console", + Version: "4.0.1", + Sha512: "sha512-apLOvSJQLlIbKlbx+Y2UDHSP05kJsV7mou+fvJoRGs/iR+jC22r8cuFVMjjfVxz/AD4B2UCltFhE1naRLXwKNw==", + Path: "serilog.sinks.console/4.0.1", + HashPath: "serilog.sinks.console.4.0.1.nupkg.sha512", }, - { - Name: "System.Runtime.CompilerServices.Unsafe", - Version: "6.0.0", - PURL: "pkg:nuget/System.Runtime.CompilerServices.Unsafe@6.0.0", - Locations: fixtureLocationSet, - Language: pkg.Dotnet, - Type: pkg.DotnetPkg, - MetadataType: pkg.DotnetDepsMetadataType, - Metadata: pkg.DotnetDepsMetadata{ - Name: "System.Runtime.CompilerServices.Unsafe", - Version: "6.0.0", - Sha512: "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", - Path: "system.runtime.compilerservices.unsafe/6.0.0", - HashPath: "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", - }, + } + serilog := pkg.Package{ + Name: "Serilog", + Version: "2.10.0", + PURL: "pkg:nuget/Serilog@2.10.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "Serilog", + Version: "2.10.0", + Sha512: "sha512-+QX0hmf37a0/OZLxM3wL7V6/ADvC1XihXN4Kq/p6d8lCPfgkRdiuhbWlMaFjR9Av0dy5F0+MBeDmDdRZN/YwQA==", + Path: "serilog/2.10.0", + HashPath: "serilog.2.10.0.nupkg.sha512", + }, + } + systemDiagnosticsDiagnosticsource := pkg.Package{ + Name: "System.Diagnostics.DiagnosticSource", + Version: "6.0.0", + PURL: "pkg:nuget/System.Diagnostics.DiagnosticSource@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "System.Diagnostics.DiagnosticSource", + Version: "6.0.0", + Sha512: "sha512-frQDfv0rl209cKm1lnwTgFPzNigy2EKk1BS3uAvHvlBVKe5cymGyHO+Sj+NLv5VF/AhHsqPIUUwya5oV4CHMUw==", + Path: "system.diagnostics.diagnosticsource/6.0.0", + HashPath: "system.diagnostics.diagnosticsource.6.0.0.nupkg.sha512", + }, + } + systemRuntimeCompilerServicesUnsafe := pkg.Package{ + Name: "System.Runtime.CompilerServices.Unsafe", + Version: "6.0.0", + PURL: "pkg:nuget/System.Runtime.CompilerServices.Unsafe@6.0.0", + Locations: fixtureLocationSet, + Language: pkg.Dotnet, + Type: pkg.DotnetPkg, + MetadataType: pkg.DotnetDepsMetadataType, + Metadata: pkg.DotnetDepsMetadata{ + Name: "System.Runtime.CompilerServices.Unsafe", + Version: "6.0.0", + Sha512: "sha512-/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==", + Path: "system.runtime.compilerservices.unsafe/6.0.0", + HashPath: "system.runtime.compilerservices.unsafe.6.0.0.nupkg.sha512", }, } - var expectedRelationships []artifact.Relationship - pkgtest.TestFileParser(t, fixture, parseDotnetDeps, expected, expectedRelationships) + expectedPkgs := []pkg.Package{ + awssdkcore, + msftDependencyInjection, + msftDependencyInjectionAbstractions, + msftExtensionsLogging, + msftLoggingAbstractions, + msftExtensionsOptions, + msftExtensionsPrimitives, + newtonsoftJson, + serilog, + serilogSinksConsole, + systemDiagnosticsDiagnosticsource, + systemRuntimeCompilerServicesUnsafe, + testCommon, + rootPkg, + } + + // ┌── (✓ = is represented in the test) + // ↓ + // + // ✓ TestLibrary/1.0.0 (project) + // ✓ ├── [a] Microsoft.Extensions.DependencyInjection/6.0.0 [file version: 6.0.21.52210] + // ✓ │ ├── [b] Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 [file version: 6.0.21.52210] + // ✓ │ └── [c!] System.Runtime.CompilerServices.Unsafe/6.0.0 [NO TARGET INFO] + // ✓ ├── Microsoft.Extensions.Logging/6.0.0 [file version: 6.0.21.52210] + // ✓ │ ├── Microsoft.Extensions.DependencyInjection/6.0.0 ...to [a] + // ✓ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b] + // ✓ │ ├── Microsoft.Extensions.Logging.Abstractions/6.0.0 [file version: 6.0.21.52210] + // ✓ │ ├── Microsoft.Extensions.Options/6.0.0 [file version: 6.0.21.52210] + // ✓ │ │ ├── Microsoft.Extensions.DependencyInjection.Abstractions/6.0.0 ...to [b] + // ✓ │ │ └── Microsoft.Extensions.Primitives/6.0.0 [file version: 6.0.21.52210] + // ✓ │ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!] + // ✓ │ └── System.Diagnostics.DiagnosticSource/6.0.0 [NO RUNTIME INFO] + // ✓ │ └── System.Runtime.CompilerServices.Unsafe/6.0.0 ...to [c!] + // ✓ ├── Newtonsoft.Json/13.0.1 [file version: 13.0.1.25517] + // ✓ ├── [d] Serilog/2.10.0 [file version: 2.10.0.0] + // ✓ ├── Serilog.Sinks.Console/4.0.1 [file version: 4.0.1.0] + // ✓ │ └── Serilog/2.10.0 ...to [d] + // ✓ └── [e!] TestCommon/1.0.0 [NOT SERVICEABLE / NO SHA] + // ✓ └── AWSSDK.Core/3.7.10.6 [file version: 3.7.10.6] + + expectedRelationships := []artifact.Relationship{ + { + From: awssdkcore, + To: testCommon, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjection, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjection, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjectionAbstractions, + To: msftDependencyInjection, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjectionAbstractions, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftDependencyInjectionAbstractions, + To: msftExtensionsOptions, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftExtensionsLogging, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftLoggingAbstractions, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftExtensionsOptions, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: msftExtensionsPrimitives, + To: msftExtensionsOptions, + Type: artifact.DependencyOfRelationship, + }, + { + From: newtonsoftJson, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: serilog, + To: serilogSinksConsole, + Type: artifact.DependencyOfRelationship, + }, + { + From: serilog, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: serilogSinksConsole, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + { + From: systemDiagnosticsDiagnosticsource, + To: msftExtensionsLogging, + Type: artifact.DependencyOfRelationship, + }, + { + From: systemRuntimeCompilerServicesUnsafe, + To: msftDependencyInjection, + Type: artifact.DependencyOfRelationship, + }, + { + From: systemRuntimeCompilerServicesUnsafe, + To: msftExtensionsPrimitives, + Type: artifact.DependencyOfRelationship, + }, + { + From: systemRuntimeCompilerServicesUnsafe, + To: systemDiagnosticsDiagnosticsource, + Type: artifact.DependencyOfRelationship, + }, + { + From: testCommon, + To: rootPkg, + Type: artifact.DependencyOfRelationship, + }, + } + + pkgtest.TestFileParser(t, fixture, parseDotnetDeps, expectedPkgs, expectedRelationships) } diff --git a/syft/pkg/cataloger/dotnet/test-fixtures/TestLibrary.deps.json b/syft/pkg/cataloger/dotnet/test-fixtures/TestLibrary.deps.json index a429ec409..788eb909e 100644 --- a/syft/pkg/cataloger/dotnet/test-fixtures/TestLibrary.deps.json +++ b/syft/pkg/cataloger/dotnet/test-fixtures/TestLibrary.deps.json @@ -232,4 +232,4 @@ "sha512": "" } } -} \ No newline at end of file +} diff --git a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go index 9182ce333..e5b44d459 100644 --- a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go +++ b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go @@ -329,6 +329,10 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi opts = append(opts, p.compareOptions...) opts = append(opts, cmp.Reporter(&r)) + // order should not matter + pkg.Sort(p.expectedPkgs) + pkg.Sort(pkgs) + if diff := cmp.Diff(p.expectedPkgs, pkgs, opts...); diff != "" { t.Log("Specific Differences:\n" + r.String()) t.Errorf("unexpected packages from parsing (-expected +actual)\n%s", diff) @@ -341,6 +345,10 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi opts = append(opts, p.compareOptions...) opts = append(opts, cmp.Reporter(&r)) + // order should not matter + pkg.SortRelationships(p.expectedRelationships) + pkg.SortRelationships(relationships) + if diff := cmp.Diff(p.expectedRelationships, relationships, opts...); diff != "" { t.Log("Specific Differences:\n" + r.String()) diff --git a/syft/pkg/relationships.go b/syft/pkg/relationships.go index 8e3628cab..204dab2d9 100644 --- a/syft/pkg/relationships.go +++ b/syft/pkg/relationships.go @@ -1,9 +1,47 @@ package pkg -import "github.com/anchore/syft/syft/artifact" +import ( + "sort" + + "github.com/anchore/syft/syft/artifact" +) func NewRelationships(catalog *Collection) []artifact.Relationship { rels := RelationshipsByFileOwnership(catalog) rels = append(rels, RelationshipsEvidentBy(catalog)...) return rels } + +// SortRelationships takes a set of package-to-package relationships and sorts them in a stable order by name and version. +// Note: this does not consider package-to-other, other-to-package, or other-to-other relationships. +// TODO: ideally this should be replaced with a more type-agnostic sort function that resides in the artifact package. +func SortRelationships(rels []artifact.Relationship) { + sort.SliceStable(rels, func(i, j int) bool { + return relationshipLess(rels[i], rels[j]) + }) +} + +func relationshipLess(i, j artifact.Relationship) bool { + iFrom, ok1 := i.From.(Package) + iTo, ok2 := i.To.(Package) + jFrom, ok3 := j.From.(Package) + jTo, ok4 := j.To.(Package) + + if !(ok1 && ok2 && ok3 && ok4) { + return false + } + + if iFrom.Name != jFrom.Name { + return iFrom.Name < jFrom.Name + } + if iFrom.Version != jFrom.Version { + return iFrom.Version < jFrom.Version + } + if iTo.Name != jTo.Name { + return iTo.Name < jTo.Name + } + if iTo.Version != jTo.Version { + return iTo.Version < jTo.Version + } + return i.Type < j.Type +} diff --git a/test/integration/catalog_packages_cases_test.go b/test/integration/catalog_packages_cases_test.go index c88b2d559..52bd6f287 100644 --- a/test/integration/catalog_packages_cases_test.go +++ b/test/integration/catalog_packages_cases_test.go @@ -247,6 +247,8 @@ var dirOnlyTestCases = []testCase{ "Serilog.Sinks.Console": "4.0.1", "System.Diagnostics.DiagnosticSource": "6.0.0", "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "TestCommon": "1.0.0", + "TestLibrary": "1.0.0", }, }, {