Handle error gracefully when we can't retrieve an image#251
Handle error gracefully when we can't retrieve an image#251nkubala merged 6 commits intoGoogleContainerTools:masterfrom
Conversation
bobcatfish
left a comment
There was a problem hiding this comment.
Thanks for jumping on this so quickly @nkubala!
I wonder if there is any chance to add some test coverage in the process :D
cmd/diff.go
Outdated
| defer wg.Done() | ||
| image, err := getImageForName(imageName) | ||
| if image.Image == nil { | ||
| errChan <- fmt.Errorf("error retrieving image %s: %s", imageName, err.Error()) |
There was a problem hiding this comment.
I'm interested in why call err.Error() in this case vs. passing err to be formatted as '%s'
There was a problem hiding this comment.
technically the format types don't match (string vs error), but this seems to work so I'll change it back
cmd/diff.go
Outdated
| func processImage(imageName string, imageMap map[string]*pkgutil.Image, wg *sync.WaitGroup, errChan chan<- error) { | ||
| defer wg.Done() | ||
| image, err := getImageForName(imageName) | ||
| if image.Image == nil { |
There was a problem hiding this comment.
2 questions:
- do you want to propagate the error to
errChananytimeerris notnil? e.g.if err != nil || image.Image == nil - what if
image.Imageis nil ANDerris nil too? (callingerr.Error()on it)
There was a problem hiding this comment.
Ok this logic isn't great so I'm gonna rework it a bit. Digging into the go-containerregistry code, if we get an error returned back from any of the three sources (tar/daemon/remote), the image returned will always be nil. So it's equivalent to just change this back to a vanilla if err != nil check.
The reason I was checking for image.Image == nil is to avoid putting the image into the returned map so we didn't attempt to clean up an empty path, but that logic is already happening in pkgutil.CleanupImage so this is safe. I moved the deferred calls up before we return the errors, and we're good.
TLDR answers for your questions: 1) yes, and 2) not possible
| return errors.New("Please include --types=file with the --filename flag") | ||
| } | ||
|
|
||
| func processImage(imageName string, imageMap map[string]*pkgutil.Image, wg *sync.WaitGroup, errChan chan<- error) { |
There was a problem hiding this comment.
Is there a name for this function that might be a bit more specific to what this function is doing? I wasn't sure what processImage meant when I was reading this.
It seems like the purpose of this function is to look up sometthing about the image (im not sure what tho - are we actually getting the bytes of the image itself, or looking up some metadata or something else?) and put that something into imageMap
So some other ideas:
AddImageToMapGetMetadataForImageFindImageSourceDownloadImage
(You might have some other name ideas tho since you probably know what exactly getImageForName is doing)
There was a problem hiding this comment.
This function is essentially just a concurrent wrapper around getImageForName, which retrieves a reference to an image and a reader for the actual bytes of the image. I can change this to getImage (the imageMap isn't really a useful struct here so I don't want to refer to it), but the reason I called it processImage is because getImageForName actually does a bit of image processing, i.e. unpacks the image filesystem to the local filesystem. I should probably just add some comments explaining what each function does :)
cmd/diff.go
Outdated
| img2, ok := imageMap[image2Arg] | ||
| if !ok { | ||
| return fmt.Errorf("cannot find image %s", image2Arg) | ||
| } |
There was a problem hiding this comment.
what would it mean if processImage returned for both images without errors, but one or both of these checks failed?
There was a problem hiding this comment.
removed these since this logic is duplicated elsewhere
cmd/diff.go
Outdated
|
|
||
| if len(errs) > 0 { | ||
| return errors.New(strings.Join(errs, "\n")) | ||
| } |
There was a problem hiding this comment.
this logic to collect together errors into one error seems like a good candidate for being moved into a separate function
| if test.expected_output { | ||
| t.Errorf("Got unexpected error: %s", err) | ||
| } else { | ||
| if (err == nil) != test.shouldError { |
There was a problem hiding this comment.
nice, shouldError is a way better name!
| defer pkgutil.CleanupImage(*imageMap[image2Arg]) | ||
| } | ||
|
|
||
| if err := readErrorsFromChannel(errChan); err != 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. |

This fixes the error handling for processing images before we do the diff or analysis. Errors are propagated back through a channel to the main loop, which will only execute the diff/analysis if we didn't receive any errors back from the image processing. Error handling also happens in the main loop now, so we'll get consistent results across diff types.
This also changes a bunch of error messages to add some more context when we do receive an error.
Fixes #250