From f7c36ce0f9473bd065d143f3f7c1015c02bcaf08 Mon Sep 17 00:00:00 2001 From: Nick Craig-Wood Date: Fri, 17 Jun 2022 14:34:57 +0100 Subject: [PATCH] s3: unwrap SDK errors to reveal underlying errors on upload The SDK doesn't wrap errors in a Go standard way so they can't be unwrapped and tested for - eg fatal error. The code looks for a Serialization or RequestError and returns the unwrapped underlying error if possible. This fixes the fs/operations integration tests checking for fatal errors being returned. --- backend/s3/s3.go | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/backend/s3/s3.go b/backend/s3/s3.go index 4e2a15cde..c81024ce3 100644 --- a/backend/s3/s3.go +++ b/backend/s3/s3.go @@ -4125,6 +4125,29 @@ func (o *Object) uploadMultipart(ctx context.Context, req *s3.PutObjectInput, si return etag, nil } +// unWrapAwsError unwraps AWS errors, looking for a non AWS error +// +// It returns true if one was found and the error, or false and the +// error passed in. +func unWrapAwsError(err error) (found bool, outErr error) { + if awsErr, ok := err.(awserr.Error); ok { + var origErrs []error + if batchErr, ok := awsErr.(awserr.BatchError); ok { + origErrs = batchErr.OrigErrs() + } else { + origErrs = []error{awsErr.OrigErr()} + } + for _, origErr := range origErrs { + found, newErr := unWrapAwsError(origErr) + if found { + return found, newErr + } + } + return false, err + } + return true, err +} + // Upload a single part using PutObject func (o *Object) uploadSinglepartPutObject(ctx context.Context, req *s3.PutObjectInput, size int64, in io.Reader) (etag string, lastModified time.Time, err error) { r, resp := o.fs.c.PutObjectRequest(req) @@ -4142,6 +4165,17 @@ func (o *Object) uploadSinglepartPutObject(ctx context.Context, req *s3.PutObjec return o.fs.shouldRetry(ctx, err) }) if err != nil { + // Return the underlying error if we have a + // Serialization or RequestError error if possible + // + // These errors are synthesized locally in the SDK + // (not returned from the server) and we'd rather have + // the underlying error if there is one. + if do, ok := err.(awserr.Error); ok && (do.Code() == request.ErrCodeSerialization || do.Code() == request.ErrCodeRequestError) { + if found, newErr := unWrapAwsError(err); found { + err = newErr + } + } return etag, lastModified, err } lastModified = time.Now()