11package s3
22
33import (
4+ "context"
5+ "errors"
6+ "fmt"
47 "log"
58 "io/ioutil"
69 "os"
710
8- "github.com/aws/aws-sdk-go/aws "
9- "github.com/aws/aws-sdk-go/aws/awserr "
10- "github.com/aws/aws-sdk-go/aws/ec2metadata "
11- "github.com/aws/aws-sdk-go/aws/session "
12- "github.com/aws/aws-sdk-go/service/s3"
13- "github.com/aws/aws-sdk-go/service/s3/s3manager "
11+ "github.com/aws/aws-sdk-go-v2/config "
12+ "github.com/aws/aws-sdk-go-v2/feature/ec2/imds "
13+ "github.com/aws/aws-sdk-go-v2/feature/s3/manager "
14+ "github.com/aws/aws-sdk-go-v2/service/s3 "
15+ "github.com/aws/aws-sdk-go-v2 /service/s3/types "
16+ "github.com/aws/smithy-go "
1417 "github.com/buildkite/elastic-ci-stack-s3-secrets-hooks/s3secrets-helper/v2/sentinel"
1518)
1619
17- const envDefaultRegion = "AWS_DEFAULT_REGION"
18-
1920type Client struct {
20- s3 * s3.S3
21+ s3 * s3.Client
2122 bucket string
2223}
2324
24- func New (log * log.Logger , bucket string ) (* Client , error ) {
25- sess , err := session .NewSession ()
26- if err != nil {
27- return nil , err
25+ func getRegion (ctx context.Context ) (string , error ) {
26+ if region := os .Getenv ("AWS_DEFAULT_REGION" ); len (region ) > 0 {
27+ return region , nil
2828 }
2929
30- currentRegion := os .Getenv (envDefaultRegion )
31- // Discover our executing region using the IMDS
32- if currentRegion == "" {
33- idms := ec2metadata .New (sess )
34- currentRegion , _ = idms .Region ()
35- }
36- // Fall back to us-east-1 :(
37- if currentRegion == "" {
38- currentRegion = "us-east-1"
30+ imdsClient := imds .New (imds.Options {})
31+ if result , err := imdsClient .GetRegion (ctx , nil ); err == nil {
32+ if len (result .Region ) > 0 {
33+ return result .Region , nil
34+ }
3935 }
4036
41- log .Printf ("Discovered current region as %q\n " , currentRegion )
37+ return "" , errors .New ("Unknown current region" )
38+ }
39+
40+ func New (log * log.Logger , bucket string ) (* Client , error ) {
41+ ctx := context .Background ()
4242
4343 // Using the current region (or a guess) find where the bucket lives
44- bucketRegion , err := s3manager .GetBucketRegion (aws .BackgroundContext (), sess , bucket , currentRegion )
44+
45+ region , err := getRegion (ctx )
4546 if err != nil {
46- return nil , err
47+ // Ignore error and fallback to us-east-1 for bucket lookup
48+ region = "us-east-1"
4749 }
4850
49- log .Printf ("Discovered bucket region as %q\n " , bucketRegion )
51+ config , err := config .LoadDefaultConfig (ctx ,
52+ config .WithRegion (region ),
53+ )
54+ if err != nil {
55+ return nil , fmt .Errorf ("Could not load the AWS SDK config (%v)" , err )
56+ }
5057
51- sess , err = session . NewSession ( & aws. Config {
52- Region : & bucketRegion ,
53- } )
58+ log . Printf ( "Discovered current region as %q \n " , config . Region )
59+
60+ bucketRegion , err := manager . GetBucketRegion ( ctx , s3 . NewFromConfig ( config ), bucket )
5461 if err != nil {
55- return nil , err
62+ return nil , fmt . Errorf ( "Could not discover the region for bucket %q: (%v)" , bucket , err )
5663 }
64+
65+ log .Printf ("Discovered bucket region as %q\n " , bucketRegion )
66+
67+ config .Region = bucketRegion
68+
5769 return & Client {
58- s3 : s3 .New ( sess ),
70+ s3 : s3 .NewFromConfig ( config ),
5971 bucket : bucket ,
6072 }, nil
6173}
@@ -69,23 +81,26 @@ func (c *Client) Bucket() (string) {
6981// sentinel.ErrNotFound and sentinel.ErrForbidden are returned for those cases.
7082// Other errors are returned verbatim.
7183func (c * Client ) Get (key string ) ([]byte , error ) {
72- out , err := c .s3 .GetObject (& s3.GetObjectInput {
84+ out , err := c .s3 .GetObject (context . TODO (), & s3.GetObjectInput {
7385 Bucket : & c .bucket ,
7486 Key : & key ,
7587 })
7688 if err != nil {
77- if aerr , ok := err .(awserr.Error ); ok {
78- switch aerr .Code () {
79- case "NoSuchKey" :
80- return nil , sentinel .ErrNotFound
81- case "Forbidden" :
89+ var noSuchKey * types.NoSuchKey
90+ if errors .As (err , & noSuchKey ) {
91+ return nil , sentinel .ErrNotFound
92+ }
93+
94+ // Possible values can be found at https://docs.aws.amazon.com/AmazonS3/latest/API/API_Error.html
95+ var apiErr smithy.APIError
96+ if errors .As (err , & apiErr ) {
97+ code := apiErr .ErrorCode ()
98+ if code == "AccessDenied" {
8299 return nil , sentinel .ErrForbidden
83- default :
84- return nil , aerr
85100 }
86- } else {
87- return nil , err
88101 }
102+
103+ return nil , fmt .Errorf ("Could not GetObject (%s) in bucket (%s). Ensure your IAM Identity has s3:GetObject permission for this key and bucket. (%v)" , key , c .bucket , err )
89104 }
90105 defer out .Body .Close ()
91106 // we probably should return io.Reader or io.ReadCloser rather than []byte,
@@ -98,18 +113,8 @@ func (c *Client) Get(key string) ([]byte, error) {
98113// 404 Not Found and 403 Forbidden return false without error.
99114// Other errors result in false with an error.
100115func (c * Client ) BucketExists () (bool , error ) {
101- if _ , err := c .s3 .HeadBucket (& s3.HeadBucketInput {Bucket : & c .bucket }); err != nil {
102- if aerr , ok := err .(awserr.Error ); ok {
103- switch aerr .Code () {
104- // https://github.com/aws/aws-sdk-go/issues/2593#issuecomment-491436818
105- case "NoSuchBucket" , "NotFound" :
106- return false , nil
107- default : // e.g. NoCredentialProviders, Forbidden
108- return false , aerr
109- }
110- } else {
111- return false , err
112- }
116+ if _ , err := c .s3 .HeadBucket (context .TODO (), & s3.HeadBucketInput {Bucket : & c .bucket }); err != nil {
117+ return false , fmt .Errorf ("Could not HeadBucket (%s). Ensure your IAM Identity has s3:ListBucket permission for this bucket. (%v)" , c .bucket , err )
113118 }
114119 return true , nil
115120}
0 commit comments