diff --git a/syft/cataloger/catalog.go b/syft/cataloger/catalog.go index bda9a1a2e..022f81855 100644 --- a/syft/cataloger/catalog.go +++ b/syft/cataloger/catalog.go @@ -43,15 +43,16 @@ func Catalog(resolver scope.Resolver, catalogers ...Cataloger) (*pkg.Catalog, er // perform analysis, accumulating errors for each failed analysis var errs error for _, theCataloger := range catalogers { - // TODO: check for multiple rounds of analyses by Iterate error packages, err := theCataloger.Catalog(resolver) if err != nil { errs = multierror.Append(errs, err) continue } - log.Debugf("cataloger '%s' discovered '%d' packages", theCataloger.Name(), len(packages)) - packagesDiscovered.N += int64(len(packages)) + catalogedPackages := len(packages) + + log.Debugf("cataloger '%s' discovered '%d' packages", theCataloger.Name(), catalogedPackages) + packagesDiscovered.N += int64(catalogedPackages) for _, p := range packages { catalog.Add(p) diff --git a/syft/cataloger/cataloger.go b/syft/cataloger/cataloger.go index 817a7f8d4..e10de959b 100644 --- a/syft/cataloger/cataloger.go +++ b/syft/cataloger/cataloger.go @@ -24,10 +24,8 @@ import ( type Cataloger interface { // Name returns a string that uniquely describes a cataloger Name() string - // Catalog is given an object to resolve file references and content, this function should return any discovered Packages after analyzing the catalog source. + // Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source. Catalog(resolver scope.Resolver) ([]pkg.Package, error) - // TODO: add "IterationNeeded" error to indicate to the driver to continue with another Select/Catalog pass - // TODO: we should consider refactoring to return a set of io.Readers instead of the full contents themselves (allow for optional buffering). } // ImageCatalogers returns a slice of locally implemented catalogers that are fit for detecting installations of packages. diff --git a/syft/cataloger/common/generic_cataloger.go b/syft/cataloger/common/generic_cataloger.go index ce9c6d885..90ca62d06 100644 --- a/syft/cataloger/common/generic_cataloger.go +++ b/syft/cataloger/common/generic_cataloger.go @@ -52,6 +52,7 @@ func (c *GenericCataloger) clear() { c.parsers = make(map[file.Reference]ParserFn) } +// Catalog is given an object to resolve file references and content, this function returns any discovered Packages after analyzing the catalog source. func (c *GenericCataloger) Catalog(resolver scope.Resolver) ([]pkg.Package, error) { fileSelection := c.selectFiles(resolver) contents, err := resolver.MultipleFileContentsByRef(fileSelection...) @@ -88,7 +89,7 @@ func (c *GenericCataloger) selectFiles(resolver scope.FileResolver) []file.Refer return c.selectedFiles } -// Catalog takes a set of file contents and uses any configured parser functions to resolve and return discovered packages +// catalog takes a set of file contents and uses any configured parser functions to resolve and return discovered packages func (c *GenericCataloger) catalog(contents map[file.Reference]string) ([]pkg.Package, error) { defer c.clear() diff --git a/syft/cataloger/common/generic_cataloger_test.go b/syft/cataloger/common/generic_cataloger_test.go index 0478ed7be..39298ec71 100644 --- a/syft/cataloger/common/generic_cataloger_test.go +++ b/syft/cataloger/common/generic_cataloger_test.go @@ -20,6 +20,10 @@ func newTestResolver() *testResolver { } } +func (r *testResolver) FileContentsByRef(_ file.Reference) (string, error) { + return "", fmt.Errorf("not implemented") +} + func (r *testResolver) MultipleFileContentsByRef(_ ...file.Reference) (map[file.Reference]string, error) { return r.contents, nil } diff --git a/syft/scope/resolver.go b/syft/scope/resolver.go index 4a965d30e..74a20783a 100644 --- a/syft/scope/resolver.go +++ b/syft/scope/resolver.go @@ -16,7 +16,9 @@ type Resolver interface { // ContentResolver knows how to get file content for given file.References type ContentResolver interface { + FileContentsByRef(ref file.Reference) (string, error) MultipleFileContentsByRef(f ...file.Reference) (map[file.Reference]string, error) + // TODO: we should consider refactoring to return a set of io.Readers or file.Openers instead of the full contents themselves (allow for optional buffering). } // FileResolver knows how to get file.References for given string paths and globs diff --git a/syft/scope/resolvers/all_layers_resolver.go b/syft/scope/resolvers/all_layers_resolver.go index e14eae8fd..757d8129f 100644 --- a/syft/scope/resolvers/all_layers_resolver.go +++ b/syft/scope/resolvers/all_layers_resolver.go @@ -114,3 +114,9 @@ func (r *AllLayersResolver) FilesByGlob(patterns ...string) ([]file.Reference, e func (r *AllLayersResolver) MultipleFileContentsByRef(f ...file.Reference) (map[file.Reference]string, error) { return r.img.MultipleFileContentsByRef(f...) } + +// FileContentsByRef fetches file contents for a single file reference, irregardless of the source layer. +// If the path does not exist an error is returned. +func (r *AllLayersResolver) FileContentsByRef(ref file.Reference) (string, error) { + return r.img.FileContentsByRef(ref) +} diff --git a/syft/scope/resolvers/directory_resolver.go b/syft/scope/resolvers/directory_resolver.go index b8ed280bc..54db895bc 100644 --- a/syft/scope/resolvers/directory_resolver.go +++ b/syft/scope/resolvers/directory_resolver.go @@ -82,9 +82,20 @@ func (s DirectoryResolver) MultipleFileContentsByRef(f ...file.Reference) (map[f contents, err := fileContents(fileRef.Path) if err != nil { - return refContents, fmt.Errorf("could not read contents of file: %s", fileRef.Path) + return nil, fmt.Errorf("could not read contents of file: %s", fileRef.Path) } refContents[fileRef] = string(contents) } return refContents, nil } + +// FileContentsByRef fetches file contents for a single file reference relative to a directory. +// If the path does not exist an error is returned. +func (s DirectoryResolver) FileContentsByRef(ref file.Reference) (string, error) { + contents, err := fileContents(ref.Path) + if err != nil { + return "", fmt.Errorf("could not read contents of file: %s", ref.Path) + } + + return string(contents), nil +} diff --git a/syft/scope/resolvers/image_squash_resolver.go b/syft/scope/resolvers/image_squash_resolver.go index 3df7efb88..429adff34 100644 --- a/syft/scope/resolvers/image_squash_resolver.go +++ b/syft/scope/resolvers/image_squash_resolver.go @@ -78,3 +78,9 @@ func (r *ImageSquashResolver) FilesByGlob(patterns ...string) ([]file.Reference, func (r *ImageSquashResolver) MultipleFileContentsByRef(f ...file.Reference) (map[file.Reference]string, error) { return r.img.MultipleFileContentsByRef(f...) } + +// FileContentsByRef fetches file contents for a single file reference, irregardless of the source layer. +// If the path does not exist an error is returned. +func (r *ImageSquashResolver) FileContentsByRef(ref file.Reference) (string, error) { + return r.img.FileContentsByRef(ref) +}