Projects STRLCPY syft Commits 07e76907
🤬
Showing first 200 files as there are too many
  • ■ ■ ■ ■ ■ ■
    DEVELOPING.md
    skipped 166 lines
    167 167   
    168 168  #### Searching for files
    169 169   
    170  -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
     170 +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
    171 171  abstractions leverage [`stereoscope`](https://github.com/anchore/stereoscope) in order to perform searching. Here is a
    172 172  rough outline how that works:
    173 173   
    174  -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.
    175  -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.
     174 +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.
     175 +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.
    176 176  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).
    177 177   
    178 178  ## Testing
    skipped 155 lines
  • ■ ■ ■ ■ ■ ■
    cmd/syft/cli/eventloop/tasks.go
    skipped 7 lines
    8 8   "github.com/anchore/syft/syft"
    9 9   "github.com/anchore/syft/syft/artifact"
    10 10   "github.com/anchore/syft/syft/file"
     11 + "github.com/anchore/syft/syft/file/cataloger/filecontent"
     12 + "github.com/anchore/syft/syft/file/cataloger/filedigest"
     13 + "github.com/anchore/syft/syft/file/cataloger/filemetadata"
     14 + "github.com/anchore/syft/syft/file/cataloger/secrets"
    11 15   "github.com/anchore/syft/syft/sbom"
    12 16   "github.com/anchore/syft/syft/source"
    13 17  )
    skipped 47 lines
    61 65   return nil, nil
    62 66   }
    63 67   
    64  - metadataCataloger := file.NewMetadataCataloger()
     68 + metadataCataloger := filemetadata.NewCataloger()
    65 69   
    66 70   task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
    67 71   resolver, err := src.FileResolver(app.FileMetadata.Cataloger.ScopeOpt)
    skipped 36 lines
    104 108   hashes = append(hashes, hashObj)
    105 109   }
    106 110   
    107  - digestsCataloger, err := file.NewDigestsCataloger(hashes)
    108  - if err != nil {
    109  - return nil, err
    110  - }
     111 + digestsCataloger := filedigest.NewCataloger(hashes)
    111 112   
    112 113   task := func(results *sbom.Artifacts, src *source.Source) ([]artifact.Relationship, error) {
    113 114   resolver, err := src.FileResolver(app.FileMetadata.Cataloger.ScopeOpt)
    skipped 17 lines
    131 132   return nil, nil
    132 133   }
    133 134   
    134  - patterns, err := file.GenerateSearchPatterns(file.DefaultSecretsPatterns, app.Secrets.AdditionalPatterns, app.Secrets.ExcludePatternNames)
     135 + patterns, err := secrets.GenerateSearchPatterns(secrets.DefaultSecretsPatterns, app.Secrets.AdditionalPatterns, app.Secrets.ExcludePatternNames)
    135 136   if err != nil {
    136 137   return nil, err
    137 138   }
    138 139   
    139  - secretsCataloger, err := file.NewSecretsCataloger(patterns, app.Secrets.RevealValues, app.Secrets.SkipFilesAboveSize)
     140 + secretsCataloger, err := secrets.NewCataloger(patterns, app.Secrets.RevealValues, app.Secrets.SkipFilesAboveSize) //nolint:staticcheck
    140 141   if err != nil {
    141 142   return nil, err
    142 143   }
    skipped 20 lines
    163 164   return nil, nil
    164 165   }
    165 166   
    166  - contentsCataloger, err := file.NewContentsCataloger(app.FileContents.Globs, app.FileContents.SkipFilesAboveSize)
     167 + contentsCataloger, err := filecontent.NewCataloger(app.FileContents.Globs, app.FileContents.SkipFilesAboveSize) //nolint:staticcheck
    167 168   if err != nil {
    168 169   return nil, err
    169 170   }
    skipped 32 lines
  • ■ ■ ■ ■ ■ ■
    internal/licenses/parser.go
    skipped 4 lines
    5 5   
    6 6   "github.com/google/licensecheck"
    7 7   
     8 + "github.com/anchore/syft/syft/file"
    8 9   "github.com/anchore/syft/syft/license"
    9 10   "github.com/anchore/syft/syft/pkg"
    10  - "github.com/anchore/syft/syft/source"
    11 11  )
    12 12   
    13 13  const (
    skipped 2 lines
    16 16  )
    17 17   
    18 18  // Parse scans the contents of a license file to attempt to determine the type of license it is
    19  -func Parse(reader io.Reader, l source.Location) (licenses []pkg.License, err error) {
     19 +func Parse(reader io.Reader, l file.Location) (licenses []pkg.License, err error) {
    20 20   licenses = make([]pkg.License, 0)
    21 21   contents, err := io.ReadAll(reader)
    22 22   if err != nil {
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    syft/event/parsers/parsers.go
    skipped 11 lines
    12 12   
    13 13   "github.com/anchore/syft/syft/event"
    14 14   "github.com/anchore/syft/syft/event/monitor"
    15  - "github.com/anchore/syft/syft/file"
     15 + "github.com/anchore/syft/syft/file/cataloger/secrets"
    16 16   "github.com/anchore/syft/syft/pkg/cataloger"
    17 17  )
    18 18   
    skipped 35 lines
    54 54   return &monitor, nil
    55 55  }
    56 56   
    57  -func ParseSecretsCatalogingStarted(e partybus.Event) (*file.SecretsMonitor, error) {
     57 +func ParseSecretsCatalogingStarted(e partybus.Event) (*secrets.Monitor, error) {
    58 58   if err := checkEventType(e.Type, event.SecretsCatalogerStarted); err != nil {
    59 59   return nil, err
    60 60   }
    61 61   
    62  - monitor, ok := e.Value.(file.SecretsMonitor)
     62 + monitor, ok := e.Value.(secrets.Monitor)
    63 63   if !ok {
    64 64   return nil, newPayloadErr(e.Type, "Value", e.Value)
    65 65   }
    skipped 123 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/contents_cataloger.go syft/file/cataloger/filecontent/cataloger.go
    1  -package file
     1 +package filecontent
    2 2   
    3 3  import (
    4 4   "bytes"
    skipped 3 lines
    8 8   
    9 9   "github.com/anchore/syft/internal"
    10 10   "github.com/anchore/syft/internal/log"
    11  - "github.com/anchore/syft/syft/source"
     11 + "github.com/anchore/syft/syft/file"
    12 12  )
    13 13   
    14  -type ContentsCataloger struct {
     14 +// Deprecated: will be removed in syft v1.0.0
     15 +type Cataloger struct {
    15 16   globs []string
    16 17   skipFilesAboveSizeInBytes int64
    17 18  }
    18 19   
    19  -func NewContentsCataloger(globs []string, skipFilesAboveSize int64) (*ContentsCataloger, error) {
    20  - return &ContentsCataloger{
     20 +// Deprecated: will be removed in syft v1.0.0
     21 +func NewCataloger(globs []string, skipFilesAboveSize int64) (*Cataloger, error) {
     22 + return &Cataloger{
    21 23   globs: globs,
    22 24   skipFilesAboveSizeInBytes: skipFilesAboveSize,
    23 25   }, nil
    24 26  }
    25 27   
    26  -func (i *ContentsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates]string, error) {
    27  - results := make(map[source.Coordinates]string)
    28  - var locations []source.Location
     28 +func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates]string, error) {
     29 + results := make(map[file.Coordinates]string)
     30 + var locations []file.Location
    29 31   
    30 32   locations, err := resolver.FilesByGlob(i.globs...)
    31 33   if err != nil {
    skipped 24 lines
    56 58   return results, nil
    57 59  }
    58 60   
    59  -func (i *ContentsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) (string, error) {
     61 +func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) (string, error) {
    60 62   contentReader, err := resolver.FileContentsByLocation(location)
    61 63   if err != nil {
    62 64   return "", err
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/cataloger/filecontent/cataloger_test.go
     1 +package filecontent
     2 + 
     3 +import (
     4 + "testing"
     5 + 
     6 + "github.com/stretchr/testify/assert"
     7 + 
     8 + "github.com/anchore/syft/syft/file"
     9 +)
     10 + 
     11 +func TestContentsCataloger(t *testing.T) {
     12 + allFiles := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"}
     13 + 
     14 + tests := []struct {
     15 + name string
     16 + globs []string
     17 + maxSize int64
     18 + files []string
     19 + expected map[file.Coordinates]string
     20 + }{
     21 + {
     22 + name: "multi-pattern",
     23 + globs: []string{"test-fixtures/last/*.txt", "test-fixtures/*.txt"},
     24 + files: allFiles,
     25 + expected: map[file.Coordinates]string{
     26 + file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
     27 + file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
     28 + file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
     29 + },
     30 + },
     31 + {
     32 + name: "no-patterns",
     33 + globs: []string{},
     34 + files: []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"},
     35 + expected: map[file.Coordinates]string{},
     36 + },
     37 + {
     38 + name: "all-txt",
     39 + globs: []string{"**/*.txt"},
     40 + files: allFiles,
     41 + expected: map[file.Coordinates]string{
     42 + file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
     43 + file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
     44 + file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
     45 + },
     46 + },
     47 + {
     48 + name: "subpath",
     49 + globs: []string{"test-fixtures/*.txt"},
     50 + files: allFiles,
     51 + expected: map[file.Coordinates]string{
     52 + file.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
     53 + file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
     54 + },
     55 + },
     56 + {
     57 + name: "size-filter",
     58 + maxSize: 42,
     59 + globs: []string{"**/*.txt"},
     60 + files: allFiles,
     61 + expected: map[file.Coordinates]string{
     62 + file.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
     63 + file.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
     64 + },
     65 + },
     66 + }
     67 + 
     68 + for _, test := range tests {
     69 + t.Run(test.name, func(t *testing.T) {
     70 + c, err := NewCataloger(test.globs, test.maxSize)
     71 + assert.NoError(t, err)
     72 + 
     73 + resolver := file.NewMockResolverForPaths(test.files...)
     74 + actual, err := c.Catalog(resolver)
     75 + assert.NoError(t, err)
     76 + assert.Equal(t, test.expected, actual, "mismatched contents")
     77 + 
     78 + })
     79 + }
     80 +}
     81 + 
  • syft/file/test-fixtures/a-path.txt syft/file/cataloger/filecontent/test-fixtures/a-path.txt
    Content is identical
  • syft/file/test-fixtures/another-path.txt syft/file/cataloger/filecontent/test-fixtures/another-path.txt
    Content is identical
  • syft/file/test-fixtures/last/empty/empty syft/file/cataloger/filecontent/test-fixtures/last/empty/empty
    Content is identical
  • ■ ■ ■ ■ ■
    syft/file/cataloger/filecontent/test-fixtures/last/path.txt
     1 +test-fixtures/last/path.txt file contents!
  • ■ ■ ■ ■ ■ ■
    syft/file/cataloger/filedigest/cataloger.go
     1 +package filedigest
     2 + 
     3 +import (
     4 + "crypto"
     5 + "errors"
     6 + 
     7 + "github.com/wagoodman/go-partybus"
     8 + "github.com/wagoodman/go-progress"
     9 + 
     10 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
     11 + "github.com/anchore/syft/internal"
     12 + "github.com/anchore/syft/internal/bus"
     13 + "github.com/anchore/syft/internal/log"
     14 + "github.com/anchore/syft/syft/event"
     15 + "github.com/anchore/syft/syft/file"
     16 + internal2 "github.com/anchore/syft/syft/file/cataloger/internal"
     17 +)
     18 + 
     19 +var ErrUndigestableFile = errors.New("undigestable file")
     20 + 
     21 +type Cataloger struct {
     22 + hashes []crypto.Hash
     23 +}
     24 + 
     25 +func NewCataloger(hashes []crypto.Hash) *Cataloger {
     26 + return &Cataloger{
     27 + hashes: hashes,
     28 + }
     29 +}
     30 + 
     31 +func (i *Cataloger) Catalog(resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates][]file.Digest, error) {
     32 + results := make(map[file.Coordinates][]file.Digest)
     33 + var locations []file.Location
     34 + 
     35 + if len(coordinates) == 0 {
     36 + locations = internal2.AllRegularFiles(resolver)
     37 + } else {
     38 + for _, c := range coordinates {
     39 + locations = append(locations, file.NewLocationFromCoordinates(c))
     40 + }
     41 + }
     42 + 
     43 + stage, prog := digestsCatalogingProgress(int64(len(locations)))
     44 + for _, location := range locations {
     45 + stage.Current = location.RealPath
     46 + result, err := i.catalogLocation(resolver, location)
     47 + 
     48 + if errors.Is(err, ErrUndigestableFile) {
     49 + continue
     50 + }
     51 + 
     52 + if internal.IsErrPathPermission(err) {
     53 + log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err)
     54 + continue
     55 + }
     56 + 
     57 + if err != nil {
     58 + return nil, err
     59 + }
     60 + prog.Increment()
     61 + results[location.Coordinates] = result
     62 + }
     63 + log.Debugf("file digests cataloger processed %d files", prog.Current())
     64 + prog.SetCompleted()
     65 + return results, nil
     66 +}
     67 + 
     68 +func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.Digest, error) {
     69 + meta, err := resolver.FileMetadataByLocation(location)
     70 + if err != nil {
     71 + return nil, err
     72 + }
     73 + 
     74 + // we should only attempt to report digests for files that are regular files (don't attempt to resolve links)
     75 + if meta.Type != stereoscopeFile.TypeRegular {
     76 + return nil, ErrUndigestableFile
     77 + }
     78 + 
     79 + contentReader, err := resolver.FileContentsByLocation(location)
     80 + if err != nil {
     81 + return nil, err
     82 + }
     83 + defer internal.CloseAndLogError(contentReader, location.VirtualPath)
     84 + 
     85 + digests, err := file.NewDigestsFromFile(contentReader, i.hashes)
     86 + if err != nil {
     87 + return nil, internal.ErrPath{Context: "digests-cataloger", Path: location.RealPath, Err: err}
     88 + }
     89 + 
     90 + return digests, nil
     91 +}
     92 + 
     93 +func digestsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) {
     94 + stage := &progress.Stage{}
     95 + prog := progress.NewManual(locations)
     96 + 
     97 + bus.Publish(partybus.Event{
     98 + Type: event.FileDigestsCatalogerStarted,
     99 + Value: struct {
     100 + progress.Stager
     101 + progress.Progressable
     102 + }{
     103 + Stager: progress.Stager(stage),
     104 + Progressable: prog,
     105 + },
     106 + })
     107 + 
     108 + return stage, prog
     109 +}
     110 + 
  • ■ ■ ■ ■ ■ ■
    syft/file/digest_cataloger_test.go syft/file/cataloger/filedigest/cataloger_test.go
    1  -package file
     1 +package filedigest
    2 2   
    3 3  import (
    4 4   "crypto"
    5 5   "fmt"
    6  - "io/ioutil"
     6 + "io"
    7 7   "os"
    8 8   "path/filepath"
    9 9   "testing"
    skipped 1 lines
    11 11   "github.com/stretchr/testify/assert"
    12 12   "github.com/stretchr/testify/require"
    13 13   
    14  - "github.com/anchore/stereoscope/pkg/file"
     14 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    15 15   "github.com/anchore/stereoscope/pkg/imagetest"
     16 + "github.com/anchore/syft/syft/file"
    16 17   "github.com/anchore/syft/syft/source"
    17 18  )
    18 19   
    19  -func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Hash) map[source.Coordinates][]Digest {
    20  - digests := make(map[source.Coordinates][]Digest)
     20 +func testDigests(t testing.TB, root string, files []string, hashes ...crypto.Hash) map[file.Coordinates][]file.Digest {
     21 + digests := make(map[file.Coordinates][]file.Digest)
    21 22   
    22 23   for _, f := range files {
    23 24   fh, err := os.Open(filepath.Join(root, f))
    24 25   if err != nil {
    25 26   t.Fatalf("could not open %q : %+v", f, err)
    26 27   }
    27  - b, err := ioutil.ReadAll(fh)
     28 + b, err := io.ReadAll(fh)
    28 29   if err != nil {
    29 30   t.Fatalf("could not read %q : %+v", f, err)
    30 31   }
    31 32   
     33 + if len(b) == 0 {
     34 + // we don't keep digests for empty files
     35 + digests[file.NewLocation(f).Coordinates] = []file.Digest{}
     36 + continue
     37 + }
     38 + 
    32 39   for _, hash := range hashes {
    33 40   h := hash.New()
    34 41   h.Write(b)
    35  - digests[source.NewLocation(f).Coordinates] = append(digests[source.NewLocation(f).Coordinates], Digest{
    36  - Algorithm: CleanDigestAlgorithmName(hash.String()),
     42 + digests[file.NewLocation(f).Coordinates] = append(digests[file.NewLocation(f).Coordinates], file.Digest{
     43 + Algorithm: file.CleanDigestAlgorithmName(hash.String()),
    37 44   Value: fmt.Sprintf("%x", h.Sum(nil)),
    38 45   })
    39 46   }
    skipped 8 lines
    48 55   name string
    49 56   digests []crypto.Hash
    50 57   files []string
    51  - expected map[source.Coordinates][]Digest
     58 + expected map[file.Coordinates][]file.Digest
    52 59   }{
    53 60   {
    54 61   name: "md5",
    skipped 11 lines
    66 73   
    67 74   for _, test := range tests {
    68 75   t.Run(test.name, func(t *testing.T) {
    69  - c, err := NewDigestsCataloger(test.digests)
    70  - require.NoError(t, err)
     76 + c := NewCataloger(test.digests)
    71 77   
    72 78   src, err := source.NewFromDirectory("test-fixtures/last/")
    73 79   require.NoError(t, err)
    skipped 12 lines
    86 92  func TestDigestsCataloger_MixFileTypes(t *testing.T) {
    87 93   testImage := "image-file-type-mix"
    88 94   
    89  - if *updateImageGoldenFiles {
    90  - imagetest.UpdateGoldenFixtureImage(t, testImage)
    91  - }
    92  - 
    93  - img := imagetest.GetGoldenFixtureImage(t, testImage)
     95 + img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
    94 96   
    95 97   src, err := source.NewFromImage(img, "---")
    96 98   if err != nil {
    skipped 13 lines
    110 112   path: "/file-1.txt",
    111 113   expected: "888c139e550867814eb7c33b84d76e4d",
    112 114   },
    113  - {
    114  - path: "/hardlink-1",
    115  - },
     115 + // this is difficult to reproduce in a cross-platform way
     116 + //{
     117 + // path: "/hardlink-1",
     118 + //},
    116 119   {
    117 120   path: "/symlink-1",
    118 121   },
    skipped 13 lines
    132 135   
    133 136   for _, test := range tests {
    134 137   t.Run(test.path, func(t *testing.T) {
    135  - c, err := NewDigestsCataloger([]crypto.Hash{crypto.MD5})
    136  - if err != nil {
    137  - t.Fatalf("unable to get cataloger: %+v", err)
    138  - }
     138 + c := NewCataloger([]crypto.Hash{crypto.MD5})
    139 139   
    140 140   actual, err := c.Catalog(resolver)
    141 141   if err != nil {
    142 142   t.Fatalf("could not catalog: %+v", err)
    143 143   }
    144 144   
    145  - _, ref, err := img.SquashedTree().File(file.Path(test.path))
     145 + _, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path))
    146 146   if err != nil {
    147 147   t.Fatalf("unable to get file=%q : %+v", test.path, err)
    148 148   }
    149  - l := source.NewLocationFromImage(test.path, *ref.Reference, img)
     149 + l := file.NewLocationFromImage(test.path, *ref.Reference, img)
    150 150   
    151 151   if len(actual[l.Coordinates]) == 0 {
    152 152   if test.expected != "" {
    skipped 10 lines
  • ■ ■ ■ ■
    syft/file/test-fixtures/image-file-type-mix/Dockerfile syft/file/cataloger/filedigest/test-fixtures/image-file-type-mix/Dockerfile
    1  -FROM busybox:latest
     1 +FROM busybox:1.28.1@sha256:c7b0a24019b0e6eda714ec0fa137ad42bc44a754d9cea17d14fba3a80ccc1ee4
    2 2   
    3 3  ADD file-1.txt .
    4 4  RUN chmod 644 file-1.txt
    skipped 9 lines
  • syft/file/test-fixtures/image-file-type-mix/file-1.txt syft/file/cataloger/filedigest/test-fixtures/image-file-type-mix/file-1.txt
    Content is identical
  • ■ ■ ■ ■ ■
    syft/file/cataloger/filedigest/test-fixtures/last/empty/empty
     1 + 
  • syft/file/test-fixtures/last/path.txt syft/file/cataloger/filedigest/test-fixtures/last/path.txt
    Content is identical
  • ■ ■ ■ ■ ■ ■
    syft/file/cataloger/filemetadata/cataloger.go
     1 +package filemetadata
     2 + 
     3 +import (
     4 + "github.com/wagoodman/go-partybus"
     5 + "github.com/wagoodman/go-progress"
     6 + 
     7 + "github.com/anchore/syft/internal/bus"
     8 + "github.com/anchore/syft/internal/log"
     9 + "github.com/anchore/syft/syft/event"
     10 + "github.com/anchore/syft/syft/file"
     11 +)
     12 + 
     13 +type Cataloger struct {
     14 +}
     15 + 
     16 +func NewCataloger() *Cataloger {
     17 + return &Cataloger{}
     18 +}
     19 + 
     20 +func (i *Cataloger) Catalog(resolver file.Resolver, coordinates ...file.Coordinates) (map[file.Coordinates]file.Metadata, error) {
     21 + results := make(map[file.Coordinates]file.Metadata)
     22 + var locations <-chan file.Location
     23 + 
     24 + if len(coordinates) == 0 {
     25 + locations = resolver.AllLocations()
     26 + } else {
     27 + locations = func() <-chan file.Location {
     28 + ch := make(chan file.Location)
     29 + go func() {
     30 + close(ch)
     31 + for _, c := range coordinates {
     32 + ch <- file.NewLocationFromCoordinates(c)
     33 + }
     34 + }()
     35 + return ch
     36 + }()
     37 + }
     38 + 
     39 + stage, prog := metadataCatalogingProgress(int64(len(locations)))
     40 + for location := range locations {
     41 + stage.Current = location.RealPath
     42 + metadata, err := resolver.FileMetadataByLocation(location)
     43 + if err != nil {
     44 + return nil, err
     45 + }
     46 + 
     47 + results[location.Coordinates] = metadata
     48 + prog.Increment()
     49 + }
     50 + log.Debugf("file metadata cataloger processed %d files", prog.Current())
     51 + prog.SetCompleted()
     52 + return results, nil
     53 +}
     54 + 
     55 +func metadataCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) {
     56 + stage := &progress.Stage{}
     57 + prog := progress.NewManual(locations)
     58 + 
     59 + bus.Publish(partybus.Event{
     60 + Type: event.FileMetadataCatalogerStarted,
     61 + Value: struct {
     62 + progress.Stager
     63 + progress.Progressable
     64 + }{
     65 + Stager: progress.Stager(stage),
     66 + Progressable: prog,
     67 + },
     68 + })
     69 + 
     70 + return stage, prog
     71 +}
     72 + 
  • ■ ■ ■ ■ ■ ■
    syft/file/metadata_cataloger_test.go syft/file/cataloger/filemetadata/cataloger_test.go
    1  -package file
     1 +package filemetadata
    2 2   
    3 3  import (
    4  - "flag"
    5 4   "os"
    6 5   "testing"
    7 6   
    8 7   "github.com/stretchr/testify/assert"
    9 8   "github.com/stretchr/testify/require"
    10 9   
    11  - "github.com/anchore/stereoscope/pkg/file"
     10 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    12 11   "github.com/anchore/stereoscope/pkg/imagetest"
     12 + "github.com/anchore/syft/syft/file"
    13 13   "github.com/anchore/syft/syft/source"
    14 14  )
    15  - 
    16  -var updateImageGoldenFiles = flag.Bool("update-image", false, "update the golden fixture images used for testing")
    17 15   
    18 16  func TestFileMetadataCataloger(t *testing.T) {
    19 17   testImage := "image-file-type-mix"
    20 18   
    21  - if *updateImageGoldenFiles {
    22  - imagetest.UpdateGoldenFixtureImage(t, testImage)
    23  - }
    24  - 
    25  - img := imagetest.GetGoldenFixtureImage(t, testImage)
     19 + img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
    26 20   
    27  - c := NewMetadataCataloger()
     21 + c := NewCataloger()
    28 22   
    29 23   src, err := source.NewFromImage(img, "---")
    30 24   if err != nil {
    skipped 13 lines
    44 38   tests := []struct {
    45 39   path string
    46 40   exists bool
    47  - expected source.FileMetadata
     41 + expected file.Metadata
    48 42   err bool
    49 43   }{
     44 + // note: it is difficult to add a hardlink-based test in a cross-platform way and is already covered well in stereoscope
    50 45   {
    51 46   path: "/file-1.txt",
    52 47   exists: true,
    53  - expected: source.FileMetadata{
    54  - FileInfo: file.ManualInfo{
     48 + expected: file.Metadata{
     49 + FileInfo: stereoscopeFile.ManualInfo{
    55 50   NameValue: "file-1.txt",
    56 51   ModeValue: 0644,
    57 52   SizeValue: 7,
    58 53   },
    59 54   Path: "/file-1.txt",
    60  - Type: file.TypeRegular,
     55 + Type: stereoscopeFile.TypeRegular,
    61 56   UserID: 1,
    62 57   GroupID: 2,
    63 58   MIMEType: "text/plain",
    64 59   },
    65 60   },
    66 61   {
    67  - path: "/hardlink-1",
    68  - exists: true,
    69  - expected: source.FileMetadata{
    70  - FileInfo: file.ManualInfo{
    71  - NameValue: "hardlink-1",
    72  - ModeValue: 0644,
    73  - },
    74  - Path: "/hardlink-1",
    75  - Type: file.TypeHardLink,
    76  - LinkDestination: "file-1.txt",
    77  - UserID: 1,
    78  - GroupID: 2,
    79  - MIMEType: "",
    80  - },
    81  - },
    82  - {
    83 62   path: "/symlink-1",
    84 63   exists: true,
    85  - expected: source.FileMetadata{
     64 + expected: file.Metadata{
    86 65   Path: "/symlink-1",
    87  - FileInfo: file.ManualInfo{
     66 + FileInfo: stereoscopeFile.ManualInfo{
    88 67   NameValue: "symlink-1",
    89 68   ModeValue: 0777 | os.ModeSymlink,
    90 69   },
    91  - Type: file.TypeSymLink,
     70 + Type: stereoscopeFile.TypeSymLink,
    92 71   LinkDestination: "file-1.txt",
    93 72   UserID: 0,
    94 73   GroupID: 0,
    skipped 3 lines
    98 77   {
    99 78   path: "/char-device-1",
    100 79   exists: true,
    101  - expected: source.FileMetadata{
     80 + expected: file.Metadata{
    102 81   Path: "/char-device-1",
    103  - FileInfo: file.ManualInfo{
     82 + FileInfo: stereoscopeFile.ManualInfo{
    104 83   NameValue: "char-device-1",
    105 84   ModeValue: 0644 | os.ModeDevice | os.ModeCharDevice,
    106 85   },
    107  - Type: file.TypeCharacterDevice,
     86 + Type: stereoscopeFile.TypeCharacterDevice,
    108 87   UserID: 0,
    109 88   GroupID: 0,
    110 89   MIMEType: "",
    skipped 2 lines
    113 92   {
    114 93   path: "/block-device-1",
    115 94   exists: true,
    116  - expected: source.FileMetadata{
     95 + expected: file.Metadata{
    117 96   Path: "/block-device-1",
    118  - FileInfo: file.ManualInfo{
     97 + FileInfo: stereoscopeFile.ManualInfo{
    119 98   NameValue: "block-device-1",
    120 99   ModeValue: 0644 | os.ModeDevice,
    121 100   },
    122  - Type: file.TypeBlockDevice,
     101 + Type: stereoscopeFile.TypeBlockDevice,
    123 102   UserID: 0,
    124 103   GroupID: 0,
    125 104   MIMEType: "",
    skipped 2 lines
    128 107   {
    129 108   path: "/fifo-1",
    130 109   exists: true,
    131  - expected: source.FileMetadata{
     110 + expected: file.Metadata{
    132 111   Path: "/fifo-1",
    133  - FileInfo: file.ManualInfo{
     112 + FileInfo: stereoscopeFile.ManualInfo{
    134 113   NameValue: "fifo-1",
    135 114   ModeValue: 0644 | os.ModeNamedPipe,
    136 115   },
    137  - Type: file.TypeFIFO,
     116 + Type: stereoscopeFile.TypeFIFO,
    138 117   UserID: 0,
    139 118   GroupID: 0,
    140 119   MIMEType: "",
    skipped 2 lines
    143 122   {
    144 123   path: "/bin",
    145 124   exists: true,
    146  - expected: source.FileMetadata{
     125 + expected: file.Metadata{
    147 126   Path: "/bin",
    148  - FileInfo: file.ManualInfo{
     127 + FileInfo: stereoscopeFile.ManualInfo{
    149 128   NameValue: "bin",
    150 129   ModeValue: 0755 | os.ModeDir,
    151 130   },
    152  - Type: file.TypeDirectory,
     131 + Type: stereoscopeFile.TypeDirectory,
    153 132   UserID: 0,
    154 133   GroupID: 0,
    155 134   MIMEType: "",
    skipped 3 lines
    159 138   
    160 139   for _, test := range tests {
    161 140   t.Run(test.path, func(t *testing.T) {
    162  - _, ref, err := img.SquashedTree().File(file.Path(test.path))
     141 + _, ref, err := img.SquashedTree().File(stereoscopeFile.Path(test.path))
    163 142   require.NoError(t, err)
    164 143   
    165  - l := source.NewLocationFromImage(test.path, *ref.Reference, img)
     144 + l := file.NewLocationFromImage(test.path, *ref.Reference, img)
    166 145   
    167 146   if _, ok := actual[l.Coordinates]; ok {
    168 147   // we're not interested in keeping the test fixtures up to date with the latest file modification times
    169 148   // thus ModTime is not under test
    170  - fi := test.expected.FileInfo.(file.ManualInfo)
     149 + fi := test.expected.FileInfo.(stereoscopeFile.ManualInfo)
    171 150   fi.ModTimeValue = actual[l.Coordinates].ModTime()
    172 151   test.expected.FileInfo = fi
    173 152   }
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/cataloger/filemetadata/test-fixtures/image-file-type-mix/Dockerfile
     1 +FROM busybox:1.28.1@sha256:c7b0a24019b0e6eda714ec0fa137ad42bc44a754d9cea17d14fba3a80ccc1ee4
     2 + 
     3 +ADD file-1.txt .
     4 +RUN chmod 644 file-1.txt
     5 +RUN chown 1:2 file-1.txt
     6 +RUN ln -s file-1.txt symlink-1
     7 +# note: hard links may behave inconsistently, this should be a golden image
     8 +RUN ln file-1.txt hardlink-1
     9 +RUN mknod char-device-1 c 89 1
     10 +RUN mknod block-device-1 b 0 1
     11 +RUN mknod fifo-1 p
     12 +RUN mkdir /dir
     13 +RUN rm -rf home etc/group etc/localtime etc/mtab etc/network etc/passwd etc/shadow var usr bin/*
  • ■ ■ ■ ■ ■
    syft/file/cataloger/filemetadata/test-fixtures/image-file-type-mix/file-1.txt
     1 +file 1!
  • ■ ■ ■ ■ ■ ■
    syft/file/all_regular_files.go syft/file/cataloger/internal/all_regular_files.go
    1  -package file
     1 +package internal
    2 2   
    3 3  import (
    4  - "github.com/anchore/stereoscope/pkg/file"
     4 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    5 5   "github.com/anchore/syft/internal/log"
    6  - "github.com/anchore/syft/syft/source"
     6 + "github.com/anchore/syft/syft/file"
    7 7  )
    8 8   
    9  -func allRegularFiles(resolver source.FileResolver) (locations []source.Location) {
     9 +func AllRegularFiles(resolver file.Resolver) (locations []file.Location) {
    10 10   for location := range resolver.AllLocations() {
    11 11   resolvedLocations, err := resolver.FilesByPath(location.RealPath)
    12 12   if err != nil {
    skipped 8 lines
    21 21   continue
    22 22   }
    23 23   
    24  - if metadata.Type != file.TypeRegular {
     24 + if metadata.Type != stereoscopeFile.TypeRegular {
    25 25   continue
    26 26   }
    27 27   locations = append(locations, resolvedLocation)
    skipped 5 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/all_regular_files_test.go syft/file/cataloger/internal/all_regular_files_test.go
    1  -package file
     1 +package internal
    2 2   
    3 3  import (
    4 4   "testing"
    skipped 4 lines
    9 9   "github.com/stretchr/testify/require"
    10 10   
    11 11   "github.com/anchore/stereoscope/pkg/imagetest"
     12 + "github.com/anchore/syft/syft/file"
    12 13   "github.com/anchore/syft/syft/source"
    13 14  )
    14 15   
    15 16  func Test_allRegularFiles(t *testing.T) {
    16  - type access struct {
    17  - realPath string
    18  - virtualPath string
    19  - }
    20 17   tests := []struct {
    21 18   name string
    22  - setup func() source.FileResolver
     19 + setup func() file.Resolver
    23 20   wantRealPaths *strset.Set
    24 21   wantVirtualPaths *strset.Set
    25 22   }{
    26 23   {
    27 24   name: "image",
    28  - setup: func() source.FileResolver {
     25 + setup: func() file.Resolver {
    29 26   testImage := "image-file-type-mix"
    30 27   
    31  - if *updateImageGoldenFiles {
    32  - imagetest.UpdateGoldenFixtureImage(t, testImage)
    33  - }
    34  - 
    35  - img := imagetest.GetGoldenFixtureImage(t, testImage)
     28 + img := imagetest.GetFixtureImage(t, "docker-archive", testImage)
    36 29   
    37 30   s, err := source.NewFromImage(img, "---")
    38 31   require.NoError(t, err)
    skipped 8 lines
    47 40   },
    48 41   {
    49 42   name: "directory",
    50  - setup: func() source.FileResolver {
     43 + setup: func() file.Resolver {
    51 44   s, err := source.NewFromDirectory("test-fixtures/symlinked-root/nested/link-root")
    52 45   require.NoError(t, err)
    53 46   r, err := s.FileResolver(source.SquashedScope)
    skipped 7 lines
    61 54   for _, tt := range tests {
    62 55   t.Run(tt.name, func(t *testing.T) {
    63 56   resolver := tt.setup()
    64  - locations := allRegularFiles(resolver)
     57 + locations := AllRegularFiles(resolver)
    65 58   realLocations := strset.New()
    66 59   virtualLocations := strset.New()
    67 60   for _, l := range locations {
    skipped 2 lines
    70 63   virtualLocations.Add(l.VirtualPath)
    71 64   }
    72 65   }
     66 + 
     67 + // this is difficult to reproduce in a cross-platform way
     68 + realLocations.Remove("/hardlink-1")
     69 + virtualLocations.Remove("/hardlink-1")
     70 + tt.wantRealPaths.Remove("/hardlink-1")
     71 + tt.wantVirtualPaths.Remove("/hardlink-1")
     72 + 
    73 73   assert.ElementsMatch(t, tt.wantRealPaths.List(), realLocations.List(), "real paths differ: "+cmp.Diff(tt.wantRealPaths.List(), realLocations.List()))
    74 74   assert.ElementsMatch(t, tt.wantVirtualPaths.List(), virtualLocations.List(), "virtual paths differ: "+cmp.Diff(tt.wantVirtualPaths.List(), virtualLocations.List()))
    75 75   })
    skipped 3 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/cataloger/internal/test-fixtures/image-file-type-mix/Dockerfile
     1 +FROM busybox:1.28.1@sha256:c7b0a24019b0e6eda714ec0fa137ad42bc44a754d9cea17d14fba3a80ccc1ee4
     2 + 
     3 +ADD file-1.txt .
     4 +RUN chmod 644 file-1.txt
     5 +RUN chown 1:2 file-1.txt
     6 +RUN ln -s file-1.txt symlink-1
     7 +# note: hard links may behave inconsistently, this should be a golden image
     8 +RUN ln file-1.txt hardlink-1
     9 +RUN mknod char-device-1 c 89 1
     10 +RUN mknod block-device-1 b 0 1
     11 +RUN mknod fifo-1 p
     12 +RUN mkdir /dir
     13 +RUN rm -rf home etc/group etc/localtime etc/mtab etc/network etc/passwd etc/shadow var usr bin/*
  • ■ ■ ■ ■ ■
    syft/file/cataloger/internal/test-fixtures/image-file-type-mix/file-1.txt
     1 +file 1!
  • syft/file/test-fixtures/symlinked-root/real-root/file1.txt syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/file1.txt
    Content is identical
  • syft/file/test-fixtures/symlinked-root/real-root/nested/file2.txt syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/nested/file2.txt
    Content is identical
  • syft/file/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt syft/file/cataloger/internal/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt
    Content is identical
  • ■ ■ ■ ■ ■ ■
    syft/file/secrets_cataloger.go syft/file/cataloger/secrets/cataloger.go
    1  -package file
     1 +package secrets
    2 2   
    3 3  import (
    4 4   "bytes"
    skipped 9 lines
    14 14   "github.com/anchore/syft/internal/bus"
    15 15   "github.com/anchore/syft/internal/log"
    16 16   "github.com/anchore/syft/syft/event"
    17  - "github.com/anchore/syft/syft/source"
     17 + "github.com/anchore/syft/syft/file"
     18 + internal2 "github.com/anchore/syft/syft/file/cataloger/internal"
    18 19  )
    19 20   
    20 21  var DefaultSecretsPatterns = map[string]string{
    skipped 4 lines
    25 26   "generic-api-key": `(?i)api(-|_)?key["'=:\s]*?(?P<value>[A-Z0-9]{20,60})["']?(\s|$)`,
    26 27  }
    27 28   
    28  -type SecretsCataloger struct {
     29 +// Deprecated: will be removed in syft v1.0.0
     30 +type Cataloger struct {
    29 31   patterns map[string]*regexp.Regexp
    30 32   revealValues bool
    31 33   skipFilesAboveSize int64
    32 34  }
    33 35   
    34  -func NewSecretsCataloger(patterns map[string]*regexp.Regexp, revealValues bool, maxFileSize int64) (*SecretsCataloger, error) {
    35  - return &SecretsCataloger{
     36 +// Deprecated: will be removed in syft v1.0.0
     37 +func NewCataloger(patterns map[string]*regexp.Regexp, revealValues bool, maxFileSize int64) (*Cataloger, error) {
     38 + return &Cataloger{
    36 39   patterns: patterns,
    37 40   revealValues: revealValues,
    38 41   skipFilesAboveSize: maxFileSize,
    39 42   }, nil
    40 43  }
    41 44   
    42  -func (i *SecretsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]SearchResult, error) {
    43  - results := make(map[source.Coordinates][]SearchResult)
    44  - locations := allRegularFiles(resolver)
     45 +func (i *Cataloger) Catalog(resolver file.Resolver) (map[file.Coordinates][]file.SearchResult, error) {
     46 + results := make(map[file.Coordinates][]file.SearchResult)
     47 + locations := internal2.AllRegularFiles(resolver)
    45 48   stage, prog, secretsDiscovered := secretsCatalogingProgress(int64(len(locations)))
    46 49   for _, location := range locations {
    47 50   stage.Current = location.RealPath
    skipped 17 lines
    65 68   return results, nil
    66 69  }
    67 70   
    68  -func (i *SecretsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) ([]SearchResult, error) {
     71 +func (i *Cataloger) catalogLocation(resolver file.Resolver, location file.Location) ([]file.SearchResult, error) {
    69 72   metadata, err := resolver.FileMetadataByLocation(location)
    70 73   if err != nil {
    71 74   return nil, err
    skipped 31 lines
    103 106   return secrets, nil
    104 107  }
    105 108   
    106  -func extractValue(resolver source.FileResolver, location source.Location, start, length int64) (string, error) {
     109 +func extractValue(resolver file.Resolver, location file.Location, start, length int64) (string, error) {
    107 110   readCloser, err := resolver.FileContentsByLocation(location)
    108 111   if err != nil {
    109 112   return "", fmt.Errorf("unable to fetch reader for location=%q : %w", location, err)
    skipped 20 lines
    130 133   return buf.String(), nil
    131 134  }
    132 135   
    133  -type SecretsMonitor struct {
     136 +type Monitor struct {
    134 137   progress.Stager
    135 138   SecretsDiscovered progress.Monitorable
    136 139   progress.Progressable
    skipped 7 lines
    144 147   bus.Publish(partybus.Event{
    145 148   Type: event.SecretsCatalogerStarted,
    146 149   Source: secretsDiscovered,
    147  - Value: SecretsMonitor{
     150 + Value: Monitor{
    148 151   Stager: progress.Stager(stage),
    149 152   SecretsDiscovered: secretsDiscovered,
    150 153   Progressable: prog,
    skipped 6 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/secrets_cataloger_test.go syft/file/cataloger/secrets/cataloger_test.go
    1  -package file
     1 +package secrets
    2 2   
    3 3  import (
    4 4   "regexp"
    skipped 1 lines
    6 6   
    7 7   "github.com/stretchr/testify/assert"
    8 8   
    9  - "github.com/anchore/syft/internal/file"
    10  - "github.com/anchore/syft/syft/source"
     9 + intFile "github.com/anchore/syft/internal/file"
     10 + "github.com/anchore/syft/syft/file"
    11 11  )
    12 12   
    13 13  func TestSecretsCataloger(t *testing.T) {
    skipped 3 lines
    17 17   reveal bool
    18 18   maxSize int64
    19 19   patterns map[string]string
    20  - expected []SearchResult
     20 + expected []file.SearchResult
    21 21   constructorErr bool
    22 22   catalogErr bool
    23 23   }{
    skipped 4 lines
    28 28   patterns: map[string]string{
    29 29   "simple-secret-key": `^secret_key=.*`,
    30 30   },
    31  - expected: []SearchResult{
     31 + expected: []file.SearchResult{
    32 32   {
    33 33   Classification: "simple-secret-key",
    34 34   LineNumber: 2,
    skipped 11 lines
    46 46   patterns: map[string]string{
    47 47   "simple-secret-key": `^secret_key=.*`,
    48 48   },
    49  - expected: []SearchResult{
     49 + expected: []file.SearchResult{
    50 50   {
    51 51   Classification: "simple-secret-key",
    52 52   LineNumber: 2,
    skipped 11 lines
    64 64   patterns: map[string]string{
    65 65   "simple-secret-key": `^secret_key=(?P<value>.*)`,
    66 66   },
    67  - expected: []SearchResult{
     67 + expected: []file.SearchResult{
    68 68   {
    69 69   Classification: "simple-secret-key",
    70 70   LineNumber: 2,
    skipped 11 lines
    82 82   patterns: map[string]string{
    83 83   "simple-secret-key": `secret_key=.*`,
    84 84   },
    85  - expected: []SearchResult{
     85 + expected: []file.SearchResult{
    86 86   {
    87 87   Classification: "simple-secret-key",
    88 88   LineNumber: 1,
    skipped 36 lines
    125 125   patterns: map[string]string{
    126 126   "simple-secret-key": `secret_key=(?P<value>.*)`,
    127 127   },
    128  - expected: []SearchResult{
     128 + expected: []file.SearchResult{
    129 129   {
    130 130   Classification: "simple-secret-key",
    131 131   LineNumber: 1,
    skipped 44 lines
    176 176   regexObjs[name] = obj
    177 177   }
    178 178   
    179  - c, err := NewSecretsCataloger(regexObjs, test.reveal, test.maxSize)
     179 + c, err := NewCataloger(regexObjs, test.reveal, test.maxSize)
    180 180   if err != nil && !test.constructorErr {
    181 181   t.Fatalf("could not create cataloger (but should have been able to): %+v", err)
    182 182   } else if err == nil && test.constructorErr {
    skipped 2 lines
    185 185   return
    186 186   }
    187 187   
    188  - resolver := source.NewMockResolverForPaths(test.fixture)
     188 + resolver := file.NewMockResolverForPaths(test.fixture)
    189 189   
    190 190   actualResults, err := c.Catalog(resolver)
    191 191   if err != nil && !test.catalogErr {
    skipped 4 lines
    196 196   return
    197 197   }
    198 198   
    199  - loc := source.NewLocation(test.fixture)
     199 + loc := file.NewLocation(test.fixture)
    200 200   if _, exists := actualResults[loc.Coordinates]; !exists {
    201 201   t.Fatalf("could not find location=%q in results", loc)
    202 202   }
    skipped 11 lines
    214 214   
    215 215   tests := []struct {
    216 216   fixture string
    217  - expected []SearchResult
     217 + expected []file.SearchResult
    218 218   }{
    219 219   {
    220 220   fixture: "test-fixtures/secrets/default/aws.env",
    221  - expected: []SearchResult{
     221 + expected: []file.SearchResult{
    222 222   {
    223 223   Classification: "aws-access-key",
    224 224   LineNumber: 2,
    skipped 14 lines
    239 239   },
    240 240   {
    241 241   fixture: "test-fixtures/secrets/default/aws.ini",
    242  - expected: []SearchResult{
     242 + expected: []file.SearchResult{
    243 243   {
    244 244   Classification: "aws-access-key",
    245 245   LineNumber: 3,
    skipped 14 lines
    260 260   },
    261 261   {
    262 262   fixture: "test-fixtures/secrets/default/private-key.pem",
    263  - expected: []SearchResult{
     263 + expected: []file.SearchResult{
    264 264   {
    265 265   Classification: "pem-private-key",
    266 266   LineNumber: 2,
    skipped 13 lines
    280 280   },
    281 281   {
    282 282   fixture: "test-fixtures/secrets/default/private-key-openssl.pem",
    283  - expected: []SearchResult{
     283 + expected: []file.SearchResult{
    284 284   {
    285 285   Classification: "pem-private-key",
    286 286   LineNumber: 2,
    skipped 15 lines
    302 302   // note: this test proves that the PEM regex matches the smallest possible match
    303 303   // since the test catches two adjacent secrets
    304 304   fixture: "test-fixtures/secrets/default/private-keys.pem",
    305  - expected: []SearchResult{
     305 + expected: []file.SearchResult{
    306 306   {
    307 307   Classification: "pem-private-key",
    308 308   LineNumber: 1,
    skipped 36 lines
    345 345   // 2. a named capture group with the correct line number and line offset case
    346 346   // 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
    347 347   fixture: "test-fixtures/secrets/default/docker-config.json",
    348  - expected: []SearchResult{
     348 + expected: []file.SearchResult{
    349 349   {
    350 350   Classification: "docker-config-auth",
    351 351   LineNumber: 5,
    skipped 10 lines
    362 362   },
    363 363   {
    364 364   fixture: "test-fixtures/secrets/default/api-key.txt",
    365  - expected: []SearchResult{
     365 + expected: []file.SearchResult{
    366 366   {
    367 367   Classification: "generic-api-key",
    368 368   LineNumber: 2,
    skipped 49 lines
    418 418   for _, test := range tests {
    419 419   t.Run(test.fixture, func(t *testing.T) {
    420 420   
    421  - c, err := NewSecretsCataloger(regexObjs, true, 10*file.MB)
     421 + c, err := NewCataloger(regexObjs, true, 10*intFile.MB)
    422 422   if err != nil {
    423 423   t.Fatalf("could not create cataloger: %+v", err)
    424 424   }
    425 425   
    426  - resolver := source.NewMockResolverForPaths(test.fixture)
     426 + resolver := file.NewMockResolverForPaths(test.fixture)
    427 427   
    428 428   actualResults, err := c.Catalog(resolver)
    429 429   if err != nil {
    430 430   t.Fatalf("could not catalog: %+v", err)
    431 431   }
    432 432   
    433  - loc := source.NewLocation(test.fixture)
     433 + loc := file.NewLocation(test.fixture)
    434 434   if _, exists := actualResults[loc.Coordinates]; !exists && test.expected != nil {
    435 435   t.Fatalf("could not find location=%q in results", loc)
    436 436   } else if !exists && test.expected == nil {
    skipped 8 lines
  • ■ ■ ■ ■
    syft/file/generate_search_patterns.go syft/file/cataloger/secrets/generate_search_patterns.go
    1  -package file
     1 +package secrets
    2 2   
    3 3  import (
    4 4   "fmt"
    skipped 53 lines
  • ■ ■ ■ ■
    syft/file/generate_search_patterns_test.go syft/file/cataloger/secrets/generate_search_patterns_test.go
    1  -package file
     1 +package secrets
    2 2   
    3 3  import (
    4 4   "testing"
    skipped 122 lines
  • ■ ■ ■ ■
    syft/file/newline_counter.go syft/file/cataloger/secrets/newline_counter.go
    1  -package file
     1 +package secrets
    2 2   
    3 3  import "io"
    4 4   
    skipped 36 lines
  • ■ ■ ■ ■
    syft/file/newline_counter_test.go syft/file/cataloger/secrets/newline_counter_test.go
    1  -package file
     1 +package secrets
    2 2   
    3 3  import (
    4 4   "bufio"
    skipped 32 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/secrets_search_by_line_strategy.go syft/file/cataloger/secrets/secrets_search_by_line_strategy.go
    1  -package file
     1 +package secrets
    2 2   
    3 3  import (
    4 4   "bufio"
    skipped 3 lines
    8 8   "regexp"
    9 9   
    10 10   "github.com/anchore/syft/internal"
    11  - "github.com/anchore/syft/syft/source"
     11 + "github.com/anchore/syft/syft/file"
    12 12  )
    13 13   
    14  -func catalogLocationByLine(resolver source.FileResolver, location source.Location, patterns map[string]*regexp.Regexp) ([]SearchResult, error) {
     14 +func catalogLocationByLine(resolver file.Resolver, location file.Location, patterns map[string]*regexp.Regexp) ([]file.SearchResult, error) {
    15 15   readCloser, err := resolver.FileContentsByLocation(location)
    16 16   if err != nil {
    17 17   return nil, fmt.Errorf("unable to fetch reader for location=%q : %w", location, err)
    skipped 2 lines
    20 20   
    21 21   var scanner = bufio.NewReader(readCloser)
    22 22   var position int64
    23  - var allSecrets []SearchResult
     23 + var allSecrets []file.SearchResult
    24 24   var lineNo int64
    25 25   var readErr error
    26 26   for !errors.Is(readErr, io.EOF) {
    skipped 16 lines
    43 43   return allSecrets, nil
    44 44  }
    45 45   
    46  -func searchForSecretsWithinLine(resolver source.FileResolver, location source.Location, patterns map[string]*regexp.Regexp, line []byte, lineNo int64, position int64) ([]SearchResult, error) {
    47  - var secrets []SearchResult
     46 +func searchForSecretsWithinLine(resolver file.Resolver, location file.Location, patterns map[string]*regexp.Regexp, line []byte, lineNo int64, position int64) ([]file.SearchResult, error) {
     47 + var secrets []file.SearchResult
    48 48   for name, pattern := range patterns {
    49 49   matches := pattern.FindAllIndex(line, -1)
    50 50   for i, match := range matches {
    skipped 21 lines
    72 72   return secrets, nil
    73 73  }
    74 74   
    75  -func readerAtPosition(resolver source.FileResolver, location source.Location, seekPosition int64) (io.ReadCloser, error) {
     75 +func readerAtPosition(resolver file.Resolver, location file.Location, seekPosition int64) (io.ReadCloser, error) {
    76 76   readCloser, err := resolver.FileContentsByLocation(location)
    77 77   if err != nil {
    78 78   return nil, fmt.Errorf("unable to fetch reader for location=%q : %w", location, err)
    skipped 10 lines
    89 89   return readCloser, nil
    90 90  }
    91 91   
    92  -func extractSecretFromPosition(readCloser io.ReadCloser, name string, pattern *regexp.Regexp, lineNo, lineOffset, seekPosition int64) *SearchResult {
     92 +func extractSecretFromPosition(readCloser io.ReadCloser, name string, pattern *regexp.Regexp, lineNo, lineOffset, seekPosition int64) *file.SearchResult {
    93 93   reader := &newlineCounter{RuneReader: bufio.NewReader(readCloser)}
    94 94   positions := pattern.FindReaderSubmatchIndex(reader)
    95 95   if len(positions) == 0 {
    skipped 29 lines
    125 125   lineOffsetOfSecret += lineOffset
    126 126   }
    127 127   
    128  - return &SearchResult{
     128 + return &file.SearchResult{
    129 129   Classification: name,
    130 130   SeekPosition: start + seekPosition,
    131 131   Length: stop - start,
    skipped 5 lines
  • syft/file/test-fixtures/secrets/default/api-key.txt syft/file/cataloger/secrets/test-fixtures/secrets/default/api-key.txt
    Content is identical
  • syft/file/test-fixtures/secrets/default/aws.env syft/file/cataloger/secrets/test-fixtures/secrets/default/aws.env
    Content is identical
  • syft/file/test-fixtures/secrets/default/aws.ini syft/file/cataloger/secrets/test-fixtures/secrets/default/aws.ini
    Content is identical
  • syft/file/test-fixtures/secrets/default/docker-config.json syft/file/cataloger/secrets/test-fixtures/secrets/default/docker-config.json
    Content is identical
  • syft/file/test-fixtures/secrets/default/not-docker-config.json syft/file/cataloger/secrets/test-fixtures/secrets/default/not-docker-config.json
    Content is identical
  • syft/file/test-fixtures/secrets/default/private-key-false-positive.pem syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key-false-positive.pem
    Content is identical
  • syft/file/test-fixtures/secrets/default/private-key-openssl.pem syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key-openssl.pem
    Content is identical
  • syft/file/test-fixtures/secrets/default/private-key.pem syft/file/cataloger/secrets/test-fixtures/secrets/default/private-key.pem
    Content is identical
  • syft/file/test-fixtures/secrets/default/private-keys.pem syft/file/cataloger/secrets/test-fixtures/secrets/default/private-keys.pem
    Content is identical
  • syft/file/test-fixtures/secrets/multiple.txt syft/file/cataloger/secrets/test-fixtures/secrets/multiple.txt
    Content is identical
  • syft/file/test-fixtures/secrets/simple.txt syft/file/cataloger/secrets/test-fixtures/secrets/simple.txt
    Content is identical
  • ■ ■ ■ ■ ■ ■
    syft/file/contents_cataloger_test.go
    1  -package file
    2  - 
    3  -import (
    4  - "testing"
    5  - 
    6  - "github.com/stretchr/testify/assert"
    7  - 
    8  - "github.com/anchore/syft/syft/source"
    9  -)
    10  - 
    11  -func TestContentsCataloger(t *testing.T) {
    12  - allFiles := []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"}
    13  - 
    14  - tests := []struct {
    15  - name string
    16  - globs []string
    17  - maxSize int64
    18  - files []string
    19  - expected map[source.Coordinates]string
    20  - }{
    21  - {
    22  - name: "multi-pattern",
    23  - globs: []string{"test-fixtures/last/*.txt", "test-fixtures/*.txt"},
    24  - files: allFiles,
    25  - expected: map[source.Coordinates]string{
    26  - source.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
    27  - source.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
    28  - source.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
    29  - },
    30  - },
    31  - {
    32  - name: "no-patterns",
    33  - globs: []string{},
    34  - files: []string{"test-fixtures/last/path.txt", "test-fixtures/another-path.txt", "test-fixtures/a-path.txt"},
    35  - expected: map[source.Coordinates]string{},
    36  - },
    37  - {
    38  - name: "all-txt",
    39  - globs: []string{"**/*.txt"},
    40  - files: allFiles,
    41  - expected: map[source.Coordinates]string{
    42  - source.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
    43  - source.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
    44  - source.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
    45  - },
    46  - },
    47  - {
    48  - name: "subpath",
    49  - globs: []string{"test-fixtures/*.txt"},
    50  - files: allFiles,
    51  - expected: map[source.Coordinates]string{
    52  - source.NewLocation("test-fixtures/another-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hbm90aGVyLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
    53  - source.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
    54  - },
    55  - },
    56  - {
    57  - name: "size-filter",
    58  - maxSize: 42,
    59  - globs: []string{"**/*.txt"},
    60  - files: allFiles,
    61  - expected: map[source.Coordinates]string{
    62  - source.NewLocation("test-fixtures/last/path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9sYXN0L3BhdGgudHh0IGZpbGUgY29udGVudHMh",
    63  - source.NewLocation("test-fixtures/a-path.txt").Coordinates: "dGVzdC1maXh0dXJlcy9hLXBhdGgudHh0IGZpbGUgY29udGVudHMh",
    64  - },
    65  - },
    66  - }
    67  - 
    68  - for _, test := range tests {
    69  - t.Run(test.name, func(t *testing.T) {
    70  - c, err := NewContentsCataloger(test.globs, test.maxSize)
    71  - assert.NoError(t, err)
    72  - 
    73  - resolver := source.NewMockResolverForPaths(test.files...)
    74  - actual, err := c.Catalog(resolver)
    75  - assert.NoError(t, err)
    76  - assert.Equal(t, test.expected, actual, "mismatched contents")
    77  - 
    78  - })
    79  - }
    80  -}
    81  - 
  • ■ ■ ■ ■
    syft/source/coordinate_set.go syft/file/coordinate_set.go
    1  -package source
     1 +package file
    2 2   
    3 3  import (
    4 4   "sort"
    skipped 83 lines
  • ■ ■ ■ ■
    syft/source/coordinate_set_test.go syft/file/coordinate_set_test.go
    1  -package source
     1 +package file
    2 2   
    3 3  import (
    4 4   "testing"
    skipped 115 lines
  • ■ ■ ■ ■
    syft/source/coordinates.go syft/file/coordinates.go
    1  -package source
     1 +package file
    2 2   
    3 3  import (
    4 4   "fmt"
    skipped 31 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/digest.go
    1 1  package file
    2 2   
     3 +import (
     4 + "crypto"
     5 + "fmt"
     6 + "hash"
     7 + "io"
     8 + "strings"
     9 +)
     10 + 
    3 11  type Digest struct {
    4 12   Algorithm string `json:"algorithm"`
    5 13   Value string `json:"value"`
    6 14  }
    7 15   
     16 +func NewDigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]Digest, error) {
     17 + // create a set of hasher objects tied together with a single writer to feed content into
     18 + hashers := make([]hash.Hash, len(hashes))
     19 + writers := make([]io.Writer, len(hashes))
     20 + for idx, hashObj := range hashes {
     21 + hashers[idx] = hashObj.New()
     22 + writers[idx] = hashers[idx]
     23 + }
     24 + 
     25 + size, err := io.Copy(io.MultiWriter(writers...), closer)
     26 + if err != nil {
     27 + return nil, err
     28 + }
     29 + 
     30 + if size == 0 {
     31 + return make([]Digest, 0), nil
     32 + }
     33 + 
     34 + result := make([]Digest, len(hashes))
     35 + // only capture digests when there is content. It is important to do this based on SIZE and not
     36 + // FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
     37 + // file type but a body is still allowed.
     38 + for idx, hasher := range hashers {
     39 + result[idx] = Digest{
     40 + Algorithm: DigestAlgorithmName(hashes[idx]),
     41 + Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
     42 + }
     43 + }
     44 + 
     45 + return result, nil
     46 +}
     47 + 
     48 +func Hashers(names ...string) ([]crypto.Hash, error) {
     49 + supportedHashAlgorithms := make(map[string]crypto.Hash)
     50 + for _, h := range []crypto.Hash{
     51 + crypto.MD5,
     52 + crypto.SHA1,
     53 + crypto.SHA256,
     54 + } {
     55 + supportedHashAlgorithms[DigestAlgorithmName(h)] = h
     56 + }
     57 + 
     58 + var hashers []crypto.Hash
     59 + for _, hashStr := range names {
     60 + hashObj, ok := supportedHashAlgorithms[CleanDigestAlgorithmName(hashStr)]
     61 + if !ok {
     62 + return nil, fmt.Errorf("unsupported hash algorithm: %s", hashStr)
     63 + }
     64 + hashers = append(hashers, hashObj)
     65 + }
     66 + return hashers, nil
     67 +}
     68 + 
     69 +func DigestAlgorithmName(hash crypto.Hash) string {
     70 + return CleanDigestAlgorithmName(hash.String())
     71 +}
     72 + 
     73 +func CleanDigestAlgorithmName(name string) string {
     74 + lower := strings.ToLower(name)
     75 + return strings.ReplaceAll(lower, "-", "")
     76 +}
     77 + 
  • ■ ■ ■ ■ ■ ■
    syft/file/digest_cataloger.go
    1  -package file
    2  - 
    3  -import (
    4  - "crypto"
    5  - "errors"
    6  - "fmt"
    7  - "hash"
    8  - "io"
    9  - "strings"
    10  - 
    11  - "github.com/wagoodman/go-partybus"
    12  - "github.com/wagoodman/go-progress"
    13  - 
    14  - "github.com/anchore/stereoscope/pkg/file"
    15  - "github.com/anchore/syft/internal"
    16  - "github.com/anchore/syft/internal/bus"
    17  - "github.com/anchore/syft/internal/log"
    18  - "github.com/anchore/syft/syft/event"
    19  - "github.com/anchore/syft/syft/source"
    20  -)
    21  - 
    22  -var errUndigestableFile = errors.New("undigestable file")
    23  - 
    24  -type DigestsCataloger struct {
    25  - hashes []crypto.Hash
    26  -}
    27  - 
    28  -func NewDigestsCataloger(hashes []crypto.Hash) (*DigestsCataloger, error) {
    29  - return &DigestsCataloger{
    30  - hashes: hashes,
    31  - }, nil
    32  -}
    33  - 
    34  -func (i *DigestsCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates][]Digest, error) {
    35  - results := make(map[source.Coordinates][]Digest)
    36  - locations := allRegularFiles(resolver)
    37  - stage, prog := digestsCatalogingProgress(int64(len(locations)))
    38  - for _, location := range locations {
    39  - stage.Current = location.RealPath
    40  - result, err := i.catalogLocation(resolver, location)
    41  - 
    42  - if errors.Is(err, errUndigestableFile) {
    43  - continue
    44  - }
    45  - 
    46  - if internal.IsErrPathPermission(err) {
    47  - log.Debugf("file digests cataloger skipping %q: %+v", location.RealPath, err)
    48  - continue
    49  - }
    50  - 
    51  - if err != nil {
    52  - return nil, err
    53  - }
    54  - prog.Increment()
    55  - results[location.Coordinates] = result
    56  - }
    57  - log.Debugf("file digests cataloger processed %d files", prog.Current())
    58  - prog.SetCompleted()
    59  - return results, nil
    60  -}
    61  - 
    62  -func (i *DigestsCataloger) catalogLocation(resolver source.FileResolver, location source.Location) ([]Digest, error) {
    63  - meta, err := resolver.FileMetadataByLocation(location)
    64  - if err != nil {
    65  - return nil, err
    66  - }
    67  - 
    68  - // we should only attempt to report digests for files that are regular files (don't attempt to resolve links)
    69  - if meta.Type != file.TypeRegular {
    70  - return nil, errUndigestableFile
    71  - }
    72  - 
    73  - contentReader, err := resolver.FileContentsByLocation(location)
    74  - if err != nil {
    75  - return nil, err
    76  - }
    77  - defer internal.CloseAndLogError(contentReader, location.VirtualPath)
    78  - 
    79  - digests, err := DigestsFromFile(contentReader, i.hashes)
    80  - if err != nil {
    81  - return nil, internal.ErrPath{Context: "digests-cataloger", Path: location.RealPath, Err: err}
    82  - }
    83  - 
    84  - return digests, nil
    85  -}
    86  - 
    87  -func DigestsFromFile(closer io.ReadCloser, hashes []crypto.Hash) ([]Digest, error) {
    88  - // create a set of hasher objects tied together with a single writer to feed content into
    89  - hashers := make([]hash.Hash, len(hashes))
    90  - writers := make([]io.Writer, len(hashes))
    91  - for idx, hashObj := range hashes {
    92  - hashers[idx] = hashObj.New()
    93  - writers[idx] = hashers[idx]
    94  - }
    95  - 
    96  - _, err := io.Copy(io.MultiWriter(writers...), closer)
    97  - if err != nil {
    98  - return nil, err
    99  - }
    100  - 
    101  - result := make([]Digest, len(hashes))
    102  - // only capture digests when there is content. It is important to do this based on SIZE and not
    103  - // FILE TYPE. The reasoning is that it is possible for a tar to be crafted with a header-only
    104  - // file type but a body is still allowed.
    105  - for idx, hasher := range hashers {
    106  - result[idx] = Digest{
    107  - Algorithm: DigestAlgorithmName(hashes[idx]),
    108  - Value: fmt.Sprintf("%+x", hasher.Sum(nil)),
    109  - }
    110  - }
    111  - 
    112  - return result, nil
    113  -}
    114  - 
    115  -func DigestAlgorithmName(hash crypto.Hash) string {
    116  - return CleanDigestAlgorithmName(hash.String())
    117  -}
    118  - 
    119  -func CleanDigestAlgorithmName(name string) string {
    120  - lower := strings.ToLower(name)
    121  - return strings.ReplaceAll(lower, "-", "")
    122  -}
    123  - 
    124  -func digestsCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) {
    125  - stage := &progress.Stage{}
    126  - prog := progress.NewManual(locations)
    127  - 
    128  - bus.Publish(partybus.Event{
    129  - Type: event.FileDigestsCatalogerStarted,
    130  - Value: struct {
    131  - progress.Stager
    132  - progress.Progressable
    133  - }{
    134  - Stager: progress.Stager(stage),
    135  - Progressable: prog,
    136  - },
    137  - })
    138  - 
    139  - return stage, prog
    140  -}
    141  - 
  • ■ ■ ■ ■ ■ ■
    syft/source/location.go syft/file/location.go
    1  -package source
     1 +package file
    2 2   
    3 3  import (
    4 4   "fmt"
    skipped 17 lines
    22 22   // since the coordinates are the minimally correct ID for a location (symlinks should not come into play)
    23 23   VirtualPath string `hash:"ignore" json:"-"` // The path to the file which may or may not have hardlinks / symlinks
    24 24   ref file.Reference `hash:"ignore"` // The file reference relative to the stereoscope.FileCatalog that has more information about this location.
     25 +}
     26 + 
     27 +func (l LocationData) Reference() file.Reference {
     28 + return l.ref
    25 29  }
    26 30   
    27 31  type LocationMetadata struct {
    skipped 80 lines
    108 112   }}
    109 113  }
    110 114   
    111  -// NewLocationFromImage creates a new Location representing the given path (extracted from the ref) relative to the given image.
     115 +// NewLocationFromImage creates a new Location representing the given path (extracted from the Reference) relative to the given image.
    112 116  func NewLocationFromImage(virtualPath string, ref file.Reference, img *image.Image) Location {
    113 117   layer := img.FileCatalog.Layer(ref)
    114 118   return Location{
    skipped 11 lines
    126 130   }
    127 131  }
    128 132   
    129  -// NewLocationFromDirectory creates a new Location representing the given path (extracted from the ref) relative to the given directory.
     133 +// NewLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory.
    130 134  func NewLocationFromDirectory(responsePath string, ref file.Reference) Location {
    131 135   return Location{
    132 136   LocationData: LocationData{
    skipped 8 lines
    141 145   }
    142 146  }
    143 147   
    144  -// NewVirtualLocationFromDirectory creates a new Location representing the given path (extracted from the ref) relative to the given directory with a separate virtual access path.
     148 +// NewVirtualLocationFromDirectory creates a new Location representing the given path (extracted from the Reference) relative to the given directory with a separate virtual access path.
    145 149  func NewVirtualLocationFromDirectory(responsePath, virtualResponsePath string, ref file.Reference) Location {
    146 150   if responsePath == virtualResponsePath {
    147 151   return NewLocationFromDirectory(responsePath, ref)
    skipped 46 lines
  • ■ ■ ■ ■
    syft/source/location_read_closer.go syft/file/location_read_closer.go
    1  -package source
     1 +package file
    2 2   
    3 3  import "io"
    4 4   
    skipped 12 lines
  • ■ ■ ■ ■
    syft/source/location_set.go syft/file/location_set.go
    1  -package source
     1 +package file
    2 2   
    3 3  import (
    4 4   "sort"
    skipped 95 lines
  • ■ ■ ■ ■
    syft/source/location_set_test.go syft/file/location_set_test.go
    1  -package source
     1 +package file
    2 2   
    3 3  import (
    4 4   "testing"
    skipped 194 lines
  • ■ ■ ■ ■
    syft/source/location_test.go syft/file/location_test.go
    1  -package source
     1 +package file
    2 2   
    3 3  import (
    4 4   "testing"
    skipped 47 lines
  • ■ ■ ■ ■
    syft/source/locations.go syft/file/locations.go
    1  -package source
     1 +package file
    2 2   
    3 3  type Locations []Location
    4 4   
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    syft/file/metadata.go
     1 +package file
     2 + 
     3 +import "github.com/anchore/stereoscope/pkg/file"
     4 + 
     5 +type Metadata = file.Metadata
     6 + 
  • ■ ■ ■ ■ ■ ■
    syft/file/metadata_cataloger.go
    1  -package file
    2  - 
    3  -import (
    4  - "github.com/wagoodman/go-partybus"
    5  - "github.com/wagoodman/go-progress"
    6  - 
    7  - "github.com/anchore/syft/internal/bus"
    8  - "github.com/anchore/syft/internal/log"
    9  - "github.com/anchore/syft/syft/event"
    10  - "github.com/anchore/syft/syft/source"
    11  -)
    12  - 
    13  -type MetadataCataloger struct {
    14  -}
    15  - 
    16  -func NewMetadataCataloger() *MetadataCataloger {
    17  - return &MetadataCataloger{}
    18  -}
    19  - 
    20  -func (i *MetadataCataloger) Catalog(resolver source.FileResolver) (map[source.Coordinates]source.FileMetadata, error) {
    21  - results := make(map[source.Coordinates]source.FileMetadata)
    22  - var locations []source.Location
    23  - for location := range resolver.AllLocations() {
    24  - locations = append(locations, location)
    25  - }
    26  - stage, prog := metadataCatalogingProgress(int64(len(locations)))
    27  - for _, location := range locations {
    28  - stage.Current = location.RealPath
    29  - metadata, err := resolver.FileMetadataByLocation(location)
    30  - if err != nil {
    31  - return nil, err
    32  - }
    33  - 
    34  - results[location.Coordinates] = metadata
    35  - prog.Increment()
    36  - }
    37  - log.Debugf("file metadata cataloger processed %d files", prog.Current())
    38  - prog.SetCompleted()
    39  - return results, nil
    40  -}
    41  - 
    42  -func metadataCatalogingProgress(locations int64) (*progress.Stage, *progress.Manual) {
    43  - stage := &progress.Stage{}
    44  - prog := progress.NewManual(locations)
    45  - 
    46  - bus.Publish(partybus.Event{
    47  - Type: event.FileMetadataCatalogerStarted,
    48  - Value: struct {
    49  - progress.Stager
    50  - progress.Progressable
    51  - }{
    52  - Stager: progress.Stager(stage),
    53  - Progressable: prog,
    54  - },
    55  - })
    56  - 
    57  - return stage, prog
    58  -}
    59  - 
  • ■ ■ ■ ■ ■ ■
    syft/source/mock_resolver.go syft/file/mock_resolver.go
    1  -package source
     1 +package file
    2 2   
    3 3  import (
    4 4   "fmt"
    skipped 6 lines
    11 11   "github.com/anchore/stereoscope/pkg/file"
    12 12  )
    13 13   
    14  -var _ FileResolver = (*MockResolver)(nil)
     14 +var _ Resolver = (*MockResolver)(nil)
    15 15   
    16 16  // MockResolver implements the FileResolver interface and is intended for use *only in test code*.
    17 17  // It provides an implementation that can resolve local filesystem paths using only a provided discrete list of file
    18 18  // paths, which are typically paths to test fixtures.
    19 19  type MockResolver struct {
    20 20   locations []Location
    21  - metadata map[Coordinates]FileMetadata
     21 + metadata map[Coordinates]Metadata
    22 22   mimeTypeIndex map[string][]Location
    23 23   extension map[string][]Location
    24 24   basename map[string][]Location
    skipped 16 lines
    41 41   
    42 42   return &MockResolver{
    43 43   locations: locations,
    44  - metadata: make(map[Coordinates]FileMetadata),
     44 + metadata: make(map[Coordinates]Metadata),
    45 45   extension: extension,
    46 46   basename: basename,
    47 47   }
    48 48  }
    49 49   
    50  -func NewMockResolverForPathsWithMetadata(metadata map[Coordinates]FileMetadata) *MockResolver {
     50 +func NewMockResolverForPathsWithMetadata(metadata map[Coordinates]Metadata) *MockResolver {
    51 51   var locations []Location
    52 52   var mimeTypeIndex = make(map[string][]Location)
    53 53   extension := make(map[string][]Location)
    skipped 101 lines
    155 155   return results
    156 156  }
    157 157   
    158  -func (r MockResolver) FileMetadataByLocation(l Location) (FileMetadata, error) {
     158 +func (r MockResolver) FileMetadataByLocation(l Location) (Metadata, error) {
    159 159   info, err := os.Stat(l.RealPath)
    160 160   if err != nil {
    161  - return FileMetadata{}, err
     161 + return Metadata{}, err
    162 162   }
    163 163   
    164 164   // other types not supported
    skipped 2 lines
    167 167   ty = file.TypeDirectory
    168 168   }
    169 169   
    170  - return FileMetadata{
     170 + return Metadata{
    171 171   FileInfo: info,
    172 172   Type: ty,
    173 173   UserID: 0, // not supported
    skipped 37 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/file_resolver.go syft/file/resolver.go
    1  -package source
     1 +package file
    2 2   
    3  -import (
    4  - "io"
    5  -)
     3 +import "io"
    6 4   
    7  -// FileResolver is an interface that encompasses how to get specific file references and file contents for a generic data source.
    8  -type FileResolver interface {
    9  - FileContentResolver
    10  - FilePathResolver
    11  - FileLocationResolver
    12  - FileMetadataResolver
     5 +// Resolver is an interface that encompasses how to get specific file references and file contents for a generic data source.
     6 +type Resolver interface {
     7 + ContentResolver
     8 + PathResolver
     9 + LocationResolver
     10 + MetadataResolver
    13 11  }
    14 12   
    15  -// FileContentResolver knows how to get file content for a given Location
    16  -type FileContentResolver interface {
     13 +// ContentResolver knows how to get file content for a given Location
     14 +type ContentResolver interface {
    17 15   FileContentsByLocation(Location) (io.ReadCloser, error)
    18 16  }
    19 17   
    20  -type FileMetadataResolver interface {
    21  - FileMetadataByLocation(Location) (FileMetadata, error)
     18 +type MetadataResolver interface {
     19 + FileMetadataByLocation(Location) (Metadata, error)
    22 20  }
    23 21   
    24  -// FilePathResolver knows how to get a Location for given string paths and globs
    25  -type FilePathResolver interface {
     22 +// PathResolver knows how to get a Location for given string paths and globs
     23 +type PathResolver interface {
    26 24   // HasPath indicates if the given path exists in the underlying source.
    27 25   // The implementation for this may vary, however, generally the following considerations should be made:
    28 26   // - full symlink resolution should be performed on all requests
    skipped 21 lines
    50 48   RelativeFileByPath(_ Location, path string) *Location
    51 49  }
    52 50   
    53  -type FileLocationResolver interface {
     51 +type LocationResolver interface {
    54 52   // AllLocations returns a channel of all file references from the underlying source.
    55 53   // The implementation for this may vary, however, generally the following considerations should be made:
    56 54   // - NO symlink resolution should be performed on results
    skipped 1 lines
    58 56   AllLocations() <-chan Location
    59 57  }
    60 58   
    61  -type WritableFileResolver interface {
    62  - FileResolver
     59 +type WritableResolver interface {
     60 + Resolver
    63 61   
    64 62   Write(location Location, reader io.Reader) error
    65 63  }
    skipped 1 lines
  • syft/file/test-fixtures/snapshot/stereoscope-fixture-image-file-type-mix.golden
    Binary file.
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/component.go
    skipped 5 lines
    6 6   "github.com/CycloneDX/cyclonedx-go"
    7 7   
    8 8   "github.com/anchore/packageurl-go"
     9 + "github.com/anchore/syft/syft/file"
    9 10   "github.com/anchore/syft/syft/formats/common"
    10 11   "github.com/anchore/syft/syft/pkg"
    11  - "github.com/anchore/syft/syft/source"
    12 12  )
    13 13   
    14 14  func encodeComponent(p pkg.Package) cyclonedx.Component {
    skipped 85 lines
    100 100   return p
    101 101  }
    102 102   
    103  -func decodeLocations(vals map[string]string) source.LocationSet {
    104  - v := common.Decode(reflect.TypeOf([]source.Location{}), vals, "syft:location", CycloneDXFields)
    105  - out, ok := v.([]source.Location)
     103 +func decodeLocations(vals map[string]string) file.LocationSet {
     104 + v := common.Decode(reflect.TypeOf([]file.Location{}), vals, "syft:location", CycloneDXFields)
     105 + out, ok := v.([]file.Location)
    106 106   if !ok {
    107 107   out = nil
    108 108   }
    109  - return source.NewLocationSet(out...)
     109 + return file.NewLocationSet(out...)
    110 110  }
    111 111   
    112 112  func decodePackageMetadata(vals map[string]string, c *cyclonedx.Component, typ pkg.MetadataType) interface{} {
    skipped 22 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/cyclonedxhelpers/component_test.go
    skipped 7 lines
    8 8   "github.com/CycloneDX/cyclonedx-go"
    9 9   "github.com/stretchr/testify/assert"
    10 10   
     11 + "github.com/anchore/syft/syft/file"
    11 12   "github.com/anchore/syft/syft/pkg"
    12  - "github.com/anchore/syft/syft/source"
    13 13  )
    14 14   
    15 15  func Test_encodeComponentProperties(t *testing.T) {
    skipped 12 lines
    28 28   name: "from apk",
    29 29   input: pkg.Package{
    30 30   FoundBy: "cataloger",
    31  - Locations: source.NewLocationSet(
    32  - source.NewLocationFromCoordinates(source.Coordinates{RealPath: "test"}),
     31 + Locations: file.NewLocationSet(
     32 + file.NewLocationFromCoordinates(file.Coordinates{RealPath: "test"}),
    33 33   ),
    34 34   Metadata: pkg.ApkMetadata{
    35 35   Package: "libc-utils",
    skipped 312 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/source_info_test.go
    skipped 4 lines
    5 5   
    6 6   "github.com/stretchr/testify/assert"
    7 7   
     8 + "github.com/anchore/syft/syft/file"
    8 9   "github.com/anchore/syft/syft/pkg"
    9  - "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12 12  func Test_SourceInfo(t *testing.T) {
    skipped 6 lines
    19 19   name: "locations are captured",
    20 20   input: pkg.Package{
    21 21   // note: no type given
    22  - Locations: source.NewLocationSet(
    23  - source.NewVirtualLocation("/a-place", "/b-place"),
    24  - source.NewVirtualLocation("/c-place", "/d-place"),
     22 + Locations: file.NewLocationSet(
     23 + file.NewVirtualLocation("/a-place", "/b-place"),
     24 + file.NewVirtualLocation("/c-place", "/d-place"),
    25 25   ),
    26 26   },
    27 27   expected: []string{
    skipped 222 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_format_model.go
    skipped 20 lines
    21 21   "github.com/anchore/syft/syft/formats/common/util"
    22 22   "github.com/anchore/syft/syft/pkg"
    23 23   "github.com/anchore/syft/syft/sbom"
    24  - "github.com/anchore/syft/syft/source"
    25 24  )
    26 25   
    27 26  const (
    skipped 109 lines
    137 136   switch it := identifiable.(type) {
    138 137   case pkg.Package:
    139 138   id = SanitizeElementID(fmt.Sprintf("Package-%s-%s-%s", it.Type, it.Name, it.ID()))
    140  - case source.Coordinates:
     139 + case file.Coordinates:
    141 140   p := ""
    142 141   parts := strings.Split(it.RealPath, "/")
    143 142   for i := len(parts); i > 0; i-- {
    skipped 293 lines
    437 436   artifacts := s.Artifacts
    438 437   
    439 438   for _, coordinates := range s.AllCoordinates() {
    440  - var metadata *source.FileMetadata
     439 + var metadata *file.Metadata
    441 440   if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
    442 441   metadata = &metadataForLocation
    443 442   }
    skipped 56 lines
    500 499   return spdx.ChecksumAlgorithm(strings.ToUpper(algorithm))
    501 500  }
    502 501   
    503  -func toFileTypes(metadata *source.FileMetadata) (ty []string) {
     502 +func toFileTypes(metadata *file.Metadata) (ty []string) {
    504 503   if metadata == nil {
    505 504   return nil
    506 505   }
    skipped 117 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_format_model_test.go
    skipped 114 lines
    115 115   
    116 116   tests := []struct {
    117 117   name string
    118  - metadata source.FileMetadata
     118 + metadata file.Metadata
    119 119   expected []string
    120 120   }{
    121 121   {
    122 122   name: "application",
    123  - metadata: source.FileMetadata{
     123 + metadata: file.Metadata{
    124 124   MIMEType: "application/vnd.unknown",
    125 125   },
    126 126   expected: []string{
    skipped 2 lines
    129 129   },
    130 130   {
    131 131   name: "archive",
    132  - metadata: source.FileMetadata{
     132 + metadata: file.Metadata{
    133 133   MIMEType: "application/zip",
    134 134   },
    135 135   expected: []string{
    skipped 3 lines
    139 139   },
    140 140   {
    141 141   name: "audio",
    142  - metadata: source.FileMetadata{
     142 + metadata: file.Metadata{
    143 143   MIMEType: "audio/ogg",
    144 144   },
    145 145   expected: []string{
    skipped 2 lines
    148 148   },
    149 149   {
    150 150   name: "video",
    151  - metadata: source.FileMetadata{
     151 + metadata: file.Metadata{
    152 152   MIMEType: "video/3gpp",
    153 153   },
    154 154   expected: []string{
    skipped 2 lines
    157 157   },
    158 158   {
    159 159   name: "text",
    160  - metadata: source.FileMetadata{
     160 + metadata: file.Metadata{
    161 161   MIMEType: "text/html",
    162 162   },
    163 163   expected: []string{
    skipped 2 lines
    166 166   },
    167 167   {
    168 168   name: "image",
    169  - metadata: source.FileMetadata{
     169 + metadata: file.Metadata{
    170 170   MIMEType: "image/png",
    171 171   },
    172 172   expected: []string{
    skipped 2 lines
    175 175   },
    176 176   {
    177 177   name: "binary",
    178  - metadata: source.FileMetadata{
     178 + metadata: file.Metadata{
    179 179   MIMEType: "application/x-sharedlib",
    180 180   },
    181 181   expected: []string{
    skipped 94 lines
    276 276   Name: "bogus",
    277 277   }
    278 278   
    279  - c := source.Coordinates{
     279 + c := file.Coordinates{
    280 280   RealPath: "/path",
    281 281   FileSystemID: "nowhere",
    282 282   }
    skipped 257 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_syft_model.go
    skipped 34 lines
    35 35   Source: src,
    36 36   Artifacts: sbom.Artifacts{
    37 37   Packages: pkg.NewCollection(),
    38  - FileMetadata: map[source.Coordinates]source.FileMetadata{},
    39  - FileDigests: map[source.Coordinates][]file.Digest{},
     38 + FileMetadata: map[file.Coordinates]file.Metadata{},
     39 + FileDigests: map[file.Coordinates][]file.Digest{},
    40 40   LinuxDistribution: findLinuxReleaseByPURL(doc),
    41 41   },
    42 42   }
    skipped 92 lines
    135 135   return digests
    136 136  }
    137 137   
    138  -func toFileMetadata(f *spdx.File) (meta source.FileMetadata) {
     138 +func toFileMetadata(f *spdx.File) (meta file.Metadata) {
    139 139   // FIXME Syft is currently lossy due to the SPDX 2.2.1 spec not supporting arbitrary mimetypes
    140 140   for _, typ := range f.FileTypes {
    141 141   switch FileType(typ) {
    skipped 27 lines
    169 169   b := spdxIDMap[string(r.RefB.ElementRefID)]
    170 170   from, fromOk := a.(*pkg.Package)
    171 171   toPackage, toPackageOk := b.(*pkg.Package)
    172  - toLocation, toLocationOk := b.(*source.Location)
     172 + toLocation, toLocationOk := b.(*file.Location)
    173 173   if !fromOk || !(toPackageOk || toLocationOk) {
    174 174   log.Debugf("unable to find valid relationship mapping from SPDX 2.2 JSON, ignoring: (from: %+v) (to: %+v)", a, b)
    175 175   continue
    skipped 36 lines
    212 212   return out
    213 213  }
    214 214   
    215  -func toSyftCoordinates(f *spdx.File) source.Coordinates {
     215 +func toSyftCoordinates(f *spdx.File) file.Coordinates {
    216 216   const layerIDPrefix = "layerID: "
    217 217   var fileSystemID string
    218 218   if strings.Index(f.FileComment, layerIDPrefix) == 0 {
    skipped 2 lines
    221 221   if strings.Index(string(f.FileSPDXIdentifier), layerIDPrefix) == 0 {
    222 222   fileSystemID = strings.TrimPrefix(string(f.FileSPDXIdentifier), layerIDPrefix)
    223 223   }
    224  - return source.Coordinates{
     224 + return file.Coordinates{
    225 225   RealPath: f.FileName,
    226 226   FileSystemID: fileSystemID,
    227 227   }
    228 228  }
    229 229   
    230  -func toSyftLocation(f *spdx.File) *source.Location {
    231  - l := source.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName)
     230 +func toSyftLocation(f *spdx.File) *file.Location {
     231 + l := file.NewVirtualLocationFromCoordinates(toSyftCoordinates(f), f.FileName)
    232 232   return &l
    233 233  }
    234 234   
    skipped 191 lines
  • ■ ■ ■ ■ ■
    syft/formats/common/spdxhelpers/to_syft_model_test.go
    skipped 8 lines
    9 9   "github.com/stretchr/testify/require"
    10 10   
    11 11   "github.com/anchore/syft/syft/artifact"
     12 + "github.com/anchore/syft/syft/file"
    12 13   "github.com/anchore/syft/syft/pkg"
    13 14   "github.com/anchore/syft/syft/source"
    14 15  )
    skipped 321 lines
    336 337   }
    337 338   pkg3.SetID()
    338 339   
    339  - loc1 := source.NewLocationFromCoordinates(source.Coordinates{
     340 + loc1 := file.NewLocationFromCoordinates(file.Coordinates{
    340 341   RealPath: "/somewhere/real",
    341 342   FileSystemID: "abc",
    342 343   })
    skipped 80 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/github/encoder_test.go
    skipped 6 lines
    7 7   "github.com/stretchr/testify/assert"
    8 8   
    9 9   "github.com/anchore/packageurl-go"
     10 + "github.com/anchore/syft/syft/file"
    10 11   "github.com/anchore/syft/syft/linux"
    11 12   "github.com/anchore/syft/syft/pkg"
    12 13   "github.com/anchore/syft/syft/sbom"
    skipped 22 lines
    35 36   {
    36 37   Name: "pkg-1",
    37 38   Version: "1.0.1",
    38  - Locations: source.NewLocationSet(
    39  - source.NewLocationFromCoordinates(source.Coordinates{
     39 + Locations: file.NewLocationSet(
     40 + file.NewLocationFromCoordinates(file.Coordinates{
    40 41   RealPath: "/usr/lib",
    41 42   FileSystemID: "fsid-1",
    42 43   }),
    skipped 2 lines
    45 46   {
    46 47   Name: "pkg-2",
    47 48   Version: "2.0.2",
    48  - Locations: source.NewLocationSet(
    49  - source.NewLocationFromCoordinates(source.Coordinates{
     49 + Locations: file.NewLocationSet(
     50 + file.NewLocationFromCoordinates(file.Coordinates{
    50 51   RealPath: "/usr/lib",
    51 52   FileSystemID: "fsid-1",
    52 53   }),
    skipped 2 lines
    55 56   {
    56 57   Name: "pkg-3",
    57 58   Version: "3.0.3",
    58  - Locations: source.NewLocationSet(
    59  - source.NewLocationFromCoordinates(source.Coordinates{
     59 + Locations: file.NewLocationSet(
     60 + file.NewLocationFromCoordinates(file.Coordinates{
    60 61   RealPath: "/etc",
    61 62   FileSystemID: "fsid-1",
    62 63   }),
    skipped 100 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/internal/testutils/utils.go
    skipped 16 lines
    17 17   "github.com/anchore/stereoscope/pkg/imagetest"
    18 18   "github.com/anchore/syft/syft/artifact"
    19 19   "github.com/anchore/syft/syft/cpe"
     20 + "github.com/anchore/syft/syft/file"
    20 21   "github.com/anchore/syft/syft/linux"
    21 22   "github.com/anchore/syft/syft/pkg"
    22 23   "github.com/anchore/syft/syft/sbom"
    skipped 132 lines
    155 156   catalog.Add(pkg.Package{
    156 157   Name: "package-1",
    157 158   Version: "1.0.1",
    158  - Locations: source.NewLocationSet(
    159  - source.NewLocationFromImage(string(ref1.RealPath), *ref1.Reference, img),
     159 + Locations: file.NewLocationSet(
     160 + file.NewLocationFromImage(string(ref1.RealPath), *ref1.Reference, img),
    160 161   ),
    161 162   Type: pkg.PythonPkg,
    162 163   FoundBy: "the-cataloger-1",
    skipped 14 lines
    177 178   catalog.Add(pkg.Package{
    178 179   Name: "package-2",
    179 180   Version: "2.0.1",
    180  - Locations: source.NewLocationSet(
    181  - source.NewLocationFromImage(string(ref2.RealPath), *ref2.Reference, img),
     181 + Locations: file.NewLocationSet(
     182 + file.NewLocationFromImage(string(ref2.RealPath), *ref2.Reference, img),
    182 183   ),
    183 184   Type: pkg.DebPkg,
    184 185   FoundBy: "the-cataloger-2",
    skipped 80 lines
    265 266   Version: "1.0.1",
    266 267   Type: pkg.PythonPkg,
    267 268   FoundBy: "the-cataloger-1",
    268  - Locations: source.NewLocationSet(
    269  - source.NewLocation("/some/path/pkg1"),
     269 + Locations: file.NewLocationSet(
     270 + file.NewLocation("/some/path/pkg1"),
    270 271   ),
    271 272   Language: pkg.Python,
    272 273   MetadataType: pkg.PythonPackageMetadataType,
    skipped 19 lines
    292 293   Version: "2.0.1",
    293 294   Type: pkg.DebPkg,
    294 295   FoundBy: "the-cataloger-2",
    295  - Locations: source.NewLocationSet(
    296  - source.NewLocation("/some/path/pkg1"),
     296 + Locations: file.NewLocationSet(
     297 + file.NewLocation("/some/path/pkg1"),
    297 298   ),
    298 299   MetadataType: pkg.DpkgMetadataType,
    299 300   Metadata: pkg.DpkgMetadata{
    skipped 18 lines
    318 319   Version: "1.0.1",
    319 320   Type: pkg.PythonPkg,
    320 321   FoundBy: "the-cataloger-1",
    321  - Locations: source.NewLocationSet(
    322  - source.NewLocation("/some/path/pkg1"),
     322 + Locations: file.NewLocationSet(
     323 + file.NewLocation("/some/path/pkg1"),
    323 324   ),
    324 325   Language: pkg.Python,
    325 326   MetadataType: pkg.PythonPackageMetadataType,
    skipped 20 lines
    346 347   Version: "2.0.1",
    347 348   Type: pkg.DebPkg,
    348 349   FoundBy: "the-cataloger-2",
    349  - Locations: source.NewLocationSet(
    350  - source.NewLocation("/some/path/pkg1"),
     350 + Locations: file.NewLocationSet(
     351 + file.NewLocation("/some/path/pkg1"),
    351 352   ),
    352 353   MetadataType: pkg.DpkgMetadataType,
    353 354   Metadata: pkg.DpkgMetadata{
    skipped 12 lines
    366 367  //nolint:gosec
    367 368  func AddSampleFileRelationships(s *sbom.SBOM) {
    368 369   catalog := s.Artifacts.Packages.Sorted()
    369  - s.Artifacts.FileMetadata = map[source.Coordinates]source.FileMetadata{}
     370 + s.Artifacts.FileMetadata = map[file.Coordinates]file.Metadata{}
    370 371   
    371 372   files := []string{"/f1", "/f2", "/d1/f3", "/d2/f4", "/z1/f5", "/a1/f6"}
    372 373   rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
    373 374   rnd.Shuffle(len(files), func(i, j int) { files[i], files[j] = files[j], files[i] })
    374 375   
    375 376   for _, f := range files {
    376  - meta := source.FileMetadata{}
    377  - coords := source.Coordinates{RealPath: f}
     377 + meta := file.Metadata{}
     378 + coords := file.Coordinates{RealPath: f}
    378 379   s.Artifacts.FileMetadata[coords] = meta
    379 380   
    380 381   s.Relationships = append(s.Relationships, artifact.Relationship{
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/encoder_test.go
    skipped 51 lines
    52 52   p1 := pkg.Package{
    53 53   Name: "package-1",
    54 54   Version: "1.0.1",
    55  - Locations: source.NewLocationSet(
    56  - source.NewLocationFromCoordinates(source.Coordinates{
     55 + Locations: file.NewLocationSet(
     56 + file.NewLocationFromCoordinates(file.Coordinates{
    57 57   RealPath: "/a/place/a",
    58 58   }),
    59 59   ),
    skipped 16 lines
    76 76   p2 := pkg.Package{
    77 77   Name: "package-2",
    78 78   Version: "2.0.1",
    79  - Locations: source.NewLocationSet(
    80  - source.NewLocationFromCoordinates(source.Coordinates{
     79 + Locations: file.NewLocationSet(
     80 + file.NewLocationFromCoordinates(file.Coordinates{
    81 81   RealPath: "/b/place/b",
    82 82   }),
    83 83   ),
    skipped 17 lines
    101 101   s := sbom.SBOM{
    102 102   Artifacts: sbom.Artifacts{
    103 103   Packages: catalog,
    104  - FileMetadata: map[source.Coordinates]source.FileMetadata{
    105  - source.NewLocation("/a/place").Coordinates: {
     104 + FileMetadata: map[file.Coordinates]file.Metadata{
     105 + file.NewLocation("/a/place").Coordinates: {
    106 106   FileInfo: stereoFile.ManualInfo{
    107 107   NameValue: "/a/place",
    108 108   ModeValue: 0775,
    skipped 2 lines
    111 111   UserID: 0,
    112 112   GroupID: 0,
    113 113   },
    114  - source.NewLocation("/a/place/a").Coordinates: {
     114 + file.NewLocation("/a/place/a").Coordinates: {
    115 115   FileInfo: stereoFile.ManualInfo{
    116 116   NameValue: "/a/place/a",
    117 117   ModeValue: 0775,
    skipped 2 lines
    120 120   UserID: 0,
    121 121   GroupID: 0,
    122 122   },
    123  - source.NewLocation("/b").Coordinates: {
     123 + file.NewLocation("/b").Coordinates: {
    124 124   FileInfo: stereoFile.ManualInfo{
    125 125   NameValue: "/b",
    126 126   ModeValue: 0775,
    skipped 3 lines
    130 130   UserID: 0,
    131 131   GroupID: 0,
    132 132   },
    133  - source.NewLocation("/b/place/b").Coordinates: {
     133 + file.NewLocation("/b/place/b").Coordinates: {
    134 134   FileInfo: stereoFile.ManualInfo{
    135 135   NameValue: "/b/place/b",
    136 136   ModeValue: 0644,
    skipped 3 lines
    140 140   GroupID: 2,
    141 141   },
    142 142   },
    143  - FileDigests: map[source.Coordinates][]file.Digest{
    144  - source.NewLocation("/a/place/a").Coordinates: {
     143 + FileDigests: map[file.Coordinates][]file.Digest{
     144 + file.NewLocation("/a/place/a").Coordinates: {
    145 145   {
    146 146   Algorithm: "sha256",
    147 147   Value: "366a3f5653e34673b875891b021647440d0127c2ef041e3b1a22da2a7d4f3703",
    148 148   },
    149 149   },
    150  - source.NewLocation("/b/place/b").Coordinates: {
     150 + file.NewLocation("/b/place/b").Coordinates: {
    151 151   {
    152 152   Algorithm: "sha256",
    153 153   Value: "1b3722da2a7d90d033b87581a2a3f12021647445653e34666ef041e3b4f3707c",
    154 154   },
    155 155   },
    156 156   },
    157  - FileContents: map[source.Coordinates]string{
    158  - source.NewLocation("/a/place/a").Coordinates: "the-contents",
     157 + FileContents: map[file.Coordinates]string{
     158 + file.NewLocation("/a/place/a").Coordinates: "the-contents",
    159 159   },
    160 160   LinuxDistribution: &linux.Release{
    161 161   ID: "redhat",
    skipped 66 lines
  • ■ ■ ■ ■ ■
    syft/formats/syftjson/model/file.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "github.com/anchore/syft/syft/file"
    5  - "github.com/anchore/syft/syft/source"
    6 5  )
    7 6   
    8 7  type File struct {
    9 8   ID string `json:"id"`
    10  - Location source.Coordinates `json:"location"`
     9 + Location file.Coordinates `json:"location"`
    11 10   Metadata *FileMetadataEntry `json:"metadata,omitempty"`
    12 11   Contents string `json:"contents,omitempty"`
    13 12   Digests []file.Digest `json:"digests,omitempty"`
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/model/package.go
    skipped 6 lines
    7 7   "reflect"
    8 8   
    9 9   "github.com/anchore/syft/internal/log"
     10 + "github.com/anchore/syft/syft/file"
    10 11   "github.com/anchore/syft/syft/license"
    11 12   "github.com/anchore/syft/syft/pkg"
    12  - "github.com/anchore/syft/syft/source"
    13 13  )
    14 14   
    15 15  var errUnknownMetadataType = errors.New("unknown metadata type")
    skipped 6 lines
    22 22   
    23 23  // PackageBasicData contains non-ambiguous values (type-wise) from pkg.Package.
    24 24  type PackageBasicData struct {
    25  - ID string `json:"id"`
    26  - Name string `json:"name"`
    27  - Version string `json:"version"`
    28  - Type pkg.Type `json:"type"`
    29  - FoundBy string `json:"foundBy"`
    30  - Locations []source.Location `json:"locations"`
    31  - Licenses licenses `json:"licenses"`
    32  - Language pkg.Language `json:"language"`
    33  - CPEs []string `json:"cpes"`
    34  - PURL string `json:"purl"`
     25 + ID string `json:"id"`
     26 + Name string `json:"name"`
     27 + Version string `json:"version"`
     28 + Type pkg.Type `json:"type"`
     29 + FoundBy string `json:"foundBy"`
     30 + Locations []file.Location `json:"locations"`
     31 + Licenses licenses `json:"licenses"`
     32 + Language pkg.Language `json:"language"`
     33 + CPEs []string `json:"cpes"`
     34 + PURL string `json:"purl"`
    35 35  }
    36 36   
    37 37  type licenses []License
    38 38   
    39 39  type License struct {
    40  - Value string `json:"value"`
    41  - SPDXExpression string `json:"spdxExpression"`
    42  - Type license.Type `json:"type"`
    43  - URLs []string `json:"urls"`
    44  - Locations []source.Location `json:"locations"`
     40 + Value string `json:"value"`
     41 + SPDXExpression string `json:"spdxExpression"`
     42 + Type license.Type `json:"type"`
     43 + URLs []string `json:"urls"`
     44 + Locations []file.Location `json:"locations"`
    45 45  }
    46 46   
    47 47  func newModelLicensesFromValues(licenses []string) (ml []License) {
    skipped 97 lines
  • ■ ■ ■ ■ ■
    syft/formats/syftjson/model/secrets.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "github.com/anchore/syft/syft/file"
    5  - "github.com/anchore/syft/syft/source"
    6 5  )
    7 6   
    8 7  type Secrets struct {
    9  - Location source.Coordinates `json:"location"`
     8 + Location file.Coordinates `json:"location"`
    10 9   Secrets []file.SearchResult `json:"secrets"`
    11 10  }
    12 11   
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_format_model.go
    skipped 73 lines
    74 74   }
    75 75  }
    76 76   
    77  -func toSecrets(data map[source.Coordinates][]file.SearchResult) []model.Secrets {
     77 +func toSecrets(data map[file.Coordinates][]file.SearchResult) []model.Secrets {
    78 78   results := make([]model.Secrets, 0)
    79 79   for coordinates, secrets := range data {
    80 80   results = append(results, model.Secrets{
    skipped 14 lines
    95 95   artifacts := s.Artifacts
    96 96   
    97 97   for _, coordinates := range s.AllCoordinates() {
    98  - var metadata *source.FileMetadata
     98 + var metadata *file.Metadata
    99 99   if metadataForLocation, exists := artifacts.FileMetadata[coordinates]; exists {
    100 100   metadata = &metadataForLocation
    101 101   }
    skipped 24 lines
    126 126   return results
    127 127  }
    128 128   
    129  -func toFileMetadataEntry(coordinates source.Coordinates, metadata *source.FileMetadata) *model.FileMetadataEntry {
     129 +func toFileMetadataEntry(coordinates file.Coordinates, metadata *file.Metadata) *model.FileMetadataEntry {
    130 130   if metadata == nil {
    131 131   return nil
    132 132   }
    skipped 62 lines
    195 195  func toLicenseModel(pkgLicenses []pkg.License) (modelLicenses []model.License) {
    196 196   for _, l := range pkgLicenses {
    197 197   // guarantee collection
    198  - locations := make([]source.Location, 0)
     198 + locations := make([]file.Location, 0)
    199 199   if v := l.Locations.ToSlice(); v != nil {
    200 200   locations = v
    201 201   }
    skipped 101 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_format_model_test.go
    skipped 6 lines
    7 7   "github.com/stretchr/testify/assert"
    8 8   "github.com/stretchr/testify/require"
    9 9   
    10  - "github.com/anchore/stereoscope/pkg/file"
     10 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
     11 + "github.com/anchore/syft/syft/file"
    11 12   "github.com/anchore/syft/syft/formats/syftjson/model"
    12 13   "github.com/anchore/syft/syft/source"
    13 14  )
    skipped 80 lines
    94 95   
    95 96  func Test_toFileType(t *testing.T) {
    96 97   
    97  - badType := file.Type(0x1337)
    98  - var allTypesTested []file.Type
     98 + badType := stereoscopeFile.Type(0x1337)
     99 + var allTypesTested []stereoscopeFile.Type
    99 100   tests := []struct {
    100  - ty file.Type
     101 + ty stereoscopeFile.Type
    101 102   name string
    102 103   }{
    103 104   {
    104  - ty: file.TypeRegular,
     105 + ty: stereoscopeFile.TypeRegular,
    105 106   name: "RegularFile",
    106 107   },
    107 108   {
    108  - ty: file.TypeDirectory,
     109 + ty: stereoscopeFile.TypeDirectory,
    109 110   name: "Directory",
    110 111   },
    111 112   {
    112  - ty: file.TypeSymLink,
     113 + ty: stereoscopeFile.TypeSymLink,
    113 114   name: "SymbolicLink",
    114 115   },
    115 116   {
    116  - ty: file.TypeHardLink,
     117 + ty: stereoscopeFile.TypeHardLink,
    117 118   name: "HardLink",
    118 119   },
    119 120   {
    120  - ty: file.TypeSocket,
     121 + ty: stereoscopeFile.TypeSocket,
    121 122   name: "Socket",
    122 123   },
    123 124   {
    124  - ty: file.TypeCharacterDevice,
     125 + ty: stereoscopeFile.TypeCharacterDevice,
    125 126   name: "CharacterDevice",
    126 127   },
    127 128   {
    128  - ty: file.TypeBlockDevice,
     129 + ty: stereoscopeFile.TypeBlockDevice,
    129 130   name: "BlockDevice",
    130 131   },
    131 132   {
    132  - ty: file.TypeFIFO,
     133 + ty: stereoscopeFile.TypeFIFO,
    133 134   name: "FIFONode",
    134 135   },
    135 136   {
    136  - ty: file.TypeIrregular,
     137 + ty: stereoscopeFile.TypeIrregular,
    137 138   name: "IrregularFile",
    138 139   },
    139 140   {
    skipped 10 lines
    150 151   })
    151 152   }
    152 153   
    153  - assert.ElementsMatch(t, allTypesTested, file.AllTypes(), "not all file.Types are under test")
     154 + assert.ElementsMatch(t, allTypesTested, stereoscopeFile.AllTypes(), "not all file.Types are under test")
    154 155  }
    155 156   
    156 157  func Test_toFileMetadataEntry(t *testing.T) {
    157  - coords := source.Coordinates{
     158 + coords := file.Coordinates{
    158 159   RealPath: "/path",
    159 160   FileSystemID: "x",
    160 161   }
    161 162   tests := []struct {
    162 163   name string
    163  - metadata *source.FileMetadata
     164 + metadata *file.Metadata
    164 165   want *model.FileMetadataEntry
    165 166   }{
    166 167   {
    skipped 1 lines
    168 169   },
    169 170   {
    170 171   name: "no file info",
    171  - metadata: &source.FileMetadata{
     172 + metadata: &file.Metadata{
    172 173   FileInfo: nil,
    173 174   },
    174 175   want: &model.FileMetadataEntry{
    175  - Type: file.TypeRegular.String(),
     176 + Type: stereoscopeFile.TypeRegular.String(),
    176 177   },
    177 178   },
    178 179   {
    179 180   name: "with file info",
    180  - metadata: &source.FileMetadata{
    181  - FileInfo: &file.ManualInfo{
     181 + metadata: &file.Metadata{
     182 + FileInfo: &stereoscopeFile.ManualInfo{
    182 183   ModeValue: 1,
    183 184   },
    184 185   },
    185 186   want: &model.FileMetadataEntry{
    186 187   Mode: 1,
    187  - Type: file.TypeRegular.String(),
     188 + Type: stereoscopeFile.TypeRegular.String(),
    188 189   },
    189 190   },
    190 191   }
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_syft_model.go
    skipped 63 lines
    64 64   
    65 65  func toSyftFiles(files []model.File) sbom.Artifacts {
    66 66   ret := sbom.Artifacts{
    67  - FileMetadata: make(map[source.Coordinates]source.FileMetadata),
    68  - FileDigests: make(map[source.Coordinates][]file.Digest),
     67 + FileMetadata: make(map[file.Coordinates]file.Metadata),
     68 + FileDigests: make(map[file.Coordinates][]file.Digest),
    69 69   }
    70 70   
    71 71   for _, f := range files {
    skipped 7 lines
    79 79   
    80 80   fm := os.FileMode(mode)
    81 81   
    82  - ret.FileMetadata[coord] = source.FileMetadata{
     82 + ret.FileMetadata[coord] = file.Metadata{
    83 83   FileInfo: stereoscopeFile.ManualInfo{
    84 84   NameValue: path.Base(coord.RealPath),
    85 85   SizeValue: f.Metadata.Size,
    skipped 26 lines
    112 112   SPDXExpression: l.SPDXExpression,
    113 113   Type: l.Type,
    114 114   URLs: internal.NewStringSet(l.URLs...),
    115  - Locations: source.NewLocationSet(l.Locations...),
     115 + Locations: file.NewLocationSet(l.Locations...),
    116 116   })
    117 117   }
    118 118   return
    skipped 201 lines
    320 320   Name: p.Name,
    321 321   Version: p.Version,
    322 322   FoundBy: p.FoundBy,
    323  - Locations: source.NewLocationSet(p.Locations...),
     323 + Locations: file.NewLocationSet(p.Locations...),
    324 324   Licenses: pkg.NewLicenseSet(toSyftLicenses(p.Licenses)...),
    325 325   Language: p.Language,
    326 326   Type: p.Type,
    skipped 20 lines
  • ■ ■ ■ ■ ■ ■
    syft/formats/syftjson/to_syft_model_test.go
    skipped 130 lines
    131 131  }
    132 132   
    133 133  func Test_toSyftFiles(t *testing.T) {
    134  - coord := source.Coordinates{
     134 + coord := file.Coordinates{
    135 135   RealPath: "/somerwhere/place",
    136 136   FileSystemID: "abc",
    137 137   }
    skipped 7 lines
    145 145   name: "empty",
    146 146   files: []model.File{},
    147 147   want: sbom.Artifacts{
    148  - FileMetadata: map[source.Coordinates]source.FileMetadata{},
    149  - FileDigests: map[source.Coordinates][]file.Digest{},
     148 + FileMetadata: map[file.Coordinates]file.Metadata{},
     149 + FileDigests: map[file.Coordinates][]file.Digest{},
    150 150   },
    151 151   },
    152 152   {
    skipped 12 lines
    165 165   },
    166 166   },
    167 167   want: sbom.Artifacts{
    168  - FileMetadata: map[source.Coordinates]source.FileMetadata{},
    169  - FileDigests: map[source.Coordinates][]file.Digest{
     168 + FileMetadata: map[file.Coordinates]file.Metadata{},
     169 + FileDigests: map[file.Coordinates][]file.Digest{
    170 170   coord: {
    171 171   {
    172 172   Algorithm: "sha256",
    skipped 27 lines
    200 200   },
    201 201   },
    202 202   want: sbom.Artifacts{
    203  - FileMetadata: map[source.Coordinates]source.FileMetadata{
     203 + FileMetadata: map[file.Coordinates]file.Metadata{
    204 204   coord: {
    205 205   FileInfo: stereoFile.ManualInfo{
    206 206   NameValue: "place",
    skipped 8 lines
    215 215   MIMEType: "text/plain",
    216 216   },
    217 217   },
    218  - FileDigests: map[source.Coordinates][]file.Digest{
     218 + FileDigests: map[file.Coordinates][]file.Digest{
    219 219   coord: {
    220 220   {
    221 221   Algorithm: "sha256",
    skipped 131 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/image_all_layers_resolver.go syft/internal/fileresolver/container_image_all_layers.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "fmt"
    5 5   "io"
    6 6   
    7  - "github.com/anchore/stereoscope/pkg/file"
     7 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    8 8   "github.com/anchore/stereoscope/pkg/filetree"
    9 9   "github.com/anchore/stereoscope/pkg/image"
    10 10   "github.com/anchore/syft/internal/log"
     11 + "github.com/anchore/syft/syft/file"
    11 12  )
    12 13   
    13  -var _ FileResolver = (*imageAllLayersResolver)(nil)
     14 +var _ file.Resolver = (*ContainerImageAllLayers)(nil)
    14 15   
    15  -// imageAllLayersResolver implements path and content access for the AllLayers source option for container image data sources.
    16  -type imageAllLayersResolver struct {
     16 +// ContainerImageAllLayers implements path and content access for the AllLayers source option for container image data sources.
     17 +type ContainerImageAllLayers struct {
    17 18   img *image.Image
    18 19   layers []int
    19 20  }
    20 21   
    21  -// newAllLayersResolver returns a new resolver from the perspective of all image layers for the given image.
    22  -func newAllLayersResolver(img *image.Image) (*imageAllLayersResolver, error) {
     22 +// NewFromContainerImageAllLayers returns a new resolver from the perspective of all image layers for the given image.
     23 +func NewFromContainerImageAllLayers(img *image.Image) (*ContainerImageAllLayers, error) {
    23 24   if len(img.Layers) == 0 {
    24 25   return nil, fmt.Errorf("the image does not contain any layers")
    25 26   }
    skipped 2 lines
    28 29   for idx := range img.Layers {
    29 30   layers = append(layers, idx)
    30 31   }
    31  - return &imageAllLayersResolver{
     32 + return &ContainerImageAllLayers{
    32 33   img: img,
    33 34   layers: layers,
    34 35   }, nil
    35 36  }
    36 37   
    37 38  // HasPath indicates if the given path exists in the underlying source.
    38  -func (r *imageAllLayersResolver) HasPath(path string) bool {
    39  - p := file.Path(path)
     39 +func (r *ContainerImageAllLayers) HasPath(path string) bool {
     40 + p := stereoscopeFile.Path(path)
    40 41   for _, layerIdx := range r.layers {
    41 42   tree := r.img.Layers[layerIdx].Tree
    42 43   if tree.HasPath(p) {
    skipped 3 lines
    46 47   return false
    47 48  }
    48 49   
    49  -func (r *imageAllLayersResolver) fileByRef(ref file.Reference, uniqueFileIDs file.ReferenceSet, layerIdx int) ([]file.Reference, error) {
    50  - uniqueFiles := make([]file.Reference, 0)
     50 +func (r *ContainerImageAllLayers) fileByRef(ref stereoscopeFile.Reference, uniqueFileIDs stereoscopeFile.ReferenceSet, layerIdx int) ([]stereoscopeFile.Reference, error) {
     51 + uniqueFiles := make([]stereoscopeFile.Reference, 0)
    51 52   
    52 53   // 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
    53 54   entry, err := r.img.FileCatalog.Get(ref)
    skipped 1 lines
    55 56   return nil, fmt.Errorf("unable to fetch metadata (ref=%+v): %w", ref, err)
    56 57   }
    57 58   
    58  - if entry.Metadata.Type == file.TypeHardLink || entry.Metadata.Type == file.TypeSymLink {
     59 + if entry.Metadata.Type == stereoscopeFile.TypeHardLink || entry.Metadata.Type == stereoscopeFile.TypeSymLink {
    59 60   // a link may resolve in this layer or higher, assuming a squashed tree is used to search
    60 61   // we should search all possible resolutions within the valid source
    61 62   for _, subLayerIdx := range r.layers[layerIdx:] {
    skipped 15 lines
    77 78  }
    78 79   
    79 80  // FilesByPath returns all file.References that match the given paths from any layer in the image.
    80  -func (r *imageAllLayersResolver) FilesByPath(paths ...string) ([]Location, error) {
    81  - uniqueFileIDs := file.NewFileReferenceSet()
    82  - uniqueLocations := make([]Location, 0)
     81 +func (r *ContainerImageAllLayers) FilesByPath(paths ...string) ([]file.Location, error) {
     82 + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
     83 + uniqueLocations := make([]file.Location, 0)
    83 84   
    84 85   for _, path := range paths {
    85 86   for idx, layerIdx := range r.layers {
    skipped 24 lines
    110 111   return nil, err
    111 112   }
    112 113   for _, result := range results {
    113  - uniqueLocations = append(uniqueLocations, NewLocationFromImage(path, result, r.img))
     114 + uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(path, result, r.img))
    114 115   }
    115 116   }
    116 117   }
    skipped 2 lines
    119 120   
    120 121  // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image.
    121 122  // nolint:gocognit
    122  -func (r *imageAllLayersResolver) FilesByGlob(patterns ...string) ([]Location, error) {
    123  - uniqueFileIDs := file.NewFileReferenceSet()
    124  - uniqueLocations := make([]Location, 0)
     123 +func (r *ContainerImageAllLayers) FilesByGlob(patterns ...string) ([]file.Location, error) {
     124 + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
     125 + uniqueLocations := make([]file.Location, 0)
    125 126   
    126 127   for _, pattern := range patterns {
    127 128   for idx, layerIdx := range r.layers {
    skipped 25 lines
    153 154   return nil, err
    154 155   }
    155 156   for _, refResult := range refResults {
    156  - uniqueLocations = append(uniqueLocations, NewLocationFromImage(string(result.RequestPath), refResult, r.img))
     157 + uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(string(result.RequestPath), refResult, r.img))
    157 158   }
    158 159   }
    159 160   }
    skipped 4 lines
    164 165   
    165 166  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
    166 167  // This is helpful when attempting to find a file that is in the same layer or lower as another file.
    167  -func (r *imageAllLayersResolver) RelativeFileByPath(location Location, path string) *Location {
    168  - layer := r.img.FileCatalog.Layer(location.ref)
     168 +func (r *ContainerImageAllLayers) RelativeFileByPath(location file.Location, path string) *file.Location {
     169 + layer := r.img.FileCatalog.Layer(location.Reference())
    169 170   
    170  - exists, relativeRef, err := layer.SquashedTree.File(file.Path(path), filetree.FollowBasenameLinks)
     171 + exists, relativeRef, err := layer.SquashedTree.File(stereoscopeFile.Path(path), filetree.FollowBasenameLinks)
    171 172   if err != nil {
    172 173   log.Errorf("failed to find path=%q in squash: %+w", path, err)
    173 174   return nil
    skipped 2 lines
    176 177   return nil
    177 178   }
    178 179   
    179  - relativeLocation := NewLocationFromImage(path, *relativeRef.Reference, r.img)
     180 + relativeLocation := file.NewLocationFromImage(path, *relativeRef.Reference, r.img)
    180 181   
    181 182   return &relativeLocation
    182 183  }
    183 184   
    184 185  // FileContentsByLocation fetches file contents for a single file reference, irregardless of the source layer.
    185 186  // If the path does not exist an error is returned.
    186  -func (r *imageAllLayersResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) {
    187  - entry, err := r.img.FileCatalog.Get(location.ref)
     187 +func (r *ContainerImageAllLayers) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
     188 + entry, err := r.img.FileCatalog.Get(location.Reference())
    188 189   if err != nil {
    189 190   return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err)
    190 191   }
    191 192   
    192 193   switch entry.Metadata.Type {
    193  - case file.TypeSymLink, file.TypeHardLink:
     194 + case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink:
    194 195   // the location we are searching may be a symlink, we should always work with the resolved file
    195 196   newLocation := r.RelativeFileByPath(location, location.VirtualPath)
    196 197   if newLocation == nil {
    skipped 1 lines
    198 199   return nil, fmt.Errorf("no contents for location=%q", location.VirtualPath)
    199 200   }
    200 201   location = *newLocation
    201  - case file.TypeDirectory:
    202  - return nil, fmt.Errorf("cannot read contents of non-file %q", location.ref.RealPath)
     202 + case stereoscopeFile.TypeDirectory:
     203 + return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath)
    203 204   }
    204 205   
    205  - return r.img.FileContentsByRef(location.ref)
     206 + return r.img.OpenReference(location.Reference())
    206 207  }
    207 208   
    208  -func (r *imageAllLayersResolver) FilesByMIMEType(types ...string) ([]Location, error) {
    209  - uniqueFileIDs := file.NewFileReferenceSet()
    210  - uniqueLocations := make([]Location, 0)
     209 +func (r *ContainerImageAllLayers) FilesByMIMEType(types ...string) ([]file.Location, error) {
     210 + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
     211 + uniqueLocations := make([]file.Location, 0)
    211 212   
    212 213   for idx, layerIdx := range r.layers {
    213 214   refs, err := r.img.Layers[layerIdx].SearchContext.SearchByMIMEType(types...)
    skipped 11 lines
    225 226   return nil, err
    226 227   }
    227 228   for _, refResult := range refResults {
    228  - uniqueLocations = append(uniqueLocations, NewLocationFromImage(string(ref.RequestPath), refResult, r.img))
     229 + uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(string(ref.RequestPath), refResult, r.img))
    229 230   }
    230 231   }
    231 232   }
    skipped 1 lines
    233 234   return uniqueLocations, nil
    234 235  }
    235 236   
    236  -func (r *imageAllLayersResolver) AllLocations() <-chan Location {
    237  - results := make(chan Location)
     237 +func (r *ContainerImageAllLayers) AllLocations() <-chan file.Location {
     238 + results := make(chan file.Location)
    238 239   go func() {
    239 240   defer close(results)
    240 241   for _, layerIdx := range r.layers {
    241 242   tree := r.img.Layers[layerIdx].Tree
    242  - for _, ref := range tree.AllFiles(file.AllTypes()...) {
    243  - results <- NewLocationFromImage(string(ref.RealPath), ref, r.img)
     243 + for _, ref := range tree.AllFiles(stereoscopeFile.AllTypes()...) {
     244 + results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
    244 245   }
    245 246   }
    246 247   }()
    247 248   return results
    248 249  }
    249 250   
    250  -func (r *imageAllLayersResolver) FileMetadataByLocation(location Location) (FileMetadata, error) {
     251 +func (r *ContainerImageAllLayers) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
    251 252   return fileMetadataByLocation(r.img, location)
    252 253  }
    253 254   
  • ■ ■ ■ ■ ■ ■
    syft/source/image_all_layers_resolver_test.go syft/internal/fileresolver/container_image_all_layers_test.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "fmt"
    skipped 8 lines
    13 13   "github.com/stretchr/testify/require"
    14 14   
    15 15   "github.com/anchore/stereoscope/pkg/imagetest"
     16 + "github.com/anchore/syft/syft/file"
    16 17  )
    17 18   
    18 19  type resolution struct {
    skipped 74 lines
    93 94   t.Run(c.name, func(t *testing.T) {
    94 95   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    95 96   
    96  - resolver, err := newAllLayersResolver(img)
     97 + resolver, err := NewFromContainerImageAllLayers(img)
    97 98   if err != nil {
    98 99   t.Fatalf("could not create resolver: %+v", err)
    99 100   }
    skipped 21 lines
    121 122   for idx, actual := range refs {
    122 123   expected := c.resolutions[idx]
    123 124   
    124  - if string(actual.ref.RealPath) != expected.path {
    125  - t.Errorf("bad resolve path: '%s'!='%s'", string(actual.ref.RealPath), expected.path)
     125 + if string(actual.Reference().RealPath) != expected.path {
     126 + t.Errorf("bad resolve path: '%s'!='%s'", string(actual.Reference().RealPath), expected.path)
    126 127   }
    127 128   
    128  - if expected.path != "" && string(actual.ref.RealPath) != actual.RealPath {
     129 + if expected.path != "" && string(actual.Reference().RealPath) != actual.RealPath {
    129 130   t.Errorf("we should always prefer real paths over ones with links")
    130 131   }
    131 132   
    132  - layer := img.FileCatalog.Layer(actual.ref)
     133 + layer := img.FileCatalog.Layer(actual.Reference())
    133 134   if layer.Metadata.Index != expected.layer {
    134 135   t.Errorf("bad resolve layer: '%d'!='%d'", layer.Metadata.Index, expected.layer)
    135 136   }
    skipped 71 lines
    207 208   t.Run(c.name, func(t *testing.T) {
    208 209   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    209 210   
    210  - resolver, err := newAllLayersResolver(img)
     211 + resolver, err := NewFromContainerImageAllLayers(img)
    211 212   if err != nil {
    212 213   t.Fatalf("could not create resolver: %+v", err)
    213 214   }
    skipped 10 lines
    224 225   for idx, actual := range refs {
    225 226   expected := c.resolutions[idx]
    226 227   
    227  - if string(actual.ref.RealPath) != expected.path {
    228  - t.Errorf("bad resolve path: '%s'!='%s'", string(actual.ref.RealPath), expected.path)
     228 + if string(actual.Reference().RealPath) != expected.path {
     229 + t.Errorf("bad resolve path: '%s'!='%s'", string(actual.Reference().RealPath), expected.path)
    229 230   }
    230 231   
    231  - if expected.path != "" && string(actual.ref.RealPath) != actual.RealPath {
     232 + if expected.path != "" && string(actual.Reference().RealPath) != actual.RealPath {
    232 233   t.Errorf("we should always prefer real paths over ones with links")
    233 234   }
    234 235   
    235  - layer := img.FileCatalog.Layer(actual.ref)
     236 + layer := img.FileCatalog.Layer(actual.Reference())
    236 237   
    237 238   if layer.Metadata.Index != expected.layer {
    238 239   t.Errorf("bad resolve layer: '%d'!='%d'", layer.Metadata.Index, expected.layer)
    skipped 20 lines
    259 260   t.Run(test.fixtureName, func(t *testing.T) {
    260 261   img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName)
    261 262   
    262  - resolver, err := newAllLayersResolver(img)
     263 + resolver, err := NewFromContainerImageAllLayers(img)
    263 264   assert.NoError(t, err)
    264 265   
    265 266   locations, err := resolver.FilesByMIMEType(test.mimeType)
    skipped 10 lines
    276 277  func Test_imageAllLayersResolver_hasFilesystemIDInLocation(t *testing.T) {
    277 278   img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path")
    278 279   
    279  - resolver, err := newAllLayersResolver(img)
     280 + resolver, err := NewFromContainerImageAllLayers(img)
    280 281   assert.NoError(t, err)
    281 282   
    282 283   locations, err := resolver.FilesByMIMEType("text/plain")
    skipped 53 lines
    336 337   t.Run(test.name, func(t *testing.T) {
    337 338   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    338 339   
    339  - resolver, err := newAllLayersResolver(img)
     340 + resolver, err := NewFromContainerImageAllLayers(img)
    340 341   assert.NoError(t, err)
    341 342   
    342 343   refs, err := resolver.FilesByPath(test.fixture)
    skipped 20 lines
    363 364   
    364 365   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    365 366   
    366  - resolver, err := newAllLayersResolver(img)
     367 + resolver, err := NewFromContainerImageAllLayers(img)
    367 368   assert.NoError(t, err)
    368 369   
    369  - var dirLoc *Location
     370 + var dirLoc *file.Location
    370 371   for loc := range resolver.AllLocations() {
    371  - entry, err := resolver.img.FileCatalog.Get(loc.ref)
     372 + entry, err := resolver.img.FileCatalog.Get(loc.Reference())
    372 373   require.NoError(t, err)
    373 374   if entry.Metadata.IsDir() {
    374 375   dirLoc = &loc
    skipped 11 lines
    386 387  func Test_imageAllLayersResolver_resolvesLinks(t *testing.T) {
    387 388   tests := []struct {
    388 389   name string
    389  - runner func(FileResolver) []Location
    390  - expected []Location
     390 + runner func(file.Resolver) []file.Location
     391 + expected []file.Location
    391 392   }{
    392 393   {
    393 394   name: "by mimetype",
    394  - runner: func(resolver FileResolver) []Location {
     395 + runner: func(resolver file.Resolver) []file.Location {
    395 396   // links should not show up when searching mimetype
    396 397   actualLocations, err := resolver.FilesByMIMEType("text/plain")
    397 398   assert.NoError(t, err)
    398 399   return actualLocations
    399 400   },
    400  - expected: []Location{
    401  - NewVirtualLocation("/etc/group", "/etc/group"),
    402  - NewVirtualLocation("/etc/passwd", "/etc/passwd"),
    403  - NewVirtualLocation("/etc/shadow", "/etc/shadow"),
    404  - NewVirtualLocation("/file-1.txt", "/file-1.txt"),
    405  - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
     401 + expected: []file.Location{
     402 + file.NewVirtualLocation("/etc/group", "/etc/group"),
     403 + file.NewVirtualLocation("/etc/passwd", "/etc/passwd"),
     404 + file.NewVirtualLocation("/etc/shadow", "/etc/shadow"),
     405 + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"),
     406 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
    406 407   // note: we're de-duping the redundant access to file-3.txt
    407 408   // ... (there would usually be two copies)
    408  - NewVirtualLocation("/file-3.txt", "/file-3.txt"),
    409  - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
    410  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 1
    411  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 2
     409 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"),
     410 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
     411 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 1
     412 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // copy 2
    412 413   },
    413 414   },
    414 415   {
    415 416   name: "by glob to links",
    416  - runner: func(resolver FileResolver) []Location {
     417 + runner: func(resolver file.Resolver) []file.Location {
    417 418   // links are searched, but resolve to the real files
    418 419   actualLocations, err := resolver.FilesByGlob("*ink-*")
    419 420   assert.NoError(t, err)
    420 421   return actualLocations
    421 422   },
    422  - expected: []Location{
    423  - NewVirtualLocation("/file-1.txt", "/link-1"),
    424  - NewVirtualLocation("/file-2.txt", "/link-2"), // copy 1
    425  - NewVirtualLocation("/file-2.txt", "/link-2"), // copy 2
    426  - NewVirtualLocation("/file-3.txt", "/link-within"),
     423 + expected: []file.Location{
     424 + file.NewVirtualLocation("/file-1.txt", "/link-1"),
     425 + file.NewVirtualLocation("/file-2.txt", "/link-2"), // copy 1
     426 + file.NewVirtualLocation("/file-2.txt", "/link-2"), // copy 2
     427 + file.NewVirtualLocation("/file-3.txt", "/link-within"),
    427 428   },
    428 429   },
    429 430   {
    430 431   name: "by basename",
    431  - runner: func(resolver FileResolver) []Location {
     432 + runner: func(resolver file.Resolver) []file.Location {
    432 433   // links are searched, but resolve to the real files
    433 434   actualLocations, err := resolver.FilesByGlob("**/file-2.txt")
    434 435   assert.NoError(t, err)
    435 436   return actualLocations
    436 437   },
    437  - expected: []Location{
    438  - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
    439  - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
     438 + expected: []file.Location{
     439 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
     440 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
    440 441   },
    441 442   },
    442 443   {
    443 444   name: "by basename glob",
    444  - runner: func(resolver FileResolver) []Location {
     445 + runner: func(resolver file.Resolver) []file.Location {
    445 446   // links are searched, but resolve to the real files
    446 447   actualLocations, err := resolver.FilesByGlob("**/file-?.txt")
    447 448   assert.NoError(t, err)
    448 449   return actualLocations
    449 450   },
    450  - expected: []Location{
    451  - NewVirtualLocation("/file-1.txt", "/file-1.txt"),
    452  - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
    453  - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
    454  - NewVirtualLocation("/file-3.txt", "/file-3.txt"),
    455  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
    456  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied
     451 + expected: []file.Location{
     452 + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"),
     453 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
     454 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
     455 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"),
     456 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
     457 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied
    457 458   },
    458 459   },
    459 460   {
    460 461   name: "by extension",
    461  - runner: func(resolver FileResolver) []Location {
     462 + runner: func(resolver file.Resolver) []file.Location {
    462 463   // links are searched, but resolve to the real files
    463 464   actualLocations, err := resolver.FilesByGlob("**/*.txt")
    464 465   assert.NoError(t, err)
    465 466   return actualLocations
    466 467   },
    467  - expected: []Location{
    468  - NewVirtualLocation("/file-1.txt", "/file-1.txt"),
    469  - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
    470  - NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
    471  - NewVirtualLocation("/file-3.txt", "/file-3.txt"),
    472  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
    473  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied
     468 + expected: []file.Location{
     469 + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"),
     470 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 1
     471 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"), // copy 2
     472 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"),
     473 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
     474 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"), // when we copy into the link path, the same file-4.txt is copied
    474 475   },
    475 476   },
    476 477   {
    477 478   name: "by path to degree 1 link",
    478  - runner: func(resolver FileResolver) []Location {
     479 + runner: func(resolver file.Resolver) []file.Location {
    479 480   // links resolve to the final file
    480 481   actualLocations, err := resolver.FilesByPath("/link-2")
    481 482   assert.NoError(t, err)
    482 483   return actualLocations
    483 484   },
    484  - expected: []Location{
     485 + expected: []file.Location{
    485 486   // we have multiple copies across layers
    486  - NewVirtualLocation("/file-2.txt", "/link-2"),
    487  - NewVirtualLocation("/file-2.txt", "/link-2"),
     487 + file.NewVirtualLocation("/file-2.txt", "/link-2"),
     488 + file.NewVirtualLocation("/file-2.txt", "/link-2"),
    488 489   },
    489 490   },
    490 491   {
    491 492   name: "by path to degree 2 link",
    492  - runner: func(resolver FileResolver) []Location {
     493 + runner: func(resolver file.Resolver) []file.Location {
    493 494   // multiple links resolves to the final file
    494 495   actualLocations, err := resolver.FilesByPath("/link-indirect")
    495 496   assert.NoError(t, err)
    496 497   return actualLocations
    497 498   },
    498  - expected: []Location{
     499 + expected: []file.Location{
    499 500   // we have multiple copies across layers
    500  - NewVirtualLocation("/file-2.txt", "/link-indirect"),
    501  - NewVirtualLocation("/file-2.txt", "/link-indirect"),
     501 + file.NewVirtualLocation("/file-2.txt", "/link-indirect"),
     502 + file.NewVirtualLocation("/file-2.txt", "/link-indirect"),
    502 503   },
    503 504   },
    504 505   }
    skipped 3 lines
    508 509   
    509 510   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    510 511   
    511  - resolver, err := newAllLayersResolver(img)
     512 + resolver, err := NewFromContainerImageAllLayers(img)
    512 513   assert.NoError(t, err)
    513 514   
    514 515   actual := test.runner(resolver)
    skipped 12 lines
    527 528   arch = "aarch64"
    528 529   }
    529 530   
    530  - resolver, err := newAllLayersResolver(img)
     531 + resolver, err := NewFromContainerImageAllLayers(img)
    531 532   assert.NoError(t, err)
    532 533   
    533 534   paths := strset.New()
    skipped 129 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/image_squash_resolver.go syft/internal/fileresolver/container_image_squash.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "fmt"
    5 5   "io"
    6 6   
    7  - "github.com/anchore/stereoscope/pkg/file"
     7 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    8 8   "github.com/anchore/stereoscope/pkg/filetree"
    9 9   "github.com/anchore/stereoscope/pkg/image"
     10 + "github.com/anchore/syft/syft/file"
    10 11  )
    11 12   
    12  -var _ FileResolver = (*imageSquashResolver)(nil)
     13 +var _ file.Resolver = (*ContainerImageSquash)(nil)
    13 14   
    14  -// imageSquashResolver implements path and content access for the Squashed source option for container image data sources.
    15  -type imageSquashResolver struct {
     15 +// ContainerImageSquash implements path and content access for the Squashed source option for container image data sources.
     16 +type ContainerImageSquash struct {
    16 17   img *image.Image
    17 18  }
    18 19   
    19  -// newImageSquashResolver returns a new resolver from the perspective of the squashed representation for the given image.
    20  -func newImageSquashResolver(img *image.Image) (*imageSquashResolver, error) {
     20 +// NewFromContainerImageSquash returns a new resolver from the perspective of the squashed representation for the given image.
     21 +func NewFromContainerImageSquash(img *image.Image) (*ContainerImageSquash, error) {
    21 22   if img.SquashedTree() == nil {
    22 23   return nil, fmt.Errorf("the image does not have have a squashed tree")
    23 24   }
    24 25   
    25  - return &imageSquashResolver{
     26 + return &ContainerImageSquash{
    26 27   img: img,
    27 28   }, nil
    28 29  }
    29 30   
    30 31  // HasPath indicates if the given path exists in the underlying source.
    31  -func (r *imageSquashResolver) HasPath(path string) bool {
    32  - return r.img.SquashedTree().HasPath(file.Path(path))
     32 +func (r *ContainerImageSquash) HasPath(path string) bool {
     33 + return r.img.SquashedTree().HasPath(stereoscopeFile.Path(path))
    33 34  }
    34 35   
    35 36  // FilesByPath returns all file.References that match the given paths within the squashed representation of the image.
    36  -func (r *imageSquashResolver) FilesByPath(paths ...string) ([]Location, error) {
    37  - uniqueFileIDs := file.NewFileReferenceSet()
    38  - uniqueLocations := make([]Location, 0)
     37 +func (r *ContainerImageSquash) FilesByPath(paths ...string) ([]file.Location, error) {
     38 + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
     39 + uniqueLocations := make([]file.Location, 0)
    39 40   
    40 41   for _, path := range paths {
    41 42   ref, err := r.img.SquashedSearchContext.SearchByPath(path, filetree.FollowBasenameLinks)
    skipped 27 lines
    69 70   
    70 71   if resolvedRef.HasReference() && !uniqueFileIDs.Contains(*resolvedRef.Reference) {
    71 72   uniqueFileIDs.Add(*resolvedRef.Reference)
    72  - uniqueLocations = append(uniqueLocations, NewLocationFromImage(path, *resolvedRef.Reference, r.img))
     73 + uniqueLocations = append(uniqueLocations, file.NewLocationFromImage(path, *resolvedRef.Reference, r.img))
    73 74   }
    74 75   }
    75 76   
    skipped 2 lines
    78 79   
    79 80  // FilesByGlob returns all file.References that match the given path glob pattern within the squashed representation of the image.
    80 81  // nolint:gocognit
    81  -func (r *imageSquashResolver) FilesByGlob(patterns ...string) ([]Location, error) {
    82  - uniqueFileIDs := file.NewFileReferenceSet()
    83  - uniqueLocations := make([]Location, 0)
     82 +func (r *ContainerImageSquash) FilesByGlob(patterns ...string) ([]file.Location, error) {
     83 + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
     84 + uniqueLocations := make([]file.Location, 0)
    84 85   
    85 86   for _, pattern := range patterns {
    86 87   results, err := r.img.SquashedSearchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks)
    skipped 26 lines
    113 114   return nil, fmt.Errorf("failed to find files by path (result=%+v): %w", result, err)
    114 115   }
    115 116   for _, resolvedLocation := range resolvedLocations {
    116  - if uniqueFileIDs.Contains(resolvedLocation.ref) {
     117 + if uniqueFileIDs.Contains(resolvedLocation.Reference()) {
    117 118   continue
    118 119   }
    119  - uniqueFileIDs.Add(resolvedLocation.ref)
     120 + uniqueFileIDs.Add(resolvedLocation.Reference())
    120 121   uniqueLocations = append(uniqueLocations, resolvedLocation)
    121 122   }
    122 123   }
    skipped 4 lines
    127 128   
    128 129  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
    129 130  // This is helpful when attempting to find a file that is in the same layer or lower as another file. For the
    130  -// imageSquashResolver, this is a simple path lookup.
    131  -func (r *imageSquashResolver) RelativeFileByPath(_ Location, path string) *Location {
     131 +// ContainerImageSquash, this is a simple path lookup.
     132 +func (r *ContainerImageSquash) RelativeFileByPath(_ file.Location, path string) *file.Location {
    132 133   paths, err := r.FilesByPath(path)
    133 134   if err != nil {
    134 135   return nil
    skipped 7 lines
    142 143   
    143 144  // FileContentsByLocation fetches file contents for a single file reference, regardless of the source layer.
    144 145  // If the path does not exist an error is returned.
    145  -func (r *imageSquashResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) {
    146  - entry, err := r.img.FileCatalog.Get(location.ref)
     146 +func (r *ContainerImageSquash) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
     147 + entry, err := r.img.FileCatalog.Get(location.Reference())
    147 148   if err != nil {
    148 149   return nil, fmt.Errorf("unable to get metadata for path=%q from file catalog: %w", location.RealPath, err)
    149 150   }
    150 151   
    151 152   switch entry.Metadata.Type {
    152  - case file.TypeSymLink, file.TypeHardLink:
     153 + case stereoscopeFile.TypeSymLink, stereoscopeFile.TypeHardLink:
    153 154   // the location we are searching may be a symlink, we should always work with the resolved file
    154 155   locations, err := r.FilesByPath(location.RealPath)
    155 156   if err != nil {
    skipped 8 lines
    164 165   default:
    165 166   return nil, fmt.Errorf("link resolution resulted in multiple results while resolving content location: %+v", location)
    166 167   }
    167  - case file.TypeDirectory:
     168 + case stereoscopeFile.TypeDirectory:
    168 169   return nil, fmt.Errorf("unable to get file contents for directory: %+v", location)
    169 170   }
    170 171   
    171  - return r.img.FileContentsByRef(location.ref)
     172 + return r.img.OpenReference(location.Reference())
    172 173  }
    173 174   
    174  -func (r *imageSquashResolver) AllLocations() <-chan Location {
    175  - results := make(chan Location)
     175 +func (r *ContainerImageSquash) AllLocations() <-chan file.Location {
     176 + results := make(chan file.Location)
    176 177   go func() {
    177 178   defer close(results)
    178  - for _, ref := range r.img.SquashedTree().AllFiles(file.AllTypes()...) {
    179  - results <- NewLocationFromImage(string(ref.RealPath), ref, r.img)
     179 + for _, ref := range r.img.SquashedTree().AllFiles(stereoscopeFile.AllTypes()...) {
     180 + results <- file.NewLocationFromImage(string(ref.RealPath), ref, r.img)
    180 181   }
    181 182   }()
    182 183   return results
    183 184  }
    184 185   
    185  -func (r *imageSquashResolver) FilesByMIMEType(types ...string) ([]Location, error) {
     186 +func (r *ContainerImageSquash) FilesByMIMEType(types ...string) ([]file.Location, error) {
    186 187   refs, err := r.img.SquashedSearchContext.SearchByMIMEType(types...)
    187 188   if err != nil {
    188 189   return nil, err
    189 190   }
    190 191   
    191  - uniqueFileIDs := file.NewFileReferenceSet()
    192  - uniqueLocations := make([]Location, 0)
     192 + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
     193 + uniqueLocations := make([]file.Location, 0)
    193 194   
    194 195   for _, ref := range refs {
    195 196   if ref.HasReference() {
    196 197   if uniqueFileIDs.Contains(*ref.Reference) {
    197 198   continue
    198 199   }
    199  - location := NewLocationFromImage(string(ref.RequestPath), *ref.Reference, r.img)
     200 + location := file.NewLocationFromImage(string(ref.RequestPath), *ref.Reference, r.img)
    200 201   
    201 202   uniqueFileIDs.Add(*ref.Reference)
    202 203   uniqueLocations = append(uniqueLocations, location)
    skipped 3 lines
    206 207   return uniqueLocations, nil
    207 208  }
    208 209   
    209  -func (r *imageSquashResolver) FileMetadataByLocation(location Location) (FileMetadata, error) {
     210 +func (r *ContainerImageSquash) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
    210 211   return fileMetadataByLocation(r.img, location)
    211 212  }
    212 213   
  • ■ ■ ■ ■ ■ ■
    syft/source/image_squash_resolver_test.go syft/internal/fileresolver/container_image_squash_test.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "io"
    skipped 1 lines
    6 6   "testing"
    7 7   
    8 8   "github.com/google/go-cmp/cmp"
    9  - "github.com/google/go-cmp/cmp/cmpopts"
    10 9   "github.com/scylladb/go-set/strset"
    11 10   "github.com/stretchr/testify/assert"
    12 11   "github.com/stretchr/testify/require"
    13 12   
    14  - "github.com/anchore/stereoscope/pkg/file"
    15 13   "github.com/anchore/stereoscope/pkg/imagetest"
     14 + "github.com/anchore/syft/syft/file"
    16 15  )
    17 16   
    18 17  func TestImageSquashResolver_FilesByPath(t *testing.T) {
    skipped 54 lines
    73 72   t.Run(c.name, func(t *testing.T) {
    74 73   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    75 74   
    76  - resolver, err := newImageSquashResolver(img)
     75 + resolver, err := NewFromContainerImageSquash(img)
    77 76   if err != nil {
    78 77   t.Fatalf("could not create resolver: %+v", err)
    79 78   }
    skipped 30 lines
    110 109   
    111 110   actual := refs[0]
    112 111   
    113  - if string(actual.ref.RealPath) != c.resolvePath {
    114  - t.Errorf("bad resolve path: '%s'!='%s'", string(actual.ref.RealPath), c.resolvePath)
     112 + if string(actual.Reference().RealPath) != c.resolvePath {
     113 + t.Errorf("bad resolve path: '%s'!='%s'", string(actual.Reference().RealPath), c.resolvePath)
    115 114   }
    116 115   
    117  - if c.resolvePath != "" && string(actual.ref.RealPath) != actual.RealPath {
     116 + if c.resolvePath != "" && string(actual.Reference().RealPath) != actual.RealPath {
    118 117   t.Errorf("we should always prefer real paths over ones with links")
    119 118   }
    120 119   
    121  - layer := img.FileCatalog.Layer(actual.ref)
     120 + layer := img.FileCatalog.Layer(actual.Reference())
    122 121   
    123 122   if layer.Metadata.Index != c.resolveLayer {
    124 123   t.Errorf("bad resolve layer: '%d'!='%d'", layer.Metadata.Index, c.resolveLayer)
    skipped 61 lines
    186 185   t.Run(c.name, func(t *testing.T) {
    187 186   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    188 187   
    189  - resolver, err := newImageSquashResolver(img)
     188 + resolver, err := NewFromContainerImageSquash(img)
    190 189   if err != nil {
    191 190   t.Fatalf("could not create resolver: %+v", err)
    192 191   }
    skipped 19 lines
    212 211   
    213 212   actual := refs[0]
    214 213   
    215  - if string(actual.ref.RealPath) != c.resolvePath {
    216  - t.Errorf("bad resolve path: '%s'!='%s'", string(actual.ref.RealPath), c.resolvePath)
     214 + if string(actual.Reference().RealPath) != c.resolvePath {
     215 + t.Errorf("bad resolve path: '%s'!='%s'", string(actual.Reference().RealPath), c.resolvePath)
    217 216   }
    218 217   
    219  - if c.resolvePath != "" && string(actual.ref.RealPath) != actual.RealPath {
     218 + if c.resolvePath != "" && string(actual.Reference().RealPath) != actual.RealPath {
    220 219   t.Errorf("we should always prefer real paths over ones with links")
    221 220   }
    222 221   
    223  - layer := img.FileCatalog.Layer(actual.ref)
     222 + layer := img.FileCatalog.Layer(actual.Reference())
    224 223   
    225 224   if layer.Metadata.Index != c.resolveLayer {
    226 225   t.Errorf("bad resolve layer: '%d'!='%d'", layer.Metadata.Index, c.resolveLayer)
    skipped 20 lines
    247 246   t.Run(test.fixtureName, func(t *testing.T) {
    248 247   img := imagetest.GetFixtureImage(t, "docker-archive", test.fixtureName)
    249 248   
    250  - resolver, err := newImageSquashResolver(img)
     249 + resolver, err := NewFromContainerImageSquash(img)
    251 250   assert.NoError(t, err)
    252 251   
    253 252   locations, err := resolver.FilesByMIMEType(test.mimeType)
    skipped 10 lines
    264 263  func Test_imageSquashResolver_hasFilesystemIDInLocation(t *testing.T) {
    265 264   img := imagetest.GetFixtureImage(t, "docker-archive", "image-duplicate-path")
    266 265   
    267  - resolver, err := newImageSquashResolver(img)
     266 + resolver, err := NewFromContainerImageSquash(img)
    268 267   assert.NoError(t, err)
    269 268   
    270 269   locations, err := resolver.FilesByMIMEType("text/plain")
    skipped 51 lines
    322 321   t.Run(test.name, func(t *testing.T) {
    323 322   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    324 323   
    325  - resolver, err := newImageSquashResolver(img)
     324 + resolver, err := NewFromContainerImageSquash(img)
    326 325   assert.NoError(t, err)
    327 326   
    328 327   refs, err := resolver.FilesByPath(test.path)
    skipped 18 lines
    347 346   
    348 347   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    349 348   
    350  - resolver, err := newImageSquashResolver(img)
     349 + resolver, err := NewFromContainerImageSquash(img)
    351 350   assert.NoError(t, err)
    352 351   
    353  - var dirLoc *Location
     352 + var dirLoc *file.Location
    354 353   for loc := range resolver.AllLocations() {
    355  - entry, err := resolver.img.FileCatalog.Get(loc.ref)
     354 + entry, err := resolver.img.FileCatalog.Get(loc.Reference())
    356 355   require.NoError(t, err)
    357 356   if entry.Metadata.IsDir() {
    358 357   dirLoc = &loc
    skipped 11 lines
    370 369  func Test_imageSquashResolver_resolvesLinks(t *testing.T) {
    371 370   tests := []struct {
    372 371   name string
    373  - runner func(FileResolver) []Location
    374  - expected []Location
     372 + runner func(file.Resolver) []file.Location
     373 + expected []file.Location
    375 374   }{
    376 375   {
    377 376   name: "by mimetype",
    378  - runner: func(resolver FileResolver) []Location {
     377 + runner: func(resolver file.Resolver) []file.Location {
    379 378   // links should not show up when searching mimetype
    380 379   actualLocations, err := resolver.FilesByMIMEType("text/plain")
    381 380   assert.NoError(t, err)
    382 381   return actualLocations
    383 382   },
    384  - expected: []Location{
    385  - NewVirtualLocation("/etc/group", "/etc/group"),
    386  - NewVirtualLocation("/etc/passwd", "/etc/passwd"),
    387  - NewVirtualLocation("/etc/shadow", "/etc/shadow"),
    388  - NewVirtualLocation("/file-1.txt", "/file-1.txt"),
    389  - NewVirtualLocation("/file-3.txt", "/file-3.txt"),
    390  - NewVirtualLocation("/file-2.txt", "/file-2.txt"),
    391  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
     383 + expected: []file.Location{
     384 + file.NewVirtualLocation("/etc/group", "/etc/group"),
     385 + file.NewVirtualLocation("/etc/passwd", "/etc/passwd"),
     386 + file.NewVirtualLocation("/etc/shadow", "/etc/shadow"),
     387 + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"),
     388 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"),
     389 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"),
     390 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
    392 391   },
    393 392   },
    394 393   {
    395 394   name: "by glob to links",
    396  - runner: func(resolver FileResolver) []Location {
     395 + runner: func(resolver file.Resolver) []file.Location {
    397 396   // links are searched, but resolve to the real files
    398 397   actualLocations, err := resolver.FilesByGlob("*ink-*")
    399 398   assert.NoError(t, err)
    400 399   return actualLocations
    401 400   },
    402  - expected: []Location{
    403  - NewVirtualLocation("/file-1.txt", "/link-1"),
    404  - NewVirtualLocation("/file-2.txt", "/link-2"),
     401 + expected: []file.Location{
     402 + file.NewVirtualLocation("/file-1.txt", "/link-1"),
     403 + file.NewVirtualLocation("/file-2.txt", "/link-2"),
    405 404   
    406 405   // though this is a link, and it matches to the file, the resolver de-duplicates files
    407 406   // by the real path, so it is not included in the results
    408  - //NewVirtualLocation("/file-2.txt", "/link-indirect"),
     407 + //file.NewVirtualLocation("/file-2.txt", "/link-indirect"),
    409 408   
    410  - NewVirtualLocation("/file-3.txt", "/link-within"),
     409 + file.NewVirtualLocation("/file-3.txt", "/link-within"),
    411 410   },
    412 411   },
    413 412   {
    414 413   name: "by basename",
    415  - runner: func(resolver FileResolver) []Location {
     414 + runner: func(resolver file.Resolver) []file.Location {
    416 415   // links are searched, but resolve to the real files
    417 416   actualLocations, err := resolver.FilesByGlob("**/file-2.txt")
    418 417   assert.NoError(t, err)
    419 418   return actualLocations
    420 419   },
    421  - expected: []Location{
     420 + expected: []file.Location{
    422 421   // this has two copies in the base image, which overwrites the same location
    423  - NewVirtualLocation("/file-2.txt", "/file-2.txt"),
     422 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"),
    424 423   },
    425 424   },
    426 425   {
    427 426   name: "by basename glob",
    428  - runner: func(resolver FileResolver) []Location {
     427 + runner: func(resolver file.Resolver) []file.Location {
    429 428   // links are searched, but resolve to the real files
    430 429   actualLocations, err := resolver.FilesByGlob("**/file-?.txt")
    431 430   assert.NoError(t, err)
    432 431   return actualLocations
    433 432   },
    434  - expected: []Location{
    435  - NewVirtualLocation("/file-1.txt", "/file-1.txt"),
    436  - NewVirtualLocation("/file-2.txt", "/file-2.txt"),
    437  - NewVirtualLocation("/file-3.txt", "/file-3.txt"),
    438  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
     433 + expected: []file.Location{
     434 + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"),
     435 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"),
     436 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"),
     437 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
    439 438   },
    440 439   },
    441 440   {
    442 441   name: "by basename glob to links",
    443  - runner: func(resolver FileResolver) []Location {
     442 + runner: func(resolver file.Resolver) []file.Location {
    444 443   actualLocations, err := resolver.FilesByGlob("**/link-*")
    445 444   assert.NoError(t, err)
    446 445   return actualLocations
    447 446   },
    448  - expected: []Location{
     447 + expected: []file.Location{
     448 + file.NewVirtualLocation("/file-1.txt", "/link-1"),
     449 + file.NewVirtualLocation("/file-2.txt", "/link-2"),
    449 450   
    450  - {
    451  - LocationData: LocationData{
    452  - Coordinates: Coordinates{
    453  - RealPath: "/file-1.txt",
    454  - },
    455  - VirtualPath: "/link-1",
    456  - ref: file.Reference{RealPath: "/file-1.txt"},
    457  - },
    458  - },
    459  - {
    460  - LocationData: LocationData{
    461  - 
    462  - Coordinates: Coordinates{
    463  - RealPath: "/file-2.txt",
    464  - },
    465  - VirtualPath: "/link-2",
    466  - ref: file.Reference{RealPath: "/file-2.txt"},
    467  - },
    468  - },
    469 451   // we already have this real file path via another link, so only one is returned
    470  - //{
    471  - // LocationData: LocationData{
    472  - // Coordinates: Coordinates{
    473  - // RealPath: "/file-2.txt",
    474  - // },
    475  - // VirtualPath: "/link-indirect",
    476  - // ref: file.Reference{RealPath: "/file-2.txt"},
    477  - // },
    478  - //},
    479  - {
    480  - LocationData: LocationData{
    481  - Coordinates: Coordinates{
    482  - RealPath: "/file-3.txt",
    483  - },
    484  - VirtualPath: "/link-within",
    485  - ref: file.Reference{RealPath: "/file-3.txt"},
    486  - },
    487  - },
     452 + // file.NewVirtualLocation("/file-2.txt", "/link-indirect"),
     453 + 
     454 + file.NewVirtualLocation("/file-3.txt", "/link-within"),
    488 455   },
    489 456   },
    490 457   {
    491 458   name: "by extension",
    492  - runner: func(resolver FileResolver) []Location {
     459 + runner: func(resolver file.Resolver) []file.Location {
    493 460   // links are searched, but resolve to the real files
    494 461   actualLocations, err := resolver.FilesByGlob("**/*.txt")
    495 462   assert.NoError(t, err)
    496 463   return actualLocations
    497 464   },
    498  - expected: []Location{
    499  - NewVirtualLocation("/file-1.txt", "/file-1.txt"),
    500  - NewVirtualLocation("/file-2.txt", "/file-2.txt"),
    501  - NewVirtualLocation("/file-3.txt", "/file-3.txt"),
    502  - NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
     465 + expected: []file.Location{
     466 + file.NewVirtualLocation("/file-1.txt", "/file-1.txt"),
     467 + file.NewVirtualLocation("/file-2.txt", "/file-2.txt"),
     468 + file.NewVirtualLocation("/file-3.txt", "/file-3.txt"),
     469 + file.NewVirtualLocation("/parent/file-4.txt", "/parent/file-4.txt"),
    503 470   },
    504 471   },
    505 472   {
    506 473   name: "by path to degree 1 link",
    507  - runner: func(resolver FileResolver) []Location {
     474 + runner: func(resolver file.Resolver) []file.Location {
    508 475   // links resolve to the final file
    509 476   actualLocations, err := resolver.FilesByPath("/link-2")
    510 477   assert.NoError(t, err)
    511 478   return actualLocations
    512 479   },
    513  - expected: []Location{
     480 + expected: []file.Location{
    514 481   // we have multiple copies across layers
    515  - NewVirtualLocation("/file-2.txt", "/link-2"),
     482 + file.NewVirtualLocation("/file-2.txt", "/link-2"),
    516 483   },
    517 484   },
    518 485   {
    519 486   name: "by path to degree 2 link",
    520  - runner: func(resolver FileResolver) []Location {
     487 + runner: func(resolver file.Resolver) []file.Location {
    521 488   // multiple links resolves to the final file
    522 489   actualLocations, err := resolver.FilesByPath("/link-indirect")
    523 490   assert.NoError(t, err)
    524 491   return actualLocations
    525 492   },
    526  - expected: []Location{
     493 + expected: []file.Location{
    527 494   // we have multiple copies across layers
    528  - NewVirtualLocation("/file-2.txt", "/link-indirect"),
     495 + file.NewVirtualLocation("/file-2.txt", "/link-indirect"),
    529 496   },
    530 497   },
    531 498   }
    skipped 3 lines
    535 502   
    536 503   img := imagetest.GetFixtureImage(t, "docker-archive", "image-symlinks")
    537 504   
    538  - resolver, err := newImageSquashResolver(img)
     505 + resolver, err := NewFromContainerImageSquash(img)
    539 506   assert.NoError(t, err)
    540 507   
    541 508   actual := test.runner(resolver)
    skipped 4 lines
    546 513   
    547 514  }
    548 515   
    549  -func compareLocations(t *testing.T, expected, actual []Location) {
    550  - t.Helper()
    551  - ignoreUnexported := cmpopts.IgnoreFields(LocationData{}, "ref")
    552  - ignoreMetadata := cmpopts.IgnoreFields(LocationMetadata{}, "Annotations")
    553  - ignoreFS := cmpopts.IgnoreFields(Coordinates{}, "FileSystemID")
    554  - 
    555  - sort.Sort(Locations(expected))
    556  - sort.Sort(Locations(actual))
    557  - 
    558  - if d := cmp.Diff(expected, actual,
    559  - ignoreUnexported,
    560  - ignoreFS,
    561  - ignoreMetadata,
    562  - ); d != "" {
    563  - 
    564  - t.Errorf("unexpected locations (-want +got):\n%s", d)
    565  - }
    566  - 
    567  -}
    568  - 
    569 516  func TestSquashResolver_AllLocations(t *testing.T) {
    570 517   img := imagetest.GetFixtureImage(t, "docker-archive", "image-files-deleted")
    571 518   
    572  - resolver, err := newImageSquashResolver(img)
     519 + resolver, err := NewFromContainerImageSquash(img)
    573 520   assert.NoError(t, err)
    574 521   
    575 522   paths := strset.New()
    skipped 20 lines
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/deferred.go
     1 +package fileresolver
     2 + 
     3 +import (
     4 + "io"
     5 + 
     6 + "github.com/anchore/syft/internal/log"
     7 + "github.com/anchore/syft/syft/file"
     8 +)
     9 + 
     10 +var _ file.Resolver = (*Deferred)(nil)
     11 + 
     12 +func NewDeferred(creator func() (file.Resolver, error)) *Deferred {
     13 + return &Deferred{
     14 + creator: creator,
     15 + }
     16 +}
     17 + 
     18 +type Deferred struct {
     19 + creator func() (file.Resolver, error)
     20 + resolver file.Resolver
     21 +}
     22 + 
     23 +func (d *Deferred) getResolver() (file.Resolver, error) {
     24 + if d.resolver == nil {
     25 + resolver, err := d.creator()
     26 + if err != nil {
     27 + return nil, err
     28 + }
     29 + d.resolver = resolver
     30 + }
     31 + return d.resolver, nil
     32 +}
     33 + 
     34 +func (d *Deferred) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
     35 + r, err := d.getResolver()
     36 + if err != nil {
     37 + return nil, err
     38 + }
     39 + return r.FileContentsByLocation(location)
     40 +}
     41 + 
     42 +func (d *Deferred) HasPath(s string) bool {
     43 + r, err := d.getResolver()
     44 + if err != nil {
     45 + log.Debug("unable to get resolver: %v", err)
     46 + return false
     47 + }
     48 + return r.HasPath(s)
     49 +}
     50 + 
     51 +func (d *Deferred) FilesByPath(paths ...string) ([]file.Location, error) {
     52 + r, err := d.getResolver()
     53 + if err != nil {
     54 + return nil, err
     55 + }
     56 + return r.FilesByPath(paths...)
     57 +}
     58 + 
     59 +func (d *Deferred) FilesByGlob(patterns ...string) ([]file.Location, error) {
     60 + r, err := d.getResolver()
     61 + if err != nil {
     62 + return nil, err
     63 + }
     64 + return r.FilesByGlob(patterns...)
     65 +}
     66 + 
     67 +func (d *Deferred) FilesByMIMEType(types ...string) ([]file.Location, error) {
     68 + r, err := d.getResolver()
     69 + if err != nil {
     70 + return nil, err
     71 + }
     72 + return r.FilesByMIMEType(types...)
     73 +}
     74 + 
     75 +func (d *Deferred) RelativeFileByPath(location file.Location, path string) *file.Location {
     76 + r, err := d.getResolver()
     77 + if err != nil {
     78 + return nil
     79 + }
     80 + return r.RelativeFileByPath(location, path)
     81 +}
     82 + 
     83 +func (d *Deferred) AllLocations() <-chan file.Location {
     84 + r, err := d.getResolver()
     85 + if err != nil {
     86 + log.Debug("unable to get resolver: %v", err)
     87 + return nil
     88 + }
     89 + return r.AllLocations()
     90 +}
     91 + 
     92 +func (d *Deferred) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
     93 + r, err := d.getResolver()
     94 + if err != nil {
     95 + return file.Metadata{}, err
     96 + }
     97 + return r.FileMetadataByLocation(location)
     98 +}
     99 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/deferred_resolver_test.go syft/internal/fileresolver/deferred_test.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "testing"
    5 5   
    6 6   "github.com/stretchr/testify/require"
     7 + 
     8 + "github.com/anchore/syft/syft/file"
    7 9  )
    8 10   
    9 11  func Test_NewDeferredResolver(t *testing.T) {
    10 12   creatorCalled := false
    11 13   
    12  - deferredResolver := NewDeferredResolver(func() (FileResolver, error) {
     14 + deferredResolver := NewDeferred(func() (file.Resolver, error) {
    13 15   creatorCalled = true
    14  - return NewMockResolverForPaths(), nil
     16 + return file.NewMockResolverForPaths(), nil
    15 17   })
    16 18   
    17 19   require.False(t, creatorCalled)
    skipped 8 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/directory_resolver.go syft/internal/fileresolver/directory.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "errors"
    skipped 5 lines
    10 10   "runtime"
    11 11   "strings"
    12 12   
    13  - "github.com/anchore/stereoscope/pkg/file"
     13 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
    14 14   "github.com/anchore/stereoscope/pkg/filetree"
    15 15   "github.com/anchore/syft/internal/log"
     16 + "github.com/anchore/syft/syft/file"
    16 17  )
    17 18   
    18 19  const WindowsOS = "windows"
    skipped 4 lines
    23 24   "/sys",
    24 25  }
    25 26   
    26  -var errSkipPath = errors.New("skip path")
     27 +var ErrSkipPath = errors.New("skip path")
    27 28   
    28  -var _ FileResolver = (*directoryResolver)(nil)
     29 +var _ file.Resolver = (*Directory)(nil)
    29 30   
    30  -// directoryResolver implements path and content access for the directory data source.
    31  -type directoryResolver struct {
     31 +// Directory implements path and content access for the directory data source.
     32 +type Directory struct {
    32 33   path string
    33 34   base string
    34 35   currentWdRelativeToRoot string
    skipped 4 lines
    39 40   indexer *directoryIndexer
    40 41  }
    41 42   
    42  -func newDirectoryResolver(root string, base string, pathFilters ...pathIndexVisitor) (*directoryResolver, error) {
    43  - r, err := newDirectoryResolverWithoutIndex(root, base, pathFilters...)
     43 +func NewFromDirectory(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) {
     44 + r, err := newFromDirectoryWithoutIndex(root, base, pathFilters...)
    44 45   if err != nil {
    45 46   return nil, err
    46 47   }
    skipped 1 lines
    48 49   return r, r.buildIndex()
    49 50  }
    50 51   
    51  -func newDirectoryResolverWithoutIndex(root string, base string, pathFilters ...pathIndexVisitor) (*directoryResolver, error) {
     52 +func newFromDirectoryWithoutIndex(root string, base string, pathFilters ...PathIndexVisitor) (*Directory, error) {
    52 53   currentWD, err := os.Getwd()
    53 54   if err != nil {
    54 55   return nil, fmt.Errorf("could not get CWD: %w", err)
    skipped 32 lines
    87 88   currentWdRelRoot = filepath.Clean(cleanRoot)
    88 89   }
    89 90   
    90  - return &directoryResolver{
     91 + return &Directory{
    91 92   path: cleanRoot,
    92 93   base: cleanBase,
    93 94   currentWd: cleanCWD,
    skipped 4 lines
    98 99   }, nil
    99 100  }
    100 101   
    101  -func (r *directoryResolver) buildIndex() error {
     102 +func (r *Directory) buildIndex() error {
    102 103   if r.indexer == nil {
    103 104   return fmt.Errorf("no directory indexer configured")
    104 105   }
    skipped 9 lines
    114 115   return nil
    115 116  }
    116 117   
    117  -func (r directoryResolver) requestPath(userPath string) (string, error) {
     118 +func (r Directory) requestPath(userPath string) (string, error) {
    118 119   if filepath.IsAbs(userPath) {
    119 120   // don't allow input to potentially hop above root path
    120 121   userPath = path.Join(r.path, userPath)
    skipped 10 lines
    131 132   return userPath, nil
    132 133  }
    133 134   
    134  -func (r directoryResolver) responsePath(path string) string {
     135 +func (r Directory) responsePath(path string) string {
    135 136   // check to see if we need to encode back to Windows from posix
    136 137   if runtime.GOOS == WindowsOS {
    137 138   path = posixToWindows(path)
    skipped 16 lines
    154 155  }
    155 156   
    156 157  // HasPath indicates if the given path exists in the underlying source.
    157  -func (r *directoryResolver) HasPath(userPath string) bool {
     158 +func (r *Directory) HasPath(userPath string) bool {
    158 159   requestPath, err := r.requestPath(userPath)
    159 160   if err != nil {
    160 161   return false
    161 162   }
    162  - return r.tree.HasPath(file.Path(requestPath))
     163 + return r.tree.HasPath(stereoscopeFile.Path(requestPath))
    163 164  }
    164 165   
    165 166  // Stringer to represent a directory path data source
    166  -func (r directoryResolver) String() string {
     167 +func (r Directory) String() string {
    167 168   return fmt.Sprintf("dir:%s", r.path)
    168 169  }
    169 170   
    170 171  // FilesByPath returns all file.References that match the given paths from the directory.
    171  -func (r directoryResolver) FilesByPath(userPaths ...string) ([]Location, error) {
    172  - var references = make([]Location, 0)
     172 +func (r Directory) FilesByPath(userPaths ...string) ([]file.Location, error) {
     173 + var references = make([]file.Location, 0)
    173 174   
    174 175   for _, userPath := range userPaths {
    175 176   userStrPath, err := r.requestPath(userPath)
    skipped 30 lines
    206 207   
    207 208   if ref.HasReference() {
    208 209   references = append(references,
    209  - NewVirtualLocationFromDirectory(
     210 + file.NewVirtualLocationFromDirectory(
    210 211   r.responsePath(string(ref.RealPath)), // the actual path relative to the resolver root
    211 212   r.responsePath(userStrPath), // the path used to access this file, relative to the resolver root
    212 213   *ref.Reference,
    skipped 6 lines
    219 220  }
    220 221   
    221 222  // FilesByGlob returns all file.References that match the given path glob pattern from any layer in the image.
    222  -func (r directoryResolver) FilesByGlob(patterns ...string) ([]Location, error) {
    223  - uniqueFileIDs := file.NewFileReferenceSet()
    224  - uniqueLocations := make([]Location, 0)
     223 +func (r Directory) FilesByGlob(patterns ...string) ([]file.Location, error) {
     224 + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
     225 + uniqueLocations := make([]file.Location, 0)
    225 226   
    226 227   for _, pattern := range patterns {
    227 228   refVias, err := r.searchContext.SearchByGlob(pattern, filetree.FollowBasenameLinks)
    skipped 14 lines
    242 243   continue
    243 244   }
    244 245   
    245  - loc := NewVirtualLocationFromDirectory(
     246 + loc := file.NewVirtualLocationFromDirectory(
    246 247   r.responsePath(string(refVia.Reference.RealPath)), // the actual path relative to the resolver root
    247 248   r.responsePath(string(refVia.RequestPath)), // the path used to access this file, relative to the resolver root
    248 249   *refVia.Reference,
    skipped 8 lines
    257 258   
    258 259  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
    259 260  // This is helpful when attempting to find a file that is in the same layer or lower as another file. For the
    260  -// directoryResolver, this is a simple path lookup.
    261  -func (r *directoryResolver) RelativeFileByPath(_ Location, path string) *Location {
     261 +// Directory, this is a simple path lookup.
     262 +func (r *Directory) RelativeFileByPath(_ file.Location, path string) *file.Location {
    262 263   paths, err := r.FilesByPath(path)
    263 264   if err != nil {
    264 265   return nil
    skipped 7 lines
    272 273   
    273 274  // FileContentsByLocation fetches file contents for a single file reference relative to a directory.
    274 275  // If the path does not exist an error is returned.
    275  -func (r directoryResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) {
    276  - if location.ref.RealPath == "" {
     276 +func (r Directory) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
     277 + if location.RealPath == "" {
    277 278   return nil, errors.New("empty path given")
    278 279   }
    279 280   
    280  - entry, err := r.index.Get(location.ref)
     281 + entry, err := r.index.Get(location.Reference())
    281 282   if err != nil {
    282 283   return nil, err
    283 284   }
    284 285   
    285 286   // don't consider directories
    286  - if entry.Type == file.TypeDirectory {
    287  - return nil, fmt.Errorf("cannot read contents of non-file %q", location.ref.RealPath)
     287 + if entry.Type == stereoscopeFile.TypeDirectory {
     288 + return nil, fmt.Errorf("cannot read contents of non-file %q", location.Reference().RealPath)
    288 289   }
    289 290   
    290 291   // RealPath is posix so for windows directory resolver we need to translate
    291 292   // to its true on disk path.
    292  - filePath := string(location.ref.RealPath)
     293 + filePath := string(location.Reference().RealPath)
    293 294   if runtime.GOOS == WindowsOS {
    294 295   filePath = posixToWindows(filePath)
    295 296   }
    296 297   
    297  - return file.NewLazyReadCloser(filePath), nil
     298 + return stereoscopeFile.NewLazyReadCloser(filePath), nil
    298 299  }
    299 300   
    300  -func (r *directoryResolver) AllLocations() <-chan Location {
    301  - results := make(chan Location)
     301 +func (r *Directory) AllLocations() <-chan file.Location {
     302 + results := make(chan file.Location)
    302 303   go func() {
    303 304   defer close(results)
    304  - for _, ref := range r.tree.AllFiles(file.AllTypes()...) {
    305  - results <- NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), ref)
     305 + for _, ref := range r.tree.AllFiles(stereoscopeFile.AllTypes()...) {
     306 + results <- file.NewLocationFromDirectory(r.responsePath(string(ref.RealPath)), ref)
    306 307   }
    307 308   }()
    308 309   return results
    309 310  }
    310 311   
    311  -func (r *directoryResolver) FileMetadataByLocation(location Location) (FileMetadata, error) {
    312  - entry, err := r.index.Get(location.ref)
     312 +func (r *Directory) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
     313 + entry, err := r.index.Get(location.Reference())
    313 314   if err != nil {
    314  - return FileMetadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist)
     315 + return file.Metadata{}, fmt.Errorf("location: %+v : %w", location, os.ErrNotExist)
    315 316   }
    316 317   
    317 318   return entry.Metadata, nil
    318 319  }
    319 320   
    320  -func (r *directoryResolver) FilesByMIMEType(types ...string) ([]Location, error) {
    321  - uniqueFileIDs := file.NewFileReferenceSet()
    322  - uniqueLocations := make([]Location, 0)
     321 +func (r *Directory) FilesByMIMEType(types ...string) ([]file.Location, error) {
     322 + uniqueFileIDs := stereoscopeFile.NewFileReferenceSet()
     323 + uniqueLocations := make([]file.Location, 0)
    323 324   
    324 325   refVias, err := r.searchContext.SearchByMIMEType(types...)
    325 326   if err != nil {
    skipped 6 lines
    332 333   if uniqueFileIDs.Contains(*refVia.Reference) {
    333 334   continue
    334 335   }
    335  - location := NewLocationFromDirectory(
     336 + location := file.NewLocationFromDirectory(
    336 337   r.responsePath(string(refVia.Reference.RealPath)),
    337 338   *refVia.Reference,
    338 339   )
    skipped 34 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/directory_indexer.go syft/internal/fileresolver/directory_indexer.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "errors"
    skipped 15 lines
    20 20   "github.com/anchore/syft/syft/event"
    21 21  )
    22 22   
    23  -type pathIndexVisitor func(string, os.FileInfo, error) error
     23 +type PathIndexVisitor func(string, os.FileInfo, error) error
    24 24   
    25 25  type directoryIndexer struct {
    26 26   path string
    27 27   base string
    28  - pathIndexVisitors []pathIndexVisitor
     28 + pathIndexVisitors []PathIndexVisitor
    29 29   errPaths map[string]error
    30 30   tree filetree.ReadWriter
    31 31   index filetree.Index
    32 32  }
    33 33   
    34  -func newDirectoryIndexer(path, base string, visitors ...pathIndexVisitor) *directoryIndexer {
     34 +func newDirectoryIndexer(path, base string, visitors ...PathIndexVisitor) *directoryIndexer {
    35 35   i := &directoryIndexer{
    36 36   path: path,
    37 37   base: base,
    38 38   tree: filetree.New(),
    39 39   index: filetree.NewIndex(),
    40  - pathIndexVisitors: append([]pathIndexVisitor{requireFileInfo, disallowByFileType, disallowUnixSystemRuntimePath}, visitors...),
     40 + pathIndexVisitors: append([]PathIndexVisitor{requireFileInfo, disallowByFileType, disallowUnixSystemRuntimePath}, visitors...),
    41 41   errPaths: make(map[string]error),
    42 42   }
    43 43   
    44 44   // these additional stateful visitors should be the first thing considered when walking / indexing
    45 45   i.pathIndexVisitors = append(
    46  - []pathIndexVisitor{
     46 + []PathIndexVisitor{
    47 47   i.disallowRevisitingVisitor,
    48 48   i.disallowFileAccessErr,
    49 49   },
    skipped 131 lines
    181 181   
    182 182  func (r *directoryIndexer) disallowFileAccessErr(path string, _ os.FileInfo, err error) error {
    183 183   if r.isFileAccessErr(path, err) {
    184  - return errSkipPath
     184 + return ErrSkipPath
    185 185   }
    186 186   return nil
    187 187  }
    skipped 123 lines
    311 311   // signal to walk() that we should skip this directory entirely
    312 312   return fs.SkipDir
    313 313   }
    314  - return errSkipPath
     314 + return ErrSkipPath
    315 315   }
    316 316   return nil
    317 317  }
    skipped 12 lines
    330 330   }
    331 331   switch file.TypeFromMode(info.Mode()) {
    332 332   case file.TypeCharacterDevice, file.TypeSocket, file.TypeBlockDevice, file.TypeFIFO, file.TypeIrregular:
    333  - return errSkipPath
     333 + return ErrSkipPath
    334 334   // note: symlinks that point to these files may still get by.
    335 335   // We handle this later in processing to help prevent against infinite links traversal.
    336 336   }
    skipped 3 lines
    340 340   
    341 341  func requireFileInfo(_ string, info os.FileInfo, _ error) error {
    342 342   if info == nil {
    343  - return errSkipPath
     343 + return ErrSkipPath
    344 344   }
    345 345   return nil
    346 346  }
    skipped 20 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/directory_indexer_test.go syft/internal/fileresolver/directory_indexer_test.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "io/fs"
    skipped 167 lines
    172 172  }
    173 173   
    174 174  func TestDirectoryIndexer_index(t *testing.T) {
    175  - // note: this test is testing the effects from newDirectoryResolver, indexTree, and addPathToIndex
     175 + // note: this test is testing the effects from NewFromDirectory, indexTree, and addPathToIndex
    176 176   indexer := newDirectoryIndexer("test-fixtures/system_paths/target", "")
    177 177   tree, index, err := indexer.build()
    178 178   require.NoError(t, err)
    skipped 58 lines
    237 237   }
    238 238   resolver := newDirectoryIndexer("./test-fixtures/symlinks-prune-indexing", "")
    239 239   // we want to cut ahead of any possible filters to see what paths are considered for indexing (closest to walking)
    240  - resolver.pathIndexVisitors = append([]pathIndexVisitor{pathObserver}, resolver.pathIndexVisitors...)
     240 + resolver.pathIndexVisitors = append([]PathIndexVisitor{pathObserver}, resolver.pathIndexVisitors...)
    241 241   
    242 242   // note: this test is NOT about the effects left on the tree or the index, but rather the WHICH paths that are
    243 243   // considered for indexing and HOW traversal prunes paths that have already been visited
    skipped 86 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/directory_resolver_test.go syft/internal/fileresolver/directory_test.go
    1 1  //go:build !windows
    2 2  // +build !windows
    3 3   
    4  -package source
     4 +package fileresolver
    5 5   
    6 6  import (
    7 7   "io"
    8 8   "io/fs"
    9  - "io/ioutil"
    10 9   "os"
    11 10   "path/filepath"
    12 11   "sort"
    skipped 6 lines
    19 18   "github.com/stretchr/testify/assert"
    20 19   "github.com/stretchr/testify/require"
    21 20   
    22  - "github.com/anchore/stereoscope/pkg/file"
     21 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
     22 + "github.com/anchore/syft/syft/file"
    23 23  )
    24 24   
    25 25  func TestDirectoryResolver_FilesByPath_relativeRoot(t *testing.T) {
    skipped 20 lines
    46 46   },
    47 47   },
    48 48   {
    49  - name: "should find a file from a relative path (root above cwd)",
     49 + name: "should find a file from a relative path (root above cwd)",
     50 + // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great
    50 51   relativeRoot: "../",
    51  - input: "sbom/sbom.go",
     52 + input: "fileresolver/directory.go",
    52 53   expected: []string{
    53  - "sbom/sbom.go",
     54 + "fileresolver/directory.go",
    54 55   },
    55 56   },
    56 57   }
    57 58   for _, c := range cases {
    58 59   t.Run(c.name, func(t *testing.T) {
    59  - resolver, err := newDirectoryResolver(c.relativeRoot, "")
     60 + resolver, err := NewFromDirectory(c.relativeRoot, "")
    60 61   assert.NoError(t, err)
    61 62   
    62 63   refs, err := resolver.FilesByPath(c.input)
    skipped 32 lines
    95 96   },
    96 97   },
    97 98   {
    98  - name: "should find a file from a relative path (root above cwd)",
     99 + name: "should find a file from a relative path (root above cwd)",
     100 + // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great
    99 101   relativeRoot: "../",
    100  - input: "sbom/sbom.go",
     102 + input: "fileresolver/directory.go",
    101 103   expected: []string{
    102  - "sbom/sbom.go",
     104 + "fileresolver/directory.go",
    103 105   },
    104 106   },
    105 107   }
    skipped 4 lines
    110 112   absRoot, err := filepath.Abs(c.relativeRoot)
    111 113   require.NoError(t, err)
    112 114   
    113  - resolver, err := newDirectoryResolver(absRoot, "")
     115 + resolver, err := NewFromDirectory(absRoot, "")
    114 116   assert.NoError(t, err)
    115 117   
    116 118   refs, err := resolver.FilesByPath(c.input)
    skipped 54 lines
    171 173   }
    172 174   for _, c := range cases {
    173 175   t.Run(c.name, func(t *testing.T) {
    174  - resolver, err := newDirectoryResolver(c.root, "")
     176 + resolver, err := NewFromDirectory(c.root, "")
    175 177   assert.NoError(t, err)
    176 178   
    177 179   hasPath := resolver.HasPath(c.input)
    skipped 41 lines
    219 221   }
    220 222   for _, c := range cases {
    221 223   t.Run(c.name, func(t *testing.T) {
    222  - resolver, err := newDirectoryResolver("./test-fixtures", "")
     224 + resolver, err := NewFromDirectory("./test-fixtures", "")
    223 225   assert.NoError(t, err)
    224 226   refs, err := resolver.FilesByPath(c.input...)
    225 227   assert.NoError(t, err)
    skipped 6 lines
    232 234  }
    233 235   
    234 236  func TestDirectoryResolver_FilesByGlobMultiple(t *testing.T) {
    235  - resolver, err := newDirectoryResolver("./test-fixtures", "")
     237 + resolver, err := NewFromDirectory("./test-fixtures", "")
    236 238   assert.NoError(t, err)
    237 239   refs, err := resolver.FilesByGlob("**/image-symlinks/file*")
    238 240   assert.NoError(t, err)
    skipped 2 lines
    241 243  }
    242 244   
    243 245  func TestDirectoryResolver_FilesByGlobRecursive(t *testing.T) {
    244  - resolver, err := newDirectoryResolver("./test-fixtures/image-symlinks", "")
     246 + resolver, err := NewFromDirectory("./test-fixtures/image-symlinks", "")
    245 247   assert.NoError(t, err)
    246 248   refs, err := resolver.FilesByGlob("**/*.txt")
    247 249   assert.NoError(t, err)
    skipped 1 lines
    249 251  }
    250 252   
    251 253  func TestDirectoryResolver_FilesByGlobSingle(t *testing.T) {
    252  - resolver, err := newDirectoryResolver("./test-fixtures", "")
     254 + resolver, err := NewFromDirectory("./test-fixtures", "")
    253 255   assert.NoError(t, err)
    254 256   refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt")
    255 257   assert.NoError(t, err)
    skipped 20 lines
    276 278   
    277 279   for _, test := range tests {
    278 280   t.Run(test.name, func(t *testing.T) {
    279  - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "")
     281 + resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "")
    280 282   assert.NoError(t, err)
    281 283   
    282 284   refs, err := resolver.FilesByPath(test.fixture)
    skipped 16 lines
    299 301   
    300 302  func TestDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) {
    301 303   // let's make certain that "dev/place" is not ignored, since it is not "/dev/place"
    302  - resolver, err := newDirectoryResolver("test-fixtures/system_paths/target", "")
     304 + resolver, err := NewFromDirectory("test-fixtures/system_paths/target", "")
    303 305   assert.NoError(t, err)
    304 306   
    305 307   // all paths should be found (non filtering matches a path)
    skipped 77 lines
    383 385   info: testFileInfo{
    384 386   mode: os.ModeSocket,
    385 387   },
    386  - expected: errSkipPath,
     388 + expected: ErrSkipPath,
    387 389   },
    388 390   {
    389 391   name: "named pipe",
    390 392   info: testFileInfo{
    391 393   mode: os.ModeNamedPipe,
    392 394   },
    393  - expected: errSkipPath,
     395 + expected: ErrSkipPath,
    394 396   },
    395 397   {
    396 398   name: "char device",
    397 399   info: testFileInfo{
    398 400   mode: os.ModeCharDevice,
    399 401   },
    400  - expected: errSkipPath,
     402 + expected: ErrSkipPath,
    401 403   },
    402 404   {
    403 405   name: "block device",
    404 406   info: testFileInfo{
    405 407   mode: os.ModeDevice,
    406 408   },
    407  - expected: errSkipPath,
     409 + expected: ErrSkipPath,
    408 410   },
    409 411   {
    410 412   name: "irregular",
    411 413   info: testFileInfo{
    412 414   mode: os.ModeIrregular,
    413 415   },
    414  - expected: errSkipPath,
     416 + expected: ErrSkipPath,
    415 417   },
    416 418   }
    417 419   for _, test := range tests {
    skipped 17 lines
    435 437   }
    436 438   for _, test := range tests {
    437 439   t.Run(test.fixturePath, func(t *testing.T) {
    438  - resolver, err := newDirectoryResolver(test.fixturePath, "")
     440 + resolver, err := NewFromDirectory(test.fixturePath, "")
    439 441   assert.NoError(t, err)
    440 442   locations, err := resolver.FilesByMIMEType(test.mimeType)
    441 443   assert.NoError(t, err)
    skipped 6 lines
    448 450  }
    449 451   
    450 452  func Test_IndexingNestedSymLinks(t *testing.T) {
    451  - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "")
     453 + resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "")
    452 454   require.NoError(t, err)
    453 455   
    454 456   // check that we can get the real path
    skipped 44 lines
    499 501  func Test_IndexingNestedSymLinks_ignoredIndexes(t *testing.T) {
    500 502   filterFn := func(path string, _ os.FileInfo, _ error) error {
    501 503   if strings.HasSuffix(path, string(filepath.Separator)+"readme") {
    502  - return errSkipPath
     504 + return ErrSkipPath
    503 505   }
    504 506   return nil
    505 507   }
    506 508   
    507  - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-simple", "", filterFn)
     509 + resolver, err := NewFromDirectory("./test-fixtures/symlinks-simple", "", filterFn)
    508 510   require.NoError(t, err)
    509 511   
    510 512   // the path to the real file is PRUNED from the index, so we should NOT expect a location returned
    skipped 13 lines
    524 526  }
    525 527   
    526 528  func Test_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) {
    527  - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-multiple-roots/root", "")
     529 + resolver, err := NewFromDirectory("./test-fixtures/symlinks-multiple-roots/root", "")
    528 530   require.NoError(t, err)
    529 531   
    530 532   // check that we can get the real path
    skipped 11 lines
    542 544  }
    543 545   
    544 546  func Test_RootViaSymlink(t *testing.T) {
    545  - resolver, err := newDirectoryResolver("./test-fixtures/symlinked-root/nested/link-root", "")
     547 + resolver, err := NewFromDirectory("./test-fixtures/symlinked-root/nested/link-root", "")
    546 548   require.NoError(t, err)
    547 549   
    548 550   locations, err := resolver.FilesByPath("./file1.txt")
    skipped 13 lines
    562 564   cwd, err := os.Getwd()
    563 565   require.NoError(t, err)
    564 566   
    565  - r, err := newDirectoryResolver(".", "")
     567 + r, err := NewFromDirectory(".", "")
    566 568   require.NoError(t, err)
    567 569   
    568  - exists, existingPath, err := r.tree.File(file.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt")))
     570 + exists, existingPath, err := r.tree.File(stereoscopeFile.Path(filepath.Join(cwd, "test-fixtures/image-simple/file-1.txt")))
    569 571   require.True(t, exists)
    570 572   require.NoError(t, err)
    571 573   require.True(t, existingPath.HasReference())
    572 574   
    573 575   tests := []struct {
    574 576   name string
    575  - location Location
     577 + location file.Location
    576 578   expects string
    577 579   err bool
    578 580   }{
    579 581   {
    580 582   name: "use file reference for content requests",
    581  - location: NewLocationFromDirectory("some/place", *existingPath.Reference),
     583 + location: file.NewLocationFromDirectory("some/place", *existingPath.Reference),
    582 584   expects: "this file has contents",
    583 585   },
    584 586   {
    585 587   name: "error on empty file reference",
    586  - location: NewLocationFromDirectory("doesn't matter", file.Reference{}),
     588 + location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}),
    587 589   err: true,
    588 590   },
    589 591   }
    skipped 8 lines
    598 600   
    599 601   require.NoError(t, err)
    600 602   if test.expects != "" {
    601  - b, err := ioutil.ReadAll(actual)
     603 + b, err := io.ReadAll(actual)
    602 604   require.NoError(t, err)
    603 605   assert.Equal(t, test.expects, string(b))
    604 606   }
    skipped 44 lines
    649 651   
    650 652  func Test_SymlinkLoopWithGlobsShouldResolve(t *testing.T) {
    651 653   test := func(t *testing.T) {
    652  - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-loop", "")
     654 + resolver, err := NewFromDirectory("./test-fixtures/symlinks-loop", "")
    653 655   require.NoError(t, err)
    654 656   
    655 657   locations, err := resolver.FilesByGlob("**/file.target")
    skipped 6 lines
    662 664   testWithTimeout(t, 5*time.Second, test)
    663 665  }
    664 666   
    665  -func testWithTimeout(t *testing.T, timeout time.Duration, test func(*testing.T)) {
    666  - done := make(chan bool)
    667  - go func() {
    668  - test(t)
    669  - done <- true
    670  - }()
    671  - 
    672  - select {
    673  - case <-time.After(timeout):
    674  - t.Fatal("test timed out")
    675  - case <-done:
    676  - }
    677  -}
    678  - 
    679 667  func TestDirectoryResolver_FilesByPath_baseRoot(t *testing.T) {
    680 668   cases := []struct {
    681 669   name string
    skipped 52 lines
    734 722   }
    735 723   for _, c := range cases {
    736 724   t.Run(c.name, func(t *testing.T) {
    737  - resolver, err := newDirectoryResolver(c.root, c.root)
     725 + resolver, err := NewFromDirectory(c.root, c.root)
    738 726   assert.NoError(t, err)
    739 727   
    740 728   refs, err := resolver.FilesByPath(c.input)
    skipped 12 lines
    753 741  func Test_directoryResolver_resolvesLinks(t *testing.T) {
    754 742   tests := []struct {
    755 743   name string
    756  - runner func(FileResolver) []Location
    757  - expected []Location
     744 + runner func(file.Resolver) []file.Location
     745 + expected []file.Location
    758 746   }{
    759 747   {
    760 748   name: "by mimetype",
    761  - runner: func(resolver FileResolver) []Location {
     749 + runner: func(resolver file.Resolver) []file.Location {
    762 750   // links should not show up when searching mimetype
    763 751   actualLocations, err := resolver.FilesByMIMEType("text/plain")
    764 752   assert.NoError(t, err)
    765 753   return actualLocations
    766 754   },
    767  - expected: []Location{
    768  - NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
    769  - NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
    770  - NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
    771  - NewLocation("parent/file-4.txt"), // note: missing virtual path "file-4.txt"
     755 + expected: []file.Location{
     756 + file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
     757 + file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
     758 + file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
     759 + file.NewLocation("parent/file-4.txt"), // note: missing virtual path "file-4.txt"
    772 760   },
    773 761   },
    774 762   {
    775 763   name: "by glob to links",
    776  - runner: func(resolver FileResolver) []Location {
     764 + runner: func(resolver file.Resolver) []file.Location {
    777 765   // links are searched, but resolve to the real files
    778 766   // for that reason we need to place **/ in front (which is not the same for other resolvers)
    779 767   actualLocations, err := resolver.FilesByGlob("**/*ink-*")
    780 768   assert.NoError(t, err)
    781 769   return actualLocations
    782 770   },
    783  - expected: []Location{
    784  - NewVirtualLocation("file-1.txt", "link-1"),
    785  - NewVirtualLocation("file-2.txt", "link-2"),
     771 + expected: []file.Location{
     772 + file.NewVirtualLocation("file-1.txt", "link-1"),
     773 + file.NewVirtualLocation("file-2.txt", "link-2"),
    786 774   // we already have this real file path via another link, so only one is returned
    787  - //NewVirtualLocation("file-2.txt", "link-indirect"),
    788  - NewVirtualLocation("file-3.txt", "link-within"),
     775 + //file.NewVirtualLocation("file-2.txt", "link-indirect"),
     776 + file.NewVirtualLocation("file-3.txt", "link-within"),
    789 777   },
    790 778   },
    791 779   {
    792 780   name: "by basename",
    793  - runner: func(resolver FileResolver) []Location {
     781 + runner: func(resolver file.Resolver) []file.Location {
    794 782   // links are searched, but resolve to the real files
    795 783   actualLocations, err := resolver.FilesByGlob("**/file-2.txt")
    796 784   assert.NoError(t, err)
    797 785   return actualLocations
    798 786   },
    799  - expected: []Location{
     787 + expected: []file.Location{
    800 788   // this has two copies in the base image, which overwrites the same location
    801  - NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt",
     789 + file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt",
    802 790   },
    803 791   },
    804 792   {
    805 793   name: "by basename glob",
    806  - runner: func(resolver FileResolver) []Location {
     794 + runner: func(resolver file.Resolver) []file.Location {
    807 795   // links are searched, but resolve to the real files
    808 796   actualLocations, err := resolver.FilesByGlob("**/file-?.txt")
    809 797   assert.NoError(t, err)
    810 798   return actualLocations
    811 799   },
    812  - expected: []Location{
    813  - NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
    814  - NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
    815  - NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
    816  - NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt"
     800 + expected: []file.Location{
     801 + file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
     802 + file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
     803 + file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
     804 + file.NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt"
    817 805   },
    818 806   },
    819 807   {
    820 808   name: "by basename glob to links",
    821  - runner: func(resolver FileResolver) []Location {
     809 + runner: func(resolver file.Resolver) []file.Location {
    822 810   actualLocations, err := resolver.FilesByGlob("**/link-*")
    823 811   assert.NoError(t, err)
    824 812   return actualLocations
    825 813   },
    826  - expected: []Location{
    827  - {
    828  - LocationData: LocationData{
    829  - Coordinates: Coordinates{
    830  - RealPath: "file-1.txt",
    831  - },
    832  - VirtualPath: "link-1",
    833  - ref: file.Reference{RealPath: "file-1.txt"},
    834  - },
    835  - },
    836  - {
    837  - LocationData: LocationData{
    838  - Coordinates: Coordinates{
    839  - RealPath: "file-2.txt",
    840  - },
    841  - VirtualPath: "link-2",
    842  - ref: file.Reference{RealPath: "file-2.txt"},
    843  - },
    844  - },
     814 + expected: []file.Location{
     815 + file.NewVirtualLocation("file-1.txt", "link-1"),
     816 + file.NewVirtualLocation("file-2.txt", "link-2"),
     817 + 
    845 818   // we already have this real file path via another link, so only one is returned
    846  - //{
    847  - // LocationData: LocationData{
    848  - // Coordinates: Coordinates{
    849  - // RealPath: "file-2.txt",
    850  - // },
    851  - // VirtualPath: "link-indirect",
    852  - // ref: file.Reference{RealPath: "file-2.txt"},
    853  - // },
    854  - //},
    855  - {
    856  - LocationData: LocationData{
    857  - Coordinates: Coordinates{
    858  - RealPath: "file-3.txt",
    859  - },
    860  - VirtualPath: "link-within",
    861  - ref: file.Reference{RealPath: "file-3.txt"},
    862  - },
    863  - },
     819 + //file.NewVirtualLocation("file-2.txt", "link-indirect"),
     820 + 
     821 + file.NewVirtualLocation("file-3.txt", "link-within"),
    864 822   },
    865 823   },
    866 824   {
    867 825   name: "by extension",
    868  - runner: func(resolver FileResolver) []Location {
     826 + runner: func(resolver file.Resolver) []file.Location {
    869 827   // links are searched, but resolve to the real files
    870 828   actualLocations, err := resolver.FilesByGlob("**/*.txt")
    871 829   assert.NoError(t, err)
    872 830   return actualLocations
    873 831   },
    874  - expected: []Location{
    875  - NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
    876  - NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
    877  - NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
    878  - NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt"
     832 + expected: []file.Location{
     833 + file.NewLocation("file-1.txt"), // note: missing virtual path "file-1.txt"
     834 + file.NewLocation("file-2.txt"), // note: missing virtual path "file-2.txt"
     835 + file.NewLocation("file-3.txt"), // note: missing virtual path "file-3.txt"
     836 + file.NewLocation("parent/file-4.txt"), // note: missing virtual path "parent/file-4.txt"
    879 837   },
    880 838   },
    881 839   {
    882 840   name: "by path to degree 1 link",
    883  - runner: func(resolver FileResolver) []Location {
     841 + runner: func(resolver file.Resolver) []file.Location {
    884 842   // links resolve to the final file
    885 843   actualLocations, err := resolver.FilesByPath("/link-2")
    886 844   assert.NoError(t, err)
    887 845   return actualLocations
    888 846   },
    889  - expected: []Location{
     847 + expected: []file.Location{
    890 848   // we have multiple copies across layers
    891  - NewVirtualLocation("file-2.txt", "link-2"),
     849 + file.NewVirtualLocation("file-2.txt", "link-2"),
    892 850   },
    893 851   },
    894 852   {
    895 853   name: "by path to degree 2 link",
    896  - runner: func(resolver FileResolver) []Location {
     854 + runner: func(resolver file.Resolver) []file.Location {
    897 855   // multiple links resolves to the final file
    898 856   actualLocations, err := resolver.FilesByPath("/link-indirect")
    899 857   assert.NoError(t, err)
    900 858   return actualLocations
    901 859   },
    902  - expected: []Location{
     860 + expected: []file.Location{
    903 861   // we have multiple copies across layers
    904  - NewVirtualLocation("file-2.txt", "link-indirect"),
     862 + file.NewVirtualLocation("file-2.txt", "link-indirect"),
    905 863   },
    906 864   },
    907 865   }
    908 866   
    909 867   for _, test := range tests {
    910 868   t.Run(test.name, func(t *testing.T) {
    911  - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-from-image-symlinks-fixture", "")
     869 + resolver, err := NewFromDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture", "")
    912 870   require.NoError(t, err)
    913 871   assert.NoError(t, err)
    914 872   
    skipped 5 lines
    920 878  }
    921 879   
    922 880  func TestDirectoryResolver_DoNotAddVirtualPathsToTree(t *testing.T) {
    923  - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-prune-indexing", "")
     881 + resolver, err := NewFromDirectory("./test-fixtures/symlinks-prune-indexing", "")
    924 882   require.NoError(t, err)
    925 883   
    926  - var allRealPaths []file.Path
     884 + var allRealPaths []stereoscopeFile.Path
    927 885   for l := range resolver.AllLocations() {
    928  - allRealPaths = append(allRealPaths, file.Path(l.RealPath))
     886 + allRealPaths = append(allRealPaths, stereoscopeFile.Path(l.RealPath))
    929 887   }
    930  - pathSet := file.NewPathSet(allRealPaths...)
     888 + pathSet := stereoscopeFile.NewPathSet(allRealPaths...)
    931 889   
    932 890   assert.False(t,
    933 891   pathSet.Contains("before-path/file.txt"),
    skipped 8 lines
    942 900  }
    943 901   
    944 902  func TestDirectoryResolver_FilesContents_errorOnDirRequest(t *testing.T) {
    945  - resolver, err := newDirectoryResolver("./test-fixtures/system_paths", "")
     903 + resolver, err := NewFromDirectory("./test-fixtures/system_paths", "")
    946 904   assert.NoError(t, err)
    947 905   
    948  - var dirLoc *Location
     906 + var dirLoc *file.Location
    949 907   for loc := range resolver.AllLocations() {
    950  - entry, err := resolver.index.Get(loc.ref)
     908 + entry, err := resolver.index.Get(loc.Reference())
    951 909   require.NoError(t, err)
    952 910   if entry.Metadata.IsDir() {
    953 911   dirLoc = &loc
    skipped 9 lines
    963 921  }
    964 922   
    965 923  func TestDirectoryResolver_AllLocations(t *testing.T) {
    966  - resolver, err := newDirectoryResolver("./test-fixtures/symlinks-from-image-symlinks-fixture", "")
     924 + resolver, err := NewFromDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture", "")
    967 925   assert.NoError(t, err)
    968 926   
    969 927   paths := strset.New()
    970 928   for loc := range resolver.AllLocations() {
    971 929   if strings.HasPrefix(loc.RealPath, "/") {
    972  - // ignore outside of the fixture root for now
     930 + // ignore outside the fixture root for now
    973 931   continue
    974 932   }
    975 933   paths.Add(loc.RealPath)
    skipped 21 lines
  • ■ ■ ■ ■
    syft/source/directory_resolver_windows_test.go syft/internal/fileresolver/directory_windows_test.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import "testing"
    4 4   
    skipped 123 lines
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/empty.go
     1 +package fileresolver
     2 + 
     3 +import (
     4 + "io"
     5 + 
     6 + "github.com/anchore/syft/syft/file"
     7 +)
     8 + 
     9 +var _ file.WritableResolver = (*Empty)(nil)
     10 + 
     11 +type Empty struct{}
     12 + 
     13 +func (e Empty) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) {
     14 + return nil, nil
     15 +}
     16 + 
     17 +func (e Empty) HasPath(_ string) bool {
     18 + return false
     19 +}
     20 + 
     21 +func (e Empty) FilesByPath(_ ...string) ([]file.Location, error) {
     22 + return nil, nil
     23 +}
     24 + 
     25 +func (e Empty) FilesByGlob(_ ...string) ([]file.Location, error) {
     26 + return nil, nil
     27 +}
     28 + 
     29 +func (e Empty) FilesByMIMEType(_ ...string) ([]file.Location, error) {
     30 + return nil, nil
     31 +}
     32 + 
     33 +func (e Empty) RelativeFileByPath(_ file.Location, _ string) *file.Location {
     34 + return nil
     35 +}
     36 + 
     37 +func (e Empty) AllLocations() <-chan file.Location {
     38 + return nil
     39 +}
     40 + 
     41 +func (e Empty) FileMetadataByLocation(_ file.Location) (file.Metadata, error) {
     42 + return file.Metadata{}, nil
     43 +}
     44 + 
     45 +func (e Empty) Write(_ file.Location, _ io.Reader) error {
     46 + return nil
     47 +}
     48 + 
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/excluding_file.go
     1 +package fileresolver
     2 + 
     3 +import (
     4 + "fmt"
     5 + "io"
     6 + 
     7 + "github.com/anchore/syft/syft/file"
     8 +)
     9 + 
     10 +type excludeFn func(string) bool
     11 + 
     12 +// excluding decorates a resolver with an exclusion function that is used to
     13 +// filter out entries in the delegate resolver
     14 +type excluding struct {
     15 + delegate file.Resolver
     16 + excludeFn excludeFn
     17 +}
     18 + 
     19 +// NewExcluding create a new resolver which wraps the provided delegate and excludes
     20 +// entries based on a provided path exclusion function
     21 +func NewExcluding(delegate file.Resolver, excludeFn excludeFn) file.Resolver {
     22 + return &excluding{
     23 + delegate,
     24 + excludeFn,
     25 + }
     26 +}
     27 + 
     28 +func (r *excluding) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
     29 + if locationMatches(&location, r.excludeFn) {
     30 + return nil, fmt.Errorf("no such location: %+v", location.RealPath)
     31 + }
     32 + return r.delegate.FileContentsByLocation(location)
     33 +}
     34 + 
     35 +func (r *excluding) FileMetadataByLocation(location file.Location) (file.Metadata, error) {
     36 + if locationMatches(&location, r.excludeFn) {
     37 + return file.Metadata{}, fmt.Errorf("no such location: %+v", location.RealPath)
     38 + }
     39 + return r.delegate.FileMetadataByLocation(location)
     40 +}
     41 + 
     42 +func (r *excluding) HasPath(path string) bool {
     43 + if r.excludeFn(path) {
     44 + return false
     45 + }
     46 + return r.delegate.HasPath(path)
     47 +}
     48 + 
     49 +func (r *excluding) FilesByPath(paths ...string) ([]file.Location, error) {
     50 + locations, err := r.delegate.FilesByPath(paths...)
     51 + return filterLocations(locations, err, r.excludeFn)
     52 +}
     53 + 
     54 +func (r *excluding) FilesByGlob(patterns ...string) ([]file.Location, error) {
     55 + locations, err := r.delegate.FilesByGlob(patterns...)
     56 + return filterLocations(locations, err, r.excludeFn)
     57 +}
     58 + 
     59 +func (r *excluding) FilesByMIMEType(types ...string) ([]file.Location, error) {
     60 + locations, err := r.delegate.FilesByMIMEType(types...)
     61 + return filterLocations(locations, err, r.excludeFn)
     62 +}
     63 + 
     64 +func (r *excluding) RelativeFileByPath(location file.Location, path string) *file.Location {
     65 + l := r.delegate.RelativeFileByPath(location, path)
     66 + if l != nil && locationMatches(l, r.excludeFn) {
     67 + return nil
     68 + }
     69 + return l
     70 +}
     71 + 
     72 +func (r *excluding) AllLocations() <-chan file.Location {
     73 + c := make(chan file.Location)
     74 + go func() {
     75 + defer close(c)
     76 + for location := range r.delegate.AllLocations() {
     77 + if !locationMatches(&location, r.excludeFn) {
     78 + c <- location
     79 + }
     80 + }
     81 + }()
     82 + return c
     83 +}
     84 + 
     85 +func locationMatches(location *file.Location, exclusionFn excludeFn) bool {
     86 + return exclusionFn(location.RealPath) || exclusionFn(location.VirtualPath)
     87 +}
     88 + 
     89 +func filterLocations(locations []file.Location, err error, exclusionFn excludeFn) ([]file.Location, error) {
     90 + if err != nil {
     91 + return nil, err
     92 + }
     93 + if exclusionFn != nil {
     94 + for i := 0; i < len(locations); i++ {
     95 + location := &locations[i]
     96 + if locationMatches(location, exclusionFn) {
     97 + locations = append(locations[:i], locations[i+1:]...)
     98 + i--
     99 + }
     100 + }
     101 + }
     102 + return locations, nil
     103 +}
     104 + 
  • ■ ■ ■ ■ ■ ■
    syft/source/excluding_file_resolver_test.go syft/internal/fileresolver/excluding_file_test.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "io"
    skipped 1 lines
    6 6   "testing"
    7 7   
    8 8   "github.com/stretchr/testify/assert"
     9 + 
     10 + "github.com/anchore/syft/syft/file"
    9 11  )
    10 12   
    11 13  func TestExcludingResolver(t *testing.T) {
    skipped 42 lines
    54 56   resolver := &mockResolver{
    55 57   locations: test.locations,
    56 58   }
    57  - er := NewExcludingResolver(resolver, test.excludeFn)
     59 + er := NewExcluding(resolver, test.excludeFn)
    58 60   
    59 61   locations, _ := er.FilesByPath()
    60 62   assert.ElementsMatch(t, locationPaths(locations), test.expected)
    skipped 4 lines
    65 67   locations, _ = er.FilesByMIMEType()
    66 68   assert.ElementsMatch(t, locationPaths(locations), test.expected)
    67 69   
    68  - locations = []Location{}
     70 + locations = []file.Location{}
    69 71   
    70 72   channel := er.AllLocations()
    71 73   for location := range channel {
    skipped 5 lines
    77 79   
    78 80   for _, path := range diff {
    79 81   assert.False(t, er.HasPath(path))
    80  - c, err := er.FileContentsByLocation(NewLocation(path))
     82 + c, err := er.FileContentsByLocation(file.NewLocation(path))
    81 83   assert.Nil(t, c)
    82 84   assert.Error(t, err)
    83  - m, err := er.FileMetadataByLocation(NewLocation(path))
     85 + m, err := er.FileMetadataByLocation(file.NewLocation(path))
    84 86   assert.Empty(t, m.LinkDestination)
    85 87   assert.Error(t, err)
    86  - l := er.RelativeFileByPath(NewLocation(""), path)
     88 + l := er.RelativeFileByPath(file.NewLocation(""), path)
    87 89   assert.Nil(t, l)
    88 90   }
    89 91   
    90 92   for _, path := range test.expected {
    91 93   assert.True(t, er.HasPath(path))
    92  - c, err := er.FileContentsByLocation(NewLocation(path))
     94 + c, err := er.FileContentsByLocation(file.NewLocation(path))
    93 95   assert.NotNil(t, c)
    94 96   assert.Nil(t, err)
    95  - m, err := er.FileMetadataByLocation(NewLocation(path))
     97 + m, err := er.FileMetadataByLocation(file.NewLocation(path))
    96 98   assert.NotEmpty(t, m.LinkDestination)
    97 99   assert.Nil(t, err)
    98  - l := er.RelativeFileByPath(NewLocation(""), path)
     100 + l := er.RelativeFileByPath(file.NewLocation(""), path)
    99 101   assert.NotNil(t, l)
    100 102   }
    101 103   })
    skipped 15 lines
    117 119   return diff
    118 120  }
    119 121   
    120  -func locationPaths(locations []Location) []string {
     122 +func locationPaths(locations []file.Location) []string {
    121 123   paths := []string{}
    122 124   for _, l := range locations {
    123 125   paths = append(paths, l.RealPath)
    skipped 5 lines
    129 131   locations []string
    130 132  }
    131 133   
    132  -func (r *mockResolver) getLocations() ([]Location, error) {
    133  - out := []Location{}
     134 +func (r *mockResolver) getLocations() ([]file.Location, error) {
     135 + out := []file.Location{}
    134 136   for _, path := range r.locations {
    135  - out = append(out, NewLocation(path))
     137 + out = append(out, file.NewLocation(path))
    136 138   }
    137 139   return out, nil
    138 140  }
    139 141   
    140  -func (r *mockResolver) FileContentsByLocation(_ Location) (io.ReadCloser, error) {
     142 +func (r *mockResolver) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) {
    141 143   return io.NopCloser(strings.NewReader("Hello, world!")), nil
    142 144  }
    143 145   
    144  -func (r *mockResolver) FileMetadataByLocation(_ Location) (FileMetadata, error) {
    145  - return FileMetadata{
     146 +func (r *mockResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, error) {
     147 + return file.Metadata{
    146 148   LinkDestination: "MOCK",
    147 149   }, nil
    148 150  }
    skipped 2 lines
    151 153   return true
    152 154  }
    153 155   
    154  -func (r *mockResolver) FilesByPath(_ ...string) ([]Location, error) {
     156 +func (r *mockResolver) FilesByPath(_ ...string) ([]file.Location, error) {
    155 157   return r.getLocations()
    156 158  }
    157 159   
    158  -func (r *mockResolver) FilesByGlob(_ ...string) ([]Location, error) {
     160 +func (r *mockResolver) FilesByGlob(_ ...string) ([]file.Location, error) {
    159 161   return r.getLocations()
    160 162  }
    161 163   
    162  -func (r *mockResolver) FilesByMIMEType(_ ...string) ([]Location, error) {
     164 +func (r *mockResolver) FilesByMIMEType(_ ...string) ([]file.Location, error) {
    163 165   return r.getLocations()
    164 166  }
    165 167   
    166  -func (r *mockResolver) FilesByExtension(_ ...string) ([]Location, error) {
     168 +func (r *mockResolver) FilesByExtension(_ ...string) ([]file.Location, error) {
    167 169   return r.getLocations()
    168 170  }
    169 171   
    170  -func (r *mockResolver) FilesByBasename(_ ...string) ([]Location, error) {
     172 +func (r *mockResolver) FilesByBasename(_ ...string) ([]file.Location, error) {
    171 173   return r.getLocations()
    172 174  }
    173 175   
    174  -func (r *mockResolver) FilesByBasenameGlob(_ ...string) ([]Location, error) {
     176 +func (r *mockResolver) FilesByBasenameGlob(_ ...string) ([]file.Location, error) {
    175 177   return r.getLocations()
    176 178  }
    177 179   
    178  -func (r *mockResolver) RelativeFileByPath(_ Location, path string) *Location {
    179  - l := NewLocation(path)
     180 +func (r *mockResolver) RelativeFileByPath(_ file.Location, path string) *file.Location {
     181 + l := file.NewLocation(path)
    180 182   return &l
    181 183  }
    182 184   
    183  -func (r *mockResolver) AllLocations() <-chan Location {
    184  - c := make(chan Location)
     185 +func (r *mockResolver) AllLocations() <-chan file.Location {
     186 + c := make(chan file.Location)
    185 187   go func() {
    186 188   defer close(c)
    187 189   locations, _ := r.getLocations()
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/file_metadata_by_location.go
     1 +package fileresolver
     2 + 
     3 +import (
     4 + "github.com/anchore/stereoscope/pkg/image"
     5 + "github.com/anchore/syft/syft/file"
     6 +)
     7 + 
     8 +func fileMetadataByLocation(img *image.Image, location file.Location) (file.Metadata, error) {
     9 + entry, err := img.FileCatalog.Get(location.Reference())
     10 + if err != nil {
     11 + return file.Metadata{}, err
     12 + }
     13 + 
     14 + return entry.Metadata, nil
     15 +}
     16 + 
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/generate-tar-fixture-from-source-dir.sh
     1 +#!/usr/bin/env bash
     2 +set -eux
     3 + 
     4 +# $1 —— absolute path to destination file, should end with .tar
     5 +# $2 —— absolute path to directory from which to add entries to the archive
     6 + 
     7 +pushd "$2"
     8 + tar -cvf "$1" .
     9 +popd
     10 + 
  • syft/source/test-fixtures/image-duplicate-path/Dockerfile syft/internal/fileresolver/test-fixtures/image-duplicate-path/Dockerfile
    Content is identical
  • syft/source/test-fixtures/image-duplicate-path/file-1.txt syft/internal/fileresolver/test-fixtures/image-duplicate-path/file-1.txt
    Content is identical
  • syft/source/test-fixtures/image-duplicate-path/file-2.txt syft/internal/fileresolver/test-fixtures/image-duplicate-path/file-2.txt
    Content is identical
  • syft/source/test-fixtures/image-files-deleted/Dockerfile syft/internal/fileresolver/test-fixtures/image-files-deleted/Dockerfile
    Content is identical
  • syft/source/test-fixtures/image-files-deleted/file-1.txt syft/internal/fileresolver/test-fixtures/image-files-deleted/file-1.txt
    Content is identical
  • syft/source/test-fixtures/image-files-deleted/file-3.txt syft/internal/fileresolver/test-fixtures/image-files-deleted/file-3.txt
    Content is identical
  • syft/source/test-fixtures/image-files-deleted/target/file-2.txt syft/internal/fileresolver/test-fixtures/image-files-deleted/target/file-2.txt
    Content is identical
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/image-simple/Dockerfile
     1 +# Note: changes to this file will result in updating several test values. Consider making a new image fixture instead of editing this one.
     2 +FROM scratch
     3 +ADD file-1.txt /somefile-1.txt
     4 +ADD file-2.txt /somefile-2.txt
     5 +# note: adding a directory will behave differently on docker engine v18 vs v19
     6 +ADD target /
     7 + 
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/image-simple/file-1.txt
     1 +this file has contents
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/image-simple/file-2.txt
     1 +file-2 contents!
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/image-simple/target/really/nested/file-3.txt
     1 +another file!
     2 +with lines...
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/path-detected/.vimrc
     1 +" A .vimrc file
     2 + 
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/path-detected/empty
     1 + 
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/path-detected-2/.vimrc
     1 +Another .vimrc file
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/path-detected-2/empty
     1 + 
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/file1.txt
     1 +contents!
     2 + 
  • ■ ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/nested/file2.txt
     1 +more contents!
     2 + 
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/symlinked-root/real-root/nested/linked-file1.txt
     1 +../file1.txt
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/system_paths/target/dev/place
     1 +bad
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/system_paths/target/home/place
     1 +good
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/system_paths/target/proc/place
     1 +bad
  • ■ ■ ■ ■ ■
    syft/internal/fileresolver/test-fixtures/system_paths/target/sys/place
     1 +bad
  • ■ ■ ■ ■ ■ ■
    syft/source/unindexed_directory_resolver.go syft/internal/fileresolver/unindexed_directory.go
    1  -package source
     1 +package fileresolver
    2 2   
    3 3  import (
    4 4   "fmt"
    skipped 11 lines
    16 16   "golang.org/x/exp/slices"
    17 17   
    18 18   "github.com/anchore/syft/internal/log"
     19 + "github.com/anchore/syft/syft/file"
    19 20  )
    20 21   
    21  -type UnindexedDirectoryResolver struct {
     22 +var _ file.Resolver = (*UnindexedDirectory)(nil)
     23 +var _ file.WritableResolver = (*UnindexedDirectory)(nil)
     24 + 
     25 +type UnindexedDirectory struct {
    22 26   ls afero.Lstater
    23 27   lr afero.LinkReader
    24 28   base string
    skipped 1 lines
    26 30   fs afero.Fs
    27 31  }
    28 32   
    29  -func NewUnindexedDirectoryResolver(dir string) WritableFileResolver {
    30  - return NewUnindexedDirectoryResolverFS(afero.NewOsFs(), dir, "")
     33 +func NewFromUnindexedDirectory(dir string) file.WritableResolver {
     34 + return NewFromUnindexedDirectoryFS(afero.NewOsFs(), dir, "")
    31 35  }
    32 36   
    33  -func NewUnindexedDirectoryResolverRooted(dir string, base string) WritableFileResolver {
    34  - return NewUnindexedDirectoryResolverFS(afero.NewOsFs(), dir, base)
     37 +func NewFromRootedUnindexedDirectory(dir string, base string) file.WritableResolver {
     38 + return NewFromUnindexedDirectoryFS(afero.NewOsFs(), dir, base)
    35 39  }
    36 40   
    37  -func NewUnindexedDirectoryResolverFS(fs afero.Fs, dir string, base string) WritableFileResolver {
     41 +func NewFromUnindexedDirectoryFS(fs afero.Fs, dir string, base string) file.WritableResolver {
    38 42   ls, ok := fs.(afero.Lstater)
    39 43   if !ok {
    40 44   panic(fmt.Sprintf("unable to get afero.Lstater interface from: %+v", fs))
    skipped 21 lines
    62 66   base = path.Clean(path.Join(wd, base))
    63 67   }
    64 68   }
    65  - return UnindexedDirectoryResolver{
     69 + return UnindexedDirectory{
    66 70   base: base,
    67 71   dir: dir,
    68 72   fs: fs,
    skipped 2 lines
    71 75   }
    72 76  }
    73 77   
    74  -func (u UnindexedDirectoryResolver) FileContentsByLocation(location Location) (io.ReadCloser, error) {
     78 +func (u UnindexedDirectory) FileContentsByLocation(location file.Location) (io.ReadCloser, error) {
    75 79   p := u.absPath(u.scrubInputPath(location.RealPath))
    76 80   f, err := u.fs.Open(p)
    77 81   if err != nil {
    skipped 11 lines
    89 93   
    90 94  // - full symlink resolution should be performed on all requests
    91 95  // - returns locations for any file or directory
    92  -func (u UnindexedDirectoryResolver) HasPath(p string) bool {
     96 +func (u UnindexedDirectory) HasPath(p string) bool {
    93 97   locs, err := u.filesByPath(true, true, p)
    94 98   return err == nil && len(locs) > 0
    95 99  }
    96 100   
    97  -func (u UnindexedDirectoryResolver) canLstat(p string) bool {
     101 +func (u UnindexedDirectory) canLstat(p string) bool {
    98 102   _, _, err := u.ls.LstatIfPossible(u.absPath(p))
    99 103   return err == nil
    100 104  }
    101 105   
    102  -func (u UnindexedDirectoryResolver) isRegularFile(p string) bool {
     106 +func (u UnindexedDirectory) isRegularFile(p string) bool {
    103 107   fi, _, err := u.ls.LstatIfPossible(u.absPath(p))
    104 108   return err == nil && !fi.IsDir()
    105 109  }
    106 110   
    107  -func (u UnindexedDirectoryResolver) scrubInputPath(p string) string {
     111 +func (u UnindexedDirectory) scrubInputPath(p string) string {
    108 112   if path.IsAbs(p) {
    109 113   p = p[1:]
    110 114   }
    111 115   return path.Clean(p)
    112 116  }
    113 117   
    114  -func (u UnindexedDirectoryResolver) scrubResolutionPath(p string) string {
     118 +func (u UnindexedDirectory) scrubResolutionPath(p string) string {
    115 119   if u.base != "" {
    116 120   if path.IsAbs(p) {
    117 121   p = p[1:]
    skipped 5 lines
    123 127   return path.Clean(p)
    124 128  }
    125 129   
    126  -func (u UnindexedDirectoryResolver) absPath(p string) string {
     130 +func (u UnindexedDirectory) absPath(p string) string {
    127 131   if u.base != "" {
    128 132   if path.IsAbs(p) {
    129 133   p = p[1:]
    skipped 12 lines
    142 146   
    143 147  // - full symlink resolution should be performed on all requests
    144 148  // - only returns locations to files (NOT directories)
    145  -func (u UnindexedDirectoryResolver) FilesByPath(paths ...string) (out []Location, _ error) {
     149 +func (u UnindexedDirectory) FilesByPath(paths ...string) (out []file.Location, _ error) {
    146 150   return u.filesByPath(true, false, paths...)
    147 151  }
    148 152   
    149  -func (u UnindexedDirectoryResolver) filesByPath(resolveLinks bool, includeDirs bool, paths ...string) (out []Location, _ error) {
     153 +func (u UnindexedDirectory) filesByPath(resolveLinks bool, includeDirs bool, paths ...string) (out []file.Location, _ error) {
    150 154   // sort here for stable output
    151 155   sort.Strings(paths)
    152 156  nextPath:
    skipped 23 lines
    176 180  // - full symlink resolution should be performed on all requests
    177 181  // - if multiple paths to the same file are found, the best single match should be returned
    178 182  // - only returns locations to files (NOT directories)
    179  -func (u UnindexedDirectoryResolver) FilesByGlob(patterns ...string) (out []Location, _ error) {
     183 +func (u UnindexedDirectory) FilesByGlob(patterns ...string) (out []file.Location, _ error) {
    180 184   return u.filesByGlob(true, false, patterns...)
    181 185  }
    182 186   
    183  -func (u UnindexedDirectoryResolver) filesByGlob(resolveLinks bool, includeDirs bool, patterns ...string) (out []Location, _ error) {
     187 +func (u UnindexedDirectory) filesByGlob(resolveLinks bool, includeDirs bool, patterns ...string) (out []file.Location, _ error) {
    184 188   f := unindexedDirectoryResolverFS{
    185 189   u: u,
    186 190   }
    skipped 12 lines
    199 203   return u.filesByPath(resolveLinks, includeDirs, paths...)
    200 204  }
    201 205   
    202  -func (u UnindexedDirectoryResolver) FilesByMIMEType(_ ...string) ([]Location, error) {
     206 +func (u UnindexedDirectory) FilesByMIMEType(_ ...string) ([]file.Location, error) {
    203 207   panic("FilesByMIMEType unsupported")
    204 208  }
    205 209   
    206 210  // RelativeFileByPath fetches a single file at the given path relative to the layer squash of the given reference.
    207 211  // This is helpful when attempting to find a file that is in the same layer or lower as another file.
    208  -func (u UnindexedDirectoryResolver) RelativeFileByPath(l Location, p string) *Location {
     212 +func (u UnindexedDirectory) RelativeFileByPath(l file.Location, p string) *file.Location {
    209 213   p = path.Clean(path.Join(l.RealPath, p))
    210 214   locs, err := u.filesByPath(true, false, p)
    211 215   if err != nil || len(locs) == 0 {
    skipped 9 lines
    221 225   
    222 226  // - NO symlink resolution should be performed on results
    223 227  // - returns locations for any file or directory
    224  -func (u UnindexedDirectoryResolver) AllLocations() <-chan Location {
    225  - out := make(chan Location)
     228 +func (u UnindexedDirectory) AllLocations() <-chan file.Location {
     229 + out := make(chan file.Location)
    226 230   go func() {
    227 231   defer close(out)
    228 232   err := afero.Walk(u.fs, u.absPath("."), func(p string, info fs.FileInfo, err error) error {
    skipped 2 lines
    231 235   return nil
    232 236   }
    233 237   p = strings.TrimPrefix(p, "/")
    234  - out <- NewLocation(p)
     238 + out <- file.NewLocation(p)
    235 239   return nil
    236 240   })
    237 241   if err != nil {
    skipped 3 lines
    241 245   return out
    242 246  }
    243 247   
    244  -func (u UnindexedDirectoryResolver) FileMetadataByLocation(_ Location) (FileMetadata, error) {
     248 +func (u UnindexedDirectory) FileMetadataByLocation(_ file.Location) (file.Metadata, error) {
    245 249   panic("FileMetadataByLocation unsupported")
    246 250  }
    247 251   
    248  -func (u UnindexedDirectoryResolver) Write(location Location, reader io.Reader) error {
     252 +func (u UnindexedDirectory) Write(location file.Location, reader io.Reader) error {
    249 253   filePath := location.RealPath
    250 254   if path.IsAbs(filePath) {
    251 255   filePath = filePath[1:]
    skipped 2 lines
    254 258   return afero.WriteReader(u.fs, absPath, reader)
    255 259  }
    256 260   
    257  -var _ FileResolver = (*UnindexedDirectoryResolver)(nil)
    258  -var _ WritableFileResolver = (*UnindexedDirectoryResolver)(nil)
    259  - 
    260  -func (u UnindexedDirectoryResolver) newLocation(filePath string, resolveLinks bool) *Location {
     261 +func (u UnindexedDirectory) newLocation(filePath string, resolveLinks bool) *file.Location {
    261 262   filePath = path.Clean(filePath)
    262 263   
    263 264   virtualPath := ""
    skipped 13 lines
    277 278   }
    278 279   }
    279 280   
    280  - l := NewVirtualLocation(realPath, virtualPath)
     281 + l := file.NewVirtualLocation(realPath, virtualPath)
    281 282   return &l
    282 283  }
    283 284   
    284 285  //nolint:gocognit
    285  -func (u UnindexedDirectoryResolver) resolveLinks(filePath string) []string {
     286 +func (u UnindexedDirectory) resolveLinks(filePath string) []string {
    286 287   var visited []string
    287 288   
    288 289   out := []string{}
    skipped 60 lines
    349 350   return out
    350 351  }
    351 352   
    352  -func (u UnindexedDirectoryResolver) isSymlink(fi os.FileInfo) bool {
     353 +func (u UnindexedDirectory) isSymlink(fi os.FileInfo) bool {
    353 354   return fi.Mode().Type()&fs.ModeSymlink == fs.ModeSymlink
    354 355  }
    355 356   
    356 357  // ------------------------- fs.FS ------------------------------
    357 358   
    358  -// unindexedDirectoryResolverFS wraps the UnindexedDirectoryResolver as a fs.FS, fs.ReadDirFS, and fs.StatFS
     359 +// unindexedDirectoryResolverFS wraps the UnindexedDirectory as a fs.FS, fs.ReadDirFS, and fs.StatFS
    359 360  type unindexedDirectoryResolverFS struct {
    360  - u UnindexedDirectoryResolver
     361 + u UnindexedDirectory
    361 362  }
    362 363   
    363 364  // resolve takes a virtual path and returns the resolved absolute or relative path and file info
    skipped 106 lines
    470 471  var _ fs.DirEntry = (*unindexedDirectoryResolverDirEntry)(nil)
    471 472   
    472 473  type unindexedDirectoryResolverFile struct {
    473  - u UnindexedDirectoryResolver
     474 + u UnindexedDirectory
    474 475   path string
    475 476  }
    476 477   
    skipped 16 lines
    493 494  var _ fs.File = (*unindexedDirectoryResolverFile)(nil)
    494 495   
    495 496  type unindexedDirectoryResolverFileInfo struct {
    496  - u UnindexedDirectoryResolver
     497 + u UnindexedDirectory
    497 498   name string
    498 499   size int64
    499 500   mode fs.FileMode
    skipped 2 lines
    502 503   sys any
    503 504  }
    504 505   
    505  -func newFsFileInfo(u UnindexedDirectoryResolver, name string, isDir bool, fi os.FileInfo) unindexedDirectoryResolverFileInfo {
     506 +func newFsFileInfo(u UnindexedDirectory, name string, isDir bool, fi os.FileInfo) unindexedDirectoryResolverFileInfo {
    506 507   return unindexedDirectoryResolverFileInfo{
    507 508   u: u,
    508 509   name: name,
    skipped 34 lines
  • ■ ■ ■ ■ ■ ■
    syft/source/unindexed_directory_resolver_test.go syft/internal/fileresolver/unindexed_directory_test.go
    1 1  //go:build !windows
    2 2  // +build !windows
    3 3   
    4  -package source
     4 +package fileresolver
    5 5   
    6 6  import (
    7 7   "io"
    skipped 6 lines
    14 14   "time"
    15 15   
    16 16   "github.com/google/go-cmp/cmp"
     17 + "github.com/google/go-cmp/cmp/cmpopts"
    17 18   "github.com/scylladb/go-set/strset"
    18 19   "github.com/stretchr/testify/assert"
    19 20   "github.com/stretchr/testify/require"
    20 21   
    21  - "github.com/anchore/stereoscope/pkg/file"
     22 + stereoscopeFile "github.com/anchore/stereoscope/pkg/file"
     23 + "github.com/anchore/syft/syft/file"
    22 24  )
    23 25   
    24 26  func Test_UnindexedDirectoryResolver_Basic(t *testing.T) {
    25 27   wd, err := os.Getwd()
    26 28   require.NoError(t, err)
    27 29   
    28  - r := NewUnindexedDirectoryResolver(path.Join(wd, "test-fixtures"))
     30 + r := NewFromUnindexedDirectory(path.Join(wd, "test-fixtures"))
    29 31   locations, err := r.FilesByGlob("image-symlinks/*")
    30 32   require.NoError(t, err)
    31 33   require.Len(t, locations, 5)
    skipped 23 lines
    55 57   },
    56 58   },
    57 59   {
    58  - name: "should find a file from a relative path (root above cwd)",
     60 + name: "should find a file from a relative path (root above cwd)",
     61 + // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great
    59 62   relativeRoot: "../",
    60  - input: "sbom/sbom.go",
     63 + input: "fileresolver/deferred.go",
    61 64   expected: []string{
    62  - "sbom/sbom.go",
     65 + "fileresolver/deferred.go",
    63 66   },
    64 67   },
    65 68   }
    66 69   for _, c := range cases {
    67 70   t.Run(c.name, func(t *testing.T) {
    68  - resolver := NewUnindexedDirectoryResolver(c.relativeRoot)
     71 + resolver := NewFromUnindexedDirectory(c.relativeRoot)
    69 72   
    70 73   refs, err := resolver.FilesByPath(c.input)
    71 74   require.NoError(t, err)
    skipped 31 lines
    103 106   },
    104 107   },
    105 108   {
    106  - name: "should find a file from a relative path (root above cwd)",
     109 + name: "should find a file from a relative path (root above cwd)",
     110 + // TODO: refactor me! this test depends on the structure of the source dir not changing, which isn't great
    107 111   relativeRoot: "../",
    108  - input: "sbom/sbom.go",
     112 + input: "fileresolver/directory.go",
    109 113   expected: []string{
    110  - "sbom/sbom.go",
     114 + "fileresolver/directory.go",
    111 115   },
    112 116   },
    113 117   }
    skipped 4 lines
    118 122   absRoot, err := filepath.Abs(c.relativeRoot)
    119 123   require.NoError(t, err)
    120 124   
    121  - resolver := NewUnindexedDirectoryResolver(absRoot)
     125 + resolver := NewFromUnindexedDirectory(absRoot)
    122 126   assert.NoError(t, err)
    123 127   
    124 128   refs, err := resolver.FilesByPath(c.input)
    skipped 54 lines
    179 183   }
    180 184   for _, c := range cases {
    181 185   t.Run(c.name, func(t *testing.T) {
    182  - resolver := NewUnindexedDirectoryResolver(c.root)
     186 + resolver := NewFromUnindexedDirectory(c.root)
    183 187   
    184 188   hasPath := resolver.HasPath(c.input)
    185 189   if !c.forcePositiveHasPath {
    skipped 40 lines
    226 230   }
    227 231   for _, c := range cases {
    228 232   t.Run(c.name, func(t *testing.T) {
    229  - resolver := NewUnindexedDirectoryResolver("./test-fixtures")
     233 + resolver := NewFromUnindexedDirectory("./test-fixtures")
    230 234   refs, err := resolver.FilesByPath(c.input...)
    231 235   assert.NoError(t, err)
    232 236   
    skipped 5 lines
    238 242  }
    239 243   
    240 244  func Test_UnindexedDirectoryResolver_FilesByGlobMultiple(t *testing.T) {
    241  - resolver := NewUnindexedDirectoryResolver("./test-fixtures")
     245 + resolver := NewFromUnindexedDirectory("./test-fixtures")
    242 246   refs, err := resolver.FilesByGlob("**/image-symlinks/file*")
    243 247   assert.NoError(t, err)
    244 248   
    skipped 1 lines
    246 250  }
    247 251   
    248 252  func Test_UnindexedDirectoryResolver_FilesByGlobRecursive(t *testing.T) {
    249  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/image-symlinks")
     253 + resolver := NewFromUnindexedDirectory("./test-fixtures/image-symlinks")
    250 254   refs, err := resolver.FilesByGlob("**/*.txt")
    251 255   assert.NoError(t, err)
    252 256   assert.Len(t, refs, 6)
    253 257  }
    254 258   
    255 259  func Test_UnindexedDirectoryResolver_FilesByGlobSingle(t *testing.T) {
    256  - resolver := NewUnindexedDirectoryResolver("./test-fixtures")
     260 + resolver := NewFromUnindexedDirectory("./test-fixtures")
    257 261   refs, err := resolver.FilesByGlob("**/image-symlinks/*1.txt")
    258 262   assert.NoError(t, err)
    259 263   
    skipped 19 lines
    279 283   
    280 284   for _, test := range tests {
    281 285   t.Run(test.name, func(t *testing.T) {
    282  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-simple")
     286 + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-simple")
    283 287   
    284 288   refs, err := resolver.FilesByPath(test.fixture)
    285 289   require.NoError(t, err)
    skipped 15 lines
    301 305   
    302 306  func Test_UnindexedDirectoryResolverDoesNotIgnoreRelativeSystemPaths(t *testing.T) {
    303 307   // let's make certain that "dev/place" is not ignored, since it is not "/dev/place"
    304  - resolver := NewUnindexedDirectoryResolver("test-fixtures/system_paths/target")
     308 + resolver := NewFromUnindexedDirectory("test-fixtures/system_paths/target")
    305 309   
    306 310   // all paths should be found (non filtering matches a path)
    307 311   locations, err := resolver.FilesByGlob("**/place")
    skipped 18 lines
    326 330  }
    327 331   
    328 332  func Test_UnindexedDirectoryResover_IndexingNestedSymLinks(t *testing.T) {
    329  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-simple")
     333 + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-simple")
    330 334   
    331 335   // check that we can get the real path
    332 336   locations, err := resolver.FilesByPath("./readme")
    skipped 41 lines
    374 378  }
    375 379   
    376 380  func Test_UnindexedDirectoryResover_IndexingNestedSymLinksOutsideOfRoot(t *testing.T) {
    377  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-multiple-roots/root")
     381 + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-multiple-roots/root")
    378 382   
    379 383   // check that we can get the real path
    380 384   locations, err := resolver.FilesByPath("./readme")
    skipped 10 lines
    391 395  }
    392 396   
    393 397  func Test_UnindexedDirectoryResover_RootViaSymlink(t *testing.T) {
    394  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinked-root/nested/link-root")
     398 + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinked-root/nested/link-root")
    395 399   
    396 400   locations, err := resolver.FilesByPath("./file1.txt")
    397 401   require.NoError(t, err)
    skipped 12 lines
    410 414   cwd, err := os.Getwd()
    411 415   require.NoError(t, err)
    412 416   
    413  - r := NewUnindexedDirectoryResolver(path.Join(cwd, "test-fixtures/image-simple"))
     417 + r := NewFromUnindexedDirectory(path.Join(cwd, "test-fixtures/image-simple"))
    414 418   require.NoError(t, err)
    415 419   
    416 420   tests := []struct {
    417 421   name string
    418  - location Location
     422 + location file.Location
    419 423   expects string
    420 424   err bool
    421 425   }{
    422 426   {
    423 427   name: "use file reference for content requests",
    424  - location: NewLocation("file-1.txt"),
     428 + location: file.NewLocation("file-1.txt"),
    425 429   expects: "this file has contents",
    426 430   },
    427 431   {
    428 432   name: "error on empty file reference",
    429  - location: NewLocationFromDirectory("doesn't matter", file.Reference{}),
     433 + location: file.NewLocationFromDirectory("doesn't matter", stereoscopeFile.Reference{}),
    430 434   err: true,
    431 435   },
    432 436   }
    skipped 18 lines
    451 455   
    452 456  func Test_UnindexedDirectoryResover_SymlinkLoopWithGlobsShouldResolve(t *testing.T) {
    453 457   test := func(t *testing.T) {
    454  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-loop")
     458 + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-loop")
    455 459   
    456 460   locations, err := resolver.FilesByGlob("**/file.target")
    457 461   require.NoError(t, err)
    skipped 63 lines
    521 525   }
    522 526   for _, c := range cases {
    523 527   t.Run(c.name, func(t *testing.T) {
    524  - resolver := NewUnindexedDirectoryResolverRooted(c.root, c.root)
     528 + resolver := NewFromRootedUnindexedDirectory(c.root, c.root)
    525 529   
    526 530   refs, err := resolver.FilesByPath(c.input)
    527 531   require.NoError(t, err)
    skipped 11 lines
    539 543  func Test_UnindexedDirectoryResolver_resolvesLinks(t *testing.T) {
    540 544   tests := []struct {
    541 545   name string
    542  - runner func(FileResolver) []Location
    543  - expected []Location
     546 + runner func(file.Resolver) []file.Location
     547 + expected []file.Location
    544 548   }{
    545 549   {
    546 550   name: "by glob to links",
    547  - runner: func(resolver FileResolver) []Location {
     551 + runner: func(resolver file.Resolver) []file.Location {
    548 552   // links are searched, but resolve to the real files
    549 553   // for that reason we need to place **/ in front (which is not the same for other resolvers)
    550 554   actualLocations, err := resolver.FilesByGlob("**/*ink-*")
    551 555   assert.NoError(t, err)
    552 556   return actualLocations
    553 557   },
    554  - expected: []Location{
    555  - NewVirtualLocation("file-1.txt", "link-1"),
    556  - NewVirtualLocation("file-2.txt", "link-2"),
     558 + expected: []file.Location{
     559 + file.NewVirtualLocation("file-1.txt", "link-1"),
     560 + file.NewVirtualLocation("file-2.txt", "link-2"),
    557 561   // we already have this real file path via another link, so only one is returned
    558  - // NewVirtualLocation("file-2.txt", "link-indirect"),
    559  - NewVirtualLocation("file-3.txt", "link-within"),
     562 + // file.NewVirtualLocation("file-2.txt", "link-indirect"),
     563 + file.NewVirtualLocation("file-3.txt", "link-within"),
    560 564   },
    561 565   },
    562 566   {
    563 567   name: "by basename",
    564  - runner: func(resolver FileResolver) []Location {
     568 + runner: func(resolver file.Resolver) []file.Location {
    565 569   // links are searched, but resolve to the real files
    566 570   actualLocations, err := resolver.FilesByGlob("**/file-2.txt")
    567 571   assert.NoError(t, err)
    568 572   return actualLocations
    569 573   },
    570  - expected: []Location{
     574 + expected: []file.Location{
    571 575   // this has two copies in the base image, which overwrites the same location
    572  - NewLocation("file-2.txt"),
     576 + file.NewLocation("file-2.txt"),
    573 577   },
    574 578   },
    575 579   {
    576 580   name: "by basename glob",
    577  - runner: func(resolver FileResolver) []Location {
     581 + runner: func(resolver file.Resolver) []file.Location {
    578 582   // links are searched, but resolve to the real files
    579 583   actualLocations, err := resolver.FilesByGlob("**/file-?.txt")
    580 584   assert.NoError(t, err)
    581 585   return actualLocations
    582 586   },
    583  - expected: []Location{
    584  - NewLocation("file-1.txt"),
    585  - NewLocation("file-2.txt"),
    586  - NewLocation("file-3.txt"),
    587  - NewLocation("parent/file-4.txt"),
     587 + expected: []file.Location{
     588 + file.NewLocation("file-1.txt"),
     589 + file.NewLocation("file-2.txt"),
     590 + file.NewLocation("file-3.txt"),
     591 + file.NewLocation("parent/file-4.txt"),
    588 592   },
    589 593   },
    590 594   {
    591 595   name: "by basename glob to links",
    592  - runner: func(resolver FileResolver) []Location {
     596 + runner: func(resolver file.Resolver) []file.Location {
    593 597   actualLocations, err := resolver.FilesByGlob("**/link-*")
    594 598   assert.NoError(t, err)
    595 599   return actualLocations
    596 600   },
    597  - expected: []Location{
    598  - NewVirtualLocationFromDirectory("file-1.txt", "link-1", file.Reference{RealPath: "file-1.txt"}),
    599  - NewVirtualLocationFromDirectory("file-2.txt", "link-2", file.Reference{RealPath: "file-2.txt"}),
     601 + expected: []file.Location{
     602 + file.NewVirtualLocationFromDirectory("file-1.txt", "link-1", stereoscopeFile.Reference{RealPath: "file-1.txt"}),
     603 + file.NewVirtualLocationFromDirectory("file-2.txt", "link-2", stereoscopeFile.Reference{RealPath: "file-2.txt"}),
    600 604   // we already have this real file path via another link, so only one is returned
    601  - //NewVirtualLocationFromDirectory("file-2.txt", "link-indirect", file.Reference{RealPath: "file-2.txt"}),
    602  - NewVirtualLocationFromDirectory("file-3.txt", "link-within", file.Reference{RealPath: "file-3.txt"}),
     605 + //file.NewVirtualLocationFromDirectory("file-2.txt", "link-indirect", stereoscopeFile.Reference{RealPath: "file-2.txt"}),
     606 + file.NewVirtualLocationFromDirectory("file-3.txt", "link-within", stereoscopeFile.Reference{RealPath: "file-3.txt"}),
    603 607   },
    604 608   },
    605 609   {
    606 610   name: "by extension",
    607  - runner: func(resolver FileResolver) []Location {
     611 + runner: func(resolver file.Resolver) []file.Location {
    608 612   // links are searched, but resolve to the real files
    609 613   actualLocations, err := resolver.FilesByGlob("**/*.txt")
    610 614   assert.NoError(t, err)
    611 615   return actualLocations
    612 616   },
    613  - expected: []Location{
    614  - NewLocation("file-1.txt"),
    615  - NewLocation("file-2.txt"),
    616  - NewLocation("file-3.txt"),
    617  - NewLocation("parent/file-4.txt"),
     617 + expected: []file.Location{
     618 + file.NewLocation("file-1.txt"),
     619 + file.NewLocation("file-2.txt"),
     620 + file.NewLocation("file-3.txt"),
     621 + file.NewLocation("parent/file-4.txt"),
    618 622   },
    619 623   },
    620 624   {
    621 625   name: "by path to degree 1 link",
    622  - runner: func(resolver FileResolver) []Location {
     626 + runner: func(resolver file.Resolver) []file.Location {
    623 627   // links resolve to the final file
    624 628   actualLocations, err := resolver.FilesByPath("/link-2")
    625 629   assert.NoError(t, err)
    626 630   return actualLocations
    627 631   },
    628  - expected: []Location{
     632 + expected: []file.Location{
    629 633   // we have multiple copies across layers
    630  - NewVirtualLocation("file-2.txt", "link-2"),
     634 + file.NewVirtualLocation("file-2.txt", "link-2"),
    631 635   },
    632 636   },
    633 637   {
    634 638   name: "by path to degree 2 link",
    635  - runner: func(resolver FileResolver) []Location {
     639 + runner: func(resolver file.Resolver) []file.Location {
    636 640   // multiple links resolves to the final file
    637 641   actualLocations, err := resolver.FilesByPath("/link-indirect")
    638 642   assert.NoError(t, err)
    639 643   return actualLocations
    640 644   },
    641  - expected: []Location{
     645 + expected: []file.Location{
    642 646   // we have multiple copies across layers
    643  - NewVirtualLocation("file-2.txt", "link-indirect"),
     647 + file.NewVirtualLocation("file-2.txt", "link-indirect"),
    644 648   },
    645 649   },
    646 650   }
    647 651   
    648 652   for _, test := range tests {
    649 653   t.Run(test.name, func(t *testing.T) {
    650  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-from-image-symlinks-fixture")
     654 + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture")
    651 655   
    652 656   actual := test.runner(resolver)
    653 657   
    skipped 3 lines
    657 661  }
    658 662   
    659 663  func Test_UnindexedDirectoryResolver_DoNotAddVirtualPathsToTree(t *testing.T) {
    660  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-prune-indexing")
     664 + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-prune-indexing")
    661 665   
    662 666   allLocations := resolver.AllLocations()
    663  - var allRealPaths []file.Path
     667 + var allRealPaths []stereoscopeFile.Path
    664 668   for l := range allLocations {
    665  - allRealPaths = append(allRealPaths, file.Path(l.RealPath))
     669 + allRealPaths = append(allRealPaths, stereoscopeFile.Path(l.RealPath))
    666 670   }
    667  - pathSet := file.NewPathSet(allRealPaths...)
     671 + pathSet := stereoscopeFile.NewPathSet(allRealPaths...)
    668 672   
    669 673   assert.False(t,
    670 674   pathSet.Contains("before-path/file.txt"),
    skipped 7 lines
    678 682  }
    679 683   
    680 684  func Test_UnindexedDirectoryResolver_FilesContents_errorOnDirRequest(t *testing.T) {
    681  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/system_paths")
     685 + resolver := NewFromUnindexedDirectory("./test-fixtures/system_paths")
    682 686   
    683  - dirLoc := NewLocation("arg/foo")
     687 + dirLoc := file.NewLocation("arg/foo")
    684 688   
    685 689   reader, err := resolver.FileContentsByLocation(dirLoc)
    686 690   require.Error(t, err)
    skipped 1 lines
    688 692  }
    689 693   
    690 694  func Test_UnindexedDirectoryResolver_AllLocations(t *testing.T) {
    691  - resolver := NewUnindexedDirectoryResolver("./test-fixtures/symlinks-from-image-symlinks-fixture")
     695 + resolver := NewFromUnindexedDirectory("./test-fixtures/symlinks-from-image-symlinks-fixture")
    692 696   
    693 697   paths := strset.New()
    694 698   for loc := range resolver.AllLocations() {
    skipped 29 lines
    724 728   p := "some/path/file"
    725 729   c := "some contents"
    726 730   
    727  - dr := NewUnindexedDirectoryResolver(tmpdir)
     731 + dr := NewFromUnindexedDirectory(tmpdir)
    728 732   
    729 733   locations, err := dr.FilesByPath(p)
    730 734   require.NoError(t, err)
    731 735   require.Len(t, locations, 0)
    732 736   
    733  - err = dr.Write(NewLocation(p), strings.NewReader(c))
     737 + err = dr.Write(file.NewLocation(p), strings.NewReader(c))
    734 738   require.NoError(t, err)
    735 739   
    736 740   locations, err = dr.FilesByPath(p)
    skipped 6 lines
    743 747   require.Equal(t, c, string(bytes))
    744 748  }
    745 749   
     750 +func testWithTimeout(t *testing.T, timeout time.Duration, test func(*testing.T)) {
     751 + done := make(chan bool)
     752 + go func() {
     753 + test(t)
     754 + done <- true
     755 + }()
     756 + 
     757 + select {
     758 + case <-time.After(timeout):
     759 + t.Fatal("test timed out")
     760 + case <-done:
     761 + }
     762 +}
     763 + 
     764 +func compareLocations(t *testing.T, expected, actual []file.Location) {
     765 + t.Helper()
     766 + ignoreUnexported := cmpopts.IgnoreFields(file.LocationData{}, "ref")
     767 + ignoreMetadata := cmpopts.IgnoreFields(file.LocationMetadata{}, "Annotations")
     768 + ignoreFS := cmpopts.IgnoreFields(file.Coordinates{}, "FileSystemID")
     769 + 
     770 + sort.Sort(file.Locations(expected))
     771 + sort.Sort(file.Locations(actual))
     772 + 
     773 + if d := cmp.Diff(expected, actual,
     774 + ignoreUnexported,
     775 + ignoreFS,
     776 + ignoreMetadata,
     777 + ); d != "" {
     778 + 
     779 + t.Errorf("unexpected locations (-want +got):\n%s", d)
     780 + }
     781 + 
     782 +}
     783 + 
  • ■ ■ ■ ■ ■ ■
    syft/linux/identify_release.go
    skipped 10 lines
    11 11   
    12 12   "github.com/anchore/syft/internal"
    13 13   "github.com/anchore/syft/internal/log"
    14  - "github.com/anchore/syft/syft/source"
     14 + "github.com/anchore/syft/syft/file"
    15 15  )
    16 16   
    17 17  // returns a distro or nil
    skipped 36 lines
    54 54  }
    55 55   
    56 56  // IdentifyRelease parses distro-specific files to discover and raise linux distribution release details.
    57  -func IdentifyRelease(resolver source.FileResolver) *Release {
     57 +func IdentifyRelease(resolver file.Resolver) *Release {
    58 58   logger := log.Nested("operation", "identify-release")
    59 59   for _, entry := range identityFiles {
    60 60   locations, err := resolver.FilesByPath(entry.path)
    skipped 133 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/binary_metadata.go
    1 1  package pkg
    2 2   
    3  -import "github.com/anchore/syft/syft/source"
     3 +import "github.com/anchore/syft/syft/file"
    4 4   
    5 5  type BinaryMetadata struct {
    6 6   Matches []ClassifierMatch `mapstructure:"Matches" json:"matches"`
    7 7  }
    8 8   
    9 9  type ClassifierMatch struct {
    10  - Classifier string `mapstructure:"Classifier" json:"classifier"`
    11  - Location source.Location `mapstructure:"Location" json:"location"`
     10 + Classifier string `mapstructure:"Classifier" json:"classifier"`
     11 + Location file.Location `mapstructure:"Location" json:"location"`
    12 12  }
    13 13   
  • ■ ■ ■ ■ ■ ■
    syft/pkg/catalog_test.go
    skipped 8 lines
    9 9   
    10 10   "github.com/anchore/syft/syft/artifact"
    11 11   "github.com/anchore/syft/syft/cpe"
    12  - "github.com/anchore/syft/syft/source"
     12 + "github.com/anchore/syft/syft/file"
    13 13  )
    14 14   
    15 15  type expectedIndexes struct {
    skipped 59 lines
    75 75   Name: "debian",
    76 76   Version: "1",
    77 77   Type: DebPkg,
    78  - Locations: source.NewLocationSet(
    79  - source.NewVirtualLocation("/c/path", "/another/path1"),
     78 + Locations: file.NewLocationSet(
     79 + file.NewVirtualLocation("/c/path", "/another/path1"),
    80 80   ),
    81 81   },
    82 82   {
    skipped 1 lines
    84 84   Name: "debian",
    85 85   Version: "2",
    86 86   Type: DebPkg,
    87  - Locations: source.NewLocationSet(
    88  - source.NewVirtualLocation("/d/path", "/another/path2"),
     87 + Locations: file.NewLocationSet(
     88 + file.NewVirtualLocation("/d/path", "/another/path2"),
    89 89   ),
    90 90   },
    91 91   },
    skipped 18 lines
    110 110   Name: "debian",
    111 111   Version: "1",
    112 112   Type: DebPkg,
    113  - Locations: source.NewLocationSet(
    114  - source.NewVirtualLocation("/c/path", "/another/path1"),
     113 + Locations: file.NewLocationSet(
     114 + file.NewVirtualLocation("/c/path", "/another/path1"),
    115 115   ),
    116 116   },
    117 117   {
    skipped 1 lines
    119 119   Name: "debian",
    120 120   Version: "2",
    121 121   Type: DebPkg,
    122  - Locations: source.NewLocationSet(
    123  - source.NewVirtualLocation("/d/path", "/another/path2"),
     122 + Locations: file.NewLocationSet(
     123 + file.NewVirtualLocation("/d/path", "/another/path2"),
    124 124   ),
    125 125   },
    126 126   {
    skipped 1 lines
    128 128   Name: "debian",
    129 129   Version: "3",
    130 130   Type: DebPkg,
    131  - Locations: source.NewLocationSet(
    132  - source.NewVirtualLocation("/e/path", "/another/path3"),
     131 + Locations: file.NewLocationSet(
     132 + file.NewVirtualLocation("/e/path", "/another/path3"),
    133 133   ),
    134 134   },
    135 135   },
    skipped 19 lines
    155 155   Name: "debian",
    156 156   Version: "1",
    157 157   Type: DebPkg,
    158  - Locations: source.NewLocationSet(
    159  - source.NewVirtualLocation("/c/path", "/another/path1"),
     158 + Locations: file.NewLocationSet(
     159 + file.NewVirtualLocation("/c/path", "/another/path1"),
    160 160   ),
    161 161   },
    162 162   {
    skipped 1 lines
    164 164   Name: "debian",
    165 165   Version: "2",
    166 166   Type: DebPkg,
    167  - Locations: source.NewLocationSet(
    168  - source.NewVirtualLocation("/d/path", "/another/path2"),
     167 + Locations: file.NewLocationSet(
     168 + file.NewVirtualLocation("/d/path", "/another/path2"),
    169 169   ),
    170 170   },
    171 171   },
    skipped 34 lines
    206 206   
    207 207   var pkgs = []Package{
    208 208   {
    209  - Locations: source.NewLocationSet(
    210  - source.NewVirtualLocation("/a/path", "/another/path"),
    211  - source.NewVirtualLocation("/b/path", "/bee/path"),
     209 + Locations: file.NewLocationSet(
     210 + file.NewVirtualLocation("/a/path", "/another/path"),
     211 + file.NewVirtualLocation("/b/path", "/bee/path"),
    212 212   ),
    213 213   Type: RpmPkg,
    214 214   },
    215 215   {
    216  - Locations: source.NewLocationSet(
    217  - source.NewVirtualLocation("/c/path", "/another/path"),
    218  - source.NewVirtualLocation("/d/path", "/another/path"),
     216 + Locations: file.NewLocationSet(
     217 + file.NewVirtualLocation("/c/path", "/another/path"),
     218 + file.NewVirtualLocation("/d/path", "/another/path"),
    219 219   ),
    220 220   Type: NpmPkg,
    221 221   },
    skipped 69 lines
    291 291   
    292 292  func TestCatalog_PathIndexDeduplicatesRealVsVirtualPaths(t *testing.T) {
    293 293   p1 := Package{
    294  - Locations: source.NewLocationSet(
    295  - source.NewVirtualLocation("/b/path", "/another/path"),
    296  - source.NewVirtualLocation("/b/path", "/b/path"),
     294 + Locations: file.NewLocationSet(
     295 + file.NewVirtualLocation("/b/path", "/another/path"),
     296 + file.NewVirtualLocation("/b/path", "/b/path"),
    297 297   ),
    298 298   Type: RpmPkg,
    299 299   Name: "Package-1",
    300 300   }
    301 301   
    302 302   p2 := Package{
    303  - Locations: source.NewLocationSet(
    304  - source.NewVirtualLocation("/b/path", "/b/path"),
     303 + Locations: file.NewLocationSet(
     304 + file.NewVirtualLocation("/b/path", "/b/path"),
    305 305   ),
    306 306   Type: RpmPkg,
    307 307   Name: "Package-2",
    308 308   }
    309 309   p2Dup := Package{
    310  - Locations: source.NewLocationSet(
    311  - source.NewVirtualLocation("/b/path", "/another/path"),
    312  - source.NewVirtualLocation("/b/path", "/c/path/b/dup"),
     310 + Locations: file.NewLocationSet(
     311 + file.NewVirtualLocation("/b/path", "/another/path"),
     312 + file.NewVirtualLocation("/b/path", "/c/path/b/dup"),
    313 313   ),
    314 314   Type: RpmPkg,
    315 315   Name: "Package-2",
    skipped 45 lines
    361 361   var tests = []struct {
    362 362   name string
    363 363   pkgs []Package
    364  - expectedLocations []source.Location
     364 + expectedLocations []file.Location
    365 365   expectedCPECount int
    366 366   }{
    367 367   {
    skipped 1 lines
    369 369   pkgs: []Package{
    370 370   {
    371 371   CPEs: []cpe.CPE{cpe.Must("cpe:2.3:a:package:1:1:*:*:*:*:*:*:*")},
    372  - Locations: source.NewLocationSet(
    373  - source.NewVirtualLocationFromCoordinates(
    374  - source.Coordinates{
     372 + Locations: file.NewLocationSet(
     373 + file.NewVirtualLocationFromCoordinates(
     374 + file.Coordinates{
    375 375   RealPath: "/b/path",
    376 376   FileSystemID: "a",
    377 377   },
    skipped 4 lines
    382 382   },
    383 383   {
    384 384   CPEs: []cpe.CPE{cpe.Must("cpe:2.3:b:package:1:1:*:*:*:*:*:*:*")},
    385  - Locations: source.NewLocationSet(
    386  - source.NewVirtualLocationFromCoordinates(
    387  - source.Coordinates{
     385 + Locations: file.NewLocationSet(
     386 + file.NewVirtualLocationFromCoordinates(
     387 + file.Coordinates{
    388 388   RealPath: "/b/path",
    389 389   FileSystemID: "b",
    390 390   },
    skipped 3 lines
    394 394   Type: RpmPkg,
    395 395   },
    396 396   },
    397  - expectedLocations: []source.Location{
    398  - source.NewVirtualLocationFromCoordinates(
    399  - source.Coordinates{
     397 + expectedLocations: []file.Location{
     398 + file.NewVirtualLocationFromCoordinates(
     399 + file.Coordinates{
    400 400   RealPath: "/b/path",
    401 401   FileSystemID: "a",
    402 402   },
    403 403   "/another/path",
    404 404   ),
    405  - source.NewVirtualLocationFromCoordinates(
    406  - source.Coordinates{
     405 + file.NewVirtualLocationFromCoordinates(
     406 + file.Coordinates{
    407 407   RealPath: "/b/path",
    408 408   FileSystemID: "b",
    409 409   },
    skipped 56 lines
  • ■ ■ ■ ■ ■
    syft/pkg/cataloger/alpm/cataloger_test.go
    skipped 8 lines
    9 9   "github.com/anchore/syft/syft/file"
    10 10   "github.com/anchore/syft/syft/pkg"
    11 11   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    12  - "github.com/anchore/syft/syft/source"
    13 12  )
    14 13   
    15 14  func TestAlpmCataloger(t *testing.T) {
    16  - dbLocation := source.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
     15 + dbLocation := file.NewLocation("var/lib/pacman/local/gmp-6.2.1-2/desc")
    17 16   expectedPkgs := []pkg.Package{
    18 17   {
    19 18   Name: "gmp",
    skipped 4 lines
    24 23   pkg.NewLicenseFromLocations("LGPL3", dbLocation),
    25 24   pkg.NewLicenseFromLocations("GPL", dbLocation),
    26 25   ),
    27  - Locations: source.NewLocationSet(dbLocation),
     26 + Locations: file.NewLocationSet(dbLocation),
    28 27   CPEs: nil,
    29 28   PURL: "",
    30 29   MetadataType: "AlpmMetadata",
    skipped 182 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/alpm/package.go
    skipped 3 lines
    4 4   "strings"
    5 5   
    6 6   "github.com/anchore/packageurl-go"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/linux"
    8 9   "github.com/anchore/syft/syft/pkg"
    9  - "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12  -func newPackage(m *parsedData, release *linux.Release, dbLocation source.Location) pkg.Package {
     12 +func newPackage(m *parsedData, release *linux.Release, dbLocation file.Location) pkg.Package {
    13 13   licenseCandidates := strings.Split(m.Licenses, "\n")
    14 14   
    15 15   p := pkg.Package{
    16 16   Name: m.Package,
    17 17   Version: m.Version,
    18  - Locations: source.NewLocationSet(dbLocation),
     18 + Locations: file.NewLocationSet(dbLocation),
    19 19   Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation.WithoutAnnotations(), licenseCandidates...)...),
    20 20   Type: pkg.AlpmPkg,
    21 21   PURL: packageURL(m, release),
    skipped 35 lines
  • ■ ■ ■ ■ ■
    syft/pkg/cataloger/alpm/parse_alpm_db.go
    skipped 16 lines
    17 17   "github.com/anchore/syft/syft/file"
    18 18   "github.com/anchore/syft/syft/pkg"
    19 19   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    20  - "github.com/anchore/syft/syft/source"
    21 20  )
    22 21   
    23 22  var _ generic.Parser = parseAlpmDB
    skipped 12 lines
    36 35   pkg.AlpmMetadata `mapstructure:",squash"`
    37 36  }
    38 37   
    39  -func parseAlpmDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     38 +func parseAlpmDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    40 39   data, err := parseAlpmDBEntry(reader)
    41 40   if err != nil {
    42 41   return nil, nil, err
    skipped 74 lines
    117 116   return scanner
    118 117  }
    119 118   
    120  -func getFileReader(path string, resolver source.FileResolver) (io.Reader, error) {
     119 +func getFileReader(path string, resolver file.Resolver) (io.Reader, error) {
    121 120   locs, err := resolver.FilesByPath(path)
    122 121   if err != nil {
    123 122   return nil, err
    skipped 137 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/apkdb/package.go
    skipped 3 lines
    4 4   "strings"
    5 5   
    6 6   "github.com/anchore/packageurl-go"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/license"
    8 9   "github.com/anchore/syft/syft/linux"
    9 10   "github.com/anchore/syft/syft/pkg"
    10  - "github.com/anchore/syft/syft/source"
    11 11  )
    12 12   
    13  -func newPackage(d parsedData, release *linux.Release, dbLocation source.Location) pkg.Package {
     13 +func newPackage(d parsedData, release *linux.Release, dbLocation file.Location) pkg.Package {
    14 14   // check if license is a valid spdx expression before splitting
    15 15   licenseStrings := []string{d.License}
    16 16   _, err := license.ParseExpression(d.License)
    skipped 5 lines
    22 22   p := pkg.Package{
    23 23   Name: d.Package,
    24 24   Version: d.Version,
    25  - Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
     25 + Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    26 26   Licenses: pkg.NewLicenseSet(pkg.NewLicensesFromLocation(dbLocation, licenseStrings...)...),
    27 27   PURL: packageURL(d.ApkMetadata, release),
    28 28   Type: pkg.ApkPkg,
    skipped 36 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/apkdb/parse_apk_db.go
    skipped 15 lines
    16 16   "github.com/anchore/syft/syft/linux"
    17 17   "github.com/anchore/syft/syft/pkg"
    18 18   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    19  - "github.com/anchore/syft/syft/source"
    20 19  )
    21 20   
    22 21  // integrity check
    skipped 12 lines
    35 34  // information on specific fields, see https://wiki.alpinelinux.org/wiki/Apk_spec.
    36 35  //
    37 36  //nolint:funlen,gocognit
    38  -func parseApkDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     37 +func parseApkDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    39 38   scanner := bufio.NewScanner(reader)
    40 39   
    41 40   var apks []parsedData
    skipped 92 lines
    134 133   return pkgs, discoverPackageDependencies(pkgs), nil
    135 134  }
    136 135   
    137  -func findReleases(resolver source.FileResolver, dbPath string) []linux.Release {
     136 +func findReleases(resolver file.Resolver, dbPath string) []linux.Release {
    138 137   if resolver == nil {
    139 138   return nil
    140 139   }
    skipped 16 lines
    157 156   return nil
    158 157   }
    159 158   
    160  - return parseReleasesFromAPKRepository(source.LocationReadCloser{
     159 + return parseReleasesFromAPKRepository(file.LocationReadCloser{
    161 160   Location: location,
    162 161   ReadCloser: reposReader,
    163 162   })
    164 163  }
    165 164   
    166  -func parseReleasesFromAPKRepository(reader source.LocationReadCloser) []linux.Release {
     165 +func parseReleasesFromAPKRepository(reader file.LocationReadCloser) []linux.Release {
    167 166   var releases []linux.Release
    168 167   
    169 168   reposB, err := io.ReadAll(reader)
    skipped 273 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/apkdb/parse_apk_db_test.go
    skipped 17 lines
    18 18   "github.com/anchore/syft/syft/pkg"
    19 19   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    20 20   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    21  - "github.com/anchore/syft/syft/source"
    22 21  )
    23 22   
    24 23  func TestExtraFileAttributes(t *testing.T) {
    skipped 655 lines
    680 679   
    681 680   for _, test := range tests {
    682 681   t.Run(test.fixture, func(t *testing.T) {
    683  - fixtureLocation := source.NewLocation(test.fixture)
    684  - test.expected.Locations = source.NewLocationSet(fixtureLocation)
     682 + fixtureLocation := file.NewLocation(test.fixture)
     683 + test.expected.Locations = file.NewLocationSet(fixtureLocation)
    685 684   licenses := test.expected.Licenses.ToSlice()
    686 685   for i := range licenses {
    687 686   licenses[i].Locations.Add(fixtureLocation)
    skipped 6 lines
    694 693   
    695 694  func TestMultiplePackages(t *testing.T) {
    696 695   fixture := "test-fixtures/multiple"
    697  - location := source.NewLocation(fixture)
    698  - fixtureLocationSet := source.NewLocationSet(location)
     696 + location := file.NewLocation(fixture)
     697 + fixtureLocationSet := file.NewLocationSet(location)
    699 698   expectedPkgs := []pkg.Package{
    700 699   {
    701 700   Name: "libc-utils",
    skipped 322 lines
    1024 1023   t.Run(test.name, func(t *testing.T) {
    1025 1024   pkgs, wantRelationships := test.genFn()
    1026 1025   gotRelationships := discoverPackageDependencies(pkgs)
    1027  - d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, source.LocationSet{}, pkg.LicenseSet{}))
     1026 + d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{}))
    1028 1027   if d != "" {
    1029 1028   t.Fail()
    1030 1029   t.Log(d)
    skipped 30 lines
    1061 1060   require.NoError(t, err)
    1062 1061   t.Cleanup(func() { require.NoError(t, f.Close()) })
    1063 1062   
    1064  - pkgs, relationships, err := parseApkDB(nil, nil, source.LocationReadCloser{
    1065  - Location: source.NewLocation(test.fixture),
     1063 + pkgs, relationships, err := parseApkDB(nil, nil, file.LocationReadCloser{
     1064 + Location: file.NewLocation(test.fixture),
    1066 1065   ReadCloser: f,
    1067 1066   })
    1068 1067   require.NoError(t, err)
    skipped 103 lines
    1172 1171   return names
    1173 1172  }
    1174 1173   
    1175  -func newLocationReadCloser(t *testing.T, path string) source.LocationReadCloser {
     1174 +func newLocationReadCloser(t *testing.T, path string) file.LocationReadCloser {
    1176 1175   f, err := os.Open(path)
    1177 1176   require.NoError(t, err)
    1178 1177   t.Cleanup(func() { f.Close() })
    1179 1178   
    1180  - return source.NewLocationReadCloser(source.NewLocation(path), f)
     1179 + return file.NewLocationReadCloser(file.NewLocation(path), f)
    1181 1180  }
    1182 1181   
    1183 1182  func Test_stripVersionSpecifier(t *testing.T) {
    skipped 72 lines
    1256 1255   for _, tt := range tests {
    1257 1256   t.Run(tt.desc, func(t *testing.T) {
    1258 1257   reposReader := io.NopCloser(strings.NewReader(tt.repos))
    1259  - got := parseReleasesFromAPKRepository(source.LocationReadCloser{
    1260  - Location: source.NewLocation("test"),
     1258 + got := parseReleasesFromAPKRepository(file.LocationReadCloser{
     1259 + Location: file.NewLocation("test"),
    1261 1260   ReadCloser: reposReader,
    1262 1261   })
    1263 1262   assert.Equal(t, tt.want, got)
    skipped 4 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/binary/cataloger.go
    skipped 2 lines
    3 3  import (
    4 4   "github.com/anchore/syft/internal/log"
    5 5   "github.com/anchore/syft/syft/artifact"
     6 + "github.com/anchore/syft/syft/file"
    6 7   "github.com/anchore/syft/syft/pkg"
    7  - "github.com/anchore/syft/syft/source"
    8 8  )
    9 9   
    10 10  const catalogerName = "binary-cataloger"
    skipped 16 lines
    27 27   
    28 28  // Catalog is given an object to resolve file references and content, this function returns any discovered Packages
    29 29  // after analyzing the catalog source.
    30  -func (c Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
     30 +func (c Cataloger) Catalog(resolver file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
    31 31   var packages []pkg.Package
    32 32   var relationships []artifact.Relationship
    33 33   
    skipped 34 lines
    68 68   target.Metadata = meta
    69 69  }
    70 70   
    71  -func catalog(resolver source.FileResolver, cls classifier) (packages []pkg.Package, err error) {
     71 +func catalog(resolver file.Resolver, cls classifier) (packages []pkg.Package, err error) {
    72 72   locations, err := resolver.FilesByGlob(cls.FileGlob)
    73 73   if err != nil {
    74 74   return nil, err
    skipped 23 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/binary/cataloger_test.go
    skipped 12 lines
    13 13   "github.com/stretchr/testify/require"
    14 14   
    15 15   "github.com/anchore/stereoscope/pkg/imagetest"
     16 + "github.com/anchore/syft/syft/file"
    16 17   "github.com/anchore/syft/syft/pkg"
    17 18   "github.com/anchore/syft/syft/source"
    18 19  )
    skipped 709 lines
    728 729   assert.Equal(t, 0, len(actualResults))
    729 730  }
    730 731   
    731  -func locations(locations ...string) source.LocationSet {
    732  - var locs []source.Location
     732 +func locations(locations ...string) file.LocationSet {
     733 + var locs []file.Location
    733 734   for _, s := range locations {
    734  - locs = append(locs, source.NewLocation(s))
     735 + locs = append(locs, file.NewLocation(s))
    735 736   }
    736  - return source.NewLocationSet(locs...)
     737 + return file.NewLocationSet(locs...)
    737 738  }
    738 739   
    739 740  // metadata paths are: realPath, virtualPath
    skipped 17 lines
    757 758   }
    758 759   return pkg.ClassifierMatch{
    759 760   Classifier: classifier,
    760  - Location: source.NewVirtualLocationFromCoordinates(
    761  - source.Coordinates{
     761 + Location: file.NewVirtualLocationFromCoordinates(
     762 + file.Coordinates{
    762 763   RealPath: realPath,
    763 764   },
    764 765   virtualPath,
    skipped 52 lines
    817 818   if len(failMessages) > 0 {
    818 819   assert.Failf(t, strings.Join(failMessages, "; "), "diff: %s",
    819 820   cmp.Diff(expected, p,
    820  - cmp.Transformer("Locations", func(l source.LocationSet) []source.Location {
     821 + cmp.Transformer("Locations", func(l file.LocationSet) []file.Location {
    821 822   return l.ToSlice()
    822 823   }),
    823  - cmpopts.IgnoreUnexported(pkg.Package{}, source.Location{}),
     824 + cmpopts.IgnoreUnexported(pkg.Package{}, file.Location{}),
    824 825   cmpopts.IgnoreFields(pkg.Package{}, "CPEs", "FoundBy", "MetadataType", "Type"),
    825 826   ))
    826 827   }
    skipped 3 lines
    830 831   searchCalled bool
    831 832  }
    832 833   
    833  -func (p *panicyResolver) FilesByExtension(_ ...string) ([]source.Location, error) {
     834 +func (p *panicyResolver) FilesByExtension(_ ...string) ([]file.Location, error) {
    834 835   p.searchCalled = true
    835 836   return nil, errors.New("not implemented")
    836 837  }
    837 838   
    838  -func (p *panicyResolver) FilesByBasename(_ ...string) ([]source.Location, error) {
     839 +func (p *panicyResolver) FilesByBasename(_ ...string) ([]file.Location, error) {
    839 840   p.searchCalled = true
    840 841   return nil, errors.New("not implemented")
    841 842  }
    842 843   
    843  -func (p *panicyResolver) FilesByBasenameGlob(_ ...string) ([]source.Location, error) {
     844 +func (p *panicyResolver) FilesByBasenameGlob(_ ...string) ([]file.Location, error) {
    844 845   p.searchCalled = true
    845 846   return nil, errors.New("not implemented")
    846 847  }
    847 848   
    848  -func (p *panicyResolver) FileContentsByLocation(_ source.Location) (io.ReadCloser, error) {
     849 +func (p *panicyResolver) FileContentsByLocation(_ file.Location) (io.ReadCloser, error) {
    849 850   p.searchCalled = true
    850 851   return nil, errors.New("not implemented")
    851 852  }
    skipped 2 lines
    854 855   return true
    855 856  }
    856 857   
    857  -func (p *panicyResolver) FilesByPath(_ ...string) ([]source.Location, error) {
     858 +func (p *panicyResolver) FilesByPath(_ ...string) ([]file.Location, error) {
    858 859   p.searchCalled = true
    859 860   return nil, errors.New("not implemented")
    860 861  }
    861 862   
    862  -func (p *panicyResolver) FilesByGlob(_ ...string) ([]source.Location, error) {
     863 +func (p *panicyResolver) FilesByGlob(_ ...string) ([]file.Location, error) {
    863 864   p.searchCalled = true
    864 865   return nil, errors.New("not implemented")
    865 866  }
    866 867   
    867  -func (p *panicyResolver) FilesByMIMEType(_ ...string) ([]source.Location, error) {
     868 +func (p *panicyResolver) FilesByMIMEType(_ ...string) ([]file.Location, error) {
    868 869   p.searchCalled = true
    869 870   return nil, errors.New("not implemented")
    870 871  }
    871 872   
    872  -func (p *panicyResolver) RelativeFileByPath(_ source.Location, _ string) *source.Location {
     873 +func (p *panicyResolver) RelativeFileByPath(_ file.Location, _ string) *file.Location {
    873 874   return nil
    874 875  }
    875 876   
    876  -func (p *panicyResolver) AllLocations() <-chan source.Location {
     877 +func (p *panicyResolver) AllLocations() <-chan file.Location {
    877 878   return nil
    878 879  }
    879 880   
    880  -func (p *panicyResolver) FileMetadataByLocation(_ source.Location) (source.FileMetadata, error) {
    881  - return source.FileMetadata{}, errors.New("not implemented")
     881 +func (p *panicyResolver) FileMetadataByLocation(_ file.Location) (file.Metadata, error) {
     882 + return file.Metadata{}, errors.New("not implemented")
    882 883  }
    883 884   
    884  -var _ source.FileResolver = (*panicyResolver)(nil)
     885 +var _ file.Resolver = (*panicyResolver)(nil)
    885 886   
    886 887  func Test_Cataloger_ResilientToErrors(t *testing.T) {
    887 888   c := NewCataloger()
    skipped 7 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/binary/classifier.go
    skipped 14 lines
    15 15   "github.com/anchore/syft/internal"
    16 16   "github.com/anchore/syft/internal/log"
    17 17   "github.com/anchore/syft/syft/cpe"
     18 + "github.com/anchore/syft/syft/file"
    18 19   "github.com/anchore/syft/syft/pkg"
    19 20   "github.com/anchore/syft/syft/pkg/cataloger/internal/unionreader"
    20  - "github.com/anchore/syft/syft/source"
    21 21  )
    22 22   
    23 23  var emptyPURL = packageurl.PackageURL{}
    skipped 29 lines
    53 53  }
    54 54   
    55 55  // evidenceMatcher is a function called to catalog Packages that match some sort of evidence
    56  -type evidenceMatcher func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error)
     56 +type evidenceMatcher func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error)
    57 57   
    58 58  func evidenceMatchers(matchers ...evidenceMatcher) evidenceMatcher {
    59  - return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) {
     59 + return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) {
    60 60   for _, matcher := range matchers {
    61 61   match, err := matcher(resolver, classifier, location)
    62 62   if err != nil {
    skipped 9 lines
    72 72   
    73 73  func fileNameTemplateVersionMatcher(fileNamePattern string, contentTemplate string) evidenceMatcher {
    74 74   pat := regexp.MustCompile(fileNamePattern)
    75  - return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) {
     75 + return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) {
    76 76   if !pat.MatchString(location.RealPath) {
    77 77   return nil, nil
    78 78   }
    skipped 39 lines
    118 118   
    119 119  func fileContentsVersionMatcher(pattern string) evidenceMatcher {
    120 120   pat := regexp.MustCompile(pattern)
    121  - return func(resolver source.FileResolver, classifier classifier, location source.Location) ([]pkg.Package, error) {
     121 + return func(resolver file.Resolver, classifier classifier, location file.Location) ([]pkg.Package, error) {
    122 122   contents, err := getContents(resolver, location)
    123 123   if err != nil {
    124 124   return nil, fmt.Errorf("unable to get read contents for file: %w", err)
    skipped 13 lines
    138 138  //nolint:gocognit
    139 139  func sharedLibraryLookup(sharedLibraryPattern string, sharedLibraryMatcher evidenceMatcher) evidenceMatcher {
    140 140   pat := regexp.MustCompile(sharedLibraryPattern)
    141  - return func(resolver source.FileResolver, classifier classifier, location source.Location) (packages []pkg.Package, _ error) {
     141 + return func(resolver file.Resolver, classifier classifier, location file.Location) (packages []pkg.Package, _ error) {
    142 142   libs, err := sharedLibraries(resolver, location)
    143 143   if err != nil {
    144 144   return nil, err
    skipped 14 lines
    159 159   }
    160 160   for _, p := range pkgs {
    161 161   // set the source binary as the first location
    162  - locationSet := source.NewLocationSet(location)
     162 + locationSet := file.NewLocationSet(location)
    163 163   locationSet.Add(p.Locations.ToSlice()...)
    164 164   p.Locations = locationSet
    165 165   meta, _ := p.Metadata.(pkg.BinaryMetadata)
    skipped 21 lines
    187 187   return p
    188 188  }
    189 189   
    190  -func getContents(resolver source.FileResolver, location source.Location) ([]byte, error) {
     190 +func getContents(resolver file.Resolver, location file.Location) ([]byte, error) {
    191 191   reader, err := resolver.FileContentsByLocation(location)
    192 192   if err != nil {
    193 193   return nil, err
    skipped 22 lines
    216 216   
    217 217  // sharedLibraries returns a list of all shared libraries found within a binary, currently
    218 218  // supporting: elf, macho, and windows pe
    219  -func sharedLibraries(resolver source.FileResolver, location source.Location) ([]string, error) {
     219 +func sharedLibraries(resolver file.Resolver, location file.Location) ([]string, error) {
    220 220   contents, err := getContents(resolver, location)
    221 221   if err != nil {
    222 222   return nil, err
    skipped 34 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/binary/classifier_test.go
    skipped 5 lines
    6 6   "github.com/stretchr/testify/require"
    7 7   
    8 8   "github.com/anchore/syft/syft/cpe"
    9  - "github.com/anchore/syft/syft/source"
     9 + "github.com/anchore/syft/syft/file"
    10 10  )
    11 11   
    12 12  func Test_ClassifierCPEs(t *testing.T) {
    skipped 50 lines
    63 63   
    64 64   for _, test := range tests {
    65 65   t.Run(test.name, func(t *testing.T) {
    66  - resolver := source.NewMockResolverForPaths(test.fixture)
    67  - locations, err := resolver.FilesByPath(test.fixture)
     66 + resolver := file.NewMockResolverForPaths(test.fixture)
     67 + ls, err := resolver.FilesByPath(test.fixture)
    68 68   require.NoError(t, err)
    69  - require.Len(t, locations, 1)
     69 + require.Len(t, ls, 1)
    70 70   
    71  - pkgs, err := test.classifier.EvidenceMatcher(resolver, test.classifier, locations[0])
     71 + pkgs, err := test.classifier.EvidenceMatcher(resolver, test.classifier, ls[0])
    72 72   require.NoError(t, err)
    73 73   
    74 74   require.Len(t, pkgs, 1)
    skipped 12 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/binary/package.go
    skipped 3 lines
    4 4   "reflect"
    5 5   
    6 6   "github.com/anchore/syft/syft/cpe"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/pkg"
    8  - "github.com/anchore/syft/syft/source"
    9 9  )
    10 10   
    11  -func newPackage(classifier classifier, location source.Location, matchMetadata map[string]string) *pkg.Package {
     11 +func newPackage(classifier classifier, location file.Location, matchMetadata map[string]string) *pkg.Package {
    12 12   version, ok := matchMetadata["version"]
    13 13   if !ok {
    14 14   return nil
    skipped 11 lines
    26 26   p := pkg.Package{
    27 27   Name: classifier.Package,
    28 28   Version: version,
    29  - Locations: source.NewLocationSet(
     29 + Locations: file.NewLocationSet(
    30 30   location.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation),
    31 31   ),
    32 32   Type: pkg.BinaryPkg,
    skipped 32 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/catalog.go
    skipped 13 lines
    14 14   "github.com/anchore/syft/internal/log"
    15 15   "github.com/anchore/syft/syft/artifact"
    16 16   "github.com/anchore/syft/syft/event"
     17 + "github.com/anchore/syft/syft/file"
    17 18   "github.com/anchore/syft/syft/linux"
    18 19   "github.com/anchore/syft/syft/pkg"
    19 20   "github.com/anchore/syft/syft/pkg/cataloger/common/cpe"
    20  - "github.com/anchore/syft/syft/source"
    21 21  )
    22 22   
    23 23  // Monitor provides progress-related data for observing the progress of a Catalog() call (published on the event bus).
    skipped 26 lines
    50 50   return &filesProcessed, &packagesDiscovered
    51 51  }
    52 52   
    53  -func runCataloger(cataloger pkg.Cataloger, resolver source.FileResolver) (catalogerResult *catalogResult, err error) {
     53 +func runCataloger(cataloger pkg.Cataloger, resolver file.Resolver) (catalogerResult *catalogResult, err error) {
    54 54   // handle individual cataloger panics
    55 55   defer func() {
    56 56   if e := recover(); e != nil {
    skipped 48 lines
    105 105  // request.
    106 106  //
    107 107  //nolint:funlen
    108  -func Catalog(resolver source.FileResolver, _ *linux.Release, parallelism int, catalogers ...pkg.Cataloger) (*pkg.Collection, []artifact.Relationship, error) {
     108 +func Catalog(resolver file.Resolver, _ *linux.Release, parallelism int, catalogers ...pkg.Cataloger) (*pkg.Collection, []artifact.Relationship, error) {
    109 109   catalog := pkg.NewCollection()
    110 110   var allRelationships []artifact.Relationship
    111 111   
    skipped 70 lines
    182 182   return catalog, allRelationships, errs
    183 183  }
    184 184   
    185  -func packageFileOwnershipRelationships(p pkg.Package, resolver source.FilePathResolver) ([]artifact.Relationship, error) {
     185 +func packageFileOwnershipRelationships(p pkg.Package, resolver file.PathResolver) ([]artifact.Relationship, error) {
    186 186   fileOwner, ok := p.Metadata.(pkg.FileOwner)
    187 187   if !ok {
    188 188   return nil, nil
    189 189   }
    190 190   
    191  - locations := map[artifact.ID]source.Location{}
     191 + locations := map[artifact.ID]file.Location{}
    192 192   
    193 193   for _, path := range fileOwner.OwnedFiles() {
    194 194   pathRefs, err := resolver.FilesByPath(path)
    skipped 30 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/catalog_test.go
    skipped 5 lines
    6 6   "github.com/stretchr/testify/require"
    7 7   
    8 8   "github.com/anchore/syft/syft/artifact"
     9 + "github.com/anchore/syft/syft/file"
    9 10   "github.com/anchore/syft/syft/linux"
    10 11   "github.com/anchore/syft/syft/pkg"
    11  - "github.com/anchore/syft/syft/source"
    12 12  )
    13 13   
    14 14  func Test_CatalogPanicHandling(t *testing.T) {
    15 15   catalog, relationships, err := Catalog(
    16  - source.NewMockResolverForPaths(),
     16 + file.NewMockResolverForPaths(),
    17 17   &linux.Release{},
    18 18   1,
    19 19   panickingCataloger{},
    skipped 12 lines
    32 32   return "panicking-cataloger"
    33 33  }
    34 34   
    35  -func (p panickingCataloger) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
     35 +func (p panickingCataloger) Catalog(_ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
    36 36   panic("something bad happened")
    37 37  }
    38 38   
    skipped 5 lines
    44 44   return "returning-cataloger"
    45 45  }
    46 46   
    47  -func (p returningCataloger) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
     47 +func (p returningCataloger) Catalog(_ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
    48 48   pkg1 := pkg.Package{
    49 49   Name: "package-1",
    50 50   Version: "1.0",
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/cataloger_test.go
    skipped 5 lines
    6 6   "github.com/stretchr/testify/assert"
    7 7   
    8 8   "github.com/anchore/syft/syft/artifact"
     9 + "github.com/anchore/syft/syft/file"
    9 10   "github.com/anchore/syft/syft/pkg"
    10  - "github.com/anchore/syft/syft/source"
    11 11  )
    12 12   
    13 13  var _ pkg.Cataloger = (*dummy)(nil)
    skipped 6 lines
    20 20   return d.name
    21 21  }
    22 22   
    23  -func (d dummy) Catalog(_ source.FileResolver) ([]pkg.Package, []artifact.Relationship, error) {
     23 +func (d dummy) Catalog(_ file.Resolver) ([]pkg.Package, []artifact.Relationship, error) {
    24 24   panic("not implemented")
    25 25  }
    26 26   
    skipped 315 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/cpp/package.go
    skipped 3 lines
    4 4   "strings"
    5 5   
    6 6   "github.com/anchore/packageurl-go"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/pkg"
    8  - "github.com/anchore/syft/syft/source"
    9 9  )
    10 10   
    11  -func newConanfilePackage(m pkg.ConanMetadata, locations ...source.Location) *pkg.Package {
     11 +func newConanfilePackage(m pkg.ConanMetadata, locations ...file.Location) *pkg.Package {
    12 12   fields := strings.Split(strings.TrimSpace(m.Ref), "/")
    13 13   if len(fields) < 2 {
    14 14   return nil
    skipped 8 lines
    23 23   p := pkg.Package{
    24 24   Name: pkgName,
    25 25   Version: pkgVersion,
    26  - Locations: source.NewLocationSet(locations...),
     26 + Locations: file.NewLocationSet(locations...),
    27 27   PURL: packageURL(pkgName, pkgVersion),
    28 28   Language: pkg.CPP,
    29 29   Type: pkg.ConanPkg,
    skipped 6 lines
    36 36   return &p
    37 37  }
    38 38   
    39  -func newConanlockPackage(m pkg.ConanLockMetadata, locations ...source.Location) *pkg.Package {
     39 +func newConanlockPackage(m pkg.ConanLockMetadata, locations ...file.Location) *pkg.Package {
    40 40   fields := strings.Split(strings.Split(m.Ref, "@")[0], "/")
    41 41   if len(fields) < 2 {
    42 42   return nil
    skipped 8 lines
    51 51   p := pkg.Package{
    52 52   Name: pkgName,
    53 53   Version: pkgVersion,
    54  - Locations: source.NewLocationSet(locations...),
     54 + Locations: file.NewLocationSet(locations...),
    55 55   PURL: packageURL(pkgName, pkgVersion),
    56 56   Language: pkg.CPP,
    57 57   Type: pkg.ConanPkg,
    skipped 20 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/cpp/parse_conanfile.go
    skipped 7 lines
    8 8   "strings"
    9 9   
    10 10   "github.com/anchore/syft/syft/artifact"
     11 + "github.com/anchore/syft/syft/file"
    11 12   "github.com/anchore/syft/syft/pkg"
    12 13   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    13  - "github.com/anchore/syft/syft/source"
    14 14  )
    15 15   
    16 16  var _ generic.Parser = parseConanfile
    skipped 3 lines
    20 20  }
    21 21   
    22 22  // parseConanfile is a parser function for conanfile.txt contents, returning all packages discovered.
    23  -func parseConanfile(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     23 +func parseConanfile(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    24 24   r := bufio.NewReader(reader)
    25 25   inRequirements := false
    26 26   var pkgs []pkg.Package
    skipped 36 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/cpp/parse_conanfile_test.go
    skipped 3 lines
    4 4   "testing"
    5 5   
    6 6   "github.com/anchore/syft/syft/artifact"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/pkg"
    8 9   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    9  - "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12 12  func TestParseConanfile(t *testing.T) {
    13 13   fixture := "test-fixtures/conanfile.txt"
    14  - fixtureLocationSet := source.NewLocationSet(source.NewLocation(fixture))
     14 + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
    15 15   expected := []pkg.Package{
    16 16   {
    17 17   Name: "catch2",
    skipped 78 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/cpp/parse_conanlock.go
    skipped 4 lines
    5 5   "strings"
    6 6   
    7 7   "github.com/anchore/syft/syft/artifact"
     8 + "github.com/anchore/syft/syft/file"
    8 9   "github.com/anchore/syft/syft/pkg"
    9 10   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    10  - "github.com/anchore/syft/syft/source"
    11 11  )
    12 12   
    13 13  var _ generic.Parser = parseConanlock
    skipped 16 lines
    30 30  }
    31 31   
    32 32  // parseConanlock is a parser function for conan.lock contents, returning all packages discovered.
    33  -func parseConanlock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     33 +func parseConanlock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    34 34   var pkgs []pkg.Package
    35 35   var cl conanLock
    36 36   if err := json.NewDecoder(reader).Decode(&cl); err != nil {
    skipped 40 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/cpp/parse_conanlock_test.go
    skipped 3 lines
    4 4   "testing"
    5 5   
    6 6   "github.com/anchore/syft/syft/artifact"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/pkg"
    8 9   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    9  - "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12 12  func TestParseConanlock(t *testing.T) {
    skipped 3 lines
    16 16   Name: "zlib",
    17 17   Version: "1.2.12",
    18 18   PURL: "pkg:conan/[email protected]",
    19  - Locations: source.NewLocationSet(source.NewLocation(fixture)),
     19 + Locations: file.NewLocationSet(file.NewLocation(fixture)),
    20 20   Language: pkg.CPP,
    21 21   Type: pkg.ConanPkg,
    22 22   MetadataType: pkg.ConanLockMetadataType,
    skipped 18 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/dart/package.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "github.com/anchore/packageurl-go"
     5 + "github.com/anchore/syft/syft/file"
    5 6   "github.com/anchore/syft/syft/pkg"
    6  - "github.com/anchore/syft/syft/source"
    7 7  )
    8 8   
    9  -func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...source.Location) pkg.Package {
     9 +func newPubspecLockPackage(name string, raw pubspecLockPackage, locations ...file.Location) pkg.Package {
    10 10   metadata := pkg.DartPubMetadata{
    11 11   Name: name,
    12 12   Version: raw.Version,
    skipped 4 lines
    17 17   p := pkg.Package{
    18 18   Name: name,
    19 19   Version: raw.Version,
    20  - Locations: source.NewLocationSet(locations...),
     20 + Locations: file.NewLocationSet(locations...),
    21 21   PURL: packageURL(metadata),
    22 22   Language: pkg.Dart,
    23 23   Type: pkg.DartPubPkg,
    skipped 34 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/dart/parse_pubspec_lock.go
    skipped 8 lines
    9 9   
    10 10   "github.com/anchore/syft/internal/log"
    11 11   "github.com/anchore/syft/syft/artifact"
     12 + "github.com/anchore/syft/syft/file"
    12 13   "github.com/anchore/syft/syft/pkg"
    13 14   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    14  - "github.com/anchore/syft/syft/source"
    15 15  )
    16 16   
    17 17  var _ generic.Parser = parsePubspecLock
    skipped 20 lines
    38 38   ResolvedRef string `yaml:"resolved-ref" mapstructure:"resolved-ref"`
    39 39  }
    40 40   
    41  -func parsePubspecLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     41 +func parsePubspecLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    42 42   var pkgs []pkg.Package
    43 43   
    44 44   dec := yaml.NewDecoder(reader)
    skipped 53 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/dart/parse_pubspec_lock_test.go
    skipped 3 lines
    4 4   "testing"
    5 5   
    6 6   "github.com/anchore/syft/syft/artifact"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/pkg"
    8 9   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    9  - "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12 12  func TestParsePubspecLock(t *testing.T) {
    13 13   fixture := "test-fixtures/pubspec.lock"
    14  - fixtureLocationSet := source.NewLocationSet(source.NewLocation(fixture))
     14 + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
    15 15   expected := []pkg.Package{
    16 16   {
    17 17   Name: "ale",
    skipped 86 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/deb/cataloger_test.go
    skipped 5 lines
    6 6   "github.com/anchore/syft/syft/file"
    7 7   "github.com/anchore/syft/syft/pkg"
    8 8   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    9  - "github.com/anchore/syft/syft/source"
    10 9  )
    11 10   
    12 11  func TestDpkgCataloger(t *testing.T) {
    13  - licenseLocation := source.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")
     12 + licenseLocation := file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright")
    14 13   expected := []pkg.Package{
    15 14   {
    16 15   Name: "libpam-runtime",
    skipped 4 lines
    21 20   pkg.NewLicenseFromLocations("GPL-2", licenseLocation),
    22 21   pkg.NewLicenseFromLocations("LGPL-2.1", licenseLocation),
    23 22   ),
    24  - Locations: source.NewLocationSet(
    25  - source.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"),
    26  - source.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"),
    27  - source.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.conffiles", "/var/lib/dpkg/info/libpam-runtime.conffiles"),
    28  - source.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright"),
     23 + Locations: file.NewLocationSet(
     24 + file.NewVirtualLocation("/var/lib/dpkg/status", "/var/lib/dpkg/status"),
     25 + file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.md5sums", "/var/lib/dpkg/info/libpam-runtime.md5sums"),
     26 + file.NewVirtualLocation("/var/lib/dpkg/info/libpam-runtime.conffiles", "/var/lib/dpkg/info/libpam-runtime.conffiles"),
     27 + file.NewVirtualLocation("/usr/share/doc/libpam-runtime/copyright", "/usr/share/doc/libpam-runtime/copyright"),
    29 28   ),
    30 29   Type: pkg.DebPkg,
    31 30   MetadataType: pkg.DpkgMetadataType,
    skipped 84 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/deb/package.go
    skipped 9 lines
    10 10   "github.com/anchore/packageurl-go"
    11 11   "github.com/anchore/syft/internal"
    12 12   "github.com/anchore/syft/internal/log"
     13 + "github.com/anchore/syft/syft/file"
    13 14   "github.com/anchore/syft/syft/linux"
    14 15   "github.com/anchore/syft/syft/pkg"
    15  - "github.com/anchore/syft/syft/source"
    16 16  )
    17 17   
    18 18  const (
    skipped 2 lines
    21 21   docsPath = "/usr/share/doc"
    22 22  )
    23 23   
    24  -func newDpkgPackage(d pkg.DpkgMetadata, dbLocation source.Location, resolver source.FileResolver, release *linux.Release) pkg.Package {
     24 +func newDpkgPackage(d pkg.DpkgMetadata, dbLocation file.Location, resolver file.Resolver, release *linux.Release) pkg.Package {
    25 25   // TODO: separate pr to license refactor, but explore extracting dpkg-specific license parsing into a separate function
    26 26   licenses := make([]pkg.License, 0)
    27 27   p := pkg.Package{
    28 28   Name: d.Package,
    29 29   Version: d.Version,
    30 30   Licenses: pkg.NewLicenseSet(licenses...),
    31  - Locations: source.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
     31 + Locations: file.NewLocationSet(dbLocation.WithAnnotation(pkg.EvidenceAnnotationKey, pkg.PrimaryEvidenceAnnotation)),
    32 32   PURL: packageURL(d, release),
    33 33   Type: pkg.DebPkg,
    34 34   MetadataType: pkg.DpkgMetadataType,
    skipped 48 lines
    83 83   ).ToString()
    84 84  }
    85 85   
    86  -func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) {
     86 +func addLicenses(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
    87 87   metadata, ok := p.Metadata.(pkg.DpkgMetadata)
    88 88   if !ok {
    89 89   log.WithFields("package", p).Warn("unable to extract DPKG metadata to add licenses")
    skipped 15 lines
    105 105   }
    106 106  }
    107 107   
    108  -func mergeFileListing(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) {
     108 +func mergeFileListing(resolver file.Resolver, dbLocation file.Location, p *pkg.Package) {
    109 109   metadata, ok := p.Metadata.(pkg.DpkgMetadata)
    110 110   if !ok {
    111 111   log.WithFields("package", p).Warn("unable to extract DPKG metadata to file listing")
    skipped 25 lines
    137 137   p.Locations.Add(infoLocations...)
    138 138  }
    139 139   
    140  -func getAdditionalFileListing(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) ([]pkg.DpkgFileRecord, []source.Location) {
     140 +func getAdditionalFileListing(resolver file.Resolver, dbLocation file.Location, m pkg.DpkgMetadata) ([]pkg.DpkgFileRecord, []file.Location) {
    141 141   // ensure the default value for a collection is never nil since this may be shown as JSON
    142 142   var files = make([]pkg.DpkgFileRecord, 0)
    143  - var locations []source.Location
     143 + var locations []file.Location
    144 144   
    145 145   md5Reader, md5Location := fetchMd5Contents(resolver, dbLocation, m)
    146 146   
    skipped 21 lines
    168 168  }
    169 169   
    170 170  //nolint:dupl
    171  -func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) {
     171 +func fetchMd5Contents(resolver file.Resolver, dbLocation file.Location, m pkg.DpkgMetadata) (io.ReadCloser, *file.Location) {
    172 172   var md5Reader io.ReadCloser
    173 173   var err error
    174 174   
    skipped 29 lines
    204 204  }
    205 205   
    206 206  //nolint:dupl
    207  -func fetchConffileContents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) {
     207 +func fetchConffileContents(resolver file.Resolver, dbLocation file.Location, m pkg.DpkgMetadata) (io.ReadCloser, *file.Location) {
    208 208   var reader io.ReadCloser
    209 209   var err error
    210 210   
    skipped 28 lines
    239 239   return reader, &l
    240 240  }
    241 241   
    242  -func fetchCopyrightContents(resolver source.FileResolver, dbLocation source.Location, m pkg.DpkgMetadata) (io.ReadCloser, *source.Location) {
     242 +func fetchCopyrightContents(resolver file.Resolver, dbLocation file.Location, m pkg.DpkgMetadata) (io.ReadCloser, *file.Location) {
    243 243   if resolver == nil {
    244 244   return nil, nil
    245 245   }
    skipped 28 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/deb/parse_dpkg_db.go
    skipped 13 lines
    14 14   "github.com/anchore/syft/internal"
    15 15   "github.com/anchore/syft/internal/log"
    16 16   "github.com/anchore/syft/syft/artifact"
     17 + "github.com/anchore/syft/syft/file"
    17 18   "github.com/anchore/syft/syft/pkg"
    18 19   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    19  - "github.com/anchore/syft/syft/source"
    20 20  )
    21 21   
    22 22  var (
    skipped 1 lines
    24 24   sourceRegexp = regexp.MustCompile(`(?P<name>\S+)( \((?P<version>.*)\))?`)
    25 25  )
    26 26   
    27  -func parseDpkgDB(resolver source.FileResolver, env *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     27 +func parseDpkgDB(resolver file.Resolver, env *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    28 28   metadata, err := parseDpkgStatus(reader)
    29 29   if err != nil {
    30 30   return nil, nil, fmt.Errorf("unable to catalog dpkg DB=%q: %w", reader.RealPath, err)
    skipped 168 lines
  • ■ ■ ■ ■ ■
    syft/pkg/cataloger/deb/parse_dpkg_db_test.go
    skipped 14 lines
    15 15   "github.com/anchore/syft/syft/linux"
    16 16   "github.com/anchore/syft/syft/pkg"
    17 17   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    18  - "github.com/anchore/syft/syft/source"
    19 18  )
    20 19   
    21 20  func Test_parseDpkgStatus(t *testing.T) {
    skipped 286 lines
    308 307   Type: "deb",
    309 308   PURL: "pkg:deb/debian/apt?distro=debian-10",
    310 309   Licenses: pkg.NewLicenseSet(),
    311  - Locations: source.NewLocationSet(source.NewLocation("place")),
     310 + Locations: file.NewLocationSet(file.NewLocation("place")),
    312 311   MetadataType: "DpkgMetadata",
    313 312   Metadata: pkg.DpkgMetadata{
    314 313   Package: "apt",
    skipped 87 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/dotnet/package.go
    skipped 3 lines
    4 4   "strings"
    5 5   
    6 6   "github.com/anchore/packageurl-go"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/pkg"
    8  - "github.com/anchore/syft/syft/source"
    9 9  )
    10 10   
    11  -func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations ...source.Location) *pkg.Package {
     11 +func newDotnetDepsPackage(nameVersion string, lib dotnetDepsLibrary, locations ...file.Location) *pkg.Package {
    12 12   if lib.Type != "package" {
    13 13   return nil
    14 14   }
    skipped 13 lines
    28 28   p := &pkg.Package{
    29 29   Name: name,
    30 30   Version: version,
    31  - Locations: source.NewLocationSet(locations...),
     31 + Locations: file.NewLocationSet(locations...),
    32 32   PURL: packageURL(m),
    33 33   Language: pkg.Dotnet,
    34 34   Type: pkg.DotnetPkg,
    skipped 32 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/dotnet/parse_dotnet_deps.go
    skipped 5 lines
    6 6   "sort"
    7 7   
    8 8   "github.com/anchore/syft/syft/artifact"
     9 + "github.com/anchore/syft/syft/file"
    9 10   "github.com/anchore/syft/syft/pkg"
    10 11   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    11  - "github.com/anchore/syft/syft/source"
    12 12  )
    13 13   
    14 14  var _ generic.Parser = parseDotnetDeps
    skipped 9 lines
    24 24   HashPath string `json:"hashPath"`
    25 25  }
    26 26   
    27  -func parseDotnetDeps(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     27 +func parseDotnetDeps(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    28 28   var pkgs []pkg.Package
    29 29   
    30 30   dec := json.NewDecoder(reader)
    skipped 31 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/dotnet/parse_dotnet_deps_test.go
    skipped 3 lines
    4 4   "testing"
    5 5   
    6 6   "github.com/anchore/syft/syft/artifact"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/pkg"
    8 9   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    9  - "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12 12  func TestParseDotnetDeps(t *testing.T) {
    13 13   fixture := "test-fixtures/TestLibrary.deps.json"
    14  - fixtureLocationSet := source.NewLocationSet(source.NewLocation(fixture))
     14 + fixtureLocationSet := file.NewLocationSet(file.NewLocation(fixture))
    15 15   expected := []pkg.Package{
    16 16   {
    17 17   Name: "AWSSDK.Core",
    skipped 197 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/elixir/package.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "github.com/anchore/packageurl-go"
     5 + "github.com/anchore/syft/syft/file"
    5 6   "github.com/anchore/syft/syft/pkg"
    6  - "github.com/anchore/syft/syft/source"
    7 7  )
    8 8   
    9  -func newPackage(d pkg.MixLockMetadata, locations ...source.Location) pkg.Package {
     9 +func newPackage(d pkg.MixLockMetadata, locations ...file.Location) pkg.Package {
    10 10   p := pkg.Package{
    11 11   Name: d.Name,
    12 12   Version: d.Version,
    13 13   Language: pkg.Elixir,
    14  - Locations: source.NewLocationSet(locations...),
     14 + Locations: file.NewLocationSet(locations...),
    15 15   PURL: packageURL(d),
    16 16   Type: pkg.HexPkg,
    17 17   MetadataType: pkg.MixLockMetadataType,
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/elixir/parse_mix_lock.go
    skipped 8 lines
    9 9   
    10 10   "github.com/anchore/syft/internal/log"
    11 11   "github.com/anchore/syft/syft/artifact"
     12 + "github.com/anchore/syft/syft/file"
    12 13   "github.com/anchore/syft/syft/pkg"
    13 14   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    14  - "github.com/anchore/syft/syft/source"
    15 15  )
    16 16   
    17 17  // integrity check
    skipped 2 lines
    20 20  var mixLockDelimiter = regexp.MustCompile(`[%{}\n" ,:]+`)
    21 21   
    22 22  // parseMixLock parses a mix.lock and returns the discovered Elixir packages.
    23  -func parseMixLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     23 +func parseMixLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    24 24   r := bufio.NewReader(reader)
    25 25   
    26 26   var packages []pkg.Package
    skipped 33 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/elixir/parse_mix_lock_test.go
    skipped 3 lines
    4 4   "testing"
    5 5   
    6 6   "github.com/anchore/syft/syft/artifact"
     7 + "github.com/anchore/syft/syft/file"
    7 8   "github.com/anchore/syft/syft/pkg"
    8 9   "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest"
    9  - "github.com/anchore/syft/syft/source"
    10 10  )
    11 11   
    12 12  func TestParseMixLock(t *testing.T) {
    13  - locations := source.NewLocationSet(source.NewLocation("test-fixtures/mix.lock"))
     13 + locations := file.NewLocationSet(file.NewLocation("test-fixtures/mix.lock"))
    14 14   expected := []pkg.Package{
    15 15   {
    16 16   Name: "castore",
    skipped 233 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/erlang/package.go
    skipped 1 lines
    2 2   
    3 3  import (
    4 4   "github.com/anchore/packageurl-go"
     5 + "github.com/anchore/syft/syft/file"
    5 6   "github.com/anchore/syft/syft/pkg"
    6  - "github.com/anchore/syft/syft/source"
    7 7  )
    8 8   
    9  -func newPackage(d pkg.RebarLockMetadata, locations ...source.Location) pkg.Package {
     9 +func newPackage(d pkg.RebarLockMetadata, locations ...file.Location) pkg.Package {
    10 10   p := pkg.Package{
    11 11   Name: d.Name,
    12 12   Version: d.Version,
    13 13   Language: pkg.Erlang,
    14  - Locations: source.NewLocationSet(locations...),
     14 + Locations: file.NewLocationSet(locations...),
    15 15   PURL: packageURL(d),
    16 16   Type: pkg.HexPkg,
    17 17   MetadataType: pkg.RebarLockMetadataType,
    skipped 21 lines
  • ■ ■ ■ ■ ■ ■
    syft/pkg/cataloger/erlang/parse_rebar_lock.go
    skipped 2 lines
    3 3  import (
    4 4   "github.com/anchore/syft/internal/log"
    5 5   "github.com/anchore/syft/syft/artifact"
     6 + "github.com/anchore/syft/syft/file"
    6 7   "github.com/anchore/syft/syft/pkg"
    7 8   "github.com/anchore/syft/syft/pkg/cataloger/generic"
    8  - "github.com/anchore/syft/syft/source"
    9 9  )
    10 10   
    11 11  // parseRebarLock parses a rebar.lock and returns the discovered Elixir packages.
    12 12  //
    13 13  //nolint:funlen
    14  -func parseRebarLock(_ source.FileResolver, _ *generic.Environment, reader source.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
     14 +func parseRebarLock(_ file.Resolver, _ *generic.Environment, reader file.LocationReadCloser) ([]pkg.Package, []artifact.Relationship, error) {
    15 15   doc, err := parseErlang(reader)
    16 16   if err != nil {
    17 17   return nil, nil, err
    skipped 84 lines
Please wait...
Page is in error, reload to recover