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
+11 -13
View File
@@ -2,13 +2,11 @@ package cli
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"testing"
"github.com/acarl005/stripansi"
"github.com/anchore/syft/syft/source"
)
type traitAssertion func(tb testing.TB, stdout, stderr string, rc int)
@@ -29,17 +27,17 @@ func assertTableReport(tb testing.TB, stdout, _ string, _ int) {
}
}
func assertScope(scope source.Scope) traitAssertion {
return func(tb testing.TB, stdout, stderr string, rc int) {
tb.Helper()
// we can only verify source with the json report
assertJsonReport(tb, stdout, stderr, rc)
if !strings.Contains(stdout, fmt.Sprintf(`"scope": "%s"`, scope.String())) {
tb.Errorf("JSON report did not indicate the %q scope", scope)
}
}
}
//func assertScope(scope source.Scope) traitAssertion {
// return func(tb testing.TB, stdout, stderr string, rc int) {
// tb.Helper()
// // we can only verify source with the json report
// assertJsonReport(tb, stdout, stderr, rc)
//
// if !strings.Contains(stdout, fmt.Sprintf(`"scope": "%s"`, scope.String())) {
// tb.Errorf("JSON report did not indicate the %q scope", scope)
// }
// }
//}
func assertLoggingLevel(level string) traitAssertion {
// match examples:
+7 -7
View File
@@ -37,7 +37,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
b.Run(c.Name(), func(b *testing.B) {
for i := 0; i < b.N; i++ {
pc, err = cataloger.Catalog(resolver, theDistro, c)
pc, _, err = cataloger.Catalog(resolver, theDistro, c)
if err != nil {
b.Fatalf("failure during benchmark: %+v", err)
}
@@ -49,7 +49,7 @@ func BenchmarkImagePackageCatalogers(b *testing.B) {
}
func TestPkgCoverageImage(t *testing.T) {
catalog, _, _ := catalogFixtureImage(t, "image-pkg-coverage")
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage")
observedLanguages := internal.NewStringSet()
definedLanguages := internal.NewStringSet()
@@ -82,7 +82,7 @@ func TestPkgCoverageImage(t *testing.T) {
t.Run(c.name, func(t *testing.T) {
pkgCount := 0
for a := range catalog.Enumerate(c.pkgType) {
for a := range sbom.Artifacts.PackageCatalog.Enumerate(c.pkgType) {
if a.Language.String() != "" {
observedLanguages.Add(a.Language.String())
@@ -110,7 +110,7 @@ func TestPkgCoverageImage(t *testing.T) {
if pkgCount != len(c.pkgInfo)+c.duplicates {
t.Logf("Discovered packages of type %+v", c.pkgType)
for a := range catalog.Enumerate(c.pkgType) {
for a := range sbom.Artifacts.PackageCatalog.Enumerate(c.pkgType) {
t.Log(" ", a)
}
t.Fatalf("unexpected package count: %d!=%d", pkgCount, len(c.pkgInfo))
@@ -137,7 +137,7 @@ func TestPkgCoverageImage(t *testing.T) {
}
func TestPkgCoverageDirectory(t *testing.T) {
catalog, _, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
sbom, _ := catalogDirectory(t, "test-fixtures/image-pkg-coverage")
observedLanguages := internal.NewStringSet()
definedLanguages := internal.NewStringSet()
@@ -159,7 +159,7 @@ func TestPkgCoverageDirectory(t *testing.T) {
t.Run(test.name, func(t *testing.T) {
actualPkgCount := 0
for actualPkg := range catalog.Enumerate(test.pkgType) {
for actualPkg := range sbom.Artifacts.PackageCatalog.Enumerate(test.pkgType) {
observedLanguages.Add(actualPkg.Language.String())
observedPkgs.Add(string(actualPkg.Type))
@@ -184,7 +184,7 @@ func TestPkgCoverageDirectory(t *testing.T) {
}
if actualPkgCount != len(test.pkgInfo)+test.duplicates {
for actualPkg := range catalog.Enumerate(test.pkgType) {
for actualPkg := range sbom.Artifacts.PackageCatalog.Enumerate(test.pkgType) {
t.Log(" ", actualPkg)
}
t.Fatalf("unexpected package count: %d!=%d", actualPkgCount, len(test.pkgInfo))
+2 -2
View File
@@ -8,14 +8,14 @@ import (
)
func TestDistroImage(t *testing.T) {
_, actualDistro, _ := catalogFixtureImage(t, "image-distro-id")
sbom, _ := catalogFixtureImage(t, "image-distro-id")
expected, err := distro.NewDistro(distro.Busybox, "1.31.1", "")
if err != nil {
t.Fatalf("could not create distro: %+v", err)
}
for _, d := range deep.Equal(actualDistro, &expected) {
for _, d := range deep.Equal(sbom.Artifacts.Distro, &expected) {
t.Errorf("found distro difference: %+v", d)
}
@@ -0,0 +1,51 @@
package integration
import (
"bytes"
"testing"
"github.com/anchore/syft/syft"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/anchore/syft/syft/format"
"github.com/stretchr/testify/assert"
)
// TestEncodeDecodeEncodeCycleComparison is testing for differences in how SBOM documents get encoded on multiple cycles.
// By encding and decoding the sbom we can compare the differences between the set of resulting objects. However,
// this requires specific comparisons being done, and select redactions/omissions being made. Additionally, there are
// already unit tests on each format encoder-decoder for properly functioning comparisons in depth, so there is no need
// to do an object-to-object comparison. For this reason this test focuses on a bytes-to-bytes comparison after an
// encode-decode-encode loop which will detect lossy behavior in both directions.
func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
tests := []struct {
format format.Option
}{
{
format: format.JSONOption,
},
}
for _, test := range tests {
t.Run(string(test.format), func(t *testing.T) {
originalSBOM, _ := catalogFixtureImage(t, "image-pkg-coverage")
by1, err := syft.Encode(originalSBOM, test.format)
assert.NoError(t, err)
newSBOM, newFormat, err := syft.Decode(bytes.NewReader(by1))
assert.NoError(t, err)
assert.Equal(t, test.format, newFormat)
by2, err := syft.Encode(*newSBOM, test.format)
assert.NoError(t, err)
if !assert.True(t, bytes.Equal(by1, by2)) {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(string(by1), string(by2), true)
t.Errorf("diff: %s", dmp.DiffPrettyText(diffs))
}
})
}
}
+4 -4
View File
@@ -9,11 +9,11 @@ import (
)
func TestNpmPackageLockDirectory(t *testing.T) {
catalog, _, _ := catalogDirectory(t, "test-fixtures/npm-lock")
sbom, _ := catalogDirectory(t, "test-fixtures/npm-lock")
foundPackages := internal.NewStringSet()
for actualPkg := range catalog.Enumerate(pkg.NpmPkg) {
for actualPkg := range sbom.Artifacts.PackageCatalog.Enumerate(pkg.NpmPkg) {
for _, actualLocation := range actualPkg.Locations {
if strings.Contains(actualLocation.RealPath, "node_modules") {
t.Errorf("found packages from package-lock.json in node_modules: %s", actualLocation)
@@ -30,11 +30,11 @@ func TestNpmPackageLockDirectory(t *testing.T) {
}
func TestYarnPackageLockDirectory(t *testing.T) {
catalog, _, _ := catalogDirectory(t, "test-fixtures/yarn-lock")
sbom, _ := catalogDirectory(t, "test-fixtures/yarn-lock")
foundPackages := internal.NewStringSet()
for actualPkg := range catalog.Enumerate(pkg.NpmPkg) {
for actualPkg := range sbom.Artifacts.PackageCatalog.Enumerate(pkg.NpmPkg) {
for _, actualLocation := range actualPkg.Locations {
if strings.Contains(actualLocation.RealPath, "node_modules") {
t.Errorf("found packages from yarn.lock in node_modules: %s", actualLocation)
@@ -7,7 +7,6 @@ import (
"github.com/anchore/syft/internal/formats/syftjson"
syftjsonModel "github.com/anchore/syft/internal/formats/syftjson/model"
"github.com/anchore/syft/syft/sbom"
)
func TestPackageOwnershipRelationships(t *testing.T) {
@@ -23,15 +22,9 @@ func TestPackageOwnershipRelationships(t *testing.T) {
for _, test := range tests {
t.Run(test.fixture, func(t *testing.T) {
catalog, d, src := catalogFixtureImage(t, test.fixture)
sbom, _ := catalogFixtureImage(t, test.fixture)
p := syftjson.Format().Presenter(sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: catalog,
Distro: d,
},
Source: src.Metadata,
})
p := syftjson.Format().Presenter(sbom)
if p == nil {
t.Fatal("unable to get presenter")
}
@@ -9,11 +9,11 @@ import (
func TestRegression212ApkBufferSize(t *testing.T) {
// This is a regression test for issue #212 (https://github.com/anchore/syft/issues/212) in which the apk db could
// not be processed due to a scanner buffer that was too small
catalog, _, _ := catalogFixtureImage(t, "image-large-apk-data")
sbom, _ := catalogFixtureImage(t, "image-large-apk-data")
expectedPkgs := 58
actualPkgs := 0
for range catalog.Enumerate(pkg.ApkPkg) {
for range sbom.Artifacts.PackageCatalog.Enumerate(pkg.ApkPkg) {
actualPkgs += 1
}
@@ -15,11 +15,11 @@ func TestRegressionGoArchDiscovery(t *testing.T) {
)
// This is a regression test to make sure the way we detect go binary packages
// stays consistent and reproducible as the tool chain evolves
catalog, _, _ := catalogFixtureImage(t, "image-go-bin-arch-coverage")
sbom, _ := catalogFixtureImage(t, "image-go-bin-arch-coverage")
var actualELF, actualWIN, actualMACOS int
for p := range catalog.Enumerate(pkg.GoModulePkg) {
for p := range sbom.Artifacts.PackageCatalog.Enumerate(pkg.GoModulePkg) {
for _, l := range p.Locations {
switch {
case strings.Contains(l.RealPath, "elf"):
+22 -8
View File
@@ -3,14 +3,14 @@ package integration
import (
"testing"
"github.com/anchore/syft/syft/sbom"
"github.com/anchore/stereoscope/pkg/imagetest"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/source"
)
func catalogFixtureImage(t *testing.T, fixtureImageName string) (*pkg.Catalog, *distro.Distro, *source.Source) {
func catalogFixtureImage(t *testing.T, fixtureImageName string) (sbom.SBOM, *source.Source) {
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
@@ -20,25 +20,39 @@ func catalogFixtureImage(t *testing.T, fixtureImageName string) (*pkg.Catalog, *
t.Fatalf("unable to get source: %+v", err)
}
pkgCatalog, actualDistro, err := syft.CatalogPackages(theSource, source.SquashedScope)
pkgCatalog, relationships, actualDistro, err := syft.CatalogPackages(theSource, source.SquashedScope)
if err != nil {
t.Fatalf("failed to catalog image: %+v", err)
}
return pkgCatalog, actualDistro, theSource
return sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: pkgCatalog,
Distro: actualDistro,
},
Relationships: relationships,
Source: theSource.Metadata,
}, theSource
}
func catalogDirectory(t *testing.T, dir string) (*pkg.Catalog, *distro.Distro, *source.Source) {
func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, *source.Source) {
theSource, cleanupSource, err := source.New("dir:"+dir, nil)
t.Cleanup(cleanupSource)
if err != nil {
t.Fatalf("unable to get source: %+v", err)
}
pkgCatalog, actualDistro, err := syft.CatalogPackages(theSource, source.AllLayersScope)
pkgCatalog, relationships, actualDistro, err := syft.CatalogPackages(theSource, source.AllLayersScope)
if err != nil {
t.Fatalf("failed to catalog image: %+v", err)
}
return pkgCatalog, actualDistro, theSource
return sbom.SBOM{
Artifacts: sbom.Artifacts{
PackageCatalog: pkgCatalog,
Distro: actualDistro,
},
Relationships: relationships,
Source: theSource.Metadata,
}, theSource
}