@@ -5,13 +5,14 @@ import (
5
5
"encoding/json"
6
6
"errors"
7
7
"fmt"
8
- "github.com/aws/aws-sdk-go/aws/credentials/stscreds"
9
8
"io"
10
9
"io/ioutil"
11
10
"os"
12
11
"strings"
13
12
"time"
14
13
14
+ "github.com/aws/aws-sdk-go/aws/credentials/stscreds"
15
+
15
16
"net/http"
16
17
17
18
"github.com/aws/aws-sdk-go/aws"
@@ -28,6 +29,8 @@ type S3Client interface {
28
29
BucketFiles (bucketName string , prefixHint string ) ([]string , error )
29
30
BucketFileVersions (bucketName string , remotePath string ) ([]string , error )
30
31
32
+ ChunkedBucketList (bucketName string , prefix string , continuationToken * string ) (BucketListChunk , error )
33
+
31
34
UploadFile (bucketName string , remotePath string , localPath string , options UploadFileOptions ) (string , error )
32
35
DownloadFile (bucketName string , remotePath string , versionID string , localPath string ) error
33
36
@@ -149,17 +152,24 @@ func NewAwsConfig(
149
152
return awsConfig
150
153
}
151
154
152
- func (client * s3client ) BucketFiles (bucketName string , prefixHint string ) ([]string , error ) {
153
- entries , err := client .getBucketContents (bucketName , prefixHint )
154
-
155
- if err != nil {
156
- return []string {}, err
157
- }
158
-
159
- paths := make ([]string , 0 , len (entries ))
160
-
161
- for _ , entry := range entries {
162
- paths = append (paths , * entry .Key )
155
+ // BucketFiles returns all the files in bucketName immediately under directoryPrefix
156
+ func (client * s3client ) BucketFiles (bucketName string , directoryPrefix string ) ([]string , error ) {
157
+ if ! strings .HasSuffix (directoryPrefix , "/" ) {
158
+ directoryPrefix = directoryPrefix + "/"
159
+ }
160
+ var (
161
+ continuationToken * string
162
+ truncated bool
163
+ paths []string
164
+ )
165
+ for continuationToken , truncated = nil , true ; truncated ; {
166
+ s3ListChunk , err := client .ChunkedBucketList (bucketName , directoryPrefix , continuationToken )
167
+ if err != nil {
168
+ return []string {}, err
169
+ }
170
+ truncated = s3ListChunk .Truncated
171
+ continuationToken = s3ListChunk .ContinuationToken
172
+ paths = append (paths , s3ListChunk .Paths ... )
163
173
}
164
174
return paths , nil
165
175
}
@@ -189,6 +199,47 @@ func (client *s3client) BucketFileVersions(bucketName string, remotePath string)
189
199
return versions , nil
190
200
}
191
201
202
+ type BucketListChunk struct {
203
+ Truncated bool
204
+ ContinuationToken * string
205
+ CommonPrefixes []string
206
+ Paths []string
207
+ }
208
+
209
+ // ChunkedBucketList lists the S3 bucket `bucketName` content's under `prefix` one chunk at a time
210
+ //
211
+ // The returned `BucketListChunk` contains part of the files and subdirectories
212
+ // present in `bucketName` under `prefix`. The files are listed in `Paths` and
213
+ // the subdirectories in `CommonPrefixes`. If the returned chunk does not
214
+ // include all the files and subdirectories, the `Truncated` flag will be set
215
+ // to `true` and the `ContinuationToken` can be used to retrieve the next chunk.
216
+ func (client * s3client ) ChunkedBucketList (bucketName string , prefix string , continuationToken * string ) (BucketListChunk , error ) {
217
+ params := & s3.ListObjectsV2Input {
218
+ Bucket : aws .String (bucketName ),
219
+ ContinuationToken : continuationToken ,
220
+ Delimiter : aws .String ("/" ),
221
+ Prefix : aws .String (prefix ),
222
+ }
223
+ response , err := client .client .ListObjectsV2 (params )
224
+ if err != nil {
225
+ return BucketListChunk {}, err
226
+ }
227
+ commonPrefixes := make ([]string , 0 , len (response .CommonPrefixes ))
228
+ paths := make ([]string , 0 , len (response .Contents ))
229
+ for _ , commonPrefix := range response .CommonPrefixes {
230
+ commonPrefixes = append (commonPrefixes , * commonPrefix .Prefix )
231
+ }
232
+ for _ , path := range response .Contents {
233
+ paths = append (paths , * path .Key )
234
+ }
235
+ return BucketListChunk {
236
+ Truncated : * response .IsTruncated ,
237
+ ContinuationToken : response .NextContinuationToken ,
238
+ CommonPrefixes : commonPrefixes ,
239
+ Paths : paths ,
240
+ }, nil
241
+ }
242
+
192
243
func (client * s3client ) UploadFile (bucketName string , remotePath string , localPath string , options UploadFileOptions ) (string , error ) {
193
244
uploader := s3manager .NewUploaderWithClient (client .client )
194
245
@@ -398,51 +449,6 @@ func (client *s3client) DeleteFile(bucketName string, remotePath string) error {
398
449
return err
399
450
}
400
451
401
- func (client * s3client ) getBucketContents (bucketName string , prefix string ) (map [string ]* s3.Object , error ) {
402
- bucketContents := map [string ]* s3.Object {}
403
- marker := ""
404
-
405
- for {
406
- listObjectsResponse , err := client .client .ListObjects (& s3.ListObjectsInput {
407
- Bucket : aws .String (bucketName ),
408
- Prefix : aws .String (prefix ),
409
- Marker : aws .String (marker ),
410
- })
411
-
412
- if err != nil {
413
- return bucketContents , err
414
- }
415
-
416
- lastKey := ""
417
-
418
- for _ , key := range listObjectsResponse .Contents {
419
- bucketContents [* key .Key ] = key
420
-
421
- lastKey = * key .Key
422
- }
423
-
424
- if * listObjectsResponse .IsTruncated {
425
- prevMarker := marker
426
- if listObjectsResponse .NextMarker == nil {
427
- // From the s3 docs: If response does not include the
428
- // NextMarker and it is truncated, you can use the value of the
429
- // last Key in the response as the marker in the subsequent
430
- // request to get the next set of object keys.
431
- marker = lastKey
432
- } else {
433
- marker = * listObjectsResponse .NextMarker
434
- }
435
- if marker == prevMarker {
436
- return nil , errors .New ("Unable to list all bucket objects; perhaps this is a CloudFront S3 bucket that needs its `Query String Forwarding and Caching` set to `Forward all, cache based on all`?" )
437
- }
438
- } else {
439
- break
440
- }
441
- }
442
-
443
- return bucketContents , nil
444
- }
445
-
446
452
func (client * s3client ) getBucketVersioning (bucketName string ) (bool , error ) {
447
453
params := & s3.GetBucketVersioningInput {
448
454
Bucket : aws .String (bucketName ),
0 commit comments