s3p 3.5.4 → 3.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +81 -0
- package/build/S3Parallel/Lib/LibMisc.js +1 -1
- package/build/S3Parallel/Lib/LibMisc.js.map +1 -1
- package/build/S3Parallel/Lib/LocalFileLister.js +4 -0
- package/build/S3Parallel/Lib/LocalFileLister.js.map +10 -0
- package/build/S3Parallel/Lib/S3.js +1 -1
- package/build/S3Parallel/Lib/S3.js.map +1 -1
- package/build/S3Parallel/Lib/index.js +1 -0
- package/build/S3Parallel/S3Comprehensions.js +1 -1
- package/build/S3Parallel/S3Comprehensions.js.map +1 -1
- package/build/S3Parallel/S3P.js +1 -1
- package/build/S3Parallel/S3P.js.map +1 -1
- package/build/S3Parallel/S3PCli.js +1 -1
- package/build/S3Parallel/S3PCli.js.map +1 -1
- package/build/S3Parallel/S3PCliCommands.js +1 -1
- package/build/S3Parallel/S3PCliCommands.js.map +1 -1
- package/package.json +17 -10
- package/source/S3Parallel/Lib/LibMisc.caf +86 -19
- package/source/S3Parallel/Lib/LocalFileLister.caf +99 -0
- package/source/S3Parallel/Lib/S3.caf +82 -43
- package/source/S3Parallel/S3Comprehensions.caf +14 -7
- package/source/S3Parallel/S3P.caf +8 -2
- package/source/S3Parallel/S3PCli.caf +19 -1
- package/source/S3Parallel/S3PCliCommands.caf +13 -8
package/package.json
CHANGED
|
@@ -5,24 +5,26 @@
|
|
|
5
5
|
},
|
|
6
6
|
"bugs": "https:/github.com/generalui/s3p/issues",
|
|
7
7
|
"dependencies": {
|
|
8
|
-
"@art-suite/cli": "
|
|
8
|
+
"@art-suite/cli": "2.2",
|
|
9
9
|
"@aws-sdk/client-s3": "^3.436.0",
|
|
10
|
+
"@aws-sdk/lib-storage": "^3.635.0",
|
|
10
11
|
"art-class-system": "^1.11.13",
|
|
11
12
|
"art-communication-status": "^1.6.0",
|
|
12
|
-
"art-standard-lib": "^1.
|
|
13
|
+
"art-standard-lib": "^1.73.3",
|
|
13
14
|
"caffeine-script-runtime": "^1.14.0",
|
|
15
|
+
"glob": "^11.0.0",
|
|
14
16
|
"neptune-namespaces-runtime": "^3.2.9",
|
|
15
17
|
"shell-escape": "^0.2.0"
|
|
16
18
|
},
|
|
17
19
|
"description": "S3P is a CLI and library that is 5x to 50x faster than aws-cli for bulk S3 operations including: summarize, compare, copy and sync.",
|
|
18
20
|
"devDependencies": {
|
|
21
|
+
"@art-suite/standard-package-builder": "^1.2.6",
|
|
19
22
|
"art-build-configurator": "^1.29.3",
|
|
20
23
|
"art-testbench": "^3.2.2",
|
|
21
|
-
"caffeine-script": "^0.
|
|
24
|
+
"caffeine-script": "^0.75.1",
|
|
22
25
|
"jest": "^29.3.1",
|
|
23
26
|
"mock-fs": "^5.0.0",
|
|
24
|
-
"neptune-namespaces": "^4.2.9"
|
|
25
|
-
"npm-watch": "^0.11.0"
|
|
27
|
+
"neptune-namespaces": "^4.2.9"
|
|
26
28
|
},
|
|
27
29
|
"homepage": "https://github.com/generalui/s3p",
|
|
28
30
|
"jest": {
|
|
@@ -30,6 +32,10 @@
|
|
|
30
32
|
"build/**/*.js",
|
|
31
33
|
"!build/test/**/*.js"
|
|
32
34
|
],
|
|
35
|
+
"coveragePathIgnorePatterns": [
|
|
36
|
+
"/index.js$",
|
|
37
|
+
"/namespace.js$"
|
|
38
|
+
],
|
|
33
39
|
"testEnvironment": "node",
|
|
34
40
|
"testPathIgnorePatterns": [
|
|
35
41
|
"/node_modules/",
|
|
@@ -44,14 +50,15 @@
|
|
|
44
50
|
"url": "https://github.com/generalui/s3p.git"
|
|
45
51
|
},
|
|
46
52
|
"scripts": {
|
|
47
|
-
"build": "caf -m -c source -o build\nnpm run nn",
|
|
48
|
-
"build-clean": "
|
|
53
|
+
"build": "rsync -av --exclude='*.caf' ./source/ ./build/\ncaf -m -c source -o build\nnpm run nn",
|
|
54
|
+
"build-clean": "# BUILD CLEAN START\n# - building into: build-temp\n#\nrm -rf ./__build-old__\nrsync -av --exclude='*.caf' ./source/ ./build-temp/\ncaf -m -c source -o build-temp\nnpm run nn\n\nnn build-temp/*\n\n# BUILD CLEAN FINIALIZING\n# - replace old build in: build\n# - with new build from: build-temp\n#\nmv build __build-old__\nmv build-temp build\nrm -rf ./__build-old__",
|
|
55
|
+
"build-watch": "nodemon -e js,ts,jsx,tsx,caf,coffee,json -i \"build/**\" -x npm run build",
|
|
49
56
|
"clean": "rm -rf build/*",
|
|
50
57
|
"nn": "nn build/* ",
|
|
51
|
-
"test": "
|
|
52
|
-
"watch": "
|
|
58
|
+
"test": "jest",
|
|
59
|
+
"test-watch": "jest --watch"
|
|
53
60
|
},
|
|
54
|
-
"version": "3.
|
|
61
|
+
"version": "3.6.1",
|
|
55
62
|
"watch": {
|
|
56
63
|
"build": {
|
|
57
64
|
"extensions": [
|
|
@@ -1,22 +1,89 @@
|
|
|
1
1
|
import &StandardImport, {} &path
|
|
2
2
|
exec = &util.promisify &child_process.exec
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
4
|
+
{}
|
|
5
|
+
createS3Url: (bucket, folder, key) ->
|
|
6
|
+
if folder
|
|
7
|
+
path.join folder, key
|
|
8
|
+
else
|
|
9
|
+
"" s3://#{bucket}/#{key}
|
|
10
|
+
|
|
11
|
+
shellExec: (command) ->
|
|
12
|
+
exec command
|
|
13
|
+
.then ({stdout}) -> stdout
|
|
14
|
+
|
|
15
|
+
humanByteSize: (bytes, decimals = 2) ->
|
|
16
|
+
switch
|
|
17
|
+
when bytes < 1024 then "" #{bytes/1024**0 | 0}_B
|
|
18
|
+
when bytes < 1024**2 then "" #{(bytes/1024**1).toFixed decimals}kB
|
|
19
|
+
when bytes < 1024**3 then "" #{(bytes/1024**2).toFixed decimals}mB
|
|
20
|
+
when bytes < 1024**4 then "" #{(bytes/1024**3).toFixed decimals}gB
|
|
21
|
+
when bytes < 1024**5 then "" #{(bytes/1024**4).toFixed decimals}tB
|
|
22
|
+
when bytes < 1024**6 then "" #{(bytes/1024**5).toFixed decimals}pB
|
|
23
|
+
else "" #{(bytes/1024**6).toFixed decimals}eB
|
|
24
|
+
|
|
25
|
+
s3UrlPattern = ///i ^s3://([^\/]+)\/?(.*)$
|
|
26
|
+
|
|
27
|
+
# https://mybucket.s3.amazonaws.com/prefix
|
|
28
|
+
# https://mybucket.s3-us-west-2.amazonaws.com/prefix
|
|
29
|
+
httpUrlPattern =
|
|
30
|
+
///i
|
|
31
|
+
^
|
|
32
|
+
https?://
|
|
33
|
+
|
|
34
|
+
([^.]+) # bucket
|
|
35
|
+
|
|
36
|
+
\.s3
|
|
37
|
+
|
|
38
|
+
([-.]([-a-z0-9]+))? # region
|
|
39
|
+
|
|
40
|
+
\.amazonaws\.com
|
|
41
|
+
|
|
42
|
+
(\/(.+))? # prefix
|
|
43
|
+
$
|
|
44
|
+
|
|
45
|
+
# https://s3-us-west-2.amazonaws.com/mybucket/prefix
|
|
46
|
+
regionFirstHttpUrlPattern =
|
|
47
|
+
///i
|
|
48
|
+
^
|
|
49
|
+
https?://s3[-.]
|
|
50
|
+
|
|
51
|
+
([-a-z0-9]+) # region
|
|
52
|
+
|
|
53
|
+
\.amazonaws\.com
|
|
54
|
+
|
|
55
|
+
\/([^/]+) # bucket
|
|
56
|
+
|
|
57
|
+
(\/(.+))? # prefix
|
|
58
|
+
$
|
|
59
|
+
|
|
60
|
+
parseS3Url = (input) ->
|
|
61
|
+
if isString input
|
|
62
|
+
input = input.trim()
|
|
63
|
+
|
|
64
|
+
log parseS3Url: {} input
|
|
65
|
+
|
|
66
|
+
switch
|
|
67
|
+
when match = input.match s3UrlPattern
|
|
68
|
+
[_, bucket, prefix] = match
|
|
69
|
+
|
|
70
|
+
when match = input.match httpUrlPattern
|
|
71
|
+
[_, bucket, _, region, _, prefix] = match
|
|
72
|
+
|
|
73
|
+
when match = input.match regionFirstHttpUrlPattern
|
|
74
|
+
[_, region, bucket, _, prefix] = match
|
|
75
|
+
|
|
76
|
+
else
|
|
77
|
+
folder = input
|
|
78
|
+
|
|
79
|
+
{} bucket, prefix, region, folder
|
|
80
|
+
else {}
|
|
81
|
+
|
|
82
|
+
processArgsIntoOptions = (args = []) ->
|
|
83
|
+
[fromStr, toStr] = args
|
|
84
|
+
if fromStr
|
|
85
|
+
{bucket, prefix, region, folder} = parseS3Url fromStr
|
|
86
|
+
if toStr
|
|
87
|
+
{bucket: toBucket, prefix: toPrefix, region: toRegion, folder: tofolder} = parseS3Url toStr
|
|
88
|
+
|
|
89
|
+
merge {} bucket, prefix, region, folder, toBucket, toPrefix, toRegion, tofolder
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import &StandardImport, &glob, {} &path, fs: &fs.promises
|
|
2
|
+
|
|
3
|
+
class LocalFileLister extends BaseClass
|
|
4
|
+
|
|
5
|
+
constructor: ({fromFolder, _allFiles, _getFileStat}) ->
|
|
6
|
+
@_fromFolder =
|
|
7
|
+
# ensure has trailing slash
|
|
8
|
+
path.join fromFolder, "aaa"
|
|
9
|
+
.replace /aaa$/, ''
|
|
10
|
+
|
|
11
|
+
@_custom_getFileStat = _getFileStat
|
|
12
|
+
|
|
13
|
+
@_fileStats = {}
|
|
14
|
+
|
|
15
|
+
@_allFilesPromise =
|
|
16
|
+
if _allFiles
|
|
17
|
+
Promise.resolve _allFiles
|
|
18
|
+
else
|
|
19
|
+
glob
|
|
20
|
+
path.join fromFolder, "**"
|
|
21
|
+
nodir: true
|
|
22
|
+
|
|
23
|
+
.then ({found}) -> found
|
|
24
|
+
|
|
25
|
+
.then (files) ->
|
|
26
|
+
array file in files.sort()
|
|
27
|
+
file = file.slice fromFolder.length + 1, file.length
|
|
28
|
+
|
|
29
|
+
@getter files: -> @_allFilesPromise
|
|
30
|
+
@getter :fromFolder
|
|
31
|
+
|
|
32
|
+
## list
|
|
33
|
+
IN:
|
|
34
|
+
limit: maximum number of files to return
|
|
35
|
+
startAfter: if present, returns files strictly greater than startAfter
|
|
36
|
+
stopAt: if present, returns files up to and including stopAt
|
|
37
|
+
|
|
38
|
+
OUT:
|
|
39
|
+
[]
|
|
40
|
+
Key:
|
|
41
|
+
LastModified:
|
|
42
|
+
Size
|
|
43
|
+
Owner
|
|
44
|
+
list: ({limit=1000, startAfter, stopAt}) ->
|
|
45
|
+
if present startAfter then @_findStartIndex startAfter
|
|
46
|
+
else Promise.deepAll {} @files, startIndex: 0
|
|
47
|
+
.then ({files, startIndex}) ->
|
|
48
|
+
out = []
|
|
49
|
+
i = startIndex
|
|
50
|
+
tilIndex = min files.length, startIndex + limit
|
|
51
|
+
stopAt ?= peek files
|
|
52
|
+
while i < tilIndex && files[i] <= stopAt
|
|
53
|
+
out.push @_getFileStat files[i]
|
|
54
|
+
i++
|
|
55
|
+
|
|
56
|
+
Promise.all out
|
|
57
|
+
|
|
58
|
+
#################################################
|
|
59
|
+
# PRIVATE
|
|
60
|
+
#################################################
|
|
61
|
+
##
|
|
62
|
+
IN: file name
|
|
63
|
+
OUT: []
|
|
64
|
+
Key: file
|
|
65
|
+
LastModified: stats.mtime
|
|
66
|
+
Size: stats.size
|
|
67
|
+
Owner:
|
|
68
|
+
DisplayName: stats.uid.toString()
|
|
69
|
+
ID: stats.uid.toString()
|
|
70
|
+
_getFileStat: (file) ->
|
|
71
|
+
if @_custom_getFileStat
|
|
72
|
+
@_custom_getFileStat file
|
|
73
|
+
else
|
|
74
|
+
if f = @_fileStats[file]
|
|
75
|
+
Promise.resolve f
|
|
76
|
+
else
|
|
77
|
+
filePath = path.join @fromFolder, file
|
|
78
|
+
fs.stat path.join @fromFolder, file
|
|
79
|
+
.then (stats) -> @_fileStats[file] =
|
|
80
|
+
Key: file
|
|
81
|
+
LastModified: stats.mtime
|
|
82
|
+
Size: stats.size
|
|
83
|
+
Owner:
|
|
84
|
+
DisplayName: stats.uid.toString()
|
|
85
|
+
ID: stats.uid.toString()
|
|
86
|
+
|
|
87
|
+
_findStartIndex: (startAfter) ->
|
|
88
|
+
@files
|
|
89
|
+
.then (files) ->
|
|
90
|
+
j = 0
|
|
91
|
+
k = files.length
|
|
92
|
+
while j < k
|
|
93
|
+
m = (j + k) / 2 | 0
|
|
94
|
+
pivot = files[m]
|
|
95
|
+
switch
|
|
96
|
+
when startAfter == pivot then k = j = m
|
|
97
|
+
when startAfter < pivot then k = m
|
|
98
|
+
else j = m + 1
|
|
99
|
+
{} files, startIndex: j
|
|
@@ -1,22 +1,14 @@
|
|
|
1
|
-
import &StandardImport, &FsEasy, &LibMisc, &@aws-sdk/client-s3, {} &shellEscape, &util.promisify, &
|
|
1
|
+
import &StandardImport, &FsEasy, &LibMisc, &@aws-sdk/client-s3, &@aws-sdk/lib-storage, {} &shellEscape, &util.promisify, &path
|
|
2
2
|
escape = (single) -> shellEscape [] single
|
|
3
3
|
|
|
4
|
+
fs = merge
|
|
5
|
+
{createReadStream} = &fs
|
|
6
|
+
{copyFile} = &fs.promises
|
|
7
|
+
|
|
4
8
|
# &@aws-sdk/client-s3.config.setPromisesDependency &bluebird
|
|
5
9
|
|
|
6
10
|
class S3 extends BaseClass
|
|
7
11
|
|
|
8
|
-
@awsSdkS3 = new S3Client merge region: process.env.AWS_REGION # useAccelerateEndpoint: true
|
|
9
|
-
# https://docs.aws.amazon.com/AmazonS3/latest/dev/transfer-acceleration-examples.html
|
|
10
|
-
|
|
11
|
-
@classGetter
|
|
12
|
-
s3: ->
|
|
13
|
-
listBuckets: (options = {}) -> Promise.resolve @awsSdkS3.send new ListBucketsCommand options
|
|
14
|
-
listObjectsV2: (options = {}) -> Promise.resolve @awsSdkS3.send new ListObjectsV2Command options
|
|
15
|
-
getObject: (options = {}) -> Promise.resolve @awsSdkS3.send new GetObjectCommand options
|
|
16
|
-
copyObject: (options = {}) -> Promise.resolve @awsSdkS3.send new CopyObjectCommand options
|
|
17
|
-
deleteObject: (options = {}) -> Promise.resolve @awsSdkS3.send new DeleteObjectCommand options
|
|
18
|
-
headObject: (options = {}) -> Promise.resolve @awsSdkS3.send new HeadObjectCommand options
|
|
19
|
-
|
|
20
12
|
@commonCopyOptionsSdkKeys =
|
|
21
13
|
:ACL
|
|
22
14
|
:CacheControl
|
|
@@ -30,12 +22,41 @@ class S3 extends BaseClass
|
|
|
30
22
|
|
|
31
23
|
@commonSdkCopyOptionsByLowerCamelCaseKeys = object k from @commonCopyOptionsSdkKeys with-key lowerCamelCase k
|
|
32
24
|
|
|
33
|
-
|
|
25
|
+
## constructor
|
|
26
|
+
IN: options:
|
|
27
|
+
region: string, default: process.env.AWS_REGION
|
|
28
|
+
useAccelerateEndpoint: boolean, default: false
|
|
29
|
+
|
|
30
|
+
constructor: (options = []) ->
|
|
31
|
+
@awsSdkS3 = new S3Client merge
|
|
32
|
+
region: process.env.AWS_REGION
|
|
33
|
+
{region, useAccelerateEndpoint} = options
|
|
34
|
+
|
|
35
|
+
@getter
|
|
36
|
+
s3: ->
|
|
37
|
+
listBuckets: (options = {}) -> Promise.resolve @awsSdkS3.send new ListBucketsCommand options
|
|
38
|
+
listObjectsV2: (options = {}) -> Promise.resolve @awsSdkS3.send new ListObjectsV2Command options
|
|
39
|
+
getObject: (options = {}) -> Promise.resolve @awsSdkS3.send new GetObjectCommand options
|
|
40
|
+
copyObject: (options = {}) -> Promise.resolve @awsSdkS3.send new CopyObjectCommand options
|
|
41
|
+
deleteObject: (options = {}) -> Promise.resolve @awsSdkS3.send new DeleteObjectCommand options
|
|
42
|
+
headObject: (options = {}) -> Promise.resolve @awsSdkS3.send new HeadObjectCommand options
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
listBuckets: ->
|
|
34
46
|
@s3.listBuckets()
|
|
35
47
|
.then ({Buckets}) ->
|
|
36
48
|
object {Name, CreationDate} from Buckets with-key Name with CreationDate
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
## list
|
|
51
|
+
OUT:
|
|
52
|
+
[]
|
|
53
|
+
Key:
|
|
54
|
+
LastModified:
|
|
55
|
+
Size
|
|
56
|
+
ETag
|
|
57
|
+
StorageClass
|
|
58
|
+
Owner
|
|
59
|
+
list: ({bucket, prefix, limit=1000, fetchOwner, startAfter, stopAt}) ->
|
|
39
60
|
startTime = currentSecond()
|
|
40
61
|
@s3.listObjectsV2
|
|
41
62
|
Bucket: bucket
|
|
@@ -43,6 +64,7 @@ class S3 extends BaseClass
|
|
|
43
64
|
MaxKeys: limit
|
|
44
65
|
StartAfter: startAfter
|
|
45
66
|
FetchOwner: fetchOwner
|
|
67
|
+
|
|
46
68
|
.tapCatch (error) ->
|
|
47
69
|
log.error S3.list-error: {} bucket, prefix, startAfter, limit, error
|
|
48
70
|
|
|
@@ -56,14 +78,12 @@ class S3 extends BaseClass
|
|
|
56
78
|
else if duration > 60
|
|
57
79
|
log.warn S3.list-slow: {} bucket, prefix, startAfter, limit, duration, results: merge results, Contents: "Array #{results.Contents.length}"
|
|
58
80
|
|
|
59
|
-
|
|
81
|
+
if present stopAt
|
|
82
|
+
array item in-array results.Contents when item.Key <= stopAt
|
|
83
|
+
else
|
|
84
|
+
results.Contents
|
|
60
85
|
|
|
61
|
-
##
|
|
62
|
-
TODO:
|
|
63
|
-
For large files (>5GB), we need to just use the CLI:
|
|
64
|
-
aws s3 cp s3://resbio-fastq-drop-internal/180316_NB501104_0354_AHM2L2BGX5/Undetermined/Undetermined_S0_L001_R1_001.fastq.gz s3://genui-dynamodb-migration-scratch/large-test.gz
|
|
65
|
-
We can add --quiet (probably)
|
|
66
|
-
Further, that should use acceleration, if enabled - still need to test that.
|
|
86
|
+
## copy
|
|
67
87
|
IN:
|
|
68
88
|
options:
|
|
69
89
|
size: Bytes (optional) if provided and if bigger than the max size allowed by copyObject, largeCopy is used
|
|
@@ -73,25 +93,28 @@ class S3 extends BaseClass
|
|
|
73
93
|
key: <String>
|
|
74
94
|
fromKey: from-key (default: key)
|
|
75
95
|
toKey: to-key (default: key)
|
|
96
|
+
fromFolder: string
|
|
76
97
|
|
|
77
98
|
toFolder: local folder
|
|
78
99
|
OUT:
|
|
79
100
|
Promise.then -> see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#copyObject-property > Callback > Parameters
|
|
80
101
|
{} CopyObjectResult, ...
|
|
81
|
-
|
|
102
|
+
copy: (options) ->
|
|
82
103
|
if options extract largeCopyThreshold, scratchState
|
|
83
104
|
copyScratchState = scratchState.copyScratchState ?= {}
|
|
84
105
|
|
|
85
106
|
@_normalizeCopyOptions(options) extract
|
|
86
|
-
fromBucket,
|
|
87
|
-
toFolder
|
|
88
|
-
pretend
|
|
89
|
-
|
|
107
|
+
fromBucket, fromFolder, fromKey
|
|
108
|
+
toBucket, toFolder, toKey
|
|
109
|
+
pretend, verbose
|
|
110
|
+
size
|
|
90
111
|
|
|
91
112
|
if size >= largeCopyThreshold
|
|
92
113
|
@largeCopy {}
|
|
93
114
|
commonCopyOptions: @extractCommonCopyOptions options
|
|
94
|
-
fromBucket,
|
|
115
|
+
fromBucket, fromFolder, fromKey
|
|
116
|
+
toBucket, toFolder, toKey
|
|
117
|
+
pretend, verbose
|
|
95
118
|
|
|
96
119
|
else
|
|
97
120
|
copyOptions = merge
|
|
@@ -116,14 +139,30 @@ class S3 extends BaseClass
|
|
|
116
139
|
if pretend
|
|
117
140
|
timeout 1 -> pretend: true
|
|
118
141
|
|
|
142
|
+
else if fromFolder
|
|
143
|
+
fromFilePath = path.join fromFolder, fromKey
|
|
144
|
+
if toBucket
|
|
145
|
+
new Upload
|
|
146
|
+
client: @s3
|
|
147
|
+
params:
|
|
148
|
+
Bucket: toBucket
|
|
149
|
+
Key: toKey
|
|
150
|
+
Body: fs.createReadStream fromFilePath
|
|
151
|
+
|
|
152
|
+
.done()
|
|
153
|
+
|
|
154
|
+
else
|
|
155
|
+
createParentDirs toLocalFile, copyScratchState
|
|
156
|
+
.then -> fs.copyFile fromFilePath, toLocalFile
|
|
157
|
+
|
|
119
158
|
else if toFolder
|
|
120
159
|
if /\/$/.test(toKey) && size == 0 # this is a folder
|
|
121
|
-
createParentDirs
|
|
122
|
-
|
|
123
|
-
createWriteStreamSafe
|
|
124
|
-
toLocalFile
|
|
160
|
+
createParentDirs
|
|
161
|
+
path.join toLocalFile, "ignored-file-name"
|
|
125
162
|
copyScratchState
|
|
126
163
|
|
|
164
|
+
else
|
|
165
|
+
createWriteStreamSafe toLocalFile, copyScratchState
|
|
127
166
|
.then (writeStream) -> new Promise (resolve, reject) ->
|
|
128
167
|
@s3.getObject copyOptions
|
|
129
168
|
.then ({Body}) ->
|
|
@@ -135,12 +174,12 @@ class S3 extends BaseClass
|
|
|
135
174
|
else
|
|
136
175
|
@s3.copyObject copyOptions
|
|
137
176
|
|
|
138
|
-
|
|
177
|
+
delete: (options) ->
|
|
139
178
|
@s3.deleteObject
|
|
140
179
|
Bucket: options.bucket
|
|
141
180
|
Key: options.key
|
|
142
181
|
|
|
143
|
-
|
|
182
|
+
largeCopy: (options) ->
|
|
144
183
|
options extract commonCopyOptions
|
|
145
184
|
@_normalizeCopyOptions(options) extract
|
|
146
185
|
fromBucket,
|
|
@@ -170,13 +209,13 @@ class S3 extends BaseClass
|
|
|
170
209
|
else
|
|
171
210
|
shellExec command
|
|
172
211
|
|
|
173
|
-
# get object's
|
|
174
|
-
|
|
212
|
+
# get object's metadata
|
|
213
|
+
headObject: ({bucket, key}) ->
|
|
175
214
|
@s3.headObject
|
|
176
215
|
Bucket: bucket
|
|
177
216
|
Key: key
|
|
178
217
|
|
|
179
|
-
|
|
218
|
+
_normalizeCopyOptions: (options) ->
|
|
180
219
|
options extract
|
|
181
220
|
bucket, key
|
|
182
221
|
fromBucket = bucket
|
|
@@ -192,15 +231,15 @@ class S3 extends BaseClass
|
|
|
192
231
|
throw new Error "" Missing one of: fromBucket, toBucket, fromKey, toKey or bucket or key as a default
|
|
193
232
|
merge options, {} fromBucket, fromKey, toBucket, toKey
|
|
194
233
|
|
|
195
|
-
|
|
196
|
-
object sdkKey, lowerCamelCasedKey from
|
|
234
|
+
_getCommonCopyOptionsForSdk: (options) ->
|
|
235
|
+
object sdkKey, lowerCamelCasedKey from S3.commonSdkCopyOptionsByLowerCamelCaseKeys with-key sdkKey
|
|
197
236
|
options[lowerCamelCasedKey]
|
|
198
237
|
|
|
199
|
-
|
|
200
|
-
object v, k from
|
|
238
|
+
extractCommonCopyOptions: (options) ->
|
|
239
|
+
object v, k from S3.commonSdkCopyOptionsByLowerCamelCaseKeys
|
|
201
240
|
options[k]
|
|
202
241
|
|
|
203
|
-
|
|
242
|
+
shouldSyncObjects: (options) ->
|
|
204
243
|
@_normalizeCopyOptions(options) extract fromBucket, toBucket, fromKey, toKey
|
|
205
244
|
Promise.then ->
|
|
206
245
|
if options.size < 1024**2 || !options.size
|
|
@@ -216,7 +255,7 @@ class S3 extends BaseClass
|
|
|
216
255
|
OUT:
|
|
217
256
|
copied: Bool
|
|
218
257
|
status: if not copied, explains why
|
|
219
|
-
|
|
258
|
+
syncObject: (options) ->
|
|
220
259
|
@shouldSyncObjects options
|
|
221
260
|
.then (shouldSync) ->
|
|
222
261
|
if shouldSync
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import &path, &ArtStandardLib, &ArtClassSystem, &Lib
|
|
1
|
+
import &path, &ArtStandardLib, &ArtClassSystem, &Lib
|
|
2
2
|
|
|
3
3
|
class S3Comprehensions
|
|
4
4
|
|
|
@@ -210,7 +210,7 @@ class S3Comprehensions
|
|
|
210
210
|
While it returns true, it will be called once/second/parallel-branch until it returns false.
|
|
211
211
|
|
|
212
212
|
COMPREHENSION
|
|
213
|
-
with: (Item) -> ignored -
|
|
213
|
+
with: (Item) -> ignored - alias of `map`
|
|
214
214
|
when: (Item) -> T/F - if true, pass it on to `map`, else skip
|
|
215
215
|
returning: if present, return this value instead of stats
|
|
216
216
|
into: <ALIAS: returning>
|
|
@@ -234,6 +234,7 @@ class S3Comprehensions
|
|
|
234
234
|
startAfter
|
|
235
235
|
stopAt
|
|
236
236
|
filter
|
|
237
|
+
fromFolder
|
|
237
238
|
|
|
238
239
|
# compare-to options
|
|
239
240
|
compare
|
|
@@ -243,7 +244,7 @@ class S3Comprehensions
|
|
|
243
244
|
# limits
|
|
244
245
|
limit = 1000
|
|
245
246
|
maxListRequests
|
|
246
|
-
listConcurrency = 100 # max open
|
|
247
|
+
listConcurrency = 100 # max open requests
|
|
247
248
|
|
|
248
249
|
# actions
|
|
249
250
|
returnValue
|
|
@@ -257,10 +258,16 @@ class S3Comprehensions
|
|
|
257
258
|
throttle
|
|
258
259
|
stats
|
|
259
260
|
|
|
261
|
+
# s3 options
|
|
262
|
+
region
|
|
263
|
+
useAccelerateEndpoint
|
|
264
|
+
|
|
260
265
|
# internal use only
|
|
261
|
-
s3
|
|
266
|
+
s3
|
|
262
267
|
pwp = new PromiseWorkerPool listConcurrency # internal use only
|
|
263
268
|
|
|
269
|
+
s3 ?= new &Lib/S3 {} region, useAccelerateEndpoint: !!useAccelerateEndpoint
|
|
270
|
+
|
|
264
271
|
if compare && map && toBucket
|
|
265
272
|
throw new Error "cannot use both `compare` and `map` - use mapList instead"
|
|
266
273
|
|
|
@@ -303,7 +310,7 @@ class S3Comprehensions
|
|
|
303
310
|
## applyF
|
|
304
311
|
Apply the custom map or mapList functions
|
|
305
312
|
IN:
|
|
306
|
-
items: list of S3 items in the source
|
|
313
|
+
items: list of S3 items in the source bucket as returned by the AwsSdk s3 list call
|
|
307
314
|
compareItems: (optional, if compare is set), list of items in the toBucket
|
|
308
315
|
compareStartAfter
|
|
309
316
|
compareStopAt
|
|
@@ -373,8 +380,8 @@ class S3Comprehensions
|
|
|
373
380
|
maxOutstanding = Math.max maxOutstanding, outstanding += 2
|
|
374
381
|
|
|
375
382
|
Promise.all []
|
|
376
|
-
pwp.queue -> s3.list {} bucket, limit, fetchOwner, startAfter
|
|
377
|
-
pwp.queue -> s3.list {} bucket, limit, fetchOwner, startAfter: middleKey
|
|
383
|
+
pwp.queue -> s3.list {} bucket, limit, fetchOwner, startAfter, stopAt: middleKey
|
|
384
|
+
pwp.queue -> s3.list {} bucket, limit, fetchOwner, stopAt, startAfter: middleKey
|
|
378
385
|
|
|
379
386
|
.then ([rawLeftItems, rawRightItems]) ->
|
|
380
387
|
rawLeftCount = rawLeftItems?.length
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import &path, &ArtStandardLib, &ArtClassSystem, &Lib
|
|
1
|
+
import &path, &ArtStandardLib, &ArtClassSystem, &Lib
|
|
2
|
+
colors = terminalColors
|
|
2
3
|
|
|
3
4
|
S3C = &S3Comprehensions
|
|
4
5
|
|
|
@@ -304,7 +305,7 @@ class S3P
|
|
|
304
305
|
@_copyWrapper: (options, eachFunction) =>
|
|
305
306
|
options = S3C.normalizeOptions options
|
|
306
307
|
options extract
|
|
307
|
-
s3
|
|
308
|
+
s3
|
|
308
309
|
toKey
|
|
309
310
|
stats
|
|
310
311
|
pretend
|
|
@@ -316,6 +317,11 @@ class S3P
|
|
|
316
317
|
largeCopyPwp
|
|
317
318
|
largeCopyThreshold
|
|
318
319
|
|
|
320
|
+
# s3 options
|
|
321
|
+
region
|
|
322
|
+
useAccelerateEndpoint
|
|
323
|
+
s3 ?= new &Lib/S3 {} region, useAccelerateEndpoint
|
|
324
|
+
|
|
319
325
|
stats ?= {}
|
|
320
326
|
stats.copiedBytes = 0
|
|
321
327
|
stats.copiedBytesPerSecond = 0
|
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
import &StandardImport, &Lib
|
|
2
2
|
|
|
3
|
+
standardFromArgs =
|
|
4
|
+
description: "Specify the bucket and optionally a prefix. Supported forms: local/file/path, s3://bucket/prefix, https://s3.region.amazonaws.com/bucket/prefix and https://bucket.s3.amazonaws.com/prefix"
|
|
5
|
+
argument: "[bucket/prefix]"
|
|
6
|
+
argsToOptions: processArgsIntoOptions
|
|
7
|
+
|
|
8
|
+
standardFromToArgs =
|
|
9
|
+
description: "Specify where 'from' and where 'to' do the operation. Supported forms: local/file/path, s3://bucket/prefix, https://s3.region.amazonaws.com/bucket/prefix and https://bucket.s3.amazonaws.com/prefix"
|
|
10
|
+
argument: "[from [to]]"
|
|
11
|
+
argsToOptions: processArgsIntoOptions
|
|
12
|
+
|
|
3
13
|
allCommandOptions =
|
|
4
14
|
quiet: "" no output
|
|
5
15
|
verbose: "" extra output
|
|
@@ -12,6 +22,7 @@ allCommandOptions =
|
|
|
12
22
|
start-after: :key "" Start iteratating after this key. If 'prefix' is also specified, the set-intersection of the two will be used.
|
|
13
23
|
stop-at: :key "" Iterate up to, and including, this key. If 'prefix' is also specified, the set-intersection of the two will be used.
|
|
14
24
|
fetch-owner: "" If you want to filter on the Owner property of items, you'll need to add this option to fetch that property. Note: this adds about 10% overhead for listing items.
|
|
25
|
+
region: "" The AWS region of the bucket. Will use AWS_REGION environment variable if not specified.
|
|
15
26
|
|
|
16
27
|
pattern:
|
|
17
28
|
"" string OR js:/^any-javascript-regexp/i
|
|
@@ -70,7 +81,7 @@ writeOptions =
|
|
|
70
81
|
OUTPOSTS
|
|
71
82
|
|
|
72
83
|
toBucketOptions =
|
|
73
|
-
to-bucket: argument: :bucket-name
|
|
84
|
+
to-bucket: argument: :bucket-name description: "" The target bucket. It can be the same bucket.
|
|
74
85
|
to-prefix: :key-prefix "" If 'prefix' is specified, the target key will REPLACE its source prefix with toPrefix Otherwise, this is the same as add-prefix.
|
|
75
86
|
add-prefix: :key-prefix "" The source key is prepended with this string for the target bucket.
|
|
76
87
|
to-key: '"js:(key) => key"' "" Provide an arbitrary JavaScript function for re-keying keys.
|
|
@@ -106,12 +117,14 @@ cliCommands =
|
|
|
106
117
|
options: merge allCommandOptions, advancedOptionsForAll,
|
|
107
118
|
long: "" output item size and date as well as key
|
|
108
119
|
raw: "" output one S3 item per line as JSON with all properties return from listObjects
|
|
120
|
+
args: standardFromArgs
|
|
109
121
|
|
|
110
122
|
summarize:
|
|
111
123
|
description: "" Scan all items in one bucket and produce a summary of all the items. Uses s3.listObjectsV2.
|
|
112
124
|
options: merge allCommandOptions,
|
|
113
125
|
summarize-folders: "" show count and size of each folder
|
|
114
126
|
advancedOptionsForAll
|
|
127
|
+
args: standardFromArgs
|
|
115
128
|
examples:
|
|
116
129
|
bucket: :my-bucket
|
|
117
130
|
"" get a detailed summary of item counts and sizes in my-bucket
|
|
@@ -122,6 +135,7 @@ cliCommands =
|
|
|
122
135
|
compare:
|
|
123
136
|
description: "" Compare two buckets and produce a summary of their differences. Uses s3.listObjectsV2.
|
|
124
137
|
options: merge allCommandOptions, toBucketOptions, advancedOptionsForAll
|
|
138
|
+
args: standardFromToArgs
|
|
125
139
|
examples:
|
|
126
140
|
bucket: :my-bucket to-bucket: :my-to-bucket
|
|
127
141
|
"" Compare items from my-bucket with my-to-bucket. Shows how many items exist in both, only one, or are difference sizes.
|
|
@@ -133,6 +147,7 @@ cliCommands =
|
|
|
133
147
|
|
|
134
148
|
NOTE: This overwrites existing files in the target bucket. Try the 'sync' command for smarter copies when some of the files have already been copied.
|
|
135
149
|
options: merge allCommandOptions, toBucketOptions, writeOptions, advancedOptionsForCopy, toFolderOptions
|
|
150
|
+
args: standardFromToArgs
|
|
136
151
|
examples:
|
|
137
152
|
bucket: :my-bucket to-bucket: :my-to-bucket
|
|
138
153
|
"" Copy everything from my-bucket to my-to-bucket
|
|
@@ -165,6 +180,7 @@ cliCommands =
|
|
|
165
180
|
description: "" Only copy files which do not exist in the target bucket. Uses s3.listObjectsV2, s3.copyObject and shell-exec 'aws s3 cp'.
|
|
166
181
|
options: merge allCommandOptions, toBucketOptions, writeOptions, advancedOptionsForCopy,
|
|
167
182
|
overwrite: "" If set, sync will overwrite existing files with different file sizes.
|
|
183
|
+
args: standardFromToArgs
|
|
168
184
|
|
|
169
185
|
examples:
|
|
170
186
|
bucket: :my-bucket to-bucket: :my-to-bucket
|
|
@@ -175,6 +191,7 @@ cliCommands =
|
|
|
175
191
|
options: merge allCommandOptions, advancedOptionsForAll,
|
|
176
192
|
map: :function "" This gets called for each item found. A javascript function of the form (item) => ...
|
|
177
193
|
map-list: :function "" This gets called with an array of items (length between 1 and 1000). A javascript function of the form (itemList) => ...
|
|
194
|
+
args: standardFromArgs
|
|
178
195
|
examples: []
|
|
179
196
|
bucket: :my-bucket
|
|
180
197
|
map: "" "js:(item) => console.log(item)"
|
|
@@ -204,6 +221,7 @@ cliCommands =
|
|
|
204
221
|
""
|
|
205
222
|
If present, this function will be applied to produce the final result after the last call to reduce.
|
|
206
223
|
Form: (finalReduceResult) -> ...
|
|
224
|
+
args: standardFromArgs
|
|
207
225
|
|
|
208
226
|
examples:
|
|
209
227
|
bucket: :my-bucket
|