2015-08-24 11:13:21 -07:00
'use strict' ;
exports = module . exports = {
2017-09-17 18:50:29 -07:00
upload : upload ,
download : download ,
copy : copy ,
2018-07-28 09:05:44 -07:00
listDir : listDir ,
2017-09-23 11:09:36 -07:00
remove : remove ,
2017-09-27 17:34:49 -07:00
removeDir : removeDir ,
2016-10-11 11:36:25 +02:00
2017-04-18 19:15:56 +02:00
testConfig : testConfig ,
2019-02-09 18:08:10 -08:00
removePrivateFields : removePrivateFields ,
injectPrivateFields : injectPrivateFields ,
2017-04-18 19:15:56 +02:00
// Used to mock AWS
_mockInject : mockInject ,
_mockRestore : mockRestore
2015-08-24 11:13:21 -07:00
} ;
2017-04-20 15:35:52 +02:00
var assert = require ( 'assert' ) ,
2017-09-23 11:09:36 -07:00
async = require ( 'async' ) ,
2016-09-19 15:03:38 +02:00
AWS = require ( 'aws-sdk' ) ,
2019-02-09 18:08:10 -08:00
backups = require ( '../backups.js' ) ,
2019-10-22 20:36:20 -07:00
BoxError = require ( '../boxerror.js' ) ,
2017-10-05 10:01:09 -07:00
chunk = require ( 'lodash.chunk' ) ,
2017-04-18 15:33:06 +02:00
debug = require ( 'debug' ) ( 'box:storage/s3' ) ,
2017-10-04 11:00:30 -07:00
EventEmitter = require ( 'events' ) ,
2017-10-05 10:01:09 -07:00
https = require ( 'https' ) ,
2017-04-21 15:28:25 -07:00
PassThrough = require ( 'stream' ) . PassThrough ,
2017-04-18 15:33:06 +02:00
path = require ( 'path' ) ,
2019-05-08 15:33:23 -07:00
S3BlockReadStream = require ( 's3-block-read-stream' ) ;
2015-08-24 11:13:21 -07:00
2017-04-18 19:15:56 +02:00
// test only
var originalAWS ;
function mockInject ( mock ) {
originalAWS = AWS ;
AWS = mock ;
}
function mockRestore ( ) {
AWS = originalAWS ;
}
2018-07-30 07:39:34 -07:00
function S3 _NOT _FOUND ( error ) {
return error . code === 'NoSuchKey' || error . code === 'NotFound' || error . code === 'ENOENT' ;
}
2017-10-10 23:42:24 -07:00
function getS3Config ( apiConfig , callback ) {
2016-03-31 09:48:01 -07:00
assert . strictEqual ( typeof apiConfig , 'object' ) ;
2015-08-24 11:13:21 -07:00
assert . strictEqual ( typeof callback , 'function' ) ;
2015-11-06 18:22:29 -08:00
var credentials = {
2017-04-20 19:56:06 -07:00
signatureVersion : apiConfig . signatureVersion || 'v4' ,
2017-10-09 18:46:14 -07:00
s3ForcePathStyle : true , // Force use path-style url (http://endpoint/bucket/path) instead of host-style (http://bucket.endpoint/path)
2016-03-31 09:48:01 -07:00
accessKeyId : apiConfig . accessKeyId ,
secretAccessKey : apiConfig . secretAccessKey ,
2017-10-10 23:42:24 -07:00
region : apiConfig . region || 'us-east-1' ,
2017-10-11 13:57:05 -07:00
maxRetries : 5 ,
retryDelayOptions : {
2018-02-22 12:27:45 -08:00
customBackoff : ( ) => 20000 // constant backoff - https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/Config.html#retryDelayOptions-property
2018-02-22 12:19:23 -08:00
} ,
httpOptions : {
2018-03-20 19:18:48 -07:00
connectTimeout : 10000 , // https://github.com/aws/aws-sdk-js/pull/1446
timeout : 300 * 1000 // https://github.com/aws/aws-sdk-js/issues/1704 (allow 5MB chunk upload to take upto 5 minutes)
2017-10-11 13:57:05 -07:00
}
2015-11-06 18:22:29 -08:00
} ;
2015-09-09 11:43:50 -07:00
2016-12-07 10:47:06 +01:00
if ( apiConfig . endpoint ) credentials . endpoint = apiConfig . endpoint ;
2015-09-09 11:43:50 -07:00
2017-10-26 16:20:34 -07:00
if ( apiConfig . acceptSelfSignedCerts === true && credentials . endpoint && credentials . endpoint . startsWith ( 'https://' ) ) {
2018-02-22 12:19:23 -08:00
credentials . httpOptions . agent = new https . Agent ( { rejectUnauthorized : false } ) ;
2017-10-05 10:01:09 -07:00
}
2015-11-06 18:22:29 -08:00
callback ( null , credentials ) ;
2015-08-24 11:13:21 -07:00
}
2015-08-25 10:01:04 -07:00
2017-04-18 15:33:06 +02:00
// storage api
2017-09-20 09:57:16 -07:00
function upload ( apiConfig , backupFilePath , sourceStream , callback ) {
2016-09-16 11:21:08 +02:00
assert . strictEqual ( typeof apiConfig , 'object' ) ;
2017-09-19 20:40:38 -07:00
assert . strictEqual ( typeof backupFilePath , 'string' ) ;
2017-09-20 09:57:16 -07:00
assert . strictEqual ( typeof sourceStream , 'object' ) ;
2016-09-16 11:21:08 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-10-10 23:42:24 -07:00
getS3Config ( apiConfig , function ( error , credentials ) {
2015-08-25 10:01:04 -07:00
if ( error ) return callback ( error ) ;
var params = {
2016-04-04 11:44:24 -07:00
Bucket : apiConfig . bucket ,
2017-04-18 15:33:06 +02:00
Key : backupFilePath ,
2017-09-20 09:57:16 -07:00
Body : sourceStream
2015-08-25 10:01:04 -07:00
} ;
2017-04-18 15:33:06 +02:00
var s3 = new AWS . S3 ( credentials ) ;
2017-10-10 10:47:41 -07:00
2017-04-27 11:40:18 -07:00
// s3.upload automatically does a multi-part upload. we set queueSize to 1 to reduce memory usage
2017-10-10 10:47:41 -07:00
// uploader will buffer at most queueSize * partSize bytes into memory at any given time.
2019-09-30 20:42:37 -07:00
// scaleway only supports 1000 parts per object (https://www.scaleway.com/en/docs/s3-multipart-upload/)
const partSize = apiConfig . provider === 'scaleway-objectstorage' ? 100 * 1024 * 1024 : 10 * 1024 * 1024 ;
s3 . upload ( params , { partSize , queueSize : 1 } , function ( error , data ) {
2018-02-22 11:06:28 -08:00
if ( error ) {
2018-03-20 16:41:32 -07:00
debug ( 'Error uploading [%s]: s3 upload error.' , backupFilePath , error ) ;
2019-10-22 20:36:20 -07:00
return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , ` Error uploading ${ backupFilePath } . Message: ${ error . message } HTTP Code: ${ error . code } ` ) ) ;
2018-02-22 11:06:28 -08:00
}
2018-03-20 18:19:14 -07:00
debug ( ` Uploaded ${ backupFilePath } : ${ JSON . stringify ( data ) } ` ) ;
2018-02-22 11:06:28 -08:00
callback ( null ) ;
} ) ;
2015-08-25 10:01:04 -07:00
} ) ;
}
2015-08-26 16:14:51 -07:00
2017-09-20 09:57:16 -07:00
function download ( apiConfig , backupFilePath , callback ) {
2016-09-19 15:03:38 +02:00
assert . strictEqual ( typeof apiConfig , 'object' ) ;
2017-09-19 20:40:38 -07:00
assert . strictEqual ( typeof backupFilePath , 'string' ) ;
2016-09-19 15:03:38 +02:00
assert . strictEqual ( typeof callback , 'function' ) ;
2017-10-10 23:42:24 -07:00
getS3Config ( apiConfig , function ( error , credentials ) {
2016-09-19 15:03:38 +02:00
if ( error ) return callback ( error ) ;
2017-04-21 15:28:25 -07:00
var params = {
Bucket : apiConfig . bucket ,
Key : backupFilePath
} ;
2017-04-18 15:33:06 +02:00
2017-04-21 15:28:25 -07:00
var s3 = new AWS . S3 ( credentials ) ;
2017-04-18 16:44:49 +02:00
2017-09-20 09:57:16 -07:00
var ps = new PassThrough ( ) ;
2017-10-04 12:32:12 -07:00
var multipartDownload = new S3BlockReadStream ( s3 , params , { blockSize : 64 * 1024 * 1024 /*, logCallback: debug */ } ) ;
2017-04-21 15:06:54 -07:00
2017-04-27 11:50:16 -07:00
multipartDownload . on ( 'error' , function ( error ) {
2018-07-30 07:39:34 -07:00
if ( S3 _NOT _FOUND ( error ) ) {
2019-10-22 20:36:20 -07:00
ps . emit ( 'error' , new BoxError ( BoxError . NOT _FOUND , ` Backup not found: ${ backupFilePath } ` ) ) ;
2017-09-20 09:57:16 -07:00
} else {
2017-11-28 10:28:17 -08:00
debug ( ` download: ${ apiConfig . bucket } : ${ backupFilePath } s3 stream error. ` , error ) ;
2019-10-22 20:36:20 -07:00
ps . emit ( 'error' , new BoxError ( BoxError . EXTERNAL _ERROR , error . message || error . code ) ) ; // DO sets 'code'
2017-09-20 09:57:16 -07:00
}
2017-04-18 15:33:06 +02:00
} ) ;
2017-04-21 15:28:25 -07:00
2017-09-20 09:57:16 -07:00
multipartDownload . pipe ( ps ) ;
callback ( null , ps ) ;
2017-04-18 15:33:06 +02:00
} ) ;
2016-09-16 18:14:36 +02:00
}
2018-07-28 09:05:44 -07:00
function listDir ( apiConfig , dir , batchSize , iteratorCallback , callback ) {
assert . strictEqual ( typeof apiConfig , 'object' ) ;
assert . strictEqual ( typeof dir , 'string' ) ;
assert . strictEqual ( typeof batchSize , 'number' ) ;
assert . strictEqual ( typeof iteratorCallback , 'function' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-10-10 23:42:24 -07:00
getS3Config ( apiConfig , function ( error , credentials ) {
2017-09-23 14:27:35 -07:00
if ( error ) return callback ( error ) ;
var s3 = new AWS . S3 ( credentials ) ;
var listParams = {
Bucket : apiConfig . bucket ,
2018-07-28 09:05:44 -07:00
Prefix : dir ,
MaxKeys : batchSize
2017-09-23 14:27:35 -07:00
} ;
2019-12-04 11:17:42 -08:00
let done = false ;
async . whilst ( ( ) => ! done , function listAndDownload ( whilstCallback ) {
2017-10-05 12:02:12 -07:00
s3 . listObjects ( listParams , function ( error , listData ) {
2019-12-05 21:50:44 -08:00
if ( error ) return whilstCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message || error . code ) ) ;
2017-09-23 14:27:35 -07:00
2019-12-04 11:17:42 -08:00
if ( listData . Contents . length === 0 ) { done = true ; return whilstCallback ( ) ; }
2017-09-27 21:46:24 -07:00
2018-07-29 09:00:57 -07:00
const entries = listData . Contents . map ( function ( c ) { return { fullPath : c . Key , size : c . Size } ; } ) ;
2018-07-28 09:05:44 -07:00
iteratorCallback ( entries , function ( error ) {
2019-12-04 11:17:42 -08:00
if ( error ) return whilstCallback ( error ) ;
2017-09-23 14:27:35 -07:00
2019-12-04 11:17:42 -08:00
if ( ! listData . IsTruncated ) { done = true ; return whilstCallback ( ) ; }
2017-09-27 21:46:24 -07:00
2017-10-05 12:02:12 -07:00
listParams . Marker = listData . Contents [ listData . Contents . length - 1 ] . Key ; // NextMarker is returned only with delimiter
2017-09-23 14:27:35 -07:00
2019-12-04 11:17:42 -08:00
whilstCallback ( ) ;
2017-09-23 14:27:35 -07:00
} ) ;
} ) ;
2019-12-04 11:17:42 -08:00
} , callback ) ;
2017-09-23 14:27:35 -07:00
} ) ;
}
2018-02-03 22:00:33 -08:00
// https://github.com/aws/aws-sdk-js/blob/2b6bcbdec1f274fe931640c1b61ece999aae7a19/lib/util.js#L41
// https://github.com/GeorgePhillips/node-s3-url-encode/blob/master/index.js
// See aws-sdk-js/issues/1302
function encodeCopySource ( bucket , path ) {
2018-02-04 00:15:41 -08:00
var output = encodeURI ( path ) ;
2018-02-03 22:00:33 -08:00
// AWS percent-encodes some extra non-standard characters in a URI
output = output . replace ( /[+!"#$@&'()*+,:;=?@]/g , function ( ch ) {
return '%' + ch . charCodeAt ( 0 ) . toString ( 16 ) . toUpperCase ( ) ;
} ) ;
// the slash at the beginning is optional
return ` / ${ bucket } / ${ output } ` ;
}
2017-10-04 11:00:30 -07:00
function copy ( apiConfig , oldFilePath , newFilePath ) {
2016-03-31 09:48:01 -07:00
assert . strictEqual ( typeof apiConfig , 'object' ) ;
2017-09-19 20:40:38 -07:00
assert . strictEqual ( typeof oldFilePath , 'string' ) ;
assert . strictEqual ( typeof newFilePath , 'string' ) ;
2017-10-04 11:00:30 -07:00
2017-10-11 13:57:05 -07:00
var events = new EventEmitter ( ) , retryCount = 0 ;
2015-09-21 14:02:00 -07:00
2018-07-28 09:05:44 -07:00
function copyFile ( entry , iteratorCallback ) {
2018-07-29 09:00:57 -07:00
getS3Config ( apiConfig , function ( error , credentials ) {
if ( error ) return iteratorCallback ( error ) ;
2015-09-21 14:02:00 -07:00
2018-07-29 09:00:57 -07:00
var s3 = new AWS . S3 ( credentials ) ;
var relativePath = path . relative ( oldFilePath , entry . fullPath ) ;
2018-03-21 14:58:36 -07:00
2018-07-29 09:00:57 -07:00
function done ( error ) {
if ( error ) debug ( ` copy: s3 copy error when copying ${ entry . fullPath } : ${ error } ` ) ;
2017-09-22 14:40:37 -07:00
2019-10-22 20:36:20 -07:00
if ( error && S3 _NOT _FOUND ( error ) ) return iteratorCallback ( new BoxError ( BoxError . NOT _FOUND , ` Old backup not found: ${ entry . fullPath } ` ) ) ;
2019-11-20 12:43:08 -08:00
if ( error ) return iteratorCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , ` Error copying ${ entry . fullPath } ( ${ entry . size } bytes): ${ error . code || '' } ${ error } ` ) ) ;
2018-02-22 10:31:56 -08:00
2018-07-29 09:00:57 -07:00
iteratorCallback ( null ) ;
}
2017-10-11 13:57:05 -07:00
2018-07-29 09:00:57 -07:00
var copyParams = {
Bucket : apiConfig . bucket ,
Key : path . join ( newFilePath , relativePath )
} ;
2017-10-04 16:54:56 +02:00
2018-07-29 09:00:57 -07:00
// S3 copyObject has a file size limit of 5GB so if we have larger files, we do a multipart copy
// Exoscale takes too long to copy 5GB
const largeFileLimit = apiConfig . provider === 'exoscale-sos' ? 1024 * 1024 * 1024 : 5 * 1024 * 1024 * 1024 ;
2017-10-04 11:00:30 -07:00
2018-07-29 09:00:57 -07:00
if ( entry . size < largeFileLimit ) {
events . emit ( 'progress' , ` Copying ${ relativePath || oldFilePath } ` ) ;
2017-10-04 16:54:56 +02:00
2018-07-29 09:00:57 -07:00
copyParams . CopySource = encodeCopySource ( apiConfig . bucket , entry . fullPath ) ;
s3 . copyObject ( copyParams , done ) . on ( 'retry' , function ( response ) {
++ retryCount ;
events . emit ( 'progress' , ` Retrying ( ${ response . retryCount + 1 } ) copy of ${ relativePath || oldFilePath } . Error: ${ response . error } ${ response . httpResponse . statusCode } ` ) ;
// on DO, we get a random 408. these are not retried by the SDK
if ( response . error ) response . error . retryable = true ; // https://github.com/aws/aws-sdk-js/issues/412
} ) ;
2017-10-04 16:54:56 +02:00
2018-07-29 09:00:57 -07:00
return ;
}
2017-10-04 16:54:56 +02:00
2018-07-29 09:00:57 -07:00
events . emit ( 'progress' , ` Copying (multipart) ${ relativePath || oldFilePath } ` ) ;
2017-10-04 16:54:56 +02:00
2018-07-29 09:00:57 -07:00
s3 . createMultipartUpload ( copyParams , function ( error , result ) {
if ( error ) return done ( error ) ;
2017-10-04 16:54:56 +02:00
2018-07-29 09:00:57 -07:00
// Exoscale (96M) was suggested by exoscale. 1GB - rather random size for others
const chunkSize = apiConfig . provider === 'exoscale-sos' ? 96 * 1024 * 1024 : 1024 * 1024 * 1024 ;
var uploadId = result . UploadId ;
var uploadedParts = [ ] ;
var partNumber = 1 ;
var startBytes = 0 ;
var endBytes = 0 ;
var size = entry . size - 1 ;
2017-10-04 16:54:56 +02:00
2018-07-29 09:00:57 -07:00
function copyNextChunk ( ) {
endBytes = startBytes + chunkSize ;
if ( endBytes > size ) endBytes = size ;
2017-10-04 16:54:56 +02:00
2019-05-10 13:07:29 -07:00
var partCopyParams = {
2017-10-04 16:54:56 +02:00
Bucket : apiConfig . bucket ,
Key : path . join ( newFilePath , relativePath ) ,
2018-07-29 09:00:57 -07:00
CopySource : encodeCopySource ( apiConfig . bucket , entry . fullPath ) , // See aws-sdk-js/issues/1302
CopySourceRange : 'bytes=' + startBytes + '-' + endBytes ,
PartNumber : partNumber ,
2017-10-04 16:54:56 +02:00
UploadId : uploadId
} ;
2019-05-10 13:07:29 -07:00
events . emit ( 'progress' , ` Copying part ${ partCopyParams . PartNumber } - ${ partCopyParams . CopySource } ${ partCopyParams . CopySourceRange } ` ) ;
2019-03-26 11:58:32 -07:00
2019-05-10 13:07:29 -07:00
s3 . uploadPartCopy ( partCopyParams , function ( error , result ) {
2018-07-29 09:00:57 -07:00
if ( error ) return done ( error ) ;
2019-05-10 13:07:29 -07:00
events . emit ( 'progress' , ` Uploaded part ${ partCopyParams . PartNumber } - Etag: ${ result . CopyPartResult . ETag } ` ) ;
2019-03-26 11:58:32 -07:00
2019-12-05 21:50:44 -08:00
if ( ! result . CopyPartResult . ETag ) return done ( new Error ( 'Multi-part copy is broken or not implemented by the S3 storage provider' ) ) ;
2019-11-20 12:43:08 -08:00
2018-07-29 09:00:57 -07:00
uploadedParts . push ( { ETag : result . CopyPartResult . ETag , PartNumber : partNumber } ) ;
if ( endBytes < size ) {
startBytes = endBytes + 1 ;
partNumber ++ ;
return copyNextChunk ( ) ;
}
2019-05-10 13:07:29 -07:00
var completeMultipartParams = {
2018-07-29 09:00:57 -07:00
Bucket : apiConfig . bucket ,
Key : path . join ( newFilePath , relativePath ) ,
MultipartUpload : { Parts : uploadedParts } ,
UploadId : uploadId
} ;
2019-05-10 13:07:29 -07:00
events . emit ( 'progress' , ` Finishing multipart copy - ${ completeMultipartParams . Key } ` ) ;
2019-03-26 11:58:32 -07:00
2019-05-10 13:07:29 -07:00
s3 . completeMultipartUpload ( completeMultipartParams , done ) ;
2018-07-29 09:00:57 -07:00
} ) . on ( 'retry' , function ( response ) {
++ retryCount ;
events . emit ( 'progress' , ` Retrying ( ${ response . retryCount + 1 } ) multipart copy of ${ relativePath || oldFilePath } . Error: ${ response . error } ${ response . httpResponse . statusCode } ` ) ;
} ) ;
}
2017-10-04 16:54:56 +02:00
2018-07-29 09:00:57 -07:00
copyNextChunk ( ) ;
} ) ;
2017-04-18 15:33:06 +02:00
} ) ;
2017-10-11 13:57:05 -07:00
}
2019-01-14 13:17:51 -08:00
var total = 0 ;
const concurrency = apiConfig . copyConcurrency || ( apiConfig . provider === 's3' ? 500 : 10 ) ;
2017-10-11 13:57:05 -07:00
2018-07-28 09:05:44 -07:00
listDir ( apiConfig , oldFilePath , 1000 , function listDirIterator ( entries , done ) {
total += entries . length ;
2017-10-11 13:57:05 -07:00
2018-07-28 09:05:44 -07:00
events . emit ( 'progress' , ` Copying ${ total - entries . length } - ${ total } . ${ retryCount } errors so far. concurrency set to ${ concurrency } ` ) ;
2017-10-11 13:57:05 -07:00
retryCount = 0 ;
2018-07-28 09:05:44 -07:00
async . eachLimit ( entries , concurrency , copyFile , done ) ;
2017-10-04 11:00:30 -07:00
} , function ( error ) {
2018-03-21 14:22:41 -07:00
events . emit ( 'progress' , ` Copied ${ total } files with error: ${ error } ` ) ;
2017-10-10 20:23:04 -07:00
2017-10-04 11:00:30 -07:00
events . emit ( 'done' , error ) ;
} ) ;
return events ;
2015-09-21 14:02:00 -07:00
}
2016-10-10 15:04:28 +02:00
2017-09-27 17:34:49 -07:00
function remove ( apiConfig , filename , callback ) {
assert . strictEqual ( typeof apiConfig , 'object' ) ;
assert . strictEqual ( typeof filename , 'string' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2017-10-10 23:42:24 -07:00
getS3Config ( apiConfig , function ( error , credentials ) {
2017-09-27 17:34:49 -07:00
if ( error ) return callback ( error ) ;
var s3 = new AWS . S3 ( credentials ) ;
var deleteParams = {
Bucket : apiConfig . bucket ,
Delete : {
Objects : [ { Key : filename } ]
}
} ;
2018-02-22 11:05:29 -08:00
// deleteObjects does not return error if key is not found
2017-09-27 17:34:49 -07:00
s3 . deleteObjects ( deleteParams , function ( error ) {
2019-12-05 21:50:44 -08:00
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , ` Unable to remove ${ deleteParams . Key } . error: ${ error . message || error . code } ` ) ) ; // DO sets 'code'
2017-09-27 17:34:49 -07:00
2019-12-05 21:50:44 -08:00
callback ( null ) ;
2017-09-27 17:34:49 -07:00
} ) ;
} ) ;
}
2017-10-10 20:23:04 -07:00
function removeDir ( apiConfig , pathPrefix ) {
2016-10-10 15:04:28 +02:00
assert . strictEqual ( typeof apiConfig , 'object' ) ;
2017-09-23 11:09:36 -07:00
assert . strictEqual ( typeof pathPrefix , 'string' ) ;
2016-10-10 15:04:28 +02:00
2017-10-10 20:23:04 -07:00
var events = new EventEmitter ( ) ;
var total = 0 ;
2018-07-29 09:00:57 -07:00
getS3Config ( apiConfig , function ( error , credentials ) {
if ( error ) events . emit ( 'done' , error ) ;
2017-10-29 12:30:22 -07:00
2018-07-29 09:00:57 -07:00
var s3 = new AWS . S3 ( credentials ) ;
2017-10-11 13:57:05 -07:00
2018-07-29 09:00:57 -07:00
listDir ( apiConfig , pathPrefix , 1000 , function listDirIterator ( entries , done ) {
total += entries . length ;
2018-02-22 12:14:13 -08:00
2018-07-29 09:00:57 -07:00
const chunkSize = apiConfig . provider !== 'digitalocean-spaces' ? 1000 : 100 ; // throttle objects in each request
var chunks = chunk ( entries , chunkSize ) ;
2018-02-22 12:14:13 -08:00
2018-07-29 09:00:57 -07:00
async . eachSeries ( chunks , function deleteFiles ( objects , iteratorCallback ) {
var deleteParams = {
Bucket : apiConfig . bucket ,
Delete : {
Objects : objects . map ( function ( o ) { return { Key : o . fullPath } ; } )
}
} ;
2018-02-22 12:14:13 -08:00
2018-07-31 19:41:03 -07:00
events . emit ( 'progress' , ` Removing ${ objects . length } files from ${ objects [ 0 ] . fullPath } to ${ objects [ objects . length - 1 ] . fullPath } ` ) ;
2017-10-10 20:23:04 -07:00
2018-07-29 09:00:57 -07:00
// deleteObjects does not return error if key is not found
s3 . deleteObjects ( deleteParams , function ( error /*, deleteData */ ) {
2019-12-05 21:50:44 -08:00
if ( error ) {
events . emit ( 'progress' , ` Unable to remove ${ deleteParams . Key } ${ error . message || error . code } ` ) ;
return iteratorCallback ( new BoxError ( BoxError . EXTERNAL _ERROR , ` Unable to remove ${ deleteParams . Key } . error: ${ error . message || error . code } ` ) ) ; // DO sets 'code'
}
2018-07-29 09:00:57 -07:00
2019-12-05 21:50:44 -08:00
iteratorCallback ( null ) ;
2018-07-29 09:00:57 -07:00
} ) ;
} , done ) ;
} , function ( error ) {
events . emit ( 'progress' , ` Removed ${ total } files ` ) ;
events . emit ( 'done' , error ) ;
} ) ;
2017-10-10 20:23:04 -07:00
} ) ;
return events ;
2016-10-10 15:04:28 +02:00
}
2016-10-11 11:36:25 +02:00
function testConfig ( apiConfig , callback ) {
assert . strictEqual ( typeof apiConfig , 'object' ) ;
assert . strictEqual ( typeof callback , 'function' ) ;
2019-10-22 20:36:20 -07:00
if ( typeof apiConfig . accessKeyId !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'accessKeyId must be a string' , { field : 'accessKeyId' } ) ) ;
if ( typeof apiConfig . secretAccessKey !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'secretAccessKey must be a string' , { field : 'secretAccessKey' } ) ) ;
2017-09-27 10:25:36 -07:00
2019-10-22 20:36:20 -07:00
if ( typeof apiConfig . bucket !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'bucket must be a string' , { field : 'bucket' } ) ) ;
if ( typeof apiConfig . prefix !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'prefix must be a string' , { field : 'prefix' } ) ) ;
if ( 'signatureVersion' in apiConfig && typeof apiConfig . signatureVersion !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'signatureVersion must be a string' , { field : 'signatureVersion' } ) ) ;
if ( 'endpoint' in apiConfig && typeof apiConfig . endpoint !== 'string' ) return callback ( new BoxError ( BoxError . BAD _FIELD , 'endpoint must be a string' , { field : 'endpoint' } ) ) ;
2016-10-11 11:36:25 +02:00
2016-10-11 11:46:28 +02:00
// attempt to upload and delete a file with new credentials
2017-10-10 23:42:24 -07:00
getS3Config ( apiConfig , function ( error , credentials ) {
2016-10-11 11:46:28 +02:00
if ( error ) return callback ( error ) ;
var params = {
Bucket : apiConfig . bucket ,
2017-07-19 14:20:35 +02:00
Key : path . join ( apiConfig . prefix , 'cloudron-testfile' ) ,
2016-10-11 11:46:28 +02:00
Body : 'testcontent'
} ;
var s3 = new AWS . S3 ( credentials ) ;
s3 . putObject ( params , function ( error ) {
2019-10-22 20:36:20 -07:00
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message || error . code ) ) ; // DO sets 'code'
2016-10-11 11:46:28 +02:00
var params = {
Bucket : apiConfig . bucket ,
2017-07-19 14:20:35 +02:00
Key : path . join ( apiConfig . prefix , 'cloudron-testfile' )
2016-10-11 11:46:28 +02:00
} ;
s3 . deleteObject ( params , function ( error ) {
2019-10-22 20:36:20 -07:00
if ( error ) return callback ( new BoxError ( BoxError . EXTERNAL _ERROR , error . message || error . code ) ) ; // DO sets 'code'
2016-10-11 11:46:28 +02:00
2017-04-18 16:51:54 +02:00
callback ( ) ;
2016-10-11 11:46:28 +02:00
} ) ;
} ) ;
} ) ;
2016-10-11 11:36:25 +02:00
}
2019-02-09 18:08:10 -08:00
function removePrivateFields ( apiConfig ) {
apiConfig . secretAccessKey = backups . SECRET _PLACEHOLDER ;
return apiConfig ;
}
function injectPrivateFields ( newConfig , currentConfig ) {
if ( newConfig . secretAccessKey === backups . SECRET _PLACEHOLDER ) newConfig . secretAccessKey = currentConfig . secretAccessKey ;
}