github.com/Schaudge/grailbase@v0.0.0-20240223061707-44c758a471c0/s3util/s3error.go (about) 1 package s3util 2 3 import ( 4 "context" 5 6 "github.com/Schaudge/grailbase/errors" 7 8 "github.com/aws/aws-sdk-go/aws/awserr" 9 "github.com/aws/aws-sdk-go/aws/request" 10 "github.com/aws/aws-sdk-go/service/s3" 11 ) 12 13 // CtxErr will return the context's error (if any) or the other error. 14 // This is particularly useful to interpret AWS S3 API call errors 15 // because AWS sometimes wraps context errors (context.Canceled or context.DeadlineExceeded). 16 func CtxErr(ctx context.Context, other error) error { 17 if ctx.Err() != nil { 18 return ctx.Err() 19 } 20 return other 21 } 22 23 // KindAndSeverity interprets a given error and returns errors.Severity. 24 // This is particularly useful to interpret AWS S3 API call errors. 25 func Severity(err error) errors.Severity { 26 if aerr, ok := err.(awserr.Error); ok { 27 _, severity := KindAndSeverity(aerr) 28 return severity 29 } 30 if re := errors.Recover(err); re != nil { 31 return re.Severity 32 } 33 return errors.Unknown 34 } 35 36 // KindAndSeverity interprets a given error and returns errors.Kind and errors.Severity. 37 // This is particularly useful to interpret AWS S3 API call errors. 38 func KindAndSeverity(err error) (errors.Kind, errors.Severity) { 39 for { 40 if request.IsErrorThrottle(err) { 41 return errors.ResourcesExhausted, errors.Temporary 42 } 43 if request.IsErrorRetryable(err) { 44 return errors.Other, errors.Temporary 45 } 46 aerr, ok := err.(awserr.Error) 47 if !ok { 48 break 49 } 50 if aerr.Code() == request.CanceledErrorCode { 51 return errors.Canceled, errors.Fatal 52 } 53 // The underlying error was an S3 error. Try to classify it. 54 // Best guess based on Amazon's descriptions: 55 switch aerr.Code() { 56 // Code NotFound is not documented, but it's what the API actually returns. 57 case s3.ErrCodeNoSuchBucket, "NoSuchVersion", "NotFound": 58 return errors.NotExist, errors.Fatal 59 case s3.ErrCodeNoSuchKey: 60 // Treat as temporary because sometimes they are, due to S3's eventual consistency model 61 // https://aws.amazon.com/premiumsupport/knowledge-center/404-error-nosuchkey-s3/ 62 return errors.NotExist, errors.Temporary 63 case "AccessDenied": 64 return errors.NotAllowed, errors.Fatal 65 case "InvalidRequest", "InvalidArgument", "EntityTooSmall", "EntityTooLarge", "KeyTooLong", "MethodNotAllowed": 66 return errors.Invalid, errors.Fatal 67 case "ExpiredToken", "AccountProblem", "ServiceUnavailable", "TokenRefreshRequired", "OperationAborted": 68 return errors.Unavailable, errors.Fatal 69 case "PreconditionFailed": 70 return errors.Precondition, errors.Fatal 71 case "SlowDown": 72 return errors.ResourcesExhausted, errors.Temporary 73 case "BadRequest": 74 return errors.Other, errors.Temporary 75 case "InternalError": 76 // AWS recommends retrying InternalErrors: 77 // https://aws.amazon.com/premiumsupport/knowledge-center/s3-resolve-200-internalerror/ 78 // https://aws.amazon.com/premiumsupport/knowledge-center/http-5xx-errors-s3/ 79 return errors.Other, errors.Retriable 80 case "XAmzContentSHA256Mismatch": 81 // Example: 82 // 83 // XAmzContentSHA256Mismatch: The provided 'x-amz-content-sha256' header 84 // does not match what was computed. 85 // 86 // Happens sporadically for no discernible reason. Just retry. 87 return errors.Other, errors.Temporary 88 // "RequestError"s are not considered retryable by `request.IsErrorRetryable(err)` 89 // if the underlying cause is due to a "read: connection reset". For explanation, see: 90 // https://github.com/aws/aws-sdk-go/issues/2525#issuecomment-519263830 91 // So we catch all "RequestError"s here as temporary. 92 case request.ErrCodeRequestError: 93 return errors.Other, errors.Temporary 94 // "SerializationError"s are not considered retryable by `request.IsErrorRetryable(err)` 95 // if the underlying cause is due to a "read: connection reset". For explanation, see: 96 // https://github.com/aws/aws-sdk-go/issues/2525#issuecomment-519263830 97 // So we catch all "SerializationError"s here as temporary. 98 case request.ErrCodeSerialization: 99 return errors.Other, errors.Temporary 100 } 101 if aerr.OrigErr() == nil { 102 break 103 } 104 err = aerr.OrigErr() 105 } 106 return errors.Other, errors.Unknown 107 }