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  }