diff --git a/http/fetch/archive_fetcher.go b/http/fetch/archive_fetcher.go index d77986b9..98adce87 100644 --- a/http/fetch/archive_fetcher.go +++ b/http/fetch/archive_fetcher.go @@ -63,6 +63,11 @@ type Option func(a *ArchiveFetcher) // ErrFileNotFound is an error type used to signal 404 HTTP status code responses. var ErrFileNotFound = errors.New("file not found") +// ErrDigestMismatch is an error type used to signal that the downloaded content +// doesn't match the expected digest, e.g. because the artifact changed between +// reading the source status and downloading it. +var ErrDigestMismatch = errors.New("computed digest doesn't match") + // WithRetries sets the maximum amount of retries the HTTP client will be allowed to make. func WithRetries(retries int) Option { return func(a *ArchiveFetcher) { @@ -263,6 +268,7 @@ func (r *ArchiveFetcher) copyResponse(dst io.Writer, resp *http.Response) error // FetchWithContext downloads, verifies and extracts the tarball content to the specified directory. // If the file server responds with 5xx errors, the download operation is retried. // If the file server responds with 404, the returned error is of type ErrFileNotFound. +// If the digest of the downloaded content doesn't match, the returned error wraps ErrDigestMismatch. // If the file server is unavailable for more than 3 minutes, the returned error contains the original status code. func (r *ArchiveFetcher) FetchWithContext(ctx context.Context, archiveURL, digest, dir string) (err error) { resp, err := r.download(ctx, archiveURL) @@ -360,7 +366,7 @@ func (r *ArchiveFetcher) verifyDigest(dig string, reader io.Reader) error { return err } if !verifier.Verified() { - return fmt.Errorf("computed digest doesn't match provided '%s' (check whether file size exceeds max download size)", dig) + return fmt.Errorf("%w provided '%s'", ErrDigestMismatch, dig) } return nil } diff --git a/http/fetch/archive_fetcher_test.go b/http/fetch/archive_fetcher_test.go index f1e216bf..dd94fc0a 100644 --- a/http/fetch/archive_fetcher_test.go +++ b/http/fetch/archive_fetcher_test.go @@ -20,6 +20,7 @@ import ( "context" "crypto/tls" "crypto/x509" + "errors" "fmt" "net/http" "os" @@ -140,6 +141,7 @@ func TestArchiveFetcher_FetchWithContext(t *testing.T) { fetchedContentPath: filepath.Join(tmpDir, manifestsFileName), opts: []Option{WithUntar()}, wantErr: true, + wantErrType: ErrDigestMismatch, }, { name: "fails with not found error", @@ -163,7 +165,12 @@ func TestArchiveFetcher_FetchWithContext(t *testing.T) { if tt.wantErr { g.Expect(err).To(HaveOccurred()) if tt.wantErrType != nil { - g.Expect(err).To(Equal(tt.wantErrType)) + g.Expect(err).To(MatchError(tt.wantErrType)) + } + if tt.wantErrType == ErrDigestMismatch { + g.Expect(err.Error()).To(ContainSubstring("computed digest doesn't match provided")) + } else { + g.Expect(errors.Is(err, ErrDigestMismatch)).To(BeFalse()) } } else { g.Expect(err).ToNot(HaveOccurred()) @@ -222,10 +229,11 @@ func TestArchiveFetcher_FetchBytes(t *testing.T) { wantErr: true, }, { - name: "fails to verify the digest", - url: artifactURL, - digest: "sha256:5c234ee52ff0e3dcc8528d6b9383cc235ad13a11658466f29df3be9eda6ee447", - wantErr: true, + name: "fails to verify the digest", + url: artifactURL, + digest: "sha256:5c234ee52ff0e3dcc8528d6b9383cc235ad13a11658466f29df3be9eda6ee447", + wantErr: true, + wantErrType: ErrDigestMismatch, }, { name: "fails with not found error", @@ -245,7 +253,12 @@ func TestArchiveFetcher_FetchBytes(t *testing.T) { if tt.wantErr { g.Expect(err).To(HaveOccurred()) if tt.wantErrType != nil { - g.Expect(err).To(Equal(tt.wantErrType)) + g.Expect(err).To(MatchError(tt.wantErrType)) + } + if tt.wantErrType == ErrDigestMismatch { + g.Expect(err.Error()).To(ContainSubstring("computed digest doesn't match provided")) + } else { + g.Expect(errors.Is(err, ErrDigestMismatch)).To(BeFalse()) } return } @@ -390,6 +403,7 @@ func TestArchiveFetcher_Fetch(t *testing.T) { maxDownloadSize: -1, maxUntarSize: -1, wantErr: true, + wantErrType: ErrDigestMismatch, }, { name: "fails with not found error", @@ -412,7 +426,12 @@ func TestArchiveFetcher_Fetch(t *testing.T) { if tt.wantErr { g.Expect(err).To(HaveOccurred()) if tt.wantErrType != nil { - g.Expect(err).To(Equal(tt.wantErrType)) + g.Expect(err).To(MatchError(tt.wantErrType)) + } + if tt.wantErrType == ErrDigestMismatch { + g.Expect(err.Error()).To(ContainSubstring("computed digest doesn't match provided")) + } else { + g.Expect(errors.Is(err, ErrDigestMismatch)).To(BeFalse()) } } else { g.Expect(err).ToNot(HaveOccurred())