-
Notifications
You must be signed in to change notification settings - Fork 236
Handle error gracefully when we can't retrieve an image #251
Conversation
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 questions:
- do you want to propagate the error to
errChan
anytimeerr
is notnil
? e.g.if err != nil || image.Image == nil
- what if
image.Image
is nil ANDerr
is nil too? (callingerr.Error()
on it)
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.
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
cmd/diff.go
Outdated
@@ -69,33 +70,60 @@ func checkFilenameFlag(_ []string) error { | |||
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
AddImageToMap
GetMetadataForImage
FindImageSource
DownloadImage
(You might have some other name ideas tho since you probably know what exactly getImageForName
is doing)
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.
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
}(imageArg, imageMap) | ||
img2, ok := imageMap[image2Arg] | ||
if !ok { | ||
return fmt.Errorf("cannot find image %s", image2Arg) | ||
} |
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.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
removed these since this logic is duplicated elsewhere
|
||
if len(errs) > 0 { | ||
return errors.New(strings.Join(errs, "\n")) | ||
} |
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.
this logic to collect together errors into one error seems like a good candidate for being moved into a separate function
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.
agreed, done
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.
if test.expected_output { | ||
t.Errorf("Got unexpected error: %s", err) | ||
} else { | ||
if (err == nil) != test.shouldError { |
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!
|
||
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 comment
The reason will be displayed to describe this comment to others. Learn more.
niiiiiiiice
@@ -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 comment
The reason will be displayed to describe this comment to others. Learn more.
ty ty, makes sense now!
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