Introduce relationships as first-class objects (#607)

* migrate pkg.ID and pkg.Relationship to artifact package

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* return relationships from tasks

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix more tests

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add artifact.Identifiable by Identity() method

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* fix linting

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* remove catalog ID assignment

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* adjust spdx helpers to use copy of packages

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* stabilize package ID relative to encode-decode format cycles

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* rename Identity() to ID()

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* use zero value for nils in ID generation

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* enable source.Location to be identifiable

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* hoist up package relationship discovery to analysis stage

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* update ownership-by-file-overlap relationship description

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* add test reminders to put new relationships under test

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>

* adjust PHP composer.lock parser function to return relationships

Signed-off-by: Alex Goodman <alex.goodman@anchore.com>
This commit is contained in:
Alex Goodman
2021-11-16 14:14:13 -05:00
committed by GitHub
parent 45ea4177e8
commit ef627d82ef
100 changed files with 663 additions and 633 deletions
@@ -2,7 +2,7 @@ package spdxhelpers
import "github.com/anchore/syft/syft/pkg"
func Description(p *pkg.Package) string {
func Description(p pkg.Package) string {
if hasMetadata(p) {
switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata:
@@ -14,10 +14,6 @@ func Description(p *pkg.Package) string {
return ""
}
func packageExists(p *pkg.Package) bool {
return p != nil
}
func hasMetadata(p *pkg.Package) bool {
return packageExists(p) && p.Metadata != nil
func hasMetadata(p pkg.Package) bool {
return p.Metadata != nil
}
@@ -50,7 +50,7 @@ func Test_Description(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, Description(&test.input))
assert.Equal(t, test.expected, Description(test.input))
})
}
}
@@ -2,7 +2,7 @@ package spdxhelpers
import "github.com/anchore/syft/syft/pkg"
func DownloadLocation(p *pkg.Package) string {
func DownloadLocation(p pkg.Package) string {
// 3.7: Package Download Location
// Cardinality: mandatory, one
// NONE if there is no download location whatsoever.
@@ -48,7 +48,7 @@ func Test_DownloadLocation(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, DownloadLocation(&test.input))
assert.Equal(t, test.expected, DownloadLocation(test.input))
})
}
}
@@ -6,13 +6,9 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func ExternalRefs(p *pkg.Package) (externalRefs []model.ExternalRef) {
func ExternalRefs(p pkg.Package) (externalRefs []model.ExternalRef) {
externalRefs = make([]model.ExternalRef, 0)
if !packageExists(p) {
return externalRefs
}
for _, c := range p.CPEs {
externalRefs = append(externalRefs, model.ExternalRef{
ReferenceCategory: model.SecurityReferenceCategory,
@@ -39,7 +39,7 @@ func Test_ExternalRefs(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.ElementsMatch(t, test.expected, ExternalRefs(&test.input))
assert.ElementsMatch(t, test.expected, ExternalRefs(test.input))
})
}
}
+1 -1
View File
@@ -9,7 +9,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func Files(packageSpdxID string, p *pkg.Package) (files []model.File, fileIDs []string, relationships []model.Relationship) {
func Files(packageSpdxID string, p pkg.Package) (files []model.File, fileIDs []string, relationships []model.Relationship) {
files = make([]model.File, 0)
fileIDs = make([]string, 0)
relationships = make([]model.Relationship, 0)
@@ -2,7 +2,7 @@ package spdxhelpers
import "github.com/anchore/syft/syft/pkg"
func Homepage(p *pkg.Package) string {
func Homepage(p pkg.Package) string {
if hasMetadata(p) {
switch metadata := p.Metadata.(type) {
case pkg.GemMetadata:
@@ -50,7 +50,7 @@ func Test_Homepage(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, Homepage(&test.input))
assert.Equal(t, test.expected, Homepage(test.input))
})
}
}
@@ -7,7 +7,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func License(p *pkg.Package) string {
func License(p pkg.Package) string {
// source: https://spdx.github.io/spdx-spec/3-package-information/#313-concluded-license
// The options to populate this field are limited to:
// A valid SPDX License Expression as defined in Appendix IV;
@@ -17,7 +17,7 @@ func License(p *pkg.Package) string {
// (ii) the SPDX file creator has made no attempt to determine this field; or
// (iii) the SPDX file creator has intentionally provided no information (no meaning should be implied by doing so).
if !packageExists(p) || len(p.Licenses) == 0 {
if len(p.Licenses) == 0 {
return "NONE"
}
@@ -67,7 +67,7 @@ func Test_License(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, License(&test.input))
assert.Equal(t, test.expected, License(test.input))
})
}
}
@@ -108,7 +108,7 @@ func Test_Originator(t *testing.T) {
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
assert.Equal(t, test.expected, Originator(&test.input))
assert.Equal(t, test.expected, Originator(test.input))
})
}
}
@@ -6,7 +6,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func Originator(p *pkg.Package) string {
func Originator(p pkg.Package) string {
if hasMetadata(p) {
switch metadata := p.Metadata.(type) {
case pkg.ApkMetadata:
@@ -6,11 +6,7 @@ import (
"github.com/anchore/syft/syft/pkg"
)
func SourceInfo(p *pkg.Package) string {
if !packageExists(p) {
return ""
}
func SourceInfo(p pkg.Package) string {
answer := ""
switch p.Type {
case pkg.RpmPkg:
@@ -139,7 +139,7 @@ func Test_SourceInfo(t *testing.T) {
if test.input.Type != "" {
pkgTypes = append(pkgTypes, test.input.Type)
}
actual := SourceInfo(&test.input)
actual := SourceInfo(test.input)
for _, expected := range test.expected {
assert.Contains(t, actual, expected)
}
@@ -133,7 +133,6 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
// populate catalog with test data
catalog.Add(pkg.Package{
ID: "package-1-id",
Name: "package-1",
Version: "1.0.1",
Locations: []source.Location{
@@ -154,7 +153,6 @@ func populateImageCatalog(catalog *pkg.Catalog, img *image.Image) {
},
})
catalog.Add(pkg.Package{
ID: "package-2-id",
Name: "package-2",
Version: "2.0.1",
Locations: []source.Location{
@@ -197,7 +195,6 @@ func newDirectoryCatalog() *pkg.Catalog {
// populate catalog with test data
catalog.Add(pkg.Package{
ID: "package-1-id",
Name: "package-1",
Version: "1.0.1",
Type: pkg.PythonPkg,
@@ -223,7 +220,6 @@ func newDirectoryCatalog() *pkg.Catalog {
},
})
catalog.Add(pkg.Package{
ID: "package-2-id",
Name: "package-2",
Version: "2.0.1",
Type: pkg.DebPkg,
@@ -33,7 +33,7 @@ func toFormatModel(s sbom.SBOM) model.Document {
return doc
}
func toComponent(p *pkg.Package) model.Component {
func toComponent(p pkg.Package) model.Component {
return model.Component{
Type: "library", // TODO: this is not accurate
Name: p.Name,
@@ -256,7 +256,7 @@ func toFormatPackages(catalog *pkg.Catalog) map[spdx.ElementID]*spdx.Package2_2
return results
}
func formatSPDXExternalRefs(p *pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
func formatSPDXExternalRefs(p pkg.Package) (refs []*spdx.PackageExternalReference2_2) {
for _, ref := range spdxhelpers.ExternalRefs(p) {
refs = append(refs, &spdx.PackageExternalReference2_2{
Category: string(ref.ReferenceCategory),
+1 -5
View File
@@ -31,11 +31,7 @@ func TestEncodeDecodeCycle(t *testing.T) {
continue
}
// ids will never be equal
p.ID = ""
actualPackages[idx].ID = ""
for _, d := range deep.Equal(*p, *actualPackages[idx]) {
for _, d := range deep.Equal(p, actualPackages[idx]) {
if strings.Contains(d, ".VirtualPath: ") {
// location.Virtual path is not exposed in the json output
continue
@@ -1,7 +1,7 @@
{
"artifacts": [
{
"id": "package-1-id",
"id": "cbf4f3077fc7deee",
"name": "package-1",
"version": "1.0.1",
"type": "python",
@@ -36,7 +36,7 @@
}
},
{
"id": "package-2-id",
"id": "1a39aadd9705c2b9",
"name": "package-2",
"version": "2.0.1",
"type": "deb",
@@ -1,7 +1,7 @@
{
"artifacts": [
{
"id": "package-1-id",
"id": "d1d433485a31ed07",
"name": "package-1",
"version": "1.0.1",
"type": "python",
@@ -32,7 +32,7 @@
}
},
{
"id": "package-2-id",
"id": "2db629ca48fa6786",
"name": "package-2",
"version": "2.0.1",
"type": "deb",
+9 -7
View File
@@ -3,6 +3,8 @@ package syftjson
import (
"fmt"
"github.com/anchore/syft/syft/artifact"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/syft/internal"
@@ -23,7 +25,7 @@ func ToFormatModel(s sbom.SBOM, applicationConfig interface{}) model.Document {
return model.Document{
Artifacts: toPackageModels(s.Artifacts.PackageCatalog),
ArtifactRelationships: toRelationshipModel(pkg.NewRelationships(s.Artifacts.PackageCatalog)),
ArtifactRelationships: toRelationshipModel(s.Relationships),
Source: src,
Distro: toDistroModel(s.Artifacts.Distro),
Descriptor: model.Descriptor{
@@ -50,7 +52,7 @@ func toPackageModels(catalog *pkg.Catalog) []model.Package {
}
// toPackageModel crates a new Package from the given pkg.Package.
func toPackageModel(p *pkg.Package) model.Package {
func toPackageModel(p pkg.Package) model.Package {
var cpes = make([]string, len(p.CPEs))
for i, c := range p.CPEs {
cpes[i] = c.BindToFmtString()
@@ -69,7 +71,7 @@ func toPackageModel(p *pkg.Package) model.Package {
return model.Package{
PackageBasicData: model.PackageBasicData{
ID: string(p.ID),
ID: string(p.ID()),
Name: p.Name,
Version: p.Version,
Type: p.Type,
@@ -87,14 +89,14 @@ func toPackageModel(p *pkg.Package) model.Package {
}
}
func toRelationshipModel(relationships []pkg.Relationship) []model.Relationship {
func toRelationshipModel(relationships []artifact.Relationship) []model.Relationship {
result := make([]model.Relationship, len(relationships))
for i, r := range relationships {
result[i] = model.Relationship{
Parent: string(r.Parent),
Child: string(r.Child),
Parent: string(r.From.ID()),
Child: string(r.To.ID()),
Type: string(r.Type),
Metadata: r.Metadata,
Metadata: r.Data,
}
}
return result
@@ -61,7 +61,6 @@ func toSyftPackage(p model.Package) pkg.Package {
}
return pkg.Package{
ID: pkg.ID(p.ID),
Name: p.Name,
Version: p.Version,
FoundBy: p.FoundBy,
@@ -33,7 +33,6 @@ func TestJSONPresenter(t *testing.T) {
catalog := pkg.NewCatalog()
catalog.Add(pkg.Package{
ID: "package-1-id",
Name: "package-1",
Version: "1.0.1",
Locations: []source.Location{
@@ -57,7 +56,6 @@ func TestJSONPresenter(t *testing.T) {
},
})
catalog.Add(pkg.Package{
ID: "package-2-id",
Name: "package-2",
Version: "2.0.1",
Locations: []source.Location{
@@ -72,7 +72,7 @@
],
"artifacts": [
{
"id": "package-1-id",
"id": "b84dfe0eb2c5670f",
"name": "package-1",
"version": "1.0.1",
"type": "python",
@@ -102,7 +102,7 @@
}
},
{
"id": "package-2-id",
"id": "6619226d6979963f",
"name": "package-2",
"version": "2.0.1",
"type": "deb",