mirror of
https://github.com/anchore/syft.git
synced 2026-10-30 14:02:26 +01:00
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:
@@ -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:
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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"):
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user