github.com/grailbio/base@v0.0.11/file/s3file/stat.go (about) 1 package s3file 2 3 import ( 4 "context" 5 "path/filepath" 6 "strings" 7 8 "github.com/aws/aws-sdk-go/aws" 9 "github.com/aws/aws-sdk-go/service/s3" 10 "github.com/aws/aws-sdk-go/service/s3/s3iface" 11 "github.com/grailbio/base/errors" 12 "github.com/grailbio/base/file" 13 ) 14 15 // Stat implements file.Implementation interface. 16 func (impl *s3Impl) Stat(ctx context.Context, path string, opts ...file.Opts) (file.Info, error) { 17 _, bucket, key, err := ParseURL(path) 18 if err != nil { 19 return nil, errors.E(errors.Invalid, "could not parse", path, err) 20 } 21 resp := runRequest(ctx, func() response { 22 clients, err := impl.clientsForAction(ctx, "GetObject", bucket, key) 23 if err != nil { 24 return response{err: err} 25 } 26 policy := newBackoffPolicy(clients, mergeFileOpts(opts)) 27 info, err := stat(ctx, clients, policy, path, bucket, key) 28 if err != nil { 29 return response{err: err} 30 } 31 return response{info: info} 32 }) 33 return resp.info, resp.err 34 } 35 36 func stat(ctx context.Context, clients []s3iface.S3API, policy retryPolicy, path, bucket, key string) (*s3Info, error) { 37 if key == "" { 38 return nil, errors.E(errors.Invalid, "cannot stat with empty S3 key", path) 39 } 40 metric := metrics.Op("stat").Start() 41 defer metric.Done() 42 for { 43 var ids s3RequestIDs 44 output, err := policy.client().HeadObjectWithContext(ctx, 45 &s3.HeadObjectInput{ 46 Bucket: aws.String(bucket), 47 Key: aws.String(key), 48 }, 49 ids.captureOption(), 50 ) 51 if policy.shouldRetry(ctx, err, path) { 52 metric.Retry() 53 continue 54 } 55 if err != nil { 56 return nil, annotate(err, ids, &policy, "s3file.stat", path) 57 } 58 if output.ETag == nil || *output.ETag == "" { 59 return nil, errors.E("s3file.stat: empty ETag", path, errors.NotExist, "awsrequestID:", ids.String()) 60 } 61 if output.ContentLength == nil { 62 return nil, errors.E("s3file.stat: nil ContentLength", path, errors.NotExist, "awsrequestID:", ids.String()) 63 } 64 if *output.ContentLength == 0 && strings.HasSuffix(path, "/") { 65 // Assume this is a directory marker: 66 // https://web.archive.org/web/20190424231712/https://docs.aws.amazon.com/AmazonS3/latest/user-guide/using-folders.html 67 return nil, errors.E("s3file.stat: directory marker at path", path, errors.NotExist, "awsrequestID:", ids.String()) 68 } 69 if output.LastModified == nil { 70 return nil, errors.E("s3file.stat: nil LastModified", path, errors.NotExist, "awsrequestID:", ids.String()) 71 } 72 return &s3Info{ 73 name: filepath.Base(path), 74 size: *output.ContentLength, 75 modTime: *output.LastModified, 76 etag: *output.ETag, 77 }, nil 78 } 79 }