@@ -6,16 +6,19 @@ import (
66 "errors"
77 "fmt"
88 "io"
9+ "net/http"
910 "os"
1011 "path"
1112 "strings"
1213
1314 "github.com/aws/aws-sdk-go-v2/aws"
15+ awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
1416 "github.com/aws/aws-sdk-go-v2/config"
1517 "github.com/aws/aws-sdk-go-v2/feature/s3/manager"
1618 "github.com/aws/aws-sdk-go-v2/service/s3"
1719 "github.com/aws/aws-sdk-go-v2/service/s3/types"
1820 "github.com/aws/smithy-go"
21+
1922 "github.com/stellar/go/support/log"
2023 "github.com/stellar/go/support/url"
2124)
@@ -50,6 +53,15 @@ func NewS3DataStore(ctx context.Context, datastoreConfig DataStoreConfig) (DataS
5053 if endpointUrl != "" {
5154 o .BaseEndpoint = aws .String (endpointUrl )
5255 }
56+
57+ // Check if default credentials were successfully retrieved from the chain.
58+ // If not, fall back to anonymous credentials for public S3 bucket access.
59+ _ , err := cfg .Credentials .Retrieve (ctx )
60+ if err != nil {
61+ log .Infof ("No default AWS credentials found, configuring S3 client for anonymous access" )
62+ o .Credentials = aws.AnonymousCredentials {}
63+ }
64+
5365 o .Region = region
5466 o .UsePathStyle = true
5567 })
@@ -68,10 +80,37 @@ func FromS3Client(ctx context.Context, client *s3.Client, bucketPath string, sch
6880 bucketName := parsed .Host
6981 uploader := manager .NewUploader (client )
7082
71- input := & s3.HeadBucketInput {Bucket : aws .String (bucketName )}
72- _ , err = client .HeadBucket (ctx , input )
83+ log .Infof ("Creating S3 client for bucket: %s, prefix: %s" , bucketName , prefix )
84+
85+ listInput := & s3.ListObjectsV2Input {
86+ Bucket : aws .String (bucketName ),
87+ Prefix : aws .String (prefix ),
88+ MaxKeys : aws .Int32 (1 ),
89+ }
90+
91+ _ , err = client .ListObjectsV2 (ctx , listInput )
92+
7393 if err != nil {
74- return nil , fmt .Errorf ("failed to head bucket, the bucket may not exist or you may not have access: %w" , err )
94+ // check for http redirect specifically as it implies a region issue.
95+ var responseError * awshttp.ResponseError
96+ if errors .As (err , & responseError ) {
97+ if responseError .HTTPStatusCode () == http .StatusMovedPermanently {
98+ return nil , fmt .Errorf ("bucket '%s' requires a different endpoint (301 PermanentRedirect)." +
99+ " Please ensure the correct region is configured: %w" , bucketName , err )
100+ }
101+ }
102+
103+ var apiError smithy.APIError
104+ if errors .As (err , & apiError ) {
105+ switch apiError .ErrorCode () {
106+ case "NoSuchBucket" :
107+ return nil , fmt .Errorf ("bucket '%s' does not exist (NoSuchBucket): %w" , bucketName , err )
108+ case "AccessDenied" :
109+ return nil , fmt .Errorf ("access denied to bucket '%s' (AccessDenied): %w" , bucketName , err )
110+ default :
111+ }
112+ }
113+ return nil , fmt .Errorf ("failed to list objects in bucket '%s': %w" , bucketName , err )
75114 }
76115
77116 return S3DataStore {client : client , uploader : uploader , bucket : bucketName , prefix : prefix , schema : schema }, nil
@@ -107,12 +146,14 @@ func (b S3DataStore) GetFile(ctx context.Context, filePath string) (io.ReadClose
107146
108147 output , err := b .client .GetObject (ctx , input )
109148 if err != nil {
149+ log .Errorf ("Error retrieving file '%s': %v" , filePath , err )
110150 if isNotFoundError (err ) {
111151 return nil , os .ErrNotExist
112152 }
113- return nil , err
153+ return nil , fmt . Errorf ( "error retrieving file %s: %w" , filePath , err )
114154 }
115155
156+ log .Infof ("File retrieved successfully: %s" , filePath )
116157 return output .Body , nil
117158}
118159
0 commit comments