This repository was archived by the owner on Mar 27, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 237
Handle error gracefully when we can't retrieve an image #251
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
8621bc5
Handle error gracefully when we can't retrieve an image
nkubala ee51ff8
fix channel read
nkubala 6a7a550
improve error handling logic when retrieving images
nkubala 7024d3b
move error combining logic into helper function
nkubala 265b622
add comments for functions
nkubala 9c47911
add tests
nkubala File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,15 +17,16 @@ limitations under the License. | |
package cmd | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"strings" | ||
"sync" | ||
|
||
"github.com/GoogleContainerTools/container-diff/cmd/util/output" | ||
"github.com/GoogleContainerTools/container-diff/differs" | ||
pkgutil "github.com/GoogleContainerTools/container-diff/pkg/util" | ||
"github.com/GoogleContainerTools/container-diff/util" | ||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
) | ||
|
@@ -66,50 +67,74 @@ func checkFilenameFlag(_ []string) error { | |
return nil | ||
} | ||
} | ||
return errors.New("Please include --types=file with the --filename flag") | ||
return errors.New("please include --types=file with the --filename flag") | ||
} | ||
|
||
// processImage is a concurrency-friendly wrapper around getImageForName | ||
func processImage(imageName string, imageMap map[string]*pkgutil.Image, wg *sync.WaitGroup, errChan chan<- error) { | ||
defer wg.Done() | ||
image, err := getImageForName(imageName) | ||
if err != nil { | ||
errChan <- fmt.Errorf("error retrieving image %s: %s", imageName, err) | ||
} | ||
imageMap[imageName] = &image | ||
} | ||
|
||
// collects errors from a channel and combines them | ||
// assumes channel has already been closed | ||
func readErrorsFromChannel(c chan error) error { | ||
errs := []string{} | ||
for { | ||
err, ok := <-c | ||
if !ok { | ||
break | ||
} | ||
errs = append(errs, err.Error()) | ||
} | ||
|
||
if len(errs) > 0 { | ||
return errors.New(strings.Join(errs, "\n")) | ||
} | ||
return nil | ||
} | ||
|
||
func diffImages(image1Arg, image2Arg string, diffArgs []string) error { | ||
diffTypes, err := differs.GetAnalyzers(diffArgs) | ||
if err != nil { | ||
return err | ||
return errors.Wrap(err, "getting analyzers") | ||
} | ||
|
||
var wg sync.WaitGroup | ||
wg.Add(2) | ||
|
||
logrus.Infof("starting diff on images %s and %s, using differs: %s\n", image1Arg, image2Arg, diffArgs) | ||
|
||
imageMap := map[string]*pkgutil.Image{ | ||
image1Arg: {}, | ||
image2Arg: {}, | ||
} | ||
// TODO: fix error handling here | ||
for imageArg := range imageMap { | ||
go func(imageName string, imageMap map[string]*pkgutil.Image) { | ||
defer wg.Done() | ||
image, err := getImageForName(imageName) | ||
imageMap[imageName] = &image | ||
if err != nil { | ||
logrus.Warningf("Diff may be inaccurate: %s", err) | ||
} | ||
}(imageArg, imageMap) | ||
} | ||
imageMap := map[string]*pkgutil.Image{} | ||
errChan := make(chan error, 2) | ||
|
||
go processImage(image1Arg, imageMap, &wg, errChan) | ||
go processImage(image2Arg, imageMap, &wg, errChan) | ||
|
||
wg.Wait() | ||
close(errChan) | ||
|
||
if noCache && !save { | ||
defer pkgutil.CleanupImage(*imageMap[image1Arg]) | ||
defer pkgutil.CleanupImage(*imageMap[image2Arg]) | ||
} | ||
|
||
if err := readErrorsFromChannel(errChan); err != nil { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. niiiiiiiice |
||
return err | ||
} | ||
|
||
logrus.Info("computing diffs") | ||
req := differs.DiffRequest{ | ||
Image1: *imageMap[image1Arg], | ||
Image2: *imageMap[image2Arg], | ||
DiffTypes: diffTypes} | ||
diffs, err := req.GetDiff() | ||
if err != nil { | ||
return fmt.Errorf("Could not retrieve diff: %s", err) | ||
return fmt.Errorf("could not retrieve diff: %s", err) | ||
} | ||
outputResults(diffs) | ||
|
||
|
@@ -122,7 +147,7 @@ func diffImages(image1Arg, image2Arg string, diffArgs []string) error { | |
} | ||
|
||
if noCache && save { | ||
logrus.Infof("Images were saved at %s and %s", imageMap[image1Arg].FSPath, | ||
logrus.Infof("images were saved at %s and %s", imageMap[image1Arg].FSPath, | ||
imageMap[image2Arg].FSPath) | ||
} | ||
return nil | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -38,6 +38,7 @@ import ( | |
"github.com/GoogleContainerTools/container-diff/util" | ||
"github.com/google/go-containerregistry/pkg/v1" | ||
homedir "github.com/mitchellh/go-homedir" | ||
"github.com/pkg/errors" | ||
"github.com/sirupsen/logrus" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/pflag" | ||
|
@@ -131,6 +132,9 @@ func checkIfValidAnalyzer(_ []string) error { | |
return nil | ||
} | ||
|
||
// getImageForName infers the source of an image and retrieves a v1.Image reference to it. | ||
// Once a reference is obtained, it attempts to unpack the v1.Image's reader's contents | ||
// into a temp directory on the local filesystem. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ty ty, makes sense now! |
||
func getImageForName(imageName string) (pkgutil.Image, error) { | ||
logrus.Infof("retrieving image: %s", imageName) | ||
var img v1.Image | ||
|
@@ -139,17 +143,17 @@ func getImageForName(imageName string) (pkgutil.Image, error) { | |
start := time.Now() | ||
img, err = tarball.ImageFromPath(imageName, nil) | ||
if err != nil { | ||
return pkgutil.Image{}, err | ||
return pkgutil.Image{}, errors.Wrap(err, "retrieving tar from path") | ||
} | ||
elapsed := time.Now().Sub(start) | ||
logrus.Infof("retrieving image from tar took %f seconds", elapsed.Seconds()) | ||
logrus.Infof("retrieving image ref from tar took %f seconds", elapsed.Seconds()) | ||
} else if strings.HasPrefix(imageName, DaemonPrefix) { | ||
// remove the daemon prefix | ||
imageName = strings.Replace(imageName, DaemonPrefix, "", -1) | ||
|
||
ref, err := name.ParseReference(imageName, name.WeakValidation) | ||
if err != nil { | ||
return pkgutil.Image{}, err | ||
return pkgutil.Image{}, errors.Wrap(err, "parsing image reference") | ||
} | ||
|
||
start := time.Now() | ||
|
@@ -158,28 +162,28 @@ func getImageForName(imageName string) (pkgutil.Image, error) { | |
Buffer: true, | ||
}) | ||
if err != nil { | ||
return pkgutil.Image{}, err | ||
return pkgutil.Image{}, errors.Wrap(err, "retrieving image from daemon") | ||
} | ||
elapsed := time.Now().Sub(start) | ||
logrus.Infof("retrieving image from daemon took %f seconds", elapsed.Seconds()) | ||
logrus.Infof("retrieving local image ref took %f seconds", elapsed.Seconds()) | ||
} else { | ||
// either has remote prefix or has no prefix, in which case we force remote | ||
imageName = strings.Replace(imageName, RemotePrefix, "", -1) | ||
ref, err := name.ParseReference(imageName, name.WeakValidation) | ||
if err != nil { | ||
return pkgutil.Image{}, err | ||
return pkgutil.Image{}, errors.Wrap(err, "parsing image reference") | ||
} | ||
auth, err := authn.DefaultKeychain.Resolve(ref.Context().Registry) | ||
if err != nil { | ||
return pkgutil.Image{}, err | ||
return pkgutil.Image{}, errors.Wrap(err, "resolving auth") | ||
} | ||
start := time.Now() | ||
img, err = remote.Image(ref, auth, http.DefaultTransport) | ||
if err != nil { | ||
return pkgutil.Image{}, err | ||
return pkgutil.Image{}, errors.Wrap(err, "retrieving remote image") | ||
} | ||
elapsed := time.Now().Sub(start) | ||
logrus.Infof("retrieving remote image took %f seconds", elapsed.Seconds()) | ||
logrus.Infof("retrieving remote image ref took %f seconds", elapsed.Seconds()) | ||
} | ||
|
||
// create tempdir and extract fs into it | ||
|
@@ -188,7 +192,7 @@ func getImageForName(imageName string) (pkgutil.Image, error) { | |
start := time.Now() | ||
imgLayers, err := img.Layers() | ||
if err != nil { | ||
return pkgutil.Image{}, err | ||
return pkgutil.Image{}, errors.Wrap(err, "getting image layers") | ||
} | ||
for _, layer := range imgLayers { | ||
layerStart := time.Now() | ||
|
@@ -197,12 +201,12 @@ func getImageForName(imageName string) (pkgutil.Image, error) { | |
if err != nil { | ||
return pkgutil.Image{ | ||
Layers: layers, | ||
}, err | ||
}, errors.Wrap(err, "getting extract path for layer") | ||
} | ||
if err := pkgutil.GetFileSystemForLayer(layer, path, nil); err != nil { | ||
return pkgutil.Image{ | ||
Layers: layers, | ||
}, err | ||
}, errors.Wrap(err, "getting filesystem for layer") | ||
} | ||
layers = append(layers, pkgutil.Layer{ | ||
FSPath: path, | ||
|
@@ -215,12 +219,15 @@ func getImageForName(imageName string) (pkgutil.Image, error) { | |
} | ||
|
||
path, err := getExtractPathForImage(imageName, img) | ||
if err != nil { | ||
return pkgutil.Image{}, err | ||
} | ||
// extract fs into provided dir | ||
if err := pkgutil.GetFileSystemForImage(img, path, nil); err != nil { | ||
return pkgutil.Image{ | ||
FSPath: path, | ||
Layers: layers, | ||
}, err | ||
}, errors.Wrap(err, "getting filesystem for image") | ||
} | ||
return pkgutil.Image{ | ||
Image: img, | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice,
shouldError
is a way better name!