Skip to content

Commit bf2fe01

Browse files
Laren-AWScpyle0819
authored andcommitted
Added a specific error to Go S3 Hello.
Add specific errors and waiter to create bucket, update tests. Added specific errors, waiters, and paginators per audit. Finish audit work, including splitting large objects into a separate scenario. Regenerate README.
1 parent 970756b commit bf2fe01

21 files changed

+583
-141
lines changed

gov2/s3/README.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,20 @@ Code examples that show you how to perform the essential operations within a ser
4545

4646
Code excerpts that show you how to call individual service functions.
4747

48-
- [CopyObject](actions/bucket_basics.go#L220)
49-
- [CreateBucket](actions/bucket_basics.go#L81)
50-
- [DeleteBucket](actions/bucket_basics.go#L278)
48+
- [CopyObject](actions/bucket_basics.go#L288)
49+
- [CreateBucket](actions/bucket_basics.go#L94)
50+
- [DeleteBucket](actions/bucket_basics.go#L387)
5151
- [DeleteObject](../workflows/s3_object_lock/actions/s3_actions.go#L365)
52-
- [DeleteObjects](../workflows/s3_object_lock/actions/s3_actions.go#L407)
53-
- [GetObject](actions/bucket_basics.go#L149)
52+
- [DeleteObjects](../workflows/s3_object_lock/actions/s3_actions.go#L413)
53+
- [GetObject](actions/bucket_basics.go#L200)
5454
- [GetObjectLegalHold](../workflows/s3_object_lock/actions/s3_actions.go#L72)
5555
- [GetObjectLockConfiguration](../workflows/s3_object_lock/actions/s3_actions.go#L109)
5656
- [GetObjectRetention](../workflows/s3_object_lock/actions/s3_actions.go#L138)
57-
- [HeadBucket](actions/bucket_basics.go#L51)
58-
- [ListBuckets](actions/bucket_basics.go#L35)
57+
- [HeadBucket](actions/bucket_basics.go#L64)
58+
- [ListBuckets](actions/bucket_basics.go#L36)
5959
- [ListObjectVersions](../workflows/s3_object_lock/actions/s3_actions.go#L338)
60-
- [ListObjectsV2](actions/bucket_basics.go#L238)
61-
- [PutObject](actions/bucket_basics.go#L100)
60+
- [ListObjectsV2](actions/bucket_basics.go#L316)
61+
- [PutObject](actions/bucket_basics.go#L126)
6262
- [PutObjectLegalHold](../workflows/s3_object_lock/actions/s3_actions.go#L173)
6363
- [PutObjectLockConfiguration](../workflows/s3_object_lock/actions/s3_actions.go#L234)
6464
- [PutObjectRetention](../workflows/s3_object_lock/actions/s3_actions.go#L276)

gov2/s3/actions/bucket_basics.go

Lines changed: 156 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
package actions
55

6+
// snippet-start:[gov2.s3.BucketBasics.complete]
7+
// snippet-start:[gov2.s3.BucketBasics.struct]
8+
69
import (
710
"bytes"
811
"context"
@@ -11,6 +14,7 @@ import (
1114
"io"
1215
"log"
1316
"os"
17+
"time"
1418

1519
"github.com/aws/aws-sdk-go-v2/aws"
1620
"github.com/aws/aws-sdk-go-v2/feature/s3/manager"
@@ -19,9 +23,6 @@ import (
1923
"github.com/aws/smithy-go"
2024
)
2125

22-
// snippet-start:[gov2.s3.BucketBasics.complete]
23-
// snippet-start:[gov2.s3.BucketBasics.struct]
24-
2526
// BucketBasics encapsulates the Amazon Simple Storage Service (Amazon S3) actions
2627
// used in the examples.
2728
// It contains S3Client, an Amazon S3 service client that is used to perform bucket
@@ -36,12 +37,24 @@ type BucketBasics struct {
3637

3738
// ListBuckets lists the buckets in the current account.
3839
func (basics BucketBasics) ListBuckets(ctx context.Context) ([]types.Bucket, error) {
39-
result, err := basics.S3Client.ListBuckets(ctx, &s3.ListBucketsInput{})
40+
var err error
41+
var output *s3.ListBucketsOutput
4042
var buckets []types.Bucket
41-
if err != nil {
42-
log.Printf("Couldn't list buckets for your account. Here's why: %v\n", err)
43-
} else {
44-
buckets = result.Buckets
43+
bucketPaginator := s3.NewListBucketsPaginator(basics.S3Client, &s3.ListBucketsInput{})
44+
for bucketPaginator.HasMorePages() {
45+
output, err = bucketPaginator.NextPage(ctx)
46+
if err != nil {
47+
var apiErr smithy.APIError
48+
if errors.As(err, &apiErr) && apiErr.ErrorCode() == "AccessDenied" {
49+
fmt.Println("You don't have permission to list buckets for this account.")
50+
err = apiErr
51+
} else {
52+
log.Printf("Couldn't list buckets for your account. Here's why: %v\n", err)
53+
}
54+
break
55+
} else {
56+
buckets = append(buckets, output.Buckets...)
57+
}
4558
}
4659
return buckets, err
4760
}
@@ -89,8 +102,21 @@ func (basics BucketBasics) CreateBucket(ctx context.Context, name string, region
89102
},
90103
})
91104
if err != nil {
92-
log.Printf("Couldn't create bucket %v in Region %v. Here's why: %v\n",
93-
name, region, err)
105+
var owned *types.BucketAlreadyOwnedByYou
106+
var exists *types.BucketAlreadyExists
107+
if errors.As(err, &owned) {
108+
log.Printf("You already own bucket %s.\n", name)
109+
err = owned
110+
} else if errors.As(err, &exists) {
111+
log.Printf("Bucket %s already exists.\n", name)
112+
err = exists
113+
}
114+
} else {
115+
err = s3.NewBucketExistsWaiter(basics.S3Client).Wait(
116+
ctx, &s3.HeadBucketInput{Bucket: aws.String(name)}, time.Minute)
117+
if err != nil {
118+
log.Printf("Failed attempt to wait for bucket %s to exist.\n", name)
119+
}
94120
}
95121
return err
96122
}
@@ -112,8 +138,21 @@ func (basics BucketBasics) UploadFile(ctx context.Context, bucketName string, ob
112138
Body: file,
113139
})
114140
if err != nil {
115-
log.Printf("Couldn't upload file %v to %v:%v. Here's why: %v\n",
116-
fileName, bucketName, objectKey, err)
141+
var apiErr smithy.APIError
142+
if errors.As(err, &apiErr) && apiErr.ErrorCode() == "EntityTooLarge" {
143+
log.Printf("Error while uploading object to %s. The object is too large.\n"+
144+
"To upload objects larger than 5GB, use the S3 console (160GB max)\n"+
145+
"or the multipart upload API (5TB max).", bucketName)
146+
} else {
147+
log.Printf("Couldn't upload file %v to %v:%v. Here's why: %v\n",
148+
fileName, bucketName, objectKey, err)
149+
}
150+
} else {
151+
err = s3.NewObjectExistsWaiter(basics.S3Client).Wait(
152+
ctx, &s3.HeadObjectInput{Bucket: aws.String(bucketName), Key: aws.String(objectKey)}, time.Minute)
153+
if err != nil {
154+
log.Printf("Failed attempt to wait for object %s to exist.\n", objectKey)
155+
}
117156
}
118157
}
119158
return err
@@ -137,8 +176,20 @@ func (basics BucketBasics) UploadLargeObject(ctx context.Context, bucketName str
137176
Body: largeBuffer,
138177
})
139178
if err != nil {
140-
log.Printf("Couldn't upload large object to %v:%v. Here's why: %v\n",
141-
bucketName, objectKey, err)
179+
var apiErr smithy.APIError
180+
if errors.As(err, &apiErr) && apiErr.ErrorCode() == "EntityTooLarge" {
181+
log.Printf("Error while uploading object to %s. The object is too large.\n"+
182+
"The maximum size for a multipart upload is 5TB.", bucketName)
183+
} else {
184+
log.Printf("Couldn't upload large object to %v:%v. Here's why: %v\n",
185+
bucketName, objectKey, err)
186+
}
187+
} else {
188+
err = s3.NewObjectExistsWaiter(basics.S3Client).Wait(
189+
ctx, &s3.HeadObjectInput{Bucket: aws.String(bucketName), Key: aws.String(objectKey)}, time.Minute)
190+
if err != nil {
191+
log.Printf("Failed attempt to wait for object %s to exist.\n", objectKey)
192+
}
142193
}
143194

144195
return err
@@ -155,7 +206,13 @@ func (basics BucketBasics) DownloadFile(ctx context.Context, bucketName string,
155206
Key: aws.String(objectKey),
156207
})
157208
if err != nil {
158-
log.Printf("Couldn't get object %v:%v. Here's why: %v\n", bucketName, objectKey, err)
209+
var noKey *types.NoSuchKey
210+
if errors.As(err, &noKey) {
211+
log.Printf("Can't get object %s from bucket %s. No such key exists.\n", objectKey, bucketName)
212+
err = noKey
213+
} else {
214+
log.Printf("Couldn't get object %v:%v. Here's why: %v\n", bucketName, objectKey, err)
215+
}
159216
return err
160217
}
161218
defer result.Body.Close()
@@ -203,14 +260,25 @@ func (basics BucketBasics) DownloadLargeObject(ctx context.Context, bucketName s
203260

204261
// CopyToFolder copies an object in a bucket to a subfolder in the same bucket.
205262
func (basics BucketBasics) CopyToFolder(ctx context.Context, bucketName string, objectKey string, folderName string) error {
263+
objectDest := fmt.Sprintf("%v/%v", folderName, objectKey)
206264
_, err := basics.S3Client.CopyObject(ctx, &s3.CopyObjectInput{
207265
Bucket: aws.String(bucketName),
208266
CopySource: aws.String(fmt.Sprintf("%v/%v", bucketName, objectKey)),
209-
Key: aws.String(fmt.Sprintf("%v/%v", folderName, objectKey)),
267+
Key: aws.String(objectDest),
210268
})
211269
if err != nil {
212-
log.Printf("Couldn't copy object from %v:%v to %v:%v/%v. Here's why: %v\n",
213-
bucketName, objectKey, bucketName, folderName, objectKey, err)
270+
var notActive *types.ObjectNotInActiveTierError
271+
if errors.As(err, &notActive) {
272+
log.Printf("Couldn't copy object %s from %s because the object isn't in the active tier.\n",
273+
objectKey, bucketName)
274+
err = notActive
275+
}
276+
} else {
277+
err = s3.NewObjectExistsWaiter(basics.S3Client).Wait(
278+
ctx, &s3.HeadObjectInput{Bucket: aws.String(bucketName), Key: aws.String(objectDest)}, time.Minute)
279+
if err != nil {
280+
log.Printf("Failed attempt to wait for object %s to exist.\n", objectDest)
281+
}
214282
}
215283
return err
216284
}
@@ -227,8 +295,18 @@ func (basics BucketBasics) CopyToBucket(ctx context.Context, sourceBucket string
227295
Key: aws.String(objectKey),
228296
})
229297
if err != nil {
230-
log.Printf("Couldn't copy object from %v:%v to %v:%v. Here's why: %v\n",
231-
sourceBucket, objectKey, destinationBucket, objectKey, err)
298+
var notActive *types.ObjectNotInActiveTierError
299+
if errors.As(err, &notActive) {
300+
log.Printf("Couldn't copy object %s from %s because the object isn't in the active tier.\n",
301+
objectKey, sourceBucket)
302+
err = notActive
303+
}
304+
} else {
305+
err = s3.NewObjectExistsWaiter(basics.S3Client).Wait(
306+
ctx, &s3.HeadObjectInput{Bucket: aws.String(destinationBucket), Key: aws.String(objectKey)}, time.Minute)
307+
if err != nil {
308+
log.Printf("Failed attempt to wait for object %s to exist.\n", objectKey)
309+
}
232310
}
233311
return err
234312
}
@@ -239,16 +317,27 @@ func (basics BucketBasics) CopyToBucket(ctx context.Context, sourceBucket string
239317

240318
// ListObjects lists the objects in a bucket.
241319
func (basics BucketBasics) ListObjects(ctx context.Context, bucketName string) ([]types.Object, error) {
242-
result, err := basics.S3Client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{
320+
var err error
321+
var output *s3.ListObjectsV2Output
322+
input := &s3.ListObjectsV2Input{
243323
Bucket: aws.String(bucketName),
244-
})
245-
var contents []types.Object
246-
if err != nil {
247-
log.Printf("Couldn't list objects in bucket %v. Here's why: %v\n", bucketName, err)
248-
} else {
249-
contents = result.Contents
250324
}
251-
return contents, err
325+
var objects []types.Object
326+
objectPaginator := s3.NewListObjectsV2Paginator(basics.S3Client, input)
327+
for objectPaginator.HasMorePages() {
328+
output, err = objectPaginator.NextPage(ctx)
329+
if err != nil {
330+
var noBucket *types.NoSuchBucket
331+
if errors.As(err, &noBucket) {
332+
log.Printf("Bucket %s does not exist.\n", bucketName)
333+
err = noBucket
334+
}
335+
break
336+
} else {
337+
objects = append(objects, output.Contents...)
338+
}
339+
}
340+
return objects, err
252341
}
253342

254343
// snippet-end:[gov2.s3.ListObjectsV2]
@@ -263,12 +352,32 @@ func (basics BucketBasics) DeleteObjects(ctx context.Context, bucketName string,
263352
}
264353
output, err := basics.S3Client.DeleteObjects(ctx, &s3.DeleteObjectsInput{
265354
Bucket: aws.String(bucketName),
266-
Delete: &types.Delete{Objects: objectIds},
355+
Delete: &types.Delete{Objects: objectIds, Quiet: aws.Bool(true)},
267356
})
268-
if err != nil {
269-
log.Printf("Couldn't delete objects from bucket %v. Here's why: %v\n", bucketName, err)
357+
if err != nil || len(output.Errors) > 0 {
358+
log.Printf("Error deleting objects from bucket %s.\n", bucketName)
359+
if err != nil {
360+
var noBucket *types.NoSuchBucket
361+
if errors.As(err, &noBucket) {
362+
log.Printf("Bucket %s does not exist.\n", bucketName)
363+
err = noBucket
364+
}
365+
} else if len(output.Errors) > 0 {
366+
for _, outErr := range output.Errors {
367+
log.Printf("%s: %s\n", *outErr.Key, *outErr.Message)
368+
}
369+
err = fmt.Errorf("%s", *output.Errors[0].Message)
370+
}
270371
} else {
271-
log.Printf("Deleted %v objects.\n", len(output.Deleted))
372+
for _, delObjs := range output.Deleted {
373+
err = s3.NewObjectNotExistsWaiter(basics.S3Client).Wait(
374+
ctx, &s3.HeadObjectInput{Bucket: aws.String(bucketName), Key: delObjs.Key}, time.Minute)
375+
if err != nil {
376+
log.Printf("Failed attempt to wait for object %s to be deleted.\n", *delObjs.Key)
377+
} else {
378+
log.Printf("Deleted %s.\n", *delObjs.Key)
379+
}
380+
}
272381
}
273382
return err
274383
}
@@ -282,7 +391,21 @@ func (basics BucketBasics) DeleteBucket(ctx context.Context, bucketName string)
282391
_, err := basics.S3Client.DeleteBucket(ctx, &s3.DeleteBucketInput{
283392
Bucket: aws.String(bucketName)})
284393
if err != nil {
285-
log.Printf("Couldn't delete bucket %v. Here's why: %v\n", bucketName, err)
394+
var noBucket *types.NoSuchBucket
395+
if errors.As(err, &noBucket) {
396+
log.Printf("Bucket %s does not exist.\n", bucketName)
397+
err = noBucket
398+
} else {
399+
log.Printf("Couldn't delete bucket %v. Here's why: %v\n", bucketName, err)
400+
}
401+
} else {
402+
err = s3.NewBucketNotExistsWaiter(basics.S3Client).Wait(
403+
ctx, &s3.HeadBucketInput{Bucket: aws.String(bucketName)}, time.Minute)
404+
if err != nil {
405+
log.Printf("Failed attempt to wait for bucket %s to be deleted.\n", bucketName)
406+
} else {
407+
log.Printf("Deleted %s.\n", bucketName)
408+
}
286409
}
287410
return err
288411
}

gov2/s3/actions/bucket_basics_test.go

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,29 @@ package actions
88
import (
99
"context"
1010
"errors"
11+
"reflect"
1112
"testing"
1213

1314
"github.com/aws/aws-sdk-go-v2/service/s3"
15+
"github.com/aws/aws-sdk-go-v2/service/s3/types"
1416
"github.com/awsdocs/aws-doc-sdk-examples/gov2/s3/stubs"
1517
"github.com/awsdocs/aws-doc-sdk-examples/gov2/testtools"
1618
)
1719

18-
func enterTest() (*testtools.AwsmStubber, *BucketBasics) {
20+
func enterTest() (context.Context, *testtools.AwsmStubber, *BucketBasics) {
1921
stubber := testtools.NewStubber()
2022
basics := &BucketBasics{S3Client: s3.NewFromConfig(*stubber.SdkConfig)}
21-
return stubber, basics
23+
return context.Background(), stubber, basics
24+
}
25+
26+
func wrapErr(expectedErr error) (error, *testtools.StubError) {
27+
return expectedErr, &testtools.StubError{Err: expectedErr}
28+
}
29+
30+
func verifyErr(expectedErr error, actualErr error, t *testing.T) {
31+
if reflect.TypeOf(expectedErr) != reflect.TypeOf(actualErr) {
32+
t.Errorf("Expected error %T, got %T", expectedErr, actualErr)
33+
}
2234
}
2335

2436
func TestBucketBasics_CopyToBucket(t *testing.T) {
@@ -27,12 +39,13 @@ func TestBucketBasics_CopyToBucket(t *testing.T) {
2739
}
2840

2941
func CopyToBucket(raiseErr *testtools.StubError, t *testing.T) {
30-
stubber, basics := enterTest()
31-
stubber.Add(stubs.StubCopyObject("amzn-s3-demo-bucket-source", "object-key", "amzn-s3-demo-bucket-dest", "object-key", raiseErr))
32-
ctx := context.Background()
42+
ctx, stubber, basics := enterTest()
43+
defer testtools.ExitTest(stubber, t)
3344

34-
err := basics.CopyToBucket(ctx, "amzn-s3-demo-bucket-source", "amzn-s3-demo-bucket-dest", "object-key")
45+
expectedErr, stubErr := wrapErr(&types.ObjectNotInActiveTierError{})
46+
stubber.Add(stubs.StubCopyObject("amzn-s3-demo-bucket-source", "object-key", "amzn-s3-demo-bucket-dest", "object-key", stubErr))
47+
stubber.Add(stubs.StubHeadObject("amzn-s3-demo-bucket-source", "object-key", raiseErr))
3548

36-
testtools.VerifyError(err, raiseErr, t)
37-
testtools.ExitTest(stubber, t)
49+
actualErr := basics.CopyToBucket(ctx, "amzn-s3-demo-bucket-source", "amzn-s3-demo-bucket-dest", "object-key")
50+
verifyErr(expectedErr, actualErr, t)
3851
}

0 commit comments

Comments
 (0)