From f1b6f38ea874eeaf474419ef9421e5eca4dea196 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 19 May 2023 09:01:05 -0400 Subject: [PATCH 01/21] chore(deps): bump github.com/sirupsen/logrus from 1.9.1 to 1.9.2 (#1827) Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.1 to 1.9.2. - [Release notes](https://github.com/sirupsen/logrus/releases) - [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md) - [Commits](https://github.com/sirupsen/logrus/compare/v1.9.1...v1.9.2) --- updated-dependencies: - dependency-name: github.com/sirupsen/logrus dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b9844c8b2ef..a9de7418e2d 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( // pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5 github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e github.com/sergi/go-diff v1.3.1 - github.com/sirupsen/logrus v1.9.1 + github.com/sirupsen/logrus v1.9.2 github.com/spdx/tools-golang v0.5.0 github.com/spf13/afero v1.9.5 github.com/spf13/cobra v1.7.0 diff --git a/go.sum b/go.sum index 51d209ad60c..b98efad1b00 100644 --- a/go.sum +++ b/go.sum @@ -557,8 +557,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.1 h1:Ou41VVR3nMWWmTiEUnj0OlsgOSCUFgsPAOl6jRIcVtQ= -github.com/sirupsen/logrus v1.9.1/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= +github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= From 334a775cb9cd6bf50033de1bb3aa04f46b669f5d Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Fri, 19 May 2023 10:21:10 -0400 Subject: [PATCH 02/21] Keep original FileInfo persisted on file.Metadata structs (#1794) * pull in fileinfo changes from stereoscope #172 Signed-off-by: Alex Goodman * fix CLI test assumption about the docker daemon Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman Signed-off-by: <> --- go.mod | 2 +- go.sum | 4 +- syft/file/contents_cataloger.go | 2 +- syft/file/metadata_cataloger_test.go | 60 ++++++++++++------- syft/file/secrets_cataloger.go | 4 +- syft/formats/syftjson/encoder_test.go | 20 +++++-- syft/formats/syftjson/to_format_model.go | 18 ++++-- syft/formats/syftjson/to_format_model_test.go | 42 +++++++++++++ syft/formats/syftjson/to_syft_model.go | 9 ++- syft/formats/syftjson/to_syft_model_test.go | 8 ++- syft/source/directory_indexer.go | 2 +- syft/source/directory_resolver.go | 4 +- syft/source/directory_resolver_test.go | 2 +- syft/source/image_all_layers_resolver.go | 4 +- syft/source/image_all_layers_resolver_test.go | 2 +- syft/source/image_squash_resolver.go | 4 +- syft/source/image_squash_resolver_test.go | 2 +- syft/source/mock_resolver.go | 9 ++- test/cli/all_formats_convertible_test.go | 7 +++ test/cli/spdx_tooling_validation_test.go | 6 +- 20 files changed, 152 insertions(+), 59 deletions(-) diff --git a/go.mod b/go.mod index a9de7418e2d..4a8359e09f8 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/Masterminds/sprig/v3 v3.2.3 github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 - github.com/anchore/stereoscope v0.0.0-20230412183729-8602f1afc574 + github.com/anchore/stereoscope v0.0.0-20230508133058-5543439b749f github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da github.com/docker/docker v24.0.0+incompatible github.com/github/go-spdx/v2 v2.1.2 diff --git a/go.sum b/go.sum index b98efad1b00..a6ed86ff04c 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20230412183729-8602f1afc574 h1:VFX+FD9EH6am+tfqwr1KeCAmabAknSJQX95aIY3QJJI= -github.com/anchore/stereoscope v0.0.0-20230412183729-8602f1afc574/go.mod h1:2GGFHkHry/xDlEQgBrVGcarq+z7Z6hLnHdyhcKB2lfQ= +github.com/anchore/stereoscope v0.0.0-20230508133058-5543439b749f h1:wiWDirrn2a4gT2TfFeGb5zqFjKoEy3Hx+K8u8lReHzY= +github.com/anchore/stereoscope v0.0.0-20230508133058-5543439b749f/go.mod h1:2GGFHkHry/xDlEQgBrVGcarq+z7Z6hLnHdyhcKB2lfQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= diff --git a/syft/file/contents_cataloger.go b/syft/file/contents_cataloger.go index b4d7802a63d..1e0cfe33b22 100644 --- a/syft/file/contents_cataloger.go +++ b/syft/file/contents_cataloger.go @@ -37,7 +37,7 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Co return nil, err } - if i.skipFilesAboveSizeInBytes > 0 && metadata.Size > i.skipFilesAboveSizeInBytes { + if i.skipFilesAboveSizeInBytes > 0 && metadata.Size() > i.skipFilesAboveSizeInBytes { continue } diff --git a/syft/file/metadata_cataloger_test.go b/syft/file/metadata_cataloger_test.go index 93f8758fb0a..3b625ba4f10 100644 --- a/syft/file/metadata_cataloger_test.go +++ b/syft/file/metadata_cataloger_test.go @@ -4,7 +4,6 @@ import ( "flag" "os" "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -52,12 +51,15 @@ func TestFileMetadataCataloger(t *testing.T) { path: "/file-1.txt", exists: true, expected: source.FileMetadata{ + FileInfo: file.ManualInfo{ + NameValue: "file-1.txt", + ModeValue: 0644, + SizeValue: 7, + }, Path: "/file-1.txt", - Mode: 0644, Type: file.TypeRegular, UserID: 1, GroupID: 2, - Size: 7, MIMEType: "text/plain", }, }, @@ -65,8 +67,11 @@ func TestFileMetadataCataloger(t *testing.T) { path: "/hardlink-1", exists: true, expected: source.FileMetadata{ + FileInfo: file.ManualInfo{ + NameValue: "hardlink-1", + ModeValue: 0644, + }, Path: "/hardlink-1", - Mode: 0644, Type: file.TypeHardLink, LinkDestination: "file-1.txt", UserID: 1, @@ -78,8 +83,11 @@ func TestFileMetadataCataloger(t *testing.T) { path: "/symlink-1", exists: true, expected: source.FileMetadata{ - Path: "/symlink-1", - Mode: 0777 | os.ModeSymlink, + Path: "/symlink-1", + FileInfo: file.ManualInfo{ + NameValue: "symlink-1", + ModeValue: 0777 | os.ModeSymlink, + }, Type: file.TypeSymLink, LinkDestination: "file-1.txt", UserID: 0, @@ -91,8 +99,11 @@ func TestFileMetadataCataloger(t *testing.T) { path: "/char-device-1", exists: true, expected: source.FileMetadata{ - Path: "/char-device-1", - Mode: 0644 | os.ModeDevice | os.ModeCharDevice, + Path: "/char-device-1", + FileInfo: file.ManualInfo{ + NameValue: "char-device-1", + ModeValue: 0644 | os.ModeDevice | os.ModeCharDevice, + }, Type: file.TypeCharacterDevice, UserID: 0, GroupID: 0, @@ -103,8 +114,11 @@ func TestFileMetadataCataloger(t *testing.T) { path: "/block-device-1", exists: true, expected: source.FileMetadata{ - Path: "/block-device-1", - Mode: 0644 | os.ModeDevice, + Path: "/block-device-1", + FileInfo: file.ManualInfo{ + NameValue: "block-device-1", + ModeValue: 0644 | os.ModeDevice, + }, Type: file.TypeBlockDevice, UserID: 0, GroupID: 0, @@ -115,8 +129,11 @@ func TestFileMetadataCataloger(t *testing.T) { path: "/fifo-1", exists: true, expected: source.FileMetadata{ - Path: "/fifo-1", - Mode: 0644 | os.ModeNamedPipe, + Path: "/fifo-1", + FileInfo: file.ManualInfo{ + NameValue: "fifo-1", + ModeValue: 0644 | os.ModeNamedPipe, + }, Type: file.TypeFIFO, UserID: 0, GroupID: 0, @@ -127,13 +144,15 @@ func TestFileMetadataCataloger(t *testing.T) { path: "/bin", exists: true, expected: source.FileMetadata{ - Path: "/bin", - Mode: 0755 | os.ModeDir, + Path: "/bin", + FileInfo: file.ManualInfo{ + NameValue: "bin", + ModeValue: 0755 | os.ModeDir, + }, Type: file.TypeDirectory, UserID: 0, GroupID: 0, MIMEType: "", - IsDir: true, }, }, } @@ -146,13 +165,14 @@ func TestFileMetadataCataloger(t *testing.T) { l := source.NewLocationFromImage(test.path, *ref.Reference, img) if _, ok := actual[l.Coordinates]; ok { - redact := actual[l.Coordinates] - redact.ModTime = time.Time{} - actual[l.Coordinates] = redact + // we're not interested in keeping the test fixtures up to date with the latest file modification times + // thus ModTime is not under test + fi := test.expected.FileInfo.(file.ManualInfo) + fi.ModTimeValue = actual[l.Coordinates].ModTime() + test.expected.FileInfo = fi } - assert.Equal(t, test.expected, actual[l.Coordinates], "mismatched metadata") - + assert.True(t, test.expected.Equal(actual[l.Coordinates])) }) } diff --git a/syft/file/secrets_cataloger.go b/syft/file/secrets_cataloger.go index d30e16068f3..56537d04968 100644 --- a/syft/file/secrets_cataloger.go +++ b/syft/file/secrets_cataloger.go @@ -71,11 +71,11 @@ func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, locatio return nil, err } - if metadata.Size == 0 { + if metadata.Size() == 0 { return nil, nil } - if i.skipFilesAboveSize > 0 && metadata.Size > i.skipFilesAboveSize { + if i.skipFilesAboveSize > 0 && metadata.Size() > i.skipFilesAboveSize { return nil, nil } diff --git a/syft/formats/syftjson/encoder_test.go b/syft/formats/syftjson/encoder_test.go index c42cc75c410..5b9a0f25c92 100644 --- a/syft/formats/syftjson/encoder_test.go +++ b/syft/formats/syftjson/encoder_test.go @@ -103,26 +103,38 @@ func TestEncodeFullJSONDocument(t *testing.T) { Packages: catalog, FileMetadata: map[source.Coordinates]source.FileMetadata{ source.NewLocation("/a/place").Coordinates: { - Mode: 0775, + FileInfo: stereoFile.ManualInfo{ + NameValue: "/a/place", + ModeValue: 0775, + }, Type: stereoFile.TypeDirectory, UserID: 0, GroupID: 0, }, source.NewLocation("/a/place/a").Coordinates: { - Mode: 0775, + FileInfo: stereoFile.ManualInfo{ + NameValue: "/a/place/a", + ModeValue: 0775, + }, Type: stereoFile.TypeRegular, UserID: 0, GroupID: 0, }, source.NewLocation("/b").Coordinates: { - Mode: 0775, + FileInfo: stereoFile.ManualInfo{ + NameValue: "/b", + ModeValue: 0775, + }, Type: stereoFile.TypeSymLink, LinkDestination: "/c", UserID: 0, GroupID: 0, }, source.NewLocation("/b/place/b").Coordinates: { - Mode: 0644, + FileInfo: stereoFile.ManualInfo{ + NameValue: "/b/place/b", + ModeValue: 0644, + }, Type: stereoFile.TypeRegular, UserID: 1, GroupID: 2, diff --git a/syft/formats/syftjson/to_format_model.go b/syft/formats/syftjson/to_format_model.go index 718237b99b5..efddddde268 100644 --- a/syft/formats/syftjson/to_format_model.go +++ b/syft/formats/syftjson/to_format_model.go @@ -131,10 +131,18 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe return nil } - mode, err := strconv.Atoi(fmt.Sprintf("%o", metadata.Mode)) - if err != nil { - log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coordinates, metadata.Mode, err) - mode = 0 + var mode int + var size int64 + if metadata != nil && metadata.FileInfo != nil { + var err error + + mode, err = strconv.Atoi(fmt.Sprintf("%o", metadata.Mode())) + if err != nil { + log.Warnf("invalid mode found in file catalog @ location=%+v mode=%q: %+v", coordinates, metadata.Mode, err) + mode = 0 + } + + size = metadata.Size() } return &model.FileMetadataEntry{ @@ -144,7 +152,7 @@ func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMe UserID: metadata.UserID, GroupID: metadata.GroupID, MIMEType: metadata.MIMEType, - Size: metadata.Size, + Size: size, } } diff --git a/syft/formats/syftjson/to_format_model_test.go b/syft/formats/syftjson/to_format_model_test.go index a99e66a3692..9794a1b7633 100644 --- a/syft/formats/syftjson/to_format_model_test.go +++ b/syft/formats/syftjson/to_format_model_test.go @@ -152,3 +152,45 @@ func Test_toFileType(t *testing.T) { assert.ElementsMatch(t, allTypesTested, file.AllTypes(), "not all file.Types are under test") } + +func Test_toFileMetadataEntry(t *testing.T) { + coords := source.Coordinates{ + RealPath: "/path", + FileSystemID: "x", + } + tests := []struct { + name string + metadata *source.FileMetadata + want *model.FileMetadataEntry + }{ + { + name: "no metadata", + }, + { + name: "no file info", + metadata: &source.FileMetadata{ + FileInfo: nil, + }, + want: &model.FileMetadataEntry{ + Type: file.TypeRegular.String(), + }, + }, + { + name: "with file info", + metadata: &source.FileMetadata{ + FileInfo: &file.ManualInfo{ + ModeValue: 1, + }, + }, + want: &model.FileMetadataEntry{ + Mode: 1, + Type: file.TypeRegular.String(), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, toFileMetadataEntry(coords, tt.metadata)) + }) + } +} diff --git a/syft/formats/syftjson/to_syft_model.go b/syft/formats/syftjson/to_syft_model.go index 0d02ef6f974..7b420183839 100644 --- a/syft/formats/syftjson/to_syft_model.go +++ b/syft/formats/syftjson/to_syft_model.go @@ -3,6 +3,7 @@ package syftjson import ( "fmt" "os" + "path" "strconv" "strings" @@ -79,14 +80,16 @@ func toSyftFiles(files []model.File) sbom.Artifacts { fm := os.FileMode(mode) ret.FileMetadata[coord] = source.FileMetadata{ + FileInfo: stereoscopeFile.ManualInfo{ + NameValue: path.Base(coord.RealPath), + SizeValue: f.Metadata.Size, + ModeValue: fm, + }, Path: coord.RealPath, LinkDestination: f.Metadata.LinkDestination, - Size: f.Metadata.Size, UserID: f.Metadata.UserID, GroupID: f.Metadata.GroupID, Type: toSyftFileType(f.Metadata.Type), - IsDir: fm.IsDir(), - Mode: fm, MIMEType: f.Metadata.MIMEType, } } diff --git a/syft/formats/syftjson/to_syft_model_test.go b/syft/formats/syftjson/to_syft_model_test.go index de96667f14c..8c4ab3cee52 100644 --- a/syft/formats/syftjson/to_syft_model_test.go +++ b/syft/formats/syftjson/to_syft_model_test.go @@ -202,14 +202,16 @@ func Test_toSyftFiles(t *testing.T) { want: sbom.Artifacts{ FileMetadata: map[source.Coordinates]source.FileMetadata{ coord: { + FileInfo: stereoFile.ManualInfo{ + NameValue: "place", + SizeValue: 92, + ModeValue: 511, // 777 octal = 511 decimal + }, Path: coord.RealPath, LinkDestination: "", - Size: 92, UserID: 42, GroupID: 32, Type: stereoFile.TypeRegular, - IsDir: false, - Mode: 511, // 777 octal = 511 decimal MIMEType: "text/plain", }, }, diff --git a/syft/source/directory_indexer.go b/syft/source/directory_indexer.go index e840a9ddf69..186f8f8f9a6 100644 --- a/syft/source/directory_indexer.go +++ b/syft/source/directory_indexer.go @@ -307,7 +307,7 @@ func (r *directoryIndexer) disallowRevisitingVisitor(path string, _ os.FileInfo, // - link destinations twice, once for the real file and another through the virtual path // - infinite link cycles if indexed, metadata := r.hasBeenIndexed(path); indexed { - if metadata.IsDir { + if metadata.IsDir() { // signal to walk() that we should skip this directory entirely return fs.SkipDir } diff --git a/syft/source/directory_resolver.go b/syft/source/directory_resolver.go index b68ce890344..a5a0e209de5 100644 --- a/syft/source/directory_resolver.go +++ b/syft/source/directory_resolver.go @@ -196,7 +196,7 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error) } // don't consider directories - if entry.Metadata.IsDir { + if entry.Metadata.IsDir() { continue } @@ -238,7 +238,7 @@ func (r directoryResolver) FilesByGlob(patterns ...string) ([]Location, error) { } // don't consider directories - if entry.Metadata.IsDir { + if entry.Metadata.IsDir() { continue } diff --git a/syft/source/directory_resolver_test.go b/syft/source/directory_resolver_test.go index 0a752ba41a7..1ab5eca8552 100644 --- a/syft/source/directory_resolver_test.go +++ b/syft/source/directory_resolver_test.go @@ -949,7 +949,7 @@ func TestDirectoryResolver_FilesContents_errorOnDirRequest(t *testing.T) { for loc := range resolver.AllLocations() { entry, err := resolver.index.Get(loc.ref) require.NoError(t, err) - if entry.Metadata.IsDir { + if entry.Metadata.IsDir() { dirLoc = &loc break } diff --git a/syft/source/image_all_layers_resolver.go b/syft/source/image_all_layers_resolver.go index ca40b12718c..dd9a0bd2e0d 100644 --- a/syft/source/image_all_layers_resolver.go +++ b/syft/source/image_all_layers_resolver.go @@ -100,7 +100,7 @@ func (r *imageAllLayersResolver) FilesByPath(paths ...string) ([]Location, error if err != nil { return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.RealPath, err) } - if metadata.Metadata.IsDir { + if metadata.Metadata.IsDir() { continue } } @@ -143,7 +143,7 @@ func (r *imageAllLayersResolver) FilesByGlob(patterns ...string) ([]Location, er return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", result.RequestPath, err) } // don't consider directories - if metadata.Metadata.IsDir { + if metadata.Metadata.IsDir() { continue } } diff --git a/syft/source/image_all_layers_resolver_test.go b/syft/source/image_all_layers_resolver_test.go index 86892b1884f..0a804290727 100644 --- a/syft/source/image_all_layers_resolver_test.go +++ b/syft/source/image_all_layers_resolver_test.go @@ -370,7 +370,7 @@ func TestAllLayersImageResolver_FilesContents_errorOnDirRequest(t *testing.T) { for loc := range resolver.AllLocations() { entry, err := resolver.img.FileCatalog.Get(loc.ref) require.NoError(t, err) - if entry.Metadata.IsDir { + if entry.Metadata.IsDir() { dirLoc = &loc break } diff --git a/syft/source/image_squash_resolver.go b/syft/source/image_squash_resolver.go index d62927b309c..233f008436d 100644 --- a/syft/source/image_squash_resolver.go +++ b/syft/source/image_squash_resolver.go @@ -56,7 +56,7 @@ func (r *imageSquashResolver) FilesByPath(paths ...string) ([]Location, error) { return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", ref.RealPath, err) } // don't consider directories - if metadata.Metadata.IsDir { + if metadata.Metadata.IsDir() { continue } } @@ -103,7 +103,7 @@ func (r *imageSquashResolver) FilesByGlob(patterns ...string) ([]Location, error return nil, fmt.Errorf("unable to get file metadata for path=%q: %w", result.RequestPath, err) } // don't consider directories - if metadata.Metadata.IsDir { + if metadata.Metadata.IsDir() { continue } } diff --git a/syft/source/image_squash_resolver_test.go b/syft/source/image_squash_resolver_test.go index 106de9a5e22..0fd9c4f99be 100644 --- a/syft/source/image_squash_resolver_test.go +++ b/syft/source/image_squash_resolver_test.go @@ -354,7 +354,7 @@ func TestSquashImageResolver_FilesContents_errorOnDirRequest(t *testing.T) { for loc := range resolver.AllLocations() { entry, err := resolver.img.FileCatalog.Get(loc.ref) require.NoError(t, err) - if entry.Metadata.IsDir { + if entry.Metadata.IsDir() { dirLoc = &loc break } diff --git a/syft/source/mock_resolver.go b/syft/source/mock_resolver.go index 74cffac3409..12cab882b86 100644 --- a/syft/source/mock_resolver.go +++ b/syft/source/mock_resolver.go @@ -168,11 +168,10 @@ func (r MockResolver) FileMetadataByLocation(l Location) (FileMetadata, error) { } return FileMetadata{ - Mode: info.Mode(), - Type: ty, - UserID: 0, // not supported - GroupID: 0, // not supported - Size: info.Size(), + FileInfo: info, + Type: ty, + UserID: 0, // not supported + GroupID: 0, // not supported }, nil } diff --git a/test/cli/all_formats_convertible_test.go b/test/cli/all_formats_convertible_test.go index 4b3dd478d44..d855a69aa44 100644 --- a/test/cli/all_formats_convertible_test.go +++ b/test/cli/all_formats_convertible_test.go @@ -52,6 +52,13 @@ func TestAllFormatsConvertable(t *testing.T) { convertArgs = append(convertArgs, "--template", test.template) } cmd, stdout, stderr = runSyft(t, test.env, convertArgs...) + if cmd.ProcessState.ExitCode() != 0 { + t.Log("STDOUT:\n", stdout) + t.Log("STDERR:\n", stderr) + t.Log("COMMAND:", strings.Join(cmd.Args, " ")) + t.Fatalf("failure executing syft creating an sbom") + return + } for _, traitFn := range assertions { traitFn(t, stdout, stderr, cmd.ProcessState.ExitCode()) } diff --git a/test/cli/spdx_tooling_validation_test.go b/test/cli/spdx_tooling_validation_test.go index b2aa46c1a3c..2e37d5a5b17 100644 --- a/test/cli/spdx_tooling_validation_test.go +++ b/test/cli/spdx_tooling_validation_test.go @@ -15,9 +15,9 @@ import ( ) func TestSpdxValidationTooling(t *testing.T) { - img := imagetest.GetFixtureImage(t, "docker-archive", "image-java-spdx-tools") - require.NotEmpty(t, img.Metadata.Tags) - imgTag := img.Metadata.Tags[0] + // note: the external tooling requires that the daemon explicitly has the image loaded, not just that + // we can get the image from a cache tar. + imgTag := imagetest.LoadFixtureImageIntoDocker(t, "image-java-spdx-tools") images := []string{ "alpine:3.17.3@sha256:b6ca290b6b4cdcca5b3db3ffa338ee0285c11744b4a6abaa9627746ee3291d8d", From b09cf6c6b558f34d3c40b8d945e8226309b096fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 13:07:24 -0400 Subject: [PATCH 03/21] chore(deps): bump github.com/docker/docker (#1833) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.0+incompatible to 24.0.1+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v24.0.0...v24.0.1) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 4a8359e09f8..5c771ec6175 100644 --- a/go.mod +++ b/go.mod @@ -55,7 +55,7 @@ require ( github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 github.com/anchore/stereoscope v0.0.0-20230508133058-5543439b749f github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da - github.com/docker/docker v24.0.0+incompatible + github.com/docker/docker v24.0.1+incompatible github.com/github/go-spdx/v2 v2.1.2 github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-git/v5 v5.6.1 diff --git a/go.sum b/go.sum index a6ed86ff04c..a6f4406633c 100644 --- a/go.sum +++ b/go.sum @@ -166,8 +166,8 @@ github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuD github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.0+incompatible h1:z4bf8HvONXX9Tde5lGBMQ7yCJgNahmJumdrStZAbeY4= -github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.1+incompatible h1:NxN81beIxDlUaVt46iUQrYHD9/W3u9EGl52r86O/IGw= +github.com/docker/docker v24.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= From f50302b2ba4c0c7570f02c5b367e3ebbbf7ad0c5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 May 2023 14:01:17 -0400 Subject: [PATCH 04/21] chore(deps): bump github.com/stretchr/testify from 1.8.2 to 1.8.3 (#1829) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.2 to 1.8.3. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.2...v1.8.3) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 5c771ec6175..c30e9625aed 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.2 + github.com/stretchr/testify v1.8.3 github.com/vifraa/gopom v0.2.1 github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5 github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 diff --git a/go.sum b/go.sum index a6f4406633c..0d26ecf8acf 100644 --- a/go.sum +++ b/go.sum @@ -598,8 +598,9 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= From 798af578531f5f6813ef70e34a3af126110325fd Mon Sep 17 00:00:00 2001 From: "anchore-actions-token-generator[bot]" <102182147+anchore-actions-token-generator[bot]@users.noreply.github.com> Date: Tue, 23 May 2023 10:18:39 -0400 Subject: [PATCH 05/21] chore(deps): update stereoscope to e14bc4437b2eac481c5b6f101890b22df4f33596 (#1834) Signed-off-by: GitHub Co-authored-by: kzantow --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index c30e9625aed..fe106fe4af3 100644 --- a/go.mod +++ b/go.mod @@ -53,7 +53,7 @@ require ( github.com/Masterminds/semver v1.5.0 github.com/Masterminds/sprig/v3 v3.2.3 github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 - github.com/anchore/stereoscope v0.0.0-20230508133058-5543439b749f + github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da github.com/docker/docker v24.0.1+incompatible github.com/github/go-spdx/v2 v2.1.2 @@ -86,7 +86,7 @@ require ( github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/docker/cli v23.0.5+incompatible // indirect - github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect diff --git a/go.sum b/go.sum index 0d26ecf8acf..6d3d53a5881 100644 --- a/go.sum +++ b/go.sum @@ -98,8 +98,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501 h1:AV7qjwMcM4r8wFhJq3jLRztew3ywIyPTRapl2T1s9o8= github.com/anchore/packageurl-go v0.1.1-0.20230104203445-02e0a6721501/go.mod h1:Blo6OgJNiYF41ufcgHKkbCKF2MDOMlrqhXv/ij6ocR4= -github.com/anchore/stereoscope v0.0.0-20230508133058-5543439b749f h1:wiWDirrn2a4gT2TfFeGb5zqFjKoEy3Hx+K8u8lReHzY= -github.com/anchore/stereoscope v0.0.0-20230508133058-5543439b749f/go.mod h1:2GGFHkHry/xDlEQgBrVGcarq+z7Z6hLnHdyhcKB2lfQ= +github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e h1:YPWJxds1hKRedS92u7O6D6ULVOx1F2HGgS4CWqJdBYw= +github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e/go.mod h1:0LsgHgXO4QFnk2hsYwtqd3fR18PIZXlFLIl2qb9tu3g= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= @@ -164,8 +164,8 @@ github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da/go.mod h1:B3tI9iGHi4i github.com/dgrijalva/jwt-go/v4 v4.0.0-preview1/go.mod h1:+hnT3ywWDTAFrW5aE+u2Sa/wT555ZqwoCS+pk3p6ry4= github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuDEvFVVDafE= github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= -github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= +github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.1+incompatible h1:NxN81beIxDlUaVt46iUQrYHD9/W3u9EGl52r86O/IGw= github.com/docker/docker v24.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= From a3c555021746fca1eaeca466245d70c506b74613 Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Tue, 23 May 2023 10:24:25 -0400 Subject: [PATCH 06/21] fix: duplicate packages, support pnpm lockfile v6 (#1778) --- .../cataloger/javascript/parse_pnpm_lock.go | 63 ++++++++- .../javascript/parse_pnpm_lock_test.go | 92 +++++++++++++ .../test-fixtures/pnpm-v6/pnpm-lock.yaml | 127 ++++++++++++++++++ 3 files changed, 277 insertions(+), 5 deletions(-) create mode 100644 syft/pkg/cataloger/javascript/test-fixtures/pnpm-v6/pnpm-lock.yaml diff --git a/syft/pkg/cataloger/javascript/parse_pnpm_lock.go b/syft/pkg/cataloger/javascript/parse_pnpm_lock.go index 071334b466e..418f6286285 100644 --- a/syft/pkg/cataloger/javascript/parse_pnpm_lock.go +++ b/syft/pkg/cataloger/javascript/parse_pnpm_lock.go @@ -3,10 +3,13 @@ package javascript import ( "fmt" "io" + "regexp" + "strconv" "strings" "gopkg.in/yaml.v3" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" @@ -17,8 +20,9 @@ import ( var _ generic.Parser = parsePnpmLock type pnpmLockYaml struct { - Dependencies map[string]string `json:"dependencies"` - Packages map[string]interface{} `json:"packages"` + Version string `json:"lockfileVersion" yaml:"lockfileVersion"` + Dependencies map[string]interface{} `json:"dependencies" yaml:"dependencies"` + Packages map[string]interface{} `json:"packages" yaml:"packages"` } func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { @@ -34,19 +38,55 @@ func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader return nil, nil, fmt.Errorf("failed to parse pnpm-lock.yaml file: %w", err) } - for name, version := range lockFile.Dependencies { + lockVersion, _ := strconv.ParseFloat(lockFile.Version, 64) + + for name, info := range lockFile.Dependencies { + version := "" + + switch info := info.(type) { + case string: + version = info + case map[string]interface{}: + v, ok := info["version"] + if !ok { + break + } + ver, ok := v.(string) + if ok { + version = parseVersion(ver) + } + default: + log.Tracef("unsupported pnpm dependency type: %+v", info) + continue + } + + if hasPkg(pkgs, name, version) { + continue + } + pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version)) } + packageNameRegex := regexp.MustCompile(`^/?([^(]*)(?:\(.*\))*$`) + splitChar := "/" + if lockVersion >= 6.0 { + splitChar = "@" + } + // parse packages from packages section of pnpm-lock.yaml for nameVersion := range lockFile.Packages { - nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), "/") + nameVersion = packageNameRegex.ReplaceAllString(nameVersion, "$1") + nameVersionSplit := strings.Split(strings.TrimPrefix(nameVersion, "/"), splitChar) // last element in split array is version version := nameVersionSplit[len(nameVersionSplit)-1] // construct name from all array items other than last item (version) - name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], "/") + name := strings.Join(nameVersionSplit[:len(nameVersionSplit)-1], splitChar) + + if hasPkg(pkgs, name, version) { + continue + } pkgs = append(pkgs, newPnpmPackage(resolver, reader.Location, name, version)) } @@ -55,3 +95,16 @@ func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader return pkgs, nil, nil } + +func hasPkg(pkgs []pkg.Package, name, version string) bool { + for _, p := range pkgs { + if p.Name == name && p.Version == version { + return true + } + } + return false +} + +func parseVersion(version string) string { + return strings.SplitN(version, "(", 2)[0] +} diff --git a/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go b/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go index 275cc0439a6..bcf1fe40ad0 100644 --- a/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go @@ -52,3 +52,95 @@ func TestParsePnpmLock(t *testing.T) { pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships) } + +func TestParsePnpmV6Lock(t *testing.T) { + var expectedRelationships []artifact.Relationship + fixture := "test-fixtures/pnpm-v6/pnpm-lock.yaml" + + locationSet := source.NewLocationSet(source.NewLocation(fixture)) + + expectedPkgs := []pkg.Package{ + { + Name: "@testing-library/jest-dom", + Version: "5.16.5", + PURL: "pkg:npm/%40testing-library/jest-dom@5.16.5", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "@testing-library/react", + Version: "13.4.0", + PURL: "pkg:npm/%40testing-library/react@13.4.0", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "@testing-library/user-event", + Version: "13.5.0", + PURL: "pkg:npm/%40testing-library/user-event@13.5.0", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "react", + Version: "18.2.0", + PURL: "pkg:npm/react@18.2.0", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "react-dom", + Version: "18.2.0", + PURL: "pkg:npm/react-dom@18.2.0", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "web-vitals", + Version: "2.1.4", + PURL: "pkg:npm/web-vitals@2.1.4", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "@babel/core", + Version: "7.21.4", + PURL: "pkg:npm/%40babel/core@7.21.4", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "@types/eslint", + Version: "8.37.0", + PURL: "pkg:npm/%40types/eslint@8.37.0", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "read-cache", + Version: "1.0.0", + PURL: "pkg:npm/read-cache@1.0.0", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + { + Name: "schema-utils", + Version: "3.1.2", + PURL: "pkg:npm/schema-utils@3.1.2", + Locations: locationSet, + Language: pkg.JavaScript, + Type: pkg.NpmPkg, + }, + } + + pkgtest.TestFileParser(t, fixture, parsePnpmLock, expectedPkgs, expectedRelationships) +} diff --git a/syft/pkg/cataloger/javascript/test-fixtures/pnpm-v6/pnpm-lock.yaml b/syft/pkg/cataloger/javascript/test-fixtures/pnpm-v6/pnpm-lock.yaml new file mode 100644 index 00000000000..5098519b66e --- /dev/null +++ b/syft/pkg/cataloger/javascript/test-fixtures/pnpm-v6/pnpm-lock.yaml @@ -0,0 +1,127 @@ +lockfileVersion: '6.0' + +dependencies: + '@testing-library/jest-dom': + specifier: ^5.16.5 + version: 5.16.5 + '@testing-library/react': + specifier: ^13.4.0 + version: 13.4.0(react-dom@18.2.0)(react@18.2.0) + '@testing-library/user-event': + specifier: ^13.5.0 + version: 13.5.0(@testing-library/dom@9.2.0) + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + web-vitals: + specifier: ^2.1.4 + version: 2.1.4 + +packages: + /@babel/core@7.21.4: + resolution: {integrity: sha512-qt/YV149Jman/6AfmlxJ04LMIu8bMoyl3RB91yTFrxQmgbrSvQMy7cI8Q62FHx1t8wJ8B5fu0UDoLwHAhUo1QA==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.21.4 + '@babel/generator': 7.21.4 + '@babel/helper-compilation-targets': 7.21.4(@babel/core@7.21.4) + '@babel/helper-module-transforms': 7.21.2 + '@babel/helpers': 7.21.0 + '@babel/parser': 7.21.4 + '@babel/template': 7.20.7 + '@babel/traverse': 7.21.4 + '@babel/types': 7.21.4 + convert-source-map: 1.9.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.0 + transitivePeerDependencies: + - supports-color + dev: false + + /@testing-library/jest-dom@5.16.5: + resolution: {integrity: sha512-N5ixQ2qKpi5OLYfwQmUb/5mSV9LneAcaUfp32pn4yCnpb8r/Yz0pXFPck21dIicKmi+ta5WRAknkZCfA8refMA==} + engines: {node: '>=8', npm: '>=6', yarn: '>=1'} + dependencies: + '@adobe/css-tools': 4.2.0 + '@babel/runtime': 7.21.0 + '@types/testing-library__jest-dom': 5.14.5 + aria-query: 5.1.3 + chalk: 3.0.0 + css.escape: 1.5.1 + dom-accessibility-api: 0.5.16 + lodash: 4.17.21 + redent: 3.0.0 + dev: false + + /@testing-library/react@13.4.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sXOGON+WNTh3MLE9rve97ftaZukN3oNf2KjDy7YTx6hcTO2uuLHuCGynMDhFwGw/jYf4OJ2Qk0i4i79qMNNkyw==} + engines: {node: '>=12'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependencies: + '@babel/runtime': 7.21.0 + '@testing-library/dom': 8.20.0 + '@types/react-dom': 18.2.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /@testing-library/user-event@13.5.0(@testing-library/dom@9.2.0): + resolution: {integrity: sha512-5Kwtbo3Y/NowpkbRuSepbyMFkZmHgD+vPzYB/RJ4oxt5Gj/avFFBYjhw27cqSVPVw/3a67NK1PbiIr9k4Gwmdg==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + dependencies: + '@babel/runtime': 7.21.0 + '@testing-library/dom': 9.2.0 + dev: false + + /@types/eslint@8.37.0: + resolution: {integrity: sha512-Piet7dG2JBuDIfohBngQ3rCt7MgO9xCO4xIMKxBThCq5PNRB91IjlJ10eJVwfoNtvTErmxLzwBZ7rHZtbOMmFQ==} + dependencies: + '@types/estree': 1.0.1 + '@types/json-schema': 7.0.11 + dev: false + + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + dev: false + + /schema-utils@3.1.2: + resolution: {integrity: sha512-pvjEHOgWc9OWA/f/DE3ohBWTD6EleVLf7iFUkoSwAxttdBhB9QUebQgxER2kWueOvRJXPHNnyrvvh9eZINB8Eg==} + engines: {node: '>= 10.13.0'} + dependencies: + '@types/json-schema': 7.0.11 + ajv: 6.12.6 + ajv-keywords: 3.5.2(ajv@6.12.6) + dev: false + + /web-vitals@2.1.4: + resolution: {integrity: sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==} + dev: false From 26c201f7f76e115168ffef8b92578747b207ee76 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Tue, 23 May 2023 10:27:48 -0400 Subject: [PATCH 07/21] Extract go module versions from ldflags for binaries built by go (#1832) * wip Signed-off-by: Weston Steimel * with golang bin ldflags refactor Signed-off-by: Alex Goodman * add test for golang binary cataloger for ldflag extraction Signed-off-by: Alex Goodman * remove binary classfiers that overlap with new go ldflags detection Signed-off-by: Alex Goodman --------- Signed-off-by: Weston Steimel Signed-off-by: Alex Goodman Co-authored-by: Weston Steimel --- syft/pkg/cataloger/binary/cataloger_test.go | 84 ------ .../cataloger/binary/default_classifiers.go | 27 -- .../cataloger/binary/test-fixtures/Makefile | 49 ---- syft/pkg/cataloger/golang/package.go | 2 +- syft/pkg/cataloger/golang/parse_go_binary.go | 89 +++++- .../cataloger/golang/parse_go_binary_test.go | 254 +++++++++++++++++- 6 files changed, 329 insertions(+), 176 deletions(-) diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 84771a16fc9..51b0751ef81 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -239,30 +239,6 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("redis-binary"), }, }, - { - name: "positive-argocd-2.5.11", - fixtureDir: "test-fixtures/classifiers/dynamic/argocd-2.5.11", - expected: pkg.Package{ - Name: "argocd", - Version: "2.5.11", - Type: "binary", - PURL: "pkg:golang/github.com/argoproj/argo-cd@2.5.11", - Locations: locations("argocd"), - Metadata: metadata("argocd"), - }, - }, - { - name: "positive-argocd-2.6.4", - fixtureDir: "test-fixtures/classifiers/dynamic/argocd-2.6.4", - expected: pkg.Package{ - Name: "argocd", - Version: "2.6.4", - Type: "binary", - PURL: "pkg:golang/github.com/argoproj/argo-cd@2.6.4", - Locations: locations("argocd"), - Metadata: metadata("argocd"), - }, - }, { name: "positive-helm-3.11.1", fixtureDir: "test-fixtures/classifiers/dynamic/helm-3.11.1", @@ -287,66 +263,6 @@ func Test_Cataloger_DefaultClassifiers_PositiveCases(t *testing.T) { Metadata: metadata("helm"), }, }, - { - name: "positive-kubectl-1.24.11", - fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.24.11", - expected: pkg.Package{ - Name: "kubectl", - Version: "1.24.11", - Type: "binary", - PURL: "pkg:golang/k8s.io/kubectl@1.24.11", - Locations: locations("kubectl"), - Metadata: metadata("kubectl"), - }, - }, - { - name: "positive-kubectl-1.25.7", - fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.25.7", - expected: pkg.Package{ - Name: "kubectl", - Version: "1.25.7", - Type: "binary", - PURL: "pkg:golang/k8s.io/kubectl@1.25.7", - Locations: locations("kubectl"), - Metadata: metadata("kubectl"), - }, - }, - { - name: "positive-kubectl-1.26.2", - fixtureDir: "test-fixtures/classifiers/dynamic/kubectl-1.26.2", - expected: pkg.Package{ - Name: "kubectl", - Version: "1.26.2", - Type: "binary", - PURL: "pkg:golang/k8s.io/kubectl@1.26.2", - Locations: locations("kubectl"), - Metadata: metadata("kubectl"), - }, - }, - { - name: "positive-kustomize-4.5.7", - fixtureDir: "test-fixtures/classifiers/dynamic/kustomize-4.5.7", - expected: pkg.Package{ - Name: "kustomize", - Version: "4.5.7", - Type: "binary", - PURL: "pkg:golang/sigs.k8s.io/kustomize@4.5.7", - Locations: locations("kustomize"), - Metadata: metadata("kustomize"), - }, - }, - { - name: "positive-kustomize-5.0.0", - fixtureDir: "test-fixtures/classifiers/dynamic/kustomize-5.0.0", - expected: pkg.Package{ - Name: "kustomize", - Version: "5.0.0", - Type: "binary", - PURL: "pkg:golang/sigs.k8s.io/kustomize@5.0.0", - Locations: locations("kustomize"), - Metadata: metadata("kustomize"), - }, - }, { name: "positive-redis-4.0.11", fixtureDir: "test-fixtures/classifiers/positive/redis-server-4.0.11", diff --git a/syft/pkg/cataloger/binary/default_classifiers.go b/syft/pkg/cataloger/binary/default_classifiers.go index 8aa739b4b9f..5c2f2e17fe4 100644 --- a/syft/pkg/cataloger/binary/default_classifiers.go +++ b/syft/pkg/cataloger/binary/default_classifiers.go @@ -46,15 +46,6 @@ var defaultClassifiers = []classifier{ PURL: mustPURL("pkg:generic/go@version"), CPEs: singleCPE("cpe:2.3:a:golang:go:*:*:*:*:*:*:*:*"), }, - { - Class: "argocd", - FileGlob: "**/argocd", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)common\.version=(?P[0-9]+\.[0-9]+\.[0-9]+)`), - Package: "argocd", - PURL: mustPURL("pkg:golang/github.com/argoproj/argo-cd@version"), - CPEs: singleCPE("cpe:2.3:a:argoproj:argocd:*:*:*:*:*:*:*"), - }, { Class: "helm", FileGlob: "**/helm", @@ -64,24 +55,6 @@ var defaultClassifiers = []classifier{ PURL: mustPURL("pkg:golang/helm.sh/helm@version"), CPEs: singleCPE("cpe:2.3:a:helm:helm:*:*:*:*:*:*:*"), }, - { - Class: "kustomize", - FileGlob: "**/kustomize", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)version=kustomize/v(?P[0-9]+\.[0-9]+\.[0-9]+)`), - Package: "kustomize", - PURL: mustPURL("pkg:golang/sigs.k8s.io/kustomize@version"), - CPEs: singleCPE("cpe:2.3:a:kustomize:kustomize:*:*:*:*:*:*:*"), - }, - { - Class: "kubectl", - FileGlob: "**/kubectl", - EvidenceMatcher: fileContentsVersionMatcher( - `(?m)\x00v(?P[0-9]+\.[0-9]+\.[0-9]+)\x00`), - Package: "kubectl", - PURL: mustPURL("pkg:golang/k8s.io/kubectl@version"), - CPEs: singleCPE("cpe:2.3:a:kubectl:kubectl:*:*:*:*:*:*:*"), - }, { Class: "redis-binary", FileGlob: "**/redis-server", diff --git a/syft/pkg/cataloger/binary/test-fixtures/Makefile b/syft/pkg/cataloger/binary/test-fixtures/Makefile index afd160d8f09..4ed523068e4 100644 --- a/syft/pkg/cataloger/binary/test-fixtures/Makefile +++ b/syft/pkg/cataloger/binary/test-fixtures/Makefile @@ -7,15 +7,8 @@ all: \ classifiers/dynamic/ruby-library-3.2.1 \ classifiers/dynamic/ruby-library-2.7.7 \ classifiers/dynamic/ruby-library-2.6.10 \ - classifiers/dynamic/argocd-2.5.11 \ - classifiers/dynamic/argocd-2.6.4 \ classifiers/dynamic/helm-3.11.1 \ classifiers/dynamic/helm-3.10.3 \ - classifiers/dynamic/kubectl-1.24.11 \ - classifiers/dynamic/kubectl-1.25.7 \ - classifiers/dynamic/kubectl-1.26.2 \ - classifiers/dynamic/kustomize-4.5.7 \ - classifiers/dynamic/kustomize-5.0.0 \ classifiers/dynamic/consul-1.15.2 @@ -89,18 +82,6 @@ classifiers/dynamic/ruby-library-2.6.10: /usr/local/lib/libruby.so.2.6 \ $@/libruby.so.2.6 -classifiers/dynamic/argocd-2.5.11: - $(eval $@_image := "argoproj/argocd:v2.5.11@sha256:d1062935b3256ec69422843ebcb50debb54fd389436961586000c8ce6ee7f249") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/argocd \ - $@/argocd - -classifiers/dynamic/argocd-2.6.4: - $(eval $@_image := "argoproj/argocd:v2.6.4@sha256:61fcbba187ff53c00696cb580edf70cada59c45cf399d8477631acf43cf522ee") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/argocd \ - $@/argocd - classifiers/dynamic/helm-3.11.1: $(eval $@_image := "alpine/helm:3.11.1@sha256:8628e3695fb743a8b9de89626f1b7a221280c2152c0e288c2504e59b68233e8b") ./get-image-file.sh $($@_image) \ @@ -113,36 +94,6 @@ classifiers/dynamic/helm-3.10.3: /usr/local/bin/helm \ $@/helm -classifiers/dynamic/kubectl-1.24.11: - $(eval $@_image := "bitnami/kubectl:1.24.11@sha256:79d60c5ac8a1dc84e2c39f56d8e8cc0053159b5ed88f283bdf8fbda1ee86c8bc") - ./get-image-file.sh $($@_image) \ - /opt/bitnami/kubectl/bin/kubectl \ - $@/kubectl - -classifiers/dynamic/kubectl-1.25.7: - $(eval $@_image := "bitnami/kubectl:1.25.7@sha256:d7b00dbfdc6d8890aefe40edfb6c1d4c90cbb6c978794bb51a21744edc34ba7a") - ./get-image-file.sh $($@_image) \ - /opt/bitnami/kubectl/bin/kubectl \ - $@/kubectl - -classifiers/dynamic/kubectl-1.26.2: - $(eval $@_image := "line/kubectl-kustomize:1.26.2-5.0.0@sha256:9ee3b4a9a21f0777fc1d8c64208290f818a2e68c5e9e892e931621bda089bf06") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/kubectl \ - $@/kubectl - -classifiers/dynamic/kustomize-4.5.7: - $(eval $@_image := "argoproj/argocd:v2.6.4@sha256:61fcbba187ff53c00696cb580edf70cada59c45cf399d8477631acf43cf522ee") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/kustomize \ - $@/kustomize - -classifiers/dynamic/kustomize-5.0.0: - $(eval $@_image := "line/kubectl-kustomize:1.26.2-5.0.0@sha256:9ee3b4a9a21f0777fc1d8c64208290f818a2e68c5e9e892e931621bda089bf06") - ./get-image-file.sh $($@_image) \ - /usr/local/bin/kustomize \ - $@/kustomize - classifiers/dynamic/consul-1.15.2: $(eval $@_image := "hashicorp/consul:1.15.2@sha256:c2169f3bb18dd947ae8eb5f6766896695c71fb439f050a3343e0007d895615b8") ./get-image-file.sh $($@_image) \ diff --git a/syft/pkg/cataloger/golang/package.go b/syft/pkg/cataloger/golang/package.go index 2e4c2808994..a7b1ee44e82 100644 --- a/syft/pkg/cataloger/golang/package.go +++ b/syft/pkg/cataloger/golang/package.go @@ -18,7 +18,7 @@ func (c *goBinaryCataloger) newGoBinaryPackage(resolver source.FileResolver, dep licenses, err := c.licenses.getLicenses(resolver, dep.Path, dep.Version) if err != nil { - log.Tracef("error getting licenses for package: %s %v", dep.Path, err) + log.Tracef("error getting licenses for golang package: %s %v", dep.Path, err) } p := pkg.Package{ diff --git a/syft/pkg/cataloger/golang/parse_go_binary.go b/syft/pkg/cataloger/golang/parse_go_binary.go index c7b99fd2501..542fcbe0232 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -8,6 +8,7 @@ import ( "errors" "fmt" "io" + "regexp" "runtime/debug" "strings" "time" @@ -34,6 +35,11 @@ var ( // devel is used to recognize the current default version when a golang main distribution is built // https://github.com/golang/go/issues/29228 this issue has more details on the progress of being able to // inject the correct version into the main module of the build process + + knownBuildFlagPatterns = []*regexp.Regexp{ + regexp.MustCompile(`(?m)\.([gG]it)?([bB]uild)?[vV]ersion=(\S+/)*(?Pv?\d+.\d+.\d+[-\w]*)`), + regexp.MustCompile(`(?m)\.([tT]ag)=(\S+/)*(?Pv?\d+.\d+.\d+[-\w]*)`), + } ) const devel = "(devel)" @@ -71,27 +77,82 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver source.FileResolver, mod gbs, location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ) - if main.Version == devel { - if version, ok := gbs["vcs.revision"]; ok { - if timestamp, ok := gbs["vcs.time"]; ok { - //NOTE: err is ignored, because if parsing fails - // we still use the empty Time{} struct to generate an empty date, like 00010101000000 - // for consistency with the pseudo-version format: https://go.dev/ref/mod#pseudo-versions - ts, _ := time.Parse(time.RFC3339, timestamp) - if len(version) >= 12 { - version = version[:12] - } - version = module.PseudoVersion("", "", ts, version) + + if main.Version != devel { + return main + } + + version, hasVersion := gbs["vcs.revision"] + timestamp, hasTimestamp := gbs["vcs.time"] + + if hasVersion { + if hasTimestamp { + //NOTE: err is ignored, because if parsing fails + // we still use the empty Time{} struct to generate an empty date, like 00010101000000 + // for consistency with the pseudo-version format: https://go.dev/ref/mod#pseudo-versions + ts, _ := time.Parse(time.RFC3339, timestamp) + if len(version) >= 12 { + version = version[:12] + } + + var ldflags string + if metadata, ok := main.Metadata.(pkg.GolangBinMetadata); ok { + ldflags = metadata.BuildSettings["-ldflags"] + } + + majorVersion, fullVersion := extractVersionFromLDFlags(ldflags) + if fullVersion != "" { + // we've found a specific version from the ldflags! use it as the version. + // why not combine that with the pseudo version (e.g. v1.2.3-0.20210101000000-abcdef123456)? + // short answer: we're assuming that if a specific semver was provided in the ldflags that + // there is a matching vcs tag to match that could be referenced. This assumption could + // be incorrect in terms of the go.mod contents, but is not incorrect in terms of the logical + // version of the package. + version = fullVersion + } else { + version = module.PseudoVersion(majorVersion, fullVersion, ts, version) } - main.Version = version - main.PURL = packageURL(main.Name, main.Version) - main.SetID() } + + main.Version = version + main.PURL = packageURL(main.Name, main.Version) + + main.SetID() } return main } +func extractVersionFromLDFlags(ldflags string) (majorVersion string, fullVersion string) { + if ldflags == "" { + return "", "" + } + + for _, pattern := range knownBuildFlagPatterns { + groups := internal.MatchNamedCaptureGroups(pattern, ldflags) + v, ok := groups["version"] + + if !ok { + continue + } + + fullVersion = v + if !strings.HasPrefix(v, "v") { + fullVersion = fmt.Sprintf("v%s", v) + } + components := strings.Split(v, ".") + + if len(components) == 0 { + continue + } + + majorVersion = strings.TrimPrefix(components[0], "v") + return majorVersion, fullVersion + } + + return "", "" +} + // getArchs finds a binary architecture by two ways: // 1) reading build info from binaries compiled by go1.18+ // 2) reading file headers from binaries compiled by < go1.18 diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index d180d158fdc..cb0d9e3eb33 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -299,7 +299,101 @@ func TestBuildGoPkgInfo(t *testing.T) { expected: []pkg.Package{unmodifiedMain}, }, { - name: "parse main mod and replace devel version", + name: "parse main mod and replace devel pseudo version and ldflags exists (but contains no version)", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + {Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"}, + {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, + {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`}, + }, + }, + expected: []pkg.Package{ + { + Name: "github.com/anchore/syft", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Version: "v0.0.0-20221014195457-41bc6bb41035", + PURL: "pkg:golang/github.com/anchore/syft@v0.0.0-20221014195457-41bc6bb41035", + Locations: source.NewLocationSet( + source.NewLocationFromCoordinates( + source.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + BuildSettings: map[string]string{ + "GOARCH": archDetails, + "GOOS": "darwin", + "GOAMD64": "v1", + "vcs.revision": "41bc6bb410352845f22766e27dd48ba93aa825a4", + "vcs.time": "2022-10-14T19:54:57Z", + "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X blah=foobar`, + }, + MainModule: "github.com/anchore/syft", + }, + }, + }, + }, + { + name: "parse main mod and replace devel version with one from ldflags", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + {Key: "vcs.revision", Value: "41bc6bb410352845f22766e27dd48ba93aa825a4"}, + {Key: "vcs.time", Value: "2022-10-14T19:54:57Z"}, + {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`}, + }, + }, + expected: []pkg.Package{ + { + Name: "github.com/anchore/syft", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Version: "v0.79.0", + PURL: "pkg:golang/github.com/anchore/syft@v0.79.0", + Locations: source.NewLocationSet( + source.NewLocationFromCoordinates( + source.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + BuildSettings: map[string]string{ + "GOARCH": archDetails, + "GOOS": "darwin", + "GOAMD64": "v1", + "vcs.revision": "41bc6bb410352845f22766e27dd48ba93aa825a4", + "vcs.time": "2022-10-14T19:54:57Z", + "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`, + }, + MainModule: "github.com/anchore/syft", + }, + }, + }, + }, + { + name: "parse main mod and replace devel version with a pseudo version", arch: archDetails, mod: &debug.BuildInfo{ GoVersion: goCompiledVersion, @@ -512,3 +606,161 @@ func TestBuildGoPkgInfo(t *testing.T) { }) } } + +func Test_extractVersionFromLDFlags(t *testing.T) { + tests := []struct { + name string + ldflags string + wantMajorVersion string + wantFullVersion string + }{ + { + name: "empty ldflags", + ldflags: "", + }, + { + name: "syft ldflags", + ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0 -X github.com/anchore/syft/internal/version.gitCommit=b2b332e8b2b66af0905e98b54ebd713a922be1a8 -X github.com/anchore/syft/internal/version.buildDate=2023-04-21T16:20:25Z -X github.com/anchore/syft/internal/version.gitDescription=v0.79.0 "`, + wantMajorVersion: "0", + wantFullVersion: "v0.79.0", + }, + { + name: "kubectl ldflags", + ldflags: ` build -asmflags=all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes + build -compiler=gc + build -gcflags="all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes " + build -ldflags="all=-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.buildDate=2023-04-12T12:16:51Z' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.buildDate=2023-04-12T12:16:51Z' -X 'k8s.io/client-go/pkg/version.buildDate=2023-04-12T12:16:51Z' -X 'k8s.io/component-base/version.buildDate=2023-04-12T12:16:51Z' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitCommit=a1a87a0a2bcd605820920c6b0e618a8ab7d117d4' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitCommit=a1a87a0a2bcd605820920c6b0e618a8ab7d117d4' -X 'k8s.io/client-go/pkg/version.gitCommit=a1a87a0a2bcd605820920c6b0e618a8ab7d117d4' -X 'k8s.io/component-base/version.gitCommit=a1a87a0a2bcd605820920c6b0e618a8ab7d117d4' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitTreeState=clean' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitTreeState=clean' -X 'k8s.io/client-go/pkg/version.gitTreeState=clean' -X 'k8s.io/component-base/version.gitTreeState=clean' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitVersion=v1.25.9' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitVersion=v1.25.9' -X 'k8s.io/client-go/pkg/version.gitVersion=v1.25.9' -X 'k8s.io/component-base/version.gitVersion=v1.25.9' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMajor=1' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMajor=1' -X 'k8s.io/client-go/pkg/version.gitMajor=1' -X 'k8s.io/component-base/version.gitMajor=1' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMinor=25' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMinor=25' -X 'k8s.io/client-go/pkg/version.gitMinor=25' -X 'k8s.io/component-base/version.gitMinor=25' -s -w"`, + wantMajorVersion: "1", + wantFullVersion: "v1.25.9", + }, + { + name: "nerdctl ldflags", + ldflags: ` build -ldflags="-s -w -X github.com/containerd/nerdctl/pkg/version.Version=v1.3.1 -X github.com/containerd/nerdctl/pkg/version.Revision=b224b280ff3086516763c7335fc0e0997aca617a"`, + wantMajorVersion: "1", + wantFullVersion: "v1.3.1", + }, + { + name: "limactl ldflags", + ldflags: ` build -ldflags="-s -w -X github.com/lima-vm/lima/pkg/version.Version=v0.15.1"`, + wantMajorVersion: "0", + wantFullVersion: "v0.15.1", + }, + { + name: "terraform ldflags", + ldflags: ` build -ldflags="-w -s -X 'github.com/hashicorp/terraform/version.Version=1.4.6' -X 'github.com/hashicorp/terraform/version.Prerelease='"`, + wantMajorVersion: "1", + wantFullVersion: "v1.4.6", + }, + { + name: "kube-apiserver ldflags", + ldflags: ` build -asmflags=all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes + build -buildmode=exe + build -compiler=gc + build -gcflags="all=-trimpath=/workspace/src/k8s.io/kubernetes/_output/dockerized/go/src/k8s.io/kubernetes " + build -ldflags="all=-X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.buildDate=2023-04-14T13:14:42Z' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.buildDate=2023-04-14T13:14:42Z' -X 'k8s.io/client-go/pkg/version.buildDate=2023-04-14T13:14:42Z' -X 'k8s.io/component-base/version.buildDate=2023-04-14T13:14:42Z' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitCommit=4c9411232e10168d7b050c49a1b59f6df9d7ea4b' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitCommit=4c9411232e10168d7b050c49a1b59f6df9d7ea4b' -X 'k8s.io/client-go/pkg/version.gitCommit=4c9411232e10168d7b050c49a1b59f6df9d7ea4b' -X 'k8s.io/component-base/version.gitCommit=4c9411232e10168d7b050c49a1b59f6df9d7ea4b' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitTreeState=clean' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitTreeState=clean' -X 'k8s.io/client-go/pkg/version.gitTreeState=clean' -X 'k8s.io/component-base/version.gitTreeState=clean' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitVersion=v1.27.1' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitVersion=v1.27.1' -X 'k8s.io/client-go/pkg/version.gitVersion=v1.27.1' -X 'k8s.io/component-base/version.gitVersion=v1.27.1' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMajor=1' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMajor=1' -X 'k8s.io/client-go/pkg/version.gitMajor=1' -X 'k8s.io/component-base/version.gitMajor=1' -X 'k8s.io/kubernetes/vendor/k8s.io/client-go/pkg/version.gitMinor=27' -X 'k8s.io/kubernetes/vendor/k8s.io/component-base/version.gitMinor=27' -X 'k8s.io/client-go/pkg/version.gitMinor=27' -X 'k8s.io/component-base/version.gitMinor=27' -s -w"`, + wantMajorVersion: "1", + wantFullVersion: "v1.27.1", + }, + { + name: "prometheus ldflags", + ldflags: ` build -ldflags="-X github.com/prometheus/common/version.Version=2.44.0 -X github.com/prometheus/common/version.Revision=1ac5131f698ebc60f13fe2727f89b115a41f6558 -X github.com/prometheus/common/version.Branch=HEAD -X github.com/prometheus/common/version.BuildUser=root@739e8181c5db -X github.com/prometheus/common/version.BuildDate=20230514-06:18:11 -extldflags '-static'" + build -tags=netgo,builtinassets,stringlabels`, + wantMajorVersion: "2", + wantFullVersion: "v2.44.0", + }, + { + name: "influxdb ldflags", + ldflags: ` build -ldflags="-s -w -X main.version=v2.7.1 -X main.commit=407fa622e9 -X main.date=2023-04-28T13:24:27Z -linkmode=external -extld=/musl/x86_64/bin/musl-gcc -extldflags '-fno-PIC -static-pie -Wl,-z,stack-size=8388608'" + build -tags=assets,sqlite_foreign_keys,sqlite_json,static_build,noasm`, + wantMajorVersion: "2", + wantFullVersion: "v2.7.1", + }, + { + name: "gitea ldflags", + ldflags: ` build -ldflags=" -X \"main.MakeVersion=GNU Make 4.1\" -X \"main.Version=1.19.3\" -X \"main.Tags=bindata sqlite sqlite_unlock_notify\" "`, + wantMajorVersion: "1", + wantFullVersion: "v1.19.3", + }, + { + name: "docker sbom cli ldflags", + ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/docker/sbom-cli-plugin/internal/version.version=0.6.1-SNAPSHOT-02cf1c8 -X github.com/docker/sbom-cli-plugin/internal/version.gitCommit=02cf1c888ad6662109ac6e3be618392514a56316 -X github.com/docker/sbom-cli-plugin/internal/version.gitDescription=v0.6.1-dirty "`, + wantMajorVersion: "0", + wantFullVersion: "v0.6.1-SNAPSHOT-02cf1c8", + }, + { + name: "docker scout ldflags", + ldflags: ` build -ldflags="-w -s -extldflags '-static' -X github.com/docker/scout-cli-plugin/internal.version=0.10.0 "`, + wantMajorVersion: "0", + wantFullVersion: "v0.10.0", + }, + { + name: "influx telegraf ldflags", + ldflags: ` build -ldflags="-w -s -X github.com/influxdata/telegraf/internal.Commit=a3a884a1 -X github.com/influxdata/telegraf/internal.Branch=HEAD -X github.com/influxdata/telegraf/internal.Version=1.26.2"`, + wantMajorVersion: "1", + wantFullVersion: "v1.26.2", + }, + { + name: "argocd ldflags", + ldflags: ` build -ldflags="-X github.com/argoproj/argo-cd/v2/common.version=2.7.2 -X github.com/argoproj/argo-cd/v2/common.buildDate=2023-05-12T14:06:49Z -X github.com/argoproj/argo-cd/v2/common.gitCommit=cbee7e6011407ed2d1066c482db74e97e0cc6bdb -X github.com/argoproj/argo-cd/v2/common.gitTreeState=clean -X github.com/argoproj/argo-cd/v2/common.kubectlVersion=v0.24.2 -extldflags=\"-static\""`, + wantMajorVersion: "2", + wantFullVersion: "v2.7.2", + }, + { + name: "kustomize ldflags", + ldflags: ` build -ldflags="-s -X sigs.k8s.io/kustomize/api/provenance.version=kustomize/v4.5.7 -X sigs.k8s.io/kustomize/api/provenance.gitCommit=56d82a8378dfc8dc3b3b1085e5a6e67b82966bd7 -X sigs.k8s.io/kustomize/api/provenance.buildDate=2022-08-02T16:35:54Z "`, + wantMajorVersion: "4", + wantFullVersion: "v4.5.7", + }, + ////////////////////////////////////////////////////////////////// + // negative cases + { + name: "hugo ldflags", + ldflags: ` build -ldflags="-s -w -X github.com/gohugoio/hugo/common/hugo.vendorInfo=gohugoio"`, + }, + { + name: "ghostunnel ldflags", + ldflags: ` build -ldflags="-X main.version=77d9aaa"`, + }, + { + name: "opa ldflags", + ldflags: `build -ldflags=" -X github.com/open-policy-agent/opa/version.Hostname=9549178459bc"`, + }, + /////////////////////////////////////////////////////////////////// + // trickier cases + { + name: "macvlan plugin for cri-o ldflags", + ldflags: ` build -ldflags="-extldflags -static -X github.com/containernetworking/plugins/pkg/utils/buildversion.BuildVersion=v1.2.0"`, + wantMajorVersion: "1", + wantFullVersion: "v1.2.0", + }, + { + name: "coder ldflags", + ldflags: ` build -ldflags="-s -w -X 'github.com/coder/coder/buildinfo.tag=0.23.4'"`, + wantMajorVersion: "0", + wantFullVersion: "v0.23.4", + }, + /////////////////////////////////////////////////////////////////// + // don't know how to handle these... yet + //{ + // // package name: pkgName: "github.com/krakendio/krakend-ce/v2", + // name: "krakenD ldflags", + // ldflags: ` build -ldflags="-X github.com/luraproject/lura/v2/core.KrakendVersion=2.3.2 -X github.com/luraproject/lura/v2/core.GoVersion=1.20.4 -X github.com/luraproject/lura/v2/core.GlibcVersion=GLIBC-2.31_(debian-11) "`, + // wantMajorVersion: "2.3.2", + // wantFullVersion: "v2.3.2", + //}, + //{ + // // package name: pkgName: "github.com/krakendio/krakend-ce/v2", + // name: "krakenD ldflags -- answer embedded in the middle", + // ldflags: ` build -ldflags=" -X github.com/luraproject/lura/v2/core.GoVersion=1.20.4 -X github.com/luraproject/lura/v2/core.KrakendVersion=2.3.2 -X github.com/luraproject/lura/v2/core.GlibcVersion=GLIBC-2.31_(debian-11) "`, + // wantMajorVersion: "2.3.2", + // wantFullVersion: "v2.3.2", + //}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + gotMajorVersion, gotFullVersion := extractVersionFromLDFlags(tt.ldflags) + assert.Equal(t, tt.wantMajorVersion, gotMajorVersion, "unexpected major version") + assert.Equal(t, tt.wantFullVersion, gotFullVersion, "unexpected full version") + }) + } +} From 087a6356b9b1707ef388e413c86e52f0524e2d02 Mon Sep 17 00:00:00 2001 From: Idan Frimark <40820488+FrimIdan@users.noreply.github.com> Date: Tue, 23 May 2023 17:32:12 +0300 Subject: [PATCH 08/21] chore: return both failures when failed to retrieve an image with a scheme (#1801) Signed-off-by: Idan Frimark --- syft/source/source.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/syft/source/source.go b/syft/source/source.go index 8433ed31613..edbd86203d1 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -216,13 +216,27 @@ func getImageWithRetryStrategy(in Input, registryOptions *image.RegistryOptions) // We need to determine the image source again, such that this determination // doesn't take scheme parsing into account. in.ImageSource = image.DetermineDefaultImagePullSource(in.UserInput) - img, err = stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...) + img, userInputErr := stereoscope.GetImageFromSource(ctx, in.UserInput, in.ImageSource, opts...) cleanup = func() { if err := img.Cleanup(); err != nil { log.Warnf("unable to cleanup image=%q: %w", in.UserInput, err) } } - return img, cleanup, err + if userInputErr != nil { + // Image retrieval failed on both tries, we will want to return both errors. + return nil, nil, fmt.Errorf( + "scheme %q specified; "+ + "image retrieval using scheme parsing (%s) was unsuccessful: %v; "+ + "image retrieval without scheme parsing (%s) was unsuccessful: %v", + scheme, + in.Location, + err, + in.UserInput, + userInputErr, + ) + } + + return img, cleanup, nil } func generateDirectorySource(fs afero.Fs, in Input) (*Source, func(), error) { From 4ac8fdf6df0da4cd6f76820dbec9f490ee56bcba Mon Sep 17 00:00:00 2001 From: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Date: Tue, 23 May 2023 12:58:49 -0400 Subject: [PATCH 09/21] fix: add panic recovery for license parse (#1839) * fix: add panic recovery for license parse --------- Signed-off-by: Christopher Phillips --- syft/license/license.go | 16 +++++++++++++--- syft/pkg/license.go | 2 +- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/syft/license/license.go b/syft/license/license.go index e9dd93c6235..c2b9d260295 100644 --- a/syft/license/license.go +++ b/syft/license/license.go @@ -3,6 +3,7 @@ package license import ( "fmt" + "runtime/debug" "github.com/github/go-spdx/v2/spdxexp" @@ -16,19 +17,28 @@ const ( Concluded Type = "concluded" ) -func ParseExpression(expression string) (string, error) { +func ParseExpression(expression string) (ex string, err error) { + // https://github.com/anchore/syft/issues/1837 + // The current spdx library can panic when parsing some expressions + // This is a temporary fix to recover and patch until we can investigate and contribute + // a fix to the upstream github library + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("recovered from panic while parsing license expression at: \n%s", string(debug.Stack())) + } + }() + licenseID, exists := spdxlicense.ID(expression) if exists { return licenseID, nil } - // If it doesn't exist initially in the SPDX list it might be a more complex expression // ignored variable is any invalid expressions // TODO: contribute to spdxexp to expose deprecated license IDs // https://github.com/anchore/syft/issues/1814 valid, _ := spdxexp.ValidateLicenses([]string{expression}) if !valid { - return "", fmt.Errorf("failed to validate spdx expression: %s", expression) + return "", fmt.Errorf("invalid SPDX expression: %s", expression) } return expression, nil diff --git a/syft/pkg/license.go b/syft/pkg/license.go index 0e0a3f04b99..8278ba7bd90 100644 --- a/syft/pkg/license.go +++ b/syft/pkg/license.go @@ -62,7 +62,7 @@ func (l Licenses) Swap(i, j int) { func NewLicense(value string) License { spdxExpression, err := license.ParseExpression(value) if err != nil { - log.Trace("unable to parse license expression: %w", err) + log.Trace("unable to parse license expression for %q: %w", value, err) } return License{ From 4bf17a94b9f49031fda5b15f8e32b1d960afd168 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 24 May 2023 11:40:11 -0400 Subject: [PATCH 10/21] chore(deps): bump github.com/go-git/go-git/v5 from 5.6.1 to 5.7.0 (#1843) Bumps [github.com/go-git/go-git/v5](https://github.com/go-git/go-git) from 5.6.1 to 5.7.0. - [Release notes](https://github.com/go-git/go-git/releases) - [Commits](https://github.com/go-git/go-git/compare/v5.6.1...v5.7.0) --- updated-dependencies: - dependency-name: github.com/go-git/go-git/v5 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 15 +++++++------- go.sum | 62 +++++++++++++++++++++++----------------------------------- 2 files changed, 32 insertions(+), 45 deletions(-) diff --git a/go.mod b/go.mod index fe106fe4af3..ea46c730b23 100644 --- a/go.mod +++ b/go.mod @@ -58,7 +58,7 @@ require ( github.com/docker/docker v24.0.1+incompatible github.com/github/go-spdx/v2 v2.1.2 github.com/go-git/go-billy/v5 v5.4.1 - github.com/go-git/go-git/v5 v5.6.1 + github.com/go-git/go-git/v5 v5.7.0 github.com/google/go-containerregistry v0.15.2 github.com/google/licensecheck v0.3.1 github.com/invopop/jsonschema v0.7.0 @@ -77,11 +77,11 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.0 // indirect github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 // indirect github.com/acomagu/bufpipe v1.0.4 // indirect github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 // indirect github.com/becheran/wildmatch-go v1.0.0 // indirect - github.com/cloudflare/circl v1.1.0 // indirect + github.com/cloudflare/circl v1.3.3 // indirect github.com/containerd/containerd v1.7.0 // indirect github.com/containerd/stargz-snapshotter/estargz v0.14.3 // indirect github.com/davecgh/go-spew v1.1.1 // indirect @@ -94,16 +94,17 @@ require ( github.com/emirpasic/gods v1.18.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.0 // indirect - github.com/go-git/gcfg v1.5.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-restruct/restruct v1.2.0-alpha // indirect github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/huandu/xstrings v1.3.3 // indirect github.com/iancoleman/orderedmap v0.0.0-20190318233801-ac98e3ecb4b0 // indirect - github.com/imdario/mergo v0.3.13 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect @@ -127,7 +128,7 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect - github.com/skeema/knownhosts v1.1.0 // indirect + github.com/skeema/knownhosts v1.1.1 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/stretchr/objx v0.5.0 // indirect @@ -168,7 +169,7 @@ require ( // go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption github.com/andybalholm/brotli v1.0.4 // indirect github.com/pkg/errors v0.9.1 // indirect - golang.org/x/crypto v0.6.0 // indirect + golang.org/x/crypto v0.9.0 // indirect ) retract ( diff --git a/go.sum b/go.sum index 6d3d53a5881..9bfc68a1526 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= -github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903 h1:ZK3C5DtzV2nVAQTx5S5jQvMeDqWtD1By5mOoyY/xJek= +github.com/ProtonMail/go-crypto v0.0.0-20230518184743-7afd39499903/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d/go.mod h1:asat636LX7Bqt5lYEZ27JNDcqxfjdBQuJ/MM4CN/Lzo= github.com/acobaugh/osrelease v0.1.0 h1:Yb59HQDGGNhCj4suHaFQQfBps5wyoKLSSX/J/+UifRE= @@ -105,7 +105,6 @@ github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= -github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= @@ -113,7 +112,6 @@ github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/becheran/wildmatch-go v1.0.0 h1:mE3dGGkTmpKtT4Z+88t8RStG40yN9T+kFEGj2PZFSzA= github.com/becheran/wildmatch-go v1.0.0/go.mod h1:gbMvj0NtVdJ15Mg/mH9uxk2R1QCistMyU7d9KFzroX4= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= @@ -135,8 +133,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY= github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -179,6 +178,7 @@ github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5/go.mod h1:qssHWj6 github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 h1:RIB4cRk+lBqKK3Oy0r2gRX4ui7tuhiZq2SuTtTCi0/0= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -211,16 +211,13 @@ github.com/github/go-spdx/v2 v2.1.2 h1:p+Tv0yMgcuO0/vnMe9Qh4tmUgYhI6AsLVlakZ/Sx+ github.com/github/go-spdx/v2 v2.1.2/go.mod h1:hMCrsFgT0QnCwn7G8gxy/MxMpy67WgZrwFeISTn0o6w= github.com/glebarez/go-sqlite v1.20.3 h1:89BkqGOXR9oRmG58ZrzgoY/Fhy5x0M+/WV48U5zVrZ4= github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= -github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= -github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= -github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= -github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= -github.com/go-git/go-git-fixtures/v4 v4.3.1 h1:y5z6dd3qi8Hl+stezc8p3JxDkoTRqMAlKnXHuzrfjTQ= -github.com/go-git/go-git-fixtures/v4 v4.3.1/go.mod h1:8LHG1a3SRW71ettAD/jW13h8c6AqjVSeL11RAdgaqpo= -github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= -github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20230305113008-0c11038e723f h1:Pz0DHeFij3XFhoBRGUDPzSJ+w2UcK5/0JvF8DRI58r8= +github.com/go-git/go-git/v5 v5.7.0 h1:t9AudWVLmqzlo+4bqdf7GY+46SUuRsx59SboFxkq2aE= +github.com/go-git/go-git/v5 v5.7.0/go.mod h1:coJHKEOk5kUClpsNlXrUvPrDxY3w3gjHvhcZd8Fodw8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -242,6 +239,7 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -373,8 +371,8 @@ github.com/iancoleman/strcase v0.2.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47 github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= -github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= @@ -382,7 +380,6 @@ github.com/invopop/jsonschema v0.7.0 h1:2vgQcBz1n256N+FpX3Jq7Y17AjYt46Ig3zIWyy77 github.com/invopop/jsonschema v0.7.0/go.mod h1:O9uiLokuu0+MGFlyiaqtWxwqJm41/+8Nj0lD7A36YH0= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -476,7 +473,6 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/mmcloughlin/avo v0.5.0/go.mod h1:ChHFdoV7ql95Wi7vuq2YT1bwCJqiWdZrQ1im3VujLYM= github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -559,8 +555,8 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/skeema/knownhosts v1.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= -github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= +github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= +github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= github.com/spdx/tools-golang v0.5.0 h1:/fqihV2Jna7fmow65dHpgKNsilgLK7ICpd2tkCnPEyY= @@ -663,7 +659,6 @@ go.uber.org/goleak v1.1.10 h1:z+mqJhf6ss6BSfSM671tgKyZBFPTTJM+HLxnhPC3wu0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/arch v0.1.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190228161510-8dd112bcdc25/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -678,14 +673,12 @@ golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= +golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -726,7 +719,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.0/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -774,11 +767,9 @@ golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20210813160813-60bc85c4be6d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -884,25 +875,22 @@ golang.org/x/sys v0.0.0-20211205182925-97ca703d548d/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220906165534-d0df966e6959/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -916,6 +904,7 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -979,7 +968,7 @@ golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y= golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -1143,7 +1132,6 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= @@ -1163,7 +1151,6 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= @@ -1199,7 +1186,6 @@ modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= modernc.org/z v1.7.3 h1:zDJf6iHjrnB+WRD88stbXokugjyc0/pB91ri1gO6LZY= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8= From 07e76907f67ec417a6706135bb563974a1e36bf0 Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Wed, 24 May 2023 17:06:38 -0400 Subject: [PATCH 11/21] Migrate location-related structs to the file package (#1751) * migrate location structs to file package Signed-off-by: Alex Goodman * replace source.Location refs with file package call Signed-off-by: Alex Goodman * fix linting Signed-off-by: Alex Goodman * remove hardlink test for file based catalogers Signed-off-by: Alex Goodman * remove hardlink test for all-regular-files testing Signed-off-by: Alex Goodman * migrate file resolver implementations to separate package Signed-off-by: Alex Goodman * fix linting Signed-off-by: Alex Goodman * [wip] migrate resolvers to internal Signed-off-by: Alex Goodman * migrate resolvers to syft/internal Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman Signed-off-by: <> --- DEVELOPING.md | 6 +- cmd/syft/cli/eventloop/tasks.go | 17 +- internal/licenses/parser.go | 4 +- syft/event/parsers/parsers.go | 6 +- .../filecontent/cataloger.go} | 20 +- .../cataloger/filecontent/cataloger_test.go | 80 +++++++ .../filecontent}/test-fixtures/a-path.txt | 0 .../test-fixtures/another-path.txt | 0 .../test-fixtures/last/empty/empty | 0 .../filecontent}/test-fixtures/last/path.txt | 0 syft/file/cataloger/filedigest/cataloger.go | 109 +++++++++ .../filedigest/cataloger_test.go} | 50 ++-- .../image-file-type-mix/Dockerfile | 2 +- .../image-file-type-mix/file-1.txt | 0 .../test-fixtures/last/empty/empty} | 0 .../filedigest/test-fixtures/last/path.txt | 1 + .../filemetadata/cataloger.go} | 35 ++- .../filemetadata/cataloger_test.go} | 77 +++--- .../image-file-type-mix/Dockerfile | 13 + .../image-file-type-mix}/file-1.txt | 0 .../internal}/all_regular_files.go | 10 +- .../internal}/all_regular_files_test.go | 28 +-- .../image-file-type-mix/Dockerfile | 13 + .../image-file-type-mix/file-1.txt | 1 + .../symlinked-root/nested/link-root | 0 .../symlinked-root/real-root/file1.txt | 0 .../symlinked-root/real-root/nested/file2.txt | 0 .../real-root/nested/linked-file1.txt | 0 .../secrets/cataloger.go} | 27 ++- .../secrets/cataloger_test.go} | 46 ++-- .../secrets}/generate_search_patterns.go | 2 +- .../secrets}/generate_search_patterns_test.go | 2 +- .../secrets}/newline_counter.go | 2 +- .../secrets}/newline_counter_test.go | 2 +- .../secrets_search_by_line_strategy.go | 18 +- .../test-fixtures/secrets/default/api-key.txt | 0 .../test-fixtures/secrets/default/aws.env | 0 .../test-fixtures/secrets/default/aws.ini | 0 .../secrets/default/docker-config.json | 0 .../secrets/default/not-docker-config.json | 0 .../default/private-key-false-positive.pem | 0 .../secrets/default/private-key-openssl.pem | 0 .../secrets/default/private-key.pem | 0 .../secrets/default/private-keys.pem | 0 .../test-fixtures/secrets/multiple.txt | 0 .../secrets}/test-fixtures/secrets/simple.txt | 0 syft/file/contents_cataloger_test.go | 80 ------- syft/{source => file}/coordinate_set.go | 2 +- syft/{source => file}/coordinate_set_test.go | 2 +- syft/{source => file}/coordinates.go | 2 +- syft/file/digest.go | 70 ++++++ syft/file/digest_cataloger.go | 140 ----------- syft/{source => file}/location.go | 12 +- syft/{source => file}/location_read_closer.go | 2 +- syft/{source => file}/location_set.go | 2 +- syft/{source => file}/location_set_test.go | 2 +- syft/{source => file}/location_test.go | 2 +- syft/{source => file}/locations.go | 2 +- syft/file/metadata.go | 5 + syft/{source => file}/mock_resolver.go | 16 +- .../file_resolver.go => file/resolver.go} | 36 ++- ...eoscope-fixture-image-file-type-mix.golden | Bin 1731072 -> 0 bytes .../common/cyclonedxhelpers/component.go | 10 +- .../common/cyclonedxhelpers/component_test.go | 6 +- .../common/spdxhelpers/source_info_test.go | 8 +- .../common/spdxhelpers/to_format_model.go | 7 +- .../spdxhelpers/to_format_model_test.go | 18 +- .../common/spdxhelpers/to_syft_model.go | 16 +- .../common/spdxhelpers/to_syft_model_test.go | 3 +- syft/formats/github/encoder_test.go | 13 +- syft/formats/internal/testutils/utils.go | 31 +-- syft/formats/syftjson/encoder_test.go | 28 +-- syft/formats/syftjson/model/file.go | 3 +- syft/formats/syftjson/model/package.go | 32 +-- syft/formats/syftjson/model/secrets.go | 3 +- syft/formats/syftjson/to_format_model.go | 8 +- syft/formats/syftjson/to_format_model_test.go | 43 ++-- syft/formats/syftjson/to_syft_model.go | 10 +- syft/formats/syftjson/to_syft_model_test.go | 14 +- .../container_image_all_layers.go} | 81 +++---- .../container_image_all_layers_test.go} | 133 ++++++----- .../fileresolver/container_image_squash.go} | 71 +++--- .../container_image_squash_test.go} | 185 +++++--------- syft/internal/fileresolver/deferred.go | 98 ++++++++ .../fileresolver/deferred_test.go} | 8 +- .../fileresolver/directory.go} | 87 +++---- .../fileresolver}/directory_indexer.go | 20 +- .../fileresolver}/directory_indexer_test.go | 6 +- .../fileresolver/directory_test.go} | 226 +++++++----------- .../fileresolver/directory_windows_test.go} | 2 +- syft/internal/fileresolver/empty.go | 47 ++++ .../fileresolver/excluding_file.go} | 40 ++-- .../fileresolver/excluding_file_test.go} | 54 +++-- .../fileresolver/file_metadata_by_location.go | 15 ++ .../generate-tar-fixture-from-source-dir.sh | 9 + .../image-duplicate-path/Dockerfile | 0 .../image-duplicate-path/file-1.txt | 0 .../image-duplicate-path/file-2.txt | 0 .../image-files-deleted/Dockerfile | 0 .../image-files-deleted/file-1.txt | 0 .../image-files-deleted/file-3.txt | 0 .../image-files-deleted/target/file-2.txt | 0 .../test-fixtures/image-simple/Dockerfile | 6 + .../test-fixtures/image-simple/file-1.txt | 1 + .../test-fixtures/image-simple/file-2.txt | 1 + .../target/really/nested/file-3.txt | 2 + .../test-fixtures/image-symlinks/Dockerfile | 0 .../test-fixtures/image-symlinks/file-1.txt | 1 + .../test-fixtures/image-symlinks/file-2.txt | 0 .../image-symlinks/nested/nested/file-3.txt | 0 .../image-symlinks/new-file-2.txt | 0 .../image-symlinks/new-file-4.txt | 0 .../image-symlinks/parent/file-4.txt | 0 .../test-fixtures/path-detected-2/.vimrc | 1 + .../test-fixtures/path-detected-2/empty} | 0 .../test-fixtures/path-detected/.vimrc | 1 + .../test-fixtures/path-detected/empty | 0 .../symlinked-root/nested/link-root | 0 .../symlinked-root/real-root/file1.txt | 0 .../symlinked-root/real-root/nested/file2.txt | 0 .../real-root/nested/linked-file1.txt | 0 .../test-fixtures/symlinks-base/bar | 0 .../test-fixtures/symlinks-base/base | 0 .../test-fixtures/symlinks-base/baz | 0 .../test-fixtures/symlinks-base/chain | 0 .../test-fixtures/symlinks-base/foo | 0 .../test-fixtures/symlinks-base/sub/item | 0 .../test-fixtures/symlinks-base/sub/link | 0 .../file-1.txt | 0 .../file-2.txt | 0 .../file-3.txt | 0 .../link-1 | 0 .../link-2 | 0 .../link-dead | 0 .../link-indirect | 0 .../link-within | 0 .../parent-link | 0 .../parent/file-4.txt | 0 .../test-fixtures/symlinks-loop/README.md | 0 .../test-fixtures/symlinks-loop/block/loop0 | 0 .../symlinks-loop/devices/loop0/file.target | 0 .../symlinks-loop/devices/loop0/subsystem | 0 .../outside/link_to_readme | 0 .../root/link_to_link_to_readme | 0 .../symlinks-multiple-roots/root/readme | 0 .../symlinks-prune-indexing/before-path | 0 .../symlinks-prune-indexing/c-file.txt | 0 .../symlinks-prune-indexing/c-path | 0 .../path/1/2/3/4/dont-index-me-twice.txt | 0 .../5/6/7/8/dont-index-me-twice-either.txt | 0 .../symlinks-prune-indexing/path/file.txt | 0 .../link_to_link_to_new_readme | 0 .../symlinks-simple/link_to_new_readme | 0 .../test-fixtures/symlinks-simple/readme | 0 .../outside_root/link_target/place | 1 + .../system_paths/target/dev/place | 1 + .../system_paths/target/home/place | 1 + .../system_paths/target/link/a-symlink | 0 .../system_paths/target/proc/place | 1 + .../system_paths/target/sys/place | 1 + .../fileresolver/unindexed_directory.go} | 77 +++--- .../fileresolver/unindexed_directory_test.go} | 182 ++++++++------ syft/linux/identify_release.go | 4 +- syft/pkg/binary_metadata.go | 6 +- syft/pkg/catalog_test.go | 82 +++---- syft/pkg/cataloger.go | 4 +- syft/pkg/cataloger/alpm/cataloger_test.go | 5 +- syft/pkg/cataloger/alpm/package.go | 6 +- syft/pkg/cataloger/alpm/parse_alpm_db.go | 5 +- syft/pkg/cataloger/apkdb/package.go | 6 +- syft/pkg/cataloger/apkdb/parse_apk_db.go | 9 +- syft/pkg/cataloger/apkdb/parse_apk_db_test.go | 23 +- syft/pkg/cataloger/binary/cataloger.go | 6 +- syft/pkg/cataloger/binary/cataloger_test.go | 41 ++-- syft/pkg/cataloger/binary/classifier.go | 18 +- syft/pkg/cataloger/binary/classifier_test.go | 10 +- syft/pkg/cataloger/binary/package.go | 6 +- syft/pkg/cataloger/catalog.go | 10 +- syft/pkg/cataloger/catalog_test.go | 8 +- syft/pkg/cataloger/cataloger_test.go | 4 +- syft/pkg/cataloger/cpp/package.go | 10 +- syft/pkg/cataloger/cpp/parse_conanfile.go | 4 +- .../pkg/cataloger/cpp/parse_conanfile_test.go | 4 +- syft/pkg/cataloger/cpp/parse_conanlock.go | 4 +- .../pkg/cataloger/cpp/parse_conanlock_test.go | 4 +- syft/pkg/cataloger/dart/package.go | 6 +- syft/pkg/cataloger/dart/parse_pubspec_lock.go | 4 +- .../cataloger/dart/parse_pubspec_lock_test.go | 4 +- syft/pkg/cataloger/deb/cataloger_test.go | 13 +- syft/pkg/cataloger/deb/package.go | 20 +- syft/pkg/cataloger/deb/parse_dpkg_db.go | 4 +- syft/pkg/cataloger/deb/parse_dpkg_db_test.go | 3 +- syft/pkg/cataloger/dotnet/package.go | 6 +- .../pkg/cataloger/dotnet/parse_dotnet_deps.go | 4 +- .../dotnet/parse_dotnet_deps_test.go | 4 +- syft/pkg/cataloger/elixir/package.go | 6 +- syft/pkg/cataloger/elixir/parse_mix_lock.go | 4 +- .../cataloger/elixir/parse_mix_lock_test.go | 4 +- syft/pkg/cataloger/erlang/package.go | 6 +- syft/pkg/cataloger/erlang/parse_rebar_lock.go | 4 +- .../cataloger/erlang/parse_rebar_lock_test.go | 4 +- syft/pkg/cataloger/generic/cataloger.go | 20 +- syft/pkg/cataloger/generic/cataloger_test.go | 12 +- syft/pkg/cataloger/generic/parser.go | 4 +- syft/pkg/cataloger/golang/cataloger.go | 4 +- syft/pkg/cataloger/golang/licenses.go | 21 +- syft/pkg/cataloger/golang/licenses_test.go | 23 +- syft/pkg/cataloger/golang/package.go | 6 +- syft/pkg/cataloger/golang/parse_go_binary.go | 8 +- .../cataloger/golang/parse_go_binary_test.go | 75 +++--- syft/pkg/cataloger/golang/parse_go_mod.go | 18 +- .../pkg/cataloger/golang/parse_go_mod_test.go | 20 +- syft/pkg/cataloger/haskell/package.go | 6 +- .../cataloger/haskell/parse_cabal_freeze.go | 4 +- .../haskell/parse_cabal_freeze_test.go | 4 +- .../pkg/cataloger/haskell/parse_stack_lock.go | 4 +- .../haskell/parse_stack_lock_test.go | 4 +- .../pkg/cataloger/haskell/parse_stack_yaml.go | 4 +- .../haskell/parse_stack_yaml_test.go | 4 +- .../internal/pkgtest/observing_resolver.go | 32 +-- .../internal/pkgtest/test_generic_parser.go | 29 +-- syft/pkg/cataloger/java/archive_parser.go | 45 ++-- .../pkg/cataloger/java/archive_parser_test.go | 18 +- .../java/graalvm_native_image_cataloger.go | 4 +- .../cataloger/java/parse_gradle_lockfile.go | 6 +- .../java/parse_gradle_lockfile_test.go | 4 +- syft/pkg/cataloger/java/parse_pom_xml.go | 8 +- syft/pkg/cataloger/java/parse_pom_xml_test.go | 6 +- .../java/tar_wrapped_archive_parser.go | 10 +- .../java/tar_wrapped_archive_parser_test.go | 6 +- .../java/zip_wrapped_archive_parser.go | 8 +- .../java/zip_wrapped_archive_parser_test.go | 6 +- .../cataloger/javascript/cataloger_test.go | 8 +- syft/pkg/cataloger/javascript/package.go | 26 +- .../javascript/parse_package_json.go | 4 +- .../javascript/parse_package_json_test.go | 18 +- .../javascript/parse_package_lock.go | 4 +- .../javascript/parse_package_lock_test.go | 30 +-- .../cataloger/javascript/parse_pnpm_lock.go | 4 +- .../javascript/parse_pnpm_lock_test.go | 6 +- .../cataloger/javascript/parse_yarn_lock.go | 4 +- .../javascript/parse_yarn_lock_test.go | 6 +- syft/pkg/cataloger/kernel/cataloger.go | 4 +- syft/pkg/cataloger/kernel/cataloger_test.go | 9 +- syft/pkg/cataloger/kernel/package.go | 10 +- .../kernel/parse_linux_kernel_file.go | 4 +- .../kernel/parse_linux_kernel_module_file.go | 4 +- syft/pkg/cataloger/nix/cataloger.go | 10 +- syft/pkg/cataloger/nix/cataloger_test.go | 4 +- syft/pkg/cataloger/nix/package.go | 6 +- syft/pkg/cataloger/php/package.go | 6 +- syft/pkg/cataloger/php/parse_composer_lock.go | 4 +- .../cataloger/php/parse_composer_lock_test.go | 8 +- .../pkg/cataloger/php/parse_installed_json.go | 4 +- .../php/parse_installed_json_test.go | 4 +- syft/pkg/cataloger/portage/cataloger_test.go | 9 +- .../portage/parse_portage_contents.go | 11 +- syft/pkg/cataloger/python/cataloger_test.go | 18 +- syft/pkg/cataloger/python/package.go | 18 +- .../cataloger/python/parse_pipfile_lock.go | 4 +- .../python/parse_pipfile_lock_test.go | 4 +- .../pkg/cataloger/python/parse_poetry_lock.go | 4 +- .../python/parse_poetry_lock_test.go | 4 +- .../cataloger/python/parse_requirements.go | 4 +- .../python/parse_requirements_test.go | 4 +- syft/pkg/cataloger/python/parse_setup.go | 4 +- syft/pkg/cataloger/python/parse_setup_test.go | 4 +- syft/pkg/cataloger/python/parse_wheel_egg.go | 16 +- .../python/parse_wheel_egg_metadata.go | 10 +- .../python/parse_wheel_egg_metadata_test.go | 8 +- syft/pkg/cataloger/r/cataloger_test.go | 6 +- syft/pkg/cataloger/r/package.go | 8 +- syft/pkg/cataloger/r/parse_description.go | 6 +- .../pkg/cataloger/r/parse_description_test.go | 6 +- syft/pkg/cataloger/rpm/package.go | 8 +- syft/pkg/cataloger/rpm/parse_rpm.go | 3 +- syft/pkg/cataloger/rpm/parse_rpm_db.go | 5 +- syft/pkg/cataloger/rpm/parse_rpm_db_test.go | 33 ++- syft/pkg/cataloger/rpm/parse_rpm_manifest.go | 6 +- .../cataloger/rpm/parse_rpm_manifest_test.go | 12 +- syft/pkg/cataloger/rpm/parse_rpm_test.go | 9 +- syft/pkg/cataloger/ruby/package.go | 10 +- syft/pkg/cataloger/ruby/parse_gemfile_lock.go | 4 +- .../cataloger/ruby/parse_gemfile_lock_test.go | 4 +- syft/pkg/cataloger/ruby/parse_gemspec.go | 4 +- syft/pkg/cataloger/ruby/parse_gemspec_test.go | 6 +- syft/pkg/cataloger/rust/cataloger_test.go | 6 +- syft/pkg/cataloger/rust/package.go | 12 +- syft/pkg/cataloger/rust/parse_audit_binary.go | 4 +- syft/pkg/cataloger/rust/parse_cargo_lock.go | 4 +- .../cataloger/rust/parse_cargo_lock_test.go | 4 +- syft/pkg/cataloger/sbom/cataloger.go | 6 +- syft/pkg/cataloger/sbom/cataloger_test.go | 44 ++-- syft/pkg/cataloger/swift/package.go | 6 +- .../pkg/cataloger/swift/parse_podfile_lock.go | 4 +- .../swift/parse_podfile_lock_test.go | 4 +- syft/pkg/license.go | 12 +- syft/pkg/license_set_test.go | 29 +-- syft/pkg/license_test.go | 34 +-- syft/pkg/package.go | 27 +-- syft/pkg/package_test.go | 34 +-- .../relationships_by_file_ownership_test.go | 38 +-- syft/pkg/relationships_evident_by_test.go | 22 +- syft/sbom/sbom.go | 24 +- syft/source/deferred_resolver.go | 108 --------- syft/source/deprecated.go | 119 +++++++++ syft/source/empty_resolver.go | 45 ---- syft/source/file_details.go | 21 -- syft/source/file_details_win.go | 13 - syft/source/file_metadata.go | 17 -- syft/source/source.go | 28 ++- syft/source/source_test.go | 48 ++-- .../system_paths/target/link/a-symlink/place | 1 + 313 files changed, 2317 insertions(+), 2192 deletions(-) rename syft/file/{contents_cataloger.go => cataloger/filecontent/cataloger.go} (75%) create mode 100644 syft/file/cataloger/filecontent/cataloger_test.go rename syft/file/{ => cataloger/filecontent}/test-fixtures/a-path.txt (100%) rename syft/file/{ => cataloger/filecontent}/test-fixtures/another-path.txt (100%) rename syft/file/{ => cataloger/filecontent}/test-fixtures/last/empty/empty (100%) rename syft/file/{ => cataloger/filecontent}/test-fixtures/last/path.txt (100%) create mode 100644 syft/file/cataloger/filedigest/cataloger.go rename syft/file/{digest_cataloger_test.go => cataloger/filedigest/cataloger_test.go} (74%) rename syft/file/{ => cataloger/filedigest}/test-fixtures/image-file-type-mix/Dockerfile (81%) rename syft/file/{ => cataloger/filedigest}/test-fixtures/image-file-type-mix/file-1.txt (100%) rename syft/{source/test-fixtures/symlinks-base/base => file/cataloger/filedigest/test-fixtures/last/empty/empty} (100%) create mode 100644 syft/file/cataloger/filedigest/test-fixtures/last/path.txt rename syft/file/{metadata_cataloger.go => cataloger/filemetadata/cataloger.go} (59%) rename syft/file/{metadata_cataloger_test.go => cataloger/filemetadata/cataloger_test.go} (62%) create mode 100644 syft/file/cataloger/filemetadata/test-fixtures/image-file-type-mix/Dockerfile rename syft/{source/test-fixtures/image-symlinks => file/cataloger/filemetadata/test-fixtures/image-file-type-mix}/file-1.txt (100%) rename syft/file/{ => cataloger/internal}/all_regular_files.go (70%) rename syft/file/{ => cataloger/internal}/all_regular_files_test.go (78%) create mode 100644 syft/file/cataloger/internal/test-fixtures/image-file-type-mix/Dockerfile create mode 100644 syft/file/cataloger/internal/test-fixtures/image-file-type-mix/file-1.txt rename syft/file/{ => cataloger/internal}/test-fixtures/symlinked-root/nested/link-root (100%) rename syft/file/{ => cataloger/internal}/test-fixtures/symlinked-root/real-root/file1.txt (100%) rename syft/file/{ => cataloger/internal}/test-fixtures/symlinked-root/real-root/nested/file2.txt (100%) rename syft/file/{ => cataloger/internal}/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt (100%) rename syft/file/{secrets_cataloger.go => cataloger/secrets/cataloger.go} (82%) rename syft/file/{secrets_cataloger_test.go => cataloger/secrets/cataloger_test.go} (92%) rename syft/file/{ => cataloger/secrets}/generate_search_patterns.go (98%) rename syft/file/{ => cataloger/secrets}/generate_search_patterns_test.go (99%) rename syft/file/{ => cataloger/secrets}/newline_counter.go (97%) rename syft/file/{ => cataloger/secrets}/newline_counter_test.go (98%) rename syft/file/{ => cataloger/secrets}/secrets_search_by_line_strategy.go (86%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/api-key.txt (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/aws.env (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/aws.ini (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/docker-config.json (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/not-docker-config.json (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/private-key-false-positive.pem (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/private-key-openssl.pem (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/private-key.pem (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/default/private-keys.pem (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/multiple.txt (100%) rename syft/file/{ => cataloger/secrets}/test-fixtures/secrets/simple.txt (100%) delete mode 100644 syft/file/contents_cataloger_test.go rename syft/{source => file}/coordinate_set.go (99%) rename syft/{source => file}/coordinate_set_test.go (99%) rename syft/{source => file}/coordinates.go (98%) delete mode 100644 syft/file/digest_cataloger.go rename syft/{source => file}/location.go (94%) rename syft/{source => file}/location_read_closer.go (94%) rename syft/{source => file}/location_set.go (99%) rename syft/{source => file}/location_set_test.go (99%) rename syft/{source => file}/location_test.go (98%) rename syft/{source => file}/locations.go (96%) create mode 100644 syft/file/metadata.go rename syft/{source => file}/mock_resolver.go (94%) rename syft/{source/file_resolver.go => file/resolver.go} (75%) delete mode 100644 syft/file/test-fixtures/snapshot/stereoscope-fixture-image-file-type-mix.golden rename syft/{source/image_all_layers_resolver.go => internal/fileresolver/container_image_all_layers.go} (65%) rename syft/{source/image_all_layers_resolver_test.go => internal/fileresolver/container_image_all_layers_test.go} (76%) rename syft/{source/image_squash_resolver.go => internal/fileresolver/container_image_squash.go} (66%) rename syft/{source/image_squash_resolver_test.go => internal/fileresolver/container_image_squash_test.go} (72%) create mode 100644 syft/internal/fileresolver/deferred.go rename syft/{source/deferred_resolver_test.go => internal/fileresolver/deferred_test.go} (68%) rename syft/{source/directory_resolver.go => internal/fileresolver/directory.go} (77%) rename syft/{source => internal/fileresolver}/directory_indexer.go (96%) rename syft/{source => internal/fileresolver}/directory_indexer_test.go (97%) rename syft/{source/directory_resolver_test.go => internal/fileresolver/directory_test.go} (78%) rename syft/{source/directory_resolver_windows_test.go => internal/fileresolver/directory_windows_test.go} (99%) create mode 100644 syft/internal/fileresolver/empty.go rename syft/{source/excluding_file_resolver.go => internal/fileresolver/excluding_file.go} (55%) rename syft/{source/excluding_file_resolver_test.go => internal/fileresolver/excluding_file_test.go} (66%) create mode 100644 syft/internal/fileresolver/file_metadata_by_location.go create mode 100755 syft/internal/fileresolver/test-fixtures/generate-tar-fixture-from-source-dir.sh rename syft/{source => internal/fileresolver}/test-fixtures/image-duplicate-path/Dockerfile (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-duplicate-path/file-1.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-duplicate-path/file-2.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-files-deleted/Dockerfile (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-files-deleted/file-1.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-files-deleted/file-3.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-files-deleted/target/file-2.txt (100%) create mode 100644 syft/internal/fileresolver/test-fixtures/image-simple/Dockerfile create mode 100644 syft/internal/fileresolver/test-fixtures/image-simple/file-1.txt create mode 100644 syft/internal/fileresolver/test-fixtures/image-simple/file-2.txt create mode 100644 syft/internal/fileresolver/test-fixtures/image-simple/target/really/nested/file-3.txt rename syft/{source => internal/fileresolver}/test-fixtures/image-symlinks/Dockerfile (100%) create mode 100644 syft/internal/fileresolver/test-fixtures/image-symlinks/file-1.txt rename syft/{source => internal/fileresolver}/test-fixtures/image-symlinks/file-2.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-symlinks/nested/nested/file-3.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-symlinks/new-file-2.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-symlinks/new-file-4.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/image-symlinks/parent/file-4.txt (100%) create mode 100644 syft/internal/fileresolver/test-fixtures/path-detected-2/.vimrc rename syft/{source/test-fixtures/symlinks-base/sub/item => internal/fileresolver/test-fixtures/path-detected-2/empty} (100%) create mode 100644 syft/internal/fileresolver/test-fixtures/path-detected/.vimrc create mode 100644 syft/internal/fileresolver/test-fixtures/path-detected/empty rename syft/{source => internal/fileresolver}/test-fixtures/symlinked-root/nested/link-root (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinked-root/real-root/file1.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinked-root/real-root/nested/file2.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-base/bar (100%) create mode 100644 syft/internal/fileresolver/test-fixtures/symlinks-base/base rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-base/baz (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-base/chain (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-base/foo (100%) create mode 100644 syft/internal/fileresolver/test-fixtures/symlinks-base/sub/item rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-base/sub/link (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/file-1.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/file-2.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/file-3.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/link-1 (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/link-2 (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/link-dead (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/link-indirect (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/link-within (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/parent-link (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-from-image-symlinks-fixture/parent/file-4.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-loop/README.md (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-loop/block/loop0 (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-loop/devices/loop0/file.target (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-loop/devices/loop0/subsystem (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-multiple-roots/outside/link_to_readme (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-multiple-roots/root/link_to_link_to_readme (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-multiple-roots/root/readme (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-prune-indexing/before-path (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-prune-indexing/c-file.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-prune-indexing/c-path (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-prune-indexing/path/1/2/3/4/dont-index-me-twice.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-prune-indexing/path/5/6/7/8/dont-index-me-twice-either.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-prune-indexing/path/file.txt (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-simple/link_to_link_to_new_readme (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-simple/link_to_new_readme (100%) rename syft/{source => internal/fileresolver}/test-fixtures/symlinks-simple/readme (100%) create mode 100644 syft/internal/fileresolver/test-fixtures/system_paths/outside_root/link_target/place create mode 100644 syft/internal/fileresolver/test-fixtures/system_paths/target/dev/place create mode 100644 syft/internal/fileresolver/test-fixtures/system_paths/target/home/place rename syft/{source => internal/fileresolver}/test-fixtures/system_paths/target/link/a-symlink (100%) create mode 100644 syft/internal/fileresolver/test-fixtures/system_paths/target/proc/place create mode 100644 syft/internal/fileresolver/test-fixtures/system_paths/target/sys/place rename syft/{source/unindexed_directory_resolver.go => internal/fileresolver/unindexed_directory.go} (80%) rename syft/{source/unindexed_directory_resolver_test.go => internal/fileresolver/unindexed_directory_test.go} (76%) delete mode 100644 syft/source/deferred_resolver.go create mode 100644 syft/source/deprecated.go delete mode 100644 syft/source/empty_resolver.go delete mode 100644 syft/source/file_details.go delete mode 100644 syft/source/file_details_win.go delete mode 100644 syft/source/file_metadata.go create mode 100644 syft/source/test-fixtures/system_paths/target/link/a-symlink/place diff --git a/DEVELOPING.md b/DEVELOPING.md index 2a787bef11f..7d915b7a313 100644 --- a/DEVELOPING.md +++ b/DEVELOPING.md @@ -167,12 +167,12 @@ always feel free to file an issue or reach out to us [on slack](https://anchore. #### Searching for files -All catalogers are provided an instance of the [`source.FileResolver`](https://github.com/anchore/syft/blob/v0.70.0/syft/source/file_resolver.go#L8) to interface with the image and search for files. The implementations for these +All catalogers are provided an instance of the [`file.Resolver`](https://github.com/anchore/syft/blob/v0.70.0/syft/source/file_resolver.go#L8) to interface with the image and search for files. The implementations for these abstractions leverage [`stereoscope`](https://github.com/anchore/stereoscope) in order to perform searching. Here is a rough outline how that works: -1. a stereoscope `file.Index` is searched based on the input given (a path, glob, or MIME type). The index is relatively fast to search, but requires results to be filtered down to the files that exist in the specific layer(s) of interest. This is done automatically by the `filetree.Searcher` abstraction. This abstraction will fallback to searching directly against the raw `filetree.FileTree` if the index does not contain the file(s) of interest. Note: the `filetree.Searcher` is used by the `source.FileResolver` abstraction. -2. Once the set of files are returned from the `filetree.Searcher` the results are filtered down further to return the most unique file results. For example, you may have requested for files by a glob that returns multiple results. These results are filtered down to deduplicate by real files, so if a result contains two references to the same file, say one accessed via symlink and one accessed via the real path, then the real path reference is returned and the symlink reference is filtered out. If both were accessed by symlink then the first (by lexical order) is returned. This is done automatically by the `source.FileResolver` abstraction. +1. a stereoscope `file.Index` is searched based on the input given (a path, glob, or MIME type). The index is relatively fast to search, but requires results to be filtered down to the files that exist in the specific layer(s) of interest. This is done automatically by the `filetree.Searcher` abstraction. This abstraction will fallback to searching directly against the raw `filetree.FileTree` if the index does not contain the file(s) of interest. Note: the `filetree.Searcher` is used by the `file.Resolver` abstraction. +2. Once the set of files are returned from the `filetree.Searcher` the results are filtered down further to return the most unique file results. For example, you may have requested for files by a glob that returns multiple results. These results are filtered down to deduplicate by real files, so if a result contains two references to the same file, say one accessed via symlink and one accessed via the real path, then the real path reference is returned and the symlink reference is filtered out. If both were accessed by symlink then the first (by lexical order) is returned. This is done automatically by the `file.Resolver` abstraction. 3. By the time results reach the `pkg.Cataloger` you are guaranteed to have a set of unique files that exist in the layer(s) of interest (relative to what the resolver supports). ## Testing diff --git a/cmd/syft/cli/eventloop/tasks.go b/cmd/syft/cli/eventloop/tasks.go index 56bbcc93535..536a39ee6f1 100644 --- a/cmd/syft/cli/eventloop/tasks.go +++ b/cmd/syft/cli/eventloop/tasks.go @@ -8,6 +8,10 @@ import ( "github.com/anchore/syft/syft" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/file/cataloger/filecontent" + "github.com/anchore/syft/syft/file/cataloger/filedigest" + "github.com/anchore/syft/syft/file/cataloger/filemetadata" + "github.com/anchore/syft/syft/file/cataloger/secrets" "github.com/anchore/syft/syft/sbom" "github.com/anchore/syft/syft/source" ) @@ -61,7 +65,7 @@ func generateCatalogFileMetadataTask(app *config.Application) (Task, error) { return nil, nil } - metadataCataloger := file.NewMetadataCataloger() + metadataCataloger := filemetadata.NewCataloger() task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) { resolver, err := src.FileResolver(app.FileMetadata.Cataloger.ScopeOpt) @@ -104,10 +108,7 @@ func generateCatalogFileDigestsTask(app *config.Application) (Task, error) { hashes = append(hashes, hashObj) } - digestsCataloger, err := file.NewDigestsCataloger(hashes) - if err != nil { - return nil, err - } + digestsCataloger := filedigest.NewCataloger(hashes) task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) { resolver, err := src.FileResolver(app.FileMetadata.Cataloger.ScopeOpt) @@ -131,12 +132,12 @@ func generateCatalogSecretsTask(app *config.Application) (Task, error) { return nil, nil } - patterns, err := file.GenerateSearchPatterns(file.DefaultSecretsPatterns, app.Secrets.AdditionalPatterns, app.Secrets.ExcludePatternNames) + patterns, err := secrets.GenerateSearchPatterns(secrets.DefaultSecretsPatterns, app.Secrets.AdditionalPatterns, app.Secrets.ExcludePatternNames) if err != nil { return nil, err } - secretsCataloger, err := file.NewSecretsCataloger(patterns, app.Secrets.RevealValues, app.Secrets.SkipFilesAboveSize) + secretsCataloger, err := secrets.NewCataloger(patterns, app.Secrets.RevealValues, app.Secrets.SkipFilesAboveSize) //nolint:staticcheck if err != nil { return nil, err } @@ -163,7 +164,7 @@ func generateCatalogContentsTask(app *config.Application) (Task, error) { return nil, nil } - contentsCataloger, err := file.NewContentsCataloger(app.FileContents.Globs, app.FileContents.SkipFilesAboveSize) + contentsCataloger, err := filecontent.NewCataloger(app.FileContents.Globs, app.FileContents.SkipFilesAboveSize) //nolint:staticcheck if err != nil { return nil, err } diff --git a/internal/licenses/parser.go b/internal/licenses/parser.go index b5cde28f7d0..58e4deb2d22 100644 --- a/internal/licenses/parser.go +++ b/internal/licenses/parser.go @@ -5,9 +5,9 @@ import ( "github.com/google/licensecheck" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) const ( @@ -16,7 +16,7 @@ const ( ) // Parse scans the contents of a license file to attempt to determine the type of license it is -func Parse(reader io.Reader, l source.Location) (licenses []pkg.License, err error) { +func Parse(reader io.Reader, l file.Location) (licenses []pkg.License, err error) { licenses = make([]pkg.License, 0) contents, err := io.ReadAll(reader) if err != nil { diff --git a/syft/event/parsers/parsers.go b/syft/event/parsers/parsers.go index e7a3d703d4f..3d0c8bfb85b 100644 --- a/syft/event/parsers/parsers.go +++ b/syft/event/parsers/parsers.go @@ -12,7 +12,7 @@ import ( "github.com/anchore/syft/syft/event" "github.com/anchore/syft/syft/event/monitor" - "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/file/cataloger/secrets" "github.com/anchore/syft/syft/pkg/cataloger" ) @@ -54,12 +54,12 @@ func ParsePackageCatalogerStarted(e partybus.Event) (*cataloger.Monitor, error) return &monitor, nil } -func ParseSecretsCatalogingStarted(e partybus.Event) (*file.SecretsMonitor, error) { +func ParseSecretsCatalogingStarted(e partybus.Event) (*secrets.Monitor, error) { if err := checkEventType(e.Type, event.SecretsCatalogerStarted); err != nil { return nil, err } - monitor, ok := e.Value.(file.SecretsMonitor) + monitor, ok := e.Value.(secrets.Monitor) if !ok { return nil, newPayloadErr(e.Type, "Value", e.Value) } diff --git a/syft/file/contents_cataloger.go b/syft/file/cataloger/filecontent/cataloger.go similarity index 75% rename from syft/file/contents_cataloger.go rename to syft/file/cataloger/filecontent/cataloger.go index 1e0cfe33b22..d108af3931f 100644 --- a/syft/file/contents_cataloger.go +++ b/syft/file/cataloger/filecontent/cataloger.go @@ -1,4 +1,4 @@ -package file +package filecontent import ( "bytes" @@ -8,24 +8,26 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) -type ContentsCataloger struct { +// Deprecated: will be removed in syft v1.0.0 +type Cataloger struct { globs []string skipFilesAboveSizeInBytes int64 } -func NewContentsCataloger(globs []string, skipFilesAboveSize int64) (*ContentsCataloger, error) { - return &ContentsCataloger{ +// Deprecated: will be removed in syft v1.0.0 +func NewCataloger(globs []string, skipFilesAboveSize int64) (*Cataloger, error) { + return &Cataloger{ globs: globs, skipFilesAboveSizeInBytes: skipFilesAboveSize, }, nil } -func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates]string, error) { - results := make(map[source.Coordinates]string) - var locations []source.Location +func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]string, error) { + results := make(map[file.Coordinates]string) + var locations []file.Location locations, err := resolver.FilesByGlob(i.globs...) if err != nil { @@ -56,7 +58,7 @@ func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Co return results, nil } -func (i *ContentsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) (string, error) { +func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) { contentReader, err := resolver.FileContentsByLocation(location) if err != nil { return "", err diff --git a/syft/file/cataloger/filecontent/cataloger_test.go b/syft/file/cataloger/filecontent/cataloger_test.go new file mode 100644 index 00000000000..719bdd48183 --- /dev/null +++ b/syft/file/cataloger/filecontent/cataloger_test.go @@ -0,0 +1,80 @@ +package filecontent + +import ( + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/anchore/syft/syft/file" +) + +func TestContentsCataloger(t *testing.T) { + allFiles := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"} + + tests := []struct { + name string + globs []string + maxSize int64 + files []string + expected map[file.Coordinates]string + }{ + { + name: "multi-pattern", + globs: []string{"test-fixtures/last/*.txt", "test-fixtures/*.txt"}, + files: allFiles, + expected: map[file.Coordinates]string{ + file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh", + file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh", + file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh", + }, + }, + { + name: "no-patterns", + globs: []string{}, + files: []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"}, + expected: map[file.Coordinates]string{}, + }, + { + name: "all-txt", + globs: []string{"**/*.txt"}, + files: allFiles, + expected: map[file.Coordinates]string{ + file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh", + file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh", + file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh", + }, + }, + { + name: "subpath", + globs: []string{"test-fixtures/*.txt"}, + files: allFiles, + expected: map[file.Coordinates]string{ + file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh", + file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh", + }, + }, + { + name: "size-filter", + maxSize: 42, + globs: []string{"**/*.txt"}, + files: allFiles, + expected: map[file.Coordinates]string{ + file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh", + file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh", + }, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + c, err := NewCataloger(test.globs, test.maxSize) + assert.NoError(t, err) + + resolver := file.NewMockResolverForPaths(test.files...) + actual, err := c.Catalog(resolver) + assert.NoError(t, err) + assert.Equal(t, test.expected, actual, "mismatched contents") + + }) + } +} diff --git a/syft/file/test-fixtures/a-path.txt b/syft/file/cataloger/filecontent/test-fixtures/a-path.txt similarity index 100% rename from syft/file/test-fixtures/a-path.txt rename to syft/file/cataloger/filecontent/test-fixtures/a-path.txt diff --git a/syft/file/test-fixtures/another-path.txt b/syft/file/cataloger/filecontent/test-fixtures/another-path.txt similarity index 100% rename from syft/file/test-fixtures/another-path.txt rename to syft/file/cataloger/filecontent/test-fixtures/another-path.txt diff --git a/syft/file/test-fixtures/last/empty/empty b/syft/file/cataloger/filecontent/test-fixtures/last/empty/empty similarity index 100% rename from syft/file/test-fixtures/last/empty/empty rename to syft/file/cataloger/filecontent/test-fixtures/last/empty/empty diff --git a/syft/file/test-fixtures/last/path.txt b/syft/file/cataloger/filecontent/test-fixtures/last/path.txt similarity index 100% rename from syft/file/test-fixtures/last/path.txt rename to syft/file/cataloger/filecontent/test-fixtures/last/path.txt diff --git a/syft/file/cataloger/filedigest/cataloger.go b/syft/file/cataloger/filedigest/cataloger.go new file mode 100644 index 00000000000..e06c05a3514 --- /dev/null +++ b/syft/file/cataloger/filedigest/cataloger.go @@ -0,0 +1,109 @@ +package filedigest + +import ( + "crypto" + "errors" + + "github.com/wagoodman/go-partybus" + "github.com/wagoodman/go-progress" + + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/internal" + "github.com/anchore/syft/internal/bus" + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/event" + "github.com/anchore/syft/syft/file" + internal2 "github.com/anchore/syft/syft/file/cataloger/internal" +) + +var ErrUndigestableFile = errors.New("undigestable file") + +type Cataloger struct { + hashes []crypto.Hash +} + +func NewCataloger(hashes []crypto.Hash) *Cataloger { + return &Cataloger{ + hashes: hashes, + } +} + +func (i *Cataloger) Catalog(resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) { + results := make(map[file.Coordinates][]file.Digest) + var locations []file.Location + + if len(coordinates) == 0 { + locations = internal2.AllRegularFiles(resolver) + } else { + for _, c := range coordinates { + locations = append(locations, file.NewLocationFromCoordinates(c)) + } + } + + stage, prog := digestsCatalogingProgress(int64(len(locations))) + for _, location := range locations { + stage.Current = location.RealPath + result, err := i.catalogLocation(resolver, location) + + if errors.Is(err, ErrUndigestableFile) { + continue + } + + if internal.IsErrPathPermission(err) { + log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err) + continue + } + + if err != nil { + return nil, err + } + prog.Increment() + results[location.Coordinates] = result + } + log.Debugf("file digests cataloger processed %d files", prog.Current()) + prog.SetCompleted() + return results, nil +} + +func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) { + meta, err := resolver.FileMetadataByLocation(location) + if err != nil { + return nil, err + } + + // we should only attempt to report digests for files that are regular files (don't attempt to resolve links) + if meta.Type != stereoscopeFile.TypeRegular { + return nil, ErrUndigestableFile + } + + contentReader, err := resolver.FileContentsByLocation(location) + if err != nil { + return nil, err + } + defer internal.CloseAndLogError(contentReader, location.VirtualPath) + + digests, err := file.NewDigestsFromFile(contentReader, i.hashes) + if err != nil { + return nil, internal.ErrPath{Context: "digests-cataloger", Path: location.RealPath, Err: err} + } + + return digests, nil +} + +func digestsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) { + stage := &progress.Stage{} + prog := progress.NewManual(locations) + + bus.Publish(partybus.Event{ + Type: event.FileDigestsCatalogerStarted, + Value: struct { + progress.Stager + progress.Progressable + }{ + Stager: progress.Stager(stage), + Progressable: prog, + }, + }) + + return stage, prog +} diff --git a/syft/file/digest_cataloger_test.go b/syft/file/cataloger/filedigest/cataloger_test.go similarity index 74% rename from syft/file/digest_cataloger_test.go rename to syft/file/cataloger/filedigest/cataloger_test.go index 57a1a8071ee..ed8562cbd38 100644 --- a/syft/file/digest_cataloger_test.go +++ b/syft/file/cataloger/filedigest/cataloger_test.go @@ -1,9 +1,9 @@ -package file +package filedigest import ( "crypto" "fmt" - "io/ioutil" + "io" "os" "path/filepath" "testing" @@ -11,29 +11,36 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/source" ) -func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Hash) map[source.Coordinates][]Digest { - digests := make(map[source.Coordinates][]Digest) +func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Hash) map[file.Coordinates][]file.Digest { + digests := make(map[file.Coordinates][]file.Digest) for _, f := range files { fh, err := os.Open(filepath.Join(root, f)) if err != nil { t.Fatalf("could not open %q : %+v", f, err) } - b, err := ioutil.ReadAll(fh) + b, err := io.ReadAll(fh) if err != nil { t.Fatalf("could not read %q : %+v", f, err) } + if len(b) == 0 { + // we don't keep digests for empty files + digests[file.NewLocation(f).Coordinates] = []file.Digest{} + continue + } + for _, hash := range hashes { h := hash.New() h.Write(b) - digests[source.NewLocation(f).Coordinates] = append(digests[source.NewLocation(f).Coordinates], Digest{ - Algorithm: CleanDigestAlgorithmName(hash.String()), + digests[file.NewLocation(f).Coordinates] = append(digests[file.NewLocation(f).Coordinates], file.Digest{ + Algorithm: file.CleanDigestAlgorithmName(hash.String()), Value: fmt.Sprintf("%x", h.Sum(nil)), }) } @@ -48,7 +55,7 @@ func TestDigestsCataloger(t *testing.T) { name string digests []crypto.Hash files []string - expected map[source.Coordinates][]Digest + expected map[file.Coordinates][]file.Digest }{ { name: "md5", @@ -66,8 +73,7 @@ func TestDigestsCataloger(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - c, err := NewDigestsCataloger(test.digests) - require.NoError(t, err) + c := NewCataloger(test.digests) src, err := source.NewFromDirectory("test-fixtures/last/") require.NoError(t, err) @@ -86,11 +92,7 @@ func TestDigestsCataloger(t *testing.T) { func TestDigestsCataloger_MixFileTypes(t *testing.T) { testImage := "image-file-type-mix" - if *updateImageGoldenFiles { - imagetest.UpdateGoldenFixtureImage(t, testImage) - } - - img := imagetest.GetGoldenFixtureImage(t, testImage) + img := imagetest.GetFixtureImage(t, "docker-archive", testImage) src, err := source.NewFromImage(img, "---") if err != nil { @@ -110,9 +112,10 @@ func TestDigestsCataloger_MixFileTypes(t *testing.T) { path: "/file-1.txt", expected: "888c139e550867814eb7c33b84d76e4d", }, - { - path: "/hardlink-1", - }, + // this is difficult to reproduce in a cross-platform way + //{ + // path: "/hardlink-1", + //}, { path: "/symlink-1", }, @@ -132,21 +135,18 @@ func TestDigestsCataloger_MixFileTypes(t *testing.T) { for _, test := range tests { t.Run(test.path, func(t *testing.T) { - c, err := NewDigestsCataloger([]crypto.Hash{crypto.MD5}) - if err != nil { - t.Fatalf("unable to get cataloger: %+v", err) - } + c := NewCataloger([]crypto.Hash{crypto.MD5}) actual, err := c.Catalog(resolver) if err != nil { t.Fatalf("could not catalog: %+v", err) } - _, ref, err := img.SquashedTree().File(file.Path(test.path)) + _, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path)) if err != nil { t.Fatalf("unable to get file=%q : %+v", test.path, err) } - l := source.NewLocationFromImage(test.path, *ref.Reference, img) + l := file.NewLocationFromImage(test.path, *ref.Reference, img) if len(actual[l.Coordinates]) == 0 { if test.expected != "" { diff --git a/syft/file/test-fixtures/image-file-type-mix/Dockerfile b/syft/file/cataloger/filedigest/test-fixtures/image-file-type-mix/Dockerfile similarity index 81% rename from syft/file/test-fixtures/image-file-type-mix/Dockerfile rename to syft/file/cataloger/filedigest/test-fixtures/image-file-type-mix/Dockerfile index c2d61ef4da9..6ede1b68da8 100644 --- a/syft/file/test-fixtures/image-file-type-mix/Dockerfile +++ b/syft/file/cataloger/filedigest/test-fixtures/image-file-type-mix/Dockerfile @@ -1,4 +1,4 @@ -FROM busybox:latest +FROM busybox:1.28.1@sha256:c7b0a24019b0e6eda714ec0fa137ad42bc44a754d9cea17d14fba3a80ccc1ee4 ADD file-1.txt . RUN chmod 644 file-1.txt diff --git a/syft/file/test-fixtures/image-file-type-mix/file-1.txt b/syft/file/cataloger/filedigest/test-fixtures/image-file-type-mix/file-1.txt similarity index 100% rename from syft/file/test-fixtures/image-file-type-mix/file-1.txt rename to syft/file/cataloger/filedigest/test-fixtures/image-file-type-mix/file-1.txt diff --git a/syft/source/test-fixtures/symlinks-base/base b/syft/file/cataloger/filedigest/test-fixtures/last/empty/empty similarity index 100% rename from syft/source/test-fixtures/symlinks-base/base rename to syft/file/cataloger/filedigest/test-fixtures/last/empty/empty diff --git a/syft/file/cataloger/filedigest/test-fixtures/last/path.txt b/syft/file/cataloger/filedigest/test-fixtures/last/path.txt new file mode 100644 index 00000000000..3d4a165ab88 --- /dev/null +++ b/syft/file/cataloger/filedigest/test-fixtures/last/path.txt @@ -0,0 +1 @@ +test-fixtures/last/path.txt file contents! \ No newline at end of file diff --git a/syft/file/metadata_cataloger.go b/syft/file/cataloger/filemetadata/cataloger.go similarity index 59% rename from syft/file/metadata_cataloger.go rename to syft/file/cataloger/filemetadata/cataloger.go index 44d46f030ce..bae2344f479 100644 --- a/syft/file/metadata_cataloger.go +++ b/syft/file/cataloger/filemetadata/cataloger.go @@ -1,4 +1,4 @@ -package file +package filemetadata import ( "github.com/wagoodman/go-partybus" @@ -7,24 +7,37 @@ import ( "github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/event" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) -type MetadataCataloger struct { +type Cataloger struct { } -func NewMetadataCataloger() *MetadataCataloger { - return &MetadataCataloger{} +func NewCataloger() *Cataloger { + return &Cataloger{} } -func (i *MetadataCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates]source.FileMetadata, error) { - results := make(map[source.Coordinates]source.FileMetadata) - var locations []source.Location - for location := range resolver.AllLocations() { - locations = append(locations, location) +func (i *Cataloger) Catalog(resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates]file.Metadata, error) { + results := make(map[file.Coordinates]file.Metadata) + var locations <-chan file.Location + + if len(coordinates) == 0 { + locations = resolver.AllLocations() + } else { + locations = func() <-chan file.Location { + ch := make(chan file.Location) + go func() { + close(ch) + for _, c := range coordinates { + ch <- file.NewLocationFromCoordinates(c) + } + }() + return ch + }() } + stage, prog := metadataCatalogingProgress(int64(len(locations))) - for _, location := range locations { + for location := range locations { stage.Current = location.RealPath metadata, err := resolver.FileMetadataByLocation(location) if err != nil { diff --git a/syft/file/metadata_cataloger_test.go b/syft/file/cataloger/filemetadata/cataloger_test.go similarity index 62% rename from syft/file/metadata_cataloger_test.go rename to syft/file/cataloger/filemetadata/cataloger_test.go index 3b625ba4f10..99dfa908a9f 100644 --- a/syft/file/metadata_cataloger_test.go +++ b/syft/file/cataloger/filemetadata/cataloger_test.go @@ -1,30 +1,24 @@ -package file +package filemetadata import ( - "flag" "os" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/source" ) -var updateImageGoldenFiles = flag.Bool("update-image", false, "update the golden fixture images used for testing") - func TestFileMetadataCataloger(t *testing.T) { testImage := "image-file-type-mix" - if *updateImageGoldenFiles { - imagetest.UpdateGoldenFixtureImage(t, testImage) - } - - img := imagetest.GetGoldenFixtureImage(t, testImage) + img := imagetest.GetFixtureImage(t, "docker-archive", testImage) - c := NewMetadataCataloger() + c := NewCataloger() src, err := source.NewFromImage(img, "---") if err != nil { @@ -44,51 +38,36 @@ func TestFileMetadataCataloger(t *testing.T) { tests := []struct { path string exists bool - expected source.FileMetadata + expected file.Metadata err bool }{ + // note: it is difficult to add a hardlink-based test in a cross-platform way and is already covered well in stereoscope { path: "/file-1.txt", exists: true, - expected: source.FileMetadata{ - FileInfo: file.ManualInfo{ + expected: file.Metadata{ + FileInfo: stereoscopeFile.ManualInfo{ NameValue: "file-1.txt", ModeValue: 0644, SizeValue: 7, }, Path: "/file-1.txt", - Type: file.TypeRegular, + Type: stereoscopeFile.TypeRegular, UserID: 1, GroupID: 2, MIMEType: "text/plain", }, }, - { - path: "/hardlink-1", - exists: true, - expected: source.FileMetadata{ - FileInfo: file.ManualInfo{ - NameValue: "hardlink-1", - ModeValue: 0644, - }, - Path: "/hardlink-1", - Type: file.TypeHardLink, - LinkDestination: "file-1.txt", - UserID: 1, - GroupID: 2, - MIMEType: "", - }, - }, { path: "/symlink-1", exists: true, - expected: source.FileMetadata{ + expected: file.Metadata{ Path: "/symlink-1", - FileInfo: file.ManualInfo{ + FileInfo: stereoscopeFile.ManualInfo{ NameValue: "symlink-1", ModeValue: 0777 | os.ModeSymlink, }, - Type: file.TypeSymLink, + Type: stereoscopeFile.TypeSymLink, LinkDestination: "file-1.txt", UserID: 0, GroupID: 0, @@ -98,13 +77,13 @@ func TestFileMetadataCataloger(t *testing.T) { { path: "/char-device-1", exists: true, - expected: source.FileMetadata{ + expected: file.Metadata{ Path: "/char-device-1", - FileInfo: file.ManualInfo{ + FileInfo: stereoscopeFile.ManualInfo{ NameValue: "char-device-1", ModeValue: 0644 | os.ModeDevice | os.ModeCharDevice, }, - Type: file.TypeCharacterDevice, + Type: stereoscopeFile.TypeCharacterDevice, UserID: 0, GroupID: 0, MIMEType: "", @@ -113,13 +92,13 @@ func TestFileMetadataCataloger(t *testing.T) { { path: "/block-device-1", exists: true, - expected: source.FileMetadata{ + expected: file.Metadata{ Path: "/block-device-1", - FileInfo: file.ManualInfo{ + FileInfo: stereoscopeFile.ManualInfo{ NameValue: "block-device-1", ModeValue: 0644 | os.ModeDevice, }, - Type: file.TypeBlockDevice, + Type: stereoscopeFile.TypeBlockDevice, UserID: 0, GroupID: 0, MIMEType: "", @@ -128,13 +107,13 @@ func TestFileMetadataCataloger(t *testing.T) { { path: "/fifo-1", exists: true, - expected: source.FileMetadata{ + expected: file.Metadata{ Path: "/fifo-1", - FileInfo: file.ManualInfo{ + FileInfo: stereoscopeFile.ManualInfo{ NameValue: "fifo-1", ModeValue: 0644 | os.ModeNamedPipe, }, - Type: file.TypeFIFO, + Type: stereoscopeFile.TypeFIFO, UserID: 0, GroupID: 0, MIMEType: "", @@ -143,13 +122,13 @@ func TestFileMetadataCataloger(t *testing.T) { { path: "/bin", exists: true, - expected: source.FileMetadata{ + expected: file.Metadata{ Path: "/bin", - FileInfo: file.ManualInfo{ + FileInfo: stereoscopeFile.ManualInfo{ NameValue: "bin", ModeValue: 0755 | os.ModeDir, }, - Type: file.TypeDirectory, + Type: stereoscopeFile.TypeDirectory, UserID: 0, GroupID: 0, MIMEType: "", @@ -159,15 +138,15 @@ func TestFileMetadataCataloger(t *testing.T) { for _, test := range tests { t.Run(test.path, func(t *testing.T) { - _, ref, err := img.SquashedTree().File(file.Path(test.path)) + _, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path)) require.NoError(t, err) - l := source.NewLocationFromImage(test.path, *ref.Reference, img) + l := file.NewLocationFromImage(test.path, *ref.Reference, img) if _, ok := actual[l.Coordinates]; ok { // we're not interested in keeping the test fixtures up to date with the latest file modification times // thus ModTime is not under test - fi := test.expected.FileInfo.(file.ManualInfo) + fi := test.expected.FileInfo.(stereoscopeFile.ManualInfo) fi.ModTimeValue = actual[l.Coordinates].ModTime() test.expected.FileInfo = fi } diff --git a/syft/file/cataloger/filemetadata/test-fixtures/image-file-type-mix/Dockerfile b/syft/file/cataloger/filemetadata/test-fixtures/image-file-type-mix/Dockerfile new file mode 100644 index 00000000000..6ede1b68da8 --- /dev/null +++ b/syft/file/cataloger/filemetadata/test-fixtures/image-file-type-mix/Dockerfile @@ -0,0 +1,13 @@ +FROM busybox:1.28.1@sha256:c7b0a24019b0e6eda714ec0fa137ad42bc44a754d9cea17d14fba3a80ccc1ee4 + +ADD file-1.txt . +RUN chmod 644 file-1.txt +RUN chown 1:2 file-1.txt +RUN ln -s file-1.txt symlink-1 +# note: hard links may behave inconsistently, this should be a golden image +RUN ln file-1.txt hardlink-1 +RUN mknod char-device-1 c 89 1 +RUN mknod block-device-1 b 0 1 +RUN mknod fifo-1 p +RUN mkdir /dir +RUN rm -rf home etc/group etc/localtime etc/mtab etc/network etc/passwd etc/shadow var usr bin/* \ No newline at end of file diff --git a/syft/source/test-fixtures/image-symlinks/file-1.txt b/syft/file/cataloger/filemetadata/test-fixtures/image-file-type-mix/file-1.txt similarity index 100% rename from syft/source/test-fixtures/image-symlinks/file-1.txt rename to syft/file/cataloger/filemetadata/test-fixtures/image-file-type-mix/file-1.txt diff --git a/syft/file/all_regular_files.go b/syft/file/cataloger/internal/all_regular_files.go similarity index 70% rename from syft/file/all_regular_files.go rename to syft/file/cataloger/internal/all_regular_files.go index 5dcf8974430..ccc1b3813ba 100644 --- a/syft/file/all_regular_files.go +++ b/syft/file/cataloger/internal/all_regular_files.go @@ -1,12 +1,12 @@ -package file +package internal import ( - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) -func allRegularFiles(resolver source.FileResolver) (locations []source.Location) { +func AllRegularFiles(resolver file.Resolver) (locations []file.Location) { for location := range resolver.AllLocations() { resolvedLocations, err := resolver.FilesByPath(location.RealPath) if err != nil { @@ -21,7 +21,7 @@ func allRegularFiles(resolver source.FileResolver) (locations []source.Location) continue } - if metadata.Type != file.TypeRegular { + if metadata.Type != stereoscopeFile.TypeRegular { continue } locations = append(locations, resolvedLocation) diff --git a/syft/file/all_regular_files_test.go b/syft/file/cataloger/internal/all_regular_files_test.go similarity index 78% rename from syft/file/all_regular_files_test.go rename to syft/file/cataloger/internal/all_regular_files_test.go index 096480721a5..714e733e689 100644 --- a/syft/file/all_regular_files_test.go +++ b/syft/file/cataloger/internal/all_regular_files_test.go @@ -1,4 +1,4 @@ -package file +package internal import ( "testing" @@ -9,30 +9,23 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/source" ) func Test_allRegularFiles(t *testing.T) { - type access struct { - realPath string - virtualPath string - } tests := []struct { name string - setup func() source.FileResolver + setup func() file.Resolver wantRealPaths *strset.Set wantVirtualPaths *strset.Set }{ { name: "image", - setup: func() source.FileResolver { + setup: func() file.Resolver { testImage := "image-file-type-mix" - if *updateImageGoldenFiles { - imagetest.UpdateGoldenFixtureImage(t, testImage) - } - - img := imagetest.GetGoldenFixtureImage(t, testImage) + img := imagetest.GetFixtureImage(t, "docker-archive", testImage) s, err := source.NewFromImage(img, "---") require.NoError(t, err) @@ -47,7 +40,7 @@ func Test_allRegularFiles(t *testing.T) { }, { name: "directory", - setup: func() source.FileResolver { + setup: func() file.Resolver { s, err := source.NewFromDirectory("test-fixtures/symlinked-root/nested/link-root") require.NoError(t, err) r, err := s.FileResolver(source.SquashedScope) @@ -61,7 +54,7 @@ func Test_allRegularFiles(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { resolver := tt.setup() - locations := allRegularFiles(resolver) + locations := AllRegularFiles(resolver) realLocations := strset.New() virtualLocations := strset.New() for _, l := range locations { @@ -70,6 +63,13 @@ func Test_allRegularFiles(t *testing.T) { virtualLocations.Add(l.VirtualPath) } } + + // this is difficult to reproduce in a cross-platform way + realLocations.Remove("/hardlink-1") + virtualLocations.Remove("/hardlink-1") + tt.wantRealPaths.Remove("/hardlink-1") + tt.wantVirtualPaths.Remove("/hardlink-1") + assert.ElementsMatch(t, tt.wantRealPaths.List(), realLocations.List(), "real paths differ: "+cmp.Diff(tt.wantRealPaths.List(), realLocations.List())) assert.ElementsMatch(t, tt.wantVirtualPaths.List(), virtualLocations.List(), "virtual paths differ: "+cmp.Diff(tt.wantVirtualPaths.List(), virtualLocations.List())) }) diff --git a/syft/file/cataloger/internal/test-fixtures/image-file-type-mix/Dockerfile b/syft/file/cataloger/internal/test-fixtures/image-file-type-mix/Dockerfile new file mode 100644 index 00000000000..6ede1b68da8 --- /dev/null +++ b/syft/file/cataloger/internal/test-fixtures/image-file-type-mix/Dockerfile @@ -0,0 +1,13 @@ +FROM busybox:1.28.1@sha256:c7b0a24019b0e6eda714ec0fa137ad42bc44a754d9cea17d14fba3a80ccc1ee4 + +ADD file-1.txt . +RUN chmod 644 file-1.txt +RUN chown 1:2 file-1.txt +RUN ln -s file-1.txt symlink-1 +# note: hard links may behave inconsistently, this should be a golden image +RUN ln file-1.txt hardlink-1 +RUN mknod char-device-1 c 89 1 +RUN mknod block-device-1 b 0 1 +RUN mknod fifo-1 p +RUN mkdir /dir +RUN rm -rf home etc/group etc/localtime etc/mtab etc/network etc/passwd etc/shadow var usr bin/* \ No newline at end of file diff --git a/syft/file/cataloger/internal/test-fixtures/image-file-type-mix/file-1.txt b/syft/file/cataloger/internal/test-fixtures/image-file-type-mix/file-1.txt new file mode 100644 index 00000000000..d86db8155c3 --- /dev/null +++ b/syft/file/cataloger/internal/test-fixtures/image-file-type-mix/file-1.txt @@ -0,0 +1 @@ +file 1! \ No newline at end of file diff --git a/syft/file/test-fixtures/symlinked-root/nested/link-root b/syft/file/cataloger/internal/test-fixtures/symlinked-root/nested/link-root similarity index 100% rename from syft/file/test-fixtures/symlinked-root/nested/link-root rename to syft/file/cataloger/internal/test-fixtures/symlinked-root/nested/link-root diff --git a/syft/file/test-fixtures/symlinked-root/real-root/file1.txt b/syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/file1.txt similarity index 100% rename from syft/file/test-fixtures/symlinked-root/real-root/file1.txt rename to syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/file1.txt diff --git a/syft/file/test-fixtures/symlinked-root/real-root/nested/file2.txt b/syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/nested/file2.txt similarity index 100% rename from syft/file/test-fixtures/symlinked-root/real-root/nested/file2.txt rename to syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/nested/file2.txt diff --git a/syft/file/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt b/syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt similarity index 100% rename from syft/file/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt rename to syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt diff --git a/syft/file/secrets_cataloger.go b/syft/file/cataloger/secrets/cataloger.go similarity index 82% rename from syft/file/secrets_cataloger.go rename to syft/file/cataloger/secrets/cataloger.go index 56537d04968..488f849b53b 100644 --- a/syft/file/secrets_cataloger.go +++ b/syft/file/cataloger/secrets/cataloger.go @@ -1,4 +1,4 @@ -package file +package secrets import ( "bytes" @@ -14,7 +14,8 @@ import ( "github.com/anchore/syft/internal/bus" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/event" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" + internal2 "github.com/anchore/syft/syft/file/cataloger/internal" ) var DefaultSecretsPatterns = map[string]string{ @@ -25,23 +26,25 @@ var DefaultSecretsPatterns = map[string]string{ "generic-api-key": `(?i)api(-|_)?key["'=:\s]*?(?P[A-Z0-9]{20,60})["']?(\s|$)`, } -type SecretsCataloger struct { +// Deprecated: will be removed in syft v1.0.0 +type Cataloger struct { patterns map[string]*regexp.Regexp revealValues bool skipFilesAboveSize int64 } -func NewSecretsCataloger(patterns map[string]*regexp.Regexp, revealValues bool, maxFileSize int64) (*SecretsCataloger, error) { - return &SecretsCataloger{ +// Deprecated: will be removed in syft v1.0.0 +func NewCataloger(patterns map[string]*regexp.Regexp, revealValues bool, maxFileSize int64) (*Cataloger, error) { + return &Cataloger{ patterns: patterns, revealValues: revealValues, skipFilesAboveSize: maxFileSize, }, nil } -func (i *SecretsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]SearchResult, error) { - results := make(map[source.Coordinates][]SearchResult) - locations := allRegularFiles(resolver) +func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates][]file.SearchResult, error) { + results := make(map[file.Coordinates][]file.SearchResult) + locations := internal2.AllRegularFiles(resolver) stage, prog, secretsDiscovered := secretsCatalogingProgress(int64(len(locations))) for _, location := range locations { stage.Current = location.RealPath @@ -65,7 +68,7 @@ func (i *SecretsCataloger) Catalog(resolver source.FileResolver) (map[source.Coo return results, nil } -func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) ([]SearchResult, error) { +func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.SearchResult, error) { metadata, err := resolver.FileMetadataByLocation(location) if err != nil { return nil, err @@ -103,7 +106,7 @@ func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, locatio return secrets, nil } -func extractValue(resolver source.FileResolver, location source.Location, start, length int64) (string, error) { +func extractValue(resolver file.Resolver, location file.Location, start, length int64) (string, error) { readCloser, err := resolver.FileContentsByLocation(location) if err != nil { return "", fmt.Errorf("unable to fetch reader for location=%q : %w", location, err) @@ -130,7 +133,7 @@ func extractValue(resolver source.FileResolver, location source.Location, start, return buf.String(), nil } -type SecretsMonitor struct { +type Monitor struct { progress.Stager SecretsDiscovered progress.Monitorable progress.Progressable @@ -144,7 +147,7 @@ func secretsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manu bus.Publish(partybus.Event{ Type: event.SecretsCatalogerStarted, Source: secretsDiscovered, - Value: SecretsMonitor{ + Value: Monitor{ Stager: progress.Stager(stage), SecretsDiscovered: secretsDiscovered, Progressable: prog, diff --git a/syft/file/secrets_cataloger_test.go b/syft/file/cataloger/secrets/cataloger_test.go similarity index 92% rename from syft/file/secrets_cataloger_test.go rename to syft/file/cataloger/secrets/cataloger_test.go index b2c55a9f3f0..2a44417ba5d 100644 --- a/syft/file/secrets_cataloger_test.go +++ b/syft/file/cataloger/secrets/cataloger_test.go @@ -1,4 +1,4 @@ -package file +package secrets import ( "regexp" @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/assert" - "github.com/anchore/syft/internal/file" - "github.com/anchore/syft/syft/source" + intFile "github.com/anchore/syft/internal/file" + "github.com/anchore/syft/syft/file" ) func TestSecretsCataloger(t *testing.T) { @@ -17,7 +17,7 @@ func TestSecretsCataloger(t *testing.T) { reveal bool maxSize int64 patterns map[string]string - expected []SearchResult + expected []file.SearchResult constructorErr bool catalogErr bool }{ @@ -28,7 +28,7 @@ func TestSecretsCataloger(t *testing.T) { patterns: map[string]string{ "simple-secret-key": `^secret_key=.*`, }, - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "simple-secret-key", LineNumber: 2, @@ -46,7 +46,7 @@ func TestSecretsCataloger(t *testing.T) { patterns: map[string]string{ "simple-secret-key": `^secret_key=.*`, }, - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "simple-secret-key", LineNumber: 2, @@ -64,7 +64,7 @@ func TestSecretsCataloger(t *testing.T) { patterns: map[string]string{ "simple-secret-key": `^secret_key=(?P.*)`, }, - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "simple-secret-key", LineNumber: 2, @@ -82,7 +82,7 @@ func TestSecretsCataloger(t *testing.T) { patterns: map[string]string{ "simple-secret-key": `secret_key=.*`, }, - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "simple-secret-key", LineNumber: 1, @@ -125,7 +125,7 @@ func TestSecretsCataloger(t *testing.T) { patterns: map[string]string{ "simple-secret-key": `secret_key=(?P.*)`, }, - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "simple-secret-key", LineNumber: 1, @@ -176,7 +176,7 @@ func TestSecretsCataloger(t *testing.T) { regexObjs[name] = obj } - c, err := NewSecretsCataloger(regexObjs, test.reveal, test.maxSize) + c, err := NewCataloger(regexObjs, test.reveal, test.maxSize) if err != nil && !test.constructorErr { t.Fatalf("could not create cataloger (but should have been able to): %+v", err) } else if err == nil && test.constructorErr { @@ -185,7 +185,7 @@ func TestSecretsCataloger(t *testing.T) { return } - resolver := source.NewMockResolverForPaths(test.fixture) + resolver := file.NewMockResolverForPaths(test.fixture) actualResults, err := c.Catalog(resolver) if err != nil && !test.catalogErr { @@ -196,7 +196,7 @@ func TestSecretsCataloger(t *testing.T) { return } - loc := source.NewLocation(test.fixture) + loc := file.NewLocation(test.fixture) if _, exists := actualResults[loc.Coordinates]; !exists { t.Fatalf("could not find location=%q in results", loc) } @@ -214,11 +214,11 @@ func TestSecretsCataloger_DefaultSecrets(t *testing.T) { tests := []struct { fixture string - expected []SearchResult + expected []file.SearchResult }{ { fixture: "test-fixtures/secrets/default/aws.env", - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "aws-access-key", LineNumber: 2, @@ -239,7 +239,7 @@ func TestSecretsCataloger_DefaultSecrets(t *testing.T) { }, { fixture: "test-fixtures/secrets/default/aws.ini", - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "aws-access-key", LineNumber: 3, @@ -260,7 +260,7 @@ func TestSecretsCataloger_DefaultSecrets(t *testing.T) { }, { fixture: "test-fixtures/secrets/default/private-key.pem", - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "pem-private-key", LineNumber: 2, @@ -280,7 +280,7 @@ z3P668YfhUbKdRF6S42Cg6zn }, { fixture: "test-fixtures/secrets/default/private-key-openssl.pem", - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "pem-private-key", LineNumber: 2, @@ -302,7 +302,7 @@ z3P668YfhUbKdRF6S42Cg6zn // note: this test proves that the PEM regex matches the smallest possible match // since the test catches two adjacent secrets fixture: "test-fixtures/secrets/default/private-keys.pem", - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "pem-private-key", LineNumber: 1, @@ -345,7 +345,7 @@ j4f668YfhUbKdRF6S6734856 // 2. a named capture group with the correct line number and line offset case // 3. the named capture group is in a different line than the match start, and both the match start and the capture group have different line offsets fixture: "test-fixtures/secrets/default/docker-config.json", - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "docker-config-auth", LineNumber: 5, @@ -362,7 +362,7 @@ j4f668YfhUbKdRF6S6734856 }, { fixture: "test-fixtures/secrets/default/api-key.txt", - expected: []SearchResult{ + expected: []file.SearchResult{ { Classification: "generic-api-key", LineNumber: 2, @@ -418,19 +418,19 @@ j4f668YfhUbKdRF6S6734856 for _, test := range tests { t.Run(test.fixture, func(t *testing.T) { - c, err := NewSecretsCataloger(regexObjs, true, 10*file.MB) + c, err := NewCataloger(regexObjs, true, 10*intFile.MB) if err != nil { t.Fatalf("could not create cataloger: %+v", err) } - resolver := source.NewMockResolverForPaths(test.fixture) + resolver := file.NewMockResolverForPaths(test.fixture) actualResults, err := c.Catalog(resolver) if err != nil { t.Fatalf("could not catalog: %+v", err) } - loc := source.NewLocation(test.fixture) + loc := file.NewLocation(test.fixture) if _, exists := actualResults[loc.Coordinates]; !exists && test.expected != nil { t.Fatalf("could not find location=%q in results", loc) } else if !exists && test.expected == nil { diff --git a/syft/file/generate_search_patterns.go b/syft/file/cataloger/secrets/generate_search_patterns.go similarity index 98% rename from syft/file/generate_search_patterns.go rename to syft/file/cataloger/secrets/generate_search_patterns.go index 5e2c074dc35..a46ff483cc4 100644 --- a/syft/file/generate_search_patterns.go +++ b/syft/file/cataloger/secrets/generate_search_patterns.go @@ -1,4 +1,4 @@ -package file +package secrets import ( "fmt" diff --git a/syft/file/generate_search_patterns_test.go b/syft/file/cataloger/secrets/generate_search_patterns_test.go similarity index 99% rename from syft/file/generate_search_patterns_test.go rename to syft/file/cataloger/secrets/generate_search_patterns_test.go index bdd6d422bdf..37dc3441d1a 100644 --- a/syft/file/generate_search_patterns_test.go +++ b/syft/file/cataloger/secrets/generate_search_patterns_test.go @@ -1,4 +1,4 @@ -package file +package secrets import ( "testing" diff --git a/syft/file/newline_counter.go b/syft/file/cataloger/secrets/newline_counter.go similarity index 97% rename from syft/file/newline_counter.go rename to syft/file/cataloger/secrets/newline_counter.go index fec905523cd..d3c8ef894fc 100644 --- a/syft/file/newline_counter.go +++ b/syft/file/cataloger/secrets/newline_counter.go @@ -1,4 +1,4 @@ -package file +package secrets import "io" diff --git a/syft/file/newline_counter_test.go b/syft/file/cataloger/secrets/newline_counter_test.go similarity index 98% rename from syft/file/newline_counter_test.go rename to syft/file/cataloger/secrets/newline_counter_test.go index 24282bceb84..0760e892cd3 100644 --- a/syft/file/newline_counter_test.go +++ b/syft/file/cataloger/secrets/newline_counter_test.go @@ -1,4 +1,4 @@ -package file +package secrets import ( "bufio" diff --git a/syft/file/secrets_search_by_line_strategy.go b/syft/file/cataloger/secrets/secrets_search_by_line_strategy.go similarity index 86% rename from syft/file/secrets_search_by_line_strategy.go rename to syft/file/cataloger/secrets/secrets_search_by_line_strategy.go index d241846fab1..f1beeb525e7 100644 --- a/syft/file/secrets_search_by_line_strategy.go +++ b/syft/file/cataloger/secrets/secrets_search_by_line_strategy.go @@ -1,4 +1,4 @@ -package file +package secrets import ( "bufio" @@ -8,10 +8,10 @@ import ( "regexp" "github.com/anchore/syft/internal" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) -func catalogLocationByLine(resolver source.FileResolver, location source.Location, patterns map[string]*regexp.Regexp) ([]SearchResult, error) { +func catalogLocationByLine(resolver file.Resolver, location file.Location, patterns map[string]*regexp.Regexp) ([]file.SearchResult, error) { readCloser, err := resolver.FileContentsByLocation(location) if err != nil { return nil, fmt.Errorf("unable to fetch reader for location=%q : %w", location, err) @@ -20,7 +20,7 @@ func catalogLocationByLine(resolver source.FileResolver, location source.Locatio var scanner = bufio.NewReader(readCloser) var position int64 - var allSecrets []SearchResult + var allSecrets []file.SearchResult var lineNo int64 var readErr error for !errors.Is(readErr, io.EOF) { @@ -43,8 +43,8 @@ func catalogLocationByLine(resolver source.FileResolver, location source.Locatio return allSecrets, nil } -func searchForSecretsWithinLine(resolver source.FileResolver, location source.Location, patterns map[string]*regexp.Regexp, line []byte, lineNo int64, position int64) ([]SearchResult, error) { - var secrets []SearchResult +func searchForSecretsWithinLine(resolver file.Resolver, location file.Location, patterns map[string]*regexp.Regexp, line []byte, lineNo int64, position int64) ([]file.SearchResult, error) { + var secrets []file.SearchResult for name, pattern := range patterns { matches := pattern.FindAllIndex(line, -1) for i, match := range matches { @@ -72,7 +72,7 @@ func searchForSecretsWithinLine(resolver source.FileResolver, location source.Lo return secrets, nil } -func readerAtPosition(resolver source.FileResolver, location source.Location, seekPosition int64) (io.ReadCloser, error) { +func readerAtPosition(resolver file.Resolver, location file.Location, seekPosition int64) (io.ReadCloser, error) { readCloser, err := resolver.FileContentsByLocation(location) if err != nil { return nil, fmt.Errorf("unable to fetch reader for location=%q : %w", location, err) @@ -89,7 +89,7 @@ func readerAtPosition(resolver source.FileResolver, location source.Location, se return readCloser, nil } -func extractSecretFromPosition(readCloser io.ReadCloser, name string, pattern *regexp.Regexp, lineNo, lineOffset, seekPosition int64) *SearchResult { +func extractSecretFromPosition(readCloser io.ReadCloser, name string, pattern *regexp.Regexp, lineNo, lineOffset, seekPosition int64) *file.SearchResult { reader := &newlineCounter{RuneReader: bufio.NewReader(readCloser)} positions := pattern.FindReaderSubmatchIndex(reader) if len(positions) == 0 { @@ -125,7 +125,7 @@ func extractSecretFromPosition(readCloser io.ReadCloser, name string, pattern *r lineOffsetOfSecret += lineOffset } - return &SearchResult{ + return &file.SearchResult{ Classification: name, SeekPosition: start + seekPosition, Length: stop - start, diff --git a/syft/file/test-fixtures/secrets/default/api-key.txt b/syft/file/cataloger/secrets/test-fixtures/secrets/default/api-key.txt similarity index 100% rename from syft/file/test-fixtures/secrets/default/api-key.txt rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/api-key.txt diff --git a/syft/file/test-fixtures/secrets/default/aws.env b/syft/file/cataloger/secrets/test-fixtures/secrets/default/aws.env similarity index 100% rename from syft/file/test-fixtures/secrets/default/aws.env rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/aws.env diff --git a/syft/file/test-fixtures/secrets/default/aws.ini b/syft/file/cataloger/secrets/test-fixtures/secrets/default/aws.ini similarity index 100% rename from syft/file/test-fixtures/secrets/default/aws.ini rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/aws.ini diff --git a/syft/file/test-fixtures/secrets/default/docker-config.json b/syft/file/cataloger/secrets/test-fixtures/secrets/default/docker-config.json similarity index 100% rename from syft/file/test-fixtures/secrets/default/docker-config.json rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/docker-config.json diff --git a/syft/file/test-fixtures/secrets/default/not-docker-config.json b/syft/file/cataloger/secrets/test-fixtures/secrets/default/not-docker-config.json similarity index 100% rename from syft/file/test-fixtures/secrets/default/not-docker-config.json rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/not-docker-config.json diff --git a/syft/file/test-fixtures/secrets/default/private-key-false-positive.pem b/syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key-false-positive.pem similarity index 100% rename from syft/file/test-fixtures/secrets/default/private-key-false-positive.pem rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key-false-positive.pem diff --git a/syft/file/test-fixtures/secrets/default/private-key-openssl.pem b/syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key-openssl.pem similarity index 100% rename from syft/file/test-fixtures/secrets/default/private-key-openssl.pem rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key-openssl.pem diff --git a/syft/file/test-fixtures/secrets/default/private-key.pem b/syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key.pem similarity index 100% rename from syft/file/test-fixtures/secrets/default/private-key.pem rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key.pem diff --git a/syft/file/test-fixtures/secrets/default/private-keys.pem b/syft/file/cataloger/secrets/test-fixtures/secrets/default/private-keys.pem similarity index 100% rename from syft/file/test-fixtures/secrets/default/private-keys.pem rename to syft/file/cataloger/secrets/test-fixtures/secrets/default/private-keys.pem diff --git a/syft/file/test-fixtures/secrets/multiple.txt b/syft/file/cataloger/secrets/test-fixtures/secrets/multiple.txt similarity index 100% rename from syft/file/test-fixtures/secrets/multiple.txt rename to syft/file/cataloger/secrets/test-fixtures/secrets/multiple.txt diff --git a/syft/file/test-fixtures/secrets/simple.txt b/syft/file/cataloger/secrets/test-fixtures/secrets/simple.txt similarity index 100% rename from syft/file/test-fixtures/secrets/simple.txt rename to syft/file/cataloger/secrets/test-fixtures/secrets/simple.txt diff --git a/syft/file/contents_cataloger_test.go b/syft/file/contents_cataloger_test.go deleted file mode 100644 index 526baae5c33..00000000000 --- a/syft/file/contents_cataloger_test.go +++ /dev/null @@ -1,80 +0,0 @@ -package file - -import ( - "testing" - - "github.com/stretchr/testify/assert" - - "github.com/anchore/syft/syft/source" -) - -func TestContentsCataloger(t *testing.T) { - allFiles := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"} - - tests := []struct { - name string - globs []string - maxSize int64 - files []string - expected map[source.Coordinates]string - }{ - { - name: "multi-pattern", - globs: []string{"test-fixtures/last/*.txt", "test-fixtures/*.txt"}, - files: allFiles, - expected: map[source.Coordinates]string{ - source.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh", - source.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh", - source.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh", - }, - }, - { - name: "no-patterns", - globs: []string{}, - files: []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"}, - expected: map[source.Coordinates]string{}, - }, - { - name: "all-txt", - globs: []string{"**/*.txt"}, - files: allFiles, - expected: map[source.Coordinates]string{ - source.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh", - source.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh", - source.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh", - }, - }, - { - name: "subpath", - globs: []string{"test-fixtures/*.txt"}, - files: allFiles, - expected: map[source.Coordinates]string{ - source.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh", - source.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh", - }, - }, - { - name: "size-filter", - maxSize: 42, - globs: []string{"**/*.txt"}, - files: allFiles, - expected: map[source.Coordinates]string{ - source.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh", - source.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh", - }, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - c, err := NewContentsCataloger(test.globs, test.maxSize) - assert.NoError(t, err) - - resolver := source.NewMockResolverForPaths(test.files...) - actual, err := c.Catalog(resolver) - assert.NoError(t, err) - assert.Equal(t, test.expected, actual, "mismatched contents") - - }) - } -} diff --git a/syft/source/coordinate_set.go b/syft/file/coordinate_set.go similarity index 99% rename from syft/source/coordinate_set.go rename to syft/file/coordinate_set.go index 0ae080c270f..ba56005d302 100644 --- a/syft/source/coordinate_set.go +++ b/syft/file/coordinate_set.go @@ -1,4 +1,4 @@ -package source +package file import ( "sort" diff --git a/syft/source/coordinate_set_test.go b/syft/file/coordinate_set_test.go similarity index 99% rename from syft/source/coordinate_set_test.go rename to syft/file/coordinate_set_test.go index 7f50a61ffd2..6fae658e76b 100644 --- a/syft/source/coordinate_set_test.go +++ b/syft/file/coordinate_set_test.go @@ -1,4 +1,4 @@ -package source +package file import ( "testing" diff --git a/syft/source/coordinates.go b/syft/file/coordinates.go similarity index 98% rename from syft/source/coordinates.go rename to syft/file/coordinates.go index c35d3dcc297..24ba486ae91 100644 --- a/syft/source/coordinates.go +++ b/syft/file/coordinates.go @@ -1,4 +1,4 @@ -package source +package file import ( "fmt" diff --git a/syft/file/digest.go b/syft/file/digest.go index 87b53dbb833..23219e68875 100644 --- a/syft/file/digest.go +++ b/syft/file/digest.go @@ -1,6 +1,76 @@ package file +import ( + "crypto" + "fmt" + "hash" + "io" + "strings" +) + type Digest struct { Algorithm string `json:"algorithm"` Value string `json:"value"` } + +func NewDigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]Digest, error) { + // create a set of hasher objects tied together with a single writer to feed content into + hashers := make([]hash.Hash, len(hashes)) + writers := make([]io.Writer, len(hashes)) + for idx, hashObj := range hashes { + hashers[idx] = hashObj.New() + writers[idx] = hashers[idx] + } + + size, err := io.Copy(io.MultiWriter(writers...), closer) + if err != nil { + return nil, err + } + + if size == 0 { + return make([]Digest, 0), nil + } + + result := make([]Digest, len(hashes)) + // only capture digests when there is content. It is important to do this based on SIZE and not + // FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only + // file type but a body is still allowed. + for idx, hasher := range hashers { + result[idx] = Digest{ + Algorithm: DigestAlgorithmName(hashes[idx]), + Value: fmt.Sprintf("%+x", hasher.Sum(nil)), + } + } + + return result, nil +} + +func Hashers(names ...string) ([]crypto.Hash, error) { + supportedHashAlgorithms := make(map[string]crypto.Hash) + for _, h := range []crypto.Hash{ + crypto.MD5, + crypto.SHA1, + crypto.SHA256, + } { + supportedHashAlgorithms[DigestAlgorithmName(h)] = h + } + + var hashers []crypto.Hash + for _, hashStr := range names { + hashObj, ok := supportedHashAlgorithms[CleanDigestAlgorithmName(hashStr)] + if !ok { + return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr) + } + hashers = append(hashers, hashObj) + } + return hashers, nil +} + +func DigestAlgorithmName(hash crypto.Hash) string { + return CleanDigestAlgorithmName(hash.String()) +} + +func CleanDigestAlgorithmName(name string) string { + lower := strings.ToLower(name) + return strings.ReplaceAll(lower, "-", "") +} diff --git a/syft/file/digest_cataloger.go b/syft/file/digest_cataloger.go deleted file mode 100644 index db2d468a31f..00000000000 --- a/syft/file/digest_cataloger.go +++ /dev/null @@ -1,140 +0,0 @@ -package file - -import ( - "crypto" - "errors" - "fmt" - "hash" - "io" - "strings" - - "github.com/wagoodman/go-partybus" - "github.com/wagoodman/go-progress" - - "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/syft/internal" - "github.com/anchore/syft/internal/bus" - "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/event" - "github.com/anchore/syft/syft/source" -) - -var errUndigestableFile = errors.New("undigestable file") - -type DigestsCataloger struct { - hashes []crypto.Hash -} - -func NewDigestsCataloger(hashes []crypto.Hash) (*DigestsCataloger, error) { - return &DigestsCataloger{ - hashes: hashes, - }, nil -} - -func (i *DigestsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]Digest, error) { - results := make(map[source.Coordinates][]Digest) - locations := allRegularFiles(resolver) - stage, prog := digestsCatalogingProgress(int64(len(locations))) - for _, location := range locations { - stage.Current = location.RealPath - result, err := i.catalogLocation(resolver, location) - - if errors.Is(err, errUndigestableFile) { - continue - } - - if internal.IsErrPathPermission(err) { - log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err) - continue - } - - if err != nil { - return nil, err - } - prog.Increment() - results[location.Coordinates] = result - } - log.Debugf("file digests cataloger processed %d files", prog.Current()) - prog.SetCompleted() - return results, nil -} - -func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) ([]Digest, error) { - meta, err := resolver.FileMetadataByLocation(location) - if err != nil { - return nil, err - } - - // we should only attempt to report digests for files that are regular files (don't attempt to resolve links) - if meta.Type != file.TypeRegular { - return nil, errUndigestableFile - } - - contentReader, err := resolver.FileContentsByLocation(location) - if err != nil { - return nil, err - } - defer internal.CloseAndLogError(contentReader, location.VirtualPath) - - digests, err := DigestsFromFile(contentReader, i.hashes) - if err != nil { - return nil, internal.ErrPath{Context: "digests-cataloger", Path: location.RealPath, Err: err} - } - - return digests, nil -} - -func DigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]Digest, error) { - // create a set of hasher objects tied together with a single writer to feed content into - hashers := make([]hash.Hash, len(hashes)) - writers := make([]io.Writer, len(hashes)) - for idx, hashObj := range hashes { - hashers[idx] = hashObj.New() - writers[idx] = hashers[idx] - } - - _, err := io.Copy(io.MultiWriter(writers...), closer) - if err != nil { - return nil, err - } - - result := make([]Digest, len(hashes)) - // only capture digests when there is content. It is important to do this based on SIZE and not - // FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only - // file type but a body is still allowed. - for idx, hasher := range hashers { - result[idx] = Digest{ - Algorithm: DigestAlgorithmName(hashes[idx]), - Value: fmt.Sprintf("%+x", hasher.Sum(nil)), - } - } - - return result, nil -} - -func DigestAlgorithmName(hash crypto.Hash) string { - return CleanDigestAlgorithmName(hash.String()) -} - -func CleanDigestAlgorithmName(name string) string { - lower := strings.ToLower(name) - return strings.ReplaceAll(lower, "-", "") -} - -func digestsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) { - stage := &progress.Stage{} - prog := progress.NewManual(locations) - - bus.Publish(partybus.Event{ - Type: event.FileDigestsCatalogerStarted, - Value: struct { - progress.Stager - progress.Progressable - }{ - Stager: progress.Stager(stage), - Progressable: prog, - }, - }) - - return stage, prog -} diff --git a/syft/source/location.go b/syft/file/location.go similarity index 94% rename from syft/source/location.go rename to syft/file/location.go index 3abadd3fb1f..65af91c5164 100644 --- a/syft/source/location.go +++ b/syft/file/location.go @@ -1,4 +1,4 @@ -package source +package file import ( "fmt" @@ -24,6 +24,10 @@ type LocationData struct { ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location. } +func (l LocationData) Reference() file.Reference { + return l.ref +} + type LocationMetadata struct { Annotations map[string]string `json:"annotations,omitempty"` // Arbitrary key-value pairs that can be used to annotate a location } @@ -108,7 +112,7 @@ func NewVirtualLocationFromCoordinates(coordinates Coordinates, virtualPath stri }} } -// NewLocationFromImage creates a new Location representing the given path (extracted from the ref) relative to the given image. +// NewLocationFromImage creates a new Location representing the given path (extracted from the Reference) relative to the given image. func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Image) Location { layer := img.FileCatalog.Layer(ref) return Location{ @@ -126,7 +130,7 @@ func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Ima } } -// NewLocationFromDirectory creates a new Location representing the given path (extracted from the ref) relative to the given directory. +// NewLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory. func NewLocationFromDirectory(responsePath string, ref file.Reference) Location { return Location{ LocationData: LocationData{ @@ -141,7 +145,7 @@ func NewLocationFromDirectory(responsePath string, ref file.Reference) Location } } -// NewVirtualLocationFromDirectory creates a new Location representing the given path (extracted from the ref) relative to the given directory with a separate virtual access path. +// NewVirtualLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory with a separate virtual access path. func NewVirtualLocationFromDirectory(responsePath, virtualResponsePath string, ref file.Reference) Location { if responsePath == virtualResponsePath { return NewLocationFromDirectory(responsePath, ref) diff --git a/syft/source/location_read_closer.go b/syft/file/location_read_closer.go similarity index 94% rename from syft/source/location_read_closer.go rename to syft/file/location_read_closer.go index b5aa2b6efb5..480a0b50fe1 100644 --- a/syft/source/location_read_closer.go +++ b/syft/file/location_read_closer.go @@ -1,4 +1,4 @@ -package source +package file import "io" diff --git a/syft/source/location_set.go b/syft/file/location_set.go similarity index 99% rename from syft/source/location_set.go rename to syft/file/location_set.go index 100bf95e5d4..8e9ed2fc1f4 100644 --- a/syft/source/location_set.go +++ b/syft/file/location_set.go @@ -1,4 +1,4 @@ -package source +package file import ( "sort" diff --git a/syft/source/location_set_test.go b/syft/file/location_set_test.go similarity index 99% rename from syft/source/location_set_test.go rename to syft/file/location_set_test.go index b3d53ae5856..1613c71a539 100644 --- a/syft/source/location_set_test.go +++ b/syft/file/location_set_test.go @@ -1,4 +1,4 @@ -package source +package file import ( "testing" diff --git a/syft/source/location_test.go b/syft/file/location_test.go similarity index 98% rename from syft/source/location_test.go rename to syft/file/location_test.go index 96f0e3fcd3d..261ae9caa0f 100644 --- a/syft/source/location_test.go +++ b/syft/file/location_test.go @@ -1,4 +1,4 @@ -package source +package file import ( "testing" diff --git a/syft/source/locations.go b/syft/file/locations.go similarity index 96% rename from syft/source/locations.go rename to syft/file/locations.go index 045d1ed9d3c..da298643ec9 100644 --- a/syft/source/locations.go +++ b/syft/file/locations.go @@ -1,4 +1,4 @@ -package source +package file type Locations []Location diff --git a/syft/file/metadata.go b/syft/file/metadata.go new file mode 100644 index 00000000000..b5e0669d59c --- /dev/null +++ b/syft/file/metadata.go @@ -0,0 +1,5 @@ +package file + +import "github.com/anchore/stereoscope/pkg/file" + +type Metadata = file.Metadata diff --git a/syft/source/mock_resolver.go b/syft/file/mock_resolver.go similarity index 94% rename from syft/source/mock_resolver.go rename to syft/file/mock_resolver.go index 12cab882b86..7a0f89ffd00 100644 --- a/syft/source/mock_resolver.go +++ b/syft/file/mock_resolver.go @@ -1,4 +1,4 @@ -package source +package file import ( "fmt" @@ -11,14 +11,14 @@ import ( "github.com/anchore/stereoscope/pkg/file" ) -var _ FileResolver = (*MockResolver)(nil) +var _ Resolver = (*MockResolver)(nil) // MockResolver implements the FileResolver interface and is intended for use *only in test code*. // It provides an implementation that can resolve local filesystem paths using only a provided discrete list of file // paths, which are typically paths to test fixtures. type MockResolver struct { locations []Location - metadata map[Coordinates]FileMetadata + metadata map[Coordinates]Metadata mimeTypeIndex map[string][]Location extension map[string][]Location basename map[string][]Location @@ -41,13 +41,13 @@ func NewMockResolverForPaths(paths ...string) *MockResolver { return &MockResolver{ locations: locations, - metadata: make(map[Coordinates]FileMetadata), + metadata: make(map[Coordinates]Metadata), extension: extension, basename: basename, } } -func NewMockResolverForPathsWithMetadata(metadata map[Coordinates]FileMetadata) *MockResolver { +func NewMockResolverForPathsWithMetadata(metadata map[Coordinates]Metadata) *MockResolver { var locations []Location var mimeTypeIndex = make(map[string][]Location) extension := make(map[string][]Location) @@ -155,10 +155,10 @@ func (r MockResolver) AllLocations() <-chan Location { return results } -func (r MockResolver) FileMetadataByLocation(l Location) (FileMetadata, error) { +func (r MockResolver) FileMetadataByLocation(l Location) (Metadata, error) { info, err := os.Stat(l.RealPath) if err != nil { - return FileMetadata{}, err + return Metadata{}, err } // other types not supported @@ -167,7 +167,7 @@ func (r MockResolver) FileMetadataByLocation(l Location) (FileMetadata, error) { ty = file.TypeDirectory } - return FileMetadata{ + return Metadata{ FileInfo: info, Type: ty, UserID: 0, // not supported diff --git a/syft/source/file_resolver.go b/syft/file/resolver.go similarity index 75% rename from syft/source/file_resolver.go rename to syft/file/resolver.go index 414be6373f9..57726b9b22c 100644 --- a/syft/source/file_resolver.go +++ b/syft/file/resolver.go @@ -1,28 +1,26 @@ -package source +package file -import ( - "io" -) +import "io" -// FileResolver is an interface that encompasses how to get specific file references and file contents for a generic data source. -type FileResolver interface { - FileContentResolver - FilePathResolver - FileLocationResolver - FileMetadataResolver +// Resolver is an interface that encompasses how to get specific file references and file contents for a generic data source. +type Resolver interface { + ContentResolver + PathResolver + LocationResolver + MetadataResolver } -// FileContentResolver knows how to get file content for a given Location -type FileContentResolver interface { +// ContentResolver knows how to get file content for a given Location +type ContentResolver interface { FileContentsByLocation(Location) (io.ReadCloser, error) } -type FileMetadataResolver interface { - FileMetadataByLocation(Location) (FileMetadata, error) +type MetadataResolver interface { + FileMetadataByLocation(Location) (Metadata, error) } -// FilePathResolver knows how to get a Location for given string paths and globs -type FilePathResolver interface { +// PathResolver knows how to get a Location for given string paths and globs +type PathResolver interface { // HasPath indicates if the given path exists in the underlying source. // The implementation for this may vary, however, generally the following considerations should be made: // - full symlink resolution should be performed on all requests @@ -50,7 +48,7 @@ type FilePathResolver interface { RelativeFileByPath(_ Location, path string) *Location } -type FileLocationResolver interface { +type LocationResolver interface { // AllLocations returns a channel of all file references from the underlying source. // The implementation for this may vary, however, generally the following considerations should be made: // - NO symlink resolution should be performed on results @@ -58,8 +56,8 @@ type FileLocationResolver interface { AllLocations() <-chan Location } -type WritableFileResolver interface { - FileResolver +type WritableResolver interface { + Resolver Write(location Location, reader io.Reader) error } diff --git a/syft/file/test-fixtures/snapshot/stereoscope-fixture-image-file-type-mix.golden b/syft/file/test-fixtures/snapshot/stereoscope-fixture-image-file-type-mix.golden deleted file mode 100644 index e85036214d92338a13d636bbae3906d69d5d2c09..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1731072 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 f9Uq>b;5rTc`Y@!m3wiZXrPzw79+ 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 NhuS@3+hrD7==dw;VklHW% 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

VgwdnJq_z!Cifpq7K~EOvCo1UOYSA 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 eha@jY9LS0j2vauS3);t2V(| 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

<1*d} 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 0; i-- { @@ -437,7 +436,7 @@ func toFiles(s sbom.SBOM) (results []*spdx.File) { artifacts := s.Artifacts for _, coordinates := range s.AllCoordinates() { - var metadata *source.FileMetadata + var metadata *file.Metadata if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists { metadata = &metadataForLocation } @@ -500,7 +499,7 @@ func toChecksumAlgorithm(algorithm string) spdx.ChecksumAlgorithm { return spdx.ChecksumAlgorithm(strings.ToUpper(algorithm)) } -func toFileTypes(metadata *source.FileMetadata) (ty []string) { +func toFileTypes(metadata *file.Metadata) (ty []string) { if metadata == nil { return nil } diff --git a/syft/formats/common/spdxhelpers/to_format_model_test.go b/syft/formats/common/spdxhelpers/to_format_model_test.go index 170de95ea8c..e36e29435d1 100644 --- a/syft/formats/common/spdxhelpers/to_format_model_test.go +++ b/syft/formats/common/spdxhelpers/to_format_model_test.go @@ -115,12 +115,12 @@ func Test_toFileTypes(t *testing.T) { tests := []struct { name string - metadata source.FileMetadata + metadata file.Metadata expected []string }{ { name: "application", - metadata: source.FileMetadata{ + metadata: file.Metadata{ MIMEType: "application/vnd.unknown", }, expected: []string{ @@ -129,7 +129,7 @@ func Test_toFileTypes(t *testing.T) { }, { name: "archive", - metadata: source.FileMetadata{ + metadata: file.Metadata{ MIMEType: "application/zip", }, expected: []string{ @@ -139,7 +139,7 @@ func Test_toFileTypes(t *testing.T) { }, { name: "audio", - metadata: source.FileMetadata{ + metadata: file.Metadata{ MIMEType: "audio/ogg", }, expected: []string{ @@ -148,7 +148,7 @@ func Test_toFileTypes(t *testing.T) { }, { name: "video", - metadata: source.FileMetadata{ + metadata: file.Metadata{ MIMEType: "video/3gpp", }, expected: []string{ @@ -157,7 +157,7 @@ func Test_toFileTypes(t *testing.T) { }, { name: "text", - metadata: source.FileMetadata{ + metadata: file.Metadata{ MIMEType: "text/html", }, expected: []string{ @@ -166,7 +166,7 @@ func Test_toFileTypes(t *testing.T) { }, { name: "image", - metadata: source.FileMetadata{ + metadata: file.Metadata{ MIMEType: "image/png", }, expected: []string{ @@ -175,7 +175,7 @@ func Test_toFileTypes(t *testing.T) { }, { name: "binary", - metadata: source.FileMetadata{ + metadata: file.Metadata{ MIMEType: "application/x-sharedlib", }, expected: []string{ @@ -276,7 +276,7 @@ func Test_fileIDsForPackage(t *testing.T) { Name: "bogus", } - c := source.Coordinates{ + c := file.Coordinates{ RealPath: "/path", FileSystemID: "nowhere", } diff --git a/syft/formats/common/spdxhelpers/to_syft_model.go b/syft/formats/common/spdxhelpers/to_syft_model.go index a31cee81218..fd34541df99 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model.go +++ b/syft/formats/common/spdxhelpers/to_syft_model.go @@ -35,8 +35,8 @@ func ToSyftModel(doc *spdx.Document) (*sbom.SBOM, error) { Source: src, Artifacts: sbom.Artifacts{ Packages: pkg.NewCollection(), - FileMetadata: map[source.Coordinates]source.FileMetadata{}, - FileDigests: map[source.Coordinates][]file.Digest{}, + FileMetadata: map[file.Coordinates]file.Metadata{}, + FileDigests: map[file.Coordinates][]file.Digest{}, LinuxDistribution: findLinuxReleaseByPURL(doc), }, } @@ -135,7 +135,7 @@ func toFileDigests(f *spdx.File) (digests []file.Digest) { return digests } -func toFileMetadata(f *spdx.File) (meta source.FileMetadata) { +func toFileMetadata(f *spdx.File) (meta file.Metadata) { // FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes for _, typ := range f.FileTypes { switch FileType(typ) { @@ -169,7 +169,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) [ b := spdxIDMap[string(r.RefB.ElementRefID)] from, fromOk := a.(*pkg.Package) toPackage, toPackageOk := b.(*pkg.Package) - toLocation, toLocationOk := b.(*source.Location) + toLocation, toLocationOk := b.(*file.Location) if !fromOk || !(toPackageOk || toLocationOk) { log.Debugf("unable to find valid relationship mapping from SPDX 2.2 JSON, ignoring: (from: %+v) (to: %+v)", a, b) continue @@ -212,7 +212,7 @@ func toSyftRelationships(spdxIDMap map[string]interface{}, doc *spdx.Document) [ return out } -func toSyftCoordinates(f *spdx.File) source.Coordinates { +func toSyftCoordinates(f *spdx.File) file.Coordinates { const layerIDPrefix = "layerID: " var fileSystemID string if strings.Index(f.FileComment, layerIDPrefix) == 0 { @@ -221,14 +221,14 @@ func toSyftCoordinates(f *spdx.File) source.Coordinates { if strings.Index(string(f.FileSPDXIdentifier), layerIDPrefix) == 0 { fileSystemID = strings.TrimPrefix(string(f.FileSPDXIdentifier), layerIDPrefix) } - return source.Coordinates{ + return file.Coordinates{ RealPath: f.FileName, FileSystemID: fileSystemID, } } -func toSyftLocation(f *spdx.File) *source.Location { - l := source.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName) +func toSyftLocation(f *spdx.File) *file.Location { + l := file.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName) return &l } diff --git a/syft/formats/common/spdxhelpers/to_syft_model_test.go b/syft/formats/common/spdxhelpers/to_syft_model_test.go index a4b5c1e81d9..e4a98f5abd2 100644 --- a/syft/formats/common/spdxhelpers/to_syft_model_test.go +++ b/syft/formats/common/spdxhelpers/to_syft_model_test.go @@ -9,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" ) @@ -336,7 +337,7 @@ func Test_toSyftRelationships(t *testing.T) { } pkg3.SetID() - loc1 := source.NewLocationFromCoordinates(source.Coordinates{ + loc1 := file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/somewhere/real", FileSystemID: "abc", }) diff --git a/syft/formats/github/encoder_test.go b/syft/formats/github/encoder_test.go index ba405dad63c..a0770f2520e 100644 --- a/syft/formats/github/encoder_test.go +++ b/syft/formats/github/encoder_test.go @@ -7,6 +7,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/sbom" @@ -35,8 +36,8 @@ func Test_toGithubModel(t *testing.T) { { Name: "pkg-1", Version: "1.0.1", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/usr/lib", FileSystemID: "fsid-1", }), @@ -45,8 +46,8 @@ func Test_toGithubModel(t *testing.T) { { Name: "pkg-2", Version: "2.0.2", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/usr/lib", FileSystemID: "fsid-1", }), @@ -55,8 +56,8 @@ func Test_toGithubModel(t *testing.T) { { Name: "pkg-3", Version: "3.0.3", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/etc", FileSystemID: "fsid-1", }), diff --git a/syft/formats/internal/testutils/utils.go b/syft/formats/internal/testutils/utils.go index 7ddf942173b..f9f4941d4e0 100644 --- a/syft/formats/internal/testutils/utils.go +++ b/syft/formats/internal/testutils/utils.go @@ -17,6 +17,7 @@ import ( "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/sbom" @@ -155,8 +156,8 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) { catalog.Add(pkg.Package{ Name: "package-1", Version: "1.0.1", - Locations: source.NewLocationSet( - source.NewLocationFromImage(string(ref1.RealPath), *ref1.Reference, img), + Locations: file.NewLocationSet( + file.NewLocationFromImage(string(ref1.RealPath), *ref1.Reference, img), ), Type: pkg.PythonPkg, FoundBy: "the-cataloger-1", @@ -177,8 +178,8 @@ func populateImageCatalog(catalog *pkg.Collection, img *image.Image) { catalog.Add(pkg.Package{ Name: "package-2", Version: "2.0.1", - Locations: source.NewLocationSet( - source.NewLocationFromImage(string(ref2.RealPath), *ref2.Reference, img), + Locations: file.NewLocationSet( + file.NewLocationFromImage(string(ref2.RealPath), *ref2.Reference, img), ), Type: pkg.DebPkg, FoundBy: "the-cataloger-2", @@ -265,8 +266,8 @@ func newDirectoryCatalog() *pkg.Collection { Version: "1.0.1", Type: pkg.PythonPkg, FoundBy: "the-cataloger-1", - Locations: source.NewLocationSet( - source.NewLocation("/some/path/pkg1"), + Locations: file.NewLocationSet( + file.NewLocation("/some/path/pkg1"), ), Language: pkg.Python, MetadataType: pkg.PythonPackageMetadataType, @@ -292,8 +293,8 @@ func newDirectoryCatalog() *pkg.Collection { Version: "2.0.1", Type: pkg.DebPkg, FoundBy: "the-cataloger-2", - Locations: source.NewLocationSet( - source.NewLocation("/some/path/pkg1"), + Locations: file.NewLocationSet( + file.NewLocation("/some/path/pkg1"), ), MetadataType: pkg.DpkgMetadataType, Metadata: pkg.DpkgMetadata{ @@ -318,8 +319,8 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection { Version: "1.0.1", Type: pkg.PythonPkg, FoundBy: "the-cataloger-1", - Locations: source.NewLocationSet( - source.NewLocation("/some/path/pkg1"), + Locations: file.NewLocationSet( + file.NewLocation("/some/path/pkg1"), ), Language: pkg.Python, MetadataType: pkg.PythonPackageMetadataType, @@ -346,8 +347,8 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection { Version: "2.0.1", Type: pkg.DebPkg, FoundBy: "the-cataloger-2", - Locations: source.NewLocationSet( - source.NewLocation("/some/path/pkg1"), + Locations: file.NewLocationSet( + file.NewLocation("/some/path/pkg1"), ), MetadataType: pkg.DpkgMetadataType, Metadata: pkg.DpkgMetadata{ @@ -366,15 +367,15 @@ func newDirectoryCatalogWithAuthorField() *pkg.Collection { //nolint:gosec func AddSampleFileRelationships(s *sbom.SBOM) { catalog := s.Artifacts.Packages.Sorted() - s.Artifacts.FileMetadata = map[source.Coordinates]source.FileMetadata{} + s.Artifacts.FileMetadata = map[file.Coordinates]file.Metadata{} files := []string{"/f1", "/f2", "/d1/f3", "/d2/f4", "/z1/f5", "/a1/f6"} rnd := rand.New(rand.NewSource(time.Now().UnixNano())) rnd.Shuffle(len(files), func(i, j int) { files[i], files[j] = files[j], files[i] }) for _, f := range files { - meta := source.FileMetadata{} - coords := source.Coordinates{RealPath: f} + meta := file.Metadata{} + coords := file.Coordinates{RealPath: f} s.Artifacts.FileMetadata[coords] = meta s.Relationships = append(s.Relationships, artifact.Relationship{ diff --git a/syft/formats/syftjson/encoder_test.go b/syft/formats/syftjson/encoder_test.go index 5b9a0f25c92..231333bb869 100644 --- a/syft/formats/syftjson/encoder_test.go +++ b/syft/formats/syftjson/encoder_test.go @@ -52,8 +52,8 @@ func TestEncodeFullJSONDocument(t *testing.T) { p1 := pkg.Package{ Name: "package-1", Version: "1.0.1", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/a/place/a", }), ), @@ -76,8 +76,8 @@ func TestEncodeFullJSONDocument(t *testing.T) { p2 := pkg.Package{ Name: "package-2", Version: "2.0.1", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates(source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates(file.Coordinates{ RealPath: "/b/place/b", }), ), @@ -101,8 +101,8 @@ func TestEncodeFullJSONDocument(t *testing.T) { s := sbom.SBOM{ Artifacts: sbom.Artifacts{ Packages: catalog, - FileMetadata: map[source.Coordinates]source.FileMetadata{ - source.NewLocation("/a/place").Coordinates: { + FileMetadata: map[file.Coordinates]file.Metadata{ + file.NewLocation("/a/place").Coordinates: { FileInfo: stereoFile.ManualInfo{ NameValue: "/a/place", ModeValue: 0775, @@ -111,7 +111,7 @@ func TestEncodeFullJSONDocument(t *testing.T) { UserID: 0, GroupID: 0, }, - source.NewLocation("/a/place/a").Coordinates: { + file.NewLocation("/a/place/a").Coordinates: { FileInfo: stereoFile.ManualInfo{ NameValue: "/a/place/a", ModeValue: 0775, @@ -120,7 +120,7 @@ func TestEncodeFullJSONDocument(t *testing.T) { UserID: 0, GroupID: 0, }, - source.NewLocation("/b").Coordinates: { + file.NewLocation("/b").Coordinates: { FileInfo: stereoFile.ManualInfo{ NameValue: "/b", ModeValue: 0775, @@ -130,7 +130,7 @@ func TestEncodeFullJSONDocument(t *testing.T) { UserID: 0, GroupID: 0, }, - source.NewLocation("/b/place/b").Coordinates: { + file.NewLocation("/b/place/b").Coordinates: { FileInfo: stereoFile.ManualInfo{ NameValue: "/b/place/b", ModeValue: 0644, @@ -140,22 +140,22 @@ func TestEncodeFullJSONDocument(t *testing.T) { GroupID: 2, }, }, - FileDigests: map[source.Coordinates][]file.Digest{ - source.NewLocation("/a/place/a").Coordinates: { + FileDigests: map[file.Coordinates][]file.Digest{ + file.NewLocation("/a/place/a").Coordinates: { { Algorithm: "sha256", Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703", }, }, - source.NewLocation("/b/place/b").Coordinates: { + file.NewLocation("/b/place/b").Coordinates: { { Algorithm: "sha256", Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c", }, }, }, - FileContents: map[source.Coordinates]string{ - source.NewLocation("/a/place/a").Coordinates: "the-contents", + FileContents: map[file.Coordinates]string{ + file.NewLocation("/a/place/a").Coordinates: "the-contents", }, LinuxDistribution: &linux.Release{ ID: "redhat", diff --git a/syft/formats/syftjson/model/file.go b/syft/formats/syftjson/model/file.go index 796cecebf1c..757a293154a 100644 --- a/syft/formats/syftjson/model/file.go +++ b/syft/formats/syftjson/model/file.go @@ -2,12 +2,11 @@ package model import ( "github.com/anchore/syft/syft/file" - "github.com/anchore/syft/syft/source" ) type File struct { ID string `json:"id"` - Location source.Coordinates `json:"location"` + Location file.Coordinates `json:"location"` Metadata *FileMetadataEntry `json:"metadata,omitempty"` Contents string `json:"contents,omitempty"` Digests []file.Digest `json:"digests,omitempty"` diff --git a/syft/formats/syftjson/model/package.go b/syft/formats/syftjson/model/package.go index c4fc9580467..fccf04c0bda 100644 --- a/syft/formats/syftjson/model/package.go +++ b/syft/formats/syftjson/model/package.go @@ -7,9 +7,9 @@ import ( "reflect" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) var errUnknownMetadataType = errors.New("unknown metadata type") @@ -22,26 +22,26 @@ type Package struct { // PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package. type PackageBasicData struct { - ID string `json:"id"` - Name string `json:"name"` - Version string `json:"version"` - Type pkg.Type `json:"type"` - FoundBy string `json:"foundBy"` - Locations []source.Location `json:"locations"` - Licenses licenses `json:"licenses"` - Language pkg.Language `json:"language"` - CPEs []string `json:"cpes"` - PURL string `json:"purl"` + ID string `json:"id"` + Name string `json:"name"` + Version string `json:"version"` + Type pkg.Type `json:"type"` + FoundBy string `json:"foundBy"` + Locations []file.Location `json:"locations"` + Licenses licenses `json:"licenses"` + Language pkg.Language `json:"language"` + CPEs []string `json:"cpes"` + PURL string `json:"purl"` } type licenses []License type License struct { - Value string `json:"value"` - SPDXExpression string `json:"spdxExpression"` - Type license.Type `json:"type"` - URLs []string `json:"urls"` - Locations []source.Location `json:"locations"` + Value string `json:"value"` + SPDXExpression string `json:"spdxExpression"` + Type license.Type `json:"type"` + URLs []string `json:"urls"` + Locations []file.Location `json:"locations"` } func newModelLicensesFromValues(licenses []string) (ml []License) { diff --git a/syft/formats/syftjson/model/secrets.go b/syft/formats/syftjson/model/secrets.go index c5f4685765d..5562b76bb2c 100644 --- a/syft/formats/syftjson/model/secrets.go +++ b/syft/formats/syftjson/model/secrets.go @@ -2,10 +2,9 @@ package model import ( "github.com/anchore/syft/syft/file" - "github.com/anchore/syft/syft/source" ) type Secrets struct { - Location source.Coordinates `json:"location"` + Location file.Coordinates `json:"location"` Secrets []file.SearchResult `json:"secrets"` } diff --git a/syft/formats/syftjson/to_format_model.go b/syft/formats/syftjson/to_format_model.go index efddddde268..7b3688ced69 100644 --- a/syft/formats/syftjson/to_format_model.go +++ b/syft/formats/syftjson/to_format_model.go @@ -74,7 +74,7 @@ func toDescriptor(d sbom.Descriptor) model.Descriptor { } } -func toSecrets(data map[source.Coordinates][]file.SearchResult) []model.Secrets { +func toSecrets(data map[file.Coordinates][]file.SearchResult) []model.Secrets { results := make([]model.Secrets, 0) for coordinates, secrets := range data { results = append(results, model.Secrets{ @@ -95,7 +95,7 @@ func toFile(s sbom.SBOM) []model.File { artifacts := s.Artifacts for _, coordinates := range s.AllCoordinates() { - var metadata *source.FileMetadata + var metadata *file.Metadata if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists { metadata = &metadataForLocation } @@ -126,7 +126,7 @@ func toFile(s sbom.SBOM) []model.File { return results } -func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMetadata) *model.FileMetadataEntry { +func toFileMetadataEntry(coordinates file.Coordinates, metadata *file.Metadata) *model.FileMetadataEntry { if metadata == nil { return nil } @@ -195,7 +195,7 @@ func toPackageModels(catalog *pkg.Collection) []model.Package { func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) { for _, l := range pkgLicenses { // guarantee collection - locations := make([]source.Location, 0) + locations := make([]file.Location, 0) if v := l.Locations.ToSlice(); v != nil { locations = v } diff --git a/syft/formats/syftjson/to_format_model_test.go b/syft/formats/syftjson/to_format_model_test.go index 9794a1b7633..98f03c7b08f 100644 --- a/syft/formats/syftjson/to_format_model_test.go +++ b/syft/formats/syftjson/to_format_model_test.go @@ -7,7 +7,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/formats/syftjson/model" "github.com/anchore/syft/syft/source" ) @@ -94,46 +95,46 @@ func Test_toSourceModel(t *testing.T) { func Test_toFileType(t *testing.T) { - badType := file.Type(0x1337) - var allTypesTested []file.Type + badType := stereoscopeFile.Type(0x1337) + var allTypesTested []stereoscopeFile.Type tests := []struct { - ty file.Type + ty stereoscopeFile.Type name string }{ { - ty: file.TypeRegular, + ty: stereoscopeFile.TypeRegular, name: "RegularFile", }, { - ty: file.TypeDirectory, + ty: stereoscopeFile.TypeDirectory, name: "Directory", }, { - ty: file.TypeSymLink, + ty: stereoscopeFile.TypeSymLink, name: "SymbolicLink", }, { - ty: file.TypeHardLink, + ty: stereoscopeFile.TypeHardLink, name: "HardLink", }, { - ty: file.TypeSocket, + ty: stereoscopeFile.TypeSocket, name: "Socket", }, { - ty: file.TypeCharacterDevice, + ty: stereoscopeFile.TypeCharacterDevice, name: "CharacterDevice", }, { - ty: file.TypeBlockDevice, + ty: stereoscopeFile.TypeBlockDevice, name: "BlockDevice", }, { - ty: file.TypeFIFO, + ty: stereoscopeFile.TypeFIFO, name: "FIFONode", }, { - ty: file.TypeIrregular, + ty: stereoscopeFile.TypeIrregular, name: "IrregularFile", }, { @@ -150,17 +151,17 @@ func Test_toFileType(t *testing.T) { }) } - assert.ElementsMatch(t, allTypesTested, file.AllTypes(), "not all file.Types are under test") + assert.ElementsMatch(t, allTypesTested, stereoscopeFile.AllTypes(), "not all file.Types are under test") } func Test_toFileMetadataEntry(t *testing.T) { - coords := source.Coordinates{ + coords := file.Coordinates{ RealPath: "/path", FileSystemID: "x", } tests := []struct { name string - metadata *source.FileMetadata + metadata *file.Metadata want *model.FileMetadataEntry }{ { @@ -168,23 +169,23 @@ func Test_toFileMetadataEntry(t *testing.T) { }, { name: "no file info", - metadata: &source.FileMetadata{ + metadata: &file.Metadata{ FileInfo: nil, }, want: &model.FileMetadataEntry{ - Type: file.TypeRegular.String(), + Type: stereoscopeFile.TypeRegular.String(), }, }, { name: "with file info", - metadata: &source.FileMetadata{ - FileInfo: &file.ManualInfo{ + metadata: &file.Metadata{ + FileInfo: &stereoscopeFile.ManualInfo{ ModeValue: 1, }, }, want: &model.FileMetadataEntry{ Mode: 1, - Type: file.TypeRegular.String(), + Type: stereoscopeFile.TypeRegular.String(), }, }, } diff --git a/syft/formats/syftjson/to_syft_model.go b/syft/formats/syftjson/to_syft_model.go index 7b420183839..aeb0c24f165 100644 --- a/syft/formats/syftjson/to_syft_model.go +++ b/syft/formats/syftjson/to_syft_model.go @@ -64,8 +64,8 @@ func deduplicateErrors(errors []error) []string { func toSyftFiles(files []model.File) sbom.Artifacts { ret := sbom.Artifacts{ - FileMetadata: make(map[source.Coordinates]source.FileMetadata), - FileDigests: make(map[source.Coordinates][]file.Digest), + FileMetadata: make(map[file.Coordinates]file.Metadata), + FileDigests: make(map[file.Coordinates][]file.Digest), } for _, f := range files { @@ -79,7 +79,7 @@ func toSyftFiles(files []model.File) sbom.Artifacts { fm := os.FileMode(mode) - ret.FileMetadata[coord] = source.FileMetadata{ + ret.FileMetadata[coord] = file.Metadata{ FileInfo: stereoscopeFile.ManualInfo{ NameValue: path.Base(coord.RealPath), SizeValue: f.Metadata.Size, @@ -112,7 +112,7 @@ func toSyftLicenses(m []model.License) (p []pkg.License) { SPDXExpression: l.SPDXExpression, Type: l.Type, URLs: internal.NewStringSet(l.URLs...), - Locations: source.NewLocationSet(l.Locations...), + Locations: file.NewLocationSet(l.Locations...), }) } return @@ -320,7 +320,7 @@ func toSyftPackage(p model.Package, idAliases map[string]string) pkg.Package { Name: p.Name, Version: p.Version, FoundBy: p.FoundBy, - Locations: source.NewLocationSet(p.Locations...), + Locations: file.NewLocationSet(p.Locations...), Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...), Language: p.Language, Type: p.Type, diff --git a/syft/formats/syftjson/to_syft_model_test.go b/syft/formats/syftjson/to_syft_model_test.go index 8c4ab3cee52..dabc33f3841 100644 --- a/syft/formats/syftjson/to_syft_model_test.go +++ b/syft/formats/syftjson/to_syft_model_test.go @@ -131,7 +131,7 @@ func Test_idsHaveChanged(t *testing.T) { } func Test_toSyftFiles(t *testing.T) { - coord := source.Coordinates{ + coord := file.Coordinates{ RealPath: "/somerwhere/place", FileSystemID: "abc", } @@ -145,8 +145,8 @@ func Test_toSyftFiles(t *testing.T) { name: "empty", files: []model.File{}, want: sbom.Artifacts{ - FileMetadata: map[source.Coordinates]source.FileMetadata{}, - FileDigests: map[source.Coordinates][]file.Digest{}, + FileMetadata: map[file.Coordinates]file.Metadata{}, + FileDigests: map[file.Coordinates][]file.Digest{}, }, }, { @@ -165,8 +165,8 @@ func Test_toSyftFiles(t *testing.T) { }, }, want: sbom.Artifacts{ - FileMetadata: map[source.Coordinates]source.FileMetadata{}, - FileDigests: map[source.Coordinates][]file.Digest{ + FileMetadata: map[file.Coordinates]file.Metadata{}, + FileDigests: map[file.Coordinates][]file.Digest{ coord: { { Algorithm: "sha256", @@ -200,7 +200,7 @@ func Test_toSyftFiles(t *testing.T) { }, }, want: sbom.Artifacts{ - FileMetadata: map[source.Coordinates]source.FileMetadata{ + FileMetadata: map[file.Coordinates]file.Metadata{ coord: { FileInfo: stereoFile.ManualInfo{ NameValue: "place", @@ -215,7 +215,7 @@ func Test_toSyftFiles(t *testing.T) { MIMEType: "text/plain", }, }, - FileDigests: map[source.Coordinates][]file.Digest{ + FileDigests: map[file.Coordinates][]file.Digest{ coord: { { Algorithm: "sha256", diff --git a/syft/source/image_all_layers_resolver.go b/syft/internal/fileresolver/container_image_all_layers.go similarity index 65% rename from syft/source/image_all_layers_resolver.go rename to syft/internal/fileresolver/container_image_all_layers.go index dd9a0bd2e0d..e66c92aaf1b 100644 --- a/syft/source/image_all_layers_resolver.go +++ b/syft/internal/fileresolver/container_image_all_layers.go @@ -1,25 +1,26 @@ -package source +package fileresolver import ( "fmt" "io" - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" ) -var _ FileResolver = (*imageAllLayersResolver)(nil) +var _ file.Resolver = (*ContainerImageAllLayers)(nil) -// imageAllLayersResolver implements path and content access for the AllLayers source option for container image data sources. -type imageAllLayersResolver struct { +// ContainerImageAllLayers implements path and content access for the AllLayers source option for container image data sources. +type ContainerImageAllLayers struct { img *image.Image layers []int } -// newAllLayersResolver returns a new resolver from the perspective of all image layers for the given image. -func newAllLayersResolver(img *image.Image) (*imageAllLayersResolver, error) { +// NewFromContainerImageAllLayers returns a new resolver from the perspective of all image layers for the given image. +func NewFromContainerImageAllLayers(img *image.Image) (*ContainerImageAllLayers, error) { if len(img.Layers) == 0 { return nil, fmt.Errorf("the image does not contain any layers") } @@ -28,15 +29,15 @@ func newAllLayersResolver(img *image.Image) (*imageAllLayersResolver, error) { for idx := range img.Layers { layers = append(layers, idx) } - return &imageAllLayersResolver{ + return &ContainerImageAllLayers{ img: img, layers: layers, }, nil } // HasPath indicates if the given path exists in the underlying source. -func (r *imageAllLayersResolver) HasPath(path string) bool { - p := file.Path(path) +func (r *ContainerImageAllLayers) HasPath(path string) bool { + p := stereoscopeFile.Path(path) for _, layerIdx := range r.layers { tree := r.img.Layers[layerIdx].Tree if tree.HasPath(p) { @@ -46,8 +47,8 @@ func (r *imageAllLayersResolver) HasPath(path string) bool { return false } -func (r *imageAllLayersResolver) fileByRef(ref file.Reference, uniqueFileIDs file.ReferenceSet, layerIdx int) ([]file.Reference, error) { - uniqueFiles := make([]file.Reference, 0) +func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqueFileIDs stereoscopeFile.ReferenceSet, layerIdx int) ([]stereoscopeFile.Reference, error) { + uniqueFiles := make([]stereoscopeFile.Reference, 0) // since there is potentially considerable work for each symlink/hardlink that needs to be resolved, let's check to see if this is a symlink/hardlink first entry, err := r.img.FileCatalog.Get(ref) @@ -55,7 +56,7 @@ func (r *imageAllLayersResolver) fileByRef(ref file.Reference, uniqueFileIDs fil return nil, fmt.Errorf("unable to fetch metadata (ref=%+v): %w", ref, err) } - if entry.Metadata.Type == file.TypeHardLink || entry.Metadata.Type == file.TypeSymLink { + if entry.Metadata.Type == stereoscopeFile.TypeHardLink || entry.Metadata.Type == stereoscopeFile.TypeSymLink { // a link may resolve in this layer or higher, assuming a squashed tree is used to search // we should search all possible resolutions within the valid source for _, subLayerIdx := range r.layers[layerIdx:] { @@ -77,9 +78,9 @@ func (r *imageAllLayersResolver) fileByRef(ref file.Reference, uniqueFileIDs fil } // FilesByPath returns all file.References that match the given paths from any layer in the image. -func (r *imageAllLayersResolver) FilesByPath(paths ...string) ([]Location, error) { - uniqueFileIDs := file.NewFileReferenceSet() - uniqueLocations := make([]Location, 0) +func (r *ContainerImageAllLayers) FilesByPath(paths ...string) ([]file.Location, error) { + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() + uniqueLocations := make([]file.Location, 0) for _, path := range paths { for idx, layerIdx := range r.layers { @@ -110,7 +111,7 @@ func (r *imageAllLayersResolver) FilesByPath(paths ...string) ([]Location, error return nil, err } for _, result := range results { - uniqueLocations = append(uniqueLocations, NewLocationFromImage(path, result, r.img)) + uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(path, result, r.img)) } } } @@ -119,9 +120,9 @@ func (r *imageAllLayersResolver) FilesByPath(paths ...string) ([]Location, error // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image. // nolint:gocognit -func (r *imageAllLayersResolver) FilesByGlob(patterns ...string) ([]Location, error) { - uniqueFileIDs := file.NewFileReferenceSet() - uniqueLocations := make([]Location, 0) +func (r *ContainerImageAllLayers) FilesByGlob(patterns ...string) ([]file.Location, error) { + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() + uniqueLocations := make([]file.Location, 0) for _, pattern := range patterns { for idx, layerIdx := range r.layers { @@ -153,7 +154,7 @@ func (r *imageAllLayersResolver) FilesByGlob(patterns ...string) ([]Location, er return nil, err } for _, refResult := range refResults { - uniqueLocations = append(uniqueLocations, NewLocationFromImage(string(result.RequestPath), refResult, r.img)) + uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(string(result.RequestPath), refResult, r.img)) } } } @@ -164,10 +165,10 @@ func (r *imageAllLayersResolver) FilesByGlob(patterns ...string) ([]Location, er // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. // This is helpful when attempting to find a file that is in the same layer or lower as another file. -func (r *imageAllLayersResolver) RelativeFileByPath(location Location, path string) *Location { - layer := r.img.FileCatalog.Layer(location.ref) +func (r *ContainerImageAllLayers) RelativeFileByPath(location file.Location, path string) *file.Location { + layer := r.img.FileCatalog.Layer(location.Reference()) - exists, relativeRef, err := layer.SquashedTree.File(file.Path(path), filetree.FollowBasenameLinks) + exists, relativeRef, err := layer.SquashedTree.File(stereoscopeFile.Path(path), filetree.FollowBasenameLinks) if err != nil { log.Errorf("failed to find path=%q in squash: %+w", path, err) return nil @@ -176,21 +177,21 @@ func (r *imageAllLayersResolver) RelativeFileByPath(location Location, path stri return nil } - relativeLocation := NewLocationFromImage(path, *relativeRef.Reference, r.img) + relativeLocation := file.NewLocationFromImage(path, *relativeRef.Reference, r.img) return &relativeLocation } // FileContentsByLocation fetches file contents for a single file reference, irregardless of the source layer. // If the path does not exist an error is returned. -func (r *imageAllLayersResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { - entry, err := r.img.FileCatalog.Get(location.ref) +func (r *ContainerImageAllLayers) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { + entry, err := r.img.FileCatalog.Get(location.Reference()) if err != nil { return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err) } switch entry.Metadata.Type { - case file.TypeSymLink, file.TypeHardLink: + case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink: // the location we are searching may be a symlink, we should always work with the resolved file newLocation := r.RelativeFileByPath(location, location.VirtualPath) if newLocation == nil { @@ -198,16 +199,16 @@ func (r *imageAllLayersResolver) FileContentsByLocation(location Location) (io.R return nil, fmt.Errorf("no contents for location=%q", location.VirtualPath) } location = *newLocation - case file.TypeDirectory: - return nil, fmt.Errorf("cannot read contents of non-file %q", location.ref.RealPath) + case stereoscopeFile.TypeDirectory: + return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath) } - return r.img.FileContentsByRef(location.ref) + return r.img.OpenReference(location.Reference()) } -func (r *imageAllLayersResolver) FilesByMIMEType(types ...string) ([]Location, error) { - uniqueFileIDs := file.NewFileReferenceSet() - uniqueLocations := make([]Location, 0) +func (r *ContainerImageAllLayers) FilesByMIMEType(types ...string) ([]file.Location, error) { + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() + uniqueLocations := make([]file.Location, 0) for idx, layerIdx := range r.layers { refs, err := r.img.Layers[layerIdx].SearchContext.SearchByMIMEType(types...) @@ -225,7 +226,7 @@ func (r *imageAllLayersResolver) FilesByMIMEType(types ...string) ([]Location, e return nil, err } for _, refResult := range refResults { - uniqueLocations = append(uniqueLocations, NewLocationFromImage(string(ref.RequestPath), refResult, r.img)) + uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(string(ref.RequestPath), refResult, r.img)) } } } @@ -233,20 +234,20 @@ func (r *imageAllLayersResolver) FilesByMIMEType(types ...string) ([]Location, e return uniqueLocations, nil } -func (r *imageAllLayersResolver) AllLocations() <-chan Location { - results := make(chan Location) +func (r *ContainerImageAllLayers) AllLocations() <-chan file.Location { + results := make(chan file.Location) go func() { defer close(results) for _, layerIdx := range r.layers { tree := r.img.Layers[layerIdx].Tree - for _, ref := range tree.AllFiles(file.AllTypes()...) { - results <- NewLocationFromImage(string(ref.RealPath), ref, r.img) + for _, ref := range tree.AllFiles(stereoscopeFile.AllTypes()...) { + results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img) } } }() return results } -func (r *imageAllLayersResolver) FileMetadataByLocation(location Location) (FileMetadata, error) { +func (r *ContainerImageAllLayers) FileMetadataByLocation(location file.Location) (file.Metadata, error) { return fileMetadataByLocation(r.img, location) } diff --git a/syft/source/image_all_layers_resolver_test.go b/syft/internal/fileresolver/container_image_all_layers_test.go similarity index 76% rename from syft/source/image_all_layers_resolver_test.go rename to syft/internal/fileresolver/container_image_all_layers_test.go index 0a804290727..7fb04d56b78 100644 --- a/syft/source/image_all_layers_resolver_test.go +++ b/syft/internal/fileresolver/container_image_all_layers_test.go @@ -1,4 +1,4 @@ -package source +package fileresolver import ( "fmt" @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/file" ) type resolution struct { @@ -93,7 +94,7 @@ func TestAllLayersResolver_FilesByPath(t *testing.T) { t.Run(c.name, func(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newAllLayersResolver(img) + resolver, err := NewFromContainerImageAllLayers(img) if err != nil { t.Fatalf("could not create resolver: %+v", err) } @@ -121,15 +122,15 @@ func TestAllLayersResolver_FilesByPath(t *testing.T) { for idx, actual := range refs { expected := c.resolutions[idx] - if string(actual.ref.RealPath) != expected.path { - t.Errorf("bad resolve path: '%s'!='%s'", string(actual.ref.RealPath), expected.path) + if string(actual.Reference().RealPath) != expected.path { + t.Errorf("bad resolve path: '%s'!='%s'", string(actual.Reference().RealPath), expected.path) } - if expected.path != "" && string(actual.ref.RealPath) != actual.RealPath { + if expected.path != "" && string(actual.Reference().RealPath) != actual.RealPath { t.Errorf("we should always prefer real paths over ones with links") } - layer := img.FileCatalog.Layer(actual.ref) + layer := img.FileCatalog.Layer(actual.Reference()) if layer.Metadata.Index != expected.layer { t.Errorf("bad resolve layer: '%d'!='%d'", layer.Metadata.Index, expected.layer) } @@ -207,7 +208,7 @@ func TestAllLayersResolver_FilesByGlob(t *testing.T) { t.Run(c.name, func(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newAllLayersResolver(img) + resolver, err := NewFromContainerImageAllLayers(img) if err != nil { t.Fatalf("could not create resolver: %+v", err) } @@ -224,15 +225,15 @@ func TestAllLayersResolver_FilesByGlob(t *testing.T) { for idx, actual := range refs { expected := c.resolutions[idx] - if string(actual.ref.RealPath) != expected.path { - t.Errorf("bad resolve path: '%s'!='%s'", string(actual.ref.RealPath), expected.path) + if string(actual.Reference().RealPath) != expected.path { + t.Errorf("bad resolve path: '%s'!='%s'", string(actual.Reference().RealPath), expected.path) } - if expected.path != "" && string(actual.ref.RealPath) != actual.RealPath { + if expected.path != "" && string(actual.Reference().RealPath) != actual.RealPath { t.Errorf("we should always prefer real paths over ones with links") } - layer := img.FileCatalog.Layer(actual.ref) + layer := img.FileCatalog.Layer(actual.Reference()) if layer.Metadata.Index != expected.layer { t.Errorf("bad resolve layer: '%d'!='%d'", layer.Metadata.Index, expected.layer) @@ -259,7 +260,7 @@ func Test_imageAllLayersResolver_FilesByMIMEType(t *testing.T) { t.Run(test.fixtureName, func(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName) - resolver, err := newAllLayersResolver(img) + resolver, err := NewFromContainerImageAllLayers(img) assert.NoError(t, err) locations, err := resolver.FilesByMIMEType(test.mimeType) @@ -276,7 +277,7 @@ func Test_imageAllLayersResolver_FilesByMIMEType(t *testing.T) { func Test_imageAllLayersResolver_hasFilesystemIDInLocation(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path") - resolver, err := newAllLayersResolver(img) + resolver, err := NewFromContainerImageAllLayers(img) assert.NoError(t, err) locations, err := resolver.FilesByMIMEType("text/plain") @@ -336,7 +337,7 @@ func TestAllLayersImageResolver_FilesContents(t *testing.T) { t.Run(test.name, func(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newAllLayersResolver(img) + resolver, err := NewFromContainerImageAllLayers(img) assert.NoError(t, err) refs, err := resolver.FilesByPath(test.fixture) @@ -363,12 +364,12 @@ func TestAllLayersImageResolver_FilesContents_errorOnDirRequest(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newAllLayersResolver(img) + resolver, err := NewFromContainerImageAllLayers(img) assert.NoError(t, err) - var dirLoc *Location + var dirLoc *file.Location for loc := range resolver.AllLocations() { - entry, err := resolver.img.FileCatalog.Get(loc.ref) + entry, err := resolver.img.FileCatalog.Get(loc.Reference()) require.NoError(t, err) if entry.Metadata.IsDir() { dirLoc = &loc @@ -386,119 +387,119 @@ func TestAllLayersImageResolver_FilesContents_errorOnDirRequest(t *testing.T) { func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { tests := []struct { name string - runner func(FileResolver) []Location - expected []Location + runner func(file.Resolver) []file.Location + expected []file.Location }{ { name: "by mimetype", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links should not show up when searching mimetype actualLocations, err := resolver.FilesByMIMEType("text/plain") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/etc/group", "/etc/group"), - NewVirtualLocation("/etc/passwd", "/etc/passwd"), - NewVirtualLocation("/etc/shadow", "/etc/shadow"), - NewVirtualLocation("/file-1.txt", "/file-1.txt"), - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 + expected: []file.Location{ + file.NewVirtualLocation("/etc/group", "/etc/group"), + file.NewVirtualLocation("/etc/passwd", "/etc/passwd"), + file.NewVirtualLocation("/etc/shadow", "/etc/shadow"), + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"), + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 // note: we're de-duping the redundant access to file-3.txt // ... (there would usually be two copies) - NewVirtualLocation("/file-3.txt", "/file-3.txt"), - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 1 - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 2 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"), + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 1 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 2 }, }, { name: "by glob to links", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("*ink-*") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/file-1.txt", "/link-1"), - NewVirtualLocation("/file-2.txt", "/link-2"), // copy 1 - NewVirtualLocation("/file-2.txt", "/link-2"), // copy 2 - NewVirtualLocation("/file-3.txt", "/link-within"), + expected: []file.Location{ + file.NewVirtualLocation("/file-1.txt", "/link-1"), + file.NewVirtualLocation("/file-2.txt", "/link-2"), // copy 1 + file.NewVirtualLocation("/file-2.txt", "/link-2"), // copy 2 + file.NewVirtualLocation("/file-3.txt", "/link-within"), }, }, { name: "by basename", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/file-2.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 + expected: []file.Location{ + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 }, }, { name: "by basename glob", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/file-?.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/file-1.txt", "/file-1.txt"), - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 - NewVirtualLocation("/file-3.txt", "/file-3.txt"), - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied + expected: []file.Location{ + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"), + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"), + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied }, }, { name: "by extension", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/*.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/file-1.txt", "/file-1.txt"), - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 - NewVirtualLocation("/file-3.txt", "/file-3.txt"), - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied + expected: []file.Location{ + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"), + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"), + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied }, }, { name: "by path to degree 1 link", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links resolve to the final file actualLocations, err := resolver.FilesByPath("/link-2") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // we have multiple copies across layers - NewVirtualLocation("/file-2.txt", "/link-2"), - NewVirtualLocation("/file-2.txt", "/link-2"), + file.NewVirtualLocation("/file-2.txt", "/link-2"), + file.NewVirtualLocation("/file-2.txt", "/link-2"), }, }, { name: "by path to degree 2 link", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // multiple links resolves to the final file actualLocations, err := resolver.FilesByPath("/link-indirect") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // we have multiple copies across layers - NewVirtualLocation("/file-2.txt", "/link-indirect"), - NewVirtualLocation("/file-2.txt", "/link-indirect"), + file.NewVirtualLocation("/file-2.txt", "/link-indirect"), + file.NewVirtualLocation("/file-2.txt", "/link-indirect"), }, }, } @@ -508,7 +509,7 @@ func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newAllLayersResolver(img) + resolver, err := NewFromContainerImageAllLayers(img) assert.NoError(t, err) actual := test.runner(resolver) @@ -527,7 +528,7 @@ func TestAllLayersResolver_AllLocations(t *testing.T) { arch = "aarch64" } - resolver, err := newAllLayersResolver(img) + resolver, err := NewFromContainerImageAllLayers(img) assert.NoError(t, err) paths := strset.New() diff --git a/syft/source/image_squash_resolver.go b/syft/internal/fileresolver/container_image_squash.go similarity index 66% rename from syft/source/image_squash_resolver.go rename to syft/internal/fileresolver/container_image_squash.go index 233f008436d..92b4a8a13ee 100644 --- a/syft/source/image_squash_resolver.go +++ b/syft/internal/fileresolver/container_image_squash.go @@ -1,41 +1,42 @@ -package source +package fileresolver import ( "fmt" "io" - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/syft/syft/file" ) -var _ FileResolver = (*imageSquashResolver)(nil) +var _ file.Resolver = (*ContainerImageSquash)(nil) -// imageSquashResolver implements path and content access for the Squashed source option for container image data sources. -type imageSquashResolver struct { +// ContainerImageSquash implements path and content access for the Squashed source option for container image data sources. +type ContainerImageSquash struct { img *image.Image } -// newImageSquashResolver returns a new resolver from the perspective of the squashed representation for the given image. -func newImageSquashResolver(img *image.Image) (*imageSquashResolver, error) { +// NewFromContainerImageSquash returns a new resolver from the perspective of the squashed representation for the given image. +func NewFromContainerImageSquash(img *image.Image) (*ContainerImageSquash, error) { if img.SquashedTree() == nil { return nil, fmt.Errorf("the image does not have have a squashed tree") } - return &imageSquashResolver{ + return &ContainerImageSquash{ img: img, }, nil } // HasPath indicates if the given path exists in the underlying source. -func (r *imageSquashResolver) HasPath(path string) bool { - return r.img.SquashedTree().HasPath(file.Path(path)) +func (r *ContainerImageSquash) HasPath(path string) bool { + return r.img.SquashedTree().HasPath(stereoscopeFile.Path(path)) } // FilesByPath returns all file.References that match the given paths within the squashed representation of the image. -func (r *imageSquashResolver) FilesByPath(paths ...string) ([]Location, error) { - uniqueFileIDs := file.NewFileReferenceSet() - uniqueLocations := make([]Location, 0) +func (r *ContainerImageSquash) FilesByPath(paths ...string) ([]file.Location, error) { + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() + uniqueLocations := make([]file.Location, 0) for _, path := range paths { ref, err := r.img.SquashedSearchContext.SearchByPath(path, filetree.FollowBasenameLinks) @@ -69,7 +70,7 @@ func (r *imageSquashResolver) FilesByPath(paths ...string) ([]Location, error) { if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) { uniqueFileIDs.Add(*resolvedRef.Reference) - uniqueLocations = append(uniqueLocations, NewLocationFromImage(path, *resolvedRef.Reference, r.img)) + uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(path, *resolvedRef.Reference, r.img)) } } @@ -78,9 +79,9 @@ func (r *imageSquashResolver) FilesByPath(paths ...string) ([]Location, error) { // FilesByGlob returns all file.References that match the given path glob pattern within the squashed representation of the image. // nolint:gocognit -func (r *imageSquashResolver) FilesByGlob(patterns ...string) ([]Location, error) { - uniqueFileIDs := file.NewFileReferenceSet() - uniqueLocations := make([]Location, 0) +func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location, error) { + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() + uniqueLocations := make([]file.Location, 0) for _, pattern := range patterns { results, err := r.img.SquashedSearchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks) @@ -113,10 +114,10 @@ func (r *imageSquashResolver) FilesByGlob(patterns ...string) ([]Location, error return nil, fmt.Errorf("failed to find files by path (result=%+v): %w", result, err) } for _, resolvedLocation := range resolvedLocations { - if uniqueFileIDs.Contains(resolvedLocation.ref) { + if uniqueFileIDs.Contains(resolvedLocation.Reference()) { continue } - uniqueFileIDs.Add(resolvedLocation.ref) + uniqueFileIDs.Add(resolvedLocation.Reference()) uniqueLocations = append(uniqueLocations, resolvedLocation) } } @@ -127,8 +128,8 @@ func (r *imageSquashResolver) FilesByGlob(patterns ...string) ([]Location, error // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. // This is helpful when attempting to find a file that is in the same layer or lower as another file. For the -// imageSquashResolver, this is a simple path lookup. -func (r *imageSquashResolver) RelativeFileByPath(_ Location, path string) *Location { +// ContainerImageSquash, this is a simple path lookup. +func (r *ContainerImageSquash) RelativeFileByPath(_ file.Location, path string) *file.Location { paths, err := r.FilesByPath(path) if err != nil { return nil @@ -142,14 +143,14 @@ func (r *imageSquashResolver) RelativeFileByPath(_ Location, path string) *Locat // FileContentsByLocation fetches file contents for a single file reference, regardless of the source layer. // If the path does not exist an error is returned. -func (r *imageSquashResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { - entry, err := r.img.FileCatalog.Get(location.ref) +func (r *ContainerImageSquash) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { + entry, err := r.img.FileCatalog.Get(location.Reference()) if err != nil { return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err) } switch entry.Metadata.Type { - case file.TypeSymLink, file.TypeHardLink: + case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink: // the location we are searching may be a symlink, we should always work with the resolved file locations, err := r.FilesByPath(location.RealPath) if err != nil { @@ -164,39 +165,39 @@ func (r *imageSquashResolver) FileContentsByLocation(location Location) (io.Read default: return nil, fmt.Errorf("link resolution resulted in multiple results while resolving content location: %+v", location) } - case file.TypeDirectory: + case stereoscopeFile.TypeDirectory: return nil, fmt.Errorf("unable to get file contents for directory: %+v", location) } - return r.img.FileContentsByRef(location.ref) + return r.img.OpenReference(location.Reference()) } -func (r *imageSquashResolver) AllLocations() <-chan Location { - results := make(chan Location) +func (r *ContainerImageSquash) AllLocations() <-chan file.Location { + results := make(chan file.Location) go func() { defer close(results) - for _, ref := range r.img.SquashedTree().AllFiles(file.AllTypes()...) { - results <- NewLocationFromImage(string(ref.RealPath), ref, r.img) + for _, ref := range r.img.SquashedTree().AllFiles(stereoscopeFile.AllTypes()...) { + results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img) } }() return results } -func (r *imageSquashResolver) FilesByMIMEType(types ...string) ([]Location, error) { +func (r *ContainerImageSquash) FilesByMIMEType(types ...string) ([]file.Location, error) { refs, err := r.img.SquashedSearchContext.SearchByMIMEType(types...) if err != nil { return nil, err } - uniqueFileIDs := file.NewFileReferenceSet() - uniqueLocations := make([]Location, 0) + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() + uniqueLocations := make([]file.Location, 0) for _, ref := range refs { if ref.HasReference() { if uniqueFileIDs.Contains(*ref.Reference) { continue } - location := NewLocationFromImage(string(ref.RequestPath), *ref.Reference, r.img) + location := file.NewLocationFromImage(string(ref.RequestPath), *ref.Reference, r.img) uniqueFileIDs.Add(*ref.Reference) uniqueLocations = append(uniqueLocations, location) @@ -206,6 +207,6 @@ func (r *imageSquashResolver) FilesByMIMEType(types ...string) ([]Location, erro return uniqueLocations, nil } -func (r *imageSquashResolver) FileMetadataByLocation(location Location) (FileMetadata, error) { +func (r *ContainerImageSquash) FileMetadataByLocation(location file.Location) (file.Metadata, error) { return fileMetadataByLocation(r.img, location) } diff --git a/syft/source/image_squash_resolver_test.go b/syft/internal/fileresolver/container_image_squash_test.go similarity index 72% rename from syft/source/image_squash_resolver_test.go rename to syft/internal/fileresolver/container_image_squash_test.go index 0fd9c4f99be..d65d0bccc88 100644 --- a/syft/source/image_squash_resolver_test.go +++ b/syft/internal/fileresolver/container_image_squash_test.go @@ -1,4 +1,4 @@ -package source +package fileresolver import ( "io" @@ -6,13 +6,12 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" "github.com/scylladb/go-set/strset" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/file" ) func TestImageSquashResolver_FilesByPath(t *testing.T) { @@ -73,7 +72,7 @@ func TestImageSquashResolver_FilesByPath(t *testing.T) { t.Run(c.name, func(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newImageSquashResolver(img) + resolver, err := NewFromContainerImageSquash(img) if err != nil { t.Fatalf("could not create resolver: %+v", err) } @@ -110,15 +109,15 @@ func TestImageSquashResolver_FilesByPath(t *testing.T) { actual := refs[0] - if string(actual.ref.RealPath) != c.resolvePath { - t.Errorf("bad resolve path: '%s'!='%s'", string(actual.ref.RealPath), c.resolvePath) + if string(actual.Reference().RealPath) != c.resolvePath { + t.Errorf("bad resolve path: '%s'!='%s'", string(actual.Reference().RealPath), c.resolvePath) } - if c.resolvePath != "" && string(actual.ref.RealPath) != actual.RealPath { + if c.resolvePath != "" && string(actual.Reference().RealPath) != actual.RealPath { t.Errorf("we should always prefer real paths over ones with links") } - layer := img.FileCatalog.Layer(actual.ref) + layer := img.FileCatalog.Layer(actual.Reference()) if layer.Metadata.Index != c.resolveLayer { t.Errorf("bad resolve layer: '%d'!='%d'", layer.Metadata.Index, c.resolveLayer) @@ -186,7 +185,7 @@ func TestImageSquashResolver_FilesByGlob(t *testing.T) { t.Run(c.name, func(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newImageSquashResolver(img) + resolver, err := NewFromContainerImageSquash(img) if err != nil { t.Fatalf("could not create resolver: %+v", err) } @@ -212,15 +211,15 @@ func TestImageSquashResolver_FilesByGlob(t *testing.T) { actual := refs[0] - if string(actual.ref.RealPath) != c.resolvePath { - t.Errorf("bad resolve path: '%s'!='%s'", string(actual.ref.RealPath), c.resolvePath) + if string(actual.Reference().RealPath) != c.resolvePath { + t.Errorf("bad resolve path: '%s'!='%s'", string(actual.Reference().RealPath), c.resolvePath) } - if c.resolvePath != "" && string(actual.ref.RealPath) != actual.RealPath { + if c.resolvePath != "" && string(actual.Reference().RealPath) != actual.RealPath { t.Errorf("we should always prefer real paths over ones with links") } - layer := img.FileCatalog.Layer(actual.ref) + layer := img.FileCatalog.Layer(actual.Reference()) if layer.Metadata.Index != c.resolveLayer { t.Errorf("bad resolve layer: '%d'!='%d'", layer.Metadata.Index, c.resolveLayer) @@ -247,7 +246,7 @@ func Test_imageSquashResolver_FilesByMIMEType(t *testing.T) { t.Run(test.fixtureName, func(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName) - resolver, err := newImageSquashResolver(img) + resolver, err := NewFromContainerImageSquash(img) assert.NoError(t, err) locations, err := resolver.FilesByMIMEType(test.mimeType) @@ -264,7 +263,7 @@ func Test_imageSquashResolver_FilesByMIMEType(t *testing.T) { func Test_imageSquashResolver_hasFilesystemIDInLocation(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path") - resolver, err := newImageSquashResolver(img) + resolver, err := NewFromContainerImageSquash(img) assert.NoError(t, err) locations, err := resolver.FilesByMIMEType("text/plain") @@ -322,7 +321,7 @@ func TestSquashImageResolver_FilesContents(t *testing.T) { t.Run(test.name, func(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newImageSquashResolver(img) + resolver, err := NewFromContainerImageSquash(img) assert.NoError(t, err) refs, err := resolver.FilesByPath(test.path) @@ -347,12 +346,12 @@ func TestSquashImageResolver_FilesContents_errorOnDirRequest(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newImageSquashResolver(img) + resolver, err := NewFromContainerImageSquash(img) assert.NoError(t, err) - var dirLoc *Location + var dirLoc *file.Location for loc := range resolver.AllLocations() { - entry, err := resolver.img.FileCatalog.Get(loc.ref) + entry, err := resolver.img.FileCatalog.Get(loc.Reference()) require.NoError(t, err) if entry.Metadata.IsDir() { dirLoc = &loc @@ -370,162 +369,130 @@ func TestSquashImageResolver_FilesContents_errorOnDirRequest(t *testing.T) { func Test_imageSquashResolver_resolvesLinks(t *testing.T) { tests := []struct { name string - runner func(FileResolver) []Location - expected []Location + runner func(file.Resolver) []file.Location + expected []file.Location }{ { name: "by mimetype", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links should not show up when searching mimetype actualLocations, err := resolver.FilesByMIMEType("text/plain") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/etc/group", "/etc/group"), - NewVirtualLocation("/etc/passwd", "/etc/passwd"), - NewVirtualLocation("/etc/shadow", "/etc/shadow"), - NewVirtualLocation("/file-1.txt", "/file-1.txt"), - NewVirtualLocation("/file-3.txt", "/file-3.txt"), - NewVirtualLocation("/file-2.txt", "/file-2.txt"), - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), + expected: []file.Location{ + file.NewVirtualLocation("/etc/group", "/etc/group"), + file.NewVirtualLocation("/etc/passwd", "/etc/passwd"), + file.NewVirtualLocation("/etc/shadow", "/etc/shadow"), + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"), + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"), + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), }, }, { name: "by glob to links", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("*ink-*") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/file-1.txt", "/link-1"), - NewVirtualLocation("/file-2.txt", "/link-2"), + expected: []file.Location{ + file.NewVirtualLocation("/file-1.txt", "/link-1"), + file.NewVirtualLocation("/file-2.txt", "/link-2"), // though this is a link, and it matches to the file, the resolver de-duplicates files // by the real path, so it is not included in the results - //NewVirtualLocation("/file-2.txt", "/link-indirect"), + //file.NewVirtualLocation("/file-2.txt", "/link-indirect"), - NewVirtualLocation("/file-3.txt", "/link-within"), + file.NewVirtualLocation("/file-3.txt", "/link-within"), }, }, { name: "by basename", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/file-2.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // this has two copies in the base image, which overwrites the same location - NewVirtualLocation("/file-2.txt", "/file-2.txt"), + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), }, }, { name: "by basename glob", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/file-?.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/file-1.txt", "/file-1.txt"), - NewVirtualLocation("/file-2.txt", "/file-2.txt"), - NewVirtualLocation("/file-3.txt", "/file-3.txt"), - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), + expected: []file.Location{ + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"), + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"), + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), }, }, { name: "by basename glob to links", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { actualLocations, err := resolver.FilesByGlob("**/link-*") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - - { - LocationData: LocationData{ - Coordinates: Coordinates{ - RealPath: "/file-1.txt", - }, - VirtualPath: "/link-1", - ref: file.Reference{RealPath: "/file-1.txt"}, - }, - }, - { - LocationData: LocationData{ - - Coordinates: Coordinates{ - RealPath: "/file-2.txt", - }, - VirtualPath: "/link-2", - ref: file.Reference{RealPath: "/file-2.txt"}, - }, - }, + expected: []file.Location{ + file.NewVirtualLocation("/file-1.txt", "/link-1"), + file.NewVirtualLocation("/file-2.txt", "/link-2"), + // we already have this real file path via another link, so only one is returned - //{ - // LocationData: LocationData{ - // Coordinates: Coordinates{ - // RealPath: "/file-2.txt", - // }, - // VirtualPath: "/link-indirect", - // ref: file.Reference{RealPath: "/file-2.txt"}, - // }, - //}, - { - LocationData: LocationData{ - Coordinates: Coordinates{ - RealPath: "/file-3.txt", - }, - VirtualPath: "/link-within", - ref: file.Reference{RealPath: "/file-3.txt"}, - }, - }, + // file.NewVirtualLocation("/file-2.txt", "/link-indirect"), + + file.NewVirtualLocation("/file-3.txt", "/link-within"), }, }, { name: "by extension", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/*.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("/file-1.txt", "/file-1.txt"), - NewVirtualLocation("/file-2.txt", "/file-2.txt"), - NewVirtualLocation("/file-3.txt", "/file-3.txt"), - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), + expected: []file.Location{ + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"), + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"), + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), }, }, { name: "by path to degree 1 link", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links resolve to the final file actualLocations, err := resolver.FilesByPath("/link-2") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // we have multiple copies across layers - NewVirtualLocation("/file-2.txt", "/link-2"), + file.NewVirtualLocation("/file-2.txt", "/link-2"), }, }, { name: "by path to degree 2 link", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // multiple links resolves to the final file actualLocations, err := resolver.FilesByPath("/link-indirect") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // we have multiple copies across layers - NewVirtualLocation("/file-2.txt", "/link-indirect"), + file.NewVirtualLocation("/file-2.txt", "/link-indirect"), }, }, } @@ -535,7 +502,7 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks") - resolver, err := newImageSquashResolver(img) + resolver, err := NewFromContainerImageSquash(img) assert.NoError(t, err) actual := test.runner(resolver) @@ -546,30 +513,10 @@ func Test_imageSquashResolver_resolvesLinks(t *testing.T) { } -func compareLocations(t *testing.T, expected, actual []Location) { - t.Helper() - ignoreUnexported := cmpopts.IgnoreFields(LocationData{}, "ref") - ignoreMetadata := cmpopts.IgnoreFields(LocationMetadata{}, "Annotations") - ignoreFS := cmpopts.IgnoreFields(Coordinates{}, "FileSystemID") - - sort.Sort(Locations(expected)) - sort.Sort(Locations(actual)) - - if d := cmp.Diff(expected, actual, - ignoreUnexported, - ignoreFS, - ignoreMetadata, - ); d != "" { - - t.Errorf("unexpected locations (-want +got):\n%s", d) - } - -} - func TestSquashResolver_AllLocations(t *testing.T) { img := imagetest.GetFixtureImage(t, "docker-archive", "image-files-deleted") - resolver, err := newImageSquashResolver(img) + resolver, err := NewFromContainerImageSquash(img) assert.NoError(t, err) paths := strset.New() diff --git a/syft/internal/fileresolver/deferred.go b/syft/internal/fileresolver/deferred.go new file mode 100644 index 00000000000..55dbbb1628b --- /dev/null +++ b/syft/internal/fileresolver/deferred.go @@ -0,0 +1,98 @@ +package fileresolver + +import ( + "io" + + "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" +) + +var _ file.Resolver = (*Deferred)(nil) + +func NewDeferred(creator func() (file.Resolver, error)) *Deferred { + return &Deferred{ + creator: creator, + } +} + +type Deferred struct { + creator func() (file.Resolver, error) + resolver file.Resolver +} + +func (d *Deferred) getResolver() (file.Resolver, error) { + if d.resolver == nil { + resolver, err := d.creator() + if err != nil { + return nil, err + } + d.resolver = resolver + } + return d.resolver, nil +} + +func (d *Deferred) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { + r, err := d.getResolver() + if err != nil { + return nil, err + } + return r.FileContentsByLocation(location) +} + +func (d *Deferred) HasPath(s string) bool { + r, err := d.getResolver() + if err != nil { + log.Debug("unable to get resolver: %v", err) + return false + } + return r.HasPath(s) +} + +func (d *Deferred) FilesByPath(paths ...string) ([]file.Location, error) { + r, err := d.getResolver() + if err != nil { + return nil, err + } + return r.FilesByPath(paths...) +} + +func (d *Deferred) FilesByGlob(patterns ...string) ([]file.Location, error) { + r, err := d.getResolver() + if err != nil { + return nil, err + } + return r.FilesByGlob(patterns...) +} + +func (d *Deferred) FilesByMIMEType(types ...string) ([]file.Location, error) { + r, err := d.getResolver() + if err != nil { + return nil, err + } + return r.FilesByMIMEType(types...) +} + +func (d *Deferred) RelativeFileByPath(location file.Location, path string) *file.Location { + r, err := d.getResolver() + if err != nil { + return nil + } + return r.RelativeFileByPath(location, path) +} + +func (d *Deferred) AllLocations() <-chan file.Location { + r, err := d.getResolver() + if err != nil { + log.Debug("unable to get resolver: %v", err) + return nil + } + return r.AllLocations() +} + +func (d *Deferred) FileMetadataByLocation(location file.Location) (file.Metadata, error) { + r, err := d.getResolver() + if err != nil { + return file.Metadata{}, err + } + return r.FileMetadataByLocation(location) +} diff --git a/syft/source/deferred_resolver_test.go b/syft/internal/fileresolver/deferred_test.go similarity index 68% rename from syft/source/deferred_resolver_test.go rename to syft/internal/fileresolver/deferred_test.go index c7cd166c305..61f592387be 100644 --- a/syft/source/deferred_resolver_test.go +++ b/syft/internal/fileresolver/deferred_test.go @@ -1,17 +1,19 @@ -package source +package fileresolver import ( "testing" "github.com/stretchr/testify/require" + + "github.com/anchore/syft/syft/file" ) func Test_NewDeferredResolver(t *testing.T) { creatorCalled := false - deferredResolver := NewDeferredResolver(func() (FileResolver, error) { + deferredResolver := NewDeferred(func() (file.Resolver, error) { creatorCalled = true - return NewMockResolverForPaths(), nil + return file.NewMockResolverForPaths(), nil }) require.False(t, creatorCalled) diff --git a/syft/source/directory_resolver.go b/syft/internal/fileresolver/directory.go similarity index 77% rename from syft/source/directory_resolver.go rename to syft/internal/fileresolver/directory.go index a5a0e209de5..a892360d480 100644 --- a/syft/source/directory_resolver.go +++ b/syft/internal/fileresolver/directory.go @@ -1,4 +1,4 @@ -package source +package fileresolver import ( "errors" @@ -10,9 +10,10 @@ import ( "runtime" "strings" - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" "github.com/anchore/stereoscope/pkg/filetree" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" ) const WindowsOS = "windows" @@ -23,12 +24,12 @@ var unixSystemRuntimePrefixes = []string{ "/sys", } -var errSkipPath = errors.New("skip path") +var ErrSkipPath = errors.New("skip path") -var _ FileResolver = (*directoryResolver)(nil) +var _ file.Resolver = (*Directory)(nil) -// directoryResolver implements path and content access for the directory data source. -type directoryResolver struct { +// Directory implements path and content access for the directory data source. +type Directory struct { path string base string currentWdRelativeToRoot string @@ -39,8 +40,8 @@ type directoryResolver struct { indexer *directoryIndexer } -func newDirectoryResolver(root string, base string, pathFilters ...pathIndexVisitor) (*directoryResolver, error) { - r, err := newDirectoryResolverWithoutIndex(root, base, pathFilters...) +func NewFromDirectory(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) { + r, err := newFromDirectoryWithoutIndex(root, base, pathFilters...) if err != nil { return nil, err } @@ -48,7 +49,7 @@ func newDirectoryResolver(root string, base string, pathFilters ...pathIndexVisi return r, r.buildIndex() } -func newDirectoryResolverWithoutIndex(root string, base string, pathFilters ...pathIndexVisitor) (*directoryResolver, error) { +func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) { currentWD, err := os.Getwd() if err != nil { return nil, fmt.Errorf("could not get CWD: %w", err) @@ -87,7 +88,7 @@ func newDirectoryResolverWithoutIndex(root string, base string, pathFilters ...p currentWdRelRoot = filepath.Clean(cleanRoot) } - return &directoryResolver{ + return &Directory{ path: cleanRoot, base: cleanBase, currentWd: cleanCWD, @@ -98,7 +99,7 @@ func newDirectoryResolverWithoutIndex(root string, base string, pathFilters ...p }, nil } -func (r *directoryResolver) buildIndex() error { +func (r *Directory) buildIndex() error { if r.indexer == nil { return fmt.Errorf("no directory indexer configured") } @@ -114,7 +115,7 @@ func (r *directoryResolver) buildIndex() error { return nil } -func (r directoryResolver) requestPath(userPath string) (string, error) { +func (r Directory) requestPath(userPath string) (string, error) { if filepath.IsAbs(userPath) { // don't allow input to potentially hop above root path userPath = path.Join(r.path, userPath) @@ -131,7 +132,7 @@ func (r directoryResolver) requestPath(userPath string) (string, error) { return userPath, nil } -func (r directoryResolver) responsePath(path string) string { +func (r Directory) responsePath(path string) string { // check to see if we need to encode back to Windows from posix if runtime.GOOS == WindowsOS { path = posixToWindows(path) @@ -154,22 +155,22 @@ func (r directoryResolver) responsePath(path string) string { } // HasPath indicates if the given path exists in the underlying source. -func (r *directoryResolver) HasPath(userPath string) bool { +func (r *Directory) HasPath(userPath string) bool { requestPath, err := r.requestPath(userPath) if err != nil { return false } - return r.tree.HasPath(file.Path(requestPath)) + return r.tree.HasPath(stereoscopeFile.Path(requestPath)) } // Stringer to represent a directory path data source -func (r directoryResolver) String() string { +func (r Directory) String() string { return fmt.Sprintf("dir:%s", r.path) } // FilesByPath returns all file.References that match the given paths from the directory. -func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error) { - var references = make([]Location, 0) +func (r Directory) FilesByPath(userPaths ...string) ([]file.Location, error) { + var references = make([]file.Location, 0) for _, userPath := range userPaths { userStrPath, err := r.requestPath(userPath) @@ -206,7 +207,7 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error) if ref.HasReference() { references = append(references, - NewVirtualLocationFromDirectory( + file.NewVirtualLocationFromDirectory( r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root r.responsePath(userStrPath), // the path used to access this file, relative to the resolver root *ref.Reference, @@ -219,9 +220,9 @@ func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error) } // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image. -func (r directoryResolver) FilesByGlob(patterns ...string) ([]Location, error) { - uniqueFileIDs := file.NewFileReferenceSet() - uniqueLocations := make([]Location, 0) +func (r Directory) FilesByGlob(patterns ...string) ([]file.Location, error) { + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() + uniqueLocations := make([]file.Location, 0) for _, pattern := range patterns { refVias, err := r.searchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks) @@ -242,7 +243,7 @@ func (r directoryResolver) FilesByGlob(patterns ...string) ([]Location, error) { continue } - loc := NewVirtualLocationFromDirectory( + loc := file.NewVirtualLocationFromDirectory( r.responsePath(string(refVia.Reference.RealPath)), // the actual path relative to the resolver root r.responsePath(string(refVia.RequestPath)), // the path used to access this file, relative to the resolver root *refVia.Reference, @@ -257,8 +258,8 @@ func (r directoryResolver) FilesByGlob(patterns ...string) ([]Location, error) { // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. // This is helpful when attempting to find a file that is in the same layer or lower as another file. For the -// directoryResolver, this is a simple path lookup. -func (r *directoryResolver) RelativeFileByPath(_ Location, path string) *Location { +// Directory, this is a simple path lookup. +func (r *Directory) RelativeFileByPath(_ file.Location, path string) *file.Location { paths, err := r.FilesByPath(path) if err != nil { return nil @@ -272,54 +273,54 @@ func (r *directoryResolver) RelativeFileByPath(_ Location, path string) *Locatio // FileContentsByLocation fetches file contents for a single file reference relative to a directory. // If the path does not exist an error is returned. -func (r directoryResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { - if location.ref.RealPath == "" { +func (r Directory) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { + if location.RealPath == "" { return nil, errors.New("empty path given") } - entry, err := r.index.Get(location.ref) + entry, err := r.index.Get(location.Reference()) if err != nil { return nil, err } // don't consider directories - if entry.Type == file.TypeDirectory { - return nil, fmt.Errorf("cannot read contents of non-file %q", location.ref.RealPath) + if entry.Type == stereoscopeFile.TypeDirectory { + return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath) } // RealPath is posix so for windows directory resolver we need to translate // to its true on disk path. - filePath := string(location.ref.RealPath) + filePath := string(location.Reference().RealPath) if runtime.GOOS == WindowsOS { filePath = posixToWindows(filePath) } - return file.NewLazyReadCloser(filePath), nil + return stereoscopeFile.NewLazyReadCloser(filePath), nil } -func (r *directoryResolver) AllLocations() <-chan Location { - results := make(chan Location) +func (r *Directory) AllLocations() <-chan file.Location { + results := make(chan file.Location) go func() { defer close(results) - for _, ref := range r.tree.AllFiles(file.AllTypes()...) { - results <- NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), ref) + for _, ref := range r.tree.AllFiles(stereoscopeFile.AllTypes()...) { + results <- file.NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), ref) } }() return results } -func (r *directoryResolver) FileMetadataByLocation(location Location) (FileMetadata, error) { - entry, err := r.index.Get(location.ref) +func (r *Directory) FileMetadataByLocation(location file.Location) (file.Metadata, error) { + entry, err := r.index.Get(location.Reference()) if err != nil { - return FileMetadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist) + return file.Metadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist) } return entry.Metadata, nil } -func (r *directoryResolver) FilesByMIMEType(types ...string) ([]Location, error) { - uniqueFileIDs := file.NewFileReferenceSet() - uniqueLocations := make([]Location, 0) +func (r *Directory) FilesByMIMEType(types ...string) ([]file.Location, error) { + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet() + uniqueLocations := make([]file.Location, 0) refVias, err := r.searchContext.SearchByMIMEType(types...) if err != nil { @@ -332,7 +333,7 @@ func (r *directoryResolver) FilesByMIMEType(types ...string) ([]Location, error) if uniqueFileIDs.Contains(*refVia.Reference) { continue } - location := NewLocationFromDirectory( + location := file.NewLocationFromDirectory( r.responsePath(string(refVia.Reference.RealPath)), *refVia.Reference, ) diff --git a/syft/source/directory_indexer.go b/syft/internal/fileresolver/directory_indexer.go similarity index 96% rename from syft/source/directory_indexer.go rename to syft/internal/fileresolver/directory_indexer.go index 186f8f8f9a6..c590e6caec0 100644 --- a/syft/source/directory_indexer.go +++ b/syft/internal/fileresolver/directory_indexer.go @@ -1,4 +1,4 @@ -package source +package fileresolver import ( "errors" @@ -20,30 +20,30 @@ import ( "github.com/anchore/syft/syft/event" ) -type pathIndexVisitor func(string, os.FileInfo, error) error +type PathIndexVisitor func(string, os.FileInfo, error) error type directoryIndexer struct { path string base string - pathIndexVisitors []pathIndexVisitor + pathIndexVisitors []PathIndexVisitor errPaths map[string]error tree filetree.ReadWriter index filetree.Index } -func newDirectoryIndexer(path, base string, visitors ...pathIndexVisitor) *directoryIndexer { +func newDirectoryIndexer(path, base string, visitors ...PathIndexVisitor) *directoryIndexer { i := &directoryIndexer{ path: path, base: base, tree: filetree.New(), index: filetree.NewIndex(), - pathIndexVisitors: append([]pathIndexVisitor{requireFileInfo, disallowByFileType, disallowUnixSystemRuntimePath}, visitors...), + pathIndexVisitors: append([]PathIndexVisitor{requireFileInfo, disallowByFileType, disallowUnixSystemRuntimePath}, visitors...), errPaths: make(map[string]error), } // these additional stateful visitors should be the first thing considered when walking / indexing i.pathIndexVisitors = append( - []pathIndexVisitor{ + []PathIndexVisitor{ i.disallowRevisitingVisitor, i.disallowFileAccessErr, }, @@ -181,7 +181,7 @@ func (r *directoryIndexer) indexPath(path string, info os.FileInfo, err error) ( func (r *directoryIndexer) disallowFileAccessErr(path string, _ os.FileInfo, err error) error { if r.isFileAccessErr(path, err) { - return errSkipPath + return ErrSkipPath } return nil } @@ -311,7 +311,7 @@ func (r *directoryIndexer) disallowRevisitingVisitor(path string, _ os.FileInfo, // signal to walk() that we should skip this directory entirely return fs.SkipDir } - return errSkipPath + return ErrSkipPath } return nil } @@ -330,7 +330,7 @@ func disallowByFileType(_ string, info os.FileInfo, _ error) error { } switch file.TypeFromMode(info.Mode()) { case file.TypeCharacterDevice, file.TypeSocket, file.TypeBlockDevice, file.TypeFIFO, file.TypeIrregular: - return errSkipPath + return ErrSkipPath // note: symlinks that point to these files may still get by. // We handle this later in processing to help prevent against infinite links traversal. } @@ -340,7 +340,7 @@ func disallowByFileType(_ string, info os.FileInfo, _ error) error { func requireFileInfo(_ string, info os.FileInfo, _ error) error { if info == nil { - return errSkipPath + return ErrSkipPath } return nil } diff --git a/syft/source/directory_indexer_test.go b/syft/internal/fileresolver/directory_indexer_test.go similarity index 97% rename from syft/source/directory_indexer_test.go rename to syft/internal/fileresolver/directory_indexer_test.go index b6403559d16..cccacfc2c57 100644 --- a/syft/source/directory_indexer_test.go +++ b/syft/internal/fileresolver/directory_indexer_test.go @@ -1,4 +1,4 @@ -package source +package fileresolver import ( "io/fs" @@ -172,7 +172,7 @@ func TestDirectoryIndexer_indexPath_skipsNilFileInfo(t *testing.T) { } func TestDirectoryIndexer_index(t *testing.T) { - // note: this test is testing the effects from newDirectoryResolver, indexTree, and addPathToIndex + // note: this test is testing the effects from NewFromDirectory, indexTree, and addPathToIndex indexer := newDirectoryIndexer("test-fixtures/system_paths/target", "") tree, index, err := indexer.build() require.NoError(t, err) @@ -237,7 +237,7 @@ func TestDirectoryIndexer_SkipsAlreadyVisitedLinkDestinations(t *testing.T) { } resolver := newDirectoryIndexer("./test-fixtures/symlinks-prune-indexing", "") // we want to cut ahead of any possible filters to see what paths are considered for indexing (closest to walking) - resolver.pathIndexVisitors = append([]pathIndexVisitor{pathObserver}, resolver.pathIndexVisitors...) + resolver.pathIndexVisitors = append([]PathIndexVisitor{pathObserver}, resolver.pathIndexVisitors...) // note: this test is NOT about the effects left on the tree or the index, but rather the WHICH paths that are // considered for indexing and HOW traversal prunes paths that have already been visited diff --git a/syft/source/directory_resolver_test.go b/syft/internal/fileresolver/directory_test.go similarity index 78% rename from syft/source/directory_resolver_test.go rename to syft/internal/fileresolver/directory_test.go index 1ab5eca8552..819c1df28b2 100644 --- a/syft/source/directory_resolver_test.go +++ b/syft/internal/fileresolver/directory_test.go @@ -1,12 +1,11 @@ //go:build !windows // +build !windows -package source +package fileresolver import ( "io" "io/fs" - "io/ioutil" "os" "path/filepath" "sort" @@ -19,7 +18,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/file" ) func TestDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) { @@ -46,17 +46,18 @@ func TestDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) { }, }, { - name: "should find a file from a relative path (root above cwd)", + name: "should find a file from a relative path (root above cwd)", + // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great relativeRoot: "../", - input: "sbom/sbom.go", + input: "fileresolver/directory.go", expected: []string{ - "sbom/sbom.go", + "fileresolver/directory.go", }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver, err := newDirectoryResolver(c.relativeRoot, "") + resolver, err := NewFromDirectory(c.relativeRoot, "") assert.NoError(t, err) refs, err := resolver.FilesByPath(c.input) @@ -95,11 +96,12 @@ func TestDirectoryResolver_FilesByPath_absoluteRoot(t *testing.T) { }, }, { - name: "should find a file from a relative path (root above cwd)", + name: "should find a file from a relative path (root above cwd)", + // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great relativeRoot: "../", - input: "sbom/sbom.go", + input: "fileresolver/directory.go", expected: []string{ - "sbom/sbom.go", + "fileresolver/directory.go", }, }, } @@ -110,7 +112,7 @@ func TestDirectoryResolver_FilesByPath_absoluteRoot(t *testing.T) { absRoot, err := filepath.Abs(c.relativeRoot) require.NoError(t, err) - resolver, err := newDirectoryResolver(absRoot, "") + resolver, err := NewFromDirectory(absRoot, "") assert.NoError(t, err) refs, err := resolver.FilesByPath(c.input) @@ -171,7 +173,7 @@ func TestDirectoryResolver_FilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver, err := newDirectoryResolver(c.root, "") + resolver, err := NewFromDirectory(c.root, "") assert.NoError(t, err) hasPath := resolver.HasPath(c.input) @@ -219,7 +221,7 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures", "") + resolver, err := NewFromDirectory("./test-fixtures", "") assert.NoError(t, err) refs, err := resolver.FilesByPath(c.input...) assert.NoError(t, err) @@ -232,7 +234,7 @@ func TestDirectoryResolver_MultipleFilesByPath(t *testing.T) { } func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures", "") + resolver, err := NewFromDirectory("./test-fixtures", "") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/image-symlinks/file*") assert.NoError(t, err) @@ -241,7 +243,7 @@ func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) { } func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/image-symlinks", "") + resolver, err := NewFromDirectory("./test-fixtures/image-symlinks", "") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/*.txt") assert.NoError(t, err) @@ -249,7 +251,7 @@ func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) { } func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures", "") + resolver, err := NewFromDirectory("./test-fixtures", "") assert.NoError(t, err) refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt") assert.NoError(t, err) @@ -276,7 +278,7 @@ func TestDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "") + resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "") assert.NoError(t, err) refs, err := resolver.FilesByPath(test.fixture) @@ -299,7 +301,7 @@ func TestDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) { func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) { // let's make certain that "dev/place" is not ignored, since it is not "/dev/place" - resolver, err := newDirectoryResolver("test-fixtures/system_paths/target", "") + resolver, err := NewFromDirectory("test-fixtures/system_paths/target", "") assert.NoError(t, err) // all paths should be found (non filtering matches a path) @@ -383,35 +385,35 @@ func Test_isUnallowableFileType(t *testing.T) { info: testFileInfo{ mode: os.ModeSocket, }, - expected: errSkipPath, + expected: ErrSkipPath, }, { name: "named pipe", info: testFileInfo{ mode: os.ModeNamedPipe, }, - expected: errSkipPath, + expected: ErrSkipPath, }, { name: "char device", info: testFileInfo{ mode: os.ModeCharDevice, }, - expected: errSkipPath, + expected: ErrSkipPath, }, { name: "block device", info: testFileInfo{ mode: os.ModeDevice, }, - expected: errSkipPath, + expected: ErrSkipPath, }, { name: "irregular", info: testFileInfo{ mode: os.ModeIrregular, }, - expected: errSkipPath, + expected: ErrSkipPath, }, } for _, test := range tests { @@ -435,7 +437,7 @@ func Test_directoryResolver_FilesByMIMEType(t *testing.T) { } for _, test := range tests { t.Run(test.fixturePath, func(t *testing.T) { - resolver, err := newDirectoryResolver(test.fixturePath, "") + resolver, err := NewFromDirectory(test.fixturePath, "") assert.NoError(t, err) locations, err := resolver.FilesByMIMEType(test.mimeType) assert.NoError(t, err) @@ -448,7 +450,7 @@ func Test_directoryResolver_FilesByMIMEType(t *testing.T) { } func Test_IndexingNestedSymLinks(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "") + resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "") require.NoError(t, err) // check that we can get the real path @@ -499,12 +501,12 @@ func Test_IndexingNestedSymLinks(t *testing.T) { func Test_IndexingNestedSymLinks_ignoredIndexes(t *testing.T) { filterFn := func(path string, _ os.FileInfo, _ error) error { if strings.HasSuffix(path, string(filepath.Separator)+"readme") { - return errSkipPath + return ErrSkipPath } return nil } - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "", filterFn) + resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "", filterFn) require.NoError(t, err) // the path to the real file is PRUNED from the index, so we should NOT expect a location returned @@ -524,7 +526,7 @@ func Test_IndexingNestedSymLinks_ignoredIndexes(t *testing.T) { } func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-multiple-roots/root", "") + resolver, err := NewFromDirectory("./test-fixtures/symlinks-multiple-roots/root", "") require.NoError(t, err) // check that we can get the real path @@ -542,7 +544,7 @@ func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) { } func Test_RootViaSymlink(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/symlinked-root/nested/link-root", "") + resolver, err := NewFromDirectory("./test-fixtures/symlinked-root/nested/link-root", "") require.NoError(t, err) locations, err := resolver.FilesByPath("./file1.txt") @@ -562,28 +564,28 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) { cwd, err := os.Getwd() require.NoError(t, err) - r, err := newDirectoryResolver(".", "") + r, err := NewFromDirectory(".", "") require.NoError(t, err) - exists, existingPath, err := r.tree.File(file.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt"))) + exists, existingPath, err := r.tree.File(stereoscopeFile.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt"))) require.True(t, exists) require.NoError(t, err) require.True(t, existingPath.HasReference()) tests := []struct { name string - location Location + location file.Location expects string err bool }{ { name: "use file reference for content requests", - location: NewLocationFromDirectory("some/place", *existingPath.Reference), + location: file.NewLocationFromDirectory("some/place", *existingPath.Reference), expects: "this file has contents", }, { name: "error on empty file reference", - location: NewLocationFromDirectory("doesn't matter", file.Reference{}), + location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), err: true, }, } @@ -598,7 +600,7 @@ func Test_directoryResolver_FileContentsByLocation(t *testing.T) { require.NoError(t, err) if test.expects != "" { - b, err := ioutil.ReadAll(actual) + b, err := io.ReadAll(actual) require.NoError(t, err) assert.Equal(t, test.expects, string(b)) } @@ -649,7 +651,7 @@ func Test_isUnixSystemRuntimePath(t *testing.T) { func Test_SymlinkLoopWithGlobsShouldResolve(t *testing.T) { test := func(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-loop", "") + resolver, err := NewFromDirectory("./test-fixtures/symlinks-loop", "") require.NoError(t, err) locations, err := resolver.FilesByGlob("**/file.target") @@ -662,20 +664,6 @@ func Test_SymlinkLoopWithGlobsShouldResolve(t *testing.T) { testWithTimeout(t, 5*time.Second, test) } -func testWithTimeout(t *testing.T, timeout time.Duration, test func(*testing.T)) { - done := make(chan bool) - go func() { - test(t) - done <- true - }() - - select { - case <-time.After(timeout): - t.Fatal("test timed out") - case <-done: - } -} - func TestDirectoryResolver_FilesByPath_baseRoot(t *testing.T) { cases := []struct { name string @@ -734,7 +722,7 @@ func TestDirectoryResolver_FilesByPath_baseRoot(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver, err := newDirectoryResolver(c.root, c.root) + resolver, err := NewFromDirectory(c.root, c.root) assert.NoError(t, err) refs, err := resolver.FilesByPath(c.input) @@ -753,162 +741,132 @@ func TestDirectoryResolver_FilesByPath_baseRoot(t *testing.T) { func Test_directoryResolver_resolvesLinks(t *testing.T) { tests := []struct { name string - runner func(FileResolver) []Location - expected []Location + runner func(file.Resolver) []file.Location + expected []file.Location }{ { name: "by mimetype", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links should not show up when searching mimetype actualLocations, err := resolver.FilesByMIMEType("text/plain") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" - NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" - NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" - NewLocation("parent/file-4.txt"), // note: missing virtual path "file-4.txt" + expected: []file.Location{ + file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" + file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" + file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" + file.NewLocation("parent/file-4.txt"), // note: missing virtual path "file-4.txt" }, }, { name: "by glob to links", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files // for that reason we need to place **/ in front (which is not the same for other resolvers) actualLocations, err := resolver.FilesByGlob("**/*ink-*") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("file-1.txt", "link-1"), - NewVirtualLocation("file-2.txt", "link-2"), + expected: []file.Location{ + file.NewVirtualLocation("file-1.txt", "link-1"), + file.NewVirtualLocation("file-2.txt", "link-2"), // we already have this real file path via another link, so only one is returned - //NewVirtualLocation("file-2.txt", "link-indirect"), - NewVirtualLocation("file-3.txt", "link-within"), + //file.NewVirtualLocation("file-2.txt", "link-indirect"), + file.NewVirtualLocation("file-3.txt", "link-within"), }, }, { name: "by basename", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/file-2.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // this has two copies in the base image, which overwrites the same location - NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt", + file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt", }, }, { name: "by basename glob", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/file-?.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" - NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" - NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" - NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt" + expected: []file.Location{ + file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" + file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" + file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" + file.NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt" }, }, { name: "by basename glob to links", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { actualLocations, err := resolver.FilesByGlob("**/link-*") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - { - LocationData: LocationData{ - Coordinates: Coordinates{ - RealPath: "file-1.txt", - }, - VirtualPath: "link-1", - ref: file.Reference{RealPath: "file-1.txt"}, - }, - }, - { - LocationData: LocationData{ - Coordinates: Coordinates{ - RealPath: "file-2.txt", - }, - VirtualPath: "link-2", - ref: file.Reference{RealPath: "file-2.txt"}, - }, - }, + expected: []file.Location{ + file.NewVirtualLocation("file-1.txt", "link-1"), + file.NewVirtualLocation("file-2.txt", "link-2"), + // we already have this real file path via another link, so only one is returned - //{ - // LocationData: LocationData{ - // Coordinates: Coordinates{ - // RealPath: "file-2.txt", - // }, - // VirtualPath: "link-indirect", - // ref: file.Reference{RealPath: "file-2.txt"}, - // }, - //}, - { - LocationData: LocationData{ - Coordinates: Coordinates{ - RealPath: "file-3.txt", - }, - VirtualPath: "link-within", - ref: file.Reference{RealPath: "file-3.txt"}, - }, - }, + //file.NewVirtualLocation("file-2.txt", "link-indirect"), + + file.NewVirtualLocation("file-3.txt", "link-within"), }, }, { name: "by extension", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/*.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" - NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" - NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" - NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt" + expected: []file.Location{ + file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt" + file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt" + file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt" + file.NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt" }, }, { name: "by path to degree 1 link", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links resolve to the final file actualLocations, err := resolver.FilesByPath("/link-2") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // we have multiple copies across layers - NewVirtualLocation("file-2.txt", "link-2"), + file.NewVirtualLocation("file-2.txt", "link-2"), }, }, { name: "by path to degree 2 link", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // multiple links resolves to the final file actualLocations, err := resolver.FilesByPath("/link-indirect") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // we have multiple copies across layers - NewVirtualLocation("file-2.txt", "link-indirect"), + file.NewVirtualLocation("file-2.txt", "link-indirect"), }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-from-image-symlinks-fixture", "") + resolver, err := NewFromDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture", "") require.NoError(t, err) assert.NoError(t, err) @@ -920,14 +878,14 @@ func Test_directoryResolver_resolvesLinks(t *testing.T) { } func TestDirectoryResolver_DoNotAddVirtualPathsToTree(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-prune-indexing", "") + resolver, err := NewFromDirectory("./test-fixtures/symlinks-prune-indexing", "") require.NoError(t, err) - var allRealPaths []file.Path + var allRealPaths []stereoscopeFile.Path for l := range resolver.AllLocations() { - allRealPaths = append(allRealPaths, file.Path(l.RealPath)) + allRealPaths = append(allRealPaths, stereoscopeFile.Path(l.RealPath)) } - pathSet := file.NewPathSet(allRealPaths...) + pathSet := stereoscopeFile.NewPathSet(allRealPaths...) assert.False(t, pathSet.Contains("before-path/file.txt"), @@ -942,12 +900,12 @@ func TestDirectoryResolver_DoNotAddVirtualPathsToTree(t *testing.T) { } func TestDirectoryResolver_FilesContents_errorOnDirRequest(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/system_paths", "") + resolver, err := NewFromDirectory("./test-fixtures/system_paths", "") assert.NoError(t, err) - var dirLoc *Location + var dirLoc *file.Location for loc := range resolver.AllLocations() { - entry, err := resolver.index.Get(loc.ref) + entry, err := resolver.index.Get(loc.Reference()) require.NoError(t, err) if entry.Metadata.IsDir() { dirLoc = &loc @@ -963,13 +921,13 @@ func TestDirectoryResolver_FilesContents_errorOnDirRequest(t *testing.T) { } func TestDirectoryResolver_AllLocations(t *testing.T) { - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-from-image-symlinks-fixture", "") + resolver, err := NewFromDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture", "") assert.NoError(t, err) paths := strset.New() for loc := range resolver.AllLocations() { if strings.HasPrefix(loc.RealPath, "/") { - // ignore outside of the fixture root for now + // ignore outside the fixture root for now continue } paths.Add(loc.RealPath) diff --git a/syft/source/directory_resolver_windows_test.go b/syft/internal/fileresolver/directory_windows_test.go similarity index 99% rename from syft/source/directory_resolver_windows_test.go rename to syft/internal/fileresolver/directory_windows_test.go index 18cbb7856c4..115fb30a3b1 100644 --- a/syft/source/directory_resolver_windows_test.go +++ b/syft/internal/fileresolver/directory_windows_test.go @@ -1,4 +1,4 @@ -package source +package fileresolver import "testing" diff --git a/syft/internal/fileresolver/empty.go b/syft/internal/fileresolver/empty.go new file mode 100644 index 00000000000..3b08f395c87 --- /dev/null +++ b/syft/internal/fileresolver/empty.go @@ -0,0 +1,47 @@ +package fileresolver + +import ( + "io" + + "github.com/anchore/syft/syft/file" +) + +var _ file.WritableResolver = (*Empty)(nil) + +type Empty struct{} + +func (e Empty) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) { + return nil, nil +} + +func (e Empty) HasPath(_ string) bool { + return false +} + +func (e Empty) FilesByPath(_ ...string) ([]file.Location, error) { + return nil, nil +} + +func (e Empty) FilesByGlob(_ ...string) ([]file.Location, error) { + return nil, nil +} + +func (e Empty) FilesByMIMEType(_ ...string) ([]file.Location, error) { + return nil, nil +} + +func (e Empty) RelativeFileByPath(_ file.Location, _ string) *file.Location { + return nil +} + +func (e Empty) AllLocations() <-chan file.Location { + return nil +} + +func (e Empty) FileMetadataByLocation(_ file.Location) (file.Metadata, error) { + return file.Metadata{}, nil +} + +func (e Empty) Write(_ file.Location, _ io.Reader) error { + return nil +} diff --git a/syft/source/excluding_file_resolver.go b/syft/internal/fileresolver/excluding_file.go similarity index 55% rename from syft/source/excluding_file_resolver.go rename to syft/internal/fileresolver/excluding_file.go index 50969116a81..81caa49c765 100644 --- a/syft/source/excluding_file_resolver.go +++ b/syft/internal/fileresolver/excluding_file.go @@ -1,65 +1,67 @@ -package source +package fileresolver import ( "fmt" "io" + + "github.com/anchore/syft/syft/file" ) type excludeFn func(string) bool -// excludingResolver decorates a resolver with an exclusion function that is used to +// excluding decorates a resolver with an exclusion function that is used to // filter out entries in the delegate resolver -type excludingResolver struct { - delegate FileResolver +type excluding struct { + delegate file.Resolver excludeFn excludeFn } -// NewExcludingResolver create a new resolver which wraps the provided delegate and excludes +// NewExcluding create a new resolver which wraps the provided delegate and excludes // entries based on a provided path exclusion function -func NewExcludingResolver(delegate FileResolver, excludeFn excludeFn) FileResolver { - return &excludingResolver{ +func NewExcluding(delegate file.Resolver, excludeFn excludeFn) file.Resolver { + return &excluding{ delegate, excludeFn, } } -func (r *excludingResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { +func (r *excluding) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { if locationMatches(&location, r.excludeFn) { return nil, fmt.Errorf("no such location: %+v", location.RealPath) } return r.delegate.FileContentsByLocation(location) } -func (r *excludingResolver) FileMetadataByLocation(location Location) (FileMetadata, error) { +func (r *excluding) FileMetadataByLocation(location file.Location) (file.Metadata, error) { if locationMatches(&location, r.excludeFn) { - return FileMetadata{}, fmt.Errorf("no such location: %+v", location.RealPath) + return file.Metadata{}, fmt.Errorf("no such location: %+v", location.RealPath) } return r.delegate.FileMetadataByLocation(location) } -func (r *excludingResolver) HasPath(path string) bool { +func (r *excluding) HasPath(path string) bool { if r.excludeFn(path) { return false } return r.delegate.HasPath(path) } -func (r *excludingResolver) FilesByPath(paths ...string) ([]Location, error) { +func (r *excluding) FilesByPath(paths ...string) ([]file.Location, error) { locations, err := r.delegate.FilesByPath(paths...) return filterLocations(locations, err, r.excludeFn) } -func (r *excludingResolver) FilesByGlob(patterns ...string) ([]Location, error) { +func (r *excluding) FilesByGlob(patterns ...string) ([]file.Location, error) { locations, err := r.delegate.FilesByGlob(patterns...) return filterLocations(locations, err, r.excludeFn) } -func (r *excludingResolver) FilesByMIMEType(types ...string) ([]Location, error) { +func (r *excluding) FilesByMIMEType(types ...string) ([]file.Location, error) { locations, err := r.delegate.FilesByMIMEType(types...) return filterLocations(locations, err, r.excludeFn) } -func (r *excludingResolver) RelativeFileByPath(location Location, path string) *Location { +func (r *excluding) RelativeFileByPath(location file.Location, path string) *file.Location { l := r.delegate.RelativeFileByPath(location, path) if l != nil && locationMatches(l, r.excludeFn) { return nil @@ -67,8 +69,8 @@ func (r *excludingResolver) RelativeFileByPath(location Location, path string) * return l } -func (r *excludingResolver) AllLocations() <-chan Location { - c := make(chan Location) +func (r *excluding) AllLocations() <-chan file.Location { + c := make(chan file.Location) go func() { defer close(c) for location := range r.delegate.AllLocations() { @@ -80,11 +82,11 @@ func (r *excludingResolver) AllLocations() <-chan Location { return c } -func locationMatches(location *Location, exclusionFn excludeFn) bool { +func locationMatches(location *file.Location, exclusionFn excludeFn) bool { return exclusionFn(location.RealPath) || exclusionFn(location.VirtualPath) } -func filterLocations(locations []Location, err error, exclusionFn excludeFn) ([]Location, error) { +func filterLocations(locations []file.Location, err error, exclusionFn excludeFn) ([]file.Location, error) { if err != nil { return nil, err } diff --git a/syft/source/excluding_file_resolver_test.go b/syft/internal/fileresolver/excluding_file_test.go similarity index 66% rename from syft/source/excluding_file_resolver_test.go rename to syft/internal/fileresolver/excluding_file_test.go index c448e39210e..2ba51473682 100644 --- a/syft/source/excluding_file_resolver_test.go +++ b/syft/internal/fileresolver/excluding_file_test.go @@ -1,4 +1,4 @@ -package source +package fileresolver import ( "io" @@ -6,6 +6,8 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/anchore/syft/syft/file" ) func TestExcludingResolver(t *testing.T) { @@ -54,7 +56,7 @@ func TestExcludingResolver(t *testing.T) { resolver := &mockResolver{ locations: test.locations, } - er := NewExcludingResolver(resolver, test.excludeFn) + er := NewExcluding(resolver, test.excludeFn) locations, _ := er.FilesByPath() assert.ElementsMatch(t, locationPaths(locations), test.expected) @@ -65,7 +67,7 @@ func TestExcludingResolver(t *testing.T) { locations, _ = er.FilesByMIMEType() assert.ElementsMatch(t, locationPaths(locations), test.expected) - locations = []Location{} + locations = []file.Location{} channel := er.AllLocations() for location := range channel { @@ -77,25 +79,25 @@ func TestExcludingResolver(t *testing.T) { for _, path := range diff { assert.False(t, er.HasPath(path)) - c, err := er.FileContentsByLocation(NewLocation(path)) + c, err := er.FileContentsByLocation(file.NewLocation(path)) assert.Nil(t, c) assert.Error(t, err) - m, err := er.FileMetadataByLocation(NewLocation(path)) + m, err := er.FileMetadataByLocation(file.NewLocation(path)) assert.Empty(t, m.LinkDestination) assert.Error(t, err) - l := er.RelativeFileByPath(NewLocation(""), path) + l := er.RelativeFileByPath(file.NewLocation(""), path) assert.Nil(t, l) } for _, path := range test.expected { assert.True(t, er.HasPath(path)) - c, err := er.FileContentsByLocation(NewLocation(path)) + c, err := er.FileContentsByLocation(file.NewLocation(path)) assert.NotNil(t, c) assert.Nil(t, err) - m, err := er.FileMetadataByLocation(NewLocation(path)) + m, err := er.FileMetadataByLocation(file.NewLocation(path)) assert.NotEmpty(t, m.LinkDestination) assert.Nil(t, err) - l := er.RelativeFileByPath(NewLocation(""), path) + l := er.RelativeFileByPath(file.NewLocation(""), path) assert.NotNil(t, l) } }) @@ -117,7 +119,7 @@ func difference(a, b []string) []string { return diff } -func locationPaths(locations []Location) []string { +func locationPaths(locations []file.Location) []string { paths := []string{} for _, l := range locations { paths = append(paths, l.RealPath) @@ -129,20 +131,20 @@ type mockResolver struct { locations []string } -func (r *mockResolver) getLocations() ([]Location, error) { - out := []Location{} +func (r *mockResolver) getLocations() ([]file.Location, error) { + out := []file.Location{} for _, path := range r.locations { - out = append(out, NewLocation(path)) + out = append(out, file.NewLocation(path)) } return out, nil } -func (r *mockResolver) FileContentsByLocation(_ Location) (io.ReadCloser, error) { +func (r *mockResolver) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) { return io.NopCloser(strings.NewReader("Hello, world!")), nil } -func (r *mockResolver) FileMetadataByLocation(_ Location) (FileMetadata, error) { - return FileMetadata{ +func (r *mockResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, error) { + return file.Metadata{ LinkDestination: "MOCK", }, nil } @@ -151,37 +153,37 @@ func (r *mockResolver) HasPath(_ string) bool { return true } -func (r *mockResolver) FilesByPath(_ ...string) ([]Location, error) { +func (r *mockResolver) FilesByPath(_ ...string) ([]file.Location, error) { return r.getLocations() } -func (r *mockResolver) FilesByGlob(_ ...string) ([]Location, error) { +func (r *mockResolver) FilesByGlob(_ ...string) ([]file.Location, error) { return r.getLocations() } -func (r *mockResolver) FilesByMIMEType(_ ...string) ([]Location, error) { +func (r *mockResolver) FilesByMIMEType(_ ...string) ([]file.Location, error) { return r.getLocations() } -func (r *mockResolver) FilesByExtension(_ ...string) ([]Location, error) { +func (r *mockResolver) FilesByExtension(_ ...string) ([]file.Location, error) { return r.getLocations() } -func (r *mockResolver) FilesByBasename(_ ...string) ([]Location, error) { +func (r *mockResolver) FilesByBasename(_ ...string) ([]file.Location, error) { return r.getLocations() } -func (r *mockResolver) FilesByBasenameGlob(_ ...string) ([]Location, error) { +func (r *mockResolver) FilesByBasenameGlob(_ ...string) ([]file.Location, error) { return r.getLocations() } -func (r *mockResolver) RelativeFileByPath(_ Location, path string) *Location { - l := NewLocation(path) +func (r *mockResolver) RelativeFileByPath(_ file.Location, path string) *file.Location { + l := file.NewLocation(path) return &l } -func (r *mockResolver) AllLocations() <-chan Location { - c := make(chan Location) +func (r *mockResolver) AllLocations() <-chan file.Location { + c := make(chan file.Location) go func() { defer close(c) locations, _ := r.getLocations() diff --git a/syft/internal/fileresolver/file_metadata_by_location.go b/syft/internal/fileresolver/file_metadata_by_location.go new file mode 100644 index 00000000000..9d5974d9b05 --- /dev/null +++ b/syft/internal/fileresolver/file_metadata_by_location.go @@ -0,0 +1,15 @@ +package fileresolver + +import ( + "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/syft/syft/file" +) + +func fileMetadataByLocation(img *image.Image, location file.Location) (file.Metadata, error) { + entry, err := img.FileCatalog.Get(location.Reference()) + if err != nil { + return file.Metadata{}, err + } + + return entry.Metadata, nil +} diff --git a/syft/internal/fileresolver/test-fixtures/generate-tar-fixture-from-source-dir.sh b/syft/internal/fileresolver/test-fixtures/generate-tar-fixture-from-source-dir.sh new file mode 100755 index 00000000000..922941d36fb --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/generate-tar-fixture-from-source-dir.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -eux + +# $1 —— absolute path to destination file, should end with .tar +# $2 —— absolute path to directory from which to add entries to the archive + +pushd "$2" + tar -cvf "$1" . +popd diff --git a/syft/source/test-fixtures/image-duplicate-path/Dockerfile b/syft/internal/fileresolver/test-fixtures/image-duplicate-path/Dockerfile similarity index 100% rename from syft/source/test-fixtures/image-duplicate-path/Dockerfile rename to syft/internal/fileresolver/test-fixtures/image-duplicate-path/Dockerfile diff --git a/syft/source/test-fixtures/image-duplicate-path/file-1.txt b/syft/internal/fileresolver/test-fixtures/image-duplicate-path/file-1.txt similarity index 100% rename from syft/source/test-fixtures/image-duplicate-path/file-1.txt rename to syft/internal/fileresolver/test-fixtures/image-duplicate-path/file-1.txt diff --git a/syft/source/test-fixtures/image-duplicate-path/file-2.txt b/syft/internal/fileresolver/test-fixtures/image-duplicate-path/file-2.txt similarity index 100% rename from syft/source/test-fixtures/image-duplicate-path/file-2.txt rename to syft/internal/fileresolver/test-fixtures/image-duplicate-path/file-2.txt diff --git a/syft/source/test-fixtures/image-files-deleted/Dockerfile b/syft/internal/fileresolver/test-fixtures/image-files-deleted/Dockerfile similarity index 100% rename from syft/source/test-fixtures/image-files-deleted/Dockerfile rename to syft/internal/fileresolver/test-fixtures/image-files-deleted/Dockerfile diff --git a/syft/source/test-fixtures/image-files-deleted/file-1.txt b/syft/internal/fileresolver/test-fixtures/image-files-deleted/file-1.txt similarity index 100% rename from syft/source/test-fixtures/image-files-deleted/file-1.txt rename to syft/internal/fileresolver/test-fixtures/image-files-deleted/file-1.txt diff --git a/syft/source/test-fixtures/image-files-deleted/file-3.txt b/syft/internal/fileresolver/test-fixtures/image-files-deleted/file-3.txt similarity index 100% rename from syft/source/test-fixtures/image-files-deleted/file-3.txt rename to syft/internal/fileresolver/test-fixtures/image-files-deleted/file-3.txt diff --git a/syft/source/test-fixtures/image-files-deleted/target/file-2.txt b/syft/internal/fileresolver/test-fixtures/image-files-deleted/target/file-2.txt similarity index 100% rename from syft/source/test-fixtures/image-files-deleted/target/file-2.txt rename to syft/internal/fileresolver/test-fixtures/image-files-deleted/target/file-2.txt diff --git a/syft/internal/fileresolver/test-fixtures/image-simple/Dockerfile b/syft/internal/fileresolver/test-fixtures/image-simple/Dockerfile new file mode 100644 index 00000000000..62fb151e497 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/image-simple/Dockerfile @@ -0,0 +1,6 @@ +# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one. +FROM scratch +ADD file-1.txt /somefile-1.txt +ADD file-2.txt /somefile-2.txt +# note: adding a directory will behave differently on docker engine v18 vs v19 +ADD target / diff --git a/syft/internal/fileresolver/test-fixtures/image-simple/file-1.txt b/syft/internal/fileresolver/test-fixtures/image-simple/file-1.txt new file mode 100644 index 00000000000..985d3408e98 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/image-simple/file-1.txt @@ -0,0 +1 @@ +this file has contents \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/image-simple/file-2.txt b/syft/internal/fileresolver/test-fixtures/image-simple/file-2.txt new file mode 100644 index 00000000000..396d08bbc72 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/image-simple/file-2.txt @@ -0,0 +1 @@ +file-2 contents! \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/image-simple/target/really/nested/file-3.txt b/syft/internal/fileresolver/test-fixtures/image-simple/target/really/nested/file-3.txt new file mode 100644 index 00000000000..f85472c937d --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/image-simple/target/really/nested/file-3.txt @@ -0,0 +1,2 @@ +another file! +with lines... \ No newline at end of file diff --git a/syft/source/test-fixtures/image-symlinks/Dockerfile b/syft/internal/fileresolver/test-fixtures/image-symlinks/Dockerfile similarity index 100% rename from syft/source/test-fixtures/image-symlinks/Dockerfile rename to syft/internal/fileresolver/test-fixtures/image-symlinks/Dockerfile diff --git a/syft/internal/fileresolver/test-fixtures/image-symlinks/file-1.txt b/syft/internal/fileresolver/test-fixtures/image-symlinks/file-1.txt new file mode 100644 index 00000000000..d86db8155c3 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/image-symlinks/file-1.txt @@ -0,0 +1 @@ +file 1! \ No newline at end of file diff --git a/syft/source/test-fixtures/image-symlinks/file-2.txt b/syft/internal/fileresolver/test-fixtures/image-symlinks/file-2.txt similarity index 100% rename from syft/source/test-fixtures/image-symlinks/file-2.txt rename to syft/internal/fileresolver/test-fixtures/image-symlinks/file-2.txt diff --git a/syft/source/test-fixtures/image-symlinks/nested/nested/file-3.txt b/syft/internal/fileresolver/test-fixtures/image-symlinks/nested/nested/file-3.txt similarity index 100% rename from syft/source/test-fixtures/image-symlinks/nested/nested/file-3.txt rename to syft/internal/fileresolver/test-fixtures/image-symlinks/nested/nested/file-3.txt diff --git a/syft/source/test-fixtures/image-symlinks/new-file-2.txt b/syft/internal/fileresolver/test-fixtures/image-symlinks/new-file-2.txt similarity index 100% rename from syft/source/test-fixtures/image-symlinks/new-file-2.txt rename to syft/internal/fileresolver/test-fixtures/image-symlinks/new-file-2.txt diff --git a/syft/source/test-fixtures/image-symlinks/new-file-4.txt b/syft/internal/fileresolver/test-fixtures/image-symlinks/new-file-4.txt similarity index 100% rename from syft/source/test-fixtures/image-symlinks/new-file-4.txt rename to syft/internal/fileresolver/test-fixtures/image-symlinks/new-file-4.txt diff --git a/syft/source/test-fixtures/image-symlinks/parent/file-4.txt b/syft/internal/fileresolver/test-fixtures/image-symlinks/parent/file-4.txt similarity index 100% rename from syft/source/test-fixtures/image-symlinks/parent/file-4.txt rename to syft/internal/fileresolver/test-fixtures/image-symlinks/parent/file-4.txt diff --git a/syft/internal/fileresolver/test-fixtures/path-detected-2/.vimrc b/syft/internal/fileresolver/test-fixtures/path-detected-2/.vimrc new file mode 100644 index 00000000000..7f865a925e7 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/path-detected-2/.vimrc @@ -0,0 +1 @@ +Another .vimrc file \ No newline at end of file diff --git a/syft/source/test-fixtures/symlinks-base/sub/item b/syft/internal/fileresolver/test-fixtures/path-detected-2/empty similarity index 100% rename from syft/source/test-fixtures/symlinks-base/sub/item rename to syft/internal/fileresolver/test-fixtures/path-detected-2/empty diff --git a/syft/internal/fileresolver/test-fixtures/path-detected/.vimrc b/syft/internal/fileresolver/test-fixtures/path-detected/.vimrc new file mode 100644 index 00000000000..93b07e21b93 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/path-detected/.vimrc @@ -0,0 +1 @@ +" A .vimrc file diff --git a/syft/internal/fileresolver/test-fixtures/path-detected/empty b/syft/internal/fileresolver/test-fixtures/path-detected/empty new file mode 100644 index 00000000000..e69de29bb2d diff --git a/syft/source/test-fixtures/symlinked-root/nested/link-root b/syft/internal/fileresolver/test-fixtures/symlinked-root/nested/link-root similarity index 100% rename from syft/source/test-fixtures/symlinked-root/nested/link-root rename to syft/internal/fileresolver/test-fixtures/symlinked-root/nested/link-root diff --git a/syft/source/test-fixtures/symlinked-root/real-root/file1.txt b/syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/file1.txt similarity index 100% rename from syft/source/test-fixtures/symlinked-root/real-root/file1.txt rename to syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/file1.txt diff --git a/syft/source/test-fixtures/symlinked-root/real-root/nested/file2.txt b/syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/nested/file2.txt similarity index 100% rename from syft/source/test-fixtures/symlinked-root/real-root/nested/file2.txt rename to syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/nested/file2.txt diff --git a/syft/source/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt b/syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt similarity index 100% rename from syft/source/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt rename to syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt diff --git a/syft/source/test-fixtures/symlinks-base/bar b/syft/internal/fileresolver/test-fixtures/symlinks-base/bar similarity index 100% rename from syft/source/test-fixtures/symlinks-base/bar rename to syft/internal/fileresolver/test-fixtures/symlinks-base/bar diff --git a/syft/internal/fileresolver/test-fixtures/symlinks-base/base b/syft/internal/fileresolver/test-fixtures/symlinks-base/base new file mode 100644 index 00000000000..e69de29bb2d diff --git a/syft/source/test-fixtures/symlinks-base/baz b/syft/internal/fileresolver/test-fixtures/symlinks-base/baz similarity index 100% rename from syft/source/test-fixtures/symlinks-base/baz rename to syft/internal/fileresolver/test-fixtures/symlinks-base/baz diff --git a/syft/source/test-fixtures/symlinks-base/chain b/syft/internal/fileresolver/test-fixtures/symlinks-base/chain similarity index 100% rename from syft/source/test-fixtures/symlinks-base/chain rename to syft/internal/fileresolver/test-fixtures/symlinks-base/chain diff --git a/syft/source/test-fixtures/symlinks-base/foo b/syft/internal/fileresolver/test-fixtures/symlinks-base/foo similarity index 100% rename from syft/source/test-fixtures/symlinks-base/foo rename to syft/internal/fileresolver/test-fixtures/symlinks-base/foo diff --git a/syft/internal/fileresolver/test-fixtures/symlinks-base/sub/item b/syft/internal/fileresolver/test-fixtures/symlinks-base/sub/item new file mode 100644 index 00000000000..e69de29bb2d diff --git a/syft/source/test-fixtures/symlinks-base/sub/link b/syft/internal/fileresolver/test-fixtures/symlinks-base/sub/link similarity index 100% rename from syft/source/test-fixtures/symlinks-base/sub/link rename to syft/internal/fileresolver/test-fixtures/symlinks-base/sub/link diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/file-1.txt b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/file-1.txt similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/file-1.txt rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/file-1.txt diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/file-2.txt b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/file-2.txt similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/file-2.txt rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/file-2.txt diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/file-3.txt b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/file-3.txt similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/file-3.txt rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/file-3.txt diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-1 b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-1 similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-1 rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-1 diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-2 b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-2 similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-2 rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-2 diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-dead b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-dead similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-dead rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-dead diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-indirect b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-indirect similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-indirect rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-indirect diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-within b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-within similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/link-within rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/link-within diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/parent-link b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/parent-link similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/parent-link rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/parent-link diff --git a/syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/parent/file-4.txt b/syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/parent/file-4.txt similarity index 100% rename from syft/source/test-fixtures/symlinks-from-image-symlinks-fixture/parent/file-4.txt rename to syft/internal/fileresolver/test-fixtures/symlinks-from-image-symlinks-fixture/parent/file-4.txt diff --git a/syft/source/test-fixtures/symlinks-loop/README.md b/syft/internal/fileresolver/test-fixtures/symlinks-loop/README.md similarity index 100% rename from syft/source/test-fixtures/symlinks-loop/README.md rename to syft/internal/fileresolver/test-fixtures/symlinks-loop/README.md diff --git a/syft/source/test-fixtures/symlinks-loop/block/loop0 b/syft/internal/fileresolver/test-fixtures/symlinks-loop/block/loop0 similarity index 100% rename from syft/source/test-fixtures/symlinks-loop/block/loop0 rename to syft/internal/fileresolver/test-fixtures/symlinks-loop/block/loop0 diff --git a/syft/source/test-fixtures/symlinks-loop/devices/loop0/file.target b/syft/internal/fileresolver/test-fixtures/symlinks-loop/devices/loop0/file.target similarity index 100% rename from syft/source/test-fixtures/symlinks-loop/devices/loop0/file.target rename to syft/internal/fileresolver/test-fixtures/symlinks-loop/devices/loop0/file.target diff --git a/syft/source/test-fixtures/symlinks-loop/devices/loop0/subsystem b/syft/internal/fileresolver/test-fixtures/symlinks-loop/devices/loop0/subsystem similarity index 100% rename from syft/source/test-fixtures/symlinks-loop/devices/loop0/subsystem rename to syft/internal/fileresolver/test-fixtures/symlinks-loop/devices/loop0/subsystem diff --git a/syft/source/test-fixtures/symlinks-multiple-roots/outside/link_to_readme b/syft/internal/fileresolver/test-fixtures/symlinks-multiple-roots/outside/link_to_readme similarity index 100% rename from syft/source/test-fixtures/symlinks-multiple-roots/outside/link_to_readme rename to syft/internal/fileresolver/test-fixtures/symlinks-multiple-roots/outside/link_to_readme diff --git a/syft/source/test-fixtures/symlinks-multiple-roots/root/link_to_link_to_readme b/syft/internal/fileresolver/test-fixtures/symlinks-multiple-roots/root/link_to_link_to_readme similarity index 100% rename from syft/source/test-fixtures/symlinks-multiple-roots/root/link_to_link_to_readme rename to syft/internal/fileresolver/test-fixtures/symlinks-multiple-roots/root/link_to_link_to_readme diff --git a/syft/source/test-fixtures/symlinks-multiple-roots/root/readme b/syft/internal/fileresolver/test-fixtures/symlinks-multiple-roots/root/readme similarity index 100% rename from syft/source/test-fixtures/symlinks-multiple-roots/root/readme rename to syft/internal/fileresolver/test-fixtures/symlinks-multiple-roots/root/readme diff --git a/syft/source/test-fixtures/symlinks-prune-indexing/before-path b/syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/before-path similarity index 100% rename from syft/source/test-fixtures/symlinks-prune-indexing/before-path rename to syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/before-path diff --git a/syft/source/test-fixtures/symlinks-prune-indexing/c-file.txt b/syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/c-file.txt similarity index 100% rename from syft/source/test-fixtures/symlinks-prune-indexing/c-file.txt rename to syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/c-file.txt diff --git a/syft/source/test-fixtures/symlinks-prune-indexing/c-path b/syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/c-path similarity index 100% rename from syft/source/test-fixtures/symlinks-prune-indexing/c-path rename to syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/c-path diff --git a/syft/source/test-fixtures/symlinks-prune-indexing/path/1/2/3/4/dont-index-me-twice.txt b/syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/path/1/2/3/4/dont-index-me-twice.txt similarity index 100% rename from syft/source/test-fixtures/symlinks-prune-indexing/path/1/2/3/4/dont-index-me-twice.txt rename to syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/path/1/2/3/4/dont-index-me-twice.txt diff --git a/syft/source/test-fixtures/symlinks-prune-indexing/path/5/6/7/8/dont-index-me-twice-either.txt b/syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/path/5/6/7/8/dont-index-me-twice-either.txt similarity index 100% rename from syft/source/test-fixtures/symlinks-prune-indexing/path/5/6/7/8/dont-index-me-twice-either.txt rename to syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/path/5/6/7/8/dont-index-me-twice-either.txt diff --git a/syft/source/test-fixtures/symlinks-prune-indexing/path/file.txt b/syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/path/file.txt similarity index 100% rename from syft/source/test-fixtures/symlinks-prune-indexing/path/file.txt rename to syft/internal/fileresolver/test-fixtures/symlinks-prune-indexing/path/file.txt diff --git a/syft/source/test-fixtures/symlinks-simple/link_to_link_to_new_readme b/syft/internal/fileresolver/test-fixtures/symlinks-simple/link_to_link_to_new_readme similarity index 100% rename from syft/source/test-fixtures/symlinks-simple/link_to_link_to_new_readme rename to syft/internal/fileresolver/test-fixtures/symlinks-simple/link_to_link_to_new_readme diff --git a/syft/source/test-fixtures/symlinks-simple/link_to_new_readme b/syft/internal/fileresolver/test-fixtures/symlinks-simple/link_to_new_readme similarity index 100% rename from syft/source/test-fixtures/symlinks-simple/link_to_new_readme rename to syft/internal/fileresolver/test-fixtures/symlinks-simple/link_to_new_readme diff --git a/syft/source/test-fixtures/symlinks-simple/readme b/syft/internal/fileresolver/test-fixtures/symlinks-simple/readme similarity index 100% rename from syft/source/test-fixtures/symlinks-simple/readme rename to syft/internal/fileresolver/test-fixtures/symlinks-simple/readme diff --git a/syft/internal/fileresolver/test-fixtures/system_paths/outside_root/link_target/place b/syft/internal/fileresolver/test-fixtures/system_paths/outside_root/link_target/place new file mode 100644 index 00000000000..476e93d5714 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/system_paths/outside_root/link_target/place @@ -0,0 +1 @@ +good \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/system_paths/target/dev/place b/syft/internal/fileresolver/test-fixtures/system_paths/target/dev/place new file mode 100644 index 00000000000..44d6628cdc6 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/system_paths/target/dev/place @@ -0,0 +1 @@ +bad \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/system_paths/target/home/place b/syft/internal/fileresolver/test-fixtures/system_paths/target/home/place new file mode 100644 index 00000000000..476e93d5714 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/system_paths/target/home/place @@ -0,0 +1 @@ +good \ No newline at end of file diff --git a/syft/source/test-fixtures/system_paths/target/link/a-symlink b/syft/internal/fileresolver/test-fixtures/system_paths/target/link/a-symlink similarity index 100% rename from syft/source/test-fixtures/system_paths/target/link/a-symlink rename to syft/internal/fileresolver/test-fixtures/system_paths/target/link/a-symlink diff --git a/syft/internal/fileresolver/test-fixtures/system_paths/target/proc/place b/syft/internal/fileresolver/test-fixtures/system_paths/target/proc/place new file mode 100644 index 00000000000..44d6628cdc6 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/system_paths/target/proc/place @@ -0,0 +1 @@ +bad \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/system_paths/target/sys/place b/syft/internal/fileresolver/test-fixtures/system_paths/target/sys/place new file mode 100644 index 00000000000..44d6628cdc6 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/system_paths/target/sys/place @@ -0,0 +1 @@ +bad \ No newline at end of file diff --git a/syft/source/unindexed_directory_resolver.go b/syft/internal/fileresolver/unindexed_directory.go similarity index 80% rename from syft/source/unindexed_directory_resolver.go rename to syft/internal/fileresolver/unindexed_directory.go index e965fef5c34..ae2300c5a39 100644 --- a/syft/source/unindexed_directory_resolver.go +++ b/syft/internal/fileresolver/unindexed_directory.go @@ -1,4 +1,4 @@ -package source +package fileresolver import ( "fmt" @@ -16,9 +16,13 @@ import ( "golang.org/x/exp/slices" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" ) -type UnindexedDirectoryResolver struct { +var _ file.Resolver = (*UnindexedDirectory)(nil) +var _ file.WritableResolver = (*UnindexedDirectory)(nil) + +type UnindexedDirectory struct { ls afero.Lstater lr afero.LinkReader base string @@ -26,15 +30,15 @@ type UnindexedDirectoryResolver struct { fs afero.Fs } -func NewUnindexedDirectoryResolver(dir string) WritableFileResolver { - return NewUnindexedDirectoryResolverFS(afero.NewOsFs(), dir, "") +func NewFromUnindexedDirectory(dir string) file.WritableResolver { + return NewFromUnindexedDirectoryFS(afero.NewOsFs(), dir, "") } -func NewUnindexedDirectoryResolverRooted(dir string, base string) WritableFileResolver { - return NewUnindexedDirectoryResolverFS(afero.NewOsFs(), dir, base) +func NewFromRootedUnindexedDirectory(dir string, base string) file.WritableResolver { + return NewFromUnindexedDirectoryFS(afero.NewOsFs(), dir, base) } -func NewUnindexedDirectoryResolverFS(fs afero.Fs, dir string, base string) WritableFileResolver { +func NewFromUnindexedDirectoryFS(fs afero.Fs, dir string, base string) file.WritableResolver { ls, ok := fs.(afero.Lstater) if !ok { panic(fmt.Sprintf("unable to get afero.Lstater interface from: %+v", fs)) @@ -62,7 +66,7 @@ func NewUnindexedDirectoryResolverFS(fs afero.Fs, dir string, base string) Writa base = path.Clean(path.Join(wd, base)) } } - return UnindexedDirectoryResolver{ + return UnindexedDirectory{ base: base, dir: dir, fs: fs, @@ -71,7 +75,7 @@ func NewUnindexedDirectoryResolverFS(fs afero.Fs, dir string, base string) Writa } } -func (u UnindexedDirectoryResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { +func (u UnindexedDirectory) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { p := u.absPath(u.scrubInputPath(location.RealPath)) f, err := u.fs.Open(p) if err != nil { @@ -89,29 +93,29 @@ func (u UnindexedDirectoryResolver) FileContentsByLocation(location Location) (i // - full symlink resolution should be performed on all requests // - returns locations for any file or directory -func (u UnindexedDirectoryResolver) HasPath(p string) bool { +func (u UnindexedDirectory) HasPath(p string) bool { locs, err := u.filesByPath(true, true, p) return err == nil && len(locs) > 0 } -func (u UnindexedDirectoryResolver) canLstat(p string) bool { +func (u UnindexedDirectory) canLstat(p string) bool { _, _, err := u.ls.LstatIfPossible(u.absPath(p)) return err == nil } -func (u UnindexedDirectoryResolver) isRegularFile(p string) bool { +func (u UnindexedDirectory) isRegularFile(p string) bool { fi, _, err := u.ls.LstatIfPossible(u.absPath(p)) return err == nil && !fi.IsDir() } -func (u UnindexedDirectoryResolver) scrubInputPath(p string) string { +func (u UnindexedDirectory) scrubInputPath(p string) string { if path.IsAbs(p) { p = p[1:] } return path.Clean(p) } -func (u UnindexedDirectoryResolver) scrubResolutionPath(p string) string { +func (u UnindexedDirectory) scrubResolutionPath(p string) string { if u.base != "" { if path.IsAbs(p) { p = p[1:] @@ -123,7 +127,7 @@ func (u UnindexedDirectoryResolver) scrubResolutionPath(p string) string { return path.Clean(p) } -func (u UnindexedDirectoryResolver) absPath(p string) string { +func (u UnindexedDirectory) absPath(p string) string { if u.base != "" { if path.IsAbs(p) { p = p[1:] @@ -142,11 +146,11 @@ func (u UnindexedDirectoryResolver) absPath(p string) string { // - full symlink resolution should be performed on all requests // - only returns locations to files (NOT directories) -func (u UnindexedDirectoryResolver) FilesByPath(paths ...string) (out []Location, _ error) { +func (u UnindexedDirectory) FilesByPath(paths ...string) (out []file.Location, _ error) { return u.filesByPath(true, false, paths...) } -func (u UnindexedDirectoryResolver) filesByPath(resolveLinks bool, includeDirs bool, paths ...string) (out []Location, _ error) { +func (u UnindexedDirectory) filesByPath(resolveLinks bool, includeDirs bool, paths ...string) (out []file.Location, _ error) { // sort here for stable output sort.Strings(paths) nextPath: @@ -176,11 +180,11 @@ nextPath: // - full symlink resolution should be performed on all requests // - if multiple paths to the same file are found, the best single match should be returned // - only returns locations to files (NOT directories) -func (u UnindexedDirectoryResolver) FilesByGlob(patterns ...string) (out []Location, _ error) { +func (u UnindexedDirectory) FilesByGlob(patterns ...string) (out []file.Location, _ error) { return u.filesByGlob(true, false, patterns...) } -func (u UnindexedDirectoryResolver) filesByGlob(resolveLinks bool, includeDirs bool, patterns ...string) (out []Location, _ error) { +func (u UnindexedDirectory) filesByGlob(resolveLinks bool, includeDirs bool, patterns ...string) (out []file.Location, _ error) { f := unindexedDirectoryResolverFS{ u: u, } @@ -199,13 +203,13 @@ func (u UnindexedDirectoryResolver) filesByGlob(resolveLinks bool, includeDirs b return u.filesByPath(resolveLinks, includeDirs, paths...) } -func (u UnindexedDirectoryResolver) FilesByMIMEType(_ ...string) ([]Location, error) { +func (u UnindexedDirectory) FilesByMIMEType(_ ...string) ([]file.Location, error) { panic("FilesByMIMEType unsupported") } // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference. // This is helpful when attempting to find a file that is in the same layer or lower as another file. -func (u UnindexedDirectoryResolver) RelativeFileByPath(l Location, p string) *Location { +func (u UnindexedDirectory) RelativeFileByPath(l file.Location, p string) *file.Location { p = path.Clean(path.Join(l.RealPath, p)) locs, err := u.filesByPath(true, false, p) if err != nil || len(locs) == 0 { @@ -221,8 +225,8 @@ func (u UnindexedDirectoryResolver) RelativeFileByPath(l Location, p string) *Lo // - NO symlink resolution should be performed on results // - returns locations for any file or directory -func (u UnindexedDirectoryResolver) AllLocations() <-chan Location { - out := make(chan Location) +func (u UnindexedDirectory) AllLocations() <-chan file.Location { + out := make(chan file.Location) go func() { defer close(out) err := afero.Walk(u.fs, u.absPath("."), func(p string, info fs.FileInfo, err error) error { @@ -231,7 +235,7 @@ func (u UnindexedDirectoryResolver) AllLocations() <-chan Location { return nil } p = strings.TrimPrefix(p, "/") - out <- NewLocation(p) + out <- file.NewLocation(p) return nil }) if err != nil { @@ -241,11 +245,11 @@ func (u UnindexedDirectoryResolver) AllLocations() <-chan Location { return out } -func (u UnindexedDirectoryResolver) FileMetadataByLocation(_ Location) (FileMetadata, error) { +func (u UnindexedDirectory) FileMetadataByLocation(_ file.Location) (file.Metadata, error) { panic("FileMetadataByLocation unsupported") } -func (u UnindexedDirectoryResolver) Write(location Location, reader io.Reader) error { +func (u UnindexedDirectory) Write(location file.Location, reader io.Reader) error { filePath := location.RealPath if path.IsAbs(filePath) { filePath = filePath[1:] @@ -254,10 +258,7 @@ func (u UnindexedDirectoryResolver) Write(location Location, reader io.Reader) e return afero.WriteReader(u.fs, absPath, reader) } -var _ FileResolver = (*UnindexedDirectoryResolver)(nil) -var _ WritableFileResolver = (*UnindexedDirectoryResolver)(nil) - -func (u UnindexedDirectoryResolver) newLocation(filePath string, resolveLinks bool) *Location { +func (u UnindexedDirectory) newLocation(filePath string, resolveLinks bool) *file.Location { filePath = path.Clean(filePath) virtualPath := "" @@ -277,12 +278,12 @@ func (u UnindexedDirectoryResolver) newLocation(filePath string, resolveLinks bo } } - l := NewVirtualLocation(realPath, virtualPath) + l := file.NewVirtualLocation(realPath, virtualPath) return &l } //nolint:gocognit -func (u UnindexedDirectoryResolver) resolveLinks(filePath string) []string { +func (u UnindexedDirectory) resolveLinks(filePath string) []string { var visited []string out := []string{} @@ -349,15 +350,15 @@ func (u UnindexedDirectoryResolver) resolveLinks(filePath string) []string { return out } -func (u UnindexedDirectoryResolver) isSymlink(fi os.FileInfo) bool { +func (u UnindexedDirectory) isSymlink(fi os.FileInfo) bool { return fi.Mode().Type()&fs.ModeSymlink == fs.ModeSymlink } // ------------------------- fs.FS ------------------------------ -// unindexedDirectoryResolverFS wraps the UnindexedDirectoryResolver as a fs.FS, fs.ReadDirFS, and fs.StatFS +// unindexedDirectoryResolverFS wraps the UnindexedDirectory as a fs.FS, fs.ReadDirFS, and fs.StatFS type unindexedDirectoryResolverFS struct { - u UnindexedDirectoryResolver + u UnindexedDirectory } // resolve takes a virtual path and returns the resolved absolute or relative path and file info @@ -470,7 +471,7 @@ func (f unindexedDirectoryResolverDirEntry) Info() (fs.FileInfo, error) { var _ fs.DirEntry = (*unindexedDirectoryResolverDirEntry)(nil) type unindexedDirectoryResolverFile struct { - u UnindexedDirectoryResolver + u UnindexedDirectory path string } @@ -493,7 +494,7 @@ func (f unindexedDirectoryResolverFile) Close() error { var _ fs.File = (*unindexedDirectoryResolverFile)(nil) type unindexedDirectoryResolverFileInfo struct { - u UnindexedDirectoryResolver + u UnindexedDirectory name string size int64 mode fs.FileMode @@ -502,7 +503,7 @@ type unindexedDirectoryResolverFileInfo struct { sys any } -func newFsFileInfo(u UnindexedDirectoryResolver, name string, isDir bool, fi os.FileInfo) unindexedDirectoryResolverFileInfo { +func newFsFileInfo(u UnindexedDirectory, name string, isDir bool, fi os.FileInfo) unindexedDirectoryResolverFileInfo { return unindexedDirectoryResolverFileInfo{ u: u, name: name, diff --git a/syft/source/unindexed_directory_resolver_test.go b/syft/internal/fileresolver/unindexed_directory_test.go similarity index 76% rename from syft/source/unindexed_directory_resolver_test.go rename to syft/internal/fileresolver/unindexed_directory_test.go index f6b1586718d..14631fc4cd8 100644 --- a/syft/source/unindexed_directory_resolver_test.go +++ b/syft/internal/fileresolver/unindexed_directory_test.go @@ -1,7 +1,7 @@ //go:build !windows // +build !windows -package source +package fileresolver import ( "io" @@ -14,18 +14,20 @@ import ( "time" "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/scylladb/go-set/strset" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/stereoscope/pkg/file" + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/syft/syft/file" ) func Test_UnindexedDirectoryResolver_Basic(t *testing.T) { wd, err := os.Getwd() require.NoError(t, err) - r := NewUnindexedDirectoryResolver(path.Join(wd, "test-fixtures")) + r := NewFromUnindexedDirectory(path.Join(wd, "test-fixtures")) locations, err := r.FilesByGlob("image-symlinks/*") require.NoError(t, err) require.Len(t, locations, 5) @@ -55,17 +57,18 @@ func Test_UnindexedDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) { }, }, { - name: "should find a file from a relative path (root above cwd)", + name: "should find a file from a relative path (root above cwd)", + // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great relativeRoot: "../", - input: "sbom/sbom.go", + input: "fileresolver/deferred.go", expected: []string{ - "sbom/sbom.go", + "fileresolver/deferred.go", }, }, } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver := NewUnindexedDirectoryResolver(c.relativeRoot) + resolver := NewFromUnindexedDirectory(c.relativeRoot) refs, err := resolver.FilesByPath(c.input) require.NoError(t, err) @@ -103,11 +106,12 @@ func Test_UnindexedDirectoryResolver_FilesByPath_absoluteRoot(t *testing.T) { }, }, { - name: "should find a file from a relative path (root above cwd)", + name: "should find a file from a relative path (root above cwd)", + // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great relativeRoot: "../", - input: "sbom/sbom.go", + input: "fileresolver/directory.go", expected: []string{ - "sbom/sbom.go", + "fileresolver/directory.go", }, }, } @@ -118,7 +122,7 @@ func Test_UnindexedDirectoryResolver_FilesByPath_absoluteRoot(t *testing.T) { absRoot, err := filepath.Abs(c.relativeRoot) require.NoError(t, err) - resolver := NewUnindexedDirectoryResolver(absRoot) + resolver := NewFromUnindexedDirectory(absRoot) assert.NoError(t, err) refs, err := resolver.FilesByPath(c.input) @@ -179,7 +183,7 @@ func Test_UnindexedDirectoryResolver_FilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver := NewUnindexedDirectoryResolver(c.root) + resolver := NewFromUnindexedDirectory(c.root) hasPath := resolver.HasPath(c.input) if !c.forcePositiveHasPath { @@ -226,7 +230,7 @@ func Test_UnindexedDirectoryResolver_MultipleFilesByPath(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures") + resolver := NewFromUnindexedDirectory("./test-fixtures") refs, err := resolver.FilesByPath(c.input...) assert.NoError(t, err) @@ -238,7 +242,7 @@ func Test_UnindexedDirectoryResolver_MultipleFilesByPath(t *testing.T) { } func Test_UnindexedDirectoryResolver_FilesByGlobMultiple(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures") + resolver := NewFromUnindexedDirectory("./test-fixtures") refs, err := resolver.FilesByGlob("**/image-symlinks/file*") assert.NoError(t, err) @@ -246,14 +250,14 @@ func Test_UnindexedDirectoryResolver_FilesByGlobMultiple(t *testing.T) { } func Test_UnindexedDirectoryResolver_FilesByGlobRecursive(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/image-symlinks") + resolver := NewFromUnindexedDirectory("./test-fixtures/image-symlinks") refs, err := resolver.FilesByGlob("**/*.txt") assert.NoError(t, err) assert.Len(t, refs, 6) } func Test_UnindexedDirectoryResolver_FilesByGlobSingle(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures") + resolver := NewFromUnindexedDirectory("./test-fixtures") refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt") assert.NoError(t, err) @@ -279,7 +283,7 @@ func Test_UnindexedDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) for _, test := range tests { t.Run(test.name, func(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-simple") + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-simple") refs, err := resolver.FilesByPath(test.fixture) require.NoError(t, err) @@ -301,7 +305,7 @@ func Test_UnindexedDirectoryResolver_FilesByPath_ResolvesSymlinks(t *testing.T) func Test_UnindexedDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) { // let's make certain that "dev/place" is not ignored, since it is not "/dev/place" - resolver := NewUnindexedDirectoryResolver("test-fixtures/system_paths/target") + resolver := NewFromUnindexedDirectory("test-fixtures/system_paths/target") // all paths should be found (non filtering matches a path) locations, err := resolver.FilesByGlob("**/place") @@ -326,7 +330,7 @@ func Test_UnindexedDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing. } func Test_UnindexedDirectoryResover_IndexingNestedSymLinks(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-simple") + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-simple") // check that we can get the real path locations, err := resolver.FilesByPath("./readme") @@ -374,7 +378,7 @@ func Test_UnindexedDirectoryResover_IndexingNestedSymLinks(t *testing.T) { } func Test_UnindexedDirectoryResover_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-multiple-roots/root") + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-multiple-roots/root") // check that we can get the real path locations, err := resolver.FilesByPath("./readme") @@ -391,7 +395,7 @@ func Test_UnindexedDirectoryResover_IndexingNestedSymLinksOutsideOfRoot(t *testi } func Test_UnindexedDirectoryResover_RootViaSymlink(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinked-root/nested/link-root") + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinked-root/nested/link-root") locations, err := resolver.FilesByPath("./file1.txt") require.NoError(t, err) @@ -410,23 +414,23 @@ func Test_UnindexedDirectoryResolver_FileContentsByLocation(t *testing.T) { cwd, err := os.Getwd() require.NoError(t, err) - r := NewUnindexedDirectoryResolver(path.Join(cwd, "test-fixtures/image-simple")) + r := NewFromUnindexedDirectory(path.Join(cwd, "test-fixtures/image-simple")) require.NoError(t, err) tests := []struct { name string - location Location + location file.Location expects string err bool }{ { name: "use file reference for content requests", - location: NewLocation("file-1.txt"), + location: file.NewLocation("file-1.txt"), expects: "this file has contents", }, { name: "error on empty file reference", - location: NewLocationFromDirectory("doesn't matter", file.Reference{}), + location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}), err: true, }, } @@ -451,7 +455,7 @@ func Test_UnindexedDirectoryResolver_FileContentsByLocation(t *testing.T) { func Test_UnindexedDirectoryResover_SymlinkLoopWithGlobsShouldResolve(t *testing.T) { test := func(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-loop") + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-loop") locations, err := resolver.FilesByGlob("**/file.target") require.NoError(t, err) @@ -521,7 +525,7 @@ func Test_UnindexedDirectoryResolver_FilesByPath_baseRoot(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - resolver := NewUnindexedDirectoryResolverRooted(c.root, c.root) + resolver := NewFromRootedUnindexedDirectory(c.root, c.root) refs, err := resolver.FilesByPath(c.input) require.NoError(t, err) @@ -539,115 +543,115 @@ func Test_UnindexedDirectoryResolver_FilesByPath_baseRoot(t *testing.T) { func Test_UnindexedDirectoryResolver_resolvesLinks(t *testing.T) { tests := []struct { name string - runner func(FileResolver) []Location - expected []Location + runner func(file.Resolver) []file.Location + expected []file.Location }{ { name: "by glob to links", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files // for that reason we need to place **/ in front (which is not the same for other resolvers) actualLocations, err := resolver.FilesByGlob("**/*ink-*") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocation("file-1.txt", "link-1"), - NewVirtualLocation("file-2.txt", "link-2"), + expected: []file.Location{ + file.NewVirtualLocation("file-1.txt", "link-1"), + file.NewVirtualLocation("file-2.txt", "link-2"), // we already have this real file path via another link, so only one is returned - // NewVirtualLocation("file-2.txt", "link-indirect"), - NewVirtualLocation("file-3.txt", "link-within"), + // file.NewVirtualLocation("file-2.txt", "link-indirect"), + file.NewVirtualLocation("file-3.txt", "link-within"), }, }, { name: "by basename", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/file-2.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // this has two copies in the base image, which overwrites the same location - NewLocation("file-2.txt"), + file.NewLocation("file-2.txt"), }, }, { name: "by basename glob", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/file-?.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewLocation("file-1.txt"), - NewLocation("file-2.txt"), - NewLocation("file-3.txt"), - NewLocation("parent/file-4.txt"), + expected: []file.Location{ + file.NewLocation("file-1.txt"), + file.NewLocation("file-2.txt"), + file.NewLocation("file-3.txt"), + file.NewLocation("parent/file-4.txt"), }, }, { name: "by basename glob to links", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { actualLocations, err := resolver.FilesByGlob("**/link-*") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewVirtualLocationFromDirectory("file-1.txt", "link-1", file.Reference{RealPath: "file-1.txt"}), - NewVirtualLocationFromDirectory("file-2.txt", "link-2", file.Reference{RealPath: "file-2.txt"}), + expected: []file.Location{ + file.NewVirtualLocationFromDirectory("file-1.txt", "link-1", stereoscopeFile.Reference{RealPath: "file-1.txt"}), + file.NewVirtualLocationFromDirectory("file-2.txt", "link-2", stereoscopeFile.Reference{RealPath: "file-2.txt"}), // we already have this real file path via another link, so only one is returned - //NewVirtualLocationFromDirectory("file-2.txt", "link-indirect", file.Reference{RealPath: "file-2.txt"}), - NewVirtualLocationFromDirectory("file-3.txt", "link-within", file.Reference{RealPath: "file-3.txt"}), + //file.NewVirtualLocationFromDirectory("file-2.txt", "link-indirect", stereoscopeFile.Reference{RealPath: "file-2.txt"}), + file.NewVirtualLocationFromDirectory("file-3.txt", "link-within", stereoscopeFile.Reference{RealPath: "file-3.txt"}), }, }, { name: "by extension", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links are searched, but resolve to the real files actualLocations, err := resolver.FilesByGlob("**/*.txt") assert.NoError(t, err) return actualLocations }, - expected: []Location{ - NewLocation("file-1.txt"), - NewLocation("file-2.txt"), - NewLocation("file-3.txt"), - NewLocation("parent/file-4.txt"), + expected: []file.Location{ + file.NewLocation("file-1.txt"), + file.NewLocation("file-2.txt"), + file.NewLocation("file-3.txt"), + file.NewLocation("parent/file-4.txt"), }, }, { name: "by path to degree 1 link", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // links resolve to the final file actualLocations, err := resolver.FilesByPath("/link-2") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // we have multiple copies across layers - NewVirtualLocation("file-2.txt", "link-2"), + file.NewVirtualLocation("file-2.txt", "link-2"), }, }, { name: "by path to degree 2 link", - runner: func(resolver FileResolver) []Location { + runner: func(resolver file.Resolver) []file.Location { // multiple links resolves to the final file actualLocations, err := resolver.FilesByPath("/link-indirect") assert.NoError(t, err) return actualLocations }, - expected: []Location{ + expected: []file.Location{ // we have multiple copies across layers - NewVirtualLocation("file-2.txt", "link-indirect"), + file.NewVirtualLocation("file-2.txt", "link-indirect"), }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-from-image-symlinks-fixture") + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture") actual := test.runner(resolver) @@ -657,14 +661,14 @@ func Test_UnindexedDirectoryResolver_resolvesLinks(t *testing.T) { } func Test_UnindexedDirectoryResolver_DoNotAddVirtualPathsToTree(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-prune-indexing") + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-prune-indexing") allLocations := resolver.AllLocations() - var allRealPaths []file.Path + var allRealPaths []stereoscopeFile.Path for l := range allLocations { - allRealPaths = append(allRealPaths, file.Path(l.RealPath)) + allRealPaths = append(allRealPaths, stereoscopeFile.Path(l.RealPath)) } - pathSet := file.NewPathSet(allRealPaths...) + pathSet := stereoscopeFile.NewPathSet(allRealPaths...) assert.False(t, pathSet.Contains("before-path/file.txt"), @@ -678,9 +682,9 @@ func Test_UnindexedDirectoryResolver_DoNotAddVirtualPathsToTree(t *testing.T) { } func Test_UnindexedDirectoryResolver_FilesContents_errorOnDirRequest(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/system_paths") + resolver := NewFromUnindexedDirectory("./test-fixtures/system_paths") - dirLoc := NewLocation("arg/foo") + dirLoc := file.NewLocation("arg/foo") reader, err := resolver.FileContentsByLocation(dirLoc) require.Error(t, err) @@ -688,7 +692,7 @@ func Test_UnindexedDirectoryResolver_FilesContents_errorOnDirRequest(t *testing. } func Test_UnindexedDirectoryResolver_AllLocations(t *testing.T) { - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-from-image-symlinks-fixture") + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture") paths := strset.New() for loc := range resolver.AllLocations() { @@ -724,13 +728,13 @@ func Test_WritableUnindexedDirectoryResolver(t *testing.T) { p := "some/path/file" c := "some contents" - dr := NewUnindexedDirectoryResolver(tmpdir) + dr := NewFromUnindexedDirectory(tmpdir) locations, err := dr.FilesByPath(p) require.NoError(t, err) require.Len(t, locations, 0) - err = dr.Write(NewLocation(p), strings.NewReader(c)) + err = dr.Write(file.NewLocation(p), strings.NewReader(c)) require.NoError(t, err) locations, err = dr.FilesByPath(p) @@ -742,3 +746,37 @@ func Test_WritableUnindexedDirectoryResolver(t *testing.T) { bytes, err := io.ReadAll(reader) require.Equal(t, c, string(bytes)) } + +func testWithTimeout(t *testing.T, timeout time.Duration, test func(*testing.T)) { + done := make(chan bool) + go func() { + test(t) + done <- true + }() + + select { + case <-time.After(timeout): + t.Fatal("test timed out") + case <-done: + } +} + +func compareLocations(t *testing.T, expected, actual []file.Location) { + t.Helper() + ignoreUnexported := cmpopts.IgnoreFields(file.LocationData{}, "ref") + ignoreMetadata := cmpopts.IgnoreFields(file.LocationMetadata{}, "Annotations") + ignoreFS := cmpopts.IgnoreFields(file.Coordinates{}, "FileSystemID") + + sort.Sort(file.Locations(expected)) + sort.Sort(file.Locations(actual)) + + if d := cmp.Diff(expected, actual, + ignoreUnexported, + ignoreFS, + ignoreMetadata, + ); d != "" { + + t.Errorf("unexpected locations (-want +got):\n%s", d) + } + +} diff --git a/syft/linux/identify_release.go b/syft/linux/identify_release.go index 152b53324c6..cf2da477cc3 100644 --- a/syft/linux/identify_release.go +++ b/syft/linux/identify_release.go @@ -11,7 +11,7 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) // returns a distro or nil @@ -54,7 +54,7 @@ var identityFiles = []parseEntry{ } // IdentifyRelease parses distro-specific files to discover and raise linux distribution release details. -func IdentifyRelease(resolver source.FileResolver) *Release { +func IdentifyRelease(resolver file.Resolver) *Release { logger := log.Nested("operation", "identify-release") for _, entry := range identityFiles { locations, err := resolver.FilesByPath(entry.path) diff --git a/syft/pkg/binary_metadata.go b/syft/pkg/binary_metadata.go index de0a0a2d40b..a915acc5296 100644 --- a/syft/pkg/binary_metadata.go +++ b/syft/pkg/binary_metadata.go @@ -1,12 +1,12 @@ package pkg -import "github.com/anchore/syft/syft/source" +import "github.com/anchore/syft/syft/file" type BinaryMetadata struct { Matches []ClassifierMatch `mapstructure:"Matches" json:"matches"` } type ClassifierMatch struct { - Classifier string `mapstructure:"Classifier" json:"classifier"` - Location source.Location `mapstructure:"Location" json:"location"` + Classifier string `mapstructure:"Classifier" json:"classifier"` + Location file.Location `mapstructure:"Location" json:"location"` } diff --git a/syft/pkg/catalog_test.go b/syft/pkg/catalog_test.go index 5271ac262cb..cfde34d5d6e 100644 --- a/syft/pkg/catalog_test.go +++ b/syft/pkg/catalog_test.go @@ -9,7 +9,7 @@ import ( "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) type expectedIndexes struct { @@ -75,8 +75,8 @@ func TestCatalogDeleteRemovesPackages(t *testing.T) { Name: "debian", Version: "1", Type: DebPkg, - Locations: source.NewLocationSet( - source.NewVirtualLocation("/c/path", "/another/path1"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/c/path", "/another/path1"), ), }, { @@ -84,8 +84,8 @@ func TestCatalogDeleteRemovesPackages(t *testing.T) { Name: "debian", Version: "2", Type: DebPkg, - Locations: source.NewLocationSet( - source.NewVirtualLocation("/d/path", "/another/path2"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/d/path", "/another/path2"), ), }, }, @@ -110,8 +110,8 @@ func TestCatalogDeleteRemovesPackages(t *testing.T) { Name: "debian", Version: "1", Type: DebPkg, - Locations: source.NewLocationSet( - source.NewVirtualLocation("/c/path", "/another/path1"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/c/path", "/another/path1"), ), }, { @@ -119,8 +119,8 @@ func TestCatalogDeleteRemovesPackages(t *testing.T) { Name: "debian", Version: "2", Type: DebPkg, - Locations: source.NewLocationSet( - source.NewVirtualLocation("/d/path", "/another/path2"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/d/path", "/another/path2"), ), }, { @@ -128,8 +128,8 @@ func TestCatalogDeleteRemovesPackages(t *testing.T) { Name: "debian", Version: "3", Type: DebPkg, - Locations: source.NewLocationSet( - source.NewVirtualLocation("/e/path", "/another/path3"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/e/path", "/another/path3"), ), }, }, @@ -155,8 +155,8 @@ func TestCatalogDeleteRemovesPackages(t *testing.T) { Name: "debian", Version: "1", Type: DebPkg, - Locations: source.NewLocationSet( - source.NewVirtualLocation("/c/path", "/another/path1"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/c/path", "/another/path1"), ), }, { @@ -164,8 +164,8 @@ func TestCatalogDeleteRemovesPackages(t *testing.T) { Name: "debian", Version: "2", Type: DebPkg, - Locations: source.NewLocationSet( - source.NewVirtualLocation("/d/path", "/another/path2"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/d/path", "/another/path2"), ), }, }, @@ -206,16 +206,16 @@ func TestCatalogAddPopulatesIndex(t *testing.T) { var pkgs = []Package{ { - Locations: source.NewLocationSet( - source.NewVirtualLocation("/a/path", "/another/path"), - source.NewVirtualLocation("/b/path", "/bee/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/a/path", "/another/path"), + file.NewVirtualLocation("/b/path", "/bee/path"), ), Type: RpmPkg, }, { - Locations: source.NewLocationSet( - source.NewVirtualLocation("/c/path", "/another/path"), - source.NewVirtualLocation("/d/path", "/another/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/c/path", "/another/path"), + file.NewVirtualLocation("/d/path", "/another/path"), ), Type: NpmPkg, }, @@ -291,25 +291,25 @@ func assertIndexes(t *testing.T, c *Collection, expectedIndexes expectedIndexes) func TestCatalog_PathIndexDeduplicatesRealVsVirtualPaths(t *testing.T) { p1 := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/b/path", "/another/path"), - source.NewVirtualLocation("/b/path", "/b/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/b/path", "/another/path"), + file.NewVirtualLocation("/b/path", "/b/path"), ), Type: RpmPkg, Name: "Package-1", } p2 := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/b/path", "/b/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/b/path", "/b/path"), ), Type: RpmPkg, Name: "Package-2", } p2Dup := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/b/path", "/another/path"), - source.NewVirtualLocation("/b/path", "/c/path/b/dup"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/b/path", "/another/path"), + file.NewVirtualLocation("/b/path", "/c/path/b/dup"), ), Type: RpmPkg, Name: "Package-2", @@ -361,7 +361,7 @@ func TestCatalog_MergeRecords(t *testing.T) { var tests = []struct { name string pkgs []Package - expectedLocations []source.Location + expectedLocations []file.Location expectedCPECount int }{ { @@ -369,9 +369,9 @@ func TestCatalog_MergeRecords(t *testing.T) { pkgs: []Package{ { CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:package:1:1:*:*:*:*:*:*:*")}, - Locations: source.NewLocationSet( - source.NewVirtualLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewVirtualLocationFromCoordinates( + file.Coordinates{ RealPath: "/b/path", FileSystemID: "a", }, @@ -382,9 +382,9 @@ func TestCatalog_MergeRecords(t *testing.T) { }, { CPEs: []cpe.CPE{cpe.Must("cpe:2.3:b:package:1:1:*:*:*:*:*:*:*")}, - Locations: source.NewLocationSet( - source.NewVirtualLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewVirtualLocationFromCoordinates( + file.Coordinates{ RealPath: "/b/path", FileSystemID: "b", }, @@ -394,16 +394,16 @@ func TestCatalog_MergeRecords(t *testing.T) { Type: RpmPkg, }, }, - expectedLocations: []source.Location{ - source.NewVirtualLocationFromCoordinates( - source.Coordinates{ + expectedLocations: []file.Location{ + file.NewVirtualLocationFromCoordinates( + file.Coordinates{ RealPath: "/b/path", FileSystemID: "a", }, "/another/path", ), - source.NewVirtualLocationFromCoordinates( - source.Coordinates{ + file.NewVirtualLocationFromCoordinates( + file.Coordinates{ RealPath: "/b/path", FileSystemID: "b", }, diff --git a/syft/pkg/cataloger.go b/syft/pkg/cataloger.go index 28cc57c35dd..634a17e1dc1 100644 --- a/syft/pkg/cataloger.go +++ b/syft/pkg/cataloger.go @@ -2,7 +2,7 @@ package pkg import ( "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) // Cataloger describes behavior for an object to participate in parsing container image or file system @@ -12,5 +12,5 @@ type Cataloger interface { // Name returns a string that uniquely describes a cataloger Name() string // Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source. - Catalog(resolver source.FileResolver) ([]Package, []artifact.Relationship, error) + Catalog(resolver file.Resolver) ([]Package, []artifact.Relationship, error) } diff --git a/syft/pkg/cataloger/alpm/cataloger_test.go b/syft/pkg/cataloger/alpm/cataloger_test.go index 1dedded2eff..0b8a9156ea5 100644 --- a/syft/pkg/cataloger/alpm/cataloger_test.go +++ b/syft/pkg/cataloger/alpm/cataloger_test.go @@ -9,11 +9,10 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestAlpmCataloger(t *testing.T) { - dbLocation := source.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc") + dbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc") expectedPkgs := []pkg.Package{ { Name: "gmp", @@ -24,7 +23,7 @@ func TestAlpmCataloger(t *testing.T) { pkg.NewLicenseFromLocations("LGPL3", dbLocation), pkg.NewLicenseFromLocations("GPL", dbLocation), ), - Locations: source.NewLocationSet(dbLocation), + Locations: file.NewLocationSet(dbLocation), CPEs: nil, PURL: "", MetadataType: "AlpmMetadata", diff --git a/syft/pkg/cataloger/alpm/package.go b/syft/pkg/cataloger/alpm/package.go index 2c85db47c62..4ce9bd6b6f2 100644 --- a/syft/pkg/cataloger/alpm/package.go +++ b/syft/pkg/cataloger/alpm/package.go @@ -4,18 +4,18 @@ import ( "strings" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(m *parsedData, release *linux.Release, dbLocation source.Location) pkg.Package { +func newPackage(m *parsedData, release *linux.Release, dbLocation file.Location) pkg.Package { licenseCandidates := strings.Split(m.Licenses, "\n") p := pkg.Package{ Name: m.Package, Version: m.Version, - Locations: source.NewLocationSet(dbLocation), + Locations: file.NewLocationSet(dbLocation), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...), Type: pkg.AlpmPkg, PURL: packageURL(m, release), diff --git a/syft/pkg/cataloger/alpm/parse_alpm_db.go b/syft/pkg/cataloger/alpm/parse_alpm_db.go index 987a52c076a..86c6dd3d2fd 100644 --- a/syft/pkg/cataloger/alpm/parse_alpm_db.go +++ b/syft/pkg/cataloger/alpm/parse_alpm_db.go @@ -17,7 +17,6 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseAlpmDB @@ -36,7 +35,7 @@ type parsedData struct { pkg.AlpmMetadata `mapstructure:",squash"` } -func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseAlpmDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { data, err := parseAlpmDBEntry(reader) if err != nil { return nil, nil, err @@ -117,7 +116,7 @@ func newScanner(reader io.Reader) *bufio.Scanner { return scanner } -func getFileReader(path string, resolver source.FileResolver) (io.Reader, error) { +func getFileReader(path string, resolver file.Resolver) (io.Reader, error) { locs, err := resolver.FilesByPath(path) if err != nil { return nil, err diff --git a/syft/pkg/cataloger/apkdb/package.go b/syft/pkg/cataloger/apkdb/package.go index 4bc59ba170d..8cb75bbc579 100644 --- a/syft/pkg/cataloger/apkdb/package.go +++ b/syft/pkg/cataloger/apkdb/package.go @@ -4,13 +4,13 @@ import ( "strings" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(d parsedData, release *linux.Release, dbLocation source.Location) pkg.Package { +func newPackage(d parsedData, release *linux.Release, dbLocation file.Location) pkg.Package { // check if license is a valid spdx expression before splitting licenseStrings := []string{d.License} _, err := license.ParseExpression(d.License) @@ -22,7 +22,7 @@ func newPackage(d parsedData, release *linux.Release, dbLocation source.Location p := pkg.Package{ Name: d.Package, Version: d.Version, - Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...), PURL: packageURL(d.ApkMetadata, release), Type: pkg.ApkPkg, diff --git a/syft/pkg/cataloger/apkdb/parse_apk_db.go b/syft/pkg/cataloger/apkdb/parse_apk_db.go index 748ed7d5852..fd4184f87dd 100644 --- a/syft/pkg/cataloger/apkdb/parse_apk_db.go +++ b/syft/pkg/cataloger/apkdb/parse_apk_db.go @@ -16,7 +16,6 @@ import ( "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // integrity check @@ -35,7 +34,7 @@ type parsedData struct { // information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec. // //nolint:funlen,gocognit -func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseApkDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { scanner := bufio.NewScanner(reader) var apks []parsedData @@ -134,7 +133,7 @@ func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader s return pkgs, discoverPackageDependencies(pkgs), nil } -func findReleases(resolver source.FileResolver, dbPath string) []linux.Release { +func findReleases(resolver file.Resolver, dbPath string) []linux.Release { if resolver == nil { return nil } @@ -157,13 +156,13 @@ func findReleases(resolver source.FileResolver, dbPath string) []linux.Release { return nil } - return parseReleasesFromAPKRepository(source.LocationReadCloser{ + return parseReleasesFromAPKRepository(file.LocationReadCloser{ Location: location, ReadCloser: reposReader, }) } -func parseReleasesFromAPKRepository(reader source.LocationReadCloser) []linux.Release { +func parseReleasesFromAPKRepository(reader file.LocationReadCloser) []linux.Release { var releases []linux.Release reposB, err := io.ReadAll(reader) diff --git a/syft/pkg/cataloger/apkdb/parse_apk_db_test.go b/syft/pkg/cataloger/apkdb/parse_apk_db_test.go index ac344631514..865a02c97ee 100644 --- a/syft/pkg/cataloger/apkdb/parse_apk_db_test.go +++ b/syft/pkg/cataloger/apkdb/parse_apk_db_test.go @@ -18,7 +18,6 @@ import ( "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestExtraFileAttributes(t *testing.T) { @@ -680,8 +679,8 @@ func TestSinglePackageDetails(t *testing.T) { for _, test := range tests { t.Run(test.fixture, func(t *testing.T) { - fixtureLocation := source.NewLocation(test.fixture) - test.expected.Locations = source.NewLocationSet(fixtureLocation) + fixtureLocation := file.NewLocation(test.fixture) + test.expected.Locations = file.NewLocationSet(fixtureLocation) licenses := test.expected.Licenses.ToSlice() for i := range licenses { licenses[i].Locations.Add(fixtureLocation) @@ -694,8 +693,8 @@ func TestSinglePackageDetails(t *testing.T) { func TestMultiplePackages(t *testing.T) { fixture := "test-fixtures/multiple" - location := source.NewLocation(fixture) - fixtureLocationSet := source.NewLocationSet(location) + location := file.NewLocation(fixture) + fixtureLocationSet := file.NewLocationSet(location) expectedPkgs := []pkg.Package{ { Name: "libc-utils", @@ -1024,7 +1023,7 @@ func Test_discoverPackageDependencies(t *testing.T) { t.Run(test.name, func(t *testing.T) { pkgs, wantRelationships := test.genFn() gotRelationships := discoverPackageDependencies(pkgs) - d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, source.LocationSet{}, pkg.LicenseSet{})) + d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{})) if d != "" { t.Fail() t.Log(d) @@ -1061,8 +1060,8 @@ func TestPackageDbDependenciesByParse(t *testing.T) { require.NoError(t, err) t.Cleanup(func() { require.NoError(t, f.Close()) }) - pkgs, relationships, err := parseApkDB(nil, nil, source.LocationReadCloser{ - Location: source.NewLocation(test.fixture), + pkgs, relationships, err := parseApkDB(nil, nil, file.LocationReadCloser{ + Location: file.NewLocation(test.fixture), ReadCloser: f, }) require.NoError(t, err) @@ -1172,12 +1171,12 @@ func toPackageNames(pkgs []pkg.Package) []string { return names } -func newLocationReadCloser(t *testing.T, path string) source.LocationReadCloser { +func newLocationReadCloser(t *testing.T, path string) file.LocationReadCloser { f, err := os.Open(path) require.NoError(t, err) t.Cleanup(func() { f.Close() }) - return source.NewLocationReadCloser(source.NewLocation(path), f) + return file.NewLocationReadCloser(file.NewLocation(path), f) } func Test_stripVersionSpecifier(t *testing.T) { @@ -1256,8 +1255,8 @@ https://foo.them.org/alpine/v3.14/community`, for _, tt := range tests { t.Run(tt.desc, func(t *testing.T) { reposReader := io.NopCloser(strings.NewReader(tt.repos)) - got := parseReleasesFromAPKRepository(source.LocationReadCloser{ - Location: source.NewLocation("test"), + got := parseReleasesFromAPKRepository(file.LocationReadCloser{ + Location: file.NewLocation("test"), ReadCloser: reposReader, }) assert.Equal(t, tt.want, got) diff --git a/syft/pkg/cataloger/binary/cataloger.go b/syft/pkg/cataloger/binary/cataloger.go index cf58af15ca7..0cf04b729ed 100644 --- a/syft/pkg/cataloger/binary/cataloger.go +++ b/syft/pkg/cataloger/binary/cataloger.go @@ -3,8 +3,8 @@ package binary import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) const catalogerName = "binary-cataloger" @@ -27,7 +27,7 @@ func (c Cataloger) Name() string { // Catalog is given an object to resolve file references and content, this function returns any discovered Packages // after analyzing the catalog source. -func (c Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (c Cataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { var packages []pkg.Package var relationships []artifact.Relationship @@ -68,7 +68,7 @@ func mergePackages(target *pkg.Package, extra *pkg.Package) { target.Metadata = meta } -func catalog(resolver source.FileResolver, cls classifier) (packages []pkg.Package, err error) { +func catalog(resolver file.Resolver, cls classifier) (packages []pkg.Package, err error) { locations, err := resolver.FilesByGlob(cls.FileGlob) if err != nil { return nil, err diff --git a/syft/pkg/cataloger/binary/cataloger_test.go b/syft/pkg/cataloger/binary/cataloger_test.go index 51b0751ef81..d6622423bb5 100644 --- a/syft/pkg/cataloger/binary/cataloger_test.go +++ b/syft/pkg/cataloger/binary/cataloger_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" ) @@ -728,12 +729,12 @@ func TestClassifierCataloger_DefaultClassifiers_NegativeCases(t *testing.T) { assert.Equal(t, 0, len(actualResults)) } -func locations(locations ...string) source.LocationSet { - var locs []source.Location +func locations(locations ...string) file.LocationSet { + var locs []file.Location for _, s := range locations { - locs = append(locs, source.NewLocation(s)) + locs = append(locs, file.NewLocation(s)) } - return source.NewLocationSet(locs...) + return file.NewLocationSet(locs...) } // metadata paths are: realPath, virtualPath @@ -757,8 +758,8 @@ func match(classifier string, paths ...string) pkg.ClassifierMatch { } return pkg.ClassifierMatch{ Classifier: classifier, - Location: source.NewVirtualLocationFromCoordinates( - source.Coordinates{ + Location: file.NewVirtualLocationFromCoordinates( + file.Coordinates{ RealPath: realPath, }, virtualPath, @@ -817,10 +818,10 @@ func assertPackagesAreEqual(t *testing.T, expected pkg.Package, p pkg.Package) { if len(failMessages) > 0 { assert.Failf(t, strings.Join(failMessages, "; "), "diff: %s", cmp.Diff(expected, p, - cmp.Transformer("Locations", func(l source.LocationSet) []source.Location { + cmp.Transformer("Locations", func(l file.LocationSet) []file.Location { return l.ToSlice() }), - cmpopts.IgnoreUnexported(pkg.Package{}, source.Location{}), + cmpopts.IgnoreUnexported(pkg.Package{}, file.Location{}), cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "MetadataType", "Type"), )) } @@ -830,22 +831,22 @@ type panicyResolver struct { searchCalled bool } -func (p *panicyResolver) FilesByExtension(_ ...string) ([]source.Location, error) { +func (p *panicyResolver) FilesByExtension(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } -func (p *panicyResolver) FilesByBasename(_ ...string) ([]source.Location, error) { +func (p *panicyResolver) FilesByBasename(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } -func (p *panicyResolver) FilesByBasenameGlob(_ ...string) ([]source.Location, error) { +func (p *panicyResolver) FilesByBasenameGlob(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } -func (p *panicyResolver) FileContentsByLocation(_ source.Location) (io.ReadCloser, error) { +func (p *panicyResolver) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) { p.searchCalled = true return nil, errors.New("not implemented") } @@ -854,34 +855,34 @@ func (p *panicyResolver) HasPath(_ string) bool { return true } -func (p *panicyResolver) FilesByPath(_ ...string) ([]source.Location, error) { +func (p *panicyResolver) FilesByPath(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } -func (p *panicyResolver) FilesByGlob(_ ...string) ([]source.Location, error) { +func (p *panicyResolver) FilesByGlob(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } -func (p *panicyResolver) FilesByMIMEType(_ ...string) ([]source.Location, error) { +func (p *panicyResolver) FilesByMIMEType(_ ...string) ([]file.Location, error) { p.searchCalled = true return nil, errors.New("not implemented") } -func (p *panicyResolver) RelativeFileByPath(_ source.Location, _ string) *source.Location { +func (p *panicyResolver) RelativeFileByPath(_ file.Location, _ string) *file.Location { return nil } -func (p *panicyResolver) AllLocations() <-chan source.Location { +func (p *panicyResolver) AllLocations() <-chan file.Location { return nil } -func (p *panicyResolver) FileMetadataByLocation(_ source.Location) (source.FileMetadata, error) { - return source.FileMetadata{}, errors.New("not implemented") +func (p *panicyResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, error) { + return file.Metadata{}, errors.New("not implemented") } -var _ source.FileResolver = (*panicyResolver)(nil) +var _ file.Resolver = (*panicyResolver)(nil) func Test_Cataloger_ResilientToErrors(t *testing.T) { c := NewCataloger() diff --git a/syft/pkg/cataloger/binary/classifier.go b/syft/pkg/cataloger/binary/classifier.go index 6ab18985c52..c98399f4ce9 100644 --- a/syft/pkg/cataloger/binary/classifier.go +++ b/syft/pkg/cataloger/binary/classifier.go @@ -15,9 +15,9 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" - "github.com/anchore/syft/syft/source" ) var emptyPURL = packageurl.PackageURL{} @@ -53,10 +53,10 @@ type classifier struct { } // evidenceMatcher is a function called to catalog Packages that match some sort of evidence -type evidenceMatcher func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) +type evidenceMatcher func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) func evidenceMatchers(matchers ...evidenceMatcher) evidenceMatcher { - return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) { + return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) { for _, matcher := range matchers { match, err := matcher(resolver, classifier, location) if err != nil { @@ -72,7 +72,7 @@ func evidenceMatchers(matchers ...evidenceMatcher) evidenceMatcher { func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) evidenceMatcher { pat := regexp.MustCompile(fileNamePattern) - return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) { + return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) { if !pat.MatchString(location.RealPath) { return nil, nil } @@ -118,7 +118,7 @@ func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate stri func fileContentsVersionMatcher(pattern string) evidenceMatcher { pat := regexp.MustCompile(pattern) - return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) { + return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) { contents, err := getContents(resolver, location) if err != nil { return nil, fmt.Errorf("unable to get read contents for file: %w", err) @@ -138,7 +138,7 @@ func fileContentsVersionMatcher(pattern string) evidenceMatcher { //nolint:gocognit func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evidenceMatcher) evidenceMatcher { pat := regexp.MustCompile(sharedLibraryPattern) - return func(resolver source.FileResolver, classifier classifier, location source.Location) (packages []pkg.Package, _ error) { + return func(resolver file.Resolver, classifier classifier, location file.Location) (packages []pkg.Package, _ error) { libs, err := sharedLibraries(resolver, location) if err != nil { return nil, err @@ -159,7 +159,7 @@ func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evide } for _, p := range pkgs { // set the source binary as the first location - locationSet := source.NewLocationSet(location) + locationSet := file.NewLocationSet(location) locationSet.Add(p.Locations.ToSlice()...) p.Locations = locationSet meta, _ := p.Metadata.(pkg.BinaryMetadata) @@ -187,7 +187,7 @@ func mustPURL(purl string) packageurl.PackageURL { return p } -func getContents(resolver source.FileResolver, location source.Location) ([]byte, error) { +func getContents(resolver file.Resolver, location file.Location) ([]byte, error) { reader, err := resolver.FileContentsByLocation(location) if err != nil { return nil, err @@ -216,7 +216,7 @@ func singleCPE(cpeString string) []cpe.CPE { // sharedLibraries returns a list of all shared libraries found within a binary, currently // supporting: elf, macho, and windows pe -func sharedLibraries(resolver source.FileResolver, location source.Location) ([]string, error) { +func sharedLibraries(resolver file.Resolver, location file.Location) ([]string, error) { contents, err := getContents(resolver, location) if err != nil { return nil, err diff --git a/syft/pkg/cataloger/binary/classifier_test.go b/syft/pkg/cataloger/binary/classifier_test.go index 82260a0e633..fbf88c3b1a8 100644 --- a/syft/pkg/cataloger/binary/classifier_test.go +++ b/syft/pkg/cataloger/binary/classifier_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/cpe" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) func Test_ClassifierCPEs(t *testing.T) { @@ -63,12 +63,12 @@ func Test_ClassifierCPEs(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - resolver := source.NewMockResolverForPaths(test.fixture) - locations, err := resolver.FilesByPath(test.fixture) + resolver := file.NewMockResolverForPaths(test.fixture) + ls, err := resolver.FilesByPath(test.fixture) require.NoError(t, err) - require.Len(t, locations, 1) + require.Len(t, ls, 1) - pkgs, err := test.classifier.EvidenceMatcher(resolver, test.classifier, locations[0]) + pkgs, err := test.classifier.EvidenceMatcher(resolver, test.classifier, ls[0]) require.NoError(t, err) require.Len(t, pkgs, 1) diff --git a/syft/pkg/cataloger/binary/package.go b/syft/pkg/cataloger/binary/package.go index 7c1fb7abc60..a677b02a623 100644 --- a/syft/pkg/cataloger/binary/package.go +++ b/syft/pkg/cataloger/binary/package.go @@ -4,11 +4,11 @@ import ( "reflect" "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(classifier classifier, location source.Location, matchMetadata map[string]string) *pkg.Package { +func newPackage(classifier classifier, location file.Location, matchMetadata map[string]string) *pkg.Package { version, ok := matchMetadata["version"] if !ok { return nil @@ -26,7 +26,7 @@ func newPackage(classifier classifier, location source.Location, matchMetadata m p := pkg.Package{ Name: classifier.Package, Version: version, - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), Type: pkg.BinaryPkg, diff --git a/syft/pkg/cataloger/catalog.go b/syft/pkg/cataloger/catalog.go index 793efab369b..f982223e1a4 100644 --- a/syft/pkg/cataloger/catalog.go +++ b/syft/pkg/cataloger/catalog.go @@ -14,10 +14,10 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/event" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/common/cpe" - "github.com/anchore/syft/syft/source" ) // Monitor provides progress-related data for observing the progress of a Catalog() call (published on the event bus). @@ -50,7 +50,7 @@ func newMonitor() (*progress.Manual, *progress.Manual) { return &filesProcessed, &packagesDiscovered } -func runCataloger(cataloger pkg.Cataloger, resolver source.FileResolver) (catalogerResult *catalogResult, err error) { +func runCataloger(cataloger pkg.Cataloger, resolver file.Resolver) (catalogerResult *catalogResult, err error) { // handle individual cataloger panics defer func() { if e := recover(); e != nil { @@ -105,7 +105,7 @@ func runCataloger(cataloger pkg.Cataloger, resolver source.FileResolver) (catalo // request. // //nolint:funlen -func Catalog(resolver source.FileResolver, _ *linux.Release, parallelism int, catalogers ...pkg.Cataloger) (*pkg.Collection, []artifact.Relationship, error) { +func Catalog(resolver file.Resolver, _ *linux.Release, parallelism int, catalogers ...pkg.Cataloger) (*pkg.Collection, []artifact.Relationship, error) { catalog := pkg.NewCollection() var allRelationships []artifact.Relationship @@ -182,13 +182,13 @@ func Catalog(resolver source.FileResolver, _ *linux.Release, parallelism int, ca return catalog, allRelationships, errs } -func packageFileOwnershipRelationships(p pkg.Package, resolver source.FilePathResolver) ([]artifact.Relationship, error) { +func packageFileOwnershipRelationships(p pkg.Package, resolver file.PathResolver) ([]artifact.Relationship, error) { fileOwner, ok := p.Metadata.(pkg.FileOwner) if !ok { return nil, nil } - locations := map[artifact.ID]source.Location{} + locations := map[artifact.ID]file.Location{} for _, path := range fileOwner.OwnedFiles() { pathRefs, err := resolver.FilesByPath(path) diff --git a/syft/pkg/cataloger/catalog_test.go b/syft/pkg/cataloger/catalog_test.go index 9de59f36d74..950ec133aba 100644 --- a/syft/pkg/cataloger/catalog_test.go +++ b/syft/pkg/cataloger/catalog_test.go @@ -6,14 +6,14 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) func Test_CatalogPanicHandling(t *testing.T) { catalog, relationships, err := Catalog( - source.NewMockResolverForPaths(), + file.NewMockResolverForPaths(), &linux.Release{}, 1, panickingCataloger{}, @@ -32,7 +32,7 @@ func (p panickingCataloger) Name() string { return "panicking-cataloger" } -func (p panickingCataloger) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (p panickingCataloger) Catalog(_ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { panic("something bad happened") } @@ -44,7 +44,7 @@ func (p returningCataloger) Name() string { return "returning-cataloger" } -func (p returningCataloger) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (p returningCataloger) Catalog(_ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { pkg1 := pkg.Package{ Name: "package-1", Version: "1.0", diff --git a/syft/pkg/cataloger/cataloger_test.go b/syft/pkg/cataloger/cataloger_test.go index 35cde7797d0..4f4b6ce83fd 100644 --- a/syft/pkg/cataloger/cataloger_test.go +++ b/syft/pkg/cataloger/cataloger_test.go @@ -6,8 +6,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) var _ pkg.Cataloger = (*dummy)(nil) @@ -20,7 +20,7 @@ func (d dummy) Name() string { return d.name } -func (d dummy) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (d dummy) Catalog(_ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { panic("not implemented") } diff --git a/syft/pkg/cataloger/cpp/package.go b/syft/pkg/cataloger/cpp/package.go index ba54add772f..dbbdd0b90c8 100644 --- a/syft/pkg/cataloger/cpp/package.go +++ b/syft/pkg/cataloger/cpp/package.go @@ -4,11 +4,11 @@ import ( "strings" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newConanfilePackage(m pkg.ConanMetadata, locations ...source.Location) *pkg.Package { +func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package { fields := strings.Split(strings.TrimSpace(m.Ref), "/") if len(fields) < 2 { return nil @@ -23,7 +23,7 @@ func newConanfilePackage(m pkg.ConanMetadata, locations ...source.Location) *pkg p := pkg.Package{ Name: pkgName, Version: pkgVersion, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(pkgName, pkgVersion), Language: pkg.CPP, Type: pkg.ConanPkg, @@ -36,7 +36,7 @@ func newConanfilePackage(m pkg.ConanMetadata, locations ...source.Location) *pkg return &p } -func newConanlockPackage(m pkg.ConanLockMetadata, locations ...source.Location) *pkg.Package { +func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *pkg.Package { fields := strings.Split(strings.Split(m.Ref, "@")[0], "/") if len(fields) < 2 { return nil @@ -51,7 +51,7 @@ func newConanlockPackage(m pkg.ConanLockMetadata, locations ...source.Location) p := pkg.Package{ Name: pkgName, Version: pkgVersion, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(pkgName, pkgVersion), Language: pkg.CPP, Type: pkg.ConanPkg, diff --git a/syft/pkg/cataloger/cpp/parse_conanfile.go b/syft/pkg/cataloger/cpp/parse_conanfile.go index fdaf08026be..f9ae172f37b 100644 --- a/syft/pkg/cataloger/cpp/parse_conanfile.go +++ b/syft/pkg/cataloger/cpp/parse_conanfile.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseConanfile @@ -20,7 +20,7 @@ type Conanfile struct { } // parseConanfile is a parser function for conanfile.txt contents, returning all packages discovered. -func parseConanfile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseConanfile(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { r := bufio.NewReader(reader) inRequirements := false var pkgs []pkg.Package diff --git a/syft/pkg/cataloger/cpp/parse_conanfile_test.go b/syft/pkg/cataloger/cpp/parse_conanfile_test.go index edb9ff30a41..bca49223a5d 100644 --- a/syft/pkg/cataloger/cpp/parse_conanfile_test.go +++ b/syft/pkg/cataloger/cpp/parse_conanfile_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseConanfile(t *testing.T) { fixture := "test-fixtures/conanfile.txt" - fixtureLocationSet := source.NewLocationSet(source.NewLocation(fixture)) + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture)) expected := []pkg.Package{ { Name: "catch2", diff --git a/syft/pkg/cataloger/cpp/parse_conanlock.go b/syft/pkg/cataloger/cpp/parse_conanlock.go index b3bcf31d53e..511000ea16a 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock.go @@ -5,9 +5,9 @@ import ( "strings" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseConanlock @@ -30,7 +30,7 @@ type conanLock struct { } // parseConanlock is a parser function for conan.lock contents, returning all packages discovered. -func parseConanlock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseConanlock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package var cl conanLock if err := json.NewDecoder(reader).Decode(&cl); err != nil { diff --git a/syft/pkg/cataloger/cpp/parse_conanlock_test.go b/syft/pkg/cataloger/cpp/parse_conanlock_test.go index c5a57fa795e..b699081dee5 100644 --- a/syft/pkg/cataloger/cpp/parse_conanlock_test.go +++ b/syft/pkg/cataloger/cpp/parse_conanlock_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseConanlock(t *testing.T) { @@ -16,7 +16,7 @@ func TestParseConanlock(t *testing.T) { Name: "zlib", Version: "1.2.12", PURL: "pkg:conan/zlib@1.2.12", - Locations: source.NewLocationSet(source.NewLocation(fixture)), + Locations: file.NewLocationSet(file.NewLocation(fixture)), Language: pkg.CPP, Type: pkg.ConanPkg, MetadataType: pkg.ConanLockMetadataType, diff --git a/syft/pkg/cataloger/dart/package.go b/syft/pkg/cataloger/dart/package.go index 1f78045536a..f01d80f602c 100644 --- a/syft/pkg/cataloger/dart/package.go +++ b/syft/pkg/cataloger/dart/package.go @@ -2,11 +2,11 @@ package dart import ( "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...source.Location) pkg.Package { +func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...file.Location) pkg.Package { metadata := pkg.DartPubMetadata{ Name: name, Version: raw.Version, @@ -17,7 +17,7 @@ func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...sou p := pkg.Package{ Name: name, Version: raw.Version, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(metadata), Language: pkg.Dart, Type: pkg.DartPubPkg, diff --git a/syft/pkg/cataloger/dart/parse_pubspec_lock.go b/syft/pkg/cataloger/dart/parse_pubspec_lock.go index bde8caf663d..3493f8d1df8 100644 --- a/syft/pkg/cataloger/dart/parse_pubspec_lock.go +++ b/syft/pkg/cataloger/dart/parse_pubspec_lock.go @@ -9,9 +9,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parsePubspecLock @@ -38,7 +38,7 @@ type pubspecLockDescription struct { ResolvedRef string `yaml:"resolved-ref" mapstructure:"resolved-ref"` } -func parsePubspecLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parsePubspecLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package dec := yaml.NewDecoder(reader) diff --git a/syft/pkg/cataloger/dart/parse_pubspec_lock_test.go b/syft/pkg/cataloger/dart/parse_pubspec_lock_test.go index bbef7e0492c..a5a972e80eb 100644 --- a/syft/pkg/cataloger/dart/parse_pubspec_lock_test.go +++ b/syft/pkg/cataloger/dart/parse_pubspec_lock_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParsePubspecLock(t *testing.T) { fixture := "test-fixtures/pubspec.lock" - fixtureLocationSet := source.NewLocationSet(source.NewLocation(fixture)) + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture)) expected := []pkg.Package{ { Name: "ale", diff --git a/syft/pkg/cataloger/deb/cataloger_test.go b/syft/pkg/cataloger/deb/cataloger_test.go index ab3415d5402..64a3c5f8768 100644 --- a/syft/pkg/cataloger/deb/cataloger_test.go +++ b/syft/pkg/cataloger/deb/cataloger_test.go @@ -6,11 +6,10 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestDpkgCataloger(t *testing.T) { - licenseLocation := source.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright") + licenseLocation := file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright") expected := []pkg.Package{ { Name: "libpam-runtime", @@ -21,11 +20,11 @@ func TestDpkgCataloger(t *testing.T) { pkg.NewLicenseFromLocations("GPL-2", licenseLocation), pkg.NewLicenseFromLocations("LGPL-2.1", licenseLocation), ), - Locations: source.NewLocationSet( - source.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"), - source.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"), - source.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.conffiles", "/var/lib/dpkg/info/libpam-runtime.conffiles"), - source.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"), + file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"), + file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.conffiles", "/var/lib/dpkg/info/libpam-runtime.conffiles"), + file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright"), ), Type: pkg.DebPkg, MetadataType: pkg.DpkgMetadataType, diff --git a/syft/pkg/cataloger/deb/package.go b/syft/pkg/cataloger/deb/package.go index 1685051c959..b37d8f46b7c 100644 --- a/syft/pkg/cataloger/deb/package.go +++ b/syft/pkg/cataloger/deb/package.go @@ -10,9 +10,9 @@ import ( "github.com/anchore/packageurl-go" "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) const ( @@ -21,14 +21,14 @@ const ( docsPath = "/usr/share/doc" ) -func newDpkgPackage(d pkg.DpkgMetadata, dbLocation source.Location, resolver source.FileResolver, release *linux.Release) pkg.Package { +func newDpkgPackage(d pkg.DpkgMetadata, dbLocation file.Location, resolver file.Resolver, release *linux.Release) pkg.Package { // TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function licenses := make([]pkg.License, 0) p := pkg.Package{ Name: d.Package, Version: d.Version, Licenses: pkg.NewLicenseSet(licenses...), - Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(d, release), Type: pkg.DebPkg, MetadataType: pkg.DpkgMetadataType, @@ -83,7 +83,7 @@ func packageURL(m pkg.DpkgMetadata, distro *linux.Release) string { ).ToString() } -func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) { +func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { metadata, ok := p.Metadata.(pkg.DpkgMetadata) if !ok { log.WithFields("package", p).Warn("unable to extract DPKG metadata to add licenses") @@ -105,7 +105,7 @@ func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pk } } -func mergeFileListing(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) { +func mergeFileListing(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { metadata, ok := p.Metadata.(pkg.DpkgMetadata) if !ok { log.WithFields("package", p).Warn("unable to extract DPKG metadata to file listing") @@ -137,10 +137,10 @@ loopNewFiles: p.Locations.Add(infoLocations...) } -func getAdditionalFileListing(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) ([]pkg.DpkgFileRecord, []source.Location) { +func getAdditionalFileListing(resolver file.Resolver, dbLocation file.Location, m pkg.DpkgMetadata) ([]pkg.DpkgFileRecord, []file.Location) { // ensure the default value for a collection is never nil since this may be shown as JSON var files = make([]pkg.DpkgFileRecord, 0) - var locations []source.Location + var locations []file.Location md5Reader, md5Location := fetchMd5Contents(resolver, dbLocation, m) @@ -168,7 +168,7 @@ func getAdditionalFileListing(resolver source.FileResolver, dbLocation source.Lo } //nolint:dupl -func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) { +func fetchMd5Contents(resolver file.Resolver, dbLocation file.Location, m pkg.DpkgMetadata) (io.ReadCloser, *file.Location) { var md5Reader io.ReadCloser var err error @@ -204,7 +204,7 @@ func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location, } //nolint:dupl -func fetchConffileContents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) { +func fetchConffileContents(resolver file.Resolver, dbLocation file.Location, m pkg.DpkgMetadata) (io.ReadCloser, *file.Location) { var reader io.ReadCloser var err error @@ -239,7 +239,7 @@ func fetchConffileContents(resolver source.FileResolver, dbLocation source.Locat return reader, &l } -func fetchCopyrightContents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) { +func fetchCopyrightContents(resolver file.Resolver, dbLocation file.Location, m pkg.DpkgMetadata) (io.ReadCloser, *file.Location) { if resolver == nil { return nil, nil } diff --git a/syft/pkg/cataloger/deb/parse_dpkg_db.go b/syft/pkg/cataloger/deb/parse_dpkg_db.go index cd4c1ff535e..0a7dccb2d30 100644 --- a/syft/pkg/cataloger/deb/parse_dpkg_db.go +++ b/syft/pkg/cataloger/deb/parse_dpkg_db.go @@ -14,9 +14,9 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var ( @@ -24,7 +24,7 @@ var ( sourceRegexp = regexp.MustCompile(`(?P\S+)( \((?P.*)\))?`) ) -func parseDpkgDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseDpkgDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { metadata, err := parseDpkgStatus(reader) if err != nil { return nil, nil, fmt.Errorf("unable to catalog dpkg DB=%q: %w", reader.RealPath, err) diff --git a/syft/pkg/cataloger/deb/parse_dpkg_db_test.go b/syft/pkg/cataloger/deb/parse_dpkg_db_test.go index fc4e51633ad..0a2c58bd895 100644 --- a/syft/pkg/cataloger/deb/parse_dpkg_db_test.go +++ b/syft/pkg/cataloger/deb/parse_dpkg_db_test.go @@ -15,7 +15,6 @@ import ( "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func Test_parseDpkgStatus(t *testing.T) { @@ -308,7 +307,7 @@ Installed-Size: 10kib Type: "deb", PURL: "pkg:deb/debian/apt?distro=debian-10", Licenses: pkg.NewLicenseSet(), - Locations: source.NewLocationSet(source.NewLocation("place")), + Locations: file.NewLocationSet(file.NewLocation("place")), MetadataType: "DpkgMetadata", Metadata: pkg.DpkgMetadata{ Package: "apt", diff --git a/syft/pkg/cataloger/dotnet/package.go b/syft/pkg/cataloger/dotnet/package.go index 15ef7b71071..c8cb261a6fd 100644 --- a/syft/pkg/cataloger/dotnet/package.go +++ b/syft/pkg/cataloger/dotnet/package.go @@ -4,11 +4,11 @@ import ( "strings" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations ...source.Location) *pkg.Package { +func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations ...file.Location) *pkg.Package { if lib.Type != "package" { return nil } @@ -28,7 +28,7 @@ func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations . p := &pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(m), Language: pkg.Dotnet, Type: pkg.DotnetPkg, diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go b/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go index 0e322d3db25..2c7e1cf0bf9 100644 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go +++ b/syft/pkg/cataloger/dotnet/parse_dotnet_deps.go @@ -6,9 +6,9 @@ import ( "sort" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseDotnetDeps @@ -24,7 +24,7 @@ type dotnetDepsLibrary struct { HashPath string `json:"hashPath"` } -func parseDotnetDeps(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseDotnetDeps(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package dec := json.NewDecoder(reader) diff --git a/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go b/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go index 0065f110f6b..b8535374472 100644 --- a/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go +++ b/syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseDotnetDeps(t *testing.T) { fixture := "test-fixtures/TestLibrary.deps.json" - fixtureLocationSet := source.NewLocationSet(source.NewLocation(fixture)) + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture)) expected := []pkg.Package{ { Name: "AWSSDK.Core", diff --git a/syft/pkg/cataloger/elixir/package.go b/syft/pkg/cataloger/elixir/package.go index fc1ca514736..85dcd1f4253 100644 --- a/syft/pkg/cataloger/elixir/package.go +++ b/syft/pkg/cataloger/elixir/package.go @@ -2,16 +2,16 @@ package elixir import ( "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(d pkg.MixLockMetadata, locations ...source.Location) pkg.Package { +func newPackage(d pkg.MixLockMetadata, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: d.Name, Version: d.Version, Language: pkg.Elixir, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(d), Type: pkg.HexPkg, MetadataType: pkg.MixLockMetadataType, diff --git a/syft/pkg/cataloger/elixir/parse_mix_lock.go b/syft/pkg/cataloger/elixir/parse_mix_lock.go index 6de1fc8f703..46b4f4aa36a 100644 --- a/syft/pkg/cataloger/elixir/parse_mix_lock.go +++ b/syft/pkg/cataloger/elixir/parse_mix_lock.go @@ -9,9 +9,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // integrity check @@ -20,7 +20,7 @@ var _ generic.Parser = parseMixLock var mixLockDelimiter = regexp.MustCompile(`[%{}\n" ,:]+`) // parseMixLock parses a mix.lock and returns the discovered Elixir packages. -func parseMixLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseMixLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { r := bufio.NewReader(reader) var packages []pkg.Package diff --git a/syft/pkg/cataloger/elixir/parse_mix_lock_test.go b/syft/pkg/cataloger/elixir/parse_mix_lock_test.go index 2f5de43d4e1..4b01f04cd1e 100644 --- a/syft/pkg/cataloger/elixir/parse_mix_lock_test.go +++ b/syft/pkg/cataloger/elixir/parse_mix_lock_test.go @@ -4,13 +4,13 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseMixLock(t *testing.T) { - locations := source.NewLocationSet(source.NewLocation("test-fixtures/mix.lock")) + locations := file.NewLocationSet(file.NewLocation("test-fixtures/mix.lock")) expected := []pkg.Package{ { Name: "castore", diff --git a/syft/pkg/cataloger/erlang/package.go b/syft/pkg/cataloger/erlang/package.go index 2eb89053819..5fa28e59503 100644 --- a/syft/pkg/cataloger/erlang/package.go +++ b/syft/pkg/cataloger/erlang/package.go @@ -2,16 +2,16 @@ package erlang import ( "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(d pkg.RebarLockMetadata, locations ...source.Location) pkg.Package { +func newPackage(d pkg.RebarLockMetadata, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: d.Name, Version: d.Version, Language: pkg.Erlang, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(d), Type: pkg.HexPkg, MetadataType: pkg.RebarLockMetadataType, diff --git a/syft/pkg/cataloger/erlang/parse_rebar_lock.go b/syft/pkg/cataloger/erlang/parse_rebar_lock.go index 547a4d3ec4f..a2066f2cad5 100644 --- a/syft/pkg/cataloger/erlang/parse_rebar_lock.go +++ b/syft/pkg/cataloger/erlang/parse_rebar_lock.go @@ -3,15 +3,15 @@ package erlang import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // parseRebarLock parses a rebar.lock and returns the discovered Elixir packages. // //nolint:funlen -func parseRebarLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseRebarLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { doc, err := parseErlang(reader) if err != nil { return nil, nil, err diff --git a/syft/pkg/cataloger/erlang/parse_rebar_lock_test.go b/syft/pkg/cataloger/erlang/parse_rebar_lock_test.go index b1293143277..dc4ee9104a7 100644 --- a/syft/pkg/cataloger/erlang/parse_rebar_lock_test.go +++ b/syft/pkg/cataloger/erlang/parse_rebar_lock_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseRebarLock(t *testing.T) { @@ -263,7 +263,7 @@ func TestParseRebarLock(t *testing.T) { var expectedRelationships []artifact.Relationship for idx := range test.expected { - test.expected[idx].Locations = source.NewLocationSet(source.NewLocation(test.fixture)) + test.expected[idx].Locations = file.NewLocationSet(file.NewLocation(test.fixture)) } pkgtest.TestFileParser(t, test.fixture, parseRebarLock, test.expected, expectedRelationships) diff --git a/syft/pkg/cataloger/generic/cataloger.go b/syft/pkg/cataloger/generic/cataloger.go index d2069ffff52..b898133f74d 100644 --- a/syft/pkg/cataloger/generic/cataloger.go +++ b/syft/pkg/cataloger/generic/cataloger.go @@ -4,15 +4,15 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -type processor func(resolver source.FileResolver, env Environment) []request +type processor func(resolver file.Resolver, env Environment) []request type request struct { - source.Location + file.Location Parser } @@ -25,7 +25,7 @@ type Cataloger struct { func (c *Cataloger) WithParserByGlobs(parser Parser, globs ...string) *Cataloger { c.processor = append(c.processor, - func(resolver source.FileResolver, env Environment) []request { + func(resolver file.Resolver, env Environment) []request { var requests []request for _, g := range globs { log.WithFields("glob", g).Trace("searching for paths matching glob") @@ -45,7 +45,7 @@ func (c *Cataloger) WithParserByGlobs(parser Parser, globs ...string) *Cataloger func (c *Cataloger) WithParserByMimeTypes(parser Parser, types ...string) *Cataloger { c.processor = append(c.processor, - func(resolver source.FileResolver, env Environment) []request { + func(resolver file.Resolver, env Environment) []request { var requests []request log.WithFields("mimetypes", types).Trace("searching for paths matching mimetype") matches, err := resolver.FilesByMIMEType(types...) @@ -62,7 +62,7 @@ func (c *Cataloger) WithParserByMimeTypes(parser Parser, types ...string) *Catal func (c *Cataloger) WithParserByPath(parser Parser, paths ...string) *Cataloger { c.processor = append(c.processor, - func(resolver source.FileResolver, env Environment) []request { + func(resolver file.Resolver, env Environment) []request { var requests []request for _, p := range paths { log.WithFields("path", p).Trace("searching for path") @@ -80,7 +80,7 @@ func (c *Cataloger) WithParserByPath(parser Parser, paths ...string) *Cataloger return c } -func makeRequests(parser Parser, locations []source.Location) []request { +func makeRequests(parser Parser, locations []file.Location) []request { var requests []request for _, l := range locations { requests = append(requests, request{ @@ -104,7 +104,7 @@ func (c *Cataloger) Name() string { } // Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source. -func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (c *Cataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { var packages []pkg.Package var relationships []artifact.Relationship @@ -126,7 +126,7 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []arti continue } - discoveredPackages, discoveredRelationships, err := parser(resolver, &env, source.NewLocationReadCloser(location, contentReader)) + discoveredPackages, discoveredRelationships, err := parser(resolver, &env, file.NewLocationReadCloser(location, contentReader)) internal.CloseAndLogError(contentReader, location.VirtualPath) if err != nil { logger.WithFields("location", location.RealPath, "error", err).Warnf("cataloger failed") @@ -144,7 +144,7 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []arti } // selectFiles takes a set of file trees and resolves and file references of interest for future cataloging -func (c *Cataloger) selectFiles(resolver source.FileResolver) []request { +func (c *Cataloger) selectFiles(resolver file.Resolver) []request { var requests []request for _, proc := range c.processor { requests = append(requests, proc(resolver, Environment{})...) diff --git a/syft/pkg/cataloger/generic/cataloger_test.go b/syft/pkg/cataloger/generic/cataloger_test.go index fd864787a42..d2aabf28c8d 100644 --- a/syft/pkg/cataloger/generic/cataloger_test.go +++ b/syft/pkg/cataloger/generic/cataloger_test.go @@ -2,22 +2,22 @@ package generic import ( "fmt" - "io/ioutil" + "io" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) func Test_Cataloger(t *testing.T) { allParsedPaths := make(map[string]bool) - parser := func(resolver source.FileResolver, env *Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { + parser := func(resolver file.Resolver, env *Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { allParsedPaths[reader.AccessPath()] = true - contents, err := ioutil.ReadAll(reader) + contents, err := io.ReadAll(reader) require.NoError(t, err) if len(contents) == 0 { @@ -26,7 +26,7 @@ func Test_Cataloger(t *testing.T) { p := pkg.Package{ Name: string(contents), - Locations: source.NewLocationSet(reader.Location), + Locations: file.NewLocationSet(reader.Location), } r := artifact.Relationship{ From: p, @@ -40,7 +40,7 @@ func Test_Cataloger(t *testing.T) { upstream := "some-other-cataloger" expectedSelection := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt", "test-fixtures/empty.txt"} - resolver := source.NewMockResolverForPaths(expectedSelection...) + resolver := file.NewMockResolverForPaths(expectedSelection...) cataloger := NewCataloger(upstream). WithParserByPath(parser, "test-fixtures/another-path.txt", "test-fixtures/last/path.txt"). WithParserByGlobs(parser, "**/a-path.txt", "**/empty.txt") diff --git a/syft/pkg/cataloger/generic/parser.go b/syft/pkg/cataloger/generic/parser.go index 32b62f579aa..c95808fc175 100644 --- a/syft/pkg/cataloger/generic/parser.go +++ b/syft/pkg/cataloger/generic/parser.go @@ -2,13 +2,13 @@ package generic import ( "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) type Environment struct { LinuxRelease *linux.Release } -type Parser func(source.FileResolver, *Environment, source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) +type Parser func(file.Resolver, *Environment, file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) diff --git a/syft/pkg/cataloger/golang/cataloger.go b/syft/pkg/cataloger/golang/cataloger.go index d28a9ed9aae..bde2a9b5715 100644 --- a/syft/pkg/cataloger/golang/cataloger.go +++ b/syft/pkg/cataloger/golang/cataloger.go @@ -7,9 +7,9 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/event" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // NewGoModFileCataloger returns a new Go module cataloger object. @@ -45,7 +45,7 @@ func (p *progressingCataloger) Name() string { return p.cataloger.Name() } -func (p *progressingCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (p *progressingCataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { defer p.progress.SetCompleted() return p.cataloger.Catalog(resolver) } diff --git a/syft/pkg/cataloger/golang/licenses.go b/syft/pkg/cataloger/golang/licenses.go index e85ad7ec7f0..829a73dd3f3 100644 --- a/syft/pkg/cataloger/golang/licenses.go +++ b/syft/pkg/cataloger/golang/licenses.go @@ -22,13 +22,14 @@ import ( "github.com/anchore/syft/internal/licenses" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/event" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/fileresolver" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) type goLicenses struct { opts GoCatalogerOpts - localModCacheResolver source.WritableFileResolver + localModCacheResolver file.WritableResolver progress *event.CatalogerTask } @@ -55,27 +56,27 @@ func remotesForModule(proxies []string, noProxy []string, module string) []strin return proxies } -func modCacheResolver(modCacheDir string) source.WritableFileResolver { - var r source.WritableFileResolver +func modCacheResolver(modCacheDir string) file.WritableResolver { + var r file.WritableResolver if modCacheDir == "" { log.Trace("unable to determine mod cache directory, skipping mod cache resolver") - r = source.EmptyResolver{} + r = fileresolver.Empty{} } else { stat, err := os.Stat(modCacheDir) if os.IsNotExist(err) || stat == nil || !stat.IsDir() { log.Tracef("unable to open mod cache directory: %s, skipping mod cache resolver", modCacheDir) - r = source.EmptyResolver{} + r = fileresolver.Empty{} } else { - r = source.NewUnindexedDirectoryResolver(modCacheDir) + r = fileresolver.NewFromUnindexedDirectory(modCacheDir) } } return r } -func (c *goLicenses) getLicenses(resolver source.FileResolver, moduleName, moduleVersion string) (licenses []pkg.License, err error) { +func (c *goLicenses) getLicenses(resolver file.Resolver, moduleName, moduleVersion string) (licenses []pkg.License, err error) { licenses, err = findLicenses(resolver, fmt.Sprintf(`**/go/pkg/mod/%s@%s/*`, processCaps(moduleName), moduleVersion), ) @@ -131,7 +132,7 @@ func (c *goLicenses) getLicensesFromRemote(moduleName, moduleVersion string) ([] if err != nil { return err } - return c.localModCacheResolver.Write(source.NewLocation(path.Join(dir, filePath)), f) + return c.localModCacheResolver.Write(file.NewLocation(path.Join(dir, filePath)), f) }) if err != nil { @@ -156,7 +157,7 @@ func requireCollection(licenses []pkg.License) []pkg.License { return licenses } -func findLicenses(resolver source.FileResolver, globMatch string) (out []pkg.License, err error) { +func findLicenses(resolver file.Resolver, globMatch string) (out []pkg.License, err error) { out = make([]pkg.License, 0) if resolver == nil { return diff --git a/syft/pkg/cataloger/golang/licenses_test.go b/syft/pkg/cataloger/golang/licenses_test.go index 8f4545198bf..37df6547dee 100644 --- a/syft/pkg/cataloger/golang/licenses_test.go +++ b/syft/pkg/cataloger/golang/licenses_test.go @@ -14,14 +14,15 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/internal" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/fileresolver" "github.com/anchore/syft/syft/license" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) func Test_LocalLicenseSearch(t *testing.T) { - loc1 := source.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE") - loc2 := source.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt") + loc1 := file.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE") + loc2 := file.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt") tests := []struct { name string @@ -35,7 +36,7 @@ func Test_LocalLicenseSearch(t *testing.T) { Value: "Apache-2.0", SPDXExpression: "Apache-2.0", Type: license.Concluded, - Locations: source.NewLocationSet(loc1), + Locations: file.NewLocationSet(loc1), URLs: internal.NewStringSet(), }, }, @@ -46,7 +47,7 @@ func Test_LocalLicenseSearch(t *testing.T) { Value: "MIT", SPDXExpression: "MIT", Type: license.Concluded, - Locations: source.NewLocationSet(loc2), + Locations: file.NewLocationSet(loc2), URLs: internal.NewStringSet(), }, }, @@ -63,7 +64,7 @@ func Test_LocalLicenseSearch(t *testing.T) { localModCacheDir: path.Join(wd, "test-fixtures", "licenses", "pkg", "mod"), }, ) - licenses, err := l.getLicenses(source.EmptyResolver{}, test.name, test.version) + licenses, err := l.getLicenses(fileresolver.Empty{}, test.name, test.version) require.NoError(t, err) require.Len(t, licenses, 1) @@ -74,8 +75,8 @@ func Test_LocalLicenseSearch(t *testing.T) { } func Test_RemoteProxyLicenseSearch(t *testing.T) { - loc1 := source.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE") - loc2 := source.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt") + loc1 := file.NewLocation("github.com/someorg/somename@v0.3.2/LICENSE") + loc2 := file.NewLocation("github.com/!cap!o!r!g/!cap!project@v4.111.5/LICENSE.txt") server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { buf := &bytes.Buffer{} @@ -126,7 +127,7 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) { Value: "Apache-2.0", SPDXExpression: "Apache-2.0", Type: license.Concluded, - Locations: source.NewLocationSet(loc1), + Locations: file.NewLocationSet(loc1), URLs: internal.NewStringSet(), }, }, @@ -137,7 +138,7 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) { Value: "MIT", SPDXExpression: "MIT", Type: license.Concluded, - Locations: source.NewLocationSet(loc2), + Locations: file.NewLocationSet(loc2), URLs: internal.NewStringSet(), }, }, @@ -153,7 +154,7 @@ func Test_RemoteProxyLicenseSearch(t *testing.T) { localModCacheDir: modDir, }) - licenses, err := l.getLicenses(source.EmptyResolver{}, test.name, test.version) + licenses, err := l.getLicenses(fileresolver.Empty{}, test.name, test.version) require.NoError(t, err) require.Len(t, licenses, 1) diff --git a/syft/pkg/cataloger/golang/package.go b/syft/pkg/cataloger/golang/package.go index a7b1ee44e82..30ba083b48f 100644 --- a/syft/pkg/cataloger/golang/package.go +++ b/syft/pkg/cataloger/golang/package.go @@ -7,11 +7,11 @@ import ( "github.com/anchore/packageurl-go" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func (c *goBinaryCataloger) newGoBinaryPackage(resolver source.FileResolver, dep *debug.Module, mainModule, goVersion, architecture string, buildSettings map[string]string, locations ...source.Location) pkg.Package { +func (c *goBinaryCataloger) newGoBinaryPackage(resolver file.Resolver, dep *debug.Module, mainModule, goVersion, architecture string, buildSettings map[string]string, locations ...file.Location) pkg.Package { if dep.Replace != nil { dep = dep.Replace } @@ -28,7 +28,7 @@ func (c *goBinaryCataloger) newGoBinaryPackage(resolver source.FileResolver, dep PURL: packageURL(dep.Path, dep.Version), Language: pkg.Go, Type: pkg.GoModulePkg, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), MetadataType: pkg.GolangBinMetadataType, Metadata: pkg.GolangBinMetadata{ GoCompiledVersion: goVersion, diff --git a/syft/pkg/cataloger/golang/parse_go_binary.go b/syft/pkg/cataloger/golang/parse_go_binary.go index 542fcbe0232..e2e1c53eba6 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -18,11 +18,11 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/golang/internal/xcoff" "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" - "github.com/anchore/syft/syft/source" ) const GOARCH = "GOARCH" @@ -49,7 +49,7 @@ type goBinaryCataloger struct { } // Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing rpm db installation. -func (c *goBinaryCataloger) parseGoBinary(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func (c *goBinaryCataloger) parseGoBinary(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package unionReader, err := unionreader.GetUnionReader(reader.ReadCloser) @@ -66,7 +66,7 @@ func (c *goBinaryCataloger) parseGoBinary(resolver source.FileResolver, _ *gener return pkgs, nil, nil } -func (c *goBinaryCataloger) makeGoMainPackage(resolver source.FileResolver, mod *debug.BuildInfo, arch string, location source.Location) pkg.Package { +func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *debug.BuildInfo, arch string, location file.Location) pkg.Package { gbs := getBuildSettings(mod.Settings) main := c.newGoBinaryPackage( resolver, @@ -258,7 +258,7 @@ func createMainModuleFromPath(path string) (mod debug.Module) { return } -func (c *goBinaryCataloger) buildGoPkgInfo(resolver source.FileResolver, location source.Location, mod *debug.BuildInfo, arch string) []pkg.Package { +func (c *goBinaryCataloger) buildGoPkgInfo(resolver file.Resolver, location file.Location, mod *debug.BuildInfo, arch string) []pkg.Package { var pkgs []pkg.Package if mod == nil { return pkgs diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index cb0d9e3eb33..5f483b3c4f5 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -14,8 +14,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/fileresolver" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) // make will run the default make target for the given test fixture path @@ -135,9 +136,9 @@ func TestBuildGoPkgInfo(t *testing.T) { Type: pkg.GoModulePkg, Version: "(devel)", PURL: "pkg:golang/github.com/anchore/syft@(devel)", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -182,9 +183,9 @@ func TestBuildGoPkgInfo(t *testing.T) { PURL: "pkg:golang/github.com/adrg/xdg", Language: pkg.Go, Type: pkg.GoModulePkg, - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -225,9 +226,9 @@ func TestBuildGoPkgInfo(t *testing.T) { PURL: "pkg:golang/github.com/adrg/xdg@v0.2.1", Language: pkg.Go, Type: pkg.GoModulePkg, - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -261,9 +262,9 @@ func TestBuildGoPkgInfo(t *testing.T) { PURL: "pkg:golang/github.com/a/b/c@(devel)", Language: pkg.Go, Type: pkg.GoModulePkg, - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -320,9 +321,9 @@ func TestBuildGoPkgInfo(t *testing.T) { Type: pkg.GoModulePkg, Version: "v0.0.0-20221014195457-41bc6bb41035", PURL: "pkg:golang/github.com/anchore/syft@v0.0.0-20221014195457-41bc6bb41035", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -367,9 +368,9 @@ func TestBuildGoPkgInfo(t *testing.T) { Type: pkg.GoModulePkg, Version: "v0.79.0", PURL: "pkg:golang/github.com/anchore/syft@v0.79.0", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -413,9 +414,9 @@ func TestBuildGoPkgInfo(t *testing.T) { Type: pkg.GoModulePkg, Version: "v0.0.0-20221014195457-41bc6bb41035", PURL: "pkg:golang/github.com/anchore/syft@v0.0.0-20221014195457-41bc6bb41035", - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -468,9 +469,9 @@ func TestBuildGoPkgInfo(t *testing.T) { PURL: "pkg:golang/github.com/adrg/xdg@v0.2.1", Language: pkg.Go, Type: pkg.GoModulePkg, - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -490,9 +491,9 @@ func TestBuildGoPkgInfo(t *testing.T) { PURL: "pkg:golang/github.com/anchore/client-go@v0.0.0-20210222170800-9c70f9b80bcf", Language: pkg.Go, Type: pkg.GoModulePkg, - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -545,9 +546,9 @@ func TestBuildGoPkgInfo(t *testing.T) { PURL: "pkg:golang/golang.org/x/sys@v0.0.0-20211006194710-c8a6f5223071", Language: pkg.Go, Type: pkg.GoModulePkg, - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -566,9 +567,9 @@ func TestBuildGoPkgInfo(t *testing.T) { PURL: "pkg:golang/golang.org/x/term@v0.0.0-20210916214954-140adaaadfaf", Language: pkg.Go, Type: pkg.GoModulePkg, - Locations: source.NewLocationSet( - source.NewLocationFromCoordinates( - source.Coordinates{ + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, @@ -593,15 +594,15 @@ func TestBuildGoPkgInfo(t *testing.T) { p := &test.expected[i] p.SetID() } - location := source.NewLocationFromCoordinates( - source.Coordinates{ + location := file.NewLocationFromCoordinates( + file.Coordinates{ RealPath: "/a-path", FileSystemID: "layer-id", }, ) c := goBinaryCataloger{} - pkgs := c.buildGoPkgInfo(source.EmptyResolver{}, location, test.mod, test.arch) + pkgs := c.buildGoPkgInfo(fileresolver.Empty{}, location, test.mod, test.arch) assert.Equal(t, test.expected, pkgs) }) } diff --git a/syft/pkg/cataloger/golang/parse_go_mod.go b/syft/pkg/cataloger/golang/parse_go_mod.go index 3fdc45b9a71..7ef4ac0a70f 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod.go +++ b/syft/pkg/cataloger/golang/parse_go_mod.go @@ -11,9 +11,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) type goModCataloger struct { @@ -23,7 +23,7 @@ type goModCataloger struct { // parseGoModFile takes a go.mod and lists all packages discovered. // //nolint:funlen -func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func (c *goModCataloger) parseGoModFile(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { packages := make(map[string]pkg.Package) contents, err := io.ReadAll(reader) @@ -31,7 +31,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic return nil, nil, fmt.Errorf("failed to read go module: %w", err) } - file, err := modfile.Parse(reader.RealPath, contents, nil) + f, err := modfile.Parse(reader.RealPath, contents, nil) if err != nil { return nil, nil, fmt.Errorf("failed to parse go module: %w", err) } @@ -41,7 +41,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic log.Debugf("unable to get go.sum: %v", err) } - for _, m := range file.Require { + for _, m := range f.Require { licenses, err := c.licenses.getLicenses(resolver, m.Mod.Path, m.Mod.Version) if err != nil { log.Tracef("error getting licenses for package: %s %v", m.Mod.Path, err) @@ -51,7 +51,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic Name: m.Mod.Path, Version: m.Mod.Version, Licenses: pkg.NewLicenseSet(licenses...), - Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(m.Mod.Path, m.Mod.Version), Language: pkg.Go, Type: pkg.GoModulePkg, @@ -63,7 +63,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic } // remove any old packages and replace with new ones... - for _, m := range file.Replace { + for _, m := range f.Replace { licenses, err := c.licenses.getLicenses(resolver, m.New.Path, m.New.Version) if err != nil { log.Tracef("error getting licenses for package: %s %v", m.New.Path, err) @@ -73,7 +73,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic Name: m.New.Path, Version: m.New.Version, Licenses: pkg.NewLicenseSet(licenses...), - Locations: source.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(m.New.Path, m.New.Version), Language: pkg.Go, Type: pkg.GoModulePkg, @@ -85,7 +85,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic } // remove any packages from the exclude fields - for _, m := range file.Exclude { + for _, m := range f.Exclude { delete(packages, m.Mod.Path) } @@ -104,7 +104,7 @@ func (c *goModCataloger) parseGoModFile(resolver source.FileResolver, _ *generic return pkgsSlice, nil, nil } -func parseGoSumFile(resolver source.FileResolver, reader source.LocationReadCloser) (map[string]string, error) { +func parseGoSumFile(resolver file.Resolver, reader file.LocationReadCloser) (map[string]string, error) { out := map[string]string{} if resolver == nil { diff --git a/syft/pkg/cataloger/golang/parse_go_mod_test.go b/syft/pkg/cataloger/golang/parse_go_mod_test.go index 83b75beb108..f22b7ca2a56 100644 --- a/syft/pkg/cataloger/golang/parse_go_mod_test.go +++ b/syft/pkg/cataloger/golang/parse_go_mod_test.go @@ -3,9 +3,9 @@ package golang import ( "testing" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseGoMod(t *testing.T) { @@ -20,7 +20,7 @@ func TestParseGoMod(t *testing.T) { Name: "github.com/bmatcuk/doublestar", Version: "v1.3.1", PURL: "pkg:golang/github.com/bmatcuk/doublestar@v1.3.1", - Locations: source.NewLocationSet(source.NewLocation("test-fixtures/one-package")), + Locations: file.NewLocationSet(file.NewLocation("test-fixtures/one-package")), Language: pkg.Go, Type: pkg.GoModulePkg, MetadataType: pkg.GolangModMetadataType, @@ -36,7 +36,7 @@ func TestParseGoMod(t *testing.T) { Name: "github.com/anchore/go-testutils", Version: "v0.0.0-20200624184116-66aa578126db", PURL: "pkg:golang/github.com/anchore/go-testutils@v0.0.0-20200624184116-66aa578126db", - Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Locations: file.NewLocationSet(file.NewLocation("test-fixtures/many-packages")), Language: pkg.Go, Type: pkg.GoModulePkg, MetadataType: pkg.GolangModMetadataType, @@ -46,7 +46,7 @@ func TestParseGoMod(t *testing.T) { Name: "github.com/anchore/go-version", Version: "v1.2.2-0.20200701162849-18adb9c92b9b", PURL: "pkg:golang/github.com/anchore/go-version@v1.2.2-0.20200701162849-18adb9c92b9b", - Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Locations: file.NewLocationSet(file.NewLocation("test-fixtures/many-packages")), Language: pkg.Go, Type: pkg.GoModulePkg, MetadataType: pkg.GolangModMetadataType, @@ -56,7 +56,7 @@ func TestParseGoMod(t *testing.T) { Name: "github.com/anchore/stereoscope", Version: "v0.0.0-20200706164556-7cf39d7f4639", PURL: "pkg:golang/github.com/anchore/stereoscope@v0.0.0-20200706164556-7cf39d7f4639", - Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Locations: file.NewLocationSet(file.NewLocation("test-fixtures/many-packages")), Language: pkg.Go, Type: pkg.GoModulePkg, MetadataType: pkg.GolangModMetadataType, @@ -66,7 +66,7 @@ func TestParseGoMod(t *testing.T) { Name: "github.com/bmatcuk/doublestar", Version: "v8.8.8", PURL: "pkg:golang/github.com/bmatcuk/doublestar@v8.8.8", - Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Locations: file.NewLocationSet(file.NewLocation("test-fixtures/many-packages")), Language: pkg.Go, Type: pkg.GoModulePkg, MetadataType: pkg.GolangModMetadataType, @@ -76,7 +76,7 @@ func TestParseGoMod(t *testing.T) { Name: "github.com/go-test/deep", Version: "v1.0.6", PURL: "pkg:golang/github.com/go-test/deep@v1.0.6", - Locations: source.NewLocationSet(source.NewLocation("test-fixtures/many-packages")), + Locations: file.NewLocationSet(file.NewLocation("test-fixtures/many-packages")), Language: pkg.Go, Type: pkg.GoModulePkg, MetadataType: pkg.GolangModMetadataType, @@ -109,7 +109,7 @@ func Test_GoSumHashes(t *testing.T) { Name: "github.com/CycloneDX/cyclonedx-go", Version: "v0.6.0", PURL: "pkg:golang/github.com/CycloneDX/cyclonedx-go@v0.6.0", - Locations: source.NewLocationSet(source.NewLocation("go.mod")), + Locations: file.NewLocationSet(file.NewLocation("go.mod")), FoundBy: "go-mod-file-cataloger", Language: pkg.Go, Type: pkg.GoModulePkg, @@ -120,7 +120,7 @@ func Test_GoSumHashes(t *testing.T) { Name: "github.com/acarl005/stripansi", Version: "v0.0.0-20180116102854-5a71ef0e047d", PURL: "pkg:golang/github.com/acarl005/stripansi@v0.0.0-20180116102854-5a71ef0e047d", - Locations: source.NewLocationSet(source.NewLocation("go.mod")), + Locations: file.NewLocationSet(file.NewLocation("go.mod")), FoundBy: "go-mod-file-cataloger", Language: pkg.Go, Type: pkg.GoModulePkg, @@ -133,7 +133,7 @@ func Test_GoSumHashes(t *testing.T) { Name: "github.com/mgutz/ansi", Version: "v0.0.0-20200706080929-d51e80ef957d", PURL: "pkg:golang/github.com/mgutz/ansi@v0.0.0-20200706080929-d51e80ef957d", - Locations: source.NewLocationSet(source.NewLocation("go.mod")), + Locations: file.NewLocationSet(file.NewLocation("go.mod")), FoundBy: "go-mod-file-cataloger", Language: pkg.Go, Type: pkg.GoModulePkg, diff --git a/syft/pkg/cataloger/haskell/package.go b/syft/pkg/cataloger/haskell/package.go index c7c1aa1581a..ed47921b9f0 100644 --- a/syft/pkg/cataloger/haskell/package.go +++ b/syft/pkg/cataloger/haskell/package.go @@ -2,15 +2,15 @@ package haskell import ( "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(name, version string, m *pkg.HackageMetadata, locations ...source.Location) pkg.Package { +func newPackage(name, version string, m *pkg.HackageMetadata, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(name, version), Language: pkg.Haskell, Type: pkg.HackagePkg, diff --git a/syft/pkg/cataloger/haskell/parse_cabal_freeze.go b/syft/pkg/cataloger/haskell/parse_cabal_freeze.go index d95446984cc..abb2c82c9b2 100644 --- a/syft/pkg/cataloger/haskell/parse_cabal_freeze.go +++ b/syft/pkg/cataloger/haskell/parse_cabal_freeze.go @@ -8,15 +8,15 @@ import ( "strings" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseCabalFreeze // parseCabalFreeze is a parser function for cabal.project.freeze contents, returning all packages discovered. -func parseCabalFreeze(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseCabalFreeze(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { r := bufio.NewReader(reader) var pkgs []pkg.Package for { diff --git a/syft/pkg/cataloger/haskell/parse_cabal_freeze_test.go b/syft/pkg/cataloger/haskell/parse_cabal_freeze_test.go index 2c4a96c77b8..acb58b74e97 100644 --- a/syft/pkg/cataloger/haskell/parse_cabal_freeze_test.go +++ b/syft/pkg/cataloger/haskell/parse_cabal_freeze_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseCabalFreeze(t *testing.T) { fixture := "test-fixtures/cabal.project.freeze" - locationSet := source.NewLocationSet(source.NewLocation(fixture)) + locationSet := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { diff --git a/syft/pkg/cataloger/haskell/parse_stack_lock.go b/syft/pkg/cataloger/haskell/parse_stack_lock.go index de41a57672d..3eabd79784f 100644 --- a/syft/pkg/cataloger/haskell/parse_stack_lock.go +++ b/syft/pkg/cataloger/haskell/parse_stack_lock.go @@ -8,9 +8,9 @@ import ( "gopkg.in/yaml.v3" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseStackLock @@ -38,7 +38,7 @@ type completedSnapshot struct { } // parseStackLock is a parser function for stack.yaml.lock contents, returning all packages discovered. -func parseStackLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseStackLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { bytes, err := io.ReadAll(reader) if err != nil { return nil, nil, fmt.Errorf("failed to load stack.yaml.lock file: %w", err) diff --git a/syft/pkg/cataloger/haskell/parse_stack_lock_test.go b/syft/pkg/cataloger/haskell/parse_stack_lock_test.go index 2cdfbc75b86..d41b8704261 100644 --- a/syft/pkg/cataloger/haskell/parse_stack_lock_test.go +++ b/syft/pkg/cataloger/haskell/parse_stack_lock_test.go @@ -4,15 +4,15 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseStackLock(t *testing.T) { url := "https://raw.githubusercontent.com/commercialhaskell/stackage-snapshots/master/lts/19/14.yaml" fixture := "test-fixtures/stack.yaml.lock" - locationSet := source.NewLocationSet(source.NewLocation(fixture)) + locationSet := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { diff --git a/syft/pkg/cataloger/haskell/parse_stack_yaml.go b/syft/pkg/cataloger/haskell/parse_stack_yaml.go index 8404f4bf47c..c31bc6a5cf3 100644 --- a/syft/pkg/cataloger/haskell/parse_stack_yaml.go +++ b/syft/pkg/cataloger/haskell/parse_stack_yaml.go @@ -7,9 +7,9 @@ import ( "gopkg.in/yaml.v3" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseStackYaml @@ -19,7 +19,7 @@ type stackYaml struct { } // parseStackYaml is a parser function for stack.yaml contents, returning all packages discovered. -func parseStackYaml(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseStackYaml(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { bytes, err := io.ReadAll(reader) if err != nil { return nil, nil, fmt.Errorf("failed to load stack.yaml file: %w", err) diff --git a/syft/pkg/cataloger/haskell/parse_stack_yaml_test.go b/syft/pkg/cataloger/haskell/parse_stack_yaml_test.go index 1e035a7a60e..9946de99be8 100644 --- a/syft/pkg/cataloger/haskell/parse_stack_yaml_test.go +++ b/syft/pkg/cataloger/haskell/parse_stack_yaml_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseStackYaml(t *testing.T) { fixture := "test-fixtures/stack.yaml" - locationSet := source.NewLocationSet(source.NewLocation(fixture)) + locationSet := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { diff --git a/syft/pkg/cataloger/internal/pkgtest/observing_resolver.go b/syft/pkg/cataloger/internal/pkgtest/observing_resolver.go index fd0a5428a08..6e4c23ebe83 100644 --- a/syft/pkg/cataloger/internal/pkgtest/observing_resolver.go +++ b/syft/pkg/cataloger/internal/pkgtest/observing_resolver.go @@ -7,23 +7,23 @@ import ( "github.com/scylladb/go-set/strset" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) -var _ source.FileResolver = (*ObservingResolver)(nil) +var _ file.Resolver = (*ObservingResolver)(nil) type ObservingResolver struct { - decorated source.FileResolver + decorated file.Resolver pathQueries map[string][]string - pathResponses []source.Location - contentQueries []source.Location + pathResponses []file.Location + contentQueries []file.Location emptyPathResponses map[string][]string } -func NewObservingResolver(resolver source.FileResolver) *ObservingResolver { +func NewObservingResolver(resolver file.Resolver) *ObservingResolver { return &ObservingResolver{ decorated: resolver, - pathResponses: make([]source.Location, 0), + pathResponses: make([]file.Location, 0), emptyPathResponses: make(map[string][]string), pathQueries: make(map[string][]string), } @@ -138,11 +138,11 @@ func (r *ObservingResolver) addPathQuery(name string, input ...string) { r.pathQueries[name] = append(r.pathQueries[name], input...) } -func (r *ObservingResolver) addPathResponse(locs ...source.Location) { +func (r *ObservingResolver) addPathResponse(locs ...file.Location) { r.pathResponses = append(r.pathResponses, locs...) } -func (r *ObservingResolver) addEmptyPathResponse(name string, locs []source.Location, paths ...string) { +func (r *ObservingResolver) addEmptyPathResponse(name string, locs []file.Location, paths ...string) { if len(locs) == 0 { results := r.emptyPathResponses[name] results = append(results, paths...) @@ -150,7 +150,7 @@ func (r *ObservingResolver) addEmptyPathResponse(name string, locs []source.Loca } } -func (r *ObservingResolver) FilesByPath(paths ...string) ([]source.Location, error) { +func (r *ObservingResolver) FilesByPath(paths ...string) ([]file.Location, error) { name := "FilesByPath" r.addPathQuery(name, paths...) @@ -161,7 +161,7 @@ func (r *ObservingResolver) FilesByPath(paths ...string) ([]source.Location, err return locs, err } -func (r *ObservingResolver) FilesByGlob(patterns ...string) ([]source.Location, error) { +func (r *ObservingResolver) FilesByGlob(patterns ...string) ([]file.Location, error) { name := "FilesByGlob" r.addPathQuery(name, patterns...) @@ -172,7 +172,7 @@ func (r *ObservingResolver) FilesByGlob(patterns ...string) ([]source.Location, return locs, err } -func (r *ObservingResolver) FilesByMIMEType(types ...string) ([]source.Location, error) { +func (r *ObservingResolver) FilesByMIMEType(types ...string) ([]file.Location, error) { name := "FilesByMIMEType" r.addPathQuery(name, types...) @@ -183,7 +183,7 @@ func (r *ObservingResolver) FilesByMIMEType(types ...string) ([]source.Location, return locs, err } -func (r *ObservingResolver) RelativeFileByPath(l source.Location, path string) *source.Location { +func (r *ObservingResolver) RelativeFileByPath(l file.Location, path string) *file.Location { name := "RelativeFileByPath" r.addPathQuery(name, path) @@ -201,7 +201,7 @@ func (r *ObservingResolver) RelativeFileByPath(l source.Location, path string) * // For the content resolver methods... -func (r *ObservingResolver) FileContentsByLocation(location source.Location) (io.ReadCloser, error) { +func (r *ObservingResolver) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { r.contentQueries = append(r.contentQueries, location) reader, err := r.decorated.FileContentsByLocation(location) return reader, err @@ -209,7 +209,7 @@ func (r *ObservingResolver) FileContentsByLocation(location source.Location) (io // For the remaining resolver methods... -func (r *ObservingResolver) AllLocations() <-chan source.Location { +func (r *ObservingResolver) AllLocations() <-chan file.Location { return r.decorated.AllLocations() } @@ -217,6 +217,6 @@ func (r *ObservingResolver) HasPath(s string) bool { return r.decorated.HasPath(s) } -func (r *ObservingResolver) FileMetadataByLocation(location source.Location) (source.FileMetadata, error) { +func (r *ObservingResolver) FileMetadataByLocation(location file.Location) (file.Metadata, error) { return r.decorated.FileMetadataByLocation(location) } diff --git a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go index 5d230b0119a..9545c66b6fd 100644 --- a/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go +++ b/syft/pkg/cataloger/internal/pkgtest/test_generic_parser.go @@ -14,13 +14,14 @@ import ( "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/source" ) -type locationComparer func(x, y source.Location) bool +type locationComparer func(x, y file.Location) bool type licenseComparer func(x, y pkg.License) bool type CatalogTester struct { @@ -32,8 +33,8 @@ type CatalogTester struct { ignoreUnfulfilledPathResponses map[string][]string ignoreAnyUnfulfilledPaths []string env *generic.Environment - reader source.LocationReadCloser - resolver source.FileResolver + reader file.LocationReadCloser + resolver file.Resolver wantErr require.ErrorAssertionFunc compareOptions []cmp.Option locationComparer locationComparer @@ -58,13 +59,13 @@ func NewCatalogTester() *CatalogTester { } } -func DefaultLocationComparer(x, y source.Location) bool { +func DefaultLocationComparer(x, y file.Location) bool { return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath) } func DefaultLicenseComparer(x, y pkg.License) bool { return cmp.Equal(x, y, cmp.Comparer(DefaultLocationComparer), cmp.Comparer( - func(x, y source.LocationSet) bool { + func(x, y file.LocationSet) bool { xs := x.ToSlice() ys := y.ToSlice() if len(xs) != len(ys) { @@ -100,16 +101,16 @@ func (p *CatalogTester) FromFile(t *testing.T, path string) *CatalogTester { fixture, err := os.Open(path) require.NoError(t, err) - p.reader = source.LocationReadCloser{ - Location: source.NewLocation(fixture.Name()), + p.reader = file.LocationReadCloser{ + Location: file.NewLocation(fixture.Name()), ReadCloser: fixture, } return p } func (p *CatalogTester) FromString(location, data string) *CatalogTester { - p.reader = source.LocationReadCloser{ - Location: source.NewLocation(location), + p.reader = file.LocationReadCloser{ + Location: file.NewLocation(location), ReadCloser: io.NopCloser(strings.NewReader(data)), } return p @@ -139,7 +140,7 @@ func (p *CatalogTester) WithErrorAssertion(a require.ErrorAssertionFunc) *Catalo return p } -func (p *CatalogTester) WithResolver(r source.FileResolver) *CatalogTester { +func (p *CatalogTester) WithResolver(r file.Resolver) *CatalogTester { p.resolver = r return p } @@ -158,14 +159,14 @@ func (p *CatalogTester) WithImageResolver(t *testing.T, fixtureName string) *Cat } func (p *CatalogTester) IgnoreLocationLayer() *CatalogTester { - p.locationComparer = func(x, y source.Location) bool { + p.locationComparer = func(x, y file.Location) bool { return cmp.Equal(x.Coordinates.RealPath, y.Coordinates.RealPath) && cmp.Equal(x.VirtualPath, y.VirtualPath) } // we need to update the license comparer to use the ignored location layer p.licenseComparer = func(x, y pkg.License) bool { return cmp.Equal(x, y, cmp.Comparer(p.locationComparer), cmp.Comparer( - func(x, y source.LocationSet) bool { + func(x, y file.LocationSet) bool { xs := x.ToSlice() ys := y.ToSlice() if len(xs) != len(ys) { @@ -259,7 +260,7 @@ func (p *CatalogTester) assertPkgs(t *testing.T, pkgs []pkg.Package, relationshi cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes cmpopts.SortSlices(pkg.Less), cmp.Comparer( - func(x, y source.LocationSet) bool { + func(x, y file.LocationSet) bool { xs := x.ToSlice() ys := y.ToSlice() @@ -345,7 +346,7 @@ func AssertPackagesEqual(t *testing.T, a, b pkg.Package) { opts := []cmp.Option{ cmpopts.IgnoreFields(pkg.Package{}, "id"), // note: ID is not deterministic for test purposes cmp.Comparer( - func(x, y source.LocationSet) bool { + func(x, y file.LocationSet) bool { xs := x.ToSlice() ys := y.ToSlice() diff --git a/syft/pkg/cataloger/java/archive_parser.go b/syft/pkg/cataloger/java/archive_parser.go index d6c0ad926f5..a1efd022d0c 100644 --- a/syft/pkg/cataloger/java/archive_parser.go +++ b/syft/pkg/cataloger/java/archive_parser.go @@ -7,13 +7,12 @@ import ( "path" "strings" - "github.com/anchore/syft/internal/file" + intFile "github.com/anchore/syft/internal/file" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" - syftFile "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseJavaArchive @@ -43,8 +42,8 @@ var javaArchiveHashes = []crypto.Hash{ } type archiveParser struct { - fileManifest file.ZipFileManifest - location source.Location + fileManifest intFile.ZipFileManifest + location file.Location archivePath string contentPath string fileInfo archiveFilename @@ -52,7 +51,7 @@ type archiveParser struct { } // parseJavaArchive is a parser function for java archive contents, returning all Java libraries and nested archives. -func parseJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseJavaArchive(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { parser, cleanupFn, err := newJavaArchiveParser(reader, true) // note: even on error, we should always run cleanup functions defer cleanupFn() @@ -72,7 +71,7 @@ func uniquePkgKey(p *pkg.Package) string { // newJavaArchiveParser returns a new java archive parser object for the given archive. Can be configured to discover // and parse nested archives or ignore them. -func newJavaArchiveParser(reader source.LocationReadCloser, detectNested bool) (*archiveParser, func(), error) { +func newJavaArchiveParser(reader file.LocationReadCloser, detectNested bool) (*archiveParser, func(), error) { // fetch the last element of the virtual path virtualElements := strings.Split(reader.AccessPath(), ":") currentFilepath := virtualElements[len(virtualElements)-1] @@ -82,7 +81,7 @@ func newJavaArchiveParser(reader source.LocationReadCloser, detectNested bool) ( return nil, cleanupFn, fmt.Errorf("unable to process java archive: %w", err) } - fileManifest, err := file.NewZipFileManifest(archivePath) + fileManifest, err := intFile.NewZipFileManifest(archivePath) if err != nil { return nil, cleanupFn, fmt.Errorf("unable to read files from java archive: %w", err) } @@ -160,7 +159,7 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) { } // fetch the manifest file - contents, err := file.ContentsFromZip(j.archivePath, manifestMatches...) + contents, err := intFile.ContentsFromZip(j.archivePath, manifestMatches...) if err != nil { return nil, fmt.Errorf("unable to extract java manifests (%s): %w", j.location, err) } @@ -180,7 +179,7 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) { defer archiveCloser.Close() // grab and assign digest for the entire archive - digests, err := syftFile.DigestsFromFile(archiveCloser, javaArchiveHashes) + digests, err := file.NewDigestsFromFile(archiveCloser, javaArchiveHashes) if err != nil { log.Warnf("failed to create digest for file=%q: %+v", j.archivePath, err) } @@ -192,7 +191,7 @@ func (j *archiveParser) discoverMainPackage() (*pkg.Package, error) { Version: selectVersion(manifest, j.fileInfo), Language: pkg.Java, Licenses: pkg.NewLicenseSet(licenses...), - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( j.location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), Type: j.fileInfo.pkgType(), @@ -250,9 +249,9 @@ func (j *archiveParser) discoverPkgsFromNestedArchives(parentPkg *pkg.Package) ( // discoverPkgsFromZip finds Java archives within Java archives, returning all listed Java packages found and // associating each discovered package to the given parent package. -func discoverPkgsFromZip(location source.Location, archivePath, contentPath string, fileManifest file.ZipFileManifest, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) { +func discoverPkgsFromZip(location file.Location, archivePath, contentPath string, fileManifest intFile.ZipFileManifest, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) { // search and parse pom.properties files & fetch the contents - openers, err := file.ExtractFromZipToUniqueTempFile(archivePath, contentPath, fileManifest.GlobMatch(archiveFormatGlobs...)...) + openers, err := intFile.ExtractFromZipToUniqueTempFile(archivePath, contentPath, fileManifest.GlobMatch(archiveFormatGlobs...)...) if err != nil { return nil, nil, fmt.Errorf("unable to extract files from zip: %w", err) } @@ -261,7 +260,7 @@ func discoverPkgsFromZip(location source.Location, archivePath, contentPath stri } // discoverPkgsFromOpeners finds Java archives within the given files and associates them with the given parent package. -func discoverPkgsFromOpeners(location source.Location, openers map[string]file.Opener, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) { +func discoverPkgsFromOpeners(location file.Location, openers map[string]intFile.Opener, parentPkg *pkg.Package) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package var relationships []artifact.Relationship @@ -290,7 +289,7 @@ func discoverPkgsFromOpeners(location source.Location, openers map[string]file.O } // discoverPkgsFromOpener finds Java archives within the given file. -func discoverPkgsFromOpener(location source.Location, pathWithinArchive string, archiveOpener file.Opener) ([]pkg.Package, []artifact.Relationship, error) { +func discoverPkgsFromOpener(location file.Location, pathWithinArchive string, archiveOpener intFile.Opener) ([]pkg.Package, []artifact.Relationship, error) { archiveReadCloser, err := archiveOpener.Open() if err != nil { return nil, nil, fmt.Errorf("unable to open archived file from tempdir: %w", err) @@ -302,9 +301,9 @@ func discoverPkgsFromOpener(location source.Location, pathWithinArchive string, }() nestedPath := fmt.Sprintf("%s:%s", location.AccessPath(), pathWithinArchive) - nestedLocation := source.NewLocationFromCoordinates(location.Coordinates) + nestedLocation := file.NewLocationFromCoordinates(location.Coordinates) nestedLocation.VirtualPath = nestedPath - nestedPkgs, nestedRelationships, err := parseJavaArchive(nil, nil, source.LocationReadCloser{ + nestedPkgs, nestedRelationships, err := parseJavaArchive(nil, nil, file.LocationReadCloser{ Location: nestedLocation, ReadCloser: archiveReadCloser, }) @@ -315,8 +314,8 @@ func discoverPkgsFromOpener(location source.Location, pathWithinArchive string, return nestedPkgs, nestedRelationships, nil } -func pomPropertiesByParentPath(archivePath string, location source.Location, extractPaths []string) (map[string]pkg.PomProperties, error) { - contentsOfMavenPropertiesFiles, err := file.ContentsFromZip(archivePath, extractPaths...) +func pomPropertiesByParentPath(archivePath string, location file.Location, extractPaths []string) (map[string]pkg.PomProperties, error) { + contentsOfMavenPropertiesFiles, err := intFile.ContentsFromZip(archivePath, extractPaths...) if err != nil { return nil, fmt.Errorf("unable to extract maven files: %w", err) } @@ -344,8 +343,8 @@ func pomPropertiesByParentPath(archivePath string, location source.Location, ext return propertiesByParentPath, nil } -func pomProjectByParentPath(archivePath string, location source.Location, extractPaths []string) (map[string]pkg.PomProject, error) { - contentsOfMavenProjectFiles, err := file.ContentsFromZip(archivePath, extractPaths...) +func pomProjectByParentPath(archivePath string, location file.Location, extractPaths []string) (map[string]pkg.PomProject, error) { + contentsOfMavenProjectFiles, err := intFile.ContentsFromZip(archivePath, extractPaths...) if err != nil { return nil, fmt.Errorf("unable to extract maven files: %w", err) } @@ -374,7 +373,7 @@ func pomProjectByParentPath(archivePath string, location source.Location, extrac // packagesFromPomProperties processes a single Maven POM properties for a given parent package, returning all listed Java packages found and // associating each discovered package to the given parent package. Note the pom.xml is optional, the pom.properties is not. -func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.PomProject, parentPkg *pkg.Package, location source.Location) *pkg.Package { +func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.PomProject, parentPkg *pkg.Package, location file.Location) *pkg.Package { // keep the artifact name within the virtual path if this package does not match the parent package vPathSuffix := "" if !strings.HasPrefix(pomProperties.ArtifactID, parentPkg.Name) { @@ -386,7 +385,7 @@ func newPackageFromMavenData(pomProperties pkg.PomProperties, pomProject *pkg.Po p := pkg.Package{ Name: pomProperties.ArtifactID, Version: pomProperties.Version, - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), Language: pkg.Java, diff --git a/syft/pkg/cataloger/java/archive_parser_test.go b/syft/pkg/cataloger/java/archive_parser_test.go index 5385dec7382..422de7d480f 100644 --- a/syft/pkg/cataloger/java/archive_parser_test.go +++ b/syft/pkg/cataloger/java/archive_parser_test.go @@ -16,9 +16,9 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/internal" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func generateJavaBuildFixture(t *testing.T, fixturePath string) { @@ -100,7 +100,7 @@ func TestParseJar(t *testing.T) { Version: "1.0-SNAPSHOT", PURL: "pkg:maven/io.jenkins.plugins/example-jenkins-plugin@1.0-SNAPSHOT", Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT License", source.NewLocation("test-fixtures/java-builds/packages/example-jenkins-plugin.hpi")), + pkg.NewLicenseFromLocations("MIT License", file.NewLocation("test-fixtures/java-builds/packages/example-jenkins-plugin.hpi")), ), Language: pkg.Java, Type: pkg.JenkinsPluginPkg, @@ -272,12 +272,12 @@ func TestParseJar(t *testing.T) { for k := range test.expected { p := test.expected[k] - p.Locations.Add(source.NewLocation(test.fixture)) + p.Locations.Add(file.NewLocation(test.fixture)) test.expected[k] = p } - parser, cleanupFn, err := newJavaArchiveParser(source.LocationReadCloser{ - Location: source.NewLocation(fixture.Name()), + parser, cleanupFn, err := newJavaArchiveParser(file.LocationReadCloser{ + Location: file.NewLocation(fixture.Name()), ReadCloser: fixture, }, false) defer cleanupFn() @@ -546,8 +546,8 @@ func TestParseNestedJar(t *testing.T) { fixture, err := os.Open(test.fixture) require.NoError(t, err) - actual, _, err := parseJavaArchive(nil, nil, source.LocationReadCloser{ - Location: source.NewLocation(fixture.Name()), + actual, _, err := parseJavaArchive(nil, nil, file.LocationReadCloser{ + Location: file.NewLocation(fixture.Name()), ReadCloser: fixture, }) require.NoError(t, err) @@ -975,7 +975,7 @@ func Test_newPackageFromMavenData(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - locations := source.NewLocationSet(source.NewLocation(virtualPath)) + locations := file.NewLocationSet(file.NewLocation(virtualPath)) if test.expectedPackage != nil { test.expectedPackage.Locations = locations if test.expectedPackage.Metadata.(pkg.JavaMetadata).Parent != nil { @@ -987,7 +987,7 @@ func Test_newPackageFromMavenData(t *testing.T) { } test.expectedParent.Locations = locations - actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, source.NewLocation(virtualPath)) + actualPackage := newPackageFromMavenData(test.props, test.project, test.parent, file.NewLocation(virtualPath)) if test.expectedPackage == nil { require.Nil(t, actualPackage) } else { diff --git a/syft/pkg/cataloger/java/graalvm_native_image_cataloger.go b/syft/pkg/cataloger/java/graalvm_native_image_cataloger.go index 2e8b63c932b..db462ea8c99 100644 --- a/syft/pkg/cataloger/java/graalvm_native_image_cataloger.go +++ b/syft/pkg/cataloger/java/graalvm_native_image_cataloger.go @@ -17,9 +17,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" - "github.com/anchore/syft/syft/source" ) type nativeImageCycloneDX struct { @@ -571,7 +571,7 @@ func fetchPkgs(reader unionreader.UnionReader, filename string) []pkg.Package { } // Catalog attempts to find any native image executables reachable from a resolver. -func (c *NativeImageCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (c *NativeImageCataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package fileMatches, err := resolver.FilesByMIMEType(internal.ExecutableMIMETypeSet.List()...) if err != nil { diff --git a/syft/pkg/cataloger/java/parse_gradle_lockfile.go b/syft/pkg/cataloger/java/parse_gradle_lockfile.go index 803639ab43a..65adf7aebb2 100644 --- a/syft/pkg/cataloger/java/parse_gradle_lockfile.go +++ b/syft/pkg/cataloger/java/parse_gradle_lockfile.go @@ -5,9 +5,9 @@ import ( "strings" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) const gradleLockfileGlob = "**/gradle.lockfile*" @@ -19,7 +19,7 @@ type LockfileDependency struct { Version string } -func parseGradleLockfile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseGradleLockfile(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package // Create a new scanner to read the file @@ -51,7 +51,7 @@ func parseGradleLockfile(_ source.FileResolver, _ *generic.Environment, reader s mappedPkg := pkg.Package{ Name: dep.Name, Version: dep.Version, - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), Language: pkg.Java, diff --git a/syft/pkg/cataloger/java/parse_gradle_lockfile_test.go b/syft/pkg/cataloger/java/parse_gradle_lockfile_test.go index 65129efcff2..babc3d3e558 100644 --- a/syft/pkg/cataloger/java/parse_gradle_lockfile_test.go +++ b/syft/pkg/cataloger/java/parse_gradle_lockfile_test.go @@ -3,9 +3,9 @@ package java import ( "testing" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func Test_parserGradleLockfile(t *testing.T) { @@ -44,7 +44,7 @@ func Test_parserGradleLockfile(t *testing.T) { for _, test := range tests { t.Run(test.input, func(t *testing.T) { for i := range test.expected { - test.expected[i].Locations.Add(source.NewLocation(test.input)) + test.expected[i].Locations.Add(file.NewLocation(test.input)) } pkgtest.TestFileParser(t, test.input, parseGradleLockfile, test.expected, nil) }) diff --git a/syft/pkg/cataloger/java/parse_pom_xml.go b/syft/pkg/cataloger/java/parse_pom_xml.go index 8df940869ed..b0316c860d2 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml.go +++ b/syft/pkg/cataloger/java/parse_pom_xml.go @@ -12,16 +12,16 @@ import ( "golang.org/x/net/html/charset" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) const pomXMLGlob = "*pom.xml" var propertyMatcher = regexp.MustCompile("[$][{][^}]+[}]") -func parserPomXML(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parserPomXML(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { pom, err := decodePomXML(reader) if err != nil { return nil, nil, err @@ -65,7 +65,7 @@ func newPomProject(path string, p gopom.Project) *pkg.PomProject { } } -func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...source.Location) pkg.Package { +func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...file.Location) pkg.Package { m := pkg.JavaMetadata{ PomProperties: &pkg.PomProperties{ GroupID: resolveProperty(pom, dep.GroupID), @@ -78,7 +78,7 @@ func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...sou p := pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(name, version, m), Language: pkg.Java, Type: pkg.JavaPkg, // TODO: should we differentiate between packages from jar/war/zip versus packages from a pom.xml that were not installed yet? diff --git a/syft/pkg/cataloger/java/parse_pom_xml_test.go b/syft/pkg/cataloger/java/parse_pom_xml_test.go index 2e4d7a846b6..01b19e6bb8c 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml_test.go +++ b/syft/pkg/cataloger/java/parse_pom_xml_test.go @@ -7,9 +7,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/vifraa/gopom" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func Test_parserPomXML(t *testing.T) { @@ -49,7 +49,7 @@ func Test_parserPomXML(t *testing.T) { for _, test := range tests { t.Run(test.input, func(t *testing.T) { for i := range test.expected { - test.expected[i].Locations.Add(source.NewLocation(test.input)) + test.expected[i].Locations.Add(file.NewLocation(test.input)) } pkgtest.TestFileParser(t, test.input, parserPomXML, test.expected, nil) }) @@ -181,7 +181,7 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { for _, test := range tests { t.Run(test.input, func(t *testing.T) { for i := range test.expected { - test.expected[i].Locations.Add(source.NewLocation(test.input)) + test.expected[i].Locations.Add(file.NewLocation(test.input)) } pkgtest.TestFileParser(t, test.input, parserPomXML, test.expected, nil) }) diff --git a/syft/pkg/cataloger/java/tar_wrapped_archive_parser.go b/syft/pkg/cataloger/java/tar_wrapped_archive_parser.go index 99c723f44b5..05ab6dd22a5 100644 --- a/syft/pkg/cataloger/java/tar_wrapped_archive_parser.go +++ b/syft/pkg/cataloger/java/tar_wrapped_archive_parser.go @@ -3,11 +3,11 @@ package java import ( "fmt" - "github.com/anchore/syft/internal/file" + intFile "github.com/anchore/syft/internal/file" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var genericTarGlobs = []string{ @@ -45,7 +45,7 @@ var genericTarGlobs = []string{ // note: for compressed tars this is an extremely expensive operation and can lead to performance degradation. This is // due to the fact that there is no central directory header (say as in zip), which means that in order to get // a file listing within the archive you must decompress the entire archive and seek through all of the entries. -func parseTarWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseTarWrappedJavaArchive(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader) // note: even on error, we should always run cleanup functions defer cleanupFn() @@ -57,8 +57,8 @@ func parseTarWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, r return discoverPkgsFromTar(reader.Location, archivePath, contentPath) } -func discoverPkgsFromTar(location source.Location, archivePath, contentPath string) ([]pkg.Package, []artifact.Relationship, error) { - openers, err := file.ExtractGlobsFromTarToUniqueTempFile(archivePath, contentPath, archiveFormatGlobs...) +func discoverPkgsFromTar(location file.Location, archivePath, contentPath string) ([]pkg.Package, []artifact.Relationship, error) { + openers, err := intFile.ExtractGlobsFromTarToUniqueTempFile(archivePath, contentPath, archiveFormatGlobs...) if err != nil { return nil, nil, fmt.Errorf("unable to extract files from tar: %w", err) } diff --git a/syft/pkg/cataloger/java/tar_wrapped_archive_parser_test.go b/syft/pkg/cataloger/java/tar_wrapped_archive_parser_test.go index 6f40c175d48..1a3d1d1f32c 100644 --- a/syft/pkg/cataloger/java/tar_wrapped_archive_parser_test.go +++ b/syft/pkg/cataloger/java/tar_wrapped_archive_parser_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) func Test_parseTarWrappedJavaArchive(t *testing.T) { @@ -40,8 +40,8 @@ func Test_parseTarWrappedJavaArchive(t *testing.T) { t.Fatalf("failed to open fixture: %+v", err) } - actualPkgs, _, err := parseTarWrappedJavaArchive(nil, nil, source.LocationReadCloser{ - Location: source.NewLocation(test.fixture), + actualPkgs, _, err := parseTarWrappedJavaArchive(nil, nil, file.LocationReadCloser{ + Location: file.NewLocation(test.fixture), ReadCloser: fixture, }) require.NoError(t, err) diff --git a/syft/pkg/cataloger/java/zip_wrapped_archive_parser.go b/syft/pkg/cataloger/java/zip_wrapped_archive_parser.go index dffe5df74a6..930427f38f5 100644 --- a/syft/pkg/cataloger/java/zip_wrapped_archive_parser.go +++ b/syft/pkg/cataloger/java/zip_wrapped_archive_parser.go @@ -3,11 +3,11 @@ package java import ( "fmt" - "github.com/anchore/syft/internal/file" + intFile "github.com/anchore/syft/internal/file" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var genericZipGlobs = []string{ @@ -17,7 +17,7 @@ var genericZipGlobs = []string{ // TODO: when the generic archive cataloger is implemented, this should be removed (https://github.com/anchore/syft/issues/246) // parseZipWrappedJavaArchive is a parser function for java archive contents contained within arbitrary zip files. -func parseZipWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseZipWrappedJavaArchive(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { contentPath, archivePath, cleanupFn, err := saveArchiveToTmp(reader.AccessPath(), reader) // note: even on error, we should always run cleanup functions defer cleanupFn() @@ -29,7 +29,7 @@ func parseZipWrappedJavaArchive(_ source.FileResolver, _ *generic.Environment, r // functions support zips with shell scripts prepended to the file. Specifically, the helpers use the central // header at the end of the file to determine where the beginning of the zip payload is (unlike the standard lib // or archiver). - fileManifest, err := file.NewZipFileManifest(archivePath) + fileManifest, err := intFile.NewZipFileManifest(archivePath) if err != nil { return nil, nil, fmt.Errorf("unable to read files from java archive: %w", err) } diff --git a/syft/pkg/cataloger/java/zip_wrapped_archive_parser_test.go b/syft/pkg/cataloger/java/zip_wrapped_archive_parser_test.go index aa1e5108945..2f5b3328ac9 100644 --- a/syft/pkg/cataloger/java/zip_wrapped_archive_parser_test.go +++ b/syft/pkg/cataloger/java/zip_wrapped_archive_parser_test.go @@ -8,7 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) func Test_parseZipWrappedJavaArchive(t *testing.T) { @@ -33,8 +33,8 @@ func Test_parseZipWrappedJavaArchive(t *testing.T) { t.Fatalf("failed to open fixture: %+v", err) } - actualPkgs, _, err := parseZipWrappedJavaArchive(nil, nil, source.LocationReadCloser{ - Location: source.NewLocation(test.fixture), + actualPkgs, _, err := parseZipWrappedJavaArchive(nil, nil, file.LocationReadCloser{ + Location: file.NewLocation(test.fixture), ReadCloser: fixture, }) require.NoError(t, err) diff --git a/syft/pkg/cataloger/javascript/cataloger_test.go b/syft/pkg/cataloger/javascript/cataloger_test.go index 5b9c18f0ed2..ca5169bafe5 100644 --- a/syft/pkg/cataloger/javascript/cataloger_test.go +++ b/syft/pkg/cataloger/javascript/cataloger_test.go @@ -3,13 +3,13 @@ package javascript import ( "testing" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func Test_JavascriptCataloger(t *testing.T) { - locationSet := source.NewLocationSet(source.NewLocation("package-lock.json")) + locationSet := file.NewLocationSet(file.NewLocation("package-lock.json")) expectedPkgs := []pkg.Package{ { Name: "@actions/core", @@ -20,7 +20,7 @@ func Test_JavascriptCataloger(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation("package-lock.json")), + pkg.NewLicenseFromLocations("MIT", file.NewLocation("package-lock.json")), ), MetadataType: pkg.NpmPackageLockJSONMetadataType, Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", Integrity: "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw=="}, @@ -45,7 +45,7 @@ func Test_JavascriptCataloger(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation("package-lock.json")), + pkg.NewLicenseFromLocations("MIT", file.NewLocation("package-lock.json")), ), MetadataType: pkg.NpmPackageLockJSONMetadataType, Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/cowsay/-/cowsay-1.4.0.tgz", Integrity: "sha512-rdg5k5PsHFVJheO/pmE3aDg2rUDDTfPJau6yYkZYlHFktUz+UxbE+IgnUAEyyCyv4noL5ltxXD0gZzmHPCy/9g=="}, diff --git a/syft/pkg/cataloger/javascript/package.go b/syft/pkg/cataloger/javascript/package.go index 468854a3927..4eaea055beb 100644 --- a/syft/pkg/cataloger/javascript/package.go +++ b/syft/pkg/cataloger/javascript/package.go @@ -8,11 +8,11 @@ import ( "github.com/anchore/packageurl-go" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackageJSONPackage(u packageJSON, indexLocation source.Location) pkg.Package { +func newPackageJSONPackage(u packageJSON, indexLocation file.Location) pkg.Package { licenseCandidates, err := u.licensesFromJSON() if err != nil { log.Warnf("unable to extract licenses from javascript package.json: %+v", err) @@ -23,7 +23,7 @@ func newPackageJSONPackage(u packageJSON, indexLocation source.Location) pkg.Pac Name: u.Name, Version: u.Version, PURL: packageURL(u.Name, u.Version), - Locations: source.NewLocationSet(indexLocation), + Locations: file.NewLocationSet(indexLocation), Language: pkg.JavaScript, Licenses: pkg.NewLicenseSet(license...), Type: pkg.NpmPkg, @@ -44,7 +44,7 @@ func newPackageJSONPackage(u packageJSON, indexLocation source.Location) pkg.Pac return p } -func newPackageLockV1Package(resolver source.FileResolver, location source.Location, name string, u lockDependency) pkg.Package { +func newPackageLockV1Package(resolver file.Resolver, location file.Location, name string, u lockDependency) pkg.Package { version := u.Version const aliasPrefixPackageLockV1 = "npm:" @@ -66,7 +66,7 @@ func newPackageLockV1Package(resolver source.FileResolver, location source.Locat pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, version), Language: pkg.JavaScript, Type: pkg.NpmPkg, @@ -76,14 +76,14 @@ func newPackageLockV1Package(resolver source.FileResolver, location source.Locat ) } -func newPackageLockV2Package(resolver source.FileResolver, location source.Location, name string, u lockPackage) pkg.Package { +func newPackageLockV2Package(resolver file.Resolver, location file.Location, name string, u lockPackage) pkg.Package { return finalizeLockPkg( resolver, location, pkg.Package{ Name: name, Version: u.Version, - Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(location, u.License...)...), PURL: packageURL(name, u.Version), Language: pkg.JavaScript, @@ -94,14 +94,14 @@ func newPackageLockV2Package(resolver source.FileResolver, location source.Locat ) } -func newPnpmPackage(resolver source.FileResolver, location source.Location, name, version string) pkg.Package { +func newPnpmPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package { return finalizeLockPkg( resolver, location, pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, version), Language: pkg.JavaScript, Type: pkg.NpmPkg, @@ -109,14 +109,14 @@ func newPnpmPackage(resolver source.FileResolver, location source.Location, name ) } -func newYarnLockPackage(resolver source.FileResolver, location source.Location, name, version string) pkg.Package { +func newYarnLockPackage(resolver file.Resolver, location file.Location, name, version string) pkg.Package { return finalizeLockPkg( resolver, location, pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(name, version), Language: pkg.JavaScript, Type: pkg.NpmPkg, @@ -124,14 +124,14 @@ func newYarnLockPackage(resolver source.FileResolver, location source.Location, ) } -func finalizeLockPkg(resolver source.FileResolver, location source.Location, p pkg.Package) pkg.Package { +func finalizeLockPkg(resolver file.Resolver, location file.Location, p pkg.Package) pkg.Package { licenseCandidate := addLicenses(p.Name, resolver, location) p.Licenses.Add(pkg.NewLicensesFromLocation(location, licenseCandidate...)...) p.SetID() return p } -func addLicenses(name string, resolver source.FileResolver, location source.Location) (allLicenses []string) { +func addLicenses(name string, resolver file.Resolver, location file.Location) (allLicenses []string) { if resolver == nil { return allLicenses } diff --git a/syft/pkg/cataloger/javascript/parse_package_json.go b/syft/pkg/cataloger/javascript/parse_package_json.go index 59c8a5c508d..0c05aedc0e3 100644 --- a/syft/pkg/cataloger/javascript/parse_package_json.go +++ b/syft/pkg/cataloger/javascript/parse_package_json.go @@ -12,9 +12,9 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // integrity check @@ -51,7 +51,7 @@ type repository struct { var authorPattern = regexp.MustCompile(`^\s*(?P[^<(]*)(\s+<(?P.*)>)?(\s\((?P.*)\))?\s*$`) // parsePackageJSON parses a package.json and returns the discovered JavaScript packages. -func parsePackageJSON(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parsePackageJSON(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package dec := json.NewDecoder(reader) diff --git a/syft/pkg/cataloger/javascript/parse_package_json_test.go b/syft/pkg/cataloger/javascript/parse_package_json_test.go index c0e0b17b088..3a57f3c8272 100644 --- a/syft/pkg/cataloger/javascript/parse_package_json_test.go +++ b/syft/pkg/cataloger/javascript/parse_package_json_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/assert" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParsePackageJSON(t *testing.T) { @@ -24,7 +24,7 @@ func TestParsePackageJSON(t *testing.T) { Type: pkg.NpmPkg, Language: pkg.JavaScript, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package.json")), + pkg.NewLicenseFromLocations("Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package.json")), ), MetadataType: pkg.NpmPackageJSONMetadataType, Metadata: pkg.NpmPackageJSONMetadata{ @@ -46,7 +46,7 @@ func TestParsePackageJSON(t *testing.T) { Type: pkg.NpmPkg, Language: pkg.JavaScript, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("ISC", source.NewLocation("test-fixtures/pkg-json/package-license-object.json")), + pkg.NewLicenseFromLocations("ISC", file.NewLocation("test-fixtures/pkg-json/package-license-object.json")), ), MetadataType: pkg.NpmPackageJSONMetadataType, Metadata: pkg.NpmPackageJSONMetadata{ @@ -67,8 +67,8 @@ func TestParsePackageJSON(t *testing.T) { PURL: "pkg:npm/npm@6.14.6", Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation("test-fixtures/pkg-json/package-license-objects.json")), - pkg.NewLicenseFromLocations("Apache-2.0", source.NewLocation("test-fixtures/pkg-json/package-license-objects.json")), + pkg.NewLicenseFromLocations("MIT", file.NewLocation("test-fixtures/pkg-json/package-license-objects.json")), + pkg.NewLicenseFromLocations("Apache-2.0", file.NewLocation("test-fixtures/pkg-json/package-license-objects.json")), ), Language: pkg.JavaScript, MetadataType: pkg.NpmPackageJSONMetadataType, @@ -128,7 +128,7 @@ func TestParsePackageJSON(t *testing.T) { PURL: "pkg:npm/npm@6.14.6", Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package-nested-author.json")), + pkg.NewLicenseFromLocations("Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-nested-author.json")), ), Language: pkg.JavaScript, MetadataType: pkg.NpmPackageJSONMetadataType, @@ -150,7 +150,7 @@ func TestParsePackageJSON(t *testing.T) { PURL: "pkg:npm/function-bind@1.1.1", Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation("test-fixtures/pkg-json/package-repo-string.json")), + pkg.NewLicenseFromLocations("MIT", file.NewLocation("test-fixtures/pkg-json/package-repo-string.json")), ), Language: pkg.JavaScript, MetadataType: pkg.NpmPackageJSONMetadataType, @@ -172,7 +172,7 @@ func TestParsePackageJSON(t *testing.T) { PURL: "pkg:npm/npm@6.14.6", Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("Artistic-2.0", source.NewLocation("test-fixtures/pkg-json/package-private.json")), + pkg.NewLicenseFromLocations("Artistic-2.0", file.NewLocation("test-fixtures/pkg-json/package-private.json")), ), Language: pkg.JavaScript, MetadataType: pkg.NpmPackageJSONMetadataType, @@ -191,7 +191,7 @@ func TestParsePackageJSON(t *testing.T) { for _, test := range tests { t.Run(test.Fixture, func(t *testing.T) { - test.ExpectedPkg.Locations.Add(source.NewLocation(test.Fixture)) + test.ExpectedPkg.Locations.Add(file.NewLocation(test.Fixture)) pkgtest.TestFileParser(t, test.Fixture, parsePackageJSON, []pkg.Package{test.ExpectedPkg}, nil) }) } diff --git a/syft/pkg/cataloger/javascript/parse_package_lock.go b/syft/pkg/cataloger/javascript/parse_package_lock.go index 7ca2669b38a..91663b1b250 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock.go @@ -9,9 +9,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // integrity check @@ -44,7 +44,7 @@ type lockPackage struct { type packageLockLicense []string // parsePackageLock parses a package-lock.json and returns the discovered JavaScript packages. -func parsePackageLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parsePackageLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { // in the case we find package-lock.json files in the node_modules directories, skip those // as the whole purpose of the lock file is for the specific dependencies of the root project if pathContainsNodeModulesDirectory(reader.AccessPath()) { diff --git a/syft/pkg/cataloger/javascript/parse_package_lock_test.go b/syft/pkg/cataloger/javascript/parse_package_lock_test.go index dec36fb5f3f..baa27b397b4 100644 --- a/syft/pkg/cataloger/javascript/parse_package_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_package_lock_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParsePackageLock(t *testing.T) { @@ -114,7 +114,7 @@ func TestParsePackageLock(t *testing.T) { } fixture := "test-fixtures/pkg-lock/package-lock.json" for i := range expectedPkgs { - expectedPkgs[i].Locations.Add(source.NewLocation(fixture)) + expectedPkgs[i].Locations.Add(file.NewLocation(fixture)) } pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) @@ -140,7 +140,7 @@ func TestParsePackageLockV2(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), ), MetadataType: "NpmPackageLockJsonMetadata", Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", Integrity: "sha1-XxnSuFqY6VWANvajysyIGUIPBc8="}, @@ -152,7 +152,7 @@ func TestParsePackageLockV2(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), ), MetadataType: "NpmPackageLockJsonMetadata", Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/react/-/react-18.0.17.tgz", Integrity: "sha1-RYPZwyLWfv5LOak10iPtzHBQzPQ="}, @@ -164,7 +164,7 @@ func TestParsePackageLockV2(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), ), MetadataType: "NpmPackageLockJsonMetadata", Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz", Integrity: "sha1-GmL4lSVyPd4kuhsBsJK/XfitTTk="}, @@ -176,14 +176,14 @@ func TestParsePackageLockV2(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), ), MetadataType: "NpmPackageLockJsonMetadata", Metadata: pkg.NpmPackageLockJSONMetadata{Resolved: "https://registry.npmjs.org/csstype/-/csstype-3.1.0.tgz", Integrity: "sha1-TdysNxjXh8+d8NG30VAzklyPKfI="}, }, } for i := range expectedPkgs { - expectedPkgs[i].Locations.Add(source.NewLocation(fixture)) + expectedPkgs[i].Locations.Add(file.NewLocation(fixture)) } pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) } @@ -239,7 +239,7 @@ func TestParsePackageLockV3(t *testing.T) { }, } for i := range expectedPkgs { - expectedPkgs[i].Locations.Add(source.NewLocation(fixture)) + expectedPkgs[i].Locations.Add(file.NewLocation(fixture)) } pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) } @@ -287,7 +287,7 @@ func TestParsePackageLockAlias(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("ISC", source.NewLocation(packageLockV2)), + pkg.NewLicenseFromLocations("ISC", file.NewLocation(packageLockV2)), ), MetadataType: "NpmPackageLockJsonMetadata", Metadata: pkg.NpmPackageLockJSONMetadata{}, @@ -302,7 +302,7 @@ func TestParsePackageLockAlias(t *testing.T) { } for i := range expected { - expected[i].Locations.Add(source.NewLocation(pl)) + expected[i].Locations.Add(file.NewLocation(pl)) } pkgtest.TestFileParser(t, pl, parsePackageLock, expected, expectedRelationships) } @@ -318,7 +318,7 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("ISC", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("ISC", file.NewLocation(fixture)), ), PURL: "pkg:npm/tmp@1.0.0", MetadataType: "NpmPackageLockJsonMetadata", @@ -331,8 +331,8 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) { Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), - pkg.NewLicenseFromLocations("Apache2", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), + pkg.NewLicenseFromLocations("Apache2", file.NewLocation(fixture)), ), PURL: "pkg:npm/pause-stream@0.0.11", MetadataType: "NpmPackageLockJsonMetadata", @@ -344,7 +344,7 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) { Language: pkg.JavaScript, Type: pkg.NpmPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), ), PURL: "pkg:npm/through@2.3.8", MetadataType: "NpmPackageLockJsonMetadata", @@ -352,7 +352,7 @@ func TestParsePackageLockLicenseWithArray(t *testing.T) { }, } for i := range expectedPkgs { - expectedPkgs[i].Locations.Add(source.NewLocation(fixture)) + expectedPkgs[i].Locations.Add(file.NewLocation(fixture)) } pkgtest.TestFileParser(t, fixture, parsePackageLock, expectedPkgs, expectedRelationships) } diff --git a/syft/pkg/cataloger/javascript/parse_pnpm_lock.go b/syft/pkg/cataloger/javascript/parse_pnpm_lock.go index 418f6286285..1b786752e67 100644 --- a/syft/pkg/cataloger/javascript/parse_pnpm_lock.go +++ b/syft/pkg/cataloger/javascript/parse_pnpm_lock.go @@ -11,9 +11,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // integrity check @@ -25,7 +25,7 @@ type pnpmLockYaml struct { Packages map[string]interface{} `json:"packages" yaml:"packages"` } -func parsePnpmLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parsePnpmLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { bytes, err := io.ReadAll(reader) if err != nil { return nil, nil, fmt.Errorf("failed to load pnpm-lock.yaml file: %w", err) diff --git a/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go b/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go index bcf1fe40ad0..7c0ed1c4db8 100644 --- a/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_pnpm_lock_test.go @@ -4,16 +4,16 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParsePnpmLock(t *testing.T) { var expectedRelationships []artifact.Relationship fixture := "test-fixtures/pnpm/pnpm-lock.yaml" - locationSet := source.NewLocationSet(source.NewLocation(fixture)) + locationSet := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { @@ -57,7 +57,7 @@ func TestParsePnpmV6Lock(t *testing.T) { var expectedRelationships []artifact.Relationship fixture := "test-fixtures/pnpm-v6/pnpm-lock.yaml" - locationSet := source.NewLocationSet(source.NewLocation(fixture)) + locationSet := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { diff --git a/syft/pkg/cataloger/javascript/parse_yarn_lock.go b/syft/pkg/cataloger/javascript/parse_yarn_lock.go index 048f8f05c76..a90392fe2c1 100644 --- a/syft/pkg/cataloger/javascript/parse_yarn_lock.go +++ b/syft/pkg/cataloger/javascript/parse_yarn_lock.go @@ -7,9 +7,9 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // integrity check @@ -42,7 +42,7 @@ const ( noVersion = "" ) -func parseYarnLock(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseYarnLock(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { // in the case we find yarn.lock files in the node_modules directories, skip those // as the whole purpose of the lock file is for the specific dependencies of the project if pathContainsNodeModulesDirectory(reader.AccessPath()) { diff --git a/syft/pkg/cataloger/javascript/parse_yarn_lock_test.go b/syft/pkg/cataloger/javascript/parse_yarn_lock_test.go index ded8850b1f4..cb2dacc407c 100644 --- a/syft/pkg/cataloger/javascript/parse_yarn_lock_test.go +++ b/syft/pkg/cataloger/javascript/parse_yarn_lock_test.go @@ -6,15 +6,15 @@ import ( "github.com/stretchr/testify/assert" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseYarnBerry(t *testing.T) { var expectedRelationships []artifact.Relationship fixture := "test-fixtures/yarn-berry/yarn.lock" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { @@ -98,7 +98,7 @@ func TestParseYarnBerry(t *testing.T) { func TestParseYarnLock(t *testing.T) { var expectedRelationships []artifact.Relationship fixture := "test-fixtures/yarn/yarn.lock" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { diff --git a/syft/pkg/cataloger/kernel/cataloger.go b/syft/pkg/cataloger/kernel/cataloger.go index 492c2043364..67c5bb5b727 100644 --- a/syft/pkg/cataloger/kernel/cataloger.go +++ b/syft/pkg/cataloger/kernel/cataloger.go @@ -8,9 +8,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ pkg.Cataloger = (*LinuxKernelCataloger)(nil) @@ -53,7 +53,7 @@ func (l LinuxKernelCataloger) Name() string { return "linux-kernel-cataloger" } -func (l LinuxKernelCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (l LinuxKernelCataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { var allPackages []pkg.Package var allRelationships []artifact.Relationship var errs error diff --git a/syft/pkg/cataloger/kernel/cataloger_test.go b/syft/pkg/cataloger/kernel/cataloger_test.go index b223acf1a49..f819e605a45 100644 --- a/syft/pkg/cataloger/kernel/cataloger_test.go +++ b/syft/pkg/cataloger/kernel/cataloger_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" "github.com/anchore/syft/syft/source" @@ -14,8 +15,8 @@ func Test_KernelCataloger(t *testing.T) { Name: "linux-kernel", Version: "6.0.7-301.fc37.x86_64", FoundBy: "linux-kernel-cataloger", - Locations: source.NewLocationSet( - source.NewVirtualLocation( + Locations: file.NewLocationSet( + file.NewVirtualLocation( "/lib/modules/6.0.7-301.fc37.x86_64/vmlinuz", "/lib/modules/6.0.7-301.fc37.x86_64/vmlinuz", ), @@ -42,8 +43,8 @@ func Test_KernelCataloger(t *testing.T) { Name: "ttynull", Version: "", FoundBy: "linux-kernel-cataloger", - Locations: source.NewLocationSet( - source.NewVirtualLocation("/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko", + Locations: file.NewLocationSet( + file.NewVirtualLocation("/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko", "/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko", ), ), diff --git a/syft/pkg/cataloger/kernel/package.go b/syft/pkg/cataloger/kernel/package.go index 3ea60668827..92dcb5ef14a 100644 --- a/syft/pkg/cataloger/kernel/package.go +++ b/syft/pkg/cataloger/kernel/package.go @@ -4,17 +4,17 @@ import ( "strings" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) const linuxKernelPackageName = "linux-kernel" -func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, archiveLocation source.Location) pkg.Package { +func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, archiveLocation file.Location) pkg.Package { p := pkg.Package{ Name: linuxKernelPackageName, Version: metadata.Version, - Locations: source.NewLocationSet(archiveLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(archiveLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), PURL: packageURL(linuxKernelPackageName, metadata.Version), Type: pkg.LinuxKernelPkg, MetadataType: pkg.LinuxKernelMetadataType, @@ -26,11 +26,11 @@ func newLinuxKernelPackage(metadata pkg.LinuxKernelMetadata, archiveLocation sou return p } -func newLinuxKernelModulePackage(metadata pkg.LinuxKernelModuleMetadata, kmLocation source.Location) pkg.Package { +func newLinuxKernelModulePackage(metadata pkg.LinuxKernelModuleMetadata, kmLocation file.Location) pkg.Package { p := pkg.Package{ Name: metadata.Name, Version: metadata.Version, - Locations: source.NewLocationSet(kmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(kmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(kmLocation, metadata.License)...), PURL: packageURL(metadata.Name, metadata.Version), Type: pkg.LinuxKernelModulePkg, diff --git a/syft/pkg/cataloger/kernel/parse_linux_kernel_file.go b/syft/pkg/cataloger/kernel/parse_linux_kernel_file.go index 0be32c5b658..54c26eb4297 100644 --- a/syft/pkg/cataloger/kernel/parse_linux_kernel_file.go +++ b/syft/pkg/cataloger/kernel/parse_linux_kernel_file.go @@ -9,15 +9,15 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" - "github.com/anchore/syft/syft/source" ) const linuxKernelMagicName = "Linux kernel" -func parseLinuxKernelFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseLinuxKernelFile(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { unionReader, err := unionreader.GetUnionReader(reader) if err != nil { return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err) diff --git a/syft/pkg/cataloger/kernel/parse_linux_kernel_module_file.go b/syft/pkg/cataloger/kernel/parse_linux_kernel_module_file.go index 3adeb5632f7..34974f6272c 100644 --- a/syft/pkg/cataloger/kernel/parse_linux_kernel_module_file.go +++ b/syft/pkg/cataloger/kernel/parse_linux_kernel_module_file.go @@ -6,15 +6,15 @@ import ( "strings" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" - "github.com/anchore/syft/syft/source" ) const modinfoName = ".modinfo" -func parseLinuxKernelModuleFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseLinuxKernelModuleFile(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { unionReader, err := unionreader.GetUnionReader(reader) if err != nil { return nil, nil, fmt.Errorf("unable to get union reader for file: %w", err) diff --git a/syft/pkg/cataloger/nix/cataloger.go b/syft/pkg/cataloger/nix/cataloger.go index b4b440c2687..5d920f2300c 100644 --- a/syft/pkg/cataloger/nix/cataloger.go +++ b/syft/pkg/cataloger/nix/cataloger.go @@ -7,8 +7,8 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) const ( @@ -27,10 +27,10 @@ func (c *StoreCataloger) Name() string { return catalogerName } -func (c *StoreCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) { +func (c *StoreCataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) { // we want to search for only directories, which isn't possible via the stereoscope API, so we need to apply the glob manually on all returned paths var pkgs []pkg.Package - var filesByPath = make(map[string]*source.LocationSet) + var filesByPath = make(map[string]*file.LocationSet) for location := range resolver.AllLocations() { matchesStorePath, err := doublestar.Match(nixStoreGlob, location.RealPath) if err != nil { @@ -40,7 +40,7 @@ func (c *StoreCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, [ parentStorePath := findParentNixStorePath(location.RealPath) if parentStorePath != "" { if _, ok := filesByPath[parentStorePath]; !ok { - s := source.NewLocationSet() + s := file.NewLocationSet() filesByPath[parentStorePath] = &s } filesByPath[parentStorePath].Add(location) @@ -80,7 +80,7 @@ func (c *StoreCataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, [ return pkgs, nil, nil } -func appendFiles(p *pkg.Package, location ...source.Location) { +func appendFiles(p *pkg.Package, location ...file.Location) { metadata, ok := p.Metadata.(pkg.NixStoreMetadata) if !ok { log.WithFields("package", p.Name).Warn("nix package metadata missing") diff --git a/syft/pkg/cataloger/nix/cataloger_test.go b/syft/pkg/cataloger/nix/cataloger_test.go index 10b544fc056..f43babde93b 100644 --- a/syft/pkg/cataloger/nix/cataloger_test.go +++ b/syft/pkg/cataloger/nix/cataloger_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestCataloger_Catalog(t *testing.T) { @@ -23,7 +23,7 @@ func TestCataloger_Catalog(t *testing.T) { Name: "glibc", Version: "2.34-210", PURL: "pkg:nix/glibc@2.34-210?output=bin&outputhash=h0cnbmfcn93xm5dg2x27ixhag1cwndga", - Locations: source.NewLocationSet(source.NewLocation("nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin")), + Locations: file.NewLocationSet(file.NewLocation("nix/store/h0cnbmfcn93xm5dg2x27ixhag1cwndga-glibc-2.34-210-bin")), FoundBy: catalogerName, Type: pkg.NixPkg, MetadataType: pkg.NixStoreMetadataType, diff --git a/syft/pkg/cataloger/nix/package.go b/syft/pkg/cataloger/nix/package.go index 6e473d6fdcd..090dfe1379c 100644 --- a/syft/pkg/cataloger/nix/package.go +++ b/syft/pkg/cataloger/nix/package.go @@ -2,16 +2,16 @@ package nix import ( "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newNixStorePackage(storePath nixStorePath, locations ...source.Location) pkg.Package { +func newNixStorePackage(storePath nixStorePath, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: storePath.name, Version: storePath.version, FoundBy: catalogerName, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), Type: pkg.NixPkg, PURL: packageURL(storePath), MetadataType: pkg.NixStoreMetadataType, diff --git a/syft/pkg/cataloger/php/package.go b/syft/pkg/cataloger/php/package.go index 507fd26dab2..7255d58d53e 100644 --- a/syft/pkg/cataloger/php/package.go +++ b/syft/pkg/cataloger/php/package.go @@ -4,15 +4,15 @@ import ( "strings" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newComposerLockPackage(m parsedData, indexLocation source.Location) pkg.Package { +func newComposerLockPackage(m parsedData, indexLocation file.Location) pkg.Package { p := pkg.Package{ Name: m.Name, Version: m.Version, - Locations: source.NewLocationSet(indexLocation), + Locations: file.NewLocationSet(indexLocation), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(indexLocation, m.License...)...), PURL: packageURL(m), Language: pkg.PHP, diff --git a/syft/pkg/cataloger/php/parse_composer_lock.go b/syft/pkg/cataloger/php/parse_composer_lock.go index 248b7519eb8..836befe138f 100644 --- a/syft/pkg/cataloger/php/parse_composer_lock.go +++ b/syft/pkg/cataloger/php/parse_composer_lock.go @@ -7,9 +7,9 @@ import ( "io" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseComposerLock @@ -25,7 +25,7 @@ type composerLock struct { } // parseComposerLock is a parser function for Composer.lock contents, returning "Default" php packages discovered. -func parseComposerLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseComposerLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { pkgs := make([]pkg.Package, 0) dec := json.NewDecoder(reader) diff --git a/syft/pkg/cataloger/php/parse_composer_lock_test.go b/syft/pkg/cataloger/php/parse_composer_lock_test.go index ad7814a97d5..f1038a5d4c3 100644 --- a/syft/pkg/cataloger/php/parse_composer_lock_test.go +++ b/syft/pkg/cataloger/php/parse_composer_lock_test.go @@ -4,15 +4,15 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseComposerFileLock(t *testing.T) { var expectedRelationships []artifact.Relationship fixture := "test-fixtures/composer.lock" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { Name: "adoy/fastcgi-client", @@ -20,7 +20,7 @@ func TestParseComposerFileLock(t *testing.T) { PURL: "pkg:composer/adoy/fastcgi-client@1.0.2", Locations: locations, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), ), Language: pkg.PHP, Type: pkg.PhpComposerPkg, @@ -61,7 +61,7 @@ func TestParseComposerFileLock(t *testing.T) { PURL: "pkg:composer/alcaeus/mongo-php-adapter@1.1.11", Language: pkg.PHP, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), ), Type: pkg.PhpComposerPkg, MetadataType: pkg.PhpComposerJSONMetadataType, diff --git a/syft/pkg/cataloger/php/parse_installed_json.go b/syft/pkg/cataloger/php/parse_installed_json.go index 8c1213200f4..060e01903e9 100644 --- a/syft/pkg/cataloger/php/parse_installed_json.go +++ b/syft/pkg/cataloger/php/parse_installed_json.go @@ -7,9 +7,9 @@ import ( "io" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseComposerLock @@ -41,7 +41,7 @@ func (w *installedJSONComposerV2) UnmarshalJSON(data []byte) error { } // parseInstalledJSON is a parser function for Composer.lock contents, returning "Default" php packages discovered. -func parseInstalledJSON(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseInstalledJSON(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package dec := json.NewDecoder(reader) diff --git a/syft/pkg/cataloger/php/parse_installed_json_test.go b/syft/pkg/cataloger/php/parse_installed_json_test.go index dde72021eaf..984856ed497 100644 --- a/syft/pkg/cataloger/php/parse_installed_json_test.go +++ b/syft/pkg/cataloger/php/parse_installed_json_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseInstalledJsonComposerV1(t *testing.T) { @@ -130,7 +130,7 @@ func TestParseInstalledJsonComposerV1(t *testing.T) { for _, fixture := range fixtures { t.Run(fixture, func(t *testing.T) { - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) for i := range expectedPkgs { expectedPkgs[i].Locations = locations locationLicenses := pkg.NewLicenseSet() diff --git a/syft/pkg/cataloger/portage/cataloger_test.go b/syft/pkg/cataloger/portage/cataloger_test.go index b2ff5f26d17..c556c940a14 100644 --- a/syft/pkg/cataloger/portage/cataloger_test.go +++ b/syft/pkg/cataloger/portage/cataloger_test.go @@ -7,20 +7,19 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestPortageCataloger(t *testing.T) { - expectedLicenseLocation := source.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE") + expectedLicenseLocation := file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/LICENSE") expectedPkgs := []pkg.Package{ { Name: "app-containers/skopeo", Version: "1.5.1", FoundBy: "portage-cataloger", PURL: "pkg:ebuild/app-containers/skopeo@1.5.1", - Locations: source.NewLocationSet( - source.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS"), - source.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/SIZE"), + Locations: file.NewLocationSet( + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/CONTENTS"), + file.NewLocation("var/db/pkg/app-containers/skopeo-1.5.1/SIZE"), expectedLicenseLocation, ), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(expectedLicenseLocation, "Apache-2.0", "BSD", "BSD-2", "CC-BY-SA-4.0", "ISC", "MIT")...), diff --git a/syft/pkg/cataloger/portage/parse_portage_contents.go b/syft/pkg/cataloger/portage/parse_portage_contents.go index ac93c6ea05e..941cce394bb 100644 --- a/syft/pkg/cataloger/portage/parse_portage_contents.go +++ b/syft/pkg/cataloger/portage/parse_portage_contents.go @@ -15,7 +15,6 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var ( @@ -23,7 +22,7 @@ var ( _ generic.Parser = parsePortageContents ) -func parsePortageContents(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parsePortageContents(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { cpvMatch := cpvRe.FindStringSubmatch(reader.Location.RealPath) if cpvMatch == nil { return nil, nil, fmt.Errorf("failed to match package and version in %s", reader.Location.RealPath) @@ -39,7 +38,7 @@ func parsePortageContents(resolver source.FileResolver, _ *generic.Environment, Name: name, Version: version, PURL: packageURL(name, version), - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ), Type: pkg.PortagePkg, @@ -58,7 +57,7 @@ func parsePortageContents(resolver source.FileResolver, _ *generic.Environment, return []pkg.Package{p}, nil, nil } -func addFiles(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) { +func addFiles(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { contentsReader, err := resolver.FileContentsByLocation(dbLocation) if err != nil { log.WithFields("path", dbLocation.RealPath).Warnf("failed to fetch portage contents (package=%s): %+v", p.Name, err) @@ -91,7 +90,7 @@ func addFiles(resolver source.FileResolver, dbLocation source.Location, p *pkg.P p.Locations.Add(dbLocation) } -func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) { +func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { parentPath := filepath.Dir(dbLocation.RealPath) location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "LICENSE")) @@ -121,7 +120,7 @@ func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pk p.Locations.Add(location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.SupportingEvidenceAnnotation)) } -func addSize(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) { +func addSize(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) { parentPath := filepath.Dir(dbLocation.RealPath) location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "SIZE")) diff --git a/syft/pkg/cataloger/python/cataloger_test.go b/syft/pkg/cataloger/python/cataloger_test.go index 10522f21524..da15f299313 100644 --- a/syft/pkg/cataloger/python/cataloger_test.go +++ b/syft/pkg/cataloger/python/cataloger_test.go @@ -5,9 +5,9 @@ import ( "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func Test_PackageCataloger(t *testing.T) { @@ -46,7 +46,7 @@ func Test_PackageCataloger(t *testing.T) { Type: pkg.PythonPkg, Language: pkg.Python, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("Apache 2.0", source.NewLocation("test-fixtures/egg-info/PKG-INFO")), + pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/egg-info/PKG-INFO")), ), FoundBy: "python-package-cataloger", MetadataType: pkg.PythonPackageMetadataType, @@ -84,7 +84,7 @@ func Test_PackageCataloger(t *testing.T) { Type: pkg.PythonPkg, Language: pkg.Python, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("BSD License", source.NewLocation("test-fixtures/dist-info/METADATA")), + pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/dist-info/METADATA")), ), FoundBy: "python-package-cataloger", MetadataType: pkg.PythonPackageMetadataType, @@ -122,7 +122,7 @@ func Test_PackageCataloger(t *testing.T) { Type: pkg.PythonPkg, Language: pkg.Python, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("BSD License", source.NewLocation("test-fixtures/malformed-record/dist-info/METADATA")), + pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/malformed-record/dist-info/METADATA")), ), FoundBy: "python-package-cataloger", MetadataType: pkg.PythonPackageMetadataType, @@ -154,7 +154,7 @@ func Test_PackageCataloger(t *testing.T) { Type: pkg.PythonPkg, Language: pkg.Python, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("BSD License", source.NewLocation("test-fixtures/partial.dist-info/METADATA")), + pkg.NewLicenseFromLocations("BSD License", file.NewLocation("test-fixtures/partial.dist-info/METADATA")), ), FoundBy: "python-package-cataloger", MetadataType: pkg.PythonPackageMetadataType, @@ -178,7 +178,7 @@ func Test_PackageCataloger(t *testing.T) { Type: pkg.PythonPkg, Language: pkg.Python, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("Apache 2.0", source.NewLocation("test-fixtures/test.egg-info")), + pkg.NewLicenseFromLocations("Apache 2.0", file.NewLocation("test-fixtures/test.egg-info")), ), FoundBy: "python-package-cataloger", MetadataType: pkg.PythonPackageMetadataType, @@ -196,12 +196,12 @@ func Test_PackageCataloger(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { - resolver := source.NewMockResolverForPaths(test.fixtures...) + resolver := file.NewMockResolverForPaths(test.fixtures...) locations, err := resolver.FilesByPath(test.fixtures...) require.NoError(t, err) - test.expectedPackage.Locations = source.NewLocationSet(locations...) + test.expectedPackage.Locations = file.NewLocationSet(locations...) pkgtest.NewCatalogTester(). WithResolver(resolver). @@ -225,7 +225,7 @@ func Test_PackageCataloger_IgnorePackage(t *testing.T) { for _, test := range tests { t.Run(test.MetadataFixture, func(t *testing.T) { - resolver := source.NewMockResolverForPaths(test.MetadataFixture) + resolver := file.NewMockResolverForPaths(test.MetadataFixture) actual, _, err := NewPythonPackageCataloger().Catalog(resolver) require.NoError(t, err) diff --git a/syft/pkg/cataloger/python/package.go b/syft/pkg/cataloger/python/package.go index 68f7f1dccfe..e20f878601f 100644 --- a/syft/pkg/cataloger/python/package.go +++ b/syft/pkg/cataloger/python/package.go @@ -4,15 +4,15 @@ import ( "fmt" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackageForIndex(name, version string, locations ...source.Location) pkg.Package { +func newPackageForIndex(name, version string, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(name, version, nil), Language: pkg.Python, Type: pkg.PythonPkg, @@ -23,11 +23,11 @@ func newPackageForIndex(name, version string, locations ...source.Location) pkg. return p } -func newPackageForIndexWithMetadata(name, version string, metadata pkg.PythonPipfileLockMetadata, locations ...source.Location) pkg.Package { +func newPackageForIndexWithMetadata(name, version string, metadata pkg.PythonPipfileLockMetadata, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(name, version, nil), Language: pkg.Python, Type: pkg.PythonPkg, @@ -40,11 +40,11 @@ func newPackageForIndexWithMetadata(name, version string, metadata pkg.PythonPip return p } -func newPackageForRequirementsWithMetadata(name, version string, metadata pkg.PythonRequirementsMetadata, locations ...source.Location) pkg.Package { +func newPackageForRequirementsWithMetadata(name, version string, metadata pkg.PythonRequirementsMetadata, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: name, Version: version, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(name, version, nil), Language: pkg.Python, Type: pkg.PythonPkg, @@ -57,12 +57,12 @@ func newPackageForRequirementsWithMetadata(name, version string, metadata pkg.Py return p } -func newPackageForPackage(m parsedData, sources ...source.Location) pkg.Package { +func newPackageForPackage(m parsedData, sources ...file.Location) pkg.Package { p := pkg.Package{ Name: m.Name, Version: m.Version, PURL: packageURL(m.Name, m.Version, &m.PythonPackageMetadata), - Locations: source.NewLocationSet(sources...), + Locations: file.NewLocationSet(sources...), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(m.LicenseLocation, m.Licenses)...), Language: pkg.Python, Type: pkg.PythonPkg, diff --git a/syft/pkg/cataloger/python/parse_pipfile_lock.go b/syft/pkg/cataloger/python/parse_pipfile_lock.go index c957405a647..77c8cd4fe8a 100644 --- a/syft/pkg/cataloger/python/parse_pipfile_lock.go +++ b/syft/pkg/cataloger/python/parse_pipfile_lock.go @@ -8,9 +8,9 @@ import ( "strings" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) type pipfileLock struct { @@ -41,7 +41,7 @@ type Dependency struct { var _ generic.Parser = parsePipfileLock // parsePipfileLock is a parser function for Pipfile.lock contents, returning "Default" python packages discovered. -func parsePipfileLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parsePipfileLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { pkgs := make([]pkg.Package, 0) dec := json.NewDecoder(reader) diff --git a/syft/pkg/cataloger/python/parse_pipfile_lock_test.go b/syft/pkg/cataloger/python/parse_pipfile_lock_test.go index 15b327845db..783c7dfd0ec 100644 --- a/syft/pkg/cataloger/python/parse_pipfile_lock_test.go +++ b/syft/pkg/cataloger/python/parse_pipfile_lock_test.go @@ -4,15 +4,15 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParsePipFileLock(t *testing.T) { fixture := "test-fixtures/pipfile-lock/Pipfile.lock" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { Name: "aio-pika", diff --git a/syft/pkg/cataloger/python/parse_poetry_lock.go b/syft/pkg/cataloger/python/parse_poetry_lock.go index 0e29de0178c..4bc929cbee4 100644 --- a/syft/pkg/cataloger/python/parse_poetry_lock.go +++ b/syft/pkg/cataloger/python/parse_poetry_lock.go @@ -6,9 +6,9 @@ import ( "github.com/pelletier/go-toml" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // integrity check @@ -25,7 +25,7 @@ type poetryMetadata struct { } // parsePoetryLock is a parser function for poetry.lock contents, returning all python packages discovered. -func parsePoetryLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parsePoetryLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { tree, err := toml.LoadReader(reader) if err != nil { return nil, nil, fmt.Errorf("unable to load poetry.lock for parsing: %w", err) diff --git a/syft/pkg/cataloger/python/parse_poetry_lock_test.go b/syft/pkg/cataloger/python/parse_poetry_lock_test.go index 0a3478e1bdf..fd6d1bdc805 100644 --- a/syft/pkg/cataloger/python/parse_poetry_lock_test.go +++ b/syft/pkg/cataloger/python/parse_poetry_lock_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParsePoetryLock(t *testing.T) { fixture := "test-fixtures/poetry/poetry.lock" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { Name: "added-value", diff --git a/syft/pkg/cataloger/python/parse_requirements.go b/syft/pkg/cataloger/python/parse_requirements.go index c2b5a122a0a..33e1371b07b 100644 --- a/syft/pkg/cataloger/python/parse_requirements.go +++ b/syft/pkg/cataloger/python/parse_requirements.go @@ -9,9 +9,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseRequirementsTxt @@ -23,7 +23,7 @@ var ( // parseRequirementsTxt takes a Python requirements.txt file, returning all Python packages that are locked to a // specific version. -func parseRequirementsTxt(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseRequirementsTxt(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var packages []pkg.Package scanner := bufio.NewScanner(reader) diff --git a/syft/pkg/cataloger/python/parse_requirements_test.go b/syft/pkg/cataloger/python/parse_requirements_test.go index b25179c5070..b38cae3d306 100644 --- a/syft/pkg/cataloger/python/parse_requirements_test.go +++ b/syft/pkg/cataloger/python/parse_requirements_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseRequirementsTxt(t *testing.T) { fixture := "test-fixtures/requires/requirements.txt" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { Name: "flask", diff --git a/syft/pkg/cataloger/python/parse_setup.go b/syft/pkg/cataloger/python/parse_setup.go index ee91f6ada2a..e5150b2743c 100644 --- a/syft/pkg/cataloger/python/parse_setup.go +++ b/syft/pkg/cataloger/python/parse_setup.go @@ -7,9 +7,9 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // integrity check @@ -22,7 +22,7 @@ var _ generic.Parser = parseSetup // " mypy2 == v0.770", ' mypy3== v0.770', --> match(name=mypy2 version=v0.770), match(name=mypy3, version=v0.770) var pinnedDependency = regexp.MustCompile(`['"]\W?(\w+\W?==\W?[\w.]*)`) -func parseSetup(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseSetup(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var packages []pkg.Package scanner := bufio.NewScanner(reader) diff --git a/syft/pkg/cataloger/python/parse_setup_test.go b/syft/pkg/cataloger/python/parse_setup_test.go index a3fdfd85b33..66500729631 100644 --- a/syft/pkg/cataloger/python/parse_setup_test.go +++ b/syft/pkg/cataloger/python/parse_setup_test.go @@ -6,9 +6,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseSetup(t *testing.T) { @@ -65,7 +65,7 @@ func TestParseSetup(t *testing.T) { for _, tt := range tests { t.Run(tt.fixture, func(t *testing.T) { - locations := source.NewLocationSet(source.NewLocation(tt.fixture)) + locations := file.NewLocationSet(file.NewLocation(tt.fixture)) for i := range tt.expected { tt.expected[i].Locations = locations } diff --git a/syft/pkg/cataloger/python/parse_wheel_egg.go b/syft/pkg/cataloger/python/parse_wheel_egg.go index 911e7801ca5..f3fc20ead54 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg.go @@ -10,13 +10,13 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // parseWheelOrEgg takes the primary metadata file reference and returns the python package it represents. -func parseWheelOrEgg(resolver source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseWheelOrEgg(resolver file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { pd, sources, err := assembleEggOrWheelMetadata(resolver, reader.Location) if err != nil { return nil, nil, err @@ -37,7 +37,7 @@ func parseWheelOrEgg(resolver source.FileResolver, _ *generic.Environment, reade } // fetchRecordFiles finds a corresponding installed-files.txt file for the given python package metadata file and returns the set of file records contained. -func fetchInstalledFiles(resolver source.FileResolver, metadataLocation source.Location, sitePackagesRootPath string) (files []pkg.PythonFileRecord, sources []source.Location, err error) { +func fetchInstalledFiles(resolver file.Resolver, metadataLocation file.Location, sitePackagesRootPath string) (files []pkg.PythonFileRecord, sources []file.Location, err error) { // we've been given a file reference to a specific wheel METADATA file. note: this may be for a directory // or for an image... for an image the METADATA file may be present within multiple layers, so it is important // to reconcile the installed-files.txt path to the same layer (or the next adjacent lower layer). @@ -68,7 +68,7 @@ func fetchInstalledFiles(resolver source.FileResolver, metadataLocation source.L } // fetchRecordFiles finds a corresponding RECORD file for the given python package metadata file and returns the set of file records contained. -func fetchRecordFiles(resolver source.FileResolver, metadataLocation source.Location) (files []pkg.PythonFileRecord, sources []source.Location, err error) { +func fetchRecordFiles(resolver file.Resolver, metadataLocation file.Location) (files []pkg.PythonFileRecord, sources []file.Location, err error) { // we've been given a file reference to a specific wheel METADATA file. note: this may be for a directory // or for an image... for an image the METADATA file may be present within multiple layers, so it is important // to reconcile the RECORD path to the same layer (or the next adjacent lower layer). @@ -95,7 +95,7 @@ func fetchRecordFiles(resolver source.FileResolver, metadataLocation source.Loca } // fetchTopLevelPackages finds a corresponding top_level.txt file for the given python package metadata file and returns the set of package names contained. -func fetchTopLevelPackages(resolver source.FileResolver, metadataLocation source.Location) (pkgs []string, sources []source.Location, err error) { +func fetchTopLevelPackages(resolver file.Resolver, metadataLocation file.Location) (pkgs []string, sources []file.Location, err error) { // a top_level.txt file specifies the python top-level packages (provided by this python package) installed into site-packages parentDir := filepath.Dir(metadataLocation.RealPath) topLevelPath := filepath.Join(parentDir, "top_level.txt") @@ -125,7 +125,7 @@ func fetchTopLevelPackages(resolver source.FileResolver, metadataLocation source return pkgs, sources, nil } -func fetchDirectURLData(resolver source.FileResolver, metadataLocation source.Location) (d *pkg.PythonDirectURLOriginInfo, sources []source.Location, err error) { +func fetchDirectURLData(resolver file.Resolver, metadataLocation file.Location) (d *pkg.PythonDirectURLOriginInfo, sources []file.Location, err error) { parentDir := filepath.Dir(metadataLocation.RealPath) directURLPath := filepath.Join(parentDir, "direct_url.json") directURLLocation := resolver.RelativeFileByPath(metadataLocation, directURLPath) @@ -160,8 +160,8 @@ func fetchDirectURLData(resolver source.FileResolver, metadataLocation source.Lo } // assembleEggOrWheelMetadata discovers and accumulates python package metadata from multiple file sources and returns a single metadata object as well as a list of files where the metadata was derived from. -func assembleEggOrWheelMetadata(resolver source.FileResolver, metadataLocation source.Location) (*parsedData, []source.Location, error) { - var sources = []source.Location{ +func assembleEggOrWheelMetadata(resolver file.Resolver, metadataLocation file.Location) (*parsedData, []file.Location, error) { + var sources = []file.Location{ metadataLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), } diff --git a/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go b/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go index 55ac924f002..e8d2cafafbf 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg_metadata.go @@ -9,15 +9,15 @@ import ( "github.com/mitchellh/mapstructure" - "github.com/anchore/syft/internal/file" + intFile "github.com/anchore/syft/internal/file" "github.com/anchore/syft/internal/log" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) type parsedData struct { Licenses string `mapstructure:"License"` - LicenseLocation source.Location + LicenseLocation file.Location pkg.PythonPackageMetadata `mapstructure:",squash"` } @@ -81,7 +81,7 @@ func parseWheelOrEggMetadata(path string, reader io.Reader) (parsedData, error) pd.SitePackagesRootPath = determineSitePackagesRootPath(path) if pd.Licenses != "" { - pd.LicenseLocation = source.NewLocation(path) + pd.LicenseLocation = file.NewLocation(path) } return pd, nil @@ -91,7 +91,7 @@ func parseWheelOrEggMetadata(path string, reader io.Reader) (parsedData, error) // of egg metadata (as opposed to a directory that contains more metadata // files). func isEggRegularFile(path string) bool { - return file.GlobMatch(eggInfoGlob, path) + return intFile.GlobMatch(eggInfoGlob, path) } // determineSitePackagesRootPath returns the path of the site packages root, diff --git a/syft/pkg/cataloger/python/parse_wheel_egg_metadata_test.go b/syft/pkg/cataloger/python/parse_wheel_egg_metadata_test.go index cb776b66937..e9db5446667 100644 --- a/syft/pkg/cataloger/python/parse_wheel_egg_metadata_test.go +++ b/syft/pkg/cataloger/python/parse_wheel_egg_metadata_test.go @@ -6,8 +6,8 @@ import ( "github.com/go-test/deep" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) func TestParseWheelEggMetadata(t *testing.T) { @@ -19,7 +19,7 @@ func TestParseWheelEggMetadata(t *testing.T) { Fixture: "test-fixtures/egg-info/PKG-INFO", ExpectedMetadata: parsedData{ "Apache 2.0", - source.NewLocation("test-fixtures/egg-info/PKG-INFO"), + file.NewLocation("test-fixtures/egg-info/PKG-INFO"), pkg.PythonPackageMetadata{ Name: "requests", Version: "2.22.0", @@ -34,7 +34,7 @@ func TestParseWheelEggMetadata(t *testing.T) { Fixture: "test-fixtures/dist-info/METADATA", ExpectedMetadata: parsedData{ "BSD License", - source.NewLocation("test-fixtures/dist-info/METADATA"), + file.NewLocation("test-fixtures/dist-info/METADATA"), pkg.PythonPackageMetadata{ Name: "Pygments", Version: "2.6.1", @@ -135,7 +135,7 @@ func TestParseWheelEggMetadataInvalid(t *testing.T) { Fixture: "test-fixtures/egg-info/PKG-INFO-INVALID", ExpectedMetadata: parsedData{ "", - source.Location{}, + file.Location{}, pkg.PythonPackageMetadata{ Name: "mxnet", Version: "1.8.0", diff --git a/syft/pkg/cataloger/r/cataloger_test.go b/syft/pkg/cataloger/r/cataloger_test.go index 1581e8dc657..0e2a193d6e0 100644 --- a/syft/pkg/cataloger/r/cataloger_test.go +++ b/syft/pkg/cataloger/r/cataloger_test.go @@ -4,9 +4,9 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestRPackageCataloger(t *testing.T) { @@ -15,7 +15,7 @@ func TestRPackageCataloger(t *testing.T) { Name: "base", Version: "4.3.0", FoundBy: "r-package-cataloger", - Locations: source.NewLocationSet(source.NewLocation("base/DESCRIPTION")), + Locations: file.NewLocationSet(file.NewLocation("base/DESCRIPTION")), Licenses: pkg.NewLicenseSet([]pkg.License{pkg.NewLicense("Part of R 4.3.0")}...), Language: pkg.R, Type: pkg.Rpkg, @@ -34,7 +34,7 @@ func TestRPackageCataloger(t *testing.T) { Name: "stringr", Version: "1.5.0.9000", FoundBy: "r-package-cataloger", - Locations: source.NewLocationSet(source.NewLocation("stringr/DESCRIPTION")), + Locations: file.NewLocationSet(file.NewLocation("stringr/DESCRIPTION")), Licenses: pkg.NewLicenseSet([]pkg.License{pkg.NewLicense("MIT")}...), Language: pkg.R, Type: pkg.Rpkg, diff --git a/syft/pkg/cataloger/r/package.go b/syft/pkg/cataloger/r/package.go index b916cc9da69..9fc45d3e26a 100644 --- a/syft/pkg/cataloger/r/package.go +++ b/syft/pkg/cataloger/r/package.go @@ -4,12 +4,12 @@ import ( "strings" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(pd parseData, locations ...source.Location) pkg.Package { - locationSet := source.NewLocationSet() +func newPackage(pd parseData, locations ...file.Location) pkg.Package { + locationSet := file.NewLocationSet() for _, loc := range locations { locationSet.Add(loc.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)) } @@ -45,7 +45,7 @@ func packageURL(m parseData) string { // Multiple licences can be specified separated by ‘|’ // (surrounded by spaces) in which case the user can choose any of the above cases. // https://cran.rstudio.com/doc/manuals/r-devel/R-exts.html#Licensing -func parseLicenseData(license string, locations ...source.Location) []pkg.License { +func parseLicenseData(license string, locations ...file.Location) []pkg.License { licenses := make([]pkg.License, 0) // check if multiple licenses are separated by | diff --git a/syft/pkg/cataloger/r/parse_description.go b/syft/pkg/cataloger/r/parse_description.go index b062b039559..182cd4bde2f 100644 --- a/syft/pkg/cataloger/r/parse_description.go +++ b/syft/pkg/cataloger/r/parse_description.go @@ -7,9 +7,9 @@ import ( "strings" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) /* some examples of license strings found in DESCRIPTION files: @@ -28,10 +28,10 @@ License: Part of R 4.3.0 License: Unlimited */ -func parseDescriptionFile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseDescriptionFile(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { values := extractFieldsFromDescriptionFile(reader) m := parseDataFromDescriptionMap(values) - p := newPackage(m, []source.Location{reader.Location}...) + p := newPackage(m, []file.Location{reader.Location}...) if p.Name == "" || p.Version == "" { return nil, nil, nil } diff --git a/syft/pkg/cataloger/r/parse_description_test.go b/syft/pkg/cataloger/r/parse_description_test.go index 4263995240d..483c54adbac 100644 --- a/syft/pkg/cataloger/r/parse_description_test.go +++ b/syft/pkg/cataloger/r/parse_description_test.go @@ -8,8 +8,8 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) func Test_parseDescriptionFile(t *testing.T) { @@ -53,8 +53,8 @@ func Test_parseDescriptionFile(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { f, err := os.Open(tt.fixture) - input := source.LocationReadCloser{ - Location: source.NewLocation(tt.fixture), + input := file.LocationReadCloser{ + Location: file.NewLocation(tt.fixture), ReadCloser: f, } got, _, err := parseDescriptionFile(nil, nil, input) diff --git a/syft/pkg/cataloger/rpm/package.go b/syft/pkg/cataloger/rpm/package.go index 53c0925b906..136af9f5755 100644 --- a/syft/pkg/cataloger/rpm/package.go +++ b/syft/pkg/cataloger/rpm/package.go @@ -8,18 +8,18 @@ import ( rpmdb "github.com/knqyf263/go-rpmdb/pkg" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(dbOrRpmLocation source.Location, pd parsedData, distro *linux.Release) pkg.Package { +func newPackage(dbOrRpmLocation file.Location, pd parsedData, distro *linux.Release) pkg.Package { p := pkg.Package{ Name: pd.Name, Version: toELVersion(pd.RpmMetadata), Licenses: pkg.NewLicenseSet(pd.Licenses...), PURL: packageURL(pd.RpmMetadata, distro), - Locations: source.NewLocationSet(dbOrRpmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(dbOrRpmLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, Metadata: pd.RpmMetadata, @@ -34,7 +34,7 @@ type parsedData struct { pkg.RpmMetadata } -func newParsedDataFromEntry(licenseLocation source.Location, entry rpmdb.PackageInfo, files []pkg.RpmdbFileRecord) parsedData { +func newParsedDataFromEntry(licenseLocation file.Location, entry rpmdb.PackageInfo, files []pkg.RpmdbFileRecord) parsedData { return parsedData{ Licenses: pkg.NewLicensesFromLocation(licenseLocation, entry.License), RpmMetadata: pkg.RpmMetadata{ diff --git a/syft/pkg/cataloger/rpm/parse_rpm.go b/syft/pkg/cataloger/rpm/parse_rpm.go index 6e866c5cabb..06c5f61451b 100644 --- a/syft/pkg/cataloger/rpm/parse_rpm.go +++ b/syft/pkg/cataloger/rpm/parse_rpm.go @@ -11,11 +11,10 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // parseRpm parses a single RPM -func parseRpm(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseRpm(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { rpm, err := rpmutils.ReadRpm(reader) if err != nil { return nil, nil, fmt.Errorf("RPM file found but unable to read: %s (%w)", reader.Location.RealPath, err) diff --git a/syft/pkg/cataloger/rpm/parse_rpm_db.go b/syft/pkg/cataloger/rpm/parse_rpm_db.go index ee4d64b4f02..02106f62c35 100644 --- a/syft/pkg/cataloger/rpm/parse_rpm_db.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_db.go @@ -14,11 +14,10 @@ import ( "github.com/anchore/syft/syft/linux" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // parseRpmDb parses an "Packages" RPM DB and returns the Packages listed within it. -func parseRpmDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseRpmDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { f, err := os.CreateTemp("", internal.ApplicationName+"-rpmdb") if err != nil { return nil, nil, fmt.Errorf("failed to create temp rpmdb file: %w", err) @@ -90,7 +89,7 @@ func toELVersion(metadata pkg.RpmMetadata) string { return fmt.Sprintf("%s-%s", metadata.Version, metadata.Release) } -func extractRpmdbFileRecords(resolver source.FilePathResolver, entry rpmdb.PackageInfo) []pkg.RpmdbFileRecord { +func extractRpmdbFileRecords(resolver file.PathResolver, entry rpmdb.PackageInfo) []pkg.RpmdbFileRecord { var records = make([]pkg.RpmdbFileRecord, 0) files, err := entry.InstalledFiles() diff --git a/syft/pkg/cataloger/rpm/parse_rpm_db_test.go b/syft/pkg/cataloger/rpm/parse_rpm_db_test.go index b58a01744a5..dea087880e5 100644 --- a/syft/pkg/cataloger/rpm/parse_rpm_db_test.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_db_test.go @@ -10,36 +10,35 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) -var _ source.FileResolver = (*rpmdbTestFileResolverMock)(nil) +var _ file.Resolver = (*rpmdbTestFileResolverMock)(nil) type rpmdbTestFileResolverMock struct { ignorePaths bool } -func (r rpmdbTestFileResolverMock) FilesByExtension(extensions ...string) ([]source.Location, error) { +func (r rpmdbTestFileResolverMock) FilesByExtension(extensions ...string) ([]file.Location, error) { panic("not implemented") } -func (r rpmdbTestFileResolverMock) FilesByBasename(filenames ...string) ([]source.Location, error) { +func (r rpmdbTestFileResolverMock) FilesByBasename(filenames ...string) ([]file.Location, error) { panic("not implemented") } -func (r rpmdbTestFileResolverMock) FilesByBasenameGlob(globs ...string) ([]source.Location, error) { +func (r rpmdbTestFileResolverMock) FilesByBasenameGlob(globs ...string) ([]file.Location, error) { panic("not implemented") } -func (r rpmdbTestFileResolverMock) FileContentsByLocation(location source.Location) (io.ReadCloser, error) { +func (r rpmdbTestFileResolverMock) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { panic("not implemented") } -func (r rpmdbTestFileResolverMock) AllLocations() <-chan source.Location { +func (r rpmdbTestFileResolverMock) AllLocations() <-chan file.Location { panic("not implemented") } -func (r rpmdbTestFileResolverMock) FileMetadataByLocation(location source.Location) (source.FileMetadata, error) { +func (r rpmdbTestFileResolverMock) FileMetadataByLocation(location file.Location) (file.Metadata, error) { panic("not implemented") } @@ -53,34 +52,34 @@ func (r rpmdbTestFileResolverMock) HasPath(path string) bool { return !r.ignorePaths } -func (r *rpmdbTestFileResolverMock) FilesByPath(paths ...string) ([]source.Location, error) { +func (r *rpmdbTestFileResolverMock) FilesByPath(paths ...string) ([]file.Location, error) { if r.ignorePaths { // act as if no paths exist return nil, nil } // act as if all files exist - var locations = make([]source.Location, len(paths)) + var locations = make([]file.Location, len(paths)) for i, p := range paths { - locations[i] = source.NewLocation(p) + locations[i] = file.NewLocation(p) } return locations, nil } -func (r *rpmdbTestFileResolverMock) FilesByGlob(...string) ([]source.Location, error) { +func (r *rpmdbTestFileResolverMock) FilesByGlob(...string) ([]file.Location, error) { return nil, fmt.Errorf("not implemented") } -func (r *rpmdbTestFileResolverMock) RelativeFileByPath(source.Location, string) *source.Location { +func (r *rpmdbTestFileResolverMock) RelativeFileByPath(file.Location, string) *file.Location { panic(fmt.Errorf("not implemented")) return nil } -func (r *rpmdbTestFileResolverMock) FilesByMIMEType(...string) ([]source.Location, error) { +func (r *rpmdbTestFileResolverMock) FilesByMIMEType(...string) ([]file.Location, error) { return nil, fmt.Errorf("not implemented") } func TestParseRpmDB(t *testing.T) { - packagesLocation := source.NewLocation("test-fixtures/Packages") + packagesLocation := file.NewLocation("test-fixtures/Packages") tests := []struct { fixture string expected []pkg.Package @@ -95,7 +94,7 @@ func TestParseRpmDB(t *testing.T) { Name: "dive", Version: "0.9.2-1", PURL: "pkg:rpm/dive@0.9.2-1?arch=x86_64&upstream=dive-0.9.2-1.src.rpm", - Locations: source.NewLocationSet(packagesLocation), + Locations: file.NewLocationSet(file.NewLocation("test-fixtures/Packages")), Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, Licenses: pkg.NewLicenseSet( @@ -124,7 +123,7 @@ func TestParseRpmDB(t *testing.T) { Name: "dive", Version: "0.9.2-1", PURL: "pkg:rpm/dive@0.9.2-1?arch=x86_64&upstream=dive-0.9.2-1.src.rpm", - Locations: source.NewLocationSet(packagesLocation), + Locations: file.NewLocationSet(packagesLocation), Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, Licenses: pkg.NewLicenseSet( diff --git a/syft/pkg/cataloger/rpm/parse_rpm_manifest.go b/syft/pkg/cataloger/rpm/parse_rpm_manifest.go index ee8de71c6ba..c8110d6dab4 100644 --- a/syft/pkg/cataloger/rpm/parse_rpm_manifest.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_manifest.go @@ -8,13 +8,13 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) // Parses an RPM manifest file, as used in Mariner distroless containers, and returns the Packages listed -func parseRpmManifest(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseRpmManifest(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { r := bufio.NewReader(reader) allPkgs := make([]pkg.Package, 0) @@ -52,7 +52,7 @@ func parseRpmManifest(_ source.FileResolver, _ *generic.Environment, reader sour // Each line is the output of : // rpm --query --all --query-format "%{NAME}\t%{VERSION}-%{RELEASE}\t%{INSTALLTIME}\t%{BUILDTIME}\t%{VENDOR}\t%{EPOCH}\t%{SIZE}\t%{ARCH}\t%{EPOCHNUM}\t%{SOURCERPM}\n" // https://github.com/microsoft/CBL-Mariner/blob/3df18fac373aba13a54bd02466e64969574f13af/toolkit/docs/how_it_works/5_misc.md?plain=1#L150 -func parseRpmManifestEntry(entry string, location source.Location) (*pkg.Package, error) { +func parseRpmManifestEntry(entry string, location file.Location) (*pkg.Package, error) { metadata, err := newMetadataFromManifestLine(entry) if err != nil { return nil, err diff --git a/syft/pkg/cataloger/rpm/parse_rpm_manifest_test.go b/syft/pkg/cataloger/rpm/parse_rpm_manifest_test.go index 64cca390aab..5f2c3e3b7b3 100644 --- a/syft/pkg/cataloger/rpm/parse_rpm_manifest_test.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_manifest_test.go @@ -3,20 +3,20 @@ package rpm import ( "testing" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseRpmManifest(t *testing.T) { fixture := "test-fixtures/container-manifest-2" - location := source.NewLocation(fixture) + location := file.NewLocation(fixture) expected := []pkg.Package{ { Name: "mariner-release", Version: "2.0-12.cm2", PURL: "pkg:rpm/mariner-release@2.0-12.cm2?arch=noarch&upstream=mariner-release-2.0-12.cm2.src.rpm", - Locations: source.NewLocationSet(location), + Locations: file.NewLocationSet(location), Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ @@ -34,7 +34,7 @@ func TestParseRpmManifest(t *testing.T) { Name: "filesystem", Version: "1.1-9.cm2", PURL: "pkg:rpm/filesystem@1.1-9.cm2?arch=x86_64&upstream=filesystem-1.1-9.cm2.src.rpm", - Locations: source.NewLocationSet(location), + Locations: file.NewLocationSet(location), Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ @@ -52,7 +52,7 @@ func TestParseRpmManifest(t *testing.T) { Name: "glibc", Version: "2.35-2.cm2", PURL: "pkg:rpm/glibc@2.35-2.cm2?arch=x86_64&upstream=glibc-2.35-2.cm2.src.rpm", - Locations: source.NewLocationSet(location), + Locations: file.NewLocationSet(location), Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ @@ -70,7 +70,7 @@ func TestParseRpmManifest(t *testing.T) { Name: "openssl-libs", Version: "1.1.1k-15.cm2", PURL: "pkg:rpm/openssl-libs@1.1.1k-15.cm2?arch=x86_64&upstream=openssl-1.1.1k-15.cm2.src.rpm", - Locations: source.NewLocationSet(location), + Locations: file.NewLocationSet(location), Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, Metadata: pkg.RpmMetadata{ diff --git a/syft/pkg/cataloger/rpm/parse_rpm_test.go b/syft/pkg/cataloger/rpm/parse_rpm_test.go index 253d99f59dd..83e39528b6f 100644 --- a/syft/pkg/cataloger/rpm/parse_rpm_test.go +++ b/syft/pkg/cataloger/rpm/parse_rpm_test.go @@ -6,12 +6,11 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseRpmFiles(t *testing.T) { - abcRpmLocation := source.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm") - zorkRpmLocation := source.NewLocation("zork-1.0.3-1.el7.x86_64.rpm") + abcRpmLocation := file.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm") + zorkRpmLocation := file.NewLocation("zork-1.0.3-1.el7.x86_64.rpm") tests := []struct { fixture string expected []pkg.Package @@ -23,7 +22,7 @@ func TestParseRpmFiles(t *testing.T) { Name: "abc", Version: "0:1.01-9.hg20160905.el7", PURL: "pkg:rpm/abc@1.01-9.hg20160905.el7?arch=x86_64&epoch=0&upstream=abc-1.01-9.hg20160905.el7.src.rpm", - Locations: source.NewLocationSet(abcRpmLocation), + Locations: file.NewLocationSet(file.NewLocation("abc-1.01-9.hg20160905.el7.x86_64.rpm")), FoundBy: "rpm-file-cataloger", Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, @@ -52,7 +51,7 @@ func TestParseRpmFiles(t *testing.T) { Name: "zork", Version: "0:1.0.3-1.el7", PURL: "pkg:rpm/zork@1.0.3-1.el7?arch=x86_64&epoch=0&upstream=zork-1.0.3-1.el7.src.rpm", - Locations: source.NewLocationSet(zorkRpmLocation), + Locations: file.NewLocationSet(zorkRpmLocation), FoundBy: "rpm-file-cataloger", Type: pkg.RpmPkg, MetadataType: pkg.RpmMetadataType, diff --git a/syft/pkg/cataloger/ruby/package.go b/syft/pkg/cataloger/ruby/package.go index 973d67350fb..86075274204 100644 --- a/syft/pkg/cataloger/ruby/package.go +++ b/syft/pkg/cataloger/ruby/package.go @@ -2,16 +2,16 @@ package ruby import ( "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newGemfileLockPackage(name, version string, locations ...source.Location) pkg.Package { +func newGemfileLockPackage(name, version string, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: name, Version: version, PURL: packageURL(name, version), - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), Language: pkg.Ruby, Type: pkg.GemPkg, } @@ -21,11 +21,11 @@ func newGemfileLockPackage(name, version string, locations ...source.Location) p return p } -func newGemspecPackage(m gemData, gemSpecLocation source.Location) pkg.Package { +func newGemspecPackage(m gemData, gemSpecLocation file.Location) pkg.Package { p := pkg.Package{ Name: m.Name, Version: m.Version, - Locations: source.NewLocationSet(gemSpecLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), + Locations: file.NewLocationSet(gemSpecLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)), Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(gemSpecLocation, m.Licenses...)...), PURL: packageURL(m.Name, m.Version), Language: pkg.Ruby, diff --git a/syft/pkg/cataloger/ruby/parse_gemfile_lock.go b/syft/pkg/cataloger/ruby/parse_gemfile_lock.go index 884f1ea144b..f2bedb4b2a1 100644 --- a/syft/pkg/cataloger/ruby/parse_gemfile_lock.go +++ b/syft/pkg/cataloger/ruby/parse_gemfile_lock.go @@ -6,9 +6,9 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseGemFileLockEntries @@ -16,7 +16,7 @@ var _ generic.Parser = parseGemFileLockEntries var sectionsOfInterest = internal.NewStringSet("GEM", "GIT", "PATH", "PLUGIN SOURCE") // parseGemFileLockEntries is a parser function for Gemfile.lock contents, returning all Gems discovered. -func parseGemFileLockEntries(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseGemFileLockEntries(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package scanner := bufio.NewScanner(reader) diff --git a/syft/pkg/cataloger/ruby/parse_gemfile_lock_test.go b/syft/pkg/cataloger/ruby/parse_gemfile_lock_test.go index ef2a0378429..ad94283aae3 100644 --- a/syft/pkg/cataloger/ruby/parse_gemfile_lock_test.go +++ b/syft/pkg/cataloger/ruby/parse_gemfile_lock_test.go @@ -3,14 +3,14 @@ package ruby import ( "testing" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseGemfileLockEntries(t *testing.T) { fixture := "test-fixtures/Gemfile.lock" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) var expectedPkgs = []pkg.Package{ {Name: "actionmailer", Version: "4.1.1", PURL: "pkg:gem/actionmailer@4.1.1", Locations: locations, Language: pkg.Ruby, Type: pkg.GemPkg}, {Name: "actionpack", Version: "4.1.1", PURL: "pkg:gem/actionpack@4.1.1", Locations: locations, Language: pkg.Ruby, Type: pkg.GemPkg}, diff --git a/syft/pkg/cataloger/ruby/parse_gemspec.go b/syft/pkg/cataloger/ruby/parse_gemspec.go index 347caabbd01..97c2876bd81 100644 --- a/syft/pkg/cataloger/ruby/parse_gemspec.go +++ b/syft/pkg/cataloger/ruby/parse_gemspec.go @@ -11,9 +11,9 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseGemFileLockEntries @@ -64,7 +64,7 @@ func processList(s string) []string { return results } -func parseGemSpecEntries(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseGemSpecEntries(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package var fields = make(map[string]interface{}) scanner := bufio.NewScanner(reader) diff --git a/syft/pkg/cataloger/ruby/parse_gemspec_test.go b/syft/pkg/cataloger/ruby/parse_gemspec_test.go index 53cb59ecfa1..c320185cccc 100644 --- a/syft/pkg/cataloger/ruby/parse_gemspec_test.go +++ b/syft/pkg/cataloger/ruby/parse_gemspec_test.go @@ -3,15 +3,15 @@ package ruby import ( "testing" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseGemspec(t *testing.T) { fixture := "test-fixtures/bundler.gemspec" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) var expectedPkg = pkg.Package{ Name: "bundler", @@ -20,7 +20,7 @@ func TestParseGemspec(t *testing.T) { Locations: locations, Type: pkg.GemPkg, Licenses: pkg.NewLicenseSet( - pkg.NewLicenseFromLocations("MIT", source.NewLocation(fixture)), + pkg.NewLicenseFromLocations("MIT", file.NewLocation(fixture)), ), Language: pkg.Ruby, MetadataType: pkg.GemMetadataType, diff --git a/syft/pkg/cataloger/rust/cataloger_test.go b/syft/pkg/cataloger/rust/cataloger_test.go index 73b442c817b..303b88fd41d 100644 --- a/syft/pkg/cataloger/rust/cataloger_test.go +++ b/syft/pkg/cataloger/rust/cataloger_test.go @@ -3,9 +3,9 @@ package rust import ( "testing" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestNewAuditBinaryCataloger(t *testing.T) { @@ -16,7 +16,7 @@ func TestNewAuditBinaryCataloger(t *testing.T) { Version: "0.1.0", PURL: "pkg:cargo/auditable@0.1.0", FoundBy: "cargo-auditable-binary-cataloger", - Locations: source.NewLocationSet(source.NewVirtualLocation("/hello-auditable", "/hello-auditable")), + Locations: file.NewLocationSet(file.NewVirtualLocation("/hello-auditable", "/hello-auditable")), Language: pkg.Rust, Type: pkg.RustPkg, MetadataType: pkg.RustCargoPackageMetadataType, @@ -31,7 +31,7 @@ func TestNewAuditBinaryCataloger(t *testing.T) { Version: "0.1.0", PURL: "pkg:cargo/hello-auditable@0.1.0", FoundBy: "cargo-auditable-binary-cataloger", - Locations: source.NewLocationSet(source.NewVirtualLocation("/hello-auditable", "/hello-auditable")), + Locations: file.NewLocationSet(file.NewVirtualLocation("/hello-auditable", "/hello-auditable")), Language: pkg.Rust, Type: pkg.RustPkg, MetadataType: pkg.RustCargoPackageMetadataType, diff --git a/syft/pkg/cataloger/rust/package.go b/syft/pkg/cataloger/rust/package.go index 1d661bce3b5..8787c5153bf 100644 --- a/syft/pkg/cataloger/rust/package.go +++ b/syft/pkg/cataloger/rust/package.go @@ -4,16 +4,16 @@ import ( "github.com/microsoft/go-rustaudit" "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) // Pkg returns the standard `pkg.Package` representation of the package referenced within the Cargo.lock metadata. -func newPackageFromCargoMetadata(m pkg.CargoPackageMetadata, locations ...source.Location) pkg.Package { +func newPackageFromCargoMetadata(m pkg.CargoPackageMetadata, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: m.Name, Version: m.Version, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), PURL: packageURL(m.Name, m.Version), Language: pkg.Rust, Type: pkg.RustPkg, @@ -26,7 +26,7 @@ func newPackageFromCargoMetadata(m pkg.CargoPackageMetadata, locations ...source return p } -func newPackagesFromAudit(location source.Location, versionInfo rustaudit.VersionInfo) []pkg.Package { +func newPackagesFromAudit(location file.Location, versionInfo rustaudit.VersionInfo) []pkg.Package { var pkgs []pkg.Package for _, dep := range versionInfo.Packages { @@ -40,14 +40,14 @@ func newPackagesFromAudit(location source.Location, versionInfo rustaudit.Versio return pkgs } -func newPackageFromAudit(dep *rustaudit.Package, locations ...source.Location) pkg.Package { +func newPackageFromAudit(dep *rustaudit.Package, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: dep.Name, Version: dep.Version, PURL: packageURL(dep.Name, dep.Version), Language: pkg.Rust, Type: pkg.RustPkg, - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), MetadataType: pkg.RustCargoPackageMetadataType, Metadata: pkg.CargoPackageMetadata{ Name: dep.Name, diff --git a/syft/pkg/cataloger/rust/parse_audit_binary.go b/syft/pkg/cataloger/rust/parse_audit_binary.go index 7c7e3ad54ec..de894006b56 100644 --- a/syft/pkg/cataloger/rust/parse_audit_binary.go +++ b/syft/pkg/cataloger/rust/parse_audit_binary.go @@ -7,14 +7,14 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader" - "github.com/anchore/syft/syft/source" ) // Catalog identifies executables then attempts to read Rust dependency information from them -func parseAuditBinary(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseAuditBinary(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { var pkgs []pkg.Package unionReader, err := unionreader.GetUnionReader(reader.ReadCloser) diff --git a/syft/pkg/cataloger/rust/parse_cargo_lock.go b/syft/pkg/cataloger/rust/parse_cargo_lock.go index 0e9d582a7ed..cd001728685 100644 --- a/syft/pkg/cataloger/rust/parse_cargo_lock.go +++ b/syft/pkg/cataloger/rust/parse_cargo_lock.go @@ -6,9 +6,9 @@ import ( "github.com/pelletier/go-toml" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parseCargoLock @@ -18,7 +18,7 @@ type cargoLockFile struct { } // parseCargoLock is a parser function for Cargo.lock contents, returning all rust cargo crates discovered. -func parseCargoLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseCargoLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { tree, err := toml.LoadReader(reader) if err != nil { return nil, nil, fmt.Errorf("unable to load Cargo.lock for parsing: %w", err) diff --git a/syft/pkg/cataloger/rust/parse_cargo_lock_test.go b/syft/pkg/cataloger/rust/parse_cargo_lock_test.go index d05f62d6245..fb4ed7427ab 100644 --- a/syft/pkg/cataloger/rust/parse_cargo_lock_test.go +++ b/syft/pkg/cataloger/rust/parse_cargo_lock_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParseCargoLock(t *testing.T) { fixture := "test-fixtures/Cargo.lock" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { Name: "ansi_term", diff --git a/syft/pkg/cataloger/sbom/cataloger.go b/syft/pkg/cataloger/sbom/cataloger.go index c66c9940ab9..a08c2c2a942 100644 --- a/syft/pkg/cataloger/sbom/cataloger.go +++ b/syft/pkg/cataloger/sbom/cataloger.go @@ -3,10 +3,10 @@ package sbom import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/formats" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) const catalogerName = "sbom-cataloger" @@ -29,7 +29,7 @@ func NewSBOMCataloger() *generic.Cataloger { ) } -func parseSBOM(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parseSBOM(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { s, _, err := formats.Decode(reader) if err != nil { return nil, nil, err @@ -47,7 +47,7 @@ func parseSBOM(_ source.FileResolver, _ *generic.Environment, reader source.Loca // Why not keep the original list of locations? Since the "locations" field is meant to capture // where there is evidence of this file, and the catalogers have not run against any file other than, // the SBOM, this is the only location that is relevant for this cataloger. - p.Locations = source.NewLocationSet( + p.Locations = file.NewLocationSet( reader.Location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), ) p.FoundBy = catalogerName diff --git a/syft/pkg/cataloger/sbom/cataloger_test.go b/syft/pkg/cataloger/sbom/cataloger_test.go index a2226f80e7c..46332a2a745 100644 --- a/syft/pkg/cataloger/sbom/cataloger_test.go +++ b/syft/pkg/cataloger/sbom/cataloger_test.go @@ -7,11 +7,11 @@ import ( "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/formats/syftjson" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" "github.com/anchore/syft/syft/sbom" - "github.com/anchore/syft/syft/source" ) func mustCPEs(s ...string) (c []cpe.CPE) { @@ -37,7 +37,7 @@ func Test_parseSBOM(t *testing.T) { Name: "alpine-baselayout", Version: "3.2.0-r23", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("GPL-2.0-only")), FoundBy: "sbom-cataloger", PURL: "pkg:apk/alpine/alpine-baselayout@3.2.0-r23?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.16.3", @@ -54,7 +54,7 @@ func Test_parseSBOM(t *testing.T) { Name: "alpine-baselayout-data", Version: "3.2.0-r23", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("GPL-2.0-only")), FoundBy: "sbom-cataloger", PURL: "pkg:apk/alpine/alpine-baselayout-data@3.2.0-r23?arch=x86_64&upstream=alpine-baselayout&distro=alpine-3.16.3", @@ -75,7 +75,7 @@ func Test_parseSBOM(t *testing.T) { Name: "alpine-keys", Version: "2.4-r1", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")), FoundBy: "sbom-cataloger", PURL: "pkg:apk/alpine/alpine-keys@2.4-r1?arch=x86_64&upstream=alpine-keys&distro=alpine-3.16.3", @@ -92,7 +92,7 @@ func Test_parseSBOM(t *testing.T) { Name: "apk-tools", Version: "2.12.9-r3", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("GPL-2.0-only")), FoundBy: "sbom-cataloger", PURL: "pkg:apk/alpine/apk-tools@2.12.9-r3?arch=x86_64&upstream=apk-tools&distro=alpine-3.16.3", @@ -109,7 +109,7 @@ func Test_parseSBOM(t *testing.T) { Name: "busybox", Version: "1.35.0-r17", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("GPL-2.0-only")), FoundBy: "sbom-cataloger", PURL: "pkg:apk/alpine/busybox@1.35.0-r17?arch=x86_64&upstream=busybox&distro=alpine-3.16.3", @@ -121,7 +121,7 @@ func Test_parseSBOM(t *testing.T) { Name: "ca-certificates-bundle", Version: "20220614-r0", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet( pkg.NewLicense("MPL-2.0"), pkg.NewLicense("MIT"), @@ -145,7 +145,7 @@ func Test_parseSBOM(t *testing.T) { Name: "libc-utils", Version: "0.7.2-r3", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet( pkg.NewLicense("BSD-2-Clause"), pkg.NewLicense("BSD-3-Clause"), @@ -165,7 +165,7 @@ func Test_parseSBOM(t *testing.T) { Name: "libcrypto1.1", Version: "1.1.1s-r0", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("OpenSSL")), // SPDX expression is not set FoundBy: "sbom-cataloger", PURL: "pkg:apk/alpine/libcrypto1.1@1.1.1s-r0?arch=x86_64&upstream=openssl&distro=alpine-3.16.3", @@ -177,7 +177,7 @@ func Test_parseSBOM(t *testing.T) { Name: "libssl1.1", Version: "1.1.1s-r0", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("OpenSSL")), // SPDX expression is not set FoundBy: "sbom-cataloger", PURL: "pkg:apk/alpine/libssl1.1@1.1.1s-r0?arch=x86_64&upstream=openssl&distro=alpine-3.16.3", @@ -189,7 +189,7 @@ func Test_parseSBOM(t *testing.T) { Name: "musl", Version: "1.2.3-r1", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet(pkg.NewLicense("MIT")), // SPDX expression is not set FoundBy: "sbom-cataloger", PURL: "pkg:apk/alpine/musl@1.2.3-r1?arch=x86_64&upstream=musl&distro=alpine-3.16.3", @@ -201,7 +201,7 @@ func Test_parseSBOM(t *testing.T) { Name: "musl-utils", Version: "1.2.3-r1", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet( pkg.NewLicense("MIT"), pkg.NewLicense("BSD"), @@ -222,7 +222,7 @@ func Test_parseSBOM(t *testing.T) { Name: "scanelf", Version: "1.3.4-r0", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet( pkg.NewLicense("GPL-2.0-only"), ), @@ -236,7 +236,7 @@ func Test_parseSBOM(t *testing.T) { Name: "ssl_client", Version: "1.35.0-r17", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet( pkg.NewLicense("GPL-2.0-only"), ), @@ -255,7 +255,7 @@ func Test_parseSBOM(t *testing.T) { Name: "zlib", Version: "1.2.12-r3", Type: "apk", - Locations: source.NewLocationSet(source.NewLocation("sbom.syft.json")), + Locations: file.NewLocationSet(file.NewLocation("sbom.syft.json")), Licenses: pkg.NewLicenseSet( pkg.NewLicense("Zlib"), ), @@ -267,9 +267,9 @@ func Test_parseSBOM(t *testing.T) { }, } - apkgdbLocation := source.NewLocationSet(source.Location{ - LocationData: source.LocationData{ - Coordinates: source.Coordinates{ + apkgdbLocation := file.NewLocationSet(file.Location{ + LocationData: file.LocationData{ + Coordinates: file.Coordinates{ RealPath: "/lib/apk/db/installed", FileSystemID: "sha256:e5e13b0c77cbb769548077189c3da2f0a764ceca06af49d8d558e759f5c232bd", }, @@ -359,7 +359,7 @@ func Test_parseSBOM(t *testing.T) { }, { From: libSSL, - To: source.Coordinates{ + To: file.Coordinates{ RealPath: "/lib/libssl.so.1.1", FileSystemID: "sha256:e5e13b0c77cbb769548077189c3da2f0a764ceca06af49d8d558e759f5c232bd", }, @@ -372,7 +372,7 @@ func Test_parseSBOM(t *testing.T) { }, { From: baseLayout, - To: source.Coordinates{ + To: file.Coordinates{ RealPath: "/etc/profile.d/color_prompt.sh.disabled", FileSystemID: "sha256:e5e13b0c77cbb769548077189c3da2f0a764ceca06af49d8d558e759f5c232bd", }, @@ -380,7 +380,7 @@ func Test_parseSBOM(t *testing.T) { }, { From: baseLayout, - To: source.Coordinates{ + To: file.Coordinates{ RealPath: "/etc/modprobe.d/kms.conf", FileSystemID: "sha256:e5e13b0c77cbb769548077189c3da2f0a764ceca06af49d8d558e759f5c232bd", }, @@ -396,7 +396,7 @@ func Test_parseSBOM(t *testing.T) { for _, p := range expectedPkgs { expectedRelationships = append(expectedRelationships, artifact.Relationship{ From: p, - To: source.Coordinates{ + To: file.Coordinates{ RealPath: "sbom.syft.json", }, Type: artifact.DescribedByRelationship, diff --git a/syft/pkg/cataloger/swift/package.go b/syft/pkg/cataloger/swift/package.go index 0e1c1ce4a0a..ad6416e64ae 100644 --- a/syft/pkg/cataloger/swift/package.go +++ b/syft/pkg/cataloger/swift/package.go @@ -2,16 +2,16 @@ package swift import ( "github.com/anchore/packageurl-go" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" - "github.com/anchore/syft/syft/source" ) -func newPackage(name, version, hash string, locations ...source.Location) pkg.Package { +func newPackage(name, version, hash string, locations ...file.Location) pkg.Package { p := pkg.Package{ Name: name, Version: version, PURL: packageURL(name, version), - Locations: source.NewLocationSet(locations...), + Locations: file.NewLocationSet(locations...), Type: pkg.CocoapodsPkg, Language: pkg.Swift, MetadataType: pkg.CocoapodsMetadataType, diff --git a/syft/pkg/cataloger/swift/parse_podfile_lock.go b/syft/pkg/cataloger/swift/parse_podfile_lock.go index afff41ae9b3..58a58c4643f 100644 --- a/syft/pkg/cataloger/swift/parse_podfile_lock.go +++ b/syft/pkg/cataloger/swift/parse_podfile_lock.go @@ -8,9 +8,9 @@ import ( "gopkg.in/yaml.v3" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/generic" - "github.com/anchore/syft/syft/source" ) var _ generic.Parser = parsePodfileLock @@ -25,7 +25,7 @@ type podfileLock struct { } // parsePodfileLock is a parser function for Podfile.lock contents, returning all cocoapods pods discovered. -func parsePodfileLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { +func parsePodfileLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) { bytes, err := io.ReadAll(reader) if err != nil { return nil, nil, fmt.Errorf("unable to read file: %w", err) diff --git a/syft/pkg/cataloger/swift/parse_podfile_lock_test.go b/syft/pkg/cataloger/swift/parse_podfile_lock_test.go index ef4c7d2c444..53b6dfd12e4 100644 --- a/syft/pkg/cataloger/swift/parse_podfile_lock_test.go +++ b/syft/pkg/cataloger/swift/parse_podfile_lock_test.go @@ -4,14 +4,14 @@ import ( "testing" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func TestParsePodfileLock(t *testing.T) { fixture := "test-fixtures/Podfile.lock" - locations := source.NewLocationSet(source.NewLocation(fixture)) + locations := file.NewLocationSet(file.NewLocation(fixture)) expectedPkgs := []pkg.Package{ { Name: "GlossButtonNode", diff --git a/syft/pkg/license.go b/syft/pkg/license.go index 8278ba7bd90..6e681da6348 100644 --- a/syft/pkg/license.go +++ b/syft/pkg/license.go @@ -7,8 +7,8 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" - "github.com/anchore/syft/syft/source" ) var _ sort.Interface = (*Licenses)(nil) @@ -27,7 +27,7 @@ type License struct { SPDXExpression string `json:"spdxExpression"` Type license.Type `json:"type"` URLs internal.StringSet `hash:"ignore"` - Locations source.LocationSet `hash:"ignore"` + Locations file.LocationSet `hash:"ignore"` } type Licenses []License @@ -70,7 +70,7 @@ func NewLicense(value string) License { SPDXExpression: spdxExpression, Type: license.Declared, URLs: internal.NewStringSet(), - Locations: source.NewLocationSet(), + Locations: file.NewLocationSet(), } } @@ -85,7 +85,7 @@ func NewLicenseFromType(value string, t license.Type) License { SPDXExpression: spdxExpression, Type: t, URLs: internal.NewStringSet(), - Locations: source.NewLocationSet(), + Locations: file.NewLocationSet(), } } @@ -96,7 +96,7 @@ func NewLicensesFromValues(values ...string) (licenses []License) { return } -func NewLicensesFromLocation(location source.Location, values ...string) (licenses []License) { +func NewLicensesFromLocation(location file.Location, values ...string) (licenses []License) { for _, v := range values { if v == "" { continue @@ -106,7 +106,7 @@ func NewLicensesFromLocation(location source.Location, values ...string) (licens return } -func NewLicenseFromLocations(value string, locations ...source.Location) License { +func NewLicenseFromLocations(value string, locations ...file.Location) License { l := NewLicense(value) for _, loc := range locations { l.Locations.Add(loc) diff --git a/syft/pkg/license_set_test.go b/syft/pkg/license_set_test.go index 16abd83a8a1..09c617b6095 100644 --- a/syft/pkg/license_set_test.go +++ b/syft/pkg/license_set_test.go @@ -6,6 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/anchore/syft/internal" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" "github.com/anchore/syft/syft/source" ) @@ -58,15 +59,15 @@ func TestLicenseSet_Add(t *testing.T) { { name: "deduplicate licenses with locations", licenses: []License{ - NewLicenseFromLocations("MIT", source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), - NewLicenseFromLocations("MIT", source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), - NewLicenseFromLocations("MIT", source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"})), }, want: []License{ NewLicenseFromLocations( "MIT", - source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"}), - source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"}), + file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"}), + file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"}), ), }, }, @@ -74,14 +75,14 @@ func TestLicenseSet_Add(t *testing.T) { name: "same licenses with different locations", licenses: []License{ NewLicense("MIT"), - NewLicenseFromLocations("MIT", source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"})), - NewLicenseFromLocations("MIT", source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), }, want: []License{ NewLicenseFromLocations( "MIT", - source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"}), - source.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"}), + file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"}), + file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"}), ), }, }, @@ -89,7 +90,7 @@ func TestLicenseSet_Add(t *testing.T) { name: "same license from different sources", licenses: []License{ NewLicense("MIT"), - NewLicenseFromLocations("MIT", source.NewLocation("/place")), + NewLicenseFromLocations("MIT", file.NewLocation("/place")), NewLicenseFromURLs("MIT", "https://example.com"), }, want: []License{ @@ -98,7 +99,7 @@ func TestLicenseSet_Add(t *testing.T) { SPDXExpression: "MIT", Type: license.Declared, URLs: internal.NewStringSet("https://example.com"), - Locations: source.NewLocationSet(source.NewLocation("/place")), + Locations: file.NewLocationSet(file.NewLocation("/place")), }, }, }, @@ -107,7 +108,7 @@ func TestLicenseSet_Add(t *testing.T) { licenses: []License{ NewLicenseFromType("MIT", license.Concluded), NewLicenseFromType("MIT", license.Declared), - NewLicenseFromLocations("MIT", source.NewLocation("/place")), + NewLicenseFromLocations("MIT", file.NewLocation("/place")), NewLicenseFromURLs("MIT", "https://example.com"), }, want: []License{ @@ -116,14 +117,14 @@ func TestLicenseSet_Add(t *testing.T) { SPDXExpression: "MIT", Type: license.Concluded, URLs: internal.NewStringSet(), - Locations: source.NewLocationSet(), + Locations: file.NewLocationSet(), }, { Value: "MIT", SPDXExpression: "MIT", Type: license.Declared, URLs: internal.NewStringSet("https://example.com"), - Locations: source.NewLocationSet(source.NewLocation("/place")), + Locations: file.NewLocationSet(file.NewLocation("/place")), }, }, }, diff --git a/syft/pkg/license_test.go b/syft/pkg/license_test.go index f3456f5aa21..4e9e16d943f 100644 --- a/syft/pkg/license_test.go +++ b/syft/pkg/license_test.go @@ -8,14 +8,14 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) func Test_Hash(t *testing.T) { - loc1 := source.NewLocation("place!") + loc1 := file.NewLocation("place!") loc1.FileSystemID = "fs1" - loc2 := source.NewLocation("place!") + loc2 := file.NewLocation("place!") loc2.FileSystemID = "fs2" // important! there is a different file system ID lic1 := NewLicenseFromLocations("MIT", loc1) @@ -47,44 +47,44 @@ func Test_Sort(t *testing.T) { { name: "single", licenses: []License{ - NewLicenseFromLocations("MIT", source.NewLocation("place!")), + NewLicenseFromLocations("MIT", file.NewLocation("place!")), }, expected: []License{ - NewLicenseFromLocations("MIT", source.NewLocation("place!")), + NewLicenseFromLocations("MIT", file.NewLocation("place!")), }, }, { name: "multiple", licenses: []License{ - NewLicenseFromLocations("MIT", source.NewLocation("place!")), + NewLicenseFromLocations("MIT", file.NewLocation("place!")), NewLicenseFromURLs("MIT", "https://github.com/anchore/syft/blob/main/LICENSE"), - NewLicenseFromLocations("Apache", source.NewLocation("area!")), - NewLicenseFromLocations("gpl2+", source.NewLocation("area!")), + NewLicenseFromLocations("Apache", file.NewLocation("area!")), + NewLicenseFromLocations("gpl2+", file.NewLocation("area!")), }, expected: Licenses{ - NewLicenseFromLocations("Apache", source.NewLocation("area!")), + NewLicenseFromLocations("Apache", file.NewLocation("area!")), NewLicenseFromURLs("MIT", "https://github.com/anchore/syft/blob/main/LICENSE"), - NewLicenseFromLocations("MIT", source.NewLocation("place!")), - NewLicenseFromLocations("gpl2+", source.NewLocation("area!")), + NewLicenseFromLocations("MIT", file.NewLocation("place!")), + NewLicenseFromLocations("gpl2+", file.NewLocation("area!")), }, }, { name: "multiple with location variants", licenses: []License{ - NewLicenseFromLocations("MIT", source.NewLocation("place!")), - NewLicenseFromLocations("MIT", source.NewLocation("park!")), + NewLicenseFromLocations("MIT", file.NewLocation("place!")), + NewLicenseFromLocations("MIT", file.NewLocation("park!")), NewLicense("MIT"), NewLicense("AAL"), NewLicense("Adobe-2006"), - NewLicenseFromLocations("Apache", source.NewLocation("area!")), + NewLicenseFromLocations("Apache", file.NewLocation("area!")), }, expected: Licenses{ NewLicense("AAL"), NewLicense("Adobe-2006"), - NewLicenseFromLocations("Apache", source.NewLocation("area!")), + NewLicenseFromLocations("Apache", file.NewLocation("area!")), NewLicense("MIT"), - NewLicenseFromLocations("MIT", source.NewLocation("park!")), - NewLicenseFromLocations("MIT", source.NewLocation("place!")), + NewLicenseFromLocations("MIT", file.NewLocation("park!")), + NewLicenseFromLocations("MIT", file.NewLocation("place!")), }, }, } diff --git a/syft/pkg/package.go b/syft/pkg/package.go index 6d028f20c68..c72e57d34ae 100644 --- a/syft/pkg/package.go +++ b/syft/pkg/package.go @@ -11,25 +11,24 @@ import ( "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" "github.com/anchore/syft/syft/cpe" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) // Package represents an application or library that has been bundled into a distributable format. // TODO: if we ignore FoundBy for ID generation should we merge the field to show it was found in two places? -// TODO: should cyclonedx tags exist on the struct? Why don't we use the model.Package type? type Package struct { - id artifact.ID `hash:"ignore"` - Name string // the package name - Version string // the version of the package - FoundBy string `hash:"ignore" cyclonedx:"foundBy"` // the specific cataloger that discovered this package - Locations source.LocationSet // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package) - Licenses LicenseSet // licenses discovered with the package metadata - Language Language `hash:"ignore" cyclonedx:"language"` // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc) - Type Type `cyclonedx:"type"` // the package type (e.g. Npm, Yarn, Python, Rpm, Deb, etc) - CPEs []cpe.CPE `hash:"ignore"` // all possible Common Platform Enumerators (note: this is NOT included in the definition of the ID since all fields on a CPE are derived from other fields) - PURL string `hash:"ignore"` // the Package URL (see https://github.com/package-url/purl-spec) - MetadataType MetadataType `cyclonedx:"metadataType"` // the shape of the additional data in the "metadata" field - Metadata interface{} // additional data found while parsing the package source + id artifact.ID `hash:"ignore"` + Name string // the package name + Version string // the version of the package + FoundBy string `hash:"ignore" cyclonedx:"foundBy"` // the specific cataloger that discovered this package + Locations file.LocationSet // the locations that lead to the discovery of this package (note: this is not necessarily the locations that make up this package) + Licenses LicenseSet // licenses discovered with the package metadata + Language Language `hash:"ignore" cyclonedx:"language"` // the language ecosystem this package belongs to (e.g. JavaScript, Python, etc) + Type Type `cyclonedx:"type"` // the package type (e.g. Npm, Yarn, Python, Rpm, Deb, etc) + CPEs []cpe.CPE `hash:"ignore"` // all possible Common Platform Enumerators (note: this is NOT included in the definition of the ID since all fields on a CPE are derived from other fields) + PURL string `hash:"ignore"` // the Package URL (see https://github.com/package-url/purl-spec) + MetadataType MetadataType `cyclonedx:"metadataType"` // the shape of the additional data in the "metadata" field + Metadata interface{} // additional data found while parsing the package source } func (p *Package) OverrideID(id artifact.ID) { diff --git a/syft/pkg/package_test.go b/syft/pkg/package_test.go index 7c461a680b8..24b8d37fd50 100644 --- a/syft/pkg/package_test.go +++ b/syft/pkg/package_test.go @@ -8,12 +8,12 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/cpe" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) func TestIDUniqueness(t *testing.T) { - originalLocation := source.NewVirtualLocationFromCoordinates( - source.Coordinates{ + originalLocation := file.NewVirtualLocationFromCoordinates( + file.Coordinates{ RealPath: "39.0742° N, 21.8243° E", FileSystemID: "Earth", }, @@ -24,7 +24,7 @@ func TestIDUniqueness(t *testing.T) { Name: "pi", Version: "3.14", FoundBy: "Archimedes", - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( originalLocation, ), Licenses: NewLicenseSet( @@ -101,8 +101,8 @@ func TestIDUniqueness(t *testing.T) { { name: "location is reflected", transform: func(pkg Package) Package { - locations := source.NewLocationSet(pkg.Locations.ToSlice()...) - locations.Add(source.NewLocation("/somewhere/new")) + locations := file.NewLocationSet(pkg.Locations.ToSlice()...) + locations.Add(file.NewLocation("/somewhere/new")) pkg.Locations = locations return pkg }, @@ -122,7 +122,7 @@ func TestIDUniqueness(t *testing.T) { newLocation := originalLocation newLocation.FileSystemID = "Mars" - pkg.Locations = source.NewLocationSet(newLocation) + pkg.Locations = file.NewLocationSet(newLocation) return pkg }, expectedIDComparison: assert.Equal, @@ -133,7 +133,7 @@ func TestIDUniqueness(t *testing.T) { newLocation := originalLocation newLocation.FileSystemID = "Mars" - locations := source.NewLocationSet(pkg.Locations.ToSlice()...) + locations := file.NewLocationSet(pkg.Locations.ToSlice()...) locations.Add(newLocation, originalLocation) pkg.Locations = locations @@ -236,8 +236,8 @@ func TestIDUniqueness(t *testing.T) { } func TestPackage_Merge(t *testing.T) { - originalLocation := source.NewVirtualLocationFromCoordinates( - source.Coordinates{ + originalLocation := file.NewVirtualLocationFromCoordinates( + file.Coordinates{ RealPath: "39.0742° N, 21.8243° E", FileSystemID: "Earth", }, @@ -259,7 +259,7 @@ func TestPackage_Merge(t *testing.T) { Name: "pi", Version: "3.14", FoundBy: "Archimedes", - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( originalLocation, ), Language: "math", @@ -282,7 +282,7 @@ func TestPackage_Merge(t *testing.T) { Name: "pi", Version: "3.14", FoundBy: "Archimedes", - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( similarLocation, // NOTE: difference; we have a different layer but the same path ), Language: "math", @@ -305,7 +305,7 @@ func TestPackage_Merge(t *testing.T) { Name: "pi", Version: "3.14", FoundBy: "Archimedes", - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( originalLocation, similarLocation, // NOTE: merge! ), @@ -333,7 +333,7 @@ func TestPackage_Merge(t *testing.T) { Name: "pi", Version: "3.14", FoundBy: "Archimedes", - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( originalLocation, ), Language: "math", @@ -356,7 +356,7 @@ func TestPackage_Merge(t *testing.T) { Name: "pi-DIFFERENT", // difference Version: "3.14", FoundBy: "Archimedes", - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( originalLocation, ), Language: "math", @@ -395,7 +395,7 @@ func TestPackage_Merge(t *testing.T) { if diff := cmp.Diff(*tt.expected, tt.subject, cmp.AllowUnexported(Package{}), cmp.Comparer( - func(x, y source.LocationSet) bool { + func(x, y file.LocationSet) bool { xs := x.ToSlice() ys := y.ToSlice() @@ -442,7 +442,7 @@ func licenseComparer(x, y License) bool { return cmp.Equal(x, y, cmp.Comparer(locationComparer)) } -func locationComparer(x, y source.Location) bool { +func locationComparer(x, y file.Location) bool { return cmp.Equal(x.Coordinates, y.Coordinates) && cmp.Equal(x.VirtualPath, y.VirtualPath) } diff --git a/syft/pkg/relationships_by_file_ownership_test.go b/syft/pkg/relationships_by_file_ownership_test.go index fdef2897171..f34cb8be4cb 100644 --- a/syft/pkg/relationships_by_file_ownership_test.go +++ b/syft/pkg/relationships_by_file_ownership_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) func TestOwnershipByFilesRelationship(t *testing.T) { @@ -19,9 +19,9 @@ func TestOwnershipByFilesRelationship(t *testing.T) { name: "owns-by-real-path", setup: func(t testing.TB) ([]Package, []artifact.Relationship) { parent := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/a/path", "/another/path"), - source.NewVirtualLocation("/b/path", "/bee/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/a/path", "/another/path"), + file.NewVirtualLocation("/b/path", "/bee/path"), ), Type: RpmPkg, MetadataType: RpmMetadataType, @@ -36,9 +36,9 @@ func TestOwnershipByFilesRelationship(t *testing.T) { parent.SetID() child := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/c/path", "/another/path"), - source.NewVirtualLocation("/d/path", "/another/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/c/path", "/another/path"), + file.NewVirtualLocation("/d/path", "/another/path"), ), Type: NpmPkg, } @@ -62,9 +62,9 @@ func TestOwnershipByFilesRelationship(t *testing.T) { name: "owns-by-virtual-path", setup: func(t testing.TB) ([]Package, []artifact.Relationship) { parent := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/a/path", "/some/other/path"), - source.NewVirtualLocation("/b/path", "/bee/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/a/path", "/some/other/path"), + file.NewVirtualLocation("/b/path", "/bee/path"), ), Type: RpmPkg, MetadataType: RpmMetadataType, @@ -79,9 +79,9 @@ func TestOwnershipByFilesRelationship(t *testing.T) { parent.SetID() child := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/c/path", "/another/path"), - source.NewLocation("/d/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/c/path", "/another/path"), + file.NewLocation("/d/path"), ), Type: NpmPkg, } @@ -104,9 +104,9 @@ func TestOwnershipByFilesRelationship(t *testing.T) { name: "ignore-empty-path", setup: func(t testing.TB) ([]Package, []artifact.Relationship) { parent := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/a/path", "/some/other/path"), - source.NewVirtualLocation("/b/path", "/bee/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/a/path", "/some/other/path"), + file.NewVirtualLocation("/b/path", "/bee/path"), ), Type: RpmPkg, MetadataType: RpmMetadataType, @@ -122,9 +122,9 @@ func TestOwnershipByFilesRelationship(t *testing.T) { parent.SetID() child := Package{ - Locations: source.NewLocationSet( - source.NewVirtualLocation("/c/path", "/another/path"), - source.NewLocation("/d/path"), + Locations: file.NewLocationSet( + file.NewVirtualLocation("/c/path", "/another/path"), + file.NewLocation("/d/path"), ), Type: NpmPkg, } diff --git a/syft/pkg/relationships_evident_by_test.go b/syft/pkg/relationships_evident_by_test.go index f0a99a6ba1a..21e7801bfd5 100644 --- a/syft/pkg/relationships_evident_by_test.go +++ b/syft/pkg/relationships_evident_by_test.go @@ -7,45 +7,45 @@ import ( "github.com/stretchr/testify/require" "github.com/anchore/syft/syft/artifact" - "github.com/anchore/syft/syft/source" + "github.com/anchore/syft/syft/file" ) func TestRelationshipsEvidentBy(t *testing.T) { c := NewCollection() - coordA := source.Coordinates{ + coordA := file.Coordinates{ RealPath: "/somewhere/real", FileSystemID: "abc", } - coordC := source.Coordinates{ + coordC := file.Coordinates{ RealPath: "/somewhere/real", FileSystemID: "abc", } - coordD := source.Coordinates{ + coordD := file.Coordinates{ RealPath: "/somewhere/real", FileSystemID: "abc", } pkgA := Package{ - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( // added! - source.NewLocationFromCoordinates(coordA).WithAnnotation(EvidenceAnnotationKey, PrimaryEvidenceAnnotation), + file.NewLocationFromCoordinates(coordA).WithAnnotation(EvidenceAnnotationKey, PrimaryEvidenceAnnotation), // ignored... - source.NewLocationFromCoordinates(coordC).WithAnnotation(EvidenceAnnotationKey, SupportingEvidenceAnnotation), - source.NewLocationFromCoordinates(coordD), + file.NewLocationFromCoordinates(coordC).WithAnnotation(EvidenceAnnotationKey, SupportingEvidenceAnnotation), + file.NewLocationFromCoordinates(coordD), ), } pkgA.SetID() c.Add(pkgA) - coordB := source.Coordinates{ + coordB := file.Coordinates{ RealPath: "/somewhere-else/real", FileSystemID: "def", } pkgB := Package{ - Locations: source.NewLocationSet( + Locations: file.NewLocationSet( // added! - source.NewLocationFromCoordinates(coordB).WithAnnotation(EvidenceAnnotationKey, PrimaryEvidenceAnnotation), + file.NewLocationFromCoordinates(coordB).WithAnnotation(EvidenceAnnotationKey, PrimaryEvidenceAnnotation), ), } pkgB.SetID() diff --git a/syft/sbom/sbom.go b/syft/sbom/sbom.go index 7770027182b..0bc8feb0cfa 100644 --- a/syft/sbom/sbom.go +++ b/syft/sbom/sbom.go @@ -21,11 +21,11 @@ type SBOM struct { type Artifacts struct { Packages *pkg.Collection - FileMetadata map[source.Coordinates]source.FileMetadata - FileDigests map[source.Coordinates][]file.Digest - FileContents map[source.Coordinates]string - FileLicenses map[source.Coordinates][]file.License - Secrets map[source.Coordinates][]file.SearchResult + FileMetadata map[file.Coordinates]file.Metadata + FileDigests map[file.Coordinates][]file.Digest + FileContents map[file.Coordinates]string + FileLicenses map[file.Coordinates][]file.License + Secrets map[file.Coordinates][]file.SearchResult LinuxDistribution *linux.Release } @@ -49,8 +49,8 @@ func (s SBOM) RelationshipsSorted() []artifact.Relationship { return relationships } -func (s SBOM) AllCoordinates() []source.Coordinates { - set := source.NewCoordinateSet() +func (s SBOM) AllCoordinates() []file.Coordinates { + set := file.NewCoordinateSet() for coordinates := range s.Artifacts.FileMetadata { set.Add(coordinates) } @@ -89,8 +89,8 @@ func (s SBOM) RelationshipsForPackage(p pkg.Package, rt ...artifact.Relationship // CoordinatesForPackage returns all coordinates for the provided package for provided relationship types // If no types are provided, all relationship types are considered. -func (s SBOM) CoordinatesForPackage(p pkg.Package, rt ...artifact.RelationshipType) []source.Coordinates { - var coordinates []source.Coordinates +func (s SBOM) CoordinatesForPackage(p pkg.Package, rt ...artifact.RelationshipType) []file.Coordinates { + var coordinates []file.Coordinates for _, relationship := range s.RelationshipsForPackage(p, rt...) { cords := extractCoordinates(relationship) coordinates = append(coordinates, cords...) @@ -98,12 +98,12 @@ func (s SBOM) CoordinatesForPackage(p pkg.Package, rt ...artifact.RelationshipTy return coordinates } -func extractCoordinates(relationship artifact.Relationship) (results []source.Coordinates) { - if coordinates, exists := relationship.From.(source.Coordinates); exists { +func extractCoordinates(relationship artifact.Relationship) (results []file.Coordinates) { + if coordinates, exists := relationship.From.(file.Coordinates); exists { results = append(results, coordinates) } - if coordinates, exists := relationship.To.(source.Coordinates); exists { + if coordinates, exists := relationship.To.(file.Coordinates); exists { results = append(results, coordinates) } diff --git a/syft/source/deferred_resolver.go b/syft/source/deferred_resolver.go deleted file mode 100644 index 7ca9b90eab6..00000000000 --- a/syft/source/deferred_resolver.go +++ /dev/null @@ -1,108 +0,0 @@ -package source - -import ( - "io" - - "github.com/anchore/syft/internal/log" -) - -func NewDeferredResolverFromSource(creator func() (Source, error)) *DeferredResolver { - return NewDeferredResolver(func() (FileResolver, error) { - s, err := creator() - if err != nil { - return nil, err - } - - return s.FileResolver(SquashedScope) - }) -} - -func NewDeferredResolver(creator func() (FileResolver, error)) *DeferredResolver { - return &DeferredResolver{ - creator: creator, - } -} - -type DeferredResolver struct { - creator func() (FileResolver, error) - resolver FileResolver -} - -func (d *DeferredResolver) getResolver() (FileResolver, error) { - if d.resolver == nil { - resolver, err := d.creator() - if err != nil { - return nil, err - } - d.resolver = resolver - } - return d.resolver, nil -} - -func (d *DeferredResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) { - r, err := d.getResolver() - if err != nil { - return nil, err - } - return r.FileContentsByLocation(location) -} - -func (d *DeferredResolver) HasPath(s string) bool { - r, err := d.getResolver() - if err != nil { - log.Debug("unable to get resolver: %v", err) - return false - } - return r.HasPath(s) -} - -func (d *DeferredResolver) FilesByPath(paths ...string) ([]Location, error) { - r, err := d.getResolver() - if err != nil { - return nil, err - } - return r.FilesByPath(paths...) -} - -func (d *DeferredResolver) FilesByGlob(patterns ...string) ([]Location, error) { - r, err := d.getResolver() - if err != nil { - return nil, err - } - return r.FilesByGlob(patterns...) -} - -func (d *DeferredResolver) FilesByMIMEType(types ...string) ([]Location, error) { - r, err := d.getResolver() - if err != nil { - return nil, err - } - return r.FilesByMIMEType(types...) -} - -func (d *DeferredResolver) RelativeFileByPath(location Location, path string) *Location { - r, err := d.getResolver() - if err != nil { - return nil - } - return r.RelativeFileByPath(location, path) -} - -func (d *DeferredResolver) AllLocations() <-chan Location { - r, err := d.getResolver() - if err != nil { - log.Debug("unable to get resolver: %v", err) - return nil - } - return r.AllLocations() -} - -func (d *DeferredResolver) FileMetadataByLocation(location Location) (FileMetadata, error) { - r, err := d.getResolver() - if err != nil { - return FileMetadata{}, err - } - return r.FileMetadataByLocation(location) -} - -var _ FileResolver = (*DeferredResolver)(nil) diff --git a/syft/source/deprecated.go b/syft/source/deprecated.go new file mode 100644 index 00000000000..4b7e35cf11a --- /dev/null +++ b/syft/source/deprecated.go @@ -0,0 +1,119 @@ +package source + +import ( + "io" + + stereoscopeFile "github.com/anchore/stereoscope/pkg/file" + "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/syft/syft/file" +) + +// Deprecated: use file.Metadata instead +type FileMetadata = file.Metadata + +type ( + // Deprecated: use file.Coordinates instead + Coordinates = file.Coordinates + + // Deprecated: use file.CoordinateSet instead + CoordinateSet = file.CoordinateSet + + // Deprecated: use file.Resolver instead + FileResolver = file.Resolver + + // Deprecated: use file.ContentResolver instead + FileContentResolver = file.ContentResolver + + // Deprecated: use file.PathResolver instead + FilePathResolver = file.PathResolver + + // Deprecated: use file.LocationResolver instead + FileLocationResolver = file.LocationResolver + + // Deprecated: use file.MetadataResolver instead + FileMetadataResolver = file.MetadataResolver + + // Deprecated: use file.WritableResolver instead + WritableFileResolver = file.WritableResolver + + // Deprecated: use file.MockResolver instead + MockResolver = file.MockResolver + + // Deprecated: use file.Location instead + Location = file.Location + + // Deprecated: use file.LocationData instead + LocationData = file.LocationData + + // Deprecated: use file.LocationMetadata instead + LocationMetadata = file.LocationMetadata + + // Deprecated: use file.LocationSet instead + LocationSet = file.LocationSet + + // Deprecated: use file.Locations instead + Locations = file.Locations + + // Deprecated: use file.LocationReadCloser instead + LocationReadCloser = file.LocationReadCloser +) + +// Deprecated: use file.NewCoordinateSet instead +func NewCoordinateSet(coordinates ...file.Coordinates) file.CoordinateSet { + return file.NewCoordinateSet(coordinates...) +} + +// Deprecated: use file.NewLocationSet instead +func NewLocationSet(locations ...file.Location) file.LocationSet { + return file.NewLocationSet(locations...) +} + +// Deprecated: use file.NewLocation instead +func NewLocation(realPath string) file.Location { + return file.NewLocation(realPath) +} + +// Deprecated: use file.NewVirtualLocation instead +func NewVirtualLocation(realPath, virtualPath string) file.Location { + return file.NewVirtualLocation(realPath, virtualPath) +} + +// Deprecated: use file.NewLocationFromCoordinates instead +func NewLocationFromCoordinates(coordinates file.Coordinates) file.Location { + return file.NewLocationFromCoordinates(coordinates) +} + +// Deprecated: use file.NewVirtualLocationFromCoordinates instead +func NewVirtualLocationFromCoordinates(coordinates file.Coordinates, virtualPath string) file.Location { + return file.NewVirtualLocationFromCoordinates(coordinates, virtualPath) +} + +// Deprecated: use file.NewLocationFromImage instead +func NewLocationFromImage(virtualPath string, ref stereoscopeFile.Reference, img *image.Image) file.Location { + return file.NewLocationFromImage(virtualPath, ref, img) +} + +// Deprecated: use file.NewLocationFromDirectory instead +func NewLocationFromDirectory(responsePath string, ref stereoscopeFile.Reference) file.Location { + return file.NewLocationFromDirectory(responsePath, ref) +} + +// Deprecated: use file.NewVirtualLocationFromDirectory instead +func NewVirtualLocationFromDirectory(responsePath, virtualResponsePath string, ref stereoscopeFile.Reference) file.Location { + return file.NewVirtualLocationFromDirectory(responsePath, virtualResponsePath, ref) +} + +// Deprecated: use file.NewLocationReadCloser instead +func NewLocationReadCloser(location file.Location, reader io.ReadCloser) file.LocationReadCloser { + return file.NewLocationReadCloser(location, reader) +} + +// Deprecated: use file.NewMockResolverForPaths instead +func NewMockResolverForPaths(paths ...string) *file.MockResolver { + return file.NewMockResolverForPaths(paths...) +} + +// Deprecated: use file.NewMockResolverForPathsWithMetadata instead +func NewMockResolverForPathsWithMetadata(metadata map[file.Coordinates]file.Metadata) *file.MockResolver { + return file.NewMockResolverForPathsWithMetadata(metadata) +} diff --git a/syft/source/empty_resolver.go b/syft/source/empty_resolver.go deleted file mode 100644 index 72c9331dd9d..00000000000 --- a/syft/source/empty_resolver.go +++ /dev/null @@ -1,45 +0,0 @@ -package source - -import ( - "io" -) - -type EmptyResolver struct{} - -func (e EmptyResolver) FileContentsByLocation(_ Location) (io.ReadCloser, error) { - return nil, nil -} - -func (e EmptyResolver) HasPath(_ string) bool { - return false -} - -func (e EmptyResolver) FilesByPath(_ ...string) ([]Location, error) { - return nil, nil -} - -func (e EmptyResolver) FilesByGlob(_ ...string) ([]Location, error) { - return nil, nil -} - -func (e EmptyResolver) FilesByMIMEType(_ ...string) ([]Location, error) { - return nil, nil -} - -func (e EmptyResolver) RelativeFileByPath(_ Location, _ string) *Location { - return nil -} - -func (e EmptyResolver) AllLocations() <-chan Location { - return nil -} - -func (e EmptyResolver) FileMetadataByLocation(_ Location) (FileMetadata, error) { - return FileMetadata{}, nil -} - -func (e EmptyResolver) Write(_ Location, _ io.Reader) error { - return nil -} - -var _ WritableFileResolver = (*EmptyResolver)(nil) diff --git a/syft/source/file_details.go b/syft/source/file_details.go deleted file mode 100644 index f034057ba5f..00000000000 --- a/syft/source/file_details.go +++ /dev/null @@ -1,21 +0,0 @@ -//go:build linux || darwin || netbsd -// +build linux darwin netbsd - -package source - -import ( - "os" - "syscall" -) - -// GetXid is the UID GID system info for unix -func GetXid(info os.FileInfo) (uid, gid int) { - uid = -1 - gid = -1 - if stat, ok := info.Sys().(*syscall.Stat_t); ok { - uid = int(stat.Uid) - gid = int(stat.Gid) - } - - return uid, gid -} diff --git a/syft/source/file_details_win.go b/syft/source/file_details_win.go deleted file mode 100644 index 31fd05063e7..00000000000 --- a/syft/source/file_details_win.go +++ /dev/null @@ -1,13 +0,0 @@ -//go:build windows -// +build windows - -package source - -import ( - "os" -) - -// GetXid is a placeholder for windows file information -func GetXid(info os.FileInfo) (uid, gid int) { - return -1, -1 -} diff --git a/syft/source/file_metadata.go b/syft/source/file_metadata.go deleted file mode 100644 index 0763564d0fb..00000000000 --- a/syft/source/file_metadata.go +++ /dev/null @@ -1,17 +0,0 @@ -package source - -import ( - "github.com/anchore/stereoscope/pkg/file" - "github.com/anchore/stereoscope/pkg/image" -) - -type FileMetadata = file.Metadata - -func fileMetadataByLocation(img *image.Image, location Location) (file.Metadata, error) { - entry, err := img.FileCatalog.Get(location.ref) - if err != nil { - return FileMetadata{}, err - } - - return entry.Metadata, nil -} diff --git a/syft/source/source.go b/syft/source/source.go index edbd86203d1..bc59b306d7a 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -22,6 +22,8 @@ import ( "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/internal/fileresolver" ) // Source is an object that captures the data source to be cataloged, configuration, and a specific resolver used @@ -30,7 +32,7 @@ type Source struct { id artifact.ID `hash:"ignore"` Image *image.Image `hash:"ignore"` // the image object to be cataloged (image only) Metadata Metadata - directoryResolver *directoryResolver `hash:"ignore"` + directoryResolver *fileresolver.Directory `hash:"ignore"` path string base string mutex *sync.Mutex @@ -466,7 +468,7 @@ func chain(chainID string, layers []LayerMetadata) string { return chain(chainID, layers[1:]) } -func (s *Source) FileResolver(scope Scope) (FileResolver, error) { +func (s *Source) FileResolver(scope Scope) (file.Resolver, error) { switch s.Metadata.Scheme { case DirectoryScheme, FileScheme: s.mutex.Lock() @@ -476,21 +478,21 @@ func (s *Source) FileResolver(scope Scope) (FileResolver, error) { if err != nil { return nil, err } - resolver, err := newDirectoryResolver(s.path, s.base, exclusionFunctions...) + res, err := fileresolver.NewFromDirectory(s.path, s.base, exclusionFunctions...) if err != nil { return nil, fmt.Errorf("unable to create directory resolver: %w", err) } - s.directoryResolver = resolver + s.directoryResolver = res } return s.directoryResolver, nil case ImageScheme: - var resolver FileResolver + var res file.Resolver var err error switch scope { case SquashedScope: - resolver, err = newImageSquashResolver(s.Image) + res, err = fileresolver.NewFromContainerImageSquash(s.Image) case AllLayersScope: - resolver, err = newAllLayersResolver(s.Image) + res, err = fileresolver.NewFromContainerImageAllLayers(s.Image) default: return nil, fmt.Errorf("bad image scope provided: %+v", scope) } @@ -499,9 +501,9 @@ func (s *Source) FileResolver(scope Scope) (FileResolver, error) { } // image tree contains all paths, so we filter out the excluded entries afterwards if len(s.Exclusions) > 0 { - resolver = NewExcludingResolver(resolver, getImageExclusionFunction(s.Exclusions)) + res = fileresolver.NewExcluding(res, getImageExclusionFunction(s.Exclusions)) } - return resolver, nil + return res, nil } return nil, fmt.Errorf("unable to determine FilePathResolver with current scheme=%q", s.Metadata.Scheme) } @@ -543,12 +545,12 @@ func getImageExclusionFunction(exclusions []string) func(string) bool { } } -func getDirectoryExclusionFunctions(root string, exclusions []string) ([]pathIndexVisitor, error) { +func getDirectoryExclusionFunctions(root string, exclusions []string) ([]fileresolver.PathIndexVisitor, error) { if len(exclusions) == 0 { return nil, nil } - // this is what directoryResolver.indexTree is doing to get the absolute path: + // this is what Directory.indexTree is doing to get the absolute path: root, err := filepath.Abs(root) if err != nil { return nil, err @@ -576,7 +578,7 @@ func getDirectoryExclusionFunctions(root string, exclusions []string) ([]pathInd return nil, fmt.Errorf("invalid exclusion pattern(s): '%s' (must start with one of: './', '*/', or '**/')", strings.Join(errors, "', '")) } - return []pathIndexVisitor{ + return []fileresolver.PathIndexVisitor{ func(path string, info os.FileInfo, _ error) error { for _, exclusion := range exclusions { // this is required to handle Windows filepaths @@ -589,7 +591,7 @@ func getDirectoryExclusionFunctions(root string, exclusions []string) ([]pathInd if info != nil && info.IsDir() { return filepath.SkipDir } - return errSkipPath + return fileresolver.ErrSkipPath } } return nil diff --git a/syft/source/source_test.go b/syft/source/source_test.go index cfc6b8f2923..32af0c05202 100644 --- a/syft/source/source_test.go +++ b/syft/source/source_test.go @@ -6,7 +6,6 @@ package source import ( "io" "io/fs" - "io/ioutil" "os" "os/exec" "path" @@ -24,6 +23,7 @@ import ( "github.com/anchore/stereoscope/pkg/image" "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/syft/artifact" + "github.com/anchore/syft/syft/internal/fileresolver" ) func TestParseInput(t *testing.T) { @@ -191,7 +191,7 @@ func TestNewFromDirectory(t *testing.T) { require.NoError(t, err) assert.Equal(t, test.input, src.Metadata.Path) - resolver, err := src.FileResolver(SquashedScope) + res, err := src.FileResolver(SquashedScope) if test.expectedErr { if err == nil { t.Fatal("expected an error when making the resolver but got none") @@ -201,7 +201,7 @@ func TestNewFromDirectory(t *testing.T) { require.NoError(t, err) } - refs, err := resolver.FilesByPath(test.inputPaths...) + refs, err := res.FilesByPath(test.inputPaths...) if err != nil { t.Errorf("FilesByPath call produced an error: %+v", err) } @@ -239,10 +239,10 @@ func TestNewFromFile(t *testing.T) { assert.Equal(t, test.input, src.Metadata.Path) assert.Equal(t, src.Metadata.Path, src.path) - resolver, err := src.FileResolver(SquashedScope) + res, err := src.FileResolver(SquashedScope) require.NoError(t, err) - refs, err := resolver.FilesByPath(test.inputPaths...) + refs, err := res.FilesByPath(test.inputPaths...) require.NoError(t, err) assert.Len(t, refs, test.expRefs) @@ -287,15 +287,15 @@ func TestNewFromFile_WithArchive(t *testing.T) { assert.Equal(t, archivePath, src.Metadata.Path) assert.NotEqual(t, src.Metadata.Path, src.path) - resolver, err := src.FileResolver(SquashedScope) + res, err := src.FileResolver(SquashedScope) require.NoError(t, err) - refs, err := resolver.FilesByPath(test.inputPaths...) + refs, err := res.FilesByPath(test.inputPaths...) require.NoError(t, err) assert.Len(t, refs, test.expRefs) if test.contents != "" { - reader, err := resolver.FileContentsByLocation(refs[0]) + reader, err := res.FileContentsByLocation(refs[0]) require.NoError(t, err) data, err := io.ReadAll(reader) @@ -354,10 +354,10 @@ func TestNewFromDirectoryShared(t *testing.T) { assert.NoError(t, err) src.Metadata.Path = test.notExist - resolver2, err := src.FileResolver(SquashedScope) + resolver, err := src.FileResolver(SquashedScope) assert.NoError(t, err) - refs, err := resolver2.FilesByPath(test.inputPaths...) + refs, err := resolver.FilesByPath(test.inputPaths...) if err != nil { t.Errorf("FilesByPath call produced an error: %+v", err) } @@ -389,11 +389,11 @@ func TestFilesByPathDoesNotExist(t *testing.T) { if err != nil { t.Errorf("could not create NewDirScope: %+v", err) } - resolver, err := src.FileResolver(SquashedScope) + res, err := src.FileResolver(SquashedScope) if err != nil { t.Errorf("could not get resolver error: %+v", err) } - refs, err := resolver.FilesByPath(test.path) + refs, err := res.FilesByPath(test.path) if err != nil { t.Errorf("could not get file references from path: %s, %v", test.path, err) } @@ -438,11 +438,11 @@ func TestFilesByGlob(t *testing.T) { if err != nil { t.Errorf("could not create NewDirScope: %+v", err) } - resolver, err := src.FileResolver(SquashedScope) + res, err := src.FileResolver(SquashedScope) if err != nil { t.Errorf("could not get resolver error: %+v", err) } - contents, err := resolver.FilesByGlob(test.glob) + contents, err := res.FilesByGlob(test.glob) if err != nil { t.Errorf("could not get files by glob: %s+v", err) } @@ -612,11 +612,11 @@ func TestDirectoryExclusions(t *testing.T) { if err != nil { t.Errorf("could not create NewDirScope: %+v", err) } - resolver, err := src.FileResolver(SquashedScope) + res, err := src.FileResolver(SquashedScope) if err != nil { t.Errorf("could not get resolver error: %+v", err) } - locations, err := resolver.FilesByGlob(test.glob) + locations, err := res.FilesByGlob(test.glob) if err != nil { t.Errorf("could not get files by glob: %s+v", err) } @@ -704,11 +704,11 @@ func TestImageExclusions(t *testing.T) { if err != nil { t.Errorf("could not create NewDirScope: %+v", err) } - resolver, err := src.FileResolver(SquashedScope) + res, err := src.FileResolver(SquashedScope) if err != nil { t.Errorf("could not get resolver error: %+v", err) } - contents, err := resolver.FilesByGlob(test.glob) + contents, err := res.FilesByGlob(test.glob) if err != nil { t.Errorf("could not get files by glob: %s+v", err) } @@ -774,7 +774,7 @@ func Test_crossPlatformExclusions(t *testing.T) { root: "/", path: "/usr/var/lib", exclude: "**/var/lib", - walkHint: errSkipPath, + walkHint: fileresolver.ErrSkipPath, }, // linux specific tests... { @@ -783,7 +783,7 @@ func Test_crossPlatformExclusions(t *testing.T) { path: "/usr/var/lib/etc.txt", exclude: "**/*.txt", finfo: dummyInfo{isDir: false}, - walkHint: errSkipPath, + walkHint: fileresolver.ErrSkipPath, }, { desc: "linux relative", @@ -792,7 +792,7 @@ func Test_crossPlatformExclusions(t *testing.T) { exclude: "./*.txt", finfo: dummyInfo{isDir: false}, - walkHint: errSkipPath, + walkHint: fileresolver.ErrSkipPath, }, { desc: "linux one level", @@ -814,7 +814,7 @@ func Test_crossPlatformExclusions(t *testing.T) { path: "/C:/User/stuff/thing.txt", exclude: "**/*.txt", finfo: dummyInfo{isDir: false}, - walkHint: errSkipPath, + walkHint: fileresolver.ErrSkipPath, }, { desc: "windows relative", @@ -822,7 +822,7 @@ func Test_crossPlatformExclusions(t *testing.T) { path: "/C:/User/stuff/thing.txt", exclude: "./*.txt", finfo: dummyInfo{isDir: false}, - walkHint: errSkipPath, + walkHint: fileresolver.ErrSkipPath, }, { desc: "windows one level", @@ -898,7 +898,7 @@ func createArchive(t testing.TB, sourceDirPath, destinationArchivePath string, l func setupArchiveTest(t testing.TB, sourceDirPath string, layer2 bool) string { t.Helper() - archivePrefix, err := ioutil.TempFile("", "syft-archive-TEST-") + archivePrefix, err := os.CreateTemp("", "syft-archive-TEST-") require.NoError(t, err) t.Cleanup( diff --git a/syft/source/test-fixtures/system_paths/target/link/a-symlink/place b/syft/source/test-fixtures/system_paths/target/link/a-symlink/place new file mode 100644 index 00000000000..476e93d5714 --- /dev/null +++ b/syft/source/test-fixtures/system_paths/target/link/a-symlink/place @@ -0,0 +1 @@ +good \ No newline at end of file From 6afbffce283dd3f3f0372dc90c034f061b03dc5f Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 25 May 2023 09:41:18 -0400 Subject: [PATCH 12/21] Fix directory resolver to consider CWD and root path input correctly (#1840) * [wip] put in initial fix Signed-off-by: Alex Goodman * capture expected behavior of dir resolver in tests Signed-off-by: Alex Goodman * update tests + comments to reflect current dir resolver behavior Signed-off-by: Alex Goodman * add additional test cases Signed-off-by: Alex Goodman * fix linting Signed-off-by: Alex Goodman * fix additional tests Signed-off-by: Alex Goodman * fix bad merge conflict resolution Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- .../spdxhelpers/to_format_model_test.go | 5 +- syft/internal/fileresolver/directory.go | 11 +- .../fileresolver/directory_indexer.go | 96 ++++ .../fileresolver/directory_indexer_test.go | 69 ++- syft/internal/fileresolver/directory_test.go | 495 +++++++++++++++++ .../req-resp/path/to/rel-inside.txt | 1 + .../req-resp/path/to/the/file.txt | 1 + .../req-resp/path/to/the/rel-outside.txt | 1 + .../test-fixtures/req-resp/root-link | 1 + .../req-resp/somewhere/outside.txt | 1 + .../fileresolver/unindexed_directory_test.go | 503 ++++++++++++++++++ syft/pkg/cataloger/kernel/cataloger_test.go | 3 +- syft/pkg/license_set_test.go | 19 +- 13 files changed, 1177 insertions(+), 29 deletions(-) create mode 120000 syft/internal/fileresolver/test-fixtures/req-resp/path/to/rel-inside.txt create mode 100644 syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/file.txt create mode 120000 syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/rel-outside.txt create mode 120000 syft/internal/fileresolver/test-fixtures/req-resp/root-link create mode 100644 syft/internal/fileresolver/test-fixtures/req-resp/somewhere/outside.txt diff --git a/syft/formats/common/spdxhelpers/to_format_model_test.go b/syft/formats/common/spdxhelpers/to_format_model_test.go index e36e29435d1..411eed81da7 100644 --- a/syft/formats/common/spdxhelpers/to_format_model_test.go +++ b/syft/formats/common/spdxhelpers/to_format_model_test.go @@ -13,7 +13,6 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/sbom" - "github.com/anchore/syft/syft/source" ) // TODO: Add ToFormatModel tests @@ -505,14 +504,14 @@ func Test_toSPDXID(t *testing.T) { }{ { name: "short filename", - it: source.Coordinates{ + it: file.Coordinates{ RealPath: "/short/path/file.txt", }, expected: "File-short-path-file.txt", }, { name: "long filename", - it: source.Coordinates{ + it: file.Coordinates{ RealPath: "/some/long/path/with/a/lot/of-text/that-contains-a/file.txt", }, expected: "File-...a-lot-of-text-that-contains-a-file.txt", diff --git a/syft/internal/fileresolver/directory.go b/syft/internal/fileresolver/directory.go index a892360d480..2d634cf1eed 100644 --- a/syft/internal/fileresolver/directory.go +++ b/syft/internal/fileresolver/directory.go @@ -54,12 +54,6 @@ func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathI if err != nil { return nil, fmt.Errorf("could not get CWD: %w", err) } - // we have to account for the root being accessed through a symlink path and always resolve the real path. Otherwise - // we will not be able to normalize given paths that fall under the resolver - cleanCWD, err := filepath.EvalSymlinks(currentWD) - if err != nil { - return nil, fmt.Errorf("could not evaluate CWD symlinks: %w", err) - } cleanRoot, err := filepath.EvalSymlinks(root) if err != nil { @@ -80,7 +74,7 @@ func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathI var currentWdRelRoot string if path.IsAbs(cleanRoot) { - currentWdRelRoot, err = filepath.Rel(cleanCWD, cleanRoot) + currentWdRelRoot, err = filepath.Rel(currentWD, cleanRoot) if err != nil { return nil, fmt.Errorf("could not determine given root path to CWD: %w", err) } @@ -91,7 +85,7 @@ func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathI return &Directory{ path: cleanRoot, base: cleanBase, - currentWd: cleanCWD, + currentWd: currentWD, currentWdRelativeToRoot: currentWdRelRoot, tree: filetree.New(), index: filetree.NewIndex(), @@ -132,6 +126,7 @@ func (r Directory) requestPath(userPath string) (string, error) { return userPath, nil } +// responsePath takes a path from the underlying fs domain and converts it to a path that is relative to the root of the directory resolver. func (r Directory) responsePath(path string) string { // check to see if we need to encode back to Windows from posix if runtime.GOOS == WindowsOS { diff --git a/syft/internal/fileresolver/directory_indexer.go b/syft/internal/fileresolver/directory_indexer.go index c590e6caec0..b4383d75d02 100644 --- a/syft/internal/fileresolver/directory_indexer.go +++ b/syft/internal/fileresolver/directory_indexer.go @@ -8,6 +8,7 @@ import ( "path" "path/filepath" "runtime" + "strings" "github.com/wagoodman/go-partybus" "github.com/wagoodman/go-progress" @@ -119,6 +120,22 @@ func (r *directoryIndexer) indexTree(root string, stager *progress.Stage) ([]str return roots, nil } + shouldIndexFullTree, err := isRealPath(root) + if err != nil { + return nil, err + } + + if !shouldIndexFullTree { + newRoots, err := r.indexBranch(root, stager) + if err != nil { + return nil, fmt.Errorf("unable to index branch=%q: %w", root, err) + } + + roots = append(roots, newRoots...) + + return roots, nil + } + err = filepath.Walk(root, func(path string, info os.FileInfo, err error) error { stager.Current = path @@ -143,6 +160,85 @@ func (r *directoryIndexer) indexTree(root string, stager *progress.Stage) ([]str return roots, nil } +func isRealPath(root string) (bool, error) { + rootParent := filepath.Clean(filepath.Dir(root)) + + realRootParent, err := filepath.EvalSymlinks(rootParent) + if err != nil { + return false, err + } + + realRootParent = filepath.Clean(realRootParent) + + return rootParent == realRootParent, nil +} + +func (r *directoryIndexer) indexBranch(root string, stager *progress.Stage) ([]string, error) { + rootRealPath, err := filepath.EvalSymlinks(root) + if err != nil { + return nil, err + } + + // there is a symlink within the path to the root, we need to index the real root parent first + // then capture the symlinks to the root path + roots, err := r.indexTree(rootRealPath, stager) + if err != nil { + return nil, fmt.Errorf("unable to index real root=%q: %w", rootRealPath, err) + } + + // walk down all ancestor paths and shallow-add non-existing elements to the tree + for idx, p := range allContainedPaths(root) { + var targetPath string + if idx != 0 { + parent := path.Dir(p) + cleanParent, err := filepath.EvalSymlinks(parent) + if err != nil { + return nil, fmt.Errorf("unable to evaluate symlink for contained path parent=%q: %w", parent, err) + } + targetPath = filepath.Join(cleanParent, filepath.Base(p)) + } else { + targetPath = p + } + + stager.Current = targetPath + + lstat, err := os.Lstat(targetPath) + newRoot, err := r.indexPath(targetPath, lstat, err) + if err != nil && !errors.Is(err, ErrSkipPath) && !errors.Is(err, fs.SkipDir) { + return nil, fmt.Errorf("unable to index ancestor path=%q: %w", targetPath, err) + } + if newRoot != "" { + roots = append(roots, newRoot) + } + } + + return roots, nil +} + +func allContainedPaths(p string) []string { + var all []string + var currentPath string + + cleanPath := strings.TrimSpace(p) + + if cleanPath == "" { + return nil + } + + // iterate through all parts of the path, replacing path elements with link resolutions where possible. + for idx, part := range strings.Split(filepath.Clean(cleanPath), file.DirSeparator) { + if idx == 0 && part == "" { + currentPath = file.DirSeparator + continue + } + + // cumulatively gather where we are currently at and provide a rich object + currentPath = path.Join(currentPath, part) + all = append(all, currentPath) + } + return all +} + func (r *directoryIndexer) indexPath(path string, info os.FileInfo, err error) (string, error) { // ignore any path which a filter function returns true for _, filterFn := range r.pathIndexVisitors { diff --git a/syft/internal/fileresolver/directory_indexer_test.go b/syft/internal/fileresolver/directory_indexer_test.go index cccacfc2c57..3e5c128cde3 100644 --- a/syft/internal/fileresolver/directory_indexer_test.go +++ b/syft/internal/fileresolver/directory_indexer_test.go @@ -226,8 +226,8 @@ func TestDirectoryIndexer_SkipsAlreadyVisitedLinkDestinations(t *testing.T) { var observedPaths []string pathObserver := func(p string, _ os.FileInfo, _ error) error { fields := strings.Split(p, "test-fixtures/symlinks-prune-indexing") - if len(fields) != 2 { - t.Fatalf("unable to parse path: %s", p) + if len(fields) < 2 { + return nil } clean := strings.TrimLeft(fields[1], "/") if clean != "" { @@ -261,9 +261,11 @@ func TestDirectoryIndexer_SkipsAlreadyVisitedLinkDestinations(t *testing.T) { "path/5/6/7/8/dont-index-me-twice-either.txt", "path/file.txt", // everything below is after the original tree is indexed, and we are now indexing additional roots from symlinks - "path", // considered from symlink before-path, but pruned - "before-path/file.txt", // considered from symlink c-file.txt, but pruned - "before-path", // considered from symlink c-path, but pruned + "path", // considered from symlink before-path, but pruned + "path/file.txt", // leaf + "before-path", // considered from symlink c-path, but pruned + "path/file.txt", // leaf + "before-path", // considered from symlink c-path, but pruned } assert.Equal(t, expected, observedPaths, "visited paths differ \n %s", cmp.Diff(expected, observedPaths)) @@ -282,7 +284,7 @@ func TestDirectoryIndexer_IndexesAllTypes(t *testing.T) { for _, ref := range allRefs { fields := strings.Split(string(ref.RealPath), "test-fixtures/symlinks-prune-indexing") if len(fields) != 2 { - t.Fatalf("unable to parse path: %s", ref.RealPath) + continue } clean := strings.TrimLeft(fields[1], "/") if clean == "" { @@ -326,3 +328,58 @@ func TestDirectoryIndexer_IndexesAllTypes(t *testing.T) { } } + +func Test_allContainedPaths(t *testing.T) { + + tests := []struct { + name string + path string + want []string + }{ + { + name: "empty", + path: "", + want: nil, + }, + { + name: "single relative", + path: "a", + want: []string{"a"}, + }, + { + name: "single absolute", + path: "/a", + want: []string{"/a"}, + }, + { + name: "multiple relative", + path: "a/b/c", + want: []string{"a", "a/b", "a/b/c"}, + }, + { + name: "multiple absolute", + path: "/a/b/c", + want: []string{"/a", "/a/b", "/a/b/c"}, + }, + { + name: "multiple absolute with extra slashs", + path: "///a/b//c/", + want: []string{"/a", "/a/b", "/a/b/c"}, + }, + { + name: "relative with single dot", + path: "a/./b", + want: []string{"a", "a/b"}, + }, + { + name: "relative with double single dot", + path: "a/../b", + want: []string{"b"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assert.Equal(t, tt.want, allContainedPaths(tt.path)) + }) + } +} diff --git a/syft/internal/fileresolver/directory_test.go b/syft/internal/fileresolver/directory_test.go index 819c1df28b2..09d135f9c2b 100644 --- a/syft/internal/fileresolver/directory_test.go +++ b/syft/internal/fileresolver/directory_test.go @@ -22,6 +22,501 @@ import ( "github.com/anchore/syft/syft/file" ) +func TestDirectoryResolver_FilesByPath_request_response(t *testing.T) { + // / + // somewhere/ + // outside.txt + // root-link -> ./ + // path/ + // to/ + // abs-inside.txt -> /path/to/the/file.txt # absolute link to somewhere inside of the root + // rel-inside.txt -> ./the/file.txt # relative link to somewhere inside of the root + // the/ + // file.txt + // abs-outside.txt -> /somewhere/outside.txt # absolute link to outside of the root + // rel-outside -> ../../../somewhere/outside.txt # relative link to outside of the root + // + + testDir, err := os.Getwd() + require.NoError(t, err) + relative := filepath.Join("test-fixtures", "req-resp") + absolute := filepath.Join(testDir, relative) + + absInsidePath := filepath.Join(absolute, "path", "to", "abs-inside.txt") + absOutsidePath := filepath.Join(absolute, "path", "to", "the", "abs-outside.txt") + + relativeViaLink := filepath.Join(relative, "root-link") + absoluteViaLink := filepath.Join(absolute, "root-link") + + relativeViaDoubleLink := filepath.Join(relative, "root-link", "root-link") + absoluteViaDoubleLink := filepath.Join(absolute, "root-link", "root-link") + + cleanup := func() { + _ = os.Remove(absInsidePath) + _ = os.Remove(absOutsidePath) + } + + // ensure the absolute symlinks are cleaned up from any previous runs + cleanup() + + require.NoError(t, os.Symlink(filepath.Join(absolute, "path", "to", "the", "file.txt"), absInsidePath)) + require.NoError(t, os.Symlink(filepath.Join(absolute, "somewhere", "outside.txt"), absOutsidePath)) + + t.Cleanup(cleanup) + + cases := []struct { + name string + cwd string + root string + base string + input string + expectedRealPath string + expectedVirtualPath string + }{ + { + name: "relative root, relative request, direct", + root: relative, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct", + root: absolute, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct", + root: relative, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct", + root: absolute, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within root... + { + name: "relative root, relative request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: "../../", + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: absolute, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: "../../", + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + + root: absolute, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within symlink root... + { + name: "relative root, relative request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./", + input: "path/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "path/to/the/file.txt", + expectedVirtualPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: absoluteViaLink, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./", + input: "/path/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "path/to/the/file.txt", + expectedVirtualPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: absoluteViaLink, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within symlink root, request nested within... + { + name: "relative root, relative nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./path", + input: "to/the/file.txt", + // note: why not expect "to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absoluteViaLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./path", + input: "/to/the/file.txt", + // note: why not expect "to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absoluteViaLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // cwd within DOUBLE symlink root... + { + name: "relative root, relative request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./", + input: "path/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "path/to/the/file.txt", + expectedVirtualPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: absoluteViaDoubleLink, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./", + input: "/path/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "path/to/the/file.txt", + expectedVirtualPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: absoluteViaDoubleLink, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within DOUBLE symlink root, request nested within... + { + name: "relative root, relative nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./path", + input: "to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./path", + input: "/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // cwd within DOUBLE symlink root, request nested DEEP within... + { + name: "relative root, relative nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: "../", + input: "to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: "../", + input: "/to/the/file.txt", + // note: why not expect "path/to/the/file.txt" here? + // this is because we don't know that the path used to access this path (which is a link within + // the root) resides within the root. Without this information it appears as if this file resides + // outside the root. + expectedRealPath: filepath.Join(absolute, "path/to/the/file.txt"), + //expectedRealPath: "to/the/file.txt", + expectedVirtualPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // link to outside of root cases... + { + name: "relative root, relative request, abs indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, relative request, abs indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, abs request, abs indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, abs request, abs indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + // link to outside of root cases... cwd within symlink root + { + name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: "path", + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: "path", + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/rel-outside.txt", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + // we need to mimic a shell, otherwise we won't get a path within a symlink + targetPath := filepath.Join(testDir, c.cwd) + t.Setenv("PWD", filepath.Clean(targetPath)) + + require.NoError(t, err) + require.NoError(t, os.Chdir(targetPath)) + t.Cleanup(func() { + require.NoError(t, os.Chdir(testDir)) + }) + + resolver, err := NewFromDirectory(c.root, c.base) + require.NoError(t, err) + require.NotNil(t, resolver) + + refs, err := resolver.FilesByPath(c.input) + require.NoError(t, err) + if c.expectedRealPath == "" { + require.Empty(t, refs) + return + } + require.Len(t, refs, 1) + assert.Equal(t, c.expectedRealPath, refs[0].RealPath, "real path different") + assert.Equal(t, c.expectedVirtualPath, refs[0].VirtualPath, "virtual path different") + }) + } +} + func TestDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) { cases := []struct { name string diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/path/to/rel-inside.txt b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/rel-inside.txt new file mode 120000 index 00000000000..f2bc06e87c4 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/rel-inside.txt @@ -0,0 +1 @@ +./the/file.txt \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/file.txt b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/file.txt new file mode 100644 index 00000000000..fbfd79f5e48 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/file.txt @@ -0,0 +1 @@ +file-1 diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/rel-outside.txt b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/rel-outside.txt new file mode 120000 index 00000000000..6ad08d35758 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/path/to/the/rel-outside.txt @@ -0,0 +1 @@ +../../../somewhere/outside.txt \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/root-link b/syft/internal/fileresolver/test-fixtures/req-resp/root-link new file mode 120000 index 00000000000..6a043149e81 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/root-link @@ -0,0 +1 @@ +./ \ No newline at end of file diff --git a/syft/internal/fileresolver/test-fixtures/req-resp/somewhere/outside.txt b/syft/internal/fileresolver/test-fixtures/req-resp/somewhere/outside.txt new file mode 100644 index 00000000000..37ad5611998 --- /dev/null +++ b/syft/internal/fileresolver/test-fixtures/req-resp/somewhere/outside.txt @@ -0,0 +1 @@ +file-2 diff --git a/syft/internal/fileresolver/unindexed_directory_test.go b/syft/internal/fileresolver/unindexed_directory_test.go index 14631fc4cd8..3714d8d55eb 100644 --- a/syft/internal/fileresolver/unindexed_directory_test.go +++ b/syft/internal/fileresolver/unindexed_directory_test.go @@ -23,6 +23,509 @@ import ( "github.com/anchore/syft/syft/file" ) +func Test_UnindexDirectoryResolver_RequestRelativePathWithinSymlink(t *testing.T) { + pwd, err := os.Getwd() + + // we need to mimic a shell, otherwise we won't get a path within a symlink + targetPath := filepath.Join(pwd, "./test-fixtures/symlinked-root/nested/link-root/nested") + t.Setenv("PWD", targetPath) + + require.NoError(t, err) + require.NoError(t, os.Chdir(targetPath)) + t.Cleanup(func() { + require.NoError(t, os.Chdir(pwd)) + }) + + resolver := NewFromUnindexedDirectory("./") + require.NoError(t, err) + + locations, err := resolver.FilesByPath("file2.txt") + require.NoError(t, err) + require.Len(t, locations, 1) + + // TODO: this is technically not correct behavior since this is reporting the symlink path (virtual path) and + // not the real path. + require.False(t, filepath.IsAbs(locations[0].RealPath), "should be relative path") +} + +func Test_UnindexDirectoryResolver_FilesByPath_request_response(t *testing.T) { + // / + // somewhere/ + // outside.txt + // root-link -> ./ + // path/ + // to/ + // abs-inside.txt -> /path/to/the/file.txt # absolute link to somewhere inside of the root + // rel-inside.txt -> ./the/file.txt # relative link to somewhere inside of the root + // the/ + // file.txt + // abs-outside.txt -> /somewhere/outside.txt # absolute link to outside of the root + // rel-outside -> ../../../somewhere/outside.txt # relative link to outside of the root + // + + testDir, err := os.Getwd() + require.NoError(t, err) + relative := filepath.Join("test-fixtures", "req-resp") + absolute := filepath.Join(testDir, relative) + + absInsidePath := filepath.Join(absolute, "path", "to", "abs-inside.txt") + absOutsidePath := filepath.Join(absolute, "path", "to", "the", "abs-outside.txt") + + relativeViaLink := filepath.Join(relative, "root-link") + absoluteViaLink := filepath.Join(absolute, "root-link") + + relativeViaDoubleLink := filepath.Join(relative, "root-link", "root-link") + absoluteViaDoubleLink := filepath.Join(absolute, "root-link", "root-link") + + cleanup := func() { + _ = os.Remove(absInsidePath) + _ = os.Remove(absOutsidePath) + } + + // ensure the absolute symlinks are cleaned up from any previous runs + cleanup() + + require.NoError(t, os.Symlink(filepath.Join(absolute, "path", "to", "the", "file.txt"), absInsidePath)) + require.NoError(t, os.Symlink(filepath.Join(absolute, "somewhere", "outside.txt"), absOutsidePath)) + + t.Cleanup(cleanup) + + cases := []struct { + name string + cwd string + root string + base string + input string + expectedRealPath string + expectedVirtualPath string + }{ + { + name: "relative root, relative request, direct", + root: relative, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct", + root: absolute, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct", + root: relative, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct", + root: absolute, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within root... + { + name: "relative root, relative request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: "../../", + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: absolute, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + root: "../../", + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within root", + cwd: filepath.Join(relative, "path/to"), + + root: absolute, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within symlink root... + { + name: "relative root, relative request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./", + input: "path/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: absoluteViaLink, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./", + input: "/path/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: absoluteViaLink, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within symlink root, request nested within... + { + name: "relative root, relative nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./path", + input: "to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absoluteViaLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: "./path", + input: "/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absoluteViaLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // cwd within DOUBLE symlink root... + { + name: "relative root, relative request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./", + input: "path/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, relative request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: absoluteViaDoubleLink, + input: "path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "relative root, abs request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./", + input: "/path/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "path/to/the/file.txt", + }, + { + name: "abs root, abs request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: absoluteViaDoubleLink, + input: "/path/to/the/file.txt", + expectedRealPath: "path/to/the/file.txt", + }, + // cwd within DOUBLE symlink root, request nested within... + { + name: "relative root, relative nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./path", + input: "to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: "./path", + input: "/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd within (double) symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // cwd within DOUBLE symlink root, request nested DEEP within... + { + name: "relative root, relative nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: "../", + input: "to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, relative nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + { + name: "relative root, abs nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: "../", + input: "/to/the/file.txt", + // note: this is inconsistent with the directory resolver. The real path is essentially the virtual path + // in this case for the unindexed resolver, which is not correct. + expectedRealPath: "to/the/file.txt", + }, + { + name: "abs root, abs nested request, direct, cwd deep within (double) symlink root", + cwd: filepath.Join(relativeViaDoubleLink, "path", "to"), + root: filepath.Join(absoluteViaDoubleLink, "path"), + input: "/to/the/file.txt", + expectedRealPath: "to/the/file.txt", + }, + // link to outside of root cases... + { + name: "relative root, relative request, abs indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, relative request, abs indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, abs request, abs indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, abs request, abs indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root)", + root: filepath.Join(relative, "path"), + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root)", + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + // link to outside of root cases... cwd within symlink root + { + name: "relative root, relative request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, relative request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, abs request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "abs root, abs request, abs indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/abs-outside.txt", + expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + expectedVirtualPath: "to/the/abs-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: "path", + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root), cwd within symlink root", + cwd: relativeViaLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: "path", + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, relative request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absolute, "path"), + input: "to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "relative root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: "path", + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + { + name: "abs root, abs request, relative indirect (outside of root), cwd within DOUBLE symlink root", + cwd: relativeViaDoubleLink, + root: filepath.Join(absolute, "path"), + input: "/to/the/rel-outside.txt", + //expectedRealPath: filepath.Join(absolute, "/somewhere/outside.txt"), + // TODO: the real path is not correct + expectedRealPath: "../somewhere/outside.txt", + expectedVirtualPath: "to/the/rel-outside.txt", + }, + } + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + + // we need to mimic a shell, otherwise we won't get a path within a symlink + targetPath := filepath.Join(testDir, c.cwd) + t.Setenv("PWD", filepath.Clean(targetPath)) + + require.NoError(t, err) + require.NoError(t, os.Chdir(targetPath)) + t.Cleanup(func() { + require.NoError(t, os.Chdir(testDir)) + }) + + resolver := NewFromUnindexedDirectory(c.root) + require.NotNil(t, resolver) + + refs, err := resolver.FilesByPath(c.input) + require.NoError(t, err) + if c.expectedRealPath == "" { + require.Empty(t, refs) + return + } + require.Len(t, refs, 1) + assert.Equal(t, c.expectedRealPath, refs[0].RealPath, "real path different") + assert.Equal(t, c.expectedVirtualPath, refs[0].VirtualPath, "virtual path different") + }) + } +} + func Test_UnindexedDirectoryResolver_Basic(t *testing.T) { wd, err := os.Getwd() require.NoError(t, err) diff --git a/syft/pkg/cataloger/kernel/cataloger_test.go b/syft/pkg/cataloger/kernel/cataloger_test.go index f819e605a45..0557c4bd865 100644 --- a/syft/pkg/cataloger/kernel/cataloger_test.go +++ b/syft/pkg/cataloger/kernel/cataloger_test.go @@ -7,7 +7,6 @@ import ( "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" - "github.com/anchore/syft/syft/source" ) func Test_KernelCataloger(t *testing.T) { @@ -50,7 +49,7 @@ func Test_KernelCataloger(t *testing.T) { ), Licenses: pkg.NewLicenseSet( pkg.NewLicenseFromLocations("GPL v2", - source.NewVirtualLocation( + file.NewVirtualLocation( "/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko", "/lib/modules/6.0.7-301.fc37.x86_64/kernel/drivers/tty/ttynull.ko", ), diff --git a/syft/pkg/license_set_test.go b/syft/pkg/license_set_test.go index 09c617b6095..7125c5411b8 100644 --- a/syft/pkg/license_set_test.go +++ b/syft/pkg/license_set_test.go @@ -8,7 +8,6 @@ import ( "github.com/anchore/syft/internal" "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/license" - "github.com/anchore/syft/syft/source" ) func TestLicenseSet_Add(t *testing.T) { @@ -59,15 +58,15 @@ func TestLicenseSet_Add(t *testing.T) { { name: "deduplicate licenses with locations", licenses: []License{ - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "2"})), }, want: []License{ NewLicenseFromLocations( "MIT", - file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"}), - file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"}), + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"}), + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "2"}), ), }, }, @@ -75,14 +74,14 @@ func TestLicenseSet_Add(t *testing.T) { name: "same licenses with different locations", licenses: []License{ NewLicense("MIT"), - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"})), - NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "2"})), + NewLicenseFromLocations("MIT", file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"})), }, want: []License{ NewLicenseFromLocations( "MIT", - file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "1"}), - file.NewLocationFromCoordinates(source.Coordinates{RealPath: "/place", FileSystemID: "2"}), + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "1"}), + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "/place", FileSystemID: "2"}), ), }, }, From 74013d7da7d09ce2ee691f23aec4e53eea14679c Mon Sep 17 00:00:00 2001 From: Alex Goodman Date: Thu, 25 May 2023 13:26:56 -0400 Subject: [PATCH 13/21] Add test to ensure package metadata is represented in the JSON schema (#1841) * [wip] try to reflect metadata types... probably wont work Signed-off-by: Alex Goodman * refactor to add unit test to ensure there is coverage in the schema Signed-off-by: Alex Goodman * [wip] generate metadata container Signed-off-by: Alex Goodman * add generation of metadata container struct for JSON schema generation Signed-off-by: Alex Goodman * fix linting Signed-off-by: Alex Goodman * update linter script to account for code generation Signed-off-by: Alex Goodman --------- Signed-off-by: Alex Goodman --- .github/scripts/json-schema-drift-check.sh | 20 +-- Makefile | 2 +- go.mod | 1 + go.sum | 2 + schema/json/generate/main.go | 50 +++++++ schema/json/internal/generated.go | 39 ++++++ schema/json/internal/metadata_types.go | 150 +++++++++++++++++++++ schema/json/{generate.go => main.go} | 53 ++------ schema/json/main_test.go | 39 ++++++ 9 files changed, 295 insertions(+), 61 deletions(-) create mode 100644 schema/json/generate/main.go create mode 100644 schema/json/internal/generated.go create mode 100644 schema/json/internal/metadata_types.go rename schema/json/{generate.go => main.go} (60%) create mode 100644 schema/json/main_test.go diff --git a/.github/scripts/json-schema-drift-check.sh b/.github/scripts/json-schema-drift-check.sh index 7b7f7dd2f62..3002236d68b 100755 --- a/.github/scripts/json-schema-drift-check.sh +++ b/.github/scripts/json-schema-drift-check.sh @@ -1,27 +1,17 @@ #!/usr/bin/env bash set -u -if ! git diff-index --quiet HEAD --; then - git diff-index HEAD -- - git --no-pager diff - echo "there are uncommitted changes, please commit them before running this check" +if [ "$(git status --porcelain | wc -l)" -ne "0" ]; then + echo " 🔴 there are uncommitted changes, please commit them before running this check" exit 1 fi -success=true - if ! make generate-json-schema; then echo "Generating json schema failed" - success=false -fi - -if ! git diff-index --quiet HEAD --; then - git diff-index HEAD -- - git --no-pager diff - echo "JSON schema drift detected!" - success=false + exit 1 fi -if ! $success; then +if [ "$(git status --porcelain | wc -l)" -ne "0" ]; then + echo " 🔴 there are uncommitted changes, please commit them before running this check" exit 1 fi diff --git a/Makefile b/Makefile index 0b944b83bd9..ae917777f72 100644 --- a/Makefile +++ b/Makefile @@ -302,7 +302,7 @@ compare-test-rpm-package-install: $(TEMP_DIR) $(SNAPSHOT_DIR) .PHONY: generate-json-schema generate-json-schema: ## Generate a new json schema - cd schema/json && go run generate.go + cd schema/json && go generate . && go run . .PHONY: generate-license-list generate-license-list: ## Generate an updated spdx license list diff --git a/go.mod b/go.mod index ea46c730b23..9e7456af4ff 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/Masterminds/sprig/v3 v3.2.3 github.com/anchore/go-logger v0.0.0-20220728155337-03b66a5207d8 github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e + github.com/dave/jennifer v1.6.1 github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da github.com/docker/docker v24.0.1+incompatible github.com/github/go-spdx/v2 v2.1.2 diff --git a/go.sum b/go.sum index 9bfc68a1526..bed5f726321 100644 --- a/go.sum +++ b/go.sum @@ -155,6 +155,8 @@ github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSV github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/dave/jennifer v1.6.1 h1:T4T/67t6RAA5AIV6+NP8Uk/BIsXgDoqEowgycdQQLuk= +github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/schema/json/generate/main.go b/schema/json/generate/main.go new file mode 100644 index 00000000000..fc8dc120a21 --- /dev/null +++ b/schema/json/generate/main.go @@ -0,0 +1,50 @@ +package main + +import ( + "fmt" + "os" + + "github.com/dave/jennifer/jen" + + "github.com/anchore/syft/schema/json/internal" +) + +// This program generates internal/generated.go. + +const ( + pkgImport = "github.com/anchore/syft/syft/pkg" + path = "internal/generated.go" +) + +func main() { + typeNames, err := internal.AllSyftMetadataTypeNames() + if err != nil { + panic(fmt.Errorf("unable to get all metadata type names: %w", err)) + } + + fmt.Printf("updating metadata container object with %+v types\n", len(typeNames)) + + f := jen.NewFile("internal") + f.HeaderComment("DO NOT EDIT: generated by schema/json/generate/main.go") + f.ImportName(pkgImport, "pkg") + f.Comment("ArtifactMetadataContainer is a struct that contains all the metadata types for a package, as represented in the pkg.Package.Metadata field.") + f.Type().Id("ArtifactMetadataContainer").StructFunc(func(g *jen.Group) { + for _, typeName := range typeNames { + g.Id(typeName).Qual(pkgImport, typeName) + } + }) + + rendered := fmt.Sprintf("%#v", f) + + fh, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + panic(fmt.Errorf("unable to open file: %w", err)) + } + _, err = fh.WriteString(rendered) + if err != nil { + panic(fmt.Errorf("unable to write file: %w", err)) + } + if err := fh.Close(); err != nil { + panic(fmt.Errorf("unable to close file: %w", err)) + } +} diff --git a/schema/json/internal/generated.go b/schema/json/internal/generated.go new file mode 100644 index 00000000000..3341818deb7 --- /dev/null +++ b/schema/json/internal/generated.go @@ -0,0 +1,39 @@ +// DO NOT EDIT: generated by schema/json/generate/main.go + +package internal + +import "github.com/anchore/syft/syft/pkg" + +// ArtifactMetadataContainer is a struct that contains all the metadata types for a package, as represented in the pkg.Package.Metadata field. +type ArtifactMetadataContainer struct { + AlpmMetadata pkg.AlpmMetadata + ApkMetadata pkg.ApkMetadata + BinaryMetadata pkg.BinaryMetadata + CargoPackageMetadata pkg.CargoPackageMetadata + CocoapodsMetadata pkg.CocoapodsMetadata + ConanLockMetadata pkg.ConanLockMetadata + ConanMetadata pkg.ConanMetadata + DartPubMetadata pkg.DartPubMetadata + DotnetDepsMetadata pkg.DotnetDepsMetadata + DpkgMetadata pkg.DpkgMetadata + GemMetadata pkg.GemMetadata + GolangBinMetadata pkg.GolangBinMetadata + GolangModMetadata pkg.GolangModMetadata + HackageMetadata pkg.HackageMetadata + JavaMetadata pkg.JavaMetadata + KbPackageMetadata pkg.KbPackageMetadata + LinuxKernelMetadata pkg.LinuxKernelMetadata + LinuxKernelModuleMetadata pkg.LinuxKernelModuleMetadata + MixLockMetadata pkg.MixLockMetadata + NixStoreMetadata pkg.NixStoreMetadata + NpmPackageJSONMetadata pkg.NpmPackageJSONMetadata + NpmPackageLockJSONMetadata pkg.NpmPackageLockJSONMetadata + PhpComposerJSONMetadata pkg.PhpComposerJSONMetadata + PortageMetadata pkg.PortageMetadata + PythonPackageMetadata pkg.PythonPackageMetadata + PythonPipfileLockMetadata pkg.PythonPipfileLockMetadata + PythonRequirementsMetadata pkg.PythonRequirementsMetadata + RDescriptionFileMetadata pkg.RDescriptionFileMetadata + RebarLockMetadata pkg.RebarLockMetadata + RpmMetadata pkg.RpmMetadata +} diff --git a/schema/json/internal/metadata_types.go b/schema/json/internal/metadata_types.go new file mode 100644 index 00000000000..4d515a18890 --- /dev/null +++ b/schema/json/internal/metadata_types.go @@ -0,0 +1,150 @@ +package internal + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "os/exec" + "path/filepath" + "sort" + "strings" + "unicode" + + "github.com/scylladb/go-set/strset" +) + +var metadataExceptions = strset.New( + "FileMetadata", +) + +func AllSyftMetadataTypeNames() ([]string, error) { + root, err := repoRoot() + if err != nil { + return nil, err + } + files, err := filepath.Glob(filepath.Join(root, "syft/pkg/*.go")) + if err != nil { + return nil, err + } + return findMetadataDefinitionNames(files...) +} + +func repoRoot() (string, error) { + root, err := exec.Command("git", "rev-parse", "--show-toplevel").Output() + if err != nil { + return "", fmt.Errorf("unable to find repo root dir: %+v", err) + } + absRepoRoot, err := filepath.Abs(strings.TrimSpace(string(root))) + if err != nil { + return "", fmt.Errorf("unable to get abs path to repo root: %w", err) + } + return absRepoRoot, nil +} + +func findMetadataDefinitionNames(paths ...string) ([]string, error) { + names := strset.New() + usedNames := strset.New() + for _, path := range paths { + metadataDefinitions, usedTypeNames, err := findMetadataDefinitionNamesInFile(path) + if err != nil { + return nil, err + } + + // useful for debugging... + // fmt.Println(path) + // fmt.Println("Defs:", metadataDefinitions) + // fmt.Println("Used Types:", usedTypeNames) + // fmt.Println() + + names.Add(metadataDefinitions...) + usedNames.Add(usedTypeNames...) + } + + // any definition that is used within another struct should not be considered a top-level metadata definition + names.Remove(usedNames.List()...) + + strNames := names.List() + sort.Strings(strNames) + + // note: 30 is a point-in-time gut check. This number could be updated if new metadata definitions are added, but is not required. + // it is really intended to catch any major issues with the generation process that would generate, say, 0 definitions. + if len(strNames) < 30 { + return nil, fmt.Errorf("not enough metadata definitions found (discovered: " + fmt.Sprintf("%d", len(strNames)) + ")") + } + + return strNames, nil +} + +func findMetadataDefinitionNamesInFile(path string) ([]string, []string, error) { + // set up the parser + fs := token.NewFileSet() + f, err := parser.ParseFile(fs, path, nil, parser.ParseComments) + if err != nil { + return nil, nil, err + } + + var metadataDefinitions []string + var usedTypeNames []string + for _, decl := range f.Decls { + // check if the declaration is a type declaration + spec, ok := decl.(*ast.GenDecl) + if !ok || spec.Tok != token.TYPE { + continue + } + + // loop over all types declared in the type declaration + for _, typ := range spec.Specs { + // check if the type is a struct type + spec, ok := typ.(*ast.TypeSpec) + if !ok || spec.Type == nil { + continue + } + + structType, ok := spec.Type.(*ast.StructType) + if !ok { + continue + } + + // check if the struct type ends with "Metadata" + name := spec.Name.String() + + // only look for exported types that end with "Metadata" + if isMetadataTypeCandidate(name) { + // print the full declaration of the struct type + metadataDefinitions = append(metadataDefinitions, name) + usedTypeNames = append(usedTypeNames, typeNamesUsedInStruct(structType)...) + } + } + } + return metadataDefinitions, usedTypeNames, nil +} + +func typeNamesUsedInStruct(structType *ast.StructType) []string { + // recursively find all type names used in the struct type + var names []string + for i := range structType.Fields.List { + // capture names of all of the types (not field names) + ast.Inspect(structType.Fields.List[i].Type, func(n ast.Node) bool { + ident, ok := n.(*ast.Ident) + if !ok { + return true + } + + // add the type name to the list + names = append(names, ident.Name) + + // continue inspecting + return true + }) + } + + return names +} + +func isMetadataTypeCandidate(name string) bool { + return len(name) > 0 && + strings.HasSuffix(name, "Metadata") && + unicode.IsUpper(rune(name[0])) && // must be exported + !metadataExceptions.Has(name) +} diff --git a/schema/json/generate.go b/schema/json/main.go similarity index 60% rename from schema/json/generate.go rename to schema/json/main.go index 169e3c22ff8..246abc532a5 100644 --- a/schema/json/generate.go +++ b/schema/json/main.go @@ -13,8 +13,8 @@ import ( "github.com/invopop/jsonschema" "github.com/anchore/syft/internal" + genInt "github.com/anchore/syft/schema/json/internal" syftjsonModel "github.com/anchore/syft/syft/formats/syftjson/model" - "github.com/anchore/syft/syft/pkg" ) /* @@ -24,46 +24,9 @@ are not captured (empty interfaces). This means that pkg.Package.Metadata is not can be extended to include specific package metadata struct shapes in the future. */ -// This should represent all possible metadatas represented in the pkg.Package.Metadata field (an interface{}). -// When a new package metadata definition is created it will need to be manually added here. The variable name does -// not matter as long as it is exported. - -// TODO: this should be generated from reflection of whats in the pkg package -// Should be created during generation below; use reflection's ability to -// create types at runtime. -// should be same name as struct minus metadata -type artifactMetadataContainer struct { - Alpm pkg.AlpmMetadata - Apk pkg.ApkMetadata - Binary pkg.BinaryMetadata - Cocopods pkg.CocoapodsMetadata - Conan pkg.ConanMetadata - ConanLock pkg.ConanLockMetadata - Dart pkg.DartPubMetadata - Dotnet pkg.DotnetDepsMetadata - Dpkg pkg.DpkgMetadata - Gem pkg.GemMetadata - GoBin pkg.GolangBinMetadata - GoMod pkg.GolangModMetadata - Hackage pkg.HackageMetadata - Java pkg.JavaMetadata - KbPackage pkg.KbPackageMetadata - LinuxKernel pkg.LinuxKernelMetadata - LinuxKernelModule pkg.LinuxKernelModuleMetadata - Nix pkg.NixStoreMetadata - NpmPackage pkg.NpmPackageJSONMetadata - NpmPackageLock pkg.NpmPackageLockJSONMetadata - MixLock pkg.MixLockMetadata - Php pkg.PhpComposerJSONMetadata - Portage pkg.PortageMetadata - PythonPackage pkg.PythonPackageMetadata - PythonPipfilelock pkg.PythonPipfileLockMetadata - PythonRequirements pkg.PythonRequirementsMetadata - RDescriptionFile pkg.RDescriptionFileMetadata - Rebar pkg.RebarLockMetadata - Rpm pkg.RpmMetadata - RustCargo pkg.CargoPackageMetadata -} +//go:generate go run ./generate/main.go + +const schemaVersion = internal.JSONSchemaVersion func main() { write(encode(build())) @@ -77,14 +40,14 @@ func build() *jsonschema.Schema { }, } documentSchema := reflector.ReflectFromType(reflect.TypeOf(&syftjsonModel.Document{})) - metadataSchema := reflector.ReflectFromType(reflect.TypeOf(&artifactMetadataContainer{})) + metadataSchema := reflector.ReflectFromType(reflect.TypeOf(&genInt.ArtifactMetadataContainer{})) // TODO: inject source definitions // inject the definitions of all metadatas into the schema definitions var metadataNames []string for name, definition := range metadataSchema.Definitions { - if name == "artifactMetadataContainer" { + if name == reflect.TypeOf(genInt.ArtifactMetadataContainer{}).Name() { // ignore the definition for the fake container continue } @@ -130,7 +93,7 @@ func encode(schema *jsonschema.Schema) []byte { } func write(schema []byte) { - filename := fmt.Sprintf("schema-%s.json", internal.JSONSchemaVersion) + filename := fmt.Sprintf("schema-%s.json", schemaVersion) if _, err := os.Stat(filename); !os.IsNotExist(err) { // check if the schema is the same... @@ -167,5 +130,5 @@ func write(schema []byte) { defer fh.Close() - fmt.Printf("wrote new schema to %q\n", filename) + fmt.Printf("Wrote new schema to %q\n", filename) } diff --git a/schema/json/main_test.go b/schema/json/main_test.go new file mode 100644 index 00000000000..0903b4dde39 --- /dev/null +++ b/schema/json/main_test.go @@ -0,0 +1,39 @@ +package main + +import ( + "reflect" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/anchore/syft/schema/json/internal" +) + +func TestAllMetadataRepresented(t *testing.T) { + // this test checks that all the metadata types are represented in the currently generated ArtifactMetadataContainer struct + // such that PRs will reflect when there is drift from the implemented set of metadata types and the generated struct + // which controls the JSON schema content. + expected, err := internal.AllSyftMetadataTypeNames() + require.NoError(t, err) + actual := allTypeNamesFromStruct(internal.ArtifactMetadataContainer{}) + if !assert.ElementsMatch(t, expected, actual) { + t.Errorf("metadata types not fully represented: \n%s", cmp.Diff(expected, actual)) + t.Log("did you add a new pkg.*Metadata type without updating the JSON schema?") + t.Log("if so, you need to update the schema version and regenerate the JSON schema (make generate-json-schema)") + } +} + +func allTypeNamesFromStruct(instance any) []string { + // get all the type names from the struct (not recursively) + var typeNames []string + tt := reflect.TypeOf(instance) + for i := 0; i < tt.NumField(); i++ { + field := tt.Field(i) + typeNames = append(typeNames, field.Type.Name()) + } + sort.Strings(typeNames) + return typeNames +} From f0307fdd626f0d9ae605ce90f6a0377c94c50ca8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 26 May 2023 16:08:20 -0400 Subject: [PATCH 14/21] chore(deps): bump github.com/docker/docker (#1849) Bumps [github.com/docker/docker](https://github.com/docker/docker) from 24.0.1+incompatible to 24.0.2+incompatible. - [Release notes](https://github.com/docker/docker/releases) - [Commits](https://github.com/docker/docker/compare/v24.0.1...v24.0.2) --- updated-dependencies: - dependency-name: github.com/docker/docker dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9e7456af4ff..db898fe0e5f 100644 --- a/go.mod +++ b/go.mod @@ -56,7 +56,7 @@ require ( github.com/anchore/stereoscope v0.0.0-20230522170632-e14bc4437b2e github.com/dave/jennifer v1.6.1 github.com/deitch/magic v0.0.0-20230404182410-1ff89d7342da - github.com/docker/docker v24.0.1+incompatible + github.com/docker/docker v24.0.2+incompatible github.com/github/go-spdx/v2 v2.1.2 github.com/go-git/go-billy/v5 v5.4.1 github.com/go-git/go-git/v5 v5.7.0 diff --git a/go.sum b/go.sum index bed5f726321..4d46925d47d 100644 --- a/go.sum +++ b/go.sum @@ -167,8 +167,8 @@ github.com/docker/cli v23.0.5+incompatible h1:ufWmAOuD3Vmr7JP2G5K3cyuNC4YZWiAsuD github.com/docker/cli v23.0.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.1+incompatible h1:NxN81beIxDlUaVt46iUQrYHD9/W3u9EGl52r86O/IGw= -github.com/docker/docker v24.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v24.0.2+incompatible h1:eATx+oLz9WdNVkQrr0qjQ8HvRJ4bOOxfzEo8R+dA3cg= +github.com/docker/docker v24.0.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= github.com/docker/docker-credential-helpers v0.7.0/go.mod h1:rETQfLdHNT3foU5kuNkFR1R1V12OJRRO5lzt2D1b5X0= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= From 5842fc2a6457f68bc881cb4aeea0b87418757c62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 13:48:54 -0400 Subject: [PATCH 15/21] chore(deps): bump github.com/stretchr/testify from 1.8.3 to 1.8.4 (#1852) Bumps [github.com/stretchr/testify](https://github.com/stretchr/testify) from 1.8.3 to 1.8.4. - [Release notes](https://github.com/stretchr/testify/releases) - [Commits](https://github.com/stretchr/testify/compare/v1.8.3...v1.8.4) --- updated-dependencies: - dependency-name: github.com/stretchr/testify dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index db898fe0e5f..f1d13d3a041 100644 --- a/go.mod +++ b/go.mod @@ -36,7 +36,7 @@ require ( github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.15.0 - github.com/stretchr/testify v1.8.3 + github.com/stretchr/testify v1.8.4 github.com/vifraa/gopom v0.2.1 github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5 github.com/wagoodman/go-progress v0.0.0-20230301185719-21920a456ad5 diff --git a/go.sum b/go.sum index 4d46925d47d..365f3f8a650 100644 --- a/go.sum +++ b/go.sum @@ -597,8 +597,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= From c69cdd9f4a52fd8e46f8c5a41cc148de2e79422c Mon Sep 17 00:00:00 2001 From: James Neate Date: Thu, 1 Jun 2023 13:22:29 +0100 Subject: [PATCH 16/21] feat: add scope to pom properties (#1779) * feat: add scope to pom properties Signed-off-by: James Neate * fix: fixed conflict with schema bump Signed-off-by: James Neate --------- Signed-off-by: James Neate --- internal/constants.go | 2 +- schema/json/schema-8.0.1.json | 1873 +++++++++++++++++ syft/pkg/cataloger/java/parse_pom_xml.go | 1 + syft/pkg/cataloger/java/parse_pom_xml_test.go | 50 +- syft/pkg/java_metadata.go | 1 + 5 files changed, 1916 insertions(+), 11 deletions(-) create mode 100644 schema/json/schema-8.0.1.json diff --git a/internal/constants.go b/internal/constants.go index b7be2824039..73bd40a41b8 100644 --- a/internal/constants.go +++ b/internal/constants.go @@ -6,5 +6,5 @@ const ( // JSONSchemaVersion is the current schema version output by the JSON encoder // This is roughly following the "SchemaVer" guidelines for versioning the JSON schema. Please see schema/json/README.md for details on how to increment. - JSONSchemaVersion = "8.0.0" + JSONSchemaVersion = "8.0.1" ) diff --git a/schema/json/schema-8.0.1.json b/schema/json/schema-8.0.1.json new file mode 100644 index 00000000000..1aefee9b3f5 --- /dev/null +++ b/schema/json/schema-8.0.1.json @@ -0,0 +1,1873 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/anchore/syft/syft/formats/syftjson/model/document", + "$ref": "#/$defs/Document", + "$defs": { + "AlpmFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "gid": { + "type": "string" + }, + "time": { + "type": "string", + "format": "date-time" + }, + "size": { + "type": "string" + }, + "link": { + "type": "string" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object" + }, + "AlpmMetadata": { + "properties": { + "basepackage": { + "type": "string" + }, + "package": { + "type": "string" + }, + "version": { + "type": "string" + }, + "description": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "packager": { + "type": "string" + }, + "url": { + "type": "string" + }, + "validation": { + "type": "string" + }, + "reason": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + }, + "backup": { + "items": { + "$ref": "#/$defs/AlpmFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "basepackage", + "package", + "version", + "description", + "architecture", + "size", + "packager", + "url", + "validation", + "reason", + "files", + "backup" + ] + }, + "ApkFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "ownerUid": { + "type": "string" + }, + "ownerGid": { + "type": "string" + }, + "permissions": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "ApkMetadata": { + "properties": { + "package": { + "type": "string" + }, + "originPackage": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "version": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "url": { + "type": "string" + }, + "description": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "installedSize": { + "type": "integer" + }, + "pullDependencies": { + "items": { + "type": "string" + }, + "type": "array" + }, + "provides": { + "items": { + "type": "string" + }, + "type": "array" + }, + "pullChecksum": { + "type": "string" + }, + "gitCommitOfApkPort": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/ApkFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "originPackage", + "maintainer", + "version", + "architecture", + "url", + "description", + "size", + "installedSize", + "pullDependencies", + "provides", + "pullChecksum", + "gitCommitOfApkPort", + "files" + ] + }, + "BinaryMetadata": { + "properties": { + "matches": { + "items": { + "$ref": "#/$defs/ClassifierMatch" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "matches" + ] + }, + "CargoPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "type": "string" + }, + "checksum": { + "type": "string" + }, + "dependencies": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "checksum", + "dependencies" + ] + }, + "ClassifierMatch": { + "properties": { + "classifier": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Location" + } + }, + "type": "object", + "required": [ + "classifier", + "location" + ] + }, + "CocoapodsMetadata": { + "properties": { + "checksum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "checksum" + ] + }, + "ConanLockMetadata": { + "properties": { + "ref": { + "type": "string" + }, + "package_id": { + "type": "string" + }, + "prev": { + "type": "string" + }, + "requires": { + "type": "string" + }, + "build_requires": { + "type": "string" + }, + "py_requires": { + "type": "string" + }, + "options": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "path": { + "type": "string" + }, + "context": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "ConanMetadata": { + "properties": { + "ref": { + "type": "string" + } + }, + "type": "object", + "required": [ + "ref" + ] + }, + "Coordinates": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "DartPubMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "hosted_url": { + "type": "string" + }, + "vcs_url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Descriptor": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "configuration": true + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "Digest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "Document": { + "properties": { + "artifacts": { + "items": { + "$ref": "#/$defs/Package" + }, + "type": "array" + }, + "artifactRelationships": { + "items": { + "$ref": "#/$defs/Relationship" + }, + "type": "array" + }, + "files": { + "items": { + "$ref": "#/$defs/File" + }, + "type": "array" + }, + "secrets": { + "items": { + "$ref": "#/$defs/Secrets" + }, + "type": "array" + }, + "source": { + "$ref": "#/$defs/Source" + }, + "distro": { + "$ref": "#/$defs/LinuxRelease" + }, + "descriptor": { + "$ref": "#/$defs/Descriptor" + }, + "schema": { + "$ref": "#/$defs/Schema" + } + }, + "type": "object", + "required": [ + "artifacts", + "artifactRelationships", + "source", + "distro", + "descriptor", + "schema" + ] + }, + "DotnetDepsMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sha512": { + "type": "string" + }, + "hashPath": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "path", + "sha512", + "hashPath" + ] + }, + "DpkgFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "isConfigFile": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "path", + "isConfigFile" + ] + }, + "DpkgMetadata": { + "properties": { + "package": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/DpkgFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "package", + "source", + "version", + "sourceVersion", + "architecture", + "maintainer", + "installedSize", + "files" + ] + }, + "File": { + "properties": { + "id": { + "type": "string" + }, + "location": { + "$ref": "#/$defs/Coordinates" + }, + "metadata": { + "$ref": "#/$defs/FileMetadataEntry" + }, + "contents": { + "type": "string" + }, + "digests": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "id", + "location" + ] + }, + "FileMetadataEntry": { + "properties": { + "mode": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "linkDestination": { + "type": "string" + }, + "userID": { + "type": "integer" + }, + "groupID": { + "type": "integer" + }, + "mimeType": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object", + "required": [ + "mode", + "type", + "userID", + "groupID", + "mimeType", + "size" + ] + }, + "GemMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "GolangBinMetadata": { + "properties": { + "goBuildSettings": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "goCompiledVersion": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "h1Digest": { + "type": "string" + }, + "mainModule": { + "type": "string" + } + }, + "type": "object", + "required": [ + "goCompiledVersion", + "architecture" + ] + }, + "GolangModMetadata": { + "properties": { + "h1Digest": { + "type": "string" + } + }, + "type": "object" + }, + "HackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "snapshotURL": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version" + ] + }, + "IDLikes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "JavaManifest": { + "properties": { + "main": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "namedSections": { + "patternProperties": { + ".*": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "JavaMetadata": { + "properties": { + "virtualPath": { + "type": "string" + }, + "manifest": { + "$ref": "#/$defs/JavaManifest" + }, + "pomProperties": { + "$ref": "#/$defs/PomProperties" + }, + "pomProject": { + "$ref": "#/$defs/PomProject" + }, + "digest": { + "items": { + "$ref": "#/$defs/Digest" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "virtualPath" + ] + }, + "KbPackageMetadata": { + "properties": { + "product_id": { + "type": "string" + }, + "kb": { + "type": "string" + } + }, + "type": "object", + "required": [ + "product_id", + "kb" + ] + }, + "License": { + "properties": { + "value": { + "type": "string" + }, + "spdxExpression": { + "type": "string" + }, + "type": { + "type": "string" + }, + "urls": { + "items": { + "type": "string" + }, + "type": "array" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "value", + "spdxExpression", + "type", + "urls", + "locations" + ] + }, + "LinuxKernelMetadata": { + "properties": { + "name": { + "type": "string" + }, + "architecture": { + "type": "string" + }, + "version": { + "type": "string" + }, + "extendedVersion": { + "type": "string" + }, + "buildTime": { + "type": "string" + }, + "author": { + "type": "string" + }, + "format": { + "type": "string" + }, + "rwRootFS": { + "type": "boolean" + }, + "swapDevice": { + "type": "integer" + }, + "rootDevice": { + "type": "integer" + }, + "videoMode": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "architecture", + "version" + ] + }, + "LinuxKernelModuleMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "sourceVersion": { + "type": "string" + }, + "path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "license": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "versionMagic": { + "type": "string" + }, + "parameters": { + "patternProperties": { + ".*": { + "$ref": "#/$defs/LinuxKernelModuleParameter" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "LinuxKernelModuleParameter": { + "properties": { + "type": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "LinuxRelease": { + "properties": { + "prettyName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "idLike": { + "$ref": "#/$defs/IDLikes" + }, + "version": { + "type": "string" + }, + "versionID": { + "type": "string" + }, + "versionCodename": { + "type": "string" + }, + "buildID": { + "type": "string" + }, + "imageID": { + "type": "string" + }, + "imageVersion": { + "type": "string" + }, + "variant": { + "type": "string" + }, + "variantID": { + "type": "string" + }, + "homeURL": { + "type": "string" + }, + "supportURL": { + "type": "string" + }, + "bugReportURL": { + "type": "string" + }, + "privacyPolicyURL": { + "type": "string" + }, + "cpeName": { + "type": "string" + }, + "supportEnd": { + "type": "string" + } + }, + "type": "object" + }, + "Location": { + "properties": { + "path": { + "type": "string" + }, + "layerID": { + "type": "string" + }, + "annotations": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "MixLockMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "NixStoreMetadata": { + "properties": { + "outputHash": { + "type": "string" + }, + "output": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "outputHash", + "files" + ] + }, + "NpmPackageJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + }, + "private": { + "type": "boolean" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "homepage", + "description", + "url", + "private" + ] + }, + "NpmPackageLockJSONMetadata": { + "properties": { + "resolved": { + "type": "string" + }, + "integrity": { + "type": "string" + } + }, + "type": "object", + "required": [ + "resolved", + "integrity" + ] + }, + "Package": { + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "type": { + "type": "string" + }, + "foundBy": { + "type": "string" + }, + "locations": { + "items": { + "$ref": "#/$defs/Location" + }, + "type": "array" + }, + "licenses": { + "$ref": "#/$defs/licenses" + }, + "language": { + "type": "string" + }, + "cpes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "purl": { + "type": "string" + }, + "metadataType": { + "type": "string" + }, + "metadata": { + "anyOf": [ + { + "type": "null" + }, + { + "$ref": "#/$defs/AlpmMetadata" + }, + { + "$ref": "#/$defs/ApkMetadata" + }, + { + "$ref": "#/$defs/BinaryMetadata" + }, + { + "$ref": "#/$defs/CargoPackageMetadata" + }, + { + "$ref": "#/$defs/CocoapodsMetadata" + }, + { + "$ref": "#/$defs/ConanLockMetadata" + }, + { + "$ref": "#/$defs/ConanMetadata" + }, + { + "$ref": "#/$defs/DartPubMetadata" + }, + { + "$ref": "#/$defs/DotnetDepsMetadata" + }, + { + "$ref": "#/$defs/DpkgMetadata" + }, + { + "$ref": "#/$defs/GemMetadata" + }, + { + "$ref": "#/$defs/GolangBinMetadata" + }, + { + "$ref": "#/$defs/GolangModMetadata" + }, + { + "$ref": "#/$defs/HackageMetadata" + }, + { + "$ref": "#/$defs/JavaMetadata" + }, + { + "$ref": "#/$defs/KbPackageMetadata" + }, + { + "$ref": "#/$defs/LinuxKernelMetadata" + }, + { + "$ref": "#/$defs/LinuxKernelModuleMetadata" + }, + { + "$ref": "#/$defs/MixLockMetadata" + }, + { + "$ref": "#/$defs/NixStoreMetadata" + }, + { + "$ref": "#/$defs/NpmPackageJSONMetadata" + }, + { + "$ref": "#/$defs/NpmPackageLockJSONMetadata" + }, + { + "$ref": "#/$defs/PhpComposerJSONMetadata" + }, + { + "$ref": "#/$defs/PortageMetadata" + }, + { + "$ref": "#/$defs/PythonPackageMetadata" + }, + { + "$ref": "#/$defs/PythonPipfileLockMetadata" + }, + { + "$ref": "#/$defs/PythonRequirementsMetadata" + }, + { + "$ref": "#/$defs/RDescriptionFileMetadata" + }, + { + "$ref": "#/$defs/RebarLockMetadata" + }, + { + "$ref": "#/$defs/RpmMetadata" + } + ] + } + }, + "type": "object", + "required": [ + "id", + "name", + "version", + "type", + "foundBy", + "locations", + "licenses", + "language", + "cpes", + "purl" + ] + }, + "PhpComposerAuthors": { + "properties": { + "name": { + "type": "string" + }, + "email": { + "type": "string" + }, + "homepage": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name" + ] + }, + "PhpComposerExternalReference": { + "properties": { + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "reference": { + "type": "string" + }, + "shasum": { + "type": "string" + } + }, + "type": "object", + "required": [ + "type", + "url", + "reference" + ] + }, + "PhpComposerJSONMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "source": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "dist": { + "$ref": "#/$defs/PhpComposerExternalReference" + }, + "require": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "provide": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "require-dev": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "suggest": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + }, + "license": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "notification-url": { + "type": "string" + }, + "bin": { + "items": { + "type": "string" + }, + "type": "array" + }, + "authors": { + "items": { + "$ref": "#/$defs/PhpComposerAuthors" + }, + "type": "array" + }, + "description": { + "type": "string" + }, + "homepage": { + "type": "string" + }, + "keywords": { + "items": { + "type": "string" + }, + "type": "array" + }, + "time": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "source", + "dist" + ] + }, + "PomParent": { + "properties": { + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object", + "required": [ + "groupId", + "artifactId", + "version" + ] + }, + "PomProject": { + "properties": { + "path": { + "type": "string" + }, + "parent": { + "$ref": "#/$defs/PomParent" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "groupId", + "artifactId", + "version", + "name" + ] + }, + "PomProperties": { + "properties": { + "path": { + "type": "string" + }, + "name": { + "type": "string" + }, + "groupId": { + "type": "string" + }, + "artifactId": { + "type": "string" + }, + "version": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "extraFields": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "path", + "name", + "groupId", + "artifactId", + "version" + ] + }, + "PortageFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/Digest" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PortageMetadata": { + "properties": { + "installedSize": { + "type": "integer" + }, + "files": { + "items": { + "$ref": "#/$defs/PortageFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "installedSize", + "files" + ] + }, + "PythonDirectURLOriginInfo": { + "properties": { + "url": { + "type": "string" + }, + "commitId": { + "type": "string" + }, + "vcs": { + "type": "string" + } + }, + "type": "object", + "required": [ + "url" + ] + }, + "PythonFileDigest": { + "properties": { + "algorithm": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "algorithm", + "value" + ] + }, + "PythonFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "digest": { + "$ref": "#/$defs/PythonFileDigest" + }, + "size": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path" + ] + }, + "PythonPackageMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "author": { + "type": "string" + }, + "authorEmail": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/PythonFileRecord" + }, + "type": "array" + }, + "sitePackagesRootPath": { + "type": "string" + }, + "topLevelPackages": { + "items": { + "type": "string" + }, + "type": "array" + }, + "directUrlOrigin": { + "$ref": "#/$defs/PythonDirectURLOriginInfo" + } + }, + "type": "object", + "required": [ + "name", + "version", + "author", + "authorEmail", + "platform", + "sitePackagesRootPath" + ] + }, + "PythonPipfileLockMetadata": { + "properties": { + "hashes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "index": { + "type": "string" + } + }, + "type": "object", + "required": [ + "hashes", + "index" + ] + }, + "PythonRequirementsMetadata": { + "properties": { + "name": { + "type": "string" + }, + "extras": { + "items": { + "type": "string" + }, + "type": "array" + }, + "versionConstraint": { + "type": "string" + }, + "url": { + "type": "string" + }, + "markers": { + "patternProperties": { + ".*": { + "type": "string" + } + }, + "type": "object" + } + }, + "type": "object", + "required": [ + "name", + "extras", + "versionConstraint", + "url", + "markers" + ] + }, + "RDescriptionFileMetadata": { + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "string" + }, + "maintainer": { + "type": "string" + }, + "url": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repository": { + "type": "string" + }, + "built": { + "type": "string" + }, + "needsCompilation": { + "type": "boolean" + }, + "imports": { + "items": { + "type": "string" + }, + "type": "array" + }, + "depends": { + "items": { + "type": "string" + }, + "type": "array" + }, + "suggests": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "RebarLockMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "pkgHash": { + "type": "string" + }, + "pkgHashExt": { + "type": "string" + } + }, + "type": "object", + "required": [ + "name", + "version", + "pkgHash", + "pkgHashExt" + ] + }, + "Relationship": { + "properties": { + "parent": { + "type": "string" + }, + "child": { + "type": "string" + }, + "type": { + "type": "string" + }, + "metadata": true + }, + "type": "object", + "required": [ + "parent", + "child", + "type" + ] + }, + "RpmMetadata": { + "properties": { + "name": { + "type": "string" + }, + "version": { + "type": "string" + }, + "epoch": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "architecture": { + "type": "string" + }, + "release": { + "type": "string" + }, + "sourceRpm": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "vendor": { + "type": "string" + }, + "modularityLabel": { + "type": "string" + }, + "files": { + "items": { + "$ref": "#/$defs/RpmdbFileRecord" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "name", + "version", + "epoch", + "architecture", + "release", + "sourceRpm", + "size", + "vendor", + "modularityLabel", + "files" + ] + }, + "RpmdbFileRecord": { + "properties": { + "path": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "size": { + "type": "integer" + }, + "digest": { + "$ref": "#/$defs/Digest" + }, + "userName": { + "type": "string" + }, + "groupName": { + "type": "string" + }, + "flags": { + "type": "string" + } + }, + "type": "object", + "required": [ + "path", + "mode", + "size", + "digest", + "userName", + "groupName", + "flags" + ] + }, + "Schema": { + "properties": { + "version": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "type": "object", + "required": [ + "version", + "url" + ] + }, + "SearchResult": { + "properties": { + "classification": { + "type": "string" + }, + "lineNumber": { + "type": "integer" + }, + "lineOffset": { + "type": "integer" + }, + "seekPosition": { + "type": "integer" + }, + "length": { + "type": "integer" + }, + "value": { + "type": "string" + } + }, + "type": "object", + "required": [ + "classification", + "lineNumber", + "lineOffset", + "seekPosition", + "length" + ] + }, + "Secrets": { + "properties": { + "location": { + "$ref": "#/$defs/Coordinates" + }, + "secrets": { + "items": { + "$ref": "#/$defs/SearchResult" + }, + "type": "array" + } + }, + "type": "object", + "required": [ + "location", + "secrets" + ] + }, + "Source": { + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "target": true + }, + "type": "object", + "required": [ + "id", + "type", + "target" + ] + }, + "licenses": { + "items": { + "$ref": "#/$defs/License" + }, + "type": "array" + } + } +} diff --git a/syft/pkg/cataloger/java/parse_pom_xml.go b/syft/pkg/cataloger/java/parse_pom_xml.go index b0316c860d2..8b43ada5013 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml.go +++ b/syft/pkg/cataloger/java/parse_pom_xml.go @@ -69,6 +69,7 @@ func newPackageFromPom(pom gopom.Project, dep gopom.Dependency, locations ...fil m := pkg.JavaMetadata{ PomProperties: &pkg.PomProperties{ GroupID: resolveProperty(pom, dep.GroupID), + Scope: resolveProperty(pom, dep.Scope), }, } diff --git a/syft/pkg/cataloger/java/parse_pom_xml_test.go b/syft/pkg/cataloger/java/parse_pom_xml_test.go index 01b19e6bb8c..cebe979be0b 100644 --- a/syft/pkg/cataloger/java/parse_pom_xml_test.go +++ b/syft/pkg/cataloger/java/parse_pom_xml_test.go @@ -39,7 +39,10 @@ func Test_parserPomXML(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "junit"}, + PomProperties: &pkg.PomProperties{ + GroupID: "junit", + Scope: "test", + }, }, }, }, @@ -83,7 +86,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "org.junit.jupiter"}, + PomProperties: &pkg.PomProperties{ + GroupID: "org.junit.jupiter", + Scope: "test", + }, }, }, { @@ -94,7 +100,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "org.assertj"}, + PomProperties: &pkg.PomProperties{ + GroupID: "org.assertj", + Scope: "test", + }, }, }, { @@ -105,7 +114,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "commons-io"}, + PomProperties: &pkg.PomProperties{ + GroupID: "commons-io", + Scope: "test", + }, }, }, { @@ -116,7 +128,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "org.mockito"}, + PomProperties: &pkg.PomProperties{ + GroupID: "org.mockito", + Scope: "test", + }, }, }, { @@ -127,7 +142,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "org.graalvm.js"}, + PomProperties: &pkg.PomProperties{ + GroupID: "org.graalvm.js", + Scope: "test", + }, }, }, { @@ -138,7 +156,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "org.graalvm.js"}, + PomProperties: &pkg.PomProperties{ + GroupID: "org.graalvm.js", + Scope: "test", + }, }, }, { @@ -149,7 +170,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "org.apache.commons"}, + PomProperties: &pkg.PomProperties{ + GroupID: "org.apache.commons", + Scope: "test", + }, }, }, { @@ -160,7 +184,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "org.openjdk.jmh"}, + PomProperties: &pkg.PomProperties{ + GroupID: "org.openjdk.jmh", + Scope: "test", + }, }, }, { @@ -171,7 +198,10 @@ func Test_parseCommonsTextPomXMLProject(t *testing.T) { Type: pkg.JavaPkg, MetadataType: pkg.JavaMetadataType, Metadata: pkg.JavaMetadata{ - PomProperties: &pkg.PomProperties{GroupID: "org.openjdk.jmh"}, + PomProperties: &pkg.PomProperties{ + GroupID: "org.openjdk.jmh", + Scope: "test", + }, }, }, }, diff --git a/syft/pkg/java_metadata.go b/syft/pkg/java_metadata.go index 2e134bf1125..12b9c5c534e 100644 --- a/syft/pkg/java_metadata.go +++ b/syft/pkg/java_metadata.go @@ -32,6 +32,7 @@ type PomProperties struct { GroupID string `mapstructure:"groupId" json:"groupId" cyclonedx:"groupID"` ArtifactID string `mapstructure:"artifactId" json:"artifactId" cyclonedx:"artifactID"` Version string `mapstructure:"version" json:"version"` + Scope string `mapstructure:"scope" json:"scope,omitempty"` Extra map[string]string `mapstructure:",remain" json:"extraFields,omitempty"` } From 68f8df95948ac26f506966c15e8395f6360568bf Mon Sep 17 00:00:00 2001 From: Avi Deitcher Date: Thu, 1 Jun 2023 15:34:46 +0300 Subject: [PATCH 17/21] accept main.version ldflags even without vcs (#1855) Signed-off-by: Avi Deitcher --- syft/pkg/cataloger/golang/parse_go_binary.go | 51 ++++--- .../cataloger/golang/parse_go_binary_test.go | 131 +++++++++++++++++- 2 files changed, 154 insertions(+), 28 deletions(-) diff --git a/syft/pkg/cataloger/golang/parse_go_binary.go b/syft/pkg/cataloger/golang/parse_go_binary.go index e2e1c53eba6..89ed4ba0742 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary.go +++ b/syft/pkg/cataloger/golang/parse_go_binary.go @@ -85,35 +85,32 @@ func (c *goBinaryCataloger) makeGoMainPackage(resolver file.Resolver, mod *debug version, hasVersion := gbs["vcs.revision"] timestamp, hasTimestamp := gbs["vcs.time"] - if hasVersion { - if hasTimestamp { - //NOTE: err is ignored, because if parsing fails - // we still use the empty Time{} struct to generate an empty date, like 00010101000000 - // for consistency with the pseudo-version format: https://go.dev/ref/mod#pseudo-versions - ts, _ := time.Parse(time.RFC3339, timestamp) - if len(version) >= 12 { - version = version[:12] - } - - var ldflags string - if metadata, ok := main.Metadata.(pkg.GolangBinMetadata); ok { - ldflags = metadata.BuildSettings["-ldflags"] - } - - majorVersion, fullVersion := extractVersionFromLDFlags(ldflags) - if fullVersion != "" { - // we've found a specific version from the ldflags! use it as the version. - // why not combine that with the pseudo version (e.g. v1.2.3-0.20210101000000-abcdef123456)? - // short answer: we're assuming that if a specific semver was provided in the ldflags that - // there is a matching vcs tag to match that could be referenced. This assumption could - // be incorrect in terms of the go.mod contents, but is not incorrect in terms of the logical - // version of the package. - version = fullVersion - } else { - version = module.PseudoVersion(majorVersion, fullVersion, ts, version) - } + var ldflags string + if metadata, ok := main.Metadata.(pkg.GolangBinMetadata); ok { + // we've found a specific version from the ldflags! use it as the version. + // why not combine that with the pseudo version (e.g. v1.2.3-0.20210101000000-abcdef123456)? + // short answer: we're assuming that if a specific semver was provided in the ldflags that + // there is a matching vcs tag to match that could be referenced. This assumption could + // be incorrect in terms of the go.mod contents, but is not incorrect in terms of the logical + // version of the package. + ldflags = metadata.BuildSettings["-ldflags"] + } + + majorVersion, fullVersion := extractVersionFromLDFlags(ldflags) + if fullVersion != "" { + version = fullVersion + } else if hasVersion && hasTimestamp { + //NOTE: err is ignored, because if parsing fails + // we still use the empty Time{} struct to generate an empty date, like 00010101000000 + // for consistency with the pseudo-version format: https://go.dev/ref/mod#pseudo-versions + ts, _ := time.Parse(time.RFC3339, timestamp) + if len(version) >= 12 { + version = version[:12] } + version = module.PseudoVersion(majorVersion, fullVersion, ts, version) + } + if version != "" { main.Version = version main.PURL = packageURL(main.Name, main.Version) diff --git a/syft/pkg/cataloger/golang/parse_go_binary_test.go b/syft/pkg/cataloger/golang/parse_go_binary_test.go index 5f483b3c4f5..d578b46adaa 100644 --- a/syft/pkg/cataloger/golang/parse_go_binary_test.go +++ b/syft/pkg/cataloger/golang/parse_go_binary_test.go @@ -347,7 +347,7 @@ func TestBuildGoPkgInfo(t *testing.T) { }, }, { - name: "parse main mod and replace devel version with one from ldflags", + name: "parse main mod and replace devel version with one from ldflags with vcs. build settings", arch: archDetails, mod: &debug.BuildInfo{ GoVersion: goCompiledVersion, @@ -393,6 +393,135 @@ func TestBuildGoPkgInfo(t *testing.T) { }, }, }, + { + name: "parse main mod and replace devel version with one from ldflags without any vcs. build settings", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`}, + }, + }, + expected: []pkg.Package{ + { + Name: "github.com/anchore/syft", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Version: "v0.79.0", + PURL: "pkg:golang/github.com/anchore/syft@v0.79.0", + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + BuildSettings: map[string]string{ + "GOARCH": archDetails, + "GOOS": "darwin", + "GOAMD64": "v1", + "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X github.com/anchore/syft/internal/version.version=0.79.0`, + }, + MainModule: "github.com/anchore/syft", + }, + }, + }, + }, + { + name: "parse main mod and replace devel version with one from ldflags main.version without any vcs. build settings", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`}, + }, + }, + expected: []pkg.Package{ + { + Name: "github.com/anchore/syft", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Version: "v0.79.0", + PURL: "pkg:golang/github.com/anchore/syft@v0.79.0", + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + BuildSettings: map[string]string{ + "GOARCH": archDetails, + "GOOS": "darwin", + "GOAMD64": "v1", + "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X main.version=0.79.0`, + }, + MainModule: "github.com/anchore/syft", + }, + }, + }, + }, + { + name: "parse main mod and replace devel version with one from ldflags main.Version without any vcs. build settings", + arch: archDetails, + mod: &debug.BuildInfo{ + GoVersion: goCompiledVersion, + Main: debug.Module{Path: "github.com/anchore/syft", Version: "(devel)"}, + Settings: []debug.BuildSetting{ + {Key: "GOARCH", Value: archDetails}, + {Key: "GOOS", Value: "darwin"}, + {Key: "GOAMD64", Value: "v1"}, + {Key: "-ldflags", Value: `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`}, + }, + }, + expected: []pkg.Package{ + { + Name: "github.com/anchore/syft", + Language: pkg.Go, + Type: pkg.GoModulePkg, + Version: "v0.79.0", + PURL: "pkg:golang/github.com/anchore/syft@v0.79.0", + Locations: file.NewLocationSet( + file.NewLocationFromCoordinates( + file.Coordinates{ + RealPath: "/a-path", + FileSystemID: "layer-id", + }, + ).WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation), + ), + MetadataType: pkg.GolangBinMetadataType, + Metadata: pkg.GolangBinMetadata{ + GoCompiledVersion: goCompiledVersion, + Architecture: archDetails, + BuildSettings: map[string]string{ + "GOARCH": archDetails, + "GOOS": "darwin", + "GOAMD64": "v1", + "-ldflags": `build -ldflags="-w -s -extldflags '-static' -X main.Version=0.79.0`, + }, + MainModule: "github.com/anchore/syft", + }, + }, + }, + }, { name: "parse main mod and replace devel version with a pseudo version", arch: archDetails, From 1bd9de9047dcf83f83697be1e64848f26094fd6c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jun 2023 08:35:14 -0400 Subject: [PATCH 18/21] chore(deps): bump github.com/spf13/viper from 1.15.0 to 1.16.0 (#1851) Bumps [github.com/spf13/viper](https://github.com/spf13/viper) from 1.15.0 to 1.16.0. - [Release notes](https://github.com/spf13/viper/releases) - [Commits](https://github.com/spf13/viper/compare/v1.15.0...v1.16.0) --- updated-dependencies: - dependency-name: github.com/spf13/viper dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 11 +++++------ go.sum | 29 ++++++++++++++--------------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index f1d13d3a041..39ecdcb496e 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/spf13/afero v1.9.5 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 - github.com/spf13/viper v1.15.0 + github.com/spf13/viper v1.16.0 github.com/stretchr/testify v1.8.4 github.com/vifraa/gopom v0.2.1 github.com/wagoodman/go-partybus v0.0.0-20210627031916-db1f5573bbc5 @@ -121,16 +121,15 @@ require ( github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/nwaples/rardecode v1.1.0 // indirect github.com/opencontainers/image-spec v1.1.0-rc3 // indirect - github.com/pelletier/go-toml/v2 v2.0.6 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/pierrec/lz4/v4 v4.1.15 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.2.0 // indirect - github.com/rogpeppe/go-internal v1.8.0 // indirect github.com/shopspring/decimal v1.2.0 // indirect github.com/skeema/knownhosts v1.1.1 // indirect - github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/stretchr/objx v0.5.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect @@ -149,8 +148,8 @@ require ( golang.org/x/text v0.9.0 // indirect golang.org/x/tools v0.8.0 // indirect golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd // indirect - google.golang.org/grpc v1.54.0 // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/grpc v1.55.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect diff --git a/go.sum b/go.sum index 365f3f8a650..c0ede7b9cd1 100644 --- a/go.sum +++ b/go.sum @@ -202,7 +202,7 @@ github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/set v0.2.1 h1:nn2CaJyknWE/6txyUDGwysr3G5QC6xWB/PtVjPBbeaA= github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= @@ -414,7 +414,7 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -497,14 +497,13 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU= -github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= -github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -534,8 +533,7 @@ github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= -github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.3.0/go.mod h1:uD/D+6UF4SrIR1uGEv7bBNkNqLGqUr43MRiaGWX1Nig= @@ -569,8 +567,8 @@ github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM= github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA= +github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48= github.com/spf13/cobra v1.3.0/go.mod h1:BrRVncBjOJa/eUcVVm9CE+oC6as8k+VYr4NY7WCi9V4= github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= @@ -579,8 +577,8 @@ github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0 github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.10.0/go.mod h1:SoyBPwAtKDzypXNDFKN5kzH7ppppbGZtls1UpIy5AsM= -github.com/spf13/viper v1.15.0 h1:js3yy885G8xwJa6iOISGFwd+qlUo5AvyXb7CiihdtiU= -github.com/spf13/viper v1.15.0/go.mod h1:fFcTBJxvhhzSJiZy8n+PeW6t8l+KeT/uTARa0jHOQLA= +github.com/spf13/viper v1.16.0 h1:rGGH0XDZhdUOryiDWjmIvUSWpbNqisK8Wk0Vyefw8hc= +github.com/spf13/viper v1.16.0/go.mod h1:yg78JgCJcbrQOvV9YLXgkLaZqUidkY9K+Dd1FofRzQg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -597,6 +595,7 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= @@ -1083,8 +1082,8 @@ google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ6 google.golang.org/genproto v0.0.0-20211203200212-54befc351ae9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd h1:sLpv7bNL1AsX3fdnWh9WVh7ejIzXdOc1RRHGeAmeStU= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= @@ -1112,8 +1111,8 @@ google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnD google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.54.0 h1:EhTqbhiYeixwWQtAEZAxmV9MGqcjEU2mFx52xCzNyag= -google.golang.org/grpc v1.54.0/go.mod h1:PUSEXI6iWghWaB6lXM4knEgpJNu2qUcKfDtNci3EC2g= +google.golang.org/grpc v1.55.0 h1:3Oj82/tFSCeUrRTg/5E/7d/W5A1tj6Ky1ABAuZuv5ag= +google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= From 79a955b1a9aac71fbf58db3602d988035182f68b Mon Sep 17 00:00:00 2001 From: Keith Zantow Date: Mon, 5 Jun 2023 10:36:34 -0400 Subject: [PATCH 19/21] feat: source-version flag (#1859) --- cmd/syft/cli/attest/attest.go | 2 +- cmd/syft/cli/options/packages.go | 20 ++++++++- cmd/syft/cli/packages/packages.go | 2 +- cmd/syft/cli/poweruser/poweruser.go | 2 +- internal/config/application.go | 9 ++++ syft/source/metadata.go | 1 + syft/source/source.go | 69 +++++++++++++++++++++-------- syft/source/source_test.go | 2 +- 8 files changed, 83 insertions(+), 24 deletions(-) diff --git a/cmd/syft/cli/attest/attest.go b/cmd/syft/cli/attest/attest.go index 05e17867933..997f3307de2 100644 --- a/cmd/syft/cli/attest/attest.go +++ b/cmd/syft/cli/attest/attest.go @@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error { // could be an image or a directory, with or without a scheme // TODO: validate that source is image userInput := args[0] - si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource) + si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource) if err != nil { return fmt.Errorf("could not generate source input for packages command: %w", err) } diff --git a/cmd/syft/cli/options/packages.go b/cmd/syft/cli/options/packages.go index 7ab3b1fc23f..f6992a948c2 100644 --- a/cmd/syft/cli/options/packages.go +++ b/cmd/syft/cli/options/packages.go @@ -21,7 +21,8 @@ type PackagesOptions struct { Platform string Exclude []string Catalogers []string - Name string + SourceName string + SourceVersion string } var _ Interface = (*PackagesOptions)(nil) @@ -48,7 +49,14 @@ func (o *PackagesOptions) AddFlags(cmd *cobra.Command, v *viper.Viper) error { cmd.Flags().StringArrayVarP(&o.Catalogers, "catalogers", "", nil, "enable one or more package catalogers") - cmd.Flags().StringVarP(&o.Name, "name", "", "", + cmd.Flags().StringVarP(&o.SourceName, "name", "", "", + "set the name of the target being analyzed") + cmd.Flags().Lookup("name").Deprecated = "use: source-name" + + cmd.Flags().StringVarP(&o.SourceName, "source-name", "", "", + "set the name of the target being analyzed") + + cmd.Flags().StringVarP(&o.SourceVersion, "source-version", "", "", "set the name of the target being analyzed") return bindPackageConfigOptions(cmd.Flags(), v) @@ -78,6 +86,14 @@ func bindPackageConfigOptions(flags *pflag.FlagSet, v *viper.Viper) error { return err } + if err := v.BindPFlag("source-name", flags.Lookup("source-name")); err != nil { + return err + } + + if err := v.BindPFlag("source-version", flags.Lookup("source-version")); err != nil { + return err + } + if err := v.BindPFlag("output", flags.Lookup("output")); err != nil { return err } diff --git a/cmd/syft/cli/packages/packages.go b/cmd/syft/cli/packages/packages.go index 1f3acd13e56..12695e4f086 100644 --- a/cmd/syft/cli/packages/packages.go +++ b/cmd/syft/cli/packages/packages.go @@ -42,7 +42,7 @@ func Run(_ context.Context, app *config.Application, args []string) error { // could be an image or a directory, with or without a scheme userInput := args[0] - si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource) + si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource) if err != nil { return fmt.Errorf("could not generate source input for packages command: %w", err) } diff --git a/cmd/syft/cli/poweruser/poweruser.go b/cmd/syft/cli/poweruser/poweruser.go index b6fae72fef1..b4e524feadd 100644 --- a/cmd/syft/cli/poweruser/poweruser.go +++ b/cmd/syft/cli/poweruser/poweruser.go @@ -47,7 +47,7 @@ func Run(_ context.Context, app *config.Application, args []string) error { }() userInput := args[0] - si, err := source.ParseInputWithName(userInput, app.Platform, app.Name, app.DefaultImagePullSource) + si, err := source.ParseInputWithNameVersion(userInput, app.Platform, app.SourceName, app.SourceVersion, app.DefaultImagePullSource) if err != nil { return fmt.Errorf("could not generate source input for packages command: %w", err) } diff --git a/internal/config/application.go b/internal/config/application.go index 1a37594f30b..9f3274265fa 100644 --- a/internal/config/application.go +++ b/internal/config/application.go @@ -61,6 +61,8 @@ type Application struct { Exclusions []string `yaml:"exclude" json:"exclude" mapstructure:"exclude"` Platform string `yaml:"platform" json:"platform" mapstructure:"platform"` Name string `yaml:"name" json:"name" mapstructure:"name"` + SourceName string `yaml:"source-name" json:"source-name" mapstructure:"source-name"` + SourceVersion string `yaml:"source-version" json:"source-version" mapstructure:"source-version"` Parallelism int `yaml:"parallelism" json:"parallelism" mapstructure:"parallelism"` // the number of catalog workers to run in parallel DefaultImagePullSource string `yaml:"default-image-pull-source" json:"default-image-pull-source" mapstructure:"default-image-pull-source"` // specify default image pull source } @@ -143,6 +145,13 @@ func (cfg *Application) parseConfigValues() error { return err } + if cfg.Name != "" { + log.Warnf("name parameter is deprecated. please use: source-name. name will be removed in a future version") + if cfg.SourceName == "" { + cfg.SourceName = cfg.Name + } + } + // check for valid default source options // parse nested config options // for each field in the configuration struct, see if the field implements the parser interface diff --git a/syft/source/metadata.go b/syft/source/metadata.go index 1d29973b4ec..ecbad4f1dd8 100644 --- a/syft/source/metadata.go +++ b/syft/source/metadata.go @@ -8,4 +8,5 @@ type Metadata struct { Path string // the root path to be cataloged (directory only) Base string // the base path to be cataloged (directory only) Name string + Version string } diff --git a/syft/source/source.go b/syft/source/source.go index bc59b306d7a..4ff747ae297 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -48,6 +48,7 @@ type Input struct { Location string Platform string Name string + Version string } // ParseInput generates a source Input that can be used as an argument to generate a new source @@ -59,6 +60,12 @@ func ParseInput(userInput string, platform string) (*Input, error) { // ParseInputWithName generates a source Input that can be used as an argument to generate a new source // from specific providers including a registry, with an explicit name. func ParseInputWithName(userInput string, platform, name, defaultImageSource string) (*Input, error) { + return ParseInputWithNameVersion(userInput, platform, name, "", defaultImageSource) +} + +// ParseInputWithNameVersion generates a source Input that can be used as an argument to generate a new source +// from specific providers including a registry, with an explicit name and version. +func ParseInputWithNameVersion(userInput, platform, name, version, defaultImageSource string) (*Input, error) { fs := afero.NewOsFs() scheme, source, location, err := DetectScheme(fs, image.DetectSource, userInput) if err != nil { @@ -97,6 +104,7 @@ func ParseInputWithName(userInput string, platform, name, defaultImageSource str Location: location, Platform: platform, Name: name, + Version: version, }, nil } @@ -154,7 +162,7 @@ func generateImageSource(in Input, registryOptions *image.RegistryOptions) (*Sou return nil, cleanup, fmt.Errorf("could not fetch image %q: %w", in.Location, err) } - s, err := NewFromImageWithName(img, in.Location, in.Name) + s, err := NewFromImageWithNameVersion(img, in.Location, in.Name, in.Version) if err != nil { return nil, cleanup, fmt.Errorf("could not populate source with image: %w", err) } @@ -251,7 +259,7 @@ func generateDirectorySource(fs afero.Fs, in Input) (*Source, func(), error) { return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err) } - s, err := NewFromDirectoryWithName(in.Location, in.Name) + s, err := NewFromDirectoryWithNameVersion(in.Location, in.Name, in.Version) if err != nil { return nil, func() {}, fmt.Errorf("could not populate source from path=%q: %w", in.Location, err) } @@ -269,7 +277,7 @@ func generateFileSource(fs afero.Fs, in Input) (*Source, func(), error) { return nil, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", in.Location, err) } - s, cleanupFn := NewFromFileWithName(in.Location, in.Name) + s, cleanupFn := NewFromFileWithNameVersion(in.Location, in.Name, in.Version) return &s, cleanupFn, nil } @@ -279,19 +287,20 @@ func NewFromDirectory(path string) (Source, error) { return NewFromDirectoryWithName(path, "") } -// NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively. -func NewFromDirectoryRoot(path string) (Source, error) { - return NewFromDirectoryRootWithName(path, "") -} - // NewFromDirectoryWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name. func NewFromDirectoryWithName(path string, name string) (Source, error) { + return NewFromDirectoryWithNameVersion(path, name, "") +} + +// NewFromDirectoryWithNameVersion creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name. +func NewFromDirectoryWithNameVersion(path string, name string, version string) (Source, error) { s := Source{ mutex: &sync.Mutex{}, Metadata: Metadata{ - Name: name, - Scheme: DirectoryScheme, - Path: path, + Name: name, + Version: version, + Scheme: DirectoryScheme, + Path: path, }, path: path, } @@ -299,15 +308,26 @@ func NewFromDirectoryWithName(path string, name string) (Source, error) { return s, nil } +// NewFromDirectoryRoot creates a new source object tailored to catalog a given filesystem directory recursively. +func NewFromDirectoryRoot(path string) (Source, error) { + return NewFromDirectoryRootWithName(path, "") +} + // NewFromDirectoryRootWithName creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name. func NewFromDirectoryRootWithName(path string, name string) (Source, error) { + return NewFromDirectoryRootWithNameVersion(path, name, "") +} + +// NewFromDirectoryRootWithNameVersion creates a new source object tailored to catalog a given filesystem directory recursively, with an explicitly provided name. +func NewFromDirectoryRootWithNameVersion(path string, name string, version string) (Source, error) { s := Source{ mutex: &sync.Mutex{}, Metadata: Metadata{ - Name: name, - Scheme: DirectoryScheme, - Path: path, - Base: path, + Name: name, + Version: version, + Scheme: DirectoryScheme, + Path: path, + Base: path, }, path: path, base: path, @@ -323,14 +343,20 @@ func NewFromFile(path string) (Source, func()) { // NewFromFileWithName creates a new source object tailored to catalog a file, with an explicitly provided name. func NewFromFileWithName(path string, name string) (Source, func()) { + return NewFromFileWithNameVersion(path, name, "") +} + +// NewFromFileWithNameVersion creates a new source object tailored to catalog a file, with an explicitly provided name and version. +func NewFromFileWithNameVersion(path string, name string, version string) (Source, func()) { analysisPath, cleanupFn := fileAnalysisPath(path) s := Source{ mutex: &sync.Mutex{}, Metadata: Metadata{ - Name: name, - Scheme: FileScheme, - Path: path, + Name: name, + Version: version, + Scheme: FileScheme, + Path: path, }, path: analysisPath, } @@ -380,6 +406,12 @@ func NewFromImage(img *image.Image, userImageStr string) (Source, error) { // NewFromImageWithName creates a new source object tailored to catalog a given container image, relative to the // option given (e.g. all-layers, squashed, etc), with an explicit name. func NewFromImageWithName(img *image.Image, userImageStr string, name string) (Source, error) { + return NewFromImageWithNameVersion(img, userImageStr, name, "") +} + +// NewFromImageWithNameVersion creates a new source object tailored to catalog a given container image, relative to the +// option given (e.g. all-layers, squashed, etc), with an explicit name and version. +func NewFromImageWithNameVersion(img *image.Image, userImageStr string, name string, version string) (Source, error) { if img == nil { return Source{}, fmt.Errorf("no image given") } @@ -388,6 +420,7 @@ func NewFromImageWithName(img *image.Image, userImageStr string, name string) (S Image: img, Metadata: Metadata{ Name: name, + Version: version, Scheme: ImageScheme, ImageMetadata: NewImageMetadata(img, userImageStr), }, diff --git a/syft/source/source_test.go b/syft/source/source_test.go index 32af0c05202..66f3dc955ef 100644 --- a/syft/source/source_test.go +++ b/syft/source/source_test.go @@ -125,7 +125,7 @@ func TestSetID(t *testing.T) { Path: "test-fixtures/image-simple", }, }, - expected: artifact.ID("1b0dc351e6577b01"), + expected: artifact.ID("9ee9e786412d6ae5"), }, } From 903d29b6f71be548ef4fdc736c20918f5806f50f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 10:47:59 -0400 Subject: [PATCH 20/21] chore(deps): bump modernc.org/sqlite from 1.22.1 to 1.23.0 (#1863) Bumps [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) from 1.22.1 to 1.23.0. - [Commits](https://gitlab.com/cznic/sqlite/compare/v1.22.1...v1.23.0) --- updated-dependencies: - dependency-name: modernc.org/sqlite dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 39ecdcb496e..f617e93c2c0 100644 --- a/go.mod +++ b/go.mod @@ -69,7 +69,7 @@ require ( github.com/vbatts/go-mtree v0.5.3 golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b gopkg.in/yaml.v3 v3.0.1 - modernc.org/sqlite v1.22.1 + modernc.org/sqlite v1.23.0 ) require ( diff --git a/go.sum b/go.sum index c0ede7b9cd1..c23533b1f8d 100644 --- a/go.sum +++ b/go.sum @@ -1178,8 +1178,8 @@ modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= -modernc.org/sqlite v1.22.1 h1:P2+Dhp5FR1RlVRkQ3dDfCiv3Ok8XPxqpe70IjYVA9oE= -modernc.org/sqlite v1.22.1/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= +modernc.org/sqlite v1.23.0 h1:MWTFBI5H1WLnXpNBh/BTruBVqzzoh28DA0iOnlkkRaM= +modernc.org/sqlite v1.23.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= modernc.org/tcl v1.15.2 h1:C4ybAYCGJw968e+Me18oW55kD/FexcHbqH2xak1ROSY= From d676e5e7814162d55060354f52cce74f63f79840 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 5 Jun 2023 10:48:18 -0400 Subject: [PATCH 21/21] chore(deps): bump github.com/sirupsen/logrus from 1.9.2 to 1.9.3 (#1862) Bumps [github.com/sirupsen/logrus](https://github.com/sirupsen/logrus) from 1.9.2 to 1.9.3. - [Release notes](https://github.com/sirupsen/logrus/releases) - [Changelog](https://github.com/sirupsen/logrus/blob/master/CHANGELOG.md) - [Commits](https://github.com/sirupsen/logrus/compare/v1.9.2...v1.9.3) --- updated-dependencies: - dependency-name: github.com/sirupsen/logrus dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index f617e93c2c0..00012e15567 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,7 @@ require ( // pinned to pull in 386 arch fix: https://github.com/scylladb/go-set/commit/cc7b2070d91ebf40d233207b633e28f5bd8f03a5 github.com/scylladb/go-set v1.0.3-0.20200225121959-cc7b2070d91e github.com/sergi/go-diff v1.3.1 - github.com/sirupsen/logrus v1.9.2 + github.com/sirupsen/logrus v1.9.3 github.com/spdx/tools-golang v0.5.0 github.com/spf13/afero v1.9.5 github.com/spf13/cobra v1.7.0 diff --git a/go.sum b/go.sum index c23533b1f8d..966186b95e0 100644 --- a/go.sum +++ b/go.sum @@ -553,8 +553,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= -github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.1.1 h1:MTk78x9FPgDFVFkDLTrsnnfCJl7g1C/nnKvePgrIngE= github.com/skeema/knownhosts v1.1.1/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=