@@ -13,16 +13,13 @@ import (
1313 "github.com/aws/aws-sdk-go-v2/service/s3"
1414 "github.com/aws/aws-sdk-go-v2/service/s3/types"
1515 "github.com/hashicorp/aws-sdk-go-base/v2/tfawserr"
16+ tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices"
1617)
1718
18- const (
19- listObjectVersionsMaxKeys = 1000
20- )
21-
22- // emptyBucket empties the specified S3 bucket by deleting all object versions and delete markers.
19+ // emptyBucket empties the specified S3 general purpose bucket by deleting all object versions and delete markers.
2320// If `force` is `true` then S3 Object Lock governance mode restrictions are bypassed and
2421// an attempt is made to remove any S3 Object Lock legal holds.
25- // Returns the number of objects deleted.
22+ // Returns the number of object versions and delete markers deleted.
2623func emptyBucket (ctx context.Context , conn * s3.Client , bucket string , force bool ) (int64 , error ) {
2724 nObjects , err := forEachObjectVersionsPage (ctx , conn , bucket , func (ctx context.Context , conn * s3.Client , bucket string , page * s3.ListObjectVersionsOutput ) (int64 , error ) {
2825 return deletePageOfObjectVersions (ctx , conn , bucket , force , page )
@@ -38,13 +35,18 @@ func emptyBucket(ctx context.Context, conn *s3.Client, bucket string, force bool
3835 return nObjects , err
3936}
4037
38+ // emptyDirectoryBucket empties the specified S3 directory bucket by deleting all objects.
39+ // Returns the number of objects deleted.
40+ func emptyDirectoryBucket (ctx context.Context , conn * s3.Client , bucket string ) (int64 , error ) {
41+ return forEachObjectsPage (ctx , conn , bucket , deletePageOfObjects )
42+ }
43+
4144// forEachObjectVersionsPage calls the specified function for each page returned from the S3 ListObjectVersionsPages API.
4245func forEachObjectVersionsPage (ctx context.Context , conn * s3.Client , bucket string , fn func (ctx context.Context , conn * s3.Client , bucket string , page * s3.ListObjectVersionsOutput ) (int64 , error )) (int64 , error ) {
4346 var nObjects int64
4447
4548 input := & s3.ListObjectVersionsInput {
46- Bucket : aws .String (bucket ),
47- MaxKeys : aws .Int32 (listObjectVersionsMaxKeys ),
49+ Bucket : aws .String (bucket ),
4850 }
4951 var lastErr error
5052
@@ -72,21 +74,52 @@ func forEachObjectVersionsPage(ctx context.Context, conn *s3.Client, bucket stri
7274 return nObjects , nil
7375}
7476
77+ // forEachObjectsPage calls the specified function for each page returned from the S3 ListObjectsV2 API.
78+ func forEachObjectsPage (ctx context.Context , conn * s3.Client , bucket string , fn func (ctx context.Context , conn * s3.Client , bucket string , page * s3.ListObjectsV2Output ) (int64 , error )) (int64 , error ) {
79+ var nObjects int64
80+
81+ input := & s3.ListObjectsV2Input {
82+ Bucket : aws .String (bucket ),
83+ }
84+ var lastErr error
85+
86+ pages := s3 .NewListObjectsV2Paginator (conn , input )
87+ for pages .HasMorePages () {
88+ page , err := pages .NextPage (ctx )
89+
90+ if err != nil {
91+ return nObjects , fmt .Errorf ("listing S3 bucket (%s) objects: %w" , bucket , err )
92+ }
93+
94+ n , err := fn (ctx , conn , bucket , page )
95+ nObjects += n
96+
97+ if err != nil {
98+ lastErr = err
99+ break
100+ }
101+ }
102+
103+ if lastErr != nil {
104+ return nObjects , lastErr
105+ }
106+
107+ return nObjects , nil
108+ }
109+
75110// deletePageOfObjectVersions deletes a page (<= 1000) of S3 object versions.
76111// If `force` is `true` then S3 Object Lock governance mode restrictions are bypassed and
77112// an attempt is made to remove any S3 Object Lock legal holds.
78113// Returns the number of objects deleted.
79114func deletePageOfObjectVersions (ctx context.Context , conn * s3.Client , bucket string , force bool , page * s3.ListObjectVersionsOutput ) (int64 , error ) {
80- var nObjects int64
81-
82- toDelete := make ([]types.ObjectIdentifier , 0 , len (page .Versions ))
83- for _ , v := range page .Versions {
84- toDelete = append (toDelete , types.ObjectIdentifier {
115+ toDelete := tfslices .ApplyToAll (page .Versions , func (v types.ObjectVersion ) types.ObjectIdentifier {
116+ return types.ObjectIdentifier {
85117 Key : v .Key ,
86118 VersionId : v .VersionId ,
87- })
88- }
119+ }
120+ })
89121
122+ var nObjects int64
90123 if nObjects = int64 (len (toDelete )); nObjects == 0 {
91124 return nObjects , nil
92125 }
@@ -109,16 +142,14 @@ func deletePageOfObjectVersions(ctx context.Context, conn *s3.Client, bucket str
109142 }
110143
111144 if err != nil {
112- return nObjects , fmt .Errorf ("deleting S3 bucket (%s) objects : %w" , bucket , err )
145+ return nObjects , fmt .Errorf ("deleting S3 bucket (%s) object versions : %w" , bucket , err )
113146 }
114147
115148 nObjects -= int64 (len (output .Errors ))
116149
117- var deleteErrs []error
118-
150+ var errs []error
119151 for _ , v := range output .Errors {
120152 code := aws .ToString (v .Code )
121-
122153 if code == errCodeNoSuchKey {
123154 continue
124155 }
@@ -139,8 +170,8 @@ func deletePageOfObjectVersions(ctx context.Context, conn *s3.Client, bucket str
139170
140171 if err != nil {
141172 // Add the original error and the new error.
142- deleteErrs = append (deleteErrs , newDeleteObjectVersionError (v ))
143- deleteErrs = append (deleteErrs , fmt .Errorf ("removing legal hold: %w" , newObjectVersionError (key , versionID , err )))
173+ errs = append (errs , newDeleteObjectVersionError (v ))
174+ errs = append (errs , fmt .Errorf ("removing legal hold: %w" , newObjectVersionError (key , versionID , err )))
144175 } else {
145176 // Attempt to delete the object once the legal hold has been removed.
146177 _ , err := conn .DeleteObject (ctx , & s3.DeleteObjectInput {
@@ -150,18 +181,18 @@ func deletePageOfObjectVersions(ctx context.Context, conn *s3.Client, bucket str
150181 })
151182
152183 if err != nil {
153- deleteErrs = append (deleteErrs , fmt .Errorf ("deleting: %w" , newObjectVersionError (key , versionID , err )))
184+ errs = append (errs , fmt .Errorf ("deleting: %w" , newObjectVersionError (key , versionID , err )))
154185 } else {
155186 nObjects ++
156187 }
157188 }
158189 } else {
159- deleteErrs = append (deleteErrs , newDeleteObjectVersionError (v ))
190+ errs = append (errs , newDeleteObjectVersionError (v ))
160191 }
161192 }
162193
163- if err := errors .Join (deleteErrs ... ); err != nil {
164- return nObjects , fmt .Errorf ("deleting S3 bucket (%s) objects : %w" , bucket , err )
194+ if err := errors .Join (errs ... ); err != nil {
195+ return nObjects , fmt .Errorf ("deleting S3 bucket (%s) object versions : %w" , bucket , err )
165196 }
166197
167198 return nObjects , nil
@@ -170,16 +201,14 @@ func deletePageOfObjectVersions(ctx context.Context, conn *s3.Client, bucket str
170201// deletePageOfDeleteMarkers deletes a page (<= 1000) of S3 object delete markers.
171202// Returns the number of delete markers deleted.
172203func deletePageOfDeleteMarkers (ctx context.Context , conn * s3.Client , bucket string , page * s3.ListObjectVersionsOutput ) (int64 , error ) {
173- var nObjects int64
174-
175- toDelete := make ([]types.ObjectIdentifier , 0 , len (page .Versions ))
176- for _ , v := range page .DeleteMarkers {
177- toDelete = append (toDelete , types.ObjectIdentifier {
204+ toDelete := tfslices .ApplyToAll (page .Versions , func (v types.ObjectVersion ) types.ObjectIdentifier {
205+ return types.ObjectIdentifier {
178206 Key : v .Key ,
179207 VersionId : v .VersionId ,
180- })
181- }
208+ }
209+ })
182210
211+ var nObjects int64
183212 if nObjects = int64 (len (toDelete )); nObjects == 0 {
184213 return nObjects , nil
185214 }
@@ -204,19 +233,64 @@ func deletePageOfDeleteMarkers(ctx context.Context, conn *s3.Client, bucket stri
204233
205234 nObjects -= int64 (len (output .Errors ))
206235
207- var deleteErrs []error
208-
236+ var errs []error
209237 for _ , v := range output .Errors {
210- deleteErrs = append (deleteErrs , newDeleteObjectVersionError (v ))
238+ errs = append (errs , newDeleteObjectVersionError (v ))
211239 }
212240
213- if err := errors .Join (deleteErrs ... ); err != nil {
241+ if err := errors .Join (errs ... ); err != nil {
214242 return nObjects , fmt .Errorf ("deleting S3 bucket (%s) delete markers: %w" , bucket , err )
215243 }
216244
217245 return nObjects , nil
218246}
219247
248+ // deletePageOfObjects deletes a page (<= 1000) of S3 objects.
249+ // Returns the number of objects deleted.
250+ func deletePageOfObjects (ctx context.Context , conn * s3.Client , bucket string , page * s3.ListObjectsV2Output ) (int64 , error ) {
251+ toDelete := tfslices .ApplyToAll (page .Contents , func (v types.Object ) types.ObjectIdentifier {
252+ return types.ObjectIdentifier {
253+ Key : v .Key ,
254+ }
255+ })
256+
257+ var nObjects int64
258+ if nObjects = int64 (len (toDelete )); nObjects == 0 {
259+ return nObjects , nil
260+ }
261+
262+ input := & s3.DeleteObjectsInput {
263+ Bucket : aws .String (bucket ),
264+ Delete : & types.Delete {
265+ Objects : toDelete ,
266+ Quiet : aws .Bool (true ), // Only report errors.
267+ },
268+ }
269+
270+ output , err := conn .DeleteObjects (ctx , input )
271+
272+ if tfawserr .ErrCodeEquals (err , errCodeNoSuchBucket ) {
273+ return nObjects , nil
274+ }
275+
276+ if err != nil {
277+ return nObjects , fmt .Errorf ("deleting S3 bucket (%s) objects: %w" , bucket , err )
278+ }
279+
280+ nObjects -= int64 (len (output .Errors ))
281+
282+ var errs []error
283+ for _ , v := range output .Errors {
284+ errs = append (errs , newDeleteObjectVersionError (v ))
285+ }
286+
287+ if err := errors .Join (errs ... ); err != nil {
288+ return nObjects , fmt .Errorf ("deleting S3 bucket (%s) objects: %w" , bucket , err )
289+ }
290+
291+ return nObjects , nil
292+ }
293+
220294func newObjectVersionError (key , versionID string , err error ) error {
221295 if err == nil {
222296 return nil
@@ -235,20 +309,21 @@ func newDeleteObjectVersionError(err types.Error) error {
235309 return fmt .Errorf ("deleting: %w" , newObjectVersionError (aws .ToString (err .Key ), aws .ToString (err .VersionId ), s3Err ))
236310}
237311
238- // deleteAllObjectVersions deletes all versions of a specified key from an S3 bucket.
312+ // deleteAllObjectVersions deletes all versions of a specified key from an S3 general purpose bucket.
239313// If key is empty then all versions of all objects are deleted.
240- // Set force to true to override any S3 object lock protections on object lock enabled buckets.
314+ // Set ` force` to ` true` to override any S3 object lock protections on object lock enabled buckets.
241315// Returns the number of objects deleted.
316+ // Use `emptyBucket` to delete all versions of all objects in a bucket.
242317func deleteAllObjectVersions (ctx context.Context , conn * s3.Client , bucket , key string , force , ignoreObjectErrors bool ) (int64 , error ) {
243- var nObjects int64
318+ if key == "" {
319+ return 0 , errors .New ("use `emptyBucket` to delete all versions of all objects in an S3 general purpose bucket" )
320+ }
244321
245322 input := & s3.ListObjectVersionsInput {
246- Bucket : aws .String (bucket ),
247- MaxKeys : aws .Int32 (listObjectVersionsMaxKeys ),
248- }
249- if key != "" {
250- input .Prefix = aws .String (key )
323+ Bucket : aws .String (bucket ),
324+ Prefix : aws .String (key ),
251325 }
326+ var nObjects int64
252327 var lastErr error
253328
254329 pages := s3 .NewListObjectVersionsPaginator (conn , input )
@@ -267,7 +342,7 @@ func deleteAllObjectVersions(ctx context.Context, conn *s3.Client, bucket, key s
267342 objectKey := aws .ToString (objectVersion .Key )
268343 objectVersionID := aws .ToString (objectVersion .VersionId )
269344
270- if key != "" && key != objectKey {
345+ if key != objectKey {
271346 continue
272347 }
273348
@@ -358,7 +433,7 @@ func deleteAllObjectVersions(ctx context.Context, conn *s3.Client, bucket, key s
358433 deleteMarkerKey := aws .ToString (deleteMarker .Key )
359434 deleteMarkerVersionID := aws .ToString (deleteMarker .VersionId )
360435
361- if key != "" && key != deleteMarkerKey {
436+ if key != deleteMarkerKey {
362437 continue
363438 }
364439
@@ -383,7 +458,7 @@ func deleteAllObjectVersions(ctx context.Context, conn *s3.Client, bucket, key s
383458}
384459
385460// deleteObjectVersion deletes a specific object version.
386- // Set force to true to override any S3 object lock protections.
461+ // Set ` force` to ` true` to override any S3 object lock protections.
387462func deleteObjectVersion (ctx context.Context , conn * s3.Client , b , k , v string , force bool ) error {
388463 input := & s3.DeleteObjectInput {
389464 Bucket : aws .String (b ),
0 commit comments