From fc39710d06be5599d2d1489a8eb0909813d6c93b Mon Sep 17 00:00:00 2001 From: Christopher Angelo Phillips <32073428+spiffcs@users.noreply.github.com> Date: Fri, 12 Nov 2021 17:57:04 -0500 Subject: [PATCH] 287 - Add retry for image fetch using user's input (#626) * add fallback to user input if source hint fails Signed-off-by: Christopher Angelo Phillips * refactor for smaller functions Signed-off-by: Christopher Angelo Phillips --- syft/source/source.go | 105 +++++++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 42 deletions(-) diff --git a/syft/source/source.go b/syft/source/source.go index 9c798aaff..6f2ec4a86 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -11,6 +11,7 @@ import ( "github.com/anchore/stereoscope" "github.com/anchore/stereoscope/pkg/image" + "github.com/anchore/syft/internal/log" "github.com/spf13/afero" ) @@ -35,55 +36,75 @@ func New(userInput string, registryOptions *image.RegistryOptions) (*Source, fun switch parsedScheme { case FileScheme: - fileMeta, err := fs.Stat(location) - if err != nil { - return &Source{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err) - } - - if fileMeta.IsDir() { - return &Source{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err) - } - - s, err := NewFromFile(location) - if err != nil { - return &Source{}, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err) - } - return &s, func() {}, nil - + return generateFileSource(fs, location) case DirectoryScheme: - fileMeta, err := fs.Stat(location) - if err != nil { - return &Source{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err) - } - - if !fileMeta.IsDir() { - return &Source{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err) - } - - s, err := NewFromDirectory(location) - if err != nil { - return &Source{}, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err) - } - return &s, func() {}, nil - + return generateDirectorySource(fs, location) case ImageScheme: - img, err := stereoscope.GetImageFromSource(location, imageSource, registryOptions) - cleanup := stereoscope.Cleanup - - if err != nil || img == nil { - return &Source{}, cleanup, fmt.Errorf("could not fetch image '%s': %w", location, err) - } - - s, err := NewFromImage(img, location) - if err != nil { - return &Source{}, cleanup, fmt.Errorf("could not populate source with image: %w", err) - } - return &s, cleanup, nil + return generateImageSource(location, userInput, imageSource, registryOptions) } return &Source{}, func() {}, fmt.Errorf("unable to process input for scanning: '%s'", userInput) } +func generateImageSource(location, userInput string, imageSource image.Source, registryOptions *image.RegistryOptions) (*Source, func(), error) { + img, err := stereoscope.GetImageFromSource(location, imageSource, registryOptions) + if err != nil { + log.Debugf("error parsing location: %s after detecting scheme; pulling image: %s", location, userInput) + // we may have been to aggressive reading the source hint + // try the input as supplied by the user if our initial parse failed + img, err = stereoscope.GetImageFromSource(userInput, imageSource, registryOptions) + } + + cleanup := stereoscope.Cleanup + + if err != nil || img == nil { + return &Source{}, cleanup, fmt.Errorf("could not fetch image '%s': %w", location, err) + } + + s, err := NewFromImage(img, location) + if err != nil { + return &Source{}, cleanup, fmt.Errorf("could not populate source with image: %w", err) + } + + return &s, cleanup, nil +} + +func generateDirectorySource(fs afero.Fs, location string) (*Source, func(), error) { + fileMeta, err := fs.Stat(location) + if err != nil { + return &Source{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err) + } + + if !fileMeta.IsDir() { + return &Source{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err) + } + + s, err := NewFromDirectory(location) + if err != nil { + return &Source{}, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err) + } + + return &s, func() {}, nil +} + +func generateFileSource(fs afero.Fs, location string) (*Source, func(), error) { + fileMeta, err := fs.Stat(location) + if err != nil { + return &Source{}, func() {}, fmt.Errorf("unable to stat dir=%q: %w", location, err) + } + + if fileMeta.IsDir() { + return &Source{}, func() {}, fmt.Errorf("given path is not a directory (path=%q): %w", location, err) + } + + s, err := NewFromFile(location) + if err != nil { + return &Source{}, func() {}, fmt.Errorf("could not populate source from path=%q: %w", location, err) + } + + return &s, func() {}, nil +} + // NewFromDirectory creates a new source object tailored to catalog a given filesystem directory recursively. func NewFromDirectory(path string) (Source, error) { return Source{