github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/file/s3file/awserr.go (about) 1 package s3file 2 3 import ( 4 "fmt" 5 6 "github.com/aws/aws-sdk-go/aws/awserr" 7 awsrequest "github.com/aws/aws-sdk-go/aws/request" 8 "github.com/aws/aws-sdk-go/service/s3" 9 "github.com/Schaudge/grailbase/errors" 10 ) 11 12 // Annotate interprets err as an AWS request error and returns a version of it 13 // annotated with severity and kind from the errors package. The optional args 14 // are passed to errors.E. 15 func annotate(err error, ids s3RequestIDs, retry *retryPolicy, args ...interface{}) error { 16 e := func(prefixArgs ...interface{}) error { 17 msgs := append(prefixArgs, args...) 18 msgs = append(msgs, "awsrequestID:", ids.String()) 19 if retry.waitErr != nil { 20 msgs = append(msgs, fmt.Sprintf("[waitErr=%v]", retry.waitErr)) 21 } 22 msgs = append(msgs, fmt.Sprintf("[retries=%d, start=%v]", retry.retries, retry.startTime)) 23 return errors.E(msgs...) 24 } 25 aerr, ok := getAWSError(err) 26 if !ok { 27 return e(err) 28 } 29 if awsrequest.IsErrorThrottle(err) { 30 return e(err, errors.Temporary, errors.Unavailable) 31 } 32 if awsrequest.IsErrorRetryable(err) { 33 return e(err, errors.Temporary) 34 } 35 // The underlying error was an S3 error. Try to classify it. 36 // Best guess based on Amazon's descriptions: 37 switch aerr.Code() { 38 // Code NotFound is not documented, but it's what the API actually returns. 39 case s3.ErrCodeNoSuchBucket, s3.ErrCodeNoSuchKey, "NoSuchVersion", "NotFound": 40 return e(err, errors.NotExist) 41 case awsrequest.CanceledErrorCode: 42 return e(err, errors.Canceled) 43 case "AccessDenied": 44 return e(err, errors.NotAllowed) 45 case "InvalidRequest", "InvalidArgument", "EntityTooSmall", "EntityTooLarge", "KeyTooLong", "MethodNotAllowed": 46 return e(err, errors.Fatal) 47 case "ExpiredToken", "AccountProblem", "ServiceUnavailable", "TokenRefreshRequired", "OperationAborted": 48 return e(err, errors.Unavailable) 49 case "PreconditionFailed": 50 return e(err, errors.Precondition) 51 case "SlowDown": 52 return e(errors.Temporary, errors.Unavailable) 53 } 54 return e(err) 55 } 56 57 func getAWSError(err error) (awsError awserr.Error, found bool) { 58 errors.Visit(err, func(err error) { 59 if err == nil || awsError != nil { 60 return 61 } 62 if e, ok := err.(awserr.Error); ok { 63 found = true 64 awsError = e 65 } 66 }) 67 return 68 } 69 70 type s3RequestIDs struct { 71 amzRequestID string 72 amzID2 string 73 } 74 75 func (ids s3RequestIDs) String() string { 76 return fmt.Sprintf("x-amz-request-id: %s, x-amz-id-2: %s", ids.amzRequestID, ids.amzID2) 77 } 78 79 // This is the same as awsrequest.WithGetResponseHeader, except that it doesn't 80 // crash when the request fails w/o receiving an HTTP response. 81 // 82 // TODO(saito) Revert once awsrequest.WithGetResponseHeaders starts acting more 83 // gracefully. 84 func withGetResponseHeaderWithNilCheck(key string, val *string) awsrequest.Option { 85 return func(r *awsrequest.Request) { 86 r.Handlers.Complete.PushBack(func(req *awsrequest.Request) { 87 *val = "(no HTTP response)" 88 if req.HTTPResponse != nil && req.HTTPResponse.Header != nil { 89 *val = req.HTTPResponse.Header.Get(key) 90 } 91 }) 92 } 93 } 94 95 func (ids *s3RequestIDs) captureOption() awsrequest.Option { 96 h0 := withGetResponseHeaderWithNilCheck("x-amz-request-id", &ids.amzRequestID) 97 h1 := withGetResponseHeaderWithNilCheck("x-amz-id-2", &ids.amzID2) 98 return func(r *awsrequest.Request) { 99 h0(r) 100 h1(r) 101 } 102 }