1
1
const async = require ( 'async' ) ;
2
2
const assert = require ( 'assert' ) ;
3
- const { S3 } = require ( 'aws-sdk' ) ;
3
+ const { createHash } = require ( 'crypto' ) ;
4
+ const {
5
+ S3Client,
6
+ CreateBucketCommand,
7
+ DeleteBucketCommand,
8
+ PutObjectCommand,
9
+ DeleteObjectCommand,
10
+ DeleteObjectsCommand,
11
+ CopyObjectCommand,
12
+ PutBucketVersioningCommand,
13
+ CreateMultipartUploadCommand,
14
+ UploadPartCommand,
15
+ CompleteMultipartUploadCommand,
16
+ ListObjectVersionsCommand,
17
+ GetObjectCommand,
18
+ } = require ( '@aws-sdk/client-s3' ) ;
4
19
5
20
const MockUtapi = require ( '../utilities/mock/Utapi' ) ;
6
21
const getConfig = require ( '../functional/aws-node-sdk/test/support/config' ) ;
7
22
const WAIT_MS = 100 ;
8
23
let s3Client = null ;
9
24
25
+ /**
26
+ * Creates an S3 client that uses MD5 checksums for DeleteObjects operations
27
+ * Based on https://github.com/aws/aws-sdk-js-v3/blob/main/supplemental-docs/MD5_FALLBACK.md
28
+ */
29
+ function createS3ClientWithMD5 ( configuration = { } ) {
30
+ const client = new S3Client ( configuration ) ;
31
+
32
+ client . middlewareStack . add (
33
+ ( next , context ) => async args => {
34
+ const needsMD5 = context . commandName === 'DeleteObjectsCommand' ||
35
+ context . commandName === 'UploadPartCommand' ||
36
+ context . commandName === 'CompleteMultipartUploadCommand' ;
37
+
38
+ if ( ! needsMD5 ) {
39
+ return next ( args ) ;
40
+ }
41
+
42
+ const headers = args . request . headers ;
43
+
44
+ // Remove any checksum headers
45
+ Object . keys ( headers ) . forEach ( header => {
46
+ if (
47
+ header . toLowerCase ( ) . startsWith ( 'x-amz-checksum-' ) ||
48
+ header . toLowerCase ( ) . startsWith ( 'x-amz-sdk-checksum-' )
49
+ ) {
50
+ delete headers [ header ] ;
51
+ }
52
+ } ) ;
53
+
54
+ // Add MD5
55
+ if ( args . request . body ) {
56
+ const bodyContent = Buffer . from ( args . request . body ) ;
57
+ // Create a new hash instance for each request
58
+ headers [ 'Content-MD5' ] = createHash ( 'md5' ) . update ( bodyContent ) . digest ( 'base64' ) ;
59
+ }
60
+
61
+ return await next ( args ) ;
62
+ } ,
63
+ {
64
+ step : 'build' ,
65
+ }
66
+ ) ;
67
+ return client ;
68
+ }
69
+
10
70
function wait ( timeoutMs , cb ) {
11
71
setTimeout ( cb , timeoutMs ) ;
12
72
}
73
+
13
74
function createBucket ( bucket , cb ) {
14
- return s3Client . createBucket ( {
15
- Bucket : bucket ,
16
- } , ( err , data ) => {
17
- assert . ifError ( err ) ;
18
- return cb ( err , data ) ;
19
- } ) ;
75
+ return s3Client . send ( new CreateBucketCommand ( { Bucket : bucket } ) )
76
+ . then ( data => cb ( null , data ) )
77
+ . catch ( cb ) ;
20
78
}
79
+
21
80
function deleteBucket ( bucket , cb ) {
22
- return s3Client . deleteBucket ( {
23
- Bucket : bucket ,
24
- } , err => {
25
- assert . ifError ( err ) ;
26
- return cb ( err ) ;
27
- } ) ;
81
+ return s3Client . send ( new DeleteBucketCommand ( { Bucket : bucket } ) )
82
+ . then ( ( ) => cb ( ) )
83
+ . catch ( cb ) ;
28
84
}
85
+
29
86
function putObject ( bucket , key , size , cb ) {
30
- return s3Client . putObject ( {
87
+ const body = Buffer . alloc ( size ) ;
88
+ const params = {
31
89
Bucket : bucket ,
32
90
Key : key ,
33
- Body : Buffer . alloc ( size ) ,
34
- } , ( err , data ) => {
35
- assert . ifError ( err ) ;
36
- return cb ( err , data ) ;
37
- } ) ;
91
+ Body : body ,
92
+ } ;
93
+ return s3Client . send ( new PutObjectCommand ( params ) )
94
+ . then ( data => cb ( null , data ) )
95
+ . catch ( cb ) ;
38
96
}
97
+
39
98
function deleteObject ( bucket , key , cb ) {
40
- return s3Client . deleteObject ( {
41
- Bucket : bucket ,
42
- Key : key ,
43
- } , err => {
44
- assert . ifError ( err ) ;
45
- return cb ( err ) ;
46
- } ) ;
99
+ return s3Client . send ( new DeleteObjectCommand ( { Bucket : bucket , Key : key } ) )
100
+ . then ( ( ) => cb ( ) )
101
+ . catch ( cb ) ;
47
102
}
103
+
48
104
function deleteObjects ( bucket , keys , cb ) {
49
- const objects = keys . map ( key => {
50
- const keyObj = {
51
- Key : key ,
52
- } ;
53
- return keyObj ;
54
- } ) ;
105
+ const objects = keys . map ( key => ( { Key : key } ) ) ;
106
+ const deleteRequest = { Objects : objects , Quiet : true } ;
55
107
const params = {
56
108
Bucket : bucket ,
57
- Delete : {
58
- Objects : objects ,
59
- Quiet : true ,
60
- } ,
109
+ Delete : deleteRequest ,
61
110
} ;
62
- return s3Client . deleteObjects ( params , err => {
63
- assert . ifError ( err ) ;
64
- return cb ( err ) ;
65
- } ) ;
111
+ return s3Client . send ( new DeleteObjectsCommand ( params ) )
112
+ . then ( ( ) => cb ( ) )
113
+ . catch ( cb ) ;
66
114
}
115
+
67
116
function copyObject ( bucket , key , cb ) {
68
- return s3Client . copyObject ( {
69
- Bucket : bucket ,
70
- CopySource : `/${ bucket } /${ key } ` ,
71
- Key : `${ key } -copy` ,
72
- } , err => {
73
- assert . ifError ( err ) ;
74
- return cb ( err ) ;
75
- } ) ;
117
+ const params = { Bucket : bucket , CopySource : `${ bucket } /${ key } ` , Key : `${ key } -copy` } ;
118
+ return s3Client . send ( new CopyObjectCommand ( params ) )
119
+ . then ( ( ) => cb ( ) )
120
+ . catch ( cb ) ;
76
121
}
122
+
77
123
function enableVersioning ( bucket , enable , cb ) {
78
- const versioningStatus = {
79
- Status : enable ? 'Enabled' : 'Disabled' ,
80
- } ;
81
- return s3Client . putBucketVersioning ( {
82
- Bucket : bucket ,
83
- VersioningConfiguration : versioningStatus ,
84
- } , err => {
85
- assert . ifError ( err ) ;
86
- return cb ( err ) ;
87
- } ) ;
124
+ const versioningStatus = { Status : enable ? 'Enabled' : 'Disabled' } ;
125
+ const params = { Bucket : bucket , VersioningConfiguration : versioningStatus } ;
126
+ return s3Client . send ( new PutBucketVersioningCommand ( params ) )
127
+ . then ( ( ) => cb ( ) )
128
+ . catch ( cb ) ;
88
129
}
130
+
89
131
function deleteVersionList ( versionList , bucket , callback ) {
90
132
if ( versionList === undefined || versionList . length === 0 ) {
91
133
return callback ( ) ;
92
134
}
93
- const params = { Bucket : bucket , Delete : { Objects : [ ] } } ;
135
+ const deleteRequest = { Objects : [ ] } ;
94
136
versionList . forEach ( version => {
95
- params . Delete . Objects . push ( {
96
- Key : version . Key , VersionId : version . VersionId ,
97
- } ) ;
137
+ deleteRequest . Objects . push ( { Key : version . Key , VersionId : version . VersionId } ) ;
98
138
} ) ;
99
-
100
- return s3Client . deleteObjects ( params , callback ) ;
139
+ const params = {
140
+ Bucket : bucket ,
141
+ Delete : deleteRequest ,
142
+ } ;
143
+ return s3Client . send ( new DeleteObjectsCommand ( params ) )
144
+ . then ( ( ) => callback ( ) )
145
+ . catch ( callback ) ;
101
146
}
147
+
102
148
function removeAllVersions ( params , callback ) {
103
149
const bucket = params . Bucket ;
104
150
async . waterfall ( [
105
- cb => s3Client . listObjectVersions ( params , cb ) ,
106
- ( data , cb ) => deleteVersionList ( data . DeleteMarkers , bucket ,
107
- err => cb ( err , data ) ) ,
108
- ( data , cb ) => deleteVersionList ( data . Versions , bucket ,
109
- err => cb ( err , data ) ) ,
151
+ cb => s3Client . send ( new ListObjectVersionsCommand ( params ) )
152
+ . then ( data => cb ( null , data ) )
153
+ . catch ( cb ) ,
154
+ ( data , cb ) => deleteVersionList ( data . DeleteMarkers , bucket , err => cb ( err , data ) ) ,
155
+ ( data , cb ) => deleteVersionList ( data . Versions , bucket , err => cb ( err , data ) ) ,
110
156
( data , cb ) => {
111
157
if ( data . IsTruncated ) {
112
- const params = {
113
- Bucket : bucket ,
114
- KeyMarker : data . NextKeyMarker ,
115
- VersionIdMarker : data . NextVersionIdMarker ,
116
- } ;
158
+ const params = { Bucket : bucket , KeyMarker : data . NextKeyMarker ,
159
+ VersionIdMarker : data . NextVersionIdMarker } ;
117
160
return removeAllVersions ( params , cb ) ;
118
161
}
119
162
return cb ( ) ;
120
163
} ,
121
164
] , callback ) ;
122
165
}
166
+
123
167
function objectMPU ( bucket , key , parts , partSize , callback ) {
124
168
let ETags = [ ] ;
125
169
let uploadId = null ;
126
170
const partNumbers = Array . from ( Array ( parts ) . keys ( ) ) ;
127
- const initiateMPUParams = {
128
- Bucket : bucket ,
129
- Key : key ,
130
- } ;
171
+ const initiateMPUParams = { Bucket : bucket , Key : key } ;
131
172
return async . waterfall ( [
132
- next => s3Client . createMultipartUpload ( initiateMPUParams ,
133
- ( err , data ) => {
134
- if ( err ) {
135
- return next ( err ) ;
136
- }
173
+ next => s3Client . send ( new CreateMultipartUploadCommand ( initiateMPUParams ) )
174
+ . then ( data => {
137
175
uploadId = data . UploadId ;
138
176
return next ( ) ;
139
- } ) ,
177
+ } )
178
+ . catch ( next ) ,
140
179
next =>
141
180
async . mapLimit ( partNumbers , 1 , ( partNumber , callback ) => {
181
+ const body = Buffer . alloc ( partSize ) ;
142
182
const uploadPartParams = {
143
183
Bucket : bucket ,
144
184
Key : key ,
145
185
PartNumber : partNumber + 1 ,
146
186
UploadId : uploadId ,
147
- Body : Buffer . alloc ( partSize ) ,
187
+ Body : body ,
148
188
} ;
149
-
150
- return s3Client . uploadPart ( uploadPartParams ,
151
- ( err , data ) => {
152
- if ( err ) {
153
- return callback ( err ) ;
154
- }
155
- return callback ( null , data . ETag ) ;
156
- } ) ;
189
+ return s3Client . send ( new UploadPartCommand ( uploadPartParams ) )
190
+ . then ( data => callback ( null , data . ETag ) )
191
+ . catch ( callback ) ;
157
192
} , ( err , results ) => {
158
193
if ( err ) {
159
194
return next ( err ) ;
@@ -162,33 +197,28 @@ function objectMPU(bucket, key, parts, partSize, callback) {
162
197
return next ( ) ;
163
198
} ) ,
164
199
next => {
200
+ const completeRequest = { Parts : partNumbers . map ( n => ( { ETag : ETags [ n ] , PartNumber : n + 1 } ) ) } ;
165
201
const params = {
166
202
Bucket : bucket ,
167
203
Key : key ,
168
- MultipartUpload : {
169
- Parts : partNumbers . map ( n => ( {
170
- ETag : ETags [ n ] ,
171
- PartNumber : n + 1 ,
172
- } ) ) ,
173
- } ,
204
+ MultipartUpload : completeRequest ,
174
205
UploadId : uploadId ,
175
206
} ;
176
- return s3Client . completeMultipartUpload ( params , next ) ;
207
+ return s3Client . send ( new CompleteMultipartUploadCommand ( params ) )
208
+ . then ( data => next ( null , data ) )
209
+ . catch ( next ) ;
177
210
} ,
178
211
] , callback ) ;
179
212
}
213
+
180
214
function removeVersions ( buckets , cb ) {
181
- return async . each ( buckets ,
182
- ( bucket , done ) => removeAllVersions ( { Bucket : bucket } , done ) , cb ) ;
215
+ return async . each ( buckets , ( bucket , done ) => removeAllVersions ( { Bucket : bucket } , done ) , cb ) ;
183
216
}
217
+
184
218
function getObject ( bucket , key , cb ) {
185
- return s3Client . getObject ( {
186
- Bucket : bucket ,
187
- Key : key ,
188
- } , ( err , data ) => {
189
- assert . ifError ( err ) ;
190
- return cb ( err , data ) ;
191
- } ) ;
219
+ return s3Client . send ( new GetObjectCommand ( { Bucket : bucket , Key : key } ) )
220
+ . then ( data => cb ( null , data ) )
221
+ . catch ( cb ) ;
192
222
}
193
223
194
224
describe ( 'utapi v2 metrics incoming and outgoing bytes' , function t ( ) {
@@ -204,8 +234,7 @@ describe('utapi v2 metrics incoming and outgoing bytes', function t() {
204
234
}
205
235
206
236
before ( ( ) => {
207
- const config = getConfig ( 'default' , { signatureVersion : 'v4' } ) ;
208
- s3Client = new S3 ( config ) ;
237
+ s3Client = createS3ClientWithMD5 ( getConfig ( 'default' ) ) ;
209
238
utapi . start ( ) ;
210
239
} ) ;
211
240
afterEach ( ( ) => {
0 commit comments