diff --git a/.gitignore b/.gitignore index d2252584..5e40ee9d 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ bin/ /snapshot /.tool /.task +.mise.toml # changelog generation CHANGELOG.md diff --git a/pkg/image/containerd/daemon_provider.go b/pkg/image/containerd/daemon_provider.go index d0c1d20b..85374d08 100644 --- a/pkg/image/containerd/daemon_provider.go +++ b/pkg/image/containerd/daemon_provider.go @@ -354,27 +354,28 @@ func validatePlatform(expected *image.Platform, given *platforms.Platform) error } if given == nil { - return newErrFetchingImage(fmt.Errorf("image has no platform information (might be a manifest list)")) + return newErrPlatformMismatch(expected, fmt.Errorf("image has no platform information (might be a manifest list)")) } if given.OS != expected.OS { - return newErrFetchingImage(fmt.Errorf("image has unexpected OS %q, which differs from the user specified PS %q", given.OS, expected.OS)) + return newErrPlatformMismatch(expected, fmt.Errorf("image has unexpected OS %q, which differs from the user specified PS %q", given.OS, expected.OS)) } if given.Architecture != expected.Architecture { - return newErrFetchingImage(fmt.Errorf("image has unexpected architecture %q, which differs from the user specified architecture %q", given.Architecture, expected.Architecture)) + return newErrPlatformMismatch(expected, fmt.Errorf("image has unexpected architecture %q, which differs from the user specified architecture %q", given.Architecture, expected.Architecture)) } if given.Variant != expected.Variant { - return newErrFetchingImage(fmt.Errorf("image has unexpected architecture %q, which differs from the user specified architecture %q", given.Variant, expected.Variant)) + return newErrPlatformMismatch(expected, fmt.Errorf("image has unexpected architecture %q, which differs from the user specified architecture %q", given.Variant, expected.Variant)) } return nil } -func newErrFetchingImage(err error) *image.ErrFetchingImage { - return &image.ErrFetchingImage{ - Reason: err.Error(), +func newErrPlatformMismatch(expected *image.Platform, err error) *image.ErrPlatformMismatch { + return &image.ErrPlatformMismatch{ + ExpectedPlatform: expected.String(), + Err: err, } } diff --git a/pkg/image/containerd/daemon_provider_test.go b/pkg/image/containerd/daemon_provider_test.go index df06718a..ad374538 100644 --- a/pkg/image/containerd/daemon_provider_test.go +++ b/pkg/image/containerd/daemon_provider_test.go @@ -97,7 +97,7 @@ func Test_exportPlatformComparer(t *testing.T) { func TestValidatePlatform(t *testing.T) { isFetchError := func(t require.TestingT, err error, args ...interface{}) { - var pErr *image.ErrFetchingImage + var pErr *image.ErrPlatformMismatch require.ErrorAs(t, err, &pErr) } diff --git a/pkg/image/docker/daemon_provider.go b/pkg/image/docker/daemon_provider.go index d508241f..b6468af1 100644 --- a/pkg/image/docker/daemon_provider.go +++ b/pkg/image/docker/daemon_provider.go @@ -165,7 +165,12 @@ type emitter interface { func handlePullEvent(status emitter, event *pullEvent) error { if event.Error != "" { - return &image.ErrFetchingImage{Reason: event.Error} + if strings.Contains(event.Error, "does not match the specified platform") { + return &image.ErrPlatformMismatch{ + Err: errors.New(event.Error), + } + } + return errors.New(event.Error) } // check for the last two events indicating the pull is complete diff --git a/pkg/image/docker/daemon_provider_test.go b/pkg/image/docker/daemon_provider_test.go index caecf267..9937497a 100644 --- a/pkg/image/docker/daemon_provider_test.go +++ b/pkg/image/docker/daemon_provider_test.go @@ -110,7 +110,20 @@ func TestHandlePullEventWithMockEmitter(t *testing.T) { }, expectOnEvent: false, assertFunc: func(t require.TestingT, err error, args ...interface{}) { - var pErr *image.ErrFetchingImage + require.Error(t, err) + var pErr *image.ErrPlatformMismatch + require.NotErrorAs(t, err, &pErr) + }, + }, + { + name: "platform error in event", + event: &pullEvent{ + Error: "image with reference anchore/test_images:golang was found but its platform (linux/amd64) does not match the specified platform (linux/arm64)", + }, + expectOnEvent: false, + assertFunc: func(t require.TestingT, err error, args ...interface{}) { + require.Error(t, err) + var pErr *image.ErrPlatformMismatch require.ErrorAs(t, err, &pErr) }, }, diff --git a/pkg/image/oci/registry_provider.go b/pkg/image/oci/registry_provider.go index 7ee7ca09..e3da6e84 100644 --- a/pkg/image/oci/registry_provider.go +++ b/pkg/image/oci/registry_provider.go @@ -130,25 +130,26 @@ func validatePlatform(platform *image.Platform, givenOs, givenArch string) error return nil } if givenArch == "" || givenOs == "" { - return newErrFetchingImage(fmt.Errorf("missing architecture or OS from image config when user specified platform=%q", platform.String())) + return newErrPlatformMismatch(platform, fmt.Errorf("missing architecture or OS from image config when user specified platform=%q", platform.String())) } platformStr := fmt.Sprintf("%s/%s", givenOs, givenArch) actualPlatform, err := containerregistryV1.ParsePlatform(platformStr) if err != nil { - return newErrFetchingImage(fmt.Errorf("failed to parse platform from image config: %w", err)) + return newErrPlatformMismatch(platform, fmt.Errorf("failed to parse platform from image config: %w", err)) } if actualPlatform == nil { - return newErrFetchingImage(fmt.Errorf("not platform from image config (from %q)", platformStr)) + return newErrPlatformMismatch(platform, fmt.Errorf("not platform from image config (from %q)", platformStr)) } if !matchesPlatform(*actualPlatform, *toContainerRegistryPlatform(platform)) { - return newErrFetchingImage(fmt.Errorf("image platform=%q does not match user specified platform=%q", actualPlatform.String(), platform.String())) + return newErrPlatformMismatch(platform, fmt.Errorf("image platform=%q does not match user specified platform=%q", actualPlatform.String(), platform.String())) } return nil } -func newErrFetchingImage(err error) *image.ErrFetchingImage { - return &image.ErrFetchingImage{ - Reason: err.Error(), +func newErrPlatformMismatch(platform *image.Platform, err error) *image.ErrPlatformMismatch { + return &image.ErrPlatformMismatch{ + ExpectedPlatform: platform.String(), + Err: err, } } diff --git a/pkg/image/oci/registry_provider_test.go b/pkg/image/oci/registry_provider_test.go index 811e7d38..f0e19e03 100644 --- a/pkg/image/oci/registry_provider_test.go +++ b/pkg/image/oci/registry_provider_test.go @@ -26,7 +26,7 @@ import ( func TestValidatePlatform(t *testing.T) { isFetchError := func(t require.TestingT, err error, args ...interface{}) { - var pErr *image.ErrFetchingImage + var pErr *image.ErrPlatformMismatch require.ErrorAs(t, err, &pErr) } tests := []struct { diff --git a/pkg/image/provider.go b/pkg/image/provider.go index a94bb909..9242bf39 100644 --- a/pkg/image/provider.go +++ b/pkg/image/provider.go @@ -5,17 +5,22 @@ import ( "fmt" ) -// ErrFetchingImage is meant to be used when a provider has positively resolved the image but, while fetching the -// image, an error occurred. The goal is to differentiate between a provider that cannot resolve an image (thus -// if the caller has a set of providers, it can try another provider) and a provider that can resolve an image but -// there is an unresolvable problem (e.g. network error, mismatched architecture, etc... thus the caller should -// not try any further providers). -type ErrFetchingImage struct { - Reason string +// ErrPlatformMismatch is meant to be used when a provider has positively resolved the image but the image OS or +// architecture does not match with what was requested. +type ErrPlatformMismatch struct { + ExpectedPlatform string + Err error } -func (e *ErrFetchingImage) Error() string { - return fmt.Sprintf("error fetching image: %s", e.Reason) +func (e *ErrPlatformMismatch) Error() string { + if e.ExpectedPlatform == "" { + return fmt.Sprintf("mismatched platform: %v", e.Err) + } + return fmt.Sprintf("mismatched platform (expected %v): %v", e.ExpectedPlatform, e.Err) +} + +func (e *ErrPlatformMismatch) Unwrap() error { + return e.Err } // Provider is an abstraction for any object that provides image objects (e.g. the docker daemon API, a tar file of