1package manager 2 3import ( 4 "context" 5 "errors" 6 "fmt" 7 "net/http" 8 9 "github.com/aws/aws-sdk-go-v2/aws" 10 "github.com/aws/aws-sdk-go-v2/service/s3" 11 "github.com/aws/smithy-go/middleware" 12 smithyhttp "github.com/aws/smithy-go/transport/http" 13) 14 15const bucketRegionHeader = "X-Amz-Bucket-Region" 16 17// GetBucketRegion will attempt to get the region for a bucket using the 18// client's configured region to determine which AWS partition to perform the query on. 19// 20// The request will not be signed, and will not use your AWS credentials. 21// 22// A BucketNotFound error will be returned if the bucket does not exist in the 23// AWS partition the client region belongs to. 24// 25// For example to get the region of a bucket which exists in "eu-central-1" 26// you could provide a region hint of "us-west-2". 27// 28// cfg, err := config.LoadDefaultConfig(context.TODO()) 29// if err != nil { 30// log.Println("error:", err) 31// return 32// } 33// 34// bucket := "my-bucket" 35// region, err := manager.GetBucketRegion(ctx, s3.NewFromConfig(cfg), bucket) 36// if err != nil { 37// var bnf manager.BucketNotFound 38// if errors.As(err, &bnf) { 39// fmt.Fprintf(os.Stderr, "unable to find bucket %s's region\n", bucket) 40// } 41// return 42// } 43// fmt.Printf("Bucket %s is in %s region\n", bucket, region) 44// 45// By default the request will be made to the Amazon S3 endpoint using the virtual-hosted-style addressing. 46// 47// bucketname.s3.us-west-2.amazonaws.com/ 48// 49// To configure the GetBucketRegion to make a request via the Amazon 50// S3 FIPS endpoints directly when a FIPS region name is not available, (e.g. 51// fips-us-gov-west-1) set the EndpointResolver on the config or client the 52// utility is called with. 53// 54// cfg, err := config.LoadDefaultConfig(context.TODO(), 55// config.WithEndpointResolver( 56// aws.EndpointResolverFunc(func(service, region string) (aws.Endpoint, error) { 57// return aws.Endpoint{URL: "https://s3-fips.us-west-2.amazonaws.com"}, nil 58// }), 59// ) 60// if err != nil { 61// panic(err) 62// } 63func GetBucketRegion(ctx context.Context, client HeadBucketAPIClient, bucket string, optFns ...func(*s3.Options)) (string, error) { 64 var captureBucketRegion deserializeBucketRegion 65 66 clientOptionFns := make([]func(*s3.Options), len(optFns)+1) 67 clientOptionFns[0] = func(options *s3.Options) { 68 options.Credentials = aws.AnonymousCredentials{} 69 options.APIOptions = append(options.APIOptions, captureBucketRegion.RegisterMiddleware) 70 } 71 copy(clientOptionFns[1:], optFns) 72 73 _, err := client.HeadBucket(ctx, &s3.HeadBucketInput{ 74 Bucket: aws.String(bucket), 75 }, clientOptionFns...) 76 if len(captureBucketRegion.BucketRegion) == 0 && err != nil { 77 var httpStatusErr interface { 78 HTTPStatusCode() int 79 } 80 if !errors.As(err, &httpStatusErr) { 81 return "", err 82 } 83 84 if httpStatusErr.HTTPStatusCode() == http.StatusNotFound { 85 return "", &bucketNotFound{} 86 } 87 88 return "", err 89 } 90 91 return captureBucketRegion.BucketRegion, nil 92} 93 94type deserializeBucketRegion struct { 95 BucketRegion string 96} 97 98func (d *deserializeBucketRegion) RegisterMiddleware(stack *middleware.Stack) error { 99 return stack.Deserialize.Add(d, middleware.After) 100} 101 102func (d *deserializeBucketRegion) ID() string { 103 return "DeserializeBucketRegion" 104} 105 106func (d *deserializeBucketRegion) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) ( 107 out middleware.DeserializeOutput, metadata middleware.Metadata, err error, 108) { 109 out, metadata, err = next.HandleDeserialize(ctx, in) 110 if err != nil { 111 return out, metadata, err 112 } 113 114 resp, ok := out.RawResponse.(*smithyhttp.Response) 115 if !ok { 116 return out, metadata, fmt.Errorf("unknown transport type %T", out.RawResponse) 117 } 118 119 d.BucketRegion = resp.Header.Get(bucketRegionHeader) 120 121 return out, metadata, err 122} 123 124// BucketNotFound indicates the bucket was not found in the partition when calling GetBucketRegion. 125type BucketNotFound interface { 126 error 127 128 isBucketNotFound() 129} 130 131type bucketNotFound struct{} 132 133func (b *bucketNotFound) Error() string { 134 return "bucket not found" 135} 136 137func (b *bucketNotFound) isBucketNotFound() {} 138 139var _ BucketNotFound = (*bucketNotFound)(nil) 140