diff --git a/cmd/litestream/main.go b/cmd/litestream/main.go index 80b9003..71d305c 100644 --- a/cmd/litestream/main.go +++ b/cmd/litestream/main.go @@ -295,8 +295,6 @@ func newS3ReplicaFromConfig(db *litestream.DB, config *ReplicaConfig) (*s3.Repli return nil, fmt.Errorf("%s: s3 access key id required", db.Path()) } else if config.SecretAccessKey == "" { return nil, fmt.Errorf("%s: s3 secret access key required", db.Path()) - } else if config.Region == "" { - return nil, fmt.Errorf("%s: s3 region required", db.Path()) } else if config.Bucket == "" { return nil, fmt.Errorf("%s: s3 bucket required", db.Path()) } diff --git a/s3/s3.go b/s3/s3.go index e299f8a..f7bf543 100644 --- a/s3/s3.go +++ b/s3/s3.go @@ -602,9 +602,19 @@ func (r *Replica) Init(ctx context.Context) (err error) { return nil } + // Look up region if not specified. + region := r.Region + if region == "" { + if region, err = r.findBucketRegion(ctx, r.Bucket); err != nil { + return fmt.Errorf("cannot lookup bucket region: %w", err) + } + log.Printf("%s(%s): s3 bucket region found: %q", r.db.Path(), r.Name(), region) + } + + // Create new AWS session. sess, err := session.NewSession(&aws.Config{ Credentials: credentials.NewStaticCredentials(r.AccessKeyID, r.SecretAccessKey, ""), - Region: aws.String(r.Region), + Region: aws.String(region), }) if err != nil { return fmt.Errorf("cannot create aws session: %w", err) @@ -614,6 +624,28 @@ func (r *Replica) Init(ctx context.Context) (err error) { return nil } +func (r *Replica) findBucketRegion(ctx context.Context, bucket string) (string, error) { + // Connect to US standard region to fetch info. + sess, err := session.NewSession(&aws.Config{ + Credentials: credentials.NewStaticCredentials(r.AccessKeyID, r.SecretAccessKey, ""), + Region: aws.String("us-east-1"), + }) + if err != nil { + return "", err + } + + // Fetch bucket location, if possible. Must be bucket owner. + // This call can return a nil location which means it's in us-east-1. + if out, err := s3.New(sess).GetBucketLocation(&s3.GetBucketLocationInput{ + Bucket: aws.String(bucket), + }); err != nil { + return "", err + } else if out.LocationConstraint != nil { + return *out.LocationConstraint, nil + } + return "us-east-1", nil +} + func (r *Replica) Sync(ctx context.Context) (err error) { if err := r.Init(ctx); err != nil { return err