11const async = require ( 'async' ) ;
22const 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' ) ;
419
520const MockUtapi = require ( '../utilities/mock/Utapi' ) ;
621const getConfig = require ( '../functional/aws-node-sdk/test/support/config' ) ;
722const WAIT_MS = 100 ;
823let s3Client = null ;
924
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+
1070function wait ( timeoutMs , cb ) {
1171 setTimeout ( cb , timeoutMs ) ;
1272}
73+
1374function 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 ) ;
2078}
79+
2180function 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 ) ;
2884}
85+
2986function putObject ( bucket , key , size , cb ) {
30- return s3Client . putObject ( {
87+ const body = Buffer . alloc ( size ) ;
88+ const params = {
3189 Bucket : bucket ,
3290 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 ) ;
3896}
97+
3998function 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 ) ;
47102}
103+
48104function 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 } ;
55107 const params = {
56108 Bucket : bucket ,
57- Delete : {
58- Objects : objects ,
59- Quiet : true ,
60- } ,
109+ Delete : deleteRequest ,
61110 } ;
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 ) ;
66114}
115+
67116function 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 ) ;
76121}
122+
77123function 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 ) ;
88129}
130+
89131function deleteVersionList ( versionList , bucket , callback ) {
90132 if ( versionList === undefined || versionList . length === 0 ) {
91133 return callback ( ) ;
92134 }
93- const params = { Bucket : bucket , Delete : { Objects : [ ] } } ;
135+ const deleteRequest = { Objects : [ ] } ;
94136 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 } ) ;
98138 } ) ;
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 ) ;
101146}
147+
102148function removeAllVersions ( params , callback ) {
103149 const bucket = params . Bucket ;
104150 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 ) ) ,
110156 ( data , cb ) => {
111157 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 } ;
117160 return removeAllVersions ( params , cb ) ;
118161 }
119162 return cb ( ) ;
120163 } ,
121164 ] , callback ) ;
122165}
166+
123167function objectMPU ( bucket , key , parts , partSize , callback ) {
124168 let ETags = [ ] ;
125169 let uploadId = null ;
126170 const partNumbers = Array . from ( Array ( parts ) . keys ( ) ) ;
127- const initiateMPUParams = {
128- Bucket : bucket ,
129- Key : key ,
130- } ;
171+ const initiateMPUParams = { Bucket : bucket , Key : key } ;
131172 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 => {
137175 uploadId = data . UploadId ;
138176 return next ( ) ;
139- } ) ,
177+ } )
178+ . catch ( next ) ,
140179 next =>
141180 async . mapLimit ( partNumbers , 1 , ( partNumber , callback ) => {
181+ const body = Buffer . alloc ( partSize ) ;
142182 const uploadPartParams = {
143183 Bucket : bucket ,
144184 Key : key ,
145185 PartNumber : partNumber + 1 ,
146186 UploadId : uploadId ,
147- Body : Buffer . alloc ( partSize ) ,
187+ Body : body ,
148188 } ;
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 ) ;
157192 } , ( err , results ) => {
158193 if ( err ) {
159194 return next ( err ) ;
@@ -162,33 +197,28 @@ function objectMPU(bucket, key, parts, partSize, callback) {
162197 return next ( ) ;
163198 } ) ,
164199 next => {
200+ const completeRequest = { Parts : partNumbers . map ( n => ( { ETag : ETags [ n ] , PartNumber : n + 1 } ) ) } ;
165201 const params = {
166202 Bucket : bucket ,
167203 Key : key ,
168- MultipartUpload : {
169- Parts : partNumbers . map ( n => ( {
170- ETag : ETags [ n ] ,
171- PartNumber : n + 1 ,
172- } ) ) ,
173- } ,
204+ MultipartUpload : completeRequest ,
174205 UploadId : uploadId ,
175206 } ;
176- return s3Client . completeMultipartUpload ( params , next ) ;
207+ return s3Client . send ( new CompleteMultipartUploadCommand ( params ) )
208+ . then ( data => next ( null , data ) )
209+ . catch ( next ) ;
177210 } ,
178211 ] , callback ) ;
179212}
213+
180214function 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 ) ;
183216}
217+
184218function 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 ) ;
192222}
193223
194224describe ( 'utapi v2 metrics incoming and outgoing bytes' , function t ( ) {
@@ -204,8 +234,7 @@ describe('utapi v2 metrics incoming and outgoing bytes', function t() {
204234 }
205235
206236 before ( ( ) => {
207- const config = getConfig ( 'default' , { signatureVersion : 'v4' } ) ;
208- s3Client = new S3 ( config ) ;
237+ s3Client = createS3ClientWithMD5 ( getConfig ( 'default' ) ) ;
209238 utapi . start ( ) ;
210239 } ) ;
211240 afterEach ( ( ) => {
0 commit comments