From 7de5e1288f769d29fc4b6190658020a00f933021 Mon Sep 17 00:00:00 2001 From: Dan Luhring Date: Wed, 22 Dec 2021 13:53:23 -0500 Subject: [PATCH] Fix unhelpful error message for oci-archive scheme (#705) * Improve error message Signed-off-by: Dan Luhring * Return error from stereoscope immediately Signed-off-by: Dan Luhring * Bump version of stereoscope Signed-off-by: Dan Luhring * Conditionally retry image retrieval Signed-off-by: Dan Luhring * Update error message for source construction failure Signed-off-by: Dan Luhring * Update stereoscope Signed-off-by: Dan Luhring * Retry image pull without predetermined image source Signed-off-by: Dan Luhring * Add comment to image pull source determination Signed-off-by: Dan Luhring --- cmd/packages.go | 2 +- go.mod | 2 +- go.sum | 4 +-- syft/source/source.go | 63 ++++++++++++++++++++++++++++++++++--------- 4 files changed, 55 insertions(+), 16 deletions(-) diff --git a/cmd/packages.go b/cmd/packages.go index 182daaef3..f68f131ee 100644 --- a/cmd/packages.go +++ b/cmd/packages.go @@ -262,7 +262,7 @@ func packagesExecWorker(userInput string) <-chan error { src, cleanup, err := source.New(userInput, appConfig.Registry.ToOptions(), appConfig.Exclusions) if err != nil { - errs <- fmt.Errorf("failed to determine image source: %w", err) + errs <- fmt.Errorf("failed to construct source from user input %q: %w", userInput, err) return } if cleanup != nil { diff --git a/go.mod b/go.mod index 207342e57..bdb41703b 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/anchore/go-testutils v0.0.0-20200925183923-d5f45b0d3c04 github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29 - github.com/anchore/stereoscope v0.0.0-20211203160213-5a5e323a5c89 + github.com/anchore/stereoscope v0.0.0-20211222141827-6e663afeef5d // we are hinting brotli to latest due to warning when installing archiver v3: // go: warning: github.com/andybalholm/brotli@v1.0.1: retracted by module author: occasional panics and data corruption github.com/andybalholm/brotli v1.0.4 // indirect diff --git a/go.sum b/go.sum index 1d40a165e..1698f8483 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,8 @@ github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b h1:e1bmaoJfZV github.com/anchore/go-version v1.2.2-0.20200701162849-18adb9c92b9b/go.mod h1:Bkc+JYWjMCF8OyZ340IMSIi2Ebf3uwByOk6ho4wne1E= github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29 h1:K9LfnxwhqvihqU0+MF325FNy7fsKV9EGaUxdfR4gnWk= github.com/anchore/packageurl-go v0.0.0-20210922164639-b3fa992ebd29/go.mod h1:Oc1UkGaJwY6ND6vtAqPSlYrptKRJngHwkwB6W7l1uP0= -github.com/anchore/stereoscope v0.0.0-20211203160213-5a5e323a5c89 h1:pcMFN7wjIQIxdZm8NA0R3JiRRFn5Eyx9mhagHOVS4Bw= -github.com/anchore/stereoscope v0.0.0-20211203160213-5a5e323a5c89/go.mod h1:FNm1rtauEjkC3elA3jr3bJkU+/4QiovApwJdPnHQ9x0= +github.com/anchore/stereoscope v0.0.0-20211222141827-6e663afeef5d h1:ggdC5SRb8rAXmBkwxah8gfSxwpoAVcpp12tQ+XbDi20= +github.com/anchore/stereoscope v0.0.0-20211222141827-6e663afeef5d/go.mod h1:FNm1rtauEjkC3elA3jr3bJkU+/4QiovApwJdPnHQ9x0= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= diff --git a/syft/source/source.go b/syft/source/source.go index 58fbe0e4c..236f70315 100644 --- a/syft/source/source.go +++ b/syft/source/source.go @@ -51,7 +51,7 @@ func New(userInput string, registryOptions *image.RegistryOptions, exclusions [] case DirectoryScheme: source, cleanupFn, err = generateDirectorySource(fs, location) case ImageScheme: - source, cleanupFn, err = generateImageSource(location, userInput, imageSource, registryOptions) + source, cleanupFn, err = generateImageSource(userInput, location, imageSource, registryOptions) default: err = fmt.Errorf("unable to process input for scanning: '%s'", userInput) } @@ -63,17 +63,8 @@ func New(userInput string, registryOptions *image.RegistryOptions, exclusions [] return source, cleanupFn, err } -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 - +func generateImageSource(userInput, location string, imageSource image.Source, registryOptions *image.RegistryOptions) (*Source, func(), error) { + img, cleanup, err := getImageWithRetryStrategy(userInput, location, imageSource, registryOptions) if err != nil || img == nil { return &Source{}, cleanup, fmt.Errorf("could not fetch image '%s': %w", location, err) } @@ -86,6 +77,54 @@ func generateImageSource(location, userInput string, imageSource image.Source, r return &s, cleanup, nil } +func parseScheme(userInput string) string { + parts := strings.SplitN(userInput, ":", 2) + if len(parts) < 2 { + return "" + } + + return parts[0] +} + +func getImageWithRetryStrategy(userInput, location string, imageSource image.Source, registryOptions *image.RegistryOptions) (*image.Image, func(), error) { + img, err := stereoscope.GetImageFromSource(location, imageSource, registryOptions) + if err == nil { + // Success on the first try! + return img, stereoscope.Cleanup, nil + } + + scheme := parseScheme(userInput) + if !(scheme == "docker" || scheme == "registry") { + // Image retrieval failed, and we shouldn't retry it. It's most likely that the + // user _did_ intend the parsed scheme, but there was a legitimate failure with + // using the scheme to load the image. Alert the user to this failure, so they + // can fix the problem. + return nil, nil, err + } + + // Maybe the user wanted "docker" or "registry" to refer to an _image name_ + // (e.g. "docker:latest"), not a scheme. We'll retry image retrieval with this + // alternative interpretation, in an attempt to avoid unnecessary user friction. + + log.Warnf( + "scheme %q specified, but it coincides with a common image name; re-examining user input %q"+ + " without scheme parsing because image retrieval using scheme parsing was unsuccessful: %v", + scheme, + userInput, + err, + ) + + // We need to determine the image source again, such that this determination + // doesn't take scheme parsing into account. + imageSource = image.DetermineImagePullSource(userInput) + img, err = stereoscope.GetImageFromSource(userInput, imageSource, registryOptions) + if err != nil { + return nil, nil, err + } + + return img, stereoscope.Cleanup, nil +} + func generateDirectorySource(fs afero.Fs, location string) (*Source, func(), error) { fileMeta, err := fs.Stat(location) if err != nil {