diff --git a/syft/source/content_requester.go b/syft/source/content_requester.go index ea7b7b5a8..ef99513c0 100644 --- a/syft/source/content_requester.go +++ b/syft/source/content_requester.go @@ -2,11 +2,14 @@ package source import "sync" +// ContentRequester is an object tailored for taking source.Location objects which file contents will be resolved +// upon invoking Execute(). type ContentRequester struct { request map[Location][]*FileData lock sync.Mutex } +// NewContentRequester creates a new ContentRequester object with the given initial request data. func NewContentRequester(data ...*FileData) *ContentRequester { requester := &ContentRequester{ request: make(map[Location][]*FileData), @@ -17,19 +20,24 @@ func NewContentRequester(data ...*FileData) *ContentRequester { return requester } -func (b *ContentRequester) Add(data *FileData) { - b.lock.Lock() - defer b.lock.Unlock() - b.request[data.Location] = append(b.request[data.Location], data) +// Add appends a new single FileData containing a source.Location to later have the contents fetched and stored within +// the given FileData object. +func (r *ContentRequester) Add(data *FileData) { + r.lock.Lock() + defer r.lock.Unlock() + + r.request[data.Location] = append(r.request[data.Location], data) } -func (b *ContentRequester) Execute(resolver ContentResolver) error { - b.lock.Lock() - defer b.lock.Unlock() +// Execute takes the previously provided source.Location's and resolves the file contents, storing the results within +// the previously provided FileData objects. +func (r *ContentRequester) Execute(resolver ContentResolver) error { + r.lock.Lock() + defer r.lock.Unlock() - var locations = make([]Location, len(b.request)) + var locations = make([]Location, len(r.request)) idx := 0 - for l := range b.request { + for l := range r.request { locations[idx] = l idx++ } @@ -40,8 +48,8 @@ func (b *ContentRequester) Execute(resolver ContentResolver) error { } for l, contents := range response { - for i := range b.request[l] { - b.request[l][i].Contents = contents + for i := range r.request[l] { + r.request[l][i].Contents = contents } } return nil diff --git a/syft/source/content_requester_test.go b/syft/source/content_requester_test.go new file mode 100644 index 000000000..1ca914703 --- /dev/null +++ b/syft/source/content_requester_test.go @@ -0,0 +1,69 @@ +package source + +import ( + "testing" + + "github.com/anchore/stereoscope/pkg/imagetest" + "github.com/sergi/go-diff/diffmatchpatch" +) + +func TestContentRequester(t *testing.T) { + tests := []struct { + fixture string + expectedContents map[string]string + }{ + { + fixture: "image-simple", + expectedContents: map[string]string{ + "/somefile-1.txt": "this file has contents", + "/somefile-2.txt": "file-2 contents!", + "/really/nested/file-3.txt": "another file!\nwith lines...", + }, + }, + } + + for _, test := range tests { + t.Run(test.fixture, func(t *testing.T) { + img, cleanup := imagetest.GetFixtureImage(t, "docker-archive", "image-simple") + defer cleanup() + + resolver, err := NewAllLayersResolver(img) + if err != nil { + t.Fatalf("could not create resolver: %+v", err) + } + + var data []*FileData + for path := range test.expectedContents { + + locations, err := resolver.FilesByPath(path) + if err != nil { + t.Fatalf("could not build request: %+v", err) + } + if len(locations) != 1 { + t.Fatalf("bad resolver paths: %+v", locations) + } + + data = append(data, &FileData{ + Location: locations[0], + }) + } + + if err := NewContentRequester(data...).Execute(resolver); err != nil { + t.Fatalf("could not execute request: %+v", err) + } + + for _, entry := range data { + if expected, ok := test.expectedContents[entry.Location.Path]; ok { + for expected != entry.Contents { + t.Errorf("mismatched contents for %q", entry.Location.Path) + dmp := diffmatchpatch.New() + diffs := dmp.DiffMain(expected, entry.Contents, true) + t.Errorf("diff: %s", dmp.DiffPrettyText(diffs)) + } + continue + } + t.Errorf("could not find %q", entry.Location.Path) + } + }) + } +} diff --git a/syft/source/directory_resolver_test.go b/syft/source/directory_resolver_test.go index f2bcad89f..fdc82971f 100644 --- a/syft/source/directory_resolver_test.go +++ b/syft/source/directory_resolver_test.go @@ -180,7 +180,7 @@ func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) { func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) { t.Run("finds multiple matching files", func(t *testing.T) { - resolver := DirectoryResolver{"test-fixtures"} + resolver := DirectoryResolver{"test-fixtures/image-symlinks"} refs, err := resolver.FilesByGlob("**/*.txt") if err != nil { diff --git a/syft/source/test-fixtures/image-simple/Dockerfile b/syft/source/test-fixtures/image-simple/Dockerfile new file mode 100644 index 000000000..62fb151e4 --- /dev/null +++ b/syft/source/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/source/test-fixtures/image-simple/file-1.txt b/syft/source/test-fixtures/image-simple/file-1.txt new file mode 100644 index 000000000..985d3408e --- /dev/null +++ b/syft/source/test-fixtures/image-simple/file-1.txt @@ -0,0 +1 @@ +this file has contents \ No newline at end of file diff --git a/syft/source/test-fixtures/image-simple/file-2.txt b/syft/source/test-fixtures/image-simple/file-2.txt new file mode 100644 index 000000000..396d08bbc --- /dev/null +++ b/syft/source/test-fixtures/image-simple/file-2.txt @@ -0,0 +1 @@ +file-2 contents! \ No newline at end of file diff --git a/syft/source/test-fixtures/image-simple/target/really/nested/file-3.txt b/syft/source/test-fixtures/image-simple/target/really/nested/file-3.txt new file mode 100644 index 000000000..f85472c93 --- /dev/null +++ b/syft/source/test-fixtures/image-simple/target/really/nested/file-3.txt @@ -0,0 +1,2 @@ +another file! +with lines... \ No newline at end of file