mirror of
https://github.com/anchore/syft.git
synced 2026-10-11 21:57:22 +02:00
Replace core SBOM-creation API with builder pattern (#1383)
* remove existing cataloging API Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add file cataloging config Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add package cataloging config Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add configs for cross-cutting concerns Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * rename CLI option configs to not require import aliases later Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update all nested structs for the Catalog struct Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update Catalog cli options - add new cataloger selection options (selection and default) - remove the excludeBinaryOverlapByOwnership - deprecate "catalogers" flag - add new javascript configuration Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * migrate relationship capabilities to separate internal package Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * refactor golang cataloger to use configuration options when creating packages Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * create internal object to facilitate reading from and writing to an SBOM Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * create a command-like object (task) to facilitate partial SBOM creation Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add cataloger selection capability - be able to parse string expressions into a set of resolved actions against sets - be able to use expressions to select/add/remove tasks to/from the final set of tasks to run Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add package, file, and environment related tasks Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update existing file catalogers to use nested UI elements Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add CreateSBOMConfig that drives the SBOM creation process Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * capture SBOM creation info as a struct Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add CreateSBOM() function Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix tests Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * update docs with SBOM selection help + breaking changes Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix multiple override default inputs Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix deprecation flag printing to stdout Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * refactor cataloger selection description to separate object Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * address review comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * keep expression errors and show specific suggestions only Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * address additional review feedback Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * address more review comments Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * addressed additional PR review feedback Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * fix file selection references Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * remove guess language data generation option Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add tests for coordinatesForSelection Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * rename relationship attributes Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add descriptions to relationships config fields Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * improve documentation around configuration options Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> * add explicit errors around legacy config entries Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com> --------- Signed-off-by: Alex Goodman <wagoodman@users.noreply.github.com>
This commit is contained in:
@@ -162,7 +162,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
name: "all-layers-scope-flag-by-env",
|
||||
args: []string{"scan", "-o", "json", hiddenPackagesImage},
|
||||
env: map[string]string{
|
||||
"SYFT_PACKAGE_CATALOGER_SCOPE": "all-layers",
|
||||
"SYFT_SCOPE": "all-layers",
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
assertPackageCount(163), // packages are now deduplicated for this case
|
||||
@@ -234,14 +234,50 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "catalogers-option",
|
||||
// This will detect enable python-package-cataloger, python-installed-package-cataloger and ruby-gemspec cataloger
|
||||
args: []string{"scan", "-o", "json", "--catalogers", "python,ruby-gemspec", coverageImage},
|
||||
name: "legacy-catalogers-option",
|
||||
// This will detect enable:
|
||||
// - python-installed-package-cataloger
|
||||
// - python-package-cataloger
|
||||
// - ruby-gemspec-cataloger
|
||||
// - ruby-installed-gemspec-cataloger
|
||||
args: []string{"packages", "-o", "json", "--catalogers", "python,gemspec", coverageImage},
|
||||
assertions: []traitAssertion{
|
||||
assertInOutput("Flag --catalogers has been deprecated, use: override-default-catalogers and select-catalogers"),
|
||||
assertPackageCount(13),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "select-catalogers-option",
|
||||
// This will detect enable:
|
||||
// - python-installed-package-cataloger
|
||||
// - ruby-installed-gemspec-cataloger
|
||||
args: []string{"scan", "-o", "json", "--select-catalogers", "python,gemspec", coverageImage},
|
||||
assertions: []traitAssertion{
|
||||
assertPackageCount(6),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "override-default-catalogers-option",
|
||||
// This will detect enable:
|
||||
// - python-installed-package-cataloger
|
||||
// - python-package-cataloger
|
||||
// - ruby-gemspec-cataloger
|
||||
// - ruby-installed-gemspec-cataloger
|
||||
args: []string{"packages", "-o", "json", "--override-default-catalogers", "python,gemspec", coverageImage},
|
||||
assertions: []traitAssertion{
|
||||
assertPackageCount(13),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "new and old cataloger options are mutually exclusive",
|
||||
args: []string{"packages", "-o", "json", "--override-default-catalogers", "python", "--catalogers", "gemspec", coverageImage},
|
||||
assertions: []traitAssertion{
|
||||
assertFailingReturnCode,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "override-default-parallelism",
|
||||
args: []string{"scan", "-vvv", "-o", "json", coverageImage},
|
||||
@@ -250,8 +286,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
},
|
||||
assertions: []traitAssertion{
|
||||
// the application config in the log matches that of what we expect to have been configured.
|
||||
assertInOutput("parallelism: 2"),
|
||||
assertInOutput("parallelism=2"),
|
||||
assertInOutput(`parallelism: 2`),
|
||||
assertPackageCount(coverageImageSquashedPackageCount),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
@@ -261,8 +296,7 @@ func TestPackagesCmdFlags(t *testing.T) {
|
||||
args: []string{"scan", "-vvv", "-o", "json", coverageImage},
|
||||
assertions: []traitAssertion{
|
||||
// the application config in the log matches that of what we expect to have been configured.
|
||||
assertInOutput("parallelism: 1"),
|
||||
assertInOutput("parallelism=1"),
|
||||
assertInOutput(`parallelism: 1`),
|
||||
assertPackageCount(coverageImageSquashedPackageCount),
|
||||
assertSuccessfulReturnCode,
|
||||
},
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
|
||||
func Test_AllLayersIncludesSquashed(t *testing.T) {
|
||||
// This is a verification test for issue #894 (https://github.com/anchore/syft/issues/894)
|
||||
allLayers, _ := catalogFixtureImage(t, "image-suse-all-layers", source.AllLayersScope, nil)
|
||||
squashed, _ := catalogFixtureImage(t, "image-suse-all-layers", source.SquashedScope, nil)
|
||||
allLayers, _ := catalogFixtureImage(t, "image-suse-all-layers", source.AllLayersScope)
|
||||
squashed, _ := catalogFixtureImage(t, "image-suse-all-layers", source.SquashedScope)
|
||||
|
||||
lenAllLayers := len(allLayers.Artifacts.Packages.Sorted())
|
||||
lenSquashed := len(squashed.Artifacts.Packages.Sorted())
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -9,53 +10,39 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/syft/linux"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/pkg"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func BenchmarkImagePackageCatalogers(b *testing.B) {
|
||||
// get the fixture image tar file
|
||||
fixtureImageName := "image-pkg-coverage"
|
||||
imagetest.GetFixtureImage(b, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(b, fixtureImageName)
|
||||
|
||||
var pc *pkg.Collection
|
||||
for _, c := range cataloger.ImageCatalogers(cataloger.DefaultConfig()) {
|
||||
// in case of future alteration where state is persisted, assume no dependency is safe to reuse
|
||||
userInput := "docker-archive:" + tarPath
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(b, err)
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
if err != nil {
|
||||
b.Fatalf("unable to get source: %+v", err)
|
||||
}
|
||||
b.Cleanup(func() {
|
||||
theSource.Close()
|
||||
})
|
||||
// get the source object for the image
|
||||
userInput := "docker-archive:" + tarPath
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(b, err)
|
||||
|
||||
resolver, err := theSource.FileResolver(source.SquashedScope)
|
||||
if err != nil {
|
||||
b.Fatalf("unable to get resolver: %+v", err)
|
||||
}
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
require.NoError(b, err)
|
||||
|
||||
theDistro := linux.IdentifyRelease(resolver)
|
||||
b.Cleanup(func() {
|
||||
theSource.Close()
|
||||
})
|
||||
|
||||
b.Run(c.Name(), func(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
pc, _, err = cataloger.Catalog(resolver, theDistro, 1, c)
|
||||
if err != nil {
|
||||
b.Fatalf("failure during benchmark: %+v", err)
|
||||
}
|
||||
}
|
||||
})
|
||||
// build the SBOM
|
||||
s, err := syft.CreateSBOM(context.Background(), theSource, syft.DefaultCreateSBOMConfig())
|
||||
|
||||
b.Logf("catalog for %q number of packages: %d", c.Name(), pc.PackageCount())
|
||||
}
|
||||
// did it work?
|
||||
require.NoError(b, err)
|
||||
require.NotNil(b, s)
|
||||
}
|
||||
|
||||
func TestPkgCoverageImage(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope)
|
||||
|
||||
observedLanguages := strset.New()
|
||||
definedLanguages := strset.New()
|
||||
@@ -250,29 +237,8 @@ func TestPkgCoverageDirectory(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestPkgCoverageCatalogerConfiguration(t *testing.T) {
|
||||
// Check that cataloger configuration can be used to run a cataloger on a source
|
||||
// for which that cataloger isn't enabled by defauly
|
||||
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, []string{"rust"})
|
||||
|
||||
observedLanguages := strset.New()
|
||||
definedLanguages := strset.New()
|
||||
definedLanguages.Add("rust")
|
||||
|
||||
for actualPkg := range sbom.Artifacts.Packages.Enumerate() {
|
||||
observedLanguages.Add(actualPkg.Language.String())
|
||||
}
|
||||
|
||||
assert.Equal(t, definedLanguages, observedLanguages)
|
||||
|
||||
// Verify that rust isn't actually an image cataloger
|
||||
c := cataloger.DefaultConfig()
|
||||
c.Catalogers = []string{"rust"}
|
||||
assert.Len(t, cataloger.ImageCatalogers(c), 0)
|
||||
}
|
||||
|
||||
func TestPkgCoverageImage_HasEvidence(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope)
|
||||
|
||||
var cases []testCase
|
||||
cases = append(cases, commonTestCases...)
|
||||
|
||||
@@ -59,7 +59,7 @@ func TestConvertCmd(t *testing.T) {
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
syftSbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope, nil)
|
||||
syftSbom, _ := catalogFixtureImage(t, "image-pkg-coverage", source.SquashedScope)
|
||||
syftFormat := syftjson.NewFormatEncoder()
|
||||
|
||||
syftFile, err := os.CreateTemp("", "test-convert-sbom-")
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
func TestDistroImage(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-distro-id", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-distro-id", source.SquashedScope)
|
||||
|
||||
expected := &linux.Release{
|
||||
PrettyName: "BusyBox v1.31.1",
|
||||
|
||||
@@ -15,7 +15,6 @@ import (
|
||||
"github.com/anchore/syft/syft/format/cyclonedxjson"
|
||||
"github.com/anchore/syft/syft/format/cyclonedxxml"
|
||||
"github.com/anchore/syft/syft/format/syftjson"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
@@ -32,12 +31,12 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
"image-owning-package",
|
||||
}
|
||||
tests := []struct {
|
||||
formatOption sbom.FormatID
|
||||
redactor func(in []byte) []byte
|
||||
json bool
|
||||
name string
|
||||
redactor func(in []byte) []byte
|
||||
json bool
|
||||
}{
|
||||
{
|
||||
formatOption: syftjson.ID,
|
||||
name: syftjson.ID.String(),
|
||||
redactor: func(in []byte) []byte {
|
||||
// no redactions necessary
|
||||
return in
|
||||
@@ -45,7 +44,7 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
json: true,
|
||||
},
|
||||
{
|
||||
formatOption: cyclonedxjson.ID,
|
||||
name: cyclonedxjson.ID.String(),
|
||||
redactor: func(in []byte) []byte {
|
||||
// unstable values
|
||||
in = regexp.MustCompile(`"(timestamp|serialNumber|bom-ref|ref)":\s*"(\n|[^"])+"`).ReplaceAll(in, []byte(`"$1": "redacted"`))
|
||||
@@ -55,7 +54,7 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
json: true,
|
||||
},
|
||||
{
|
||||
formatOption: cyclonedxxml.ID,
|
||||
name: cyclonedxxml.ID.String(),
|
||||
redactor: func(in []byte) []byte {
|
||||
// unstable values
|
||||
in = regexp.MustCompile(`(serialNumber|bom-ref|ref)="[^"]+"`).ReplaceAll(in, []byte{})
|
||||
@@ -75,11 +74,11 @@ func TestEncodeDecodeEncodeCycleComparison(t *testing.T) {
|
||||
decoders := format.NewDecoderCollection(format.Decoders()...)
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(string(test.formatOption), func(t *testing.T) {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
for _, image := range images {
|
||||
originalSBOM, _ := catalogFixtureImage(t, image, source.SquashedScope, nil)
|
||||
originalSBOM, _ := catalogFixtureImage(t, image, source.SquashedScope)
|
||||
|
||||
f := encoders.GetByString(string(test.formatOption))
|
||||
f := encoders.GetByString(test.name)
|
||||
require.NotNil(t, f)
|
||||
|
||||
var buff1 bytes.Buffer
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
"github.com/anchore/syft/syft/cataloging/filecataloging"
|
||||
"github.com/anchore/syft/syft/file"
|
||||
"github.com/anchore/syft/syft/file/cataloger/filecontent"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
)
|
||||
|
||||
func TestFileCataloging_Default(t *testing.T) {
|
||||
cfg := options.DefaultCatalog().ToSBOMConfig(clio.Identification{})
|
||||
cfg = cfg.WithFilesConfig(filecataloging.DefaultConfig())
|
||||
sbom, _ := catalogDirectoryWithConfig(t, "test-fixtures/files", cfg)
|
||||
|
||||
var metadata map[file.Coordinates]file.Metadata
|
||||
|
||||
var digests map[file.Coordinates][]file.Digest
|
||||
|
||||
var contents map[file.Coordinates]string
|
||||
|
||||
assertFileData(t, metadata, digests, contents, sbom)
|
||||
}
|
||||
|
||||
func TestFileCataloging_AllFiles(t *testing.T) {
|
||||
cfg := options.DefaultCatalog().ToSBOMConfig(clio.Identification{})
|
||||
cfg = cfg.WithFilesConfig(filecataloging.Config{
|
||||
Selection: file.AllFilesSelection,
|
||||
Hashers: []crypto.Hash{
|
||||
crypto.SHA256,
|
||||
},
|
||||
Content: filecontent.Config{
|
||||
// this is enough to potentially capture a/file, a-small-file, a-symlink-to-a-small-file, and a-symlink-to-file
|
||||
// but the size of a/file will cause it to be filtered, and the symlinks will not be included since
|
||||
// they are not regular files
|
||||
Globs: []string{"**/*file"},
|
||||
SkipFilesAboveSize: 30,
|
||||
},
|
||||
})
|
||||
sbom, _ := catalogDirectoryWithConfig(t, "test-fixtures/files", cfg)
|
||||
|
||||
pwd, err := os.Getwd()
|
||||
require.NoError(t, err)
|
||||
|
||||
testPath := func(path string) string {
|
||||
return filepath.Join(pwd, "test-fixtures/files", path)
|
||||
}
|
||||
|
||||
metadata := map[file.Coordinates]file.Metadata{
|
||||
{RealPath: ""}: {
|
||||
Path: testPath(""),
|
||||
Type: stereoscopeFile.TypeDirectory,
|
||||
},
|
||||
{RealPath: "/somewhere"}: {
|
||||
Path: testPath("/somewhere"),
|
||||
Type: stereoscopeFile.TypeDirectory,
|
||||
},
|
||||
{RealPath: "/somewhere/there"}: {
|
||||
Path: testPath("/somewhere/there"),
|
||||
Type: stereoscopeFile.TypeDirectory,
|
||||
},
|
||||
{RealPath: "/somewhere/there/is"}: {
|
||||
Path: testPath("/somewhere/there/is"),
|
||||
Type: stereoscopeFile.TypeDirectory,
|
||||
},
|
||||
{RealPath: "/somewhere/there/is/a"}: {
|
||||
Path: testPath("/somewhere/there/is/a"),
|
||||
Type: stereoscopeFile.TypeDirectory,
|
||||
},
|
||||
{RealPath: "/somewhere/there/is/a-small-file"}: {
|
||||
Path: testPath("/somewhere/there/is/a-small-file"),
|
||||
Type: stereoscopeFile.TypeRegular,
|
||||
MIMEType: "text/plain",
|
||||
},
|
||||
{RealPath: "/somewhere/there/is/a-symlink-to-a-small-file"}: {
|
||||
Path: testPath("/somewhere/there/is/a-symlink-to-a-small-file"),
|
||||
LinkDestination: testPath("/somewhere/there/is/a-small-file"),
|
||||
Type: stereoscopeFile.TypeSymLink,
|
||||
},
|
||||
{RealPath: "/somewhere/there/is/a-symlink-to-file"}: {
|
||||
Path: testPath("/somewhere/there/is/a-symlink-to-file"),
|
||||
LinkDestination: testPath("/somewhere/there/is/a/file"),
|
||||
Type: stereoscopeFile.TypeSymLink,
|
||||
},
|
||||
{RealPath: "/somewhere/there/is/a/file"}: {
|
||||
Path: testPath("/somewhere/there/is/a/file"),
|
||||
Type: stereoscopeFile.TypeRegular,
|
||||
MIMEType: "text/plain",
|
||||
},
|
||||
}
|
||||
|
||||
digests := map[file.Coordinates][]file.Digest{
|
||||
{RealPath: "/somewhere/there/is/a-small-file"}: {
|
||||
file.Digest{Algorithm: "sha256", Value: "672c23470e4ce99cf270bb63ae66ad2b8a80aa19090c40e59fbb1229a4ab661a"},
|
||||
},
|
||||
{RealPath: "/somewhere/there/is/a/file"}: {
|
||||
file.Digest{Algorithm: "sha256", Value: "00dac26d6d94353ac0d92bb9640cba76f82f5ca8707bb845ecdc574bd002348e"},
|
||||
},
|
||||
}
|
||||
|
||||
contents := map[file.Coordinates]string{
|
||||
{RealPath: "/somewhere/there/is/a-small-file"}: "c29tZSBjb250ZW50cyE=",
|
||||
}
|
||||
|
||||
assertFileData(t, metadata, digests, contents, sbom)
|
||||
|
||||
}
|
||||
|
||||
func assertFileData(t testing.TB, metadata map[file.Coordinates]file.Metadata, digests map[file.Coordinates][]file.Digest, contents map[file.Coordinates]string, sbom sbom.SBOM) {
|
||||
metadataCompareOpts := cmp.Options{
|
||||
cmp.Comparer(func(x, y file.Metadata) bool {
|
||||
if x.Path != y.Path {
|
||||
t.Logf("path mismatch: %s != %s", x.Path, y.Path)
|
||||
return false
|
||||
}
|
||||
|
||||
if x.Type != y.Type {
|
||||
t.Logf("type mismatch: %s != %s", x.Type, y.Type)
|
||||
return false
|
||||
}
|
||||
|
||||
if x.LinkDestination != y.LinkDestination {
|
||||
t.Logf("link destination mismatch: %s != %s", x.LinkDestination, y.LinkDestination)
|
||||
return false
|
||||
}
|
||||
|
||||
if x.MIMEType != y.MIMEType {
|
||||
t.Logf("mime type mismatch: %s != %s", x.MIMEType, y.MIMEType)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}),
|
||||
}
|
||||
|
||||
if d := cmp.Diff(metadata, sbom.Artifacts.FileMetadata, metadataCompareOpts...); d != "" {
|
||||
t.Errorf("unexpected metadata (-want +got):\n%s", d)
|
||||
}
|
||||
|
||||
assert.Equal(t, digests, sbom.Artifacts.FileDigests, "different digests detected")
|
||||
assert.Equal(t, contents, sbom.Artifacts.FileContents, "different contents detected")
|
||||
|
||||
}
|
||||
@@ -25,7 +25,7 @@ func TestGolangCompilerDetection(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, tt.image, source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, tt.image, source.SquashedScope)
|
||||
packages := sbom.Artifacts.Packages.PackagesByName("stdlib")
|
||||
|
||||
foundCompilerVersions := make(map[string]struct{})
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func TestJavaPURLs(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-test-java-purls", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-test-java-purls", source.SquashedScope)
|
||||
found := make(map[string]string)
|
||||
for _, p := range sbom.Artifacts.Packages.Sorted() {
|
||||
if p.Type != pkg.JavaPkg && p.Type != pkg.JenkinsPluginPkg {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestMarinerDistroless(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-mariner-distroless", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-mariner-distroless", source.SquashedScope)
|
||||
|
||||
expectedPkgs := 12
|
||||
actualPkgs := 0
|
||||
|
||||
@@ -63,7 +63,7 @@ func TestPackageDeduplication(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.scope), func(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-vertical-package-dups", tt.scope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-vertical-package-dups", tt.scope)
|
||||
for _, p := range sbom.Artifacts.Packages.Sorted() {
|
||||
if p.Type == pkg.BinaryPkg {
|
||||
assert.NotEmpty(t, p.Name)
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestPackageOwnershipRelationships(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, test.fixture, source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, test.fixture, source.SquashedScope)
|
||||
|
||||
output := bytes.NewBufferString("")
|
||||
err := syftjson.NewFormatEncoder().Encode(output, sbom)
|
||||
@@ -60,7 +60,7 @@ func TestPackageOwnershipExclusions(t *testing.T) {
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.fixture, func(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, test.fixture, source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, test.fixture, source.SquashedScope)
|
||||
binaryPackages := make([]pkg.Package, 0)
|
||||
apkPackages := make([]pkg.Package, 0)
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
|
||||
@@ -10,7 +10,7 @@ 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
|
||||
sbom, _ := catalogFixtureImage(t, "image-large-apk-data", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-large-apk-data", source.SquashedScope)
|
||||
|
||||
expectedPkgs := 58
|
||||
actualPkgs := 0
|
||||
|
||||
@@ -16,7 +16,7 @@ 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
|
||||
sbom, _ := catalogFixtureImage(t, "image-go-bin-arch-coverage", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-go-bin-arch-coverage", source.SquashedScope)
|
||||
|
||||
var actualELF, actualWIN, actualMACOS int
|
||||
|
||||
|
||||
@@ -7,5 +7,5 @@ import (
|
||||
)
|
||||
|
||||
func TestRegressionJavaNoMainPackage(t *testing.T) { // Regression: https://github.com/anchore/syft/issues/252
|
||||
catalogFixtureImage(t, "image-java-no-main-package", source.SquashedScope, nil)
|
||||
catalogFixtureImage(t, "image-java-no-main-package", source.SquashedScope)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func TestWarCatalogedCorrectlyIfRenamed(t *testing.T) {
|
||||
// install hudson-war@2.2.1 and renames the file to `/hudson.war`
|
||||
sbom, _ := catalogFixtureImage(t, "image-java-virtualpath-regression", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-java-virtualpath-regression", source.SquashedScope)
|
||||
|
||||
badPURL := "pkg:maven/hudson/hudson@2.2.1"
|
||||
goodPURL := "pkg:maven/org.jvnet.hudson.main/hudson-war@2.2.1"
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestPhotonPackageRegression(t *testing.T) { // Regression: https://github.com/anchore/syft/pull/1997
|
||||
sbom, _ := catalogFixtureImage(t, "image-photon-all-layers", source.AllLayersScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-photon-all-layers", source.AllLayersScope)
|
||||
var packages []pkg.Package
|
||||
for p := range sbom.Artifacts.Packages.Enumerate() {
|
||||
packages = append(packages, p)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
)
|
||||
|
||||
func TestRustAudit(t *testing.T) {
|
||||
sbom, _ := catalogFixtureImage(t, "image-rust-auditable", source.SquashedScope, []string{"all"})
|
||||
sbom, _ := catalogFixtureImage(t, "image-rust-auditable", source.SquashedScope)
|
||||
|
||||
expectedPkgs := 2
|
||||
actualPkgs := 0
|
||||
|
||||
@@ -11,16 +11,16 @@ func TestSbomCataloger(t *testing.T) {
|
||||
// The image contains a go.mod file with 2 dependencies and an spdx json sbom.
|
||||
// The go.mod file contains 2 dependencies, and the sbom includes a go dependency
|
||||
// that overlaps with the go.mod
|
||||
sbom, _ := catalogFixtureImage(t, "image-sbom-cataloger", source.SquashedScope, []string{"all"})
|
||||
sbom, _ := catalogFixtureImage(t, "image-sbom-cataloger", source.SquashedScope, "+go-module-file-cataloger")
|
||||
|
||||
expectedSbomCatalogerPkgs := 1
|
||||
expectedGoModCatalogerPkgs := 2
|
||||
actualSbomPkgs := 0
|
||||
actualGoModPkgs := 0
|
||||
for pkg := range sbom.Artifacts.Packages.Enumerate(pkg.GoModulePkg) {
|
||||
if pkg.FoundBy == "go-module-file-cataloger" {
|
||||
for p := range sbom.Artifacts.Packages.Enumerate(pkg.GoModulePkg) {
|
||||
if p.FoundBy == "go-module-file-cataloger" {
|
||||
actualGoModPkgs += 1
|
||||
} else if pkg.FoundBy == "sbom-cataloger" {
|
||||
} else if p.FoundBy == "sbom-cataloger" {
|
||||
actualSbomPkgs += 1
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
func TestSqliteRpm(t *testing.T) {
|
||||
// This is a regression test for issue #469 (https://github.com/anchore/syft/issues/469). Recent RPM
|
||||
// based distribution store package data in an sqlite database
|
||||
sbom, _ := catalogFixtureImage(t, "image-sqlite-rpmdb", source.SquashedScope, nil)
|
||||
sbom, _ := catalogFixtureImage(t, "image-sqlite-rpmdb", source.SquashedScope)
|
||||
|
||||
expectedPkgs := 139
|
||||
actualPkgs := 0
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
some contents!
|
||||
+1
@@ -0,0 +1 @@
|
||||
./a-small-file
|
||||
@@ -0,0 +1 @@
|
||||
./a/file
|
||||
@@ -0,0 +1,10 @@
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
with a lot of contents!
|
||||
@@ -1,81 +1,91 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/anchore/clio"
|
||||
"github.com/anchore/stereoscope/pkg/imagetest"
|
||||
"github.com/anchore/syft/cmd/syft/cli/options"
|
||||
"github.com/anchore/syft/syft"
|
||||
"github.com/anchore/syft/syft/pkg/cataloger"
|
||||
"github.com/anchore/syft/syft/cataloging/pkgcataloging"
|
||||
"github.com/anchore/syft/syft/sbom"
|
||||
"github.com/anchore/syft/syft/source"
|
||||
)
|
||||
|
||||
func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Scope, catalogerCfg []string) (sbom.SBOM, source.Source) {
|
||||
func catalogFixtureImage(t *testing.T, fixtureImageName string, scope source.Scope, catalogerSelection ...string) (sbom.SBOM, source.Source) {
|
||||
cfg := options.DefaultCatalog().ToSBOMConfig(clio.Identification{
|
||||
Name: "syft-tester",
|
||||
Version: "v0.99.0",
|
||||
}).WithCatalogerSelection(
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
WithExpression(catalogerSelection...),
|
||||
)
|
||||
cfg.Search.Scope = scope
|
||||
|
||||
return catalogFixtureImageWithConfig(t, fixtureImageName, cfg)
|
||||
}
|
||||
|
||||
func catalogFixtureImageWithConfig(t *testing.T, fixtureImageName string, cfg *syft.CreateSBOMConfig) (sbom.SBOM, source.Source) {
|
||||
cfg.CatalogerSelection = cfg.CatalogerSelection.WithDefaults(pkgcataloging.ImageTag)
|
||||
|
||||
// get the fixture image tar file
|
||||
imagetest.GetFixtureImage(t, "docker-archive", fixtureImageName)
|
||||
tarPath := imagetest.GetFixtureImageTarPath(t, fixtureImageName)
|
||||
userInput := "docker-archive:" + tarPath
|
||||
|
||||
// get the source to build an SBOM against
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
theSource.Close()
|
||||
})
|
||||
|
||||
c := cataloger.DefaultConfig()
|
||||
c.Catalogers = catalogerCfg
|
||||
s, err := syft.CreateSBOM(context.Background(), theSource, cfg)
|
||||
|
||||
c.Search.Scope = scope
|
||||
pkgCatalog, relationships, actualDistro, err := syft.CatalogPackages(theSource, c)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog image: %+v", err)
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s)
|
||||
|
||||
return sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkgCatalog,
|
||||
LinuxDistribution: actualDistro,
|
||||
},
|
||||
Relationships: relationships,
|
||||
Source: theSource.Describe(),
|
||||
Descriptor: sbom.Descriptor{
|
||||
Name: "syft",
|
||||
Version: "v0.42.0-bogus",
|
||||
// the application configuration should be persisted here, however, we do not want to import
|
||||
// the application configuration in this package (it's reserved only for ingestion by the cmd package)
|
||||
Configuration: map[string]string{
|
||||
"config-key": "config-value",
|
||||
},
|
||||
},
|
||||
}, theSource
|
||||
return *s, theSource
|
||||
}
|
||||
|
||||
func catalogDirectory(t *testing.T, dir string) (sbom.SBOM, source.Source) {
|
||||
func catalogDirectory(t *testing.T, dir string, catalogerSelection ...string) (sbom.SBOM, source.Source) {
|
||||
cfg := options.DefaultCatalog().ToSBOMConfig(clio.Identification{
|
||||
Name: "syft-tester",
|
||||
Version: "v0.99.0",
|
||||
}).WithCatalogerSelection(
|
||||
pkgcataloging.NewSelectionRequest().
|
||||
WithExpression(catalogerSelection...),
|
||||
)
|
||||
|
||||
return catalogDirectoryWithConfig(t, dir, cfg)
|
||||
}
|
||||
|
||||
func catalogDirectoryWithConfig(t *testing.T, dir string, cfg *syft.CreateSBOMConfig) (sbom.SBOM, source.Source) {
|
||||
cfg.CatalogerSelection = cfg.CatalogerSelection.WithDefaults(pkgcataloging.DirectoryTag)
|
||||
|
||||
// get the source to build an sbom against
|
||||
userInput := "dir:" + dir
|
||||
detection, err := source.Detect(userInput, source.DefaultDetectConfig())
|
||||
require.NoError(t, err)
|
||||
|
||||
theSource, err := detection.NewSource(source.DefaultDetectionSourceConfig())
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
theSource.Close()
|
||||
})
|
||||
|
||||
// TODO: this would be better with functional options (after/during API refactor)
|
||||
c := cataloger.DefaultConfig()
|
||||
c.Search.Scope = source.AllLayersScope
|
||||
pkgCatalog, relationships, actualDistro, err := syft.CatalogPackages(theSource, c)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to catalog image: %+v", err)
|
||||
}
|
||||
// build the SBOM
|
||||
s, err := syft.CreateSBOM(context.Background(), theSource, cfg)
|
||||
|
||||
return sbom.SBOM{
|
||||
Artifacts: sbom.Artifacts{
|
||||
Packages: pkgCatalog,
|
||||
LinuxDistribution: actualDistro,
|
||||
},
|
||||
Relationships: relationships,
|
||||
Source: theSource.Describe(),
|
||||
}, theSource
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, s)
|
||||
|
||||
return *s, theSource
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user