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  }