diff --git a/lib/api/apiUtils/bucket/bucketCreation.js b/lib/api/apiUtils/bucket/bucketCreation.js index ca43c09a82..1254280086 100644 --- a/lib/api/apiUtils/bucket/bucketCreation.js +++ b/lib/api/apiUtils/bucket/bucketCreation.js @@ -18,7 +18,6 @@ const oldUsersBucket = constants.oldUsersBucket; const zenkoSeparator = constants.zenkoSeparator; const userBucketOwner = 'admin'; - function addToUsersBucket(canonicalID, bucketName, bucketMD, log, cb) { // BACKWARD: Simplify once do not have to deal with old // usersbucket name and old splitter @@ -28,8 +27,7 @@ function addToUsersBucket(canonicalID, bucketName, bucketMD, log, cb) { if (err && !err.is.NoSuchBucket && !err.is.BucketAlreadyExists) { return cb(err); } - const splitter = usersBucketAttrs ? - constants.splitter : constants.oldSplitter; + const splitter = usersBucketAttrs ? constants.splitter : constants.oldSplitter; let key = createKeyForUserBucket(canonicalID, splitter, bucketName); const omVal = { creationDate: new Date().toJSON(), @@ -38,46 +36,44 @@ function addToUsersBucket(canonicalID, bucketName, bucketMD, log, cb) { // If the new format usersbucket does not exist, try to put the // key in the old usersBucket using the old splitter. // Otherwise put the key in the new format usersBucket - const usersBucketBeingCalled = usersBucketAttrs ? - usersBucket : oldUsersBucket; - return metadata.putObjectMD(usersBucketBeingCalled, key, - omVal, {}, log, err => { - if (err?.is?.NoSuchBucket) { - // There must be no usersBucket so createBucket - // one using the new format - log.trace('users bucket does not exist, ' + - 'creating users bucket'); - key = `${canonicalID}${constants.splitter}` + - `${bucketName}`; - const creationDate = new Date().toJSON(); - const freshBucket = new BucketInfo(usersBucket, - userBucketOwner, userBucketOwner, creationDate, - BucketInfo.currentModelVersion()); - return metadata.createBucket(usersBucket, - freshBucket, log, err => { - // Note: In the event that two - // users' requests try to create the - // usersBucket at the same time, - // this will prevent one of the users - // from getting a BucketAlreadyExists - // error with respect - // to the usersBucket. - // TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver - if (err && !err.BucketAlreadyExists) { - log.error('error from metadata', { - error: err, - }); - return cb(err); - } - log.trace('Users bucket created'); - // Finally put the key in the new format - // usersBucket - return metadata.putObjectMD(usersBucket, - key, omVal, {}, log, cb); + const usersBucketBeingCalled = usersBucketAttrs ? usersBucket : oldUsersBucket; + return metadata.putObjectMD(usersBucketBeingCalled, key, omVal, {}, log, err => { + if (err?.is?.NoSuchBucket) { + // There must be no usersBucket so createBucket + // one using the new format + log.trace('users bucket does not exist, ' + 'creating users bucket'); + key = `${canonicalID}${constants.splitter}` + `${bucketName}`; + const creationDate = new Date().toJSON(); + const freshBucket = new BucketInfo( + usersBucket, + userBucketOwner, + userBucketOwner, + creationDate, + BucketInfo.currentModelVersion(), + ); + return metadata.createBucket(usersBucket, freshBucket, log, err => { + // Note: In the event that two + // users' requests try to create the + // usersBucket at the same time, + // this will prevent one of the users + // from getting a BucketAlreadyExists + // error with respect + // to the usersBucket. + // TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver + if (err && !err.BucketAlreadyExists) { + log.error('error from metadata', { + error: err, }); - } - return cb(err); - }); + return cb(err); + } + log.trace('Users bucket created'); + // Finally put the key in the new format + // usersBucket + return metadata.putObjectMD(usersBucket, key, omVal, {}, log, cb); + }); + } + return cb(err); + }); }); } @@ -92,11 +88,15 @@ function removeTransientOrDeletedLabel(bucket, log, callback) { function freshStartCreateBucket(bucket, canonicalID, log, callback) { const bucketName = bucket.getName(); metadata.createBucket(bucketName, bucket, log, err => { - if (err) { + if (err && !err.is.BucketAlreadyExists) { log.debug('error from metadata', { error: err }); return callback(err); } - log.trace('created bucket in metadata'); + if (err?.is?.BucketAlreadyExists) { + log.trace('bucket already exists in metadata'); + } else { + log.trace('created bucket in metadata'); + } return addToUsersBucket(canonicalID, bucketName, bucket, log, err => { if (err) { return callback(err); @@ -139,16 +139,15 @@ function cleanUpBucket(bucketMD, canonicalID, log, callback) { * @callback called with (err, sseInfo: object) */ function bucketLevelServerSideEncryption(bucket, headers, log, cb) { - kms.bucketLevelEncryption( - bucket, headers, log, (err, sseInfo) => { - if (err) { - log.debug('error getting bucket encryption info', { - error: err, - }); - return cb(err); - } - return cb(null, sseInfo); - }); + kms.bucketLevelEncryption(bucket, headers, log, (err, sseInfo) => { + if (err) { + log.debug('error getting bucket encryption info', { + error: err, + }); + return cb(err); + } + return cb(null, sseInfo); + }); } /** @@ -163,28 +162,44 @@ function bucketLevelServerSideEncryption(bucket, headers, log, cb) { * @param {function} cb - callback to bucketPut * @return {undefined} */ -function createBucket(authInfo, bucketName, headers, - locationConstraint, log, cb) { +function createBucket(authInfo, bucketName, headers, locationConstraint, log, cb) { // Prefer using undefined instead of null for unused properties so they are not present in metadata payload log.trace('Creating bucket'); assert.strictEqual(typeof bucketName, 'string'); const canonicalID = authInfo.getCanonicalID(); - const ownerDisplayName = - authInfo.getAccountDisplayName(); + const ownerDisplayName = authInfo.getAccountDisplayName(); const creationDate = new Date().toJSON(); const isNFSEnabled = headers['x-scal-nfs-enabled'] === 'true' || undefined; const headerObjectLock = headers['x-amz-bucket-object-lock-enabled']; - const objectLockEnabled - = headerObjectLock && headerObjectLock.toLowerCase() === 'true'; - const bucket = new BucketInfo(bucketName, canonicalID, ownerDisplayName, - creationDate, BucketInfo.currentModelVersion(), undefined, undefined, undefined, undefined, - undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined, isNFSEnabled, - undefined, undefined, objectLockEnabled); + const objectLockEnabled = headerObjectLock && headerObjectLock.toLowerCase() === 'true'; + const bucket = new BucketInfo( + bucketName, + canonicalID, + ownerDisplayName, + creationDate, + BucketInfo.currentModelVersion(), + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + isNFSEnabled, + undefined, + undefined, + objectLockEnabled, + ); let locationConstraintVal = null; if (locationConstraint) { - const [locationConstraintStr, ingestion] = - locationConstraint.split(zenkoSeparator); + const [locationConstraintStr, ingestion] = locationConstraint.split(zenkoSeparator); if (locationConstraintStr) { locationConstraintVal = locationConstraintStr; bucket.setLocationConstraint(locationConstraintStr); @@ -210,88 +225,91 @@ function createBucket(authInfo, bucketName, headers, acl: bucket.acl, log, }; - async.parallel({ - prepareNewBucketMD: function prepareNewBucketMD(callback) { - acl.parseAclFromHeaders(parseAclParams, (err, parsedACL) => { - if (err) { - log.debug('error parsing acl from headers', { - error: err, - }); - return callback(err); - } - bucket.setFullAcl(parsedACL); - return callback(null, bucket); - }); - }, - getAnyExistingBucketInfo: function getAnyExistingBucketInfo(callback) { - metadata.getBucket(bucketName, log, (err, data) => { - // TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver - if (err && err.NoSuchBucket) { - return callback(null, 'NoBucketYet'); - } - if (err) { - return callback(err); - } - return callback(null, data); - }); + async.parallel( + { + prepareNewBucketMD: function prepareNewBucketMD(callback) { + acl.parseAclFromHeaders(parseAclParams, (err, parsedACL) => { + if (err) { + log.debug('error parsing acl from headers', { + error: err, + }); + return callback(err); + } + bucket.setFullAcl(parsedACL); + return callback(null, bucket); + }); + }, + getAnyExistingBucketInfo: function getAnyExistingBucketInfo(callback) { + metadata.getBucket(bucketName, log, (err, data) => { + // TODO: move to `.is` once BKTCLT-9 is done and bumped in Cloudserver + if (err && err.NoSuchBucket) { + return callback(null, 'NoBucketYet'); + } + if (err) { + return callback(err); + } + return callback(null, data); + }); + }, }, - }, - // Function to run upon finishing both parallel requests - (err, results) => { - if (err) { - return cb(err); - } - const existingBucketMD = results.getAnyExistingBucketInfo; - if (existingBucketMD instanceof BucketInfo && - existingBucketMD.getOwner() !== canonicalID && - !isServiceAccount(canonicalID)) { - // return existingBucketMD to collect cors headers - return cb(errors.BucketAlreadyExists, existingBucketMD); - } - const newBucketMD = results.prepareNewBucketMD; - if (existingBucketMD === 'NoBucketYet') { - const bucketSseConfig = parseBucketEncryptionHeaders(headers); + // Function to run upon finishing both parallel requests + (err, results) => { + if (err) { + return cb(err); + } + const existingBucketMD = results.getAnyExistingBucketInfo; + if ( + existingBucketMD instanceof BucketInfo && + existingBucketMD.getOwner() !== canonicalID && + !isServiceAccount(canonicalID) + ) { + // return existingBucketMD to collect cors headers + return cb(errors.BucketAlreadyExists, existingBucketMD); + } + const newBucketMD = results.prepareNewBucketMD; + if (existingBucketMD === 'NoBucketYet') { + const bucketSseConfig = parseBucketEncryptionHeaders(headers); - // Apply global SSE configuration when global encryption is enabled - // and no SSE settings were specified during bucket creation. - // Bucket-specific SSE headers override the default encryption. - const sseConfig = config.globalEncryptionEnabled && !bucketSseConfig.algorithm - ? { - algorithm: 'AES256', - mandatory: true, - } : bucketSseConfig; + // Apply global SSE configuration when global encryption is enabled + // and no SSE settings were specified during bucket creation. + // Bucket-specific SSE headers override the default encryption. + const sseConfig = + config.globalEncryptionEnabled && !bucketSseConfig.algorithm + ? { + algorithm: 'AES256', + mandatory: true, + } + : bucketSseConfig; - return bucketLevelServerSideEncryption( - bucket, sseConfig, log, - (err, sseInfo) => { + return bucketLevelServerSideEncryption(bucket, sseConfig, log, (err, sseInfo) => { if (err) { return cb(err); } newBucketMD.setServerSideEncryption(sseInfo); - log.trace( - 'new bucket without flags; adding transient label'); + log.trace('new bucket without flags; adding transient label'); newBucketMD.addTransientFlag(); - return freshStartCreateBucket(newBucketMD, canonicalID, - log, cb); + return freshStartCreateBucket(newBucketMD, canonicalID, log, cb); }); - } - if (existingBucketMD.hasTransientFlag() || - existingBucketMD.hasDeletedFlag()) { - log.trace('bucket has transient flag or deleted flag. cleaning up'); - return cleanUpBucket(newBucketMD, canonicalID, log, cb); - } - // If bucket already exists in non-transient and non-deleted - // state and owned by requester, then return BucketAlreadyOwnedByYou - // error unless old AWS behavior (us-east-1) - // Existing locationConstraint must have legacyAwsBehavior === true - // New locationConstraint should have legacyAwsBehavior === true - if (isLegacyAWSBehavior(locationConstraintVal) && - isLegacyAWSBehavior(existingBucketMD.getLocationConstraint())) { - log.trace('returning 200 instead of 409 to mirror us-east-1'); - return cb(null, existingBucketMD); - } - return cb(errors.BucketAlreadyOwnedByYou, existingBucketMD); - }); + } + if (existingBucketMD.hasTransientFlag() || existingBucketMD.hasDeletedFlag()) { + log.trace('bucket has transient flag or deleted flag. cleaning up'); + return cleanUpBucket(newBucketMD, canonicalID, log, cb); + } + // If bucket already exists in non-transient and non-deleted + // state and owned by requester, then return BucketAlreadyOwnedByYou + // error unless old AWS behavior (us-east-1) + // Existing locationConstraint must have legacyAwsBehavior === true + // New locationConstraint should have legacyAwsBehavior === true + if ( + isLegacyAWSBehavior(locationConstraintVal) && + isLegacyAWSBehavior(existingBucketMD.getLocationConstraint()) + ) { + log.trace('returning 200 instead of 409 to mirror us-east-1'); + return cb(null, existingBucketMD); + } + return cb(errors.BucketAlreadyOwnedByYou, existingBucketMD); + }, + ); } module.exports = { diff --git a/lib/services.js b/lib/services.js index cbf8740170..10545099b6 100644 --- a/lib/services.js +++ b/lib/services.js @@ -12,8 +12,7 @@ const constants = require('../constants'); const { config } = require('./Config'); const { data } = require('./data/wrapper'); const metadata = require('./metadata/wrapper'); -const { setObjectLockInformation } - = require('./api/apiUtils/object/objectLockHelpers'); +const { setObjectLockInformation } = require('./api/apiUtils/object/objectLockHelpers'); const removeAWSChunked = require('./api/apiUtils/object/removeAWSChunked'); const { parseTagFromQuery } = s3middleware.tagging; @@ -40,52 +39,53 @@ const services = { // (without special increase) // TODO: Consider implementing pagination like object listing // with respect to bucket listing so can go beyond 10000 - metadata.listObject(bucketUsers, { prefix, maxKeys: 10000 }, log, - (err, listResponse) => { - // If MD responds with NoSuchBucket, this means the - // hidden usersBucket has not yet been created for - // the domain. If this is the case, it means - // that no buckets in this domain have been created so - // it follows that this particular user has no buckets. - // So, the get service listing should not have any - // buckets to list. By returning an empty array, the - // getService API will just respond with the user info - // without listing any buckets. - if (err?.is?.NoSuchBucket) { - log.trace('no buckets found'); - // If we checked the old user bucket, that means we - // already checked the new user bucket. If neither the - // old user bucket or the new user bucket exist, no buckets - // have yet been created in the namespace so an empty - // listing should be returned - if (overrideUserbucket) { - return cb(null, [], splitter); - } - // Since there were no results from checking the - // new users bucket, we check the old users bucket - return this.getService(authInfo, request, log, - constants.oldSplitter, cb, oldUsersBucket); - } - if (err) { - log.error('error from metadata', { error: err }); - return cb(err); + metadata.listObject(bucketUsers, { prefix, maxKeys: 10000 }, log, (err, listResponse) => { + // If MD responds with NoSuchBucket, this means the + // hidden usersBucket has not yet been created for + // the domain. If this is the case, it means + // that no buckets in this domain have been created so + // it follows that this particular user has no buckets. + // So, the get service listing should not have any + // buckets to list. By returning an empty array, the + // getService API will just respond with the user info + // without listing any buckets. + if (err?.is?.NoSuchBucket) { + log.trace('no buckets found'); + // If we checked the old user bucket, that means we + // already checked the new user bucket. If neither the + // old user bucket or the new user bucket exist, no buckets + // have yet been created in the namespace so an empty + // listing should be returned + if (overrideUserbucket) { + return cb(null, [], splitter); } - return cb(null, listResponse.Contents, splitter); - }); + // Since there were no results from checking the + // new users bucket, we check the old users bucket + return this.getService(authInfo, request, log, constants.oldSplitter, cb, oldUsersBucket); + } + if (err) { + log.error('error from metadata', { error: err }); + return cb(err); + } + return cb(null, listResponse.Contents, splitter); + }); }, - /** - * Check that hashedStream.completedHash matches header contentMd5. - * @param {object} contentMD5 - content-md5 header - * @param {string} completedHash - hashed stream once completed - * @param {RequestLogger} log - the current request logger - * @return {boolean} - true if contentMD5 matches or is undefined, - * false otherwise - */ + /** + * Check that hashedStream.completedHash matches header contentMd5. + * @param {object} contentMD5 - content-md5 header + * @param {string} completedHash - hashed stream once completed + * @param {RequestLogger} log - the current request logger + * @return {boolean} - true if contentMD5 matches or is undefined, + * false otherwise + */ checkHashMatchMD5(contentMD5, completedHash, log) { if (contentMD5 && completedHash && contentMD5 !== completedHash) { - log.debug('contentMD5 and completedHash does not match', - { method: 'checkHashMatchMD5', completedHash, contentMD5 }); + log.debug('contentMD5 and completedHash does not match', { + method: 'checkHashMatchMD5', + completedHash, + contentMD5, + }); return false; } return true; @@ -102,15 +102,45 @@ const services = { * @return {function} executes callback with err or ETag as arguments */ metadataStoreObject(bucketName, dataGetInfo, cipherBundle, params, cb) { - const { objectKey, authInfo, size, contentMD5, metaHeaders, - contentType, cacheControl, contentDisposition, contentEncoding, - expires, multipart, headers, overrideMetadata, log, - lastModifiedDate, versioning, versionId, uploadId, - tagging, taggingCopy, replicationInfo, defaultRetention, - dataStoreName, creationTime, retentionMode, retentionDate, - legalHold, originOp, updateMicroVersionId, archive, oldReplayId, - deleteNullKey, amzStorageClass, overheadField, needOplogUpdate, - restoredEtag, bucketOwnerId } = params; + const { + objectKey, + authInfo, + size, + contentMD5, + metaHeaders, + contentType, + cacheControl, + contentDisposition, + contentEncoding, + expires, + multipart, + headers, + overrideMetadata, + log, + lastModifiedDate, + versioning, + versionId, + uploadId, + tagging, + taggingCopy, + replicationInfo, + defaultRetention, + dataStoreName, + creationTime, + retentionMode, + retentionDate, + legalHold, + originOp, + updateMicroVersionId, + archive, + oldReplayId, + deleteNullKey, + amzStorageClass, + overheadField, + needOplogUpdate, + restoredEtag, + bucketOwnerId, + } = params; log.trace('storing object in metadata'); assert.strictEqual(typeof bucketName, 'string'); const md = new ObjectMD(); @@ -192,12 +222,15 @@ const services = { // update restore if (archive) { md.setAmzStorageClass(amzStorageClass); - md.setArchive(new ObjectMDArchive( - archive.archiveInfo, - archive.restoreRequestedAt, - archive.restoreRequestedDays, - archive.restoreCompletedAt, - archive.restoreWillExpireAt)); + md.setArchive( + new ObjectMDArchive( + archive.archiveInfo, + archive.restoreRequestedAt, + archive.restoreRequestedDays, + archive.restoreCompletedAt, + archive.restoreWillExpireAt, + ), + ); md.setAmzRestore({ 'ongoing-request': false, 'expiry-date': archive.restoreWillExpireAt, @@ -283,54 +316,56 @@ const services = { // If this is not the completion of a multipart upload or // the creation of a delete marker, parse the headers to // get the ACL's if any - return async.waterfall([ - callback => { - if (multipart || md.getIsDeleteMarker()) { - return callback(); + return async.waterfall( + [ + callback => { + if (multipart || md.getIsDeleteMarker()) { + return callback(); + } + const parseAclParams = { + headers, + resourceType: 'object', + acl: md.getAcl(), + log, + }; + log.trace('parsing acl from headers'); + acl.parseAclFromHeaders(parseAclParams, (err, parsedACL) => { + if (err) { + log.debug('error parsing acl', { error: err }); + return callback(err); + } + md.setAcl(parsedACL); + return callback(); + }); + return null; + }, + callback => metadata.putObjectMD(bucketName, objectKey, md, options, log, callback), + ], + (err, data) => { + if (err) { + log.error('error from metadata', { error: err }); + return cb(err); } - const parseAclParams = { - headers, - resourceType: 'object', - acl: md.getAcl(), - log, - }; - log.trace('parsing acl from headers'); - acl.parseAclFromHeaders(parseAclParams, (err, parsedACL) => { - if (err) { - log.debug('error parsing acl', { error: err }); - return callback(err); + log.trace('object successfully stored in metadata'); + // if versioning is enabled, data will be returned from metadata + // as JSON containing a versionId which some APIs will need sent + // back to them + let versionId; + if (data) { + if (params.isNull && params.isDeleteMarker) { + versionId = 'null'; + } else if (!params.isNull) { + versionId = JSON.parse(data).versionId; } - md.setAcl(parsedACL); - return callback(); + } + return cb(err, { + lastModified: md.getLastModified(), + tags: md.getTags(), + contentMD5, + versionId, }); - return null; }, - callback => metadata.putObjectMD(bucketName, objectKey, md, - options, log, callback), - ], (err, data) => { - if (err) { - log.error('error from metadata', { error: err }); - return cb(err); - } - log.trace('object successfully stored in metadata'); - // if versioning is enabled, data will be returned from metadata - // as JSON containing a versionId which some APIs will need sent - // back to them - let versionId; - if (data) { - if (params.isNull && params.isDeleteMarker) { - versionId = 'null'; - } else if (!params.isNull) { - versionId = JSON.parse(data).versionId; - } - } - return cb(err, { - lastModified: md.getLastModified(), - tags: md.getTags(), - contentMD5, - versionId, - }); - }); + ); }, /** @@ -353,7 +388,11 @@ const services = { assert.strictEqual(typeof objectMD, 'object'); function deleteMDandData() { - return metadata.deleteObjectMD(bucketName, objectKey, options, log, + return metadata.deleteObjectMD( + bucketName, + objectKey, + options, + log, (err, res) => { if (err) { return cb(err, res); @@ -364,8 +403,7 @@ const services = { } if (deferLocationDeletion) { - return cb(null, Array.isArray(objectMD.location) - ? objectMD.location : [objectMD.location]); + return cb(null, Array.isArray(objectMD.location) ? objectMD.location : [objectMD.location]); } if (!Array.isArray(objectMD.location)) { @@ -379,14 +417,15 @@ const services = { } return cb(null, res); }); - }, originOp); + }, + originOp, + ); } const objGetInfo = objectMD.location; // special case that prevents azure blocks from unecessary deletion // will return null if no need - return data.protectAzureBlocks(bucketName, objectKey, objGetInfo, - log, err => { + return data.protectAzureBlocks(bucketName, objectKey, objGetInfo, log, err => { if (err) { return cb(err); } @@ -406,16 +445,14 @@ const services = { */ getObjectListing(bucketName, listingParams, log, cb) { assert.strictEqual(typeof bucketName, 'string'); - log.trace('performing metadata get object listing', - { listingParams }); - metadata.listObject(bucketName, listingParams, log, - (err, listResponse) => { - if (err) { - log.debug('error from metadata', { error: err }); - return cb(err); - } - return cb(null, listResponse); - }); + log.trace('performing metadata get object listing', { listingParams }); + metadata.listObject(bucketName, listingParams, log, (err, listResponse) => { + if (err) { + log.debug('error from metadata', { error: err }); + return cb(err); + } + return cb(null, listResponse); + }); }, /** @@ -458,10 +495,8 @@ const services = { } // Check each version in current batch for matching uploadId - const matchedVersion = (listResponse.Versions || []).find(version => - version.key === objectKey && - version.value && - version.value.uploadId === uploadId + const matchedVersion = (listResponse.Versions || []).find( + version => version.key === objectKey && version.value && version.value.uploadId === uploadId, ); if (matchedVersion) { @@ -479,7 +514,7 @@ const services = { return callback(); }); }, - err => cb(err, err ? null : foundVersion) + err => cb(err, err ? null : foundVersion), ); }, @@ -495,16 +530,14 @@ const services = { */ getLifecycleListing(bucketName, listingParams, log, cb) { assert.strictEqual(typeof bucketName, 'string'); - log.trace('performing metadata get object listing for lifecycle', - { listingParams }); - metadata.listLifecycleObject(bucketName, listingParams, log, - (err, listResponse) => { - if (err) { - log.debug('error from metadata', { error: err }); - return cb(err); - } - return cb(null, listResponse); - }); + log.trace('performing metadata get object listing for lifecycle', { listingParams }); + metadata.listLifecycleObject(bucketName, listingParams, log, (err, listResponse) => { + if (err) { + log.debug('error from metadata', { error: err }); + return cb(err); + } + return cb(null, listResponse); + }); }, metadataStoreMPObject(bucketName, cipherBundle, params, log, cb) { @@ -517,9 +550,7 @@ const services = { // the splitter. // 2) UploadId's are UUID version 4 const splitter = params.splitter; - const longMPUIdentifier = - `overview${splitter}${params.objectKey}` + - `${splitter}${params.uploadId}`; + const longMPUIdentifier = `overview${splitter}${params.objectKey}` + `${splitter}${params.uploadId}`; const multipartObjectMD = {}; multipartObjectMD.id = params.uploadId; multipartObjectMD.eventualStorageBucket = params.eventualStorageBucket; @@ -541,28 +572,19 @@ const services = { multipartObjectMD.key = params.objectKey; multipartObjectMD.uploadId = params.uploadId; multipartObjectMD['cache-control'] = params.headers['cache-control']; - multipartObjectMD['content-disposition'] = - params.headers['content-disposition']; - multipartObjectMD['content-encoding'] = - removeAWSChunked(params.headers['content-encoding']); - multipartObjectMD['content-type'] = - params.headers['content-type']; - multipartObjectMD.expires = - params.headers.expires; - multipartObjectMD['x-amz-storage-class'] = params.storageClass; // TODO: removed CLDSRV-639 - multipartObjectMD['x-amz-website-redirect-location'] = - params.headers['x-amz-website-redirect-location']; + multipartObjectMD['content-disposition'] = params.headers['content-disposition']; + multipartObjectMD['content-encoding'] = removeAWSChunked(params.headers['content-encoding']); + multipartObjectMD['content-type'] = params.headers['content-type']; + multipartObjectMD.expires = params.headers.expires; + multipartObjectMD['x-amz-storage-class'] = params.storageClass; // TODO: removed CLDSRV-639 + multipartObjectMD['x-amz-website-redirect-location'] = params.headers['x-amz-website-redirect-location']; if (cipherBundle) { - multipartObjectMD['x-amz-server-side-encryption'] = - cipherBundle.algorithm; + multipartObjectMD['x-amz-server-side-encryption'] = cipherBundle.algorithm; if (cipherBundle.masterKeyId) { - multipartObjectMD[ - 'x-amz-server-side-encryption-aws-kms-key-id'] = - cipherBundle.masterKeyId; + multipartObjectMD['x-amz-server-side-encryption-aws-kms-key-id'] = cipherBundle.masterKeyId; } } - multipartObjectMD.controllingLocationConstraint = - params.controllingLocationConstraint; + multipartObjectMD.controllingLocationConstraint = params.controllingLocationConstraint; multipartObjectMD.dataStoreName = params.dataStoreName; if (params.tagging) { const validationTagRes = parseTagFromQuery(params.tagging); @@ -605,15 +627,14 @@ const services = { return cb(err); } multipartObjectMD.acl = parsedACL; - metadata.putObjectMD(bucketName, longMPUIdentifier, - multipartObjectMD, {}, log, err => { - if (err) { - log.error('error from metadata', { error: err }); - return cb(err); - } + metadata.putObjectMD(bucketName, longMPUIdentifier, multipartObjectMD, {}, log, err => { + if (err) { + log.error('error from metadata', { error: err }); + return cb(err); + } - return cb(null, multipartObjectMD); - }); + return cb(null, multipartObjectMD); + }); return undefined; }); }, @@ -640,12 +661,10 @@ const services = { assert.strictEqual(typeof params.splitter, 'string'); assert.strictEqual(typeof params.storedMetadata, 'object'); const splitter = params.splitter; - const longMPUIdentifier = - `overview${splitter}${params.objectKey}${splitter}${params.uploadId}`; + const longMPUIdentifier = `overview${splitter}${params.objectKey}${splitter}${params.uploadId}`; const multipartObjectMD = Object.assign({}, params.storedMetadata); multipartObjectMD.completeInProgress = true; - metadata.putObjectMD(params.bucketName, longMPUIdentifier, multipartObjectMD, - {}, log, err => { + metadata.putObjectMD(params.bucketName, longMPUIdentifier, multipartObjectMD, {}, log, err => { if (err) { log.error('error from metadata', { error: err }); return cb(err); @@ -677,20 +696,18 @@ const services = { const mpuBucketName = `${constants.mpuBucketPrefix}${params.bucketName}`; const splitter = params.splitter; - const mpuOverviewKey = - `overview${splitter}${params.objectKey}${splitter}${params.uploadId}`; - return metadata.getObjectMD(mpuBucketName, mpuOverviewKey, {}, log, - (err, res) => { - if (err) { - log.error('error getting the overview object from mpu bucket', { - error: err, - method: 'services.isCompleteMPUInProgress', - params, - }); - return cb(err); - } - return cb(null, Boolean(res.completeInProgress)); - }); + const mpuOverviewKey = `overview${splitter}${params.objectKey}${splitter}${params.uploadId}`; + return metadata.getObjectMD(mpuBucketName, mpuOverviewKey, {}, log, (err, res) => { + if (err) { + log.error('error getting the overview object from mpu bucket', { + error: err, + method: 'services.isCompleteMPUInProgress', + params, + }); + return cb(err); + } + return cb(null, Boolean(res.completeInProgress)); + }); }, /** @@ -707,8 +724,7 @@ const services = { * - the overview key stored metadata */ metadataValidateMultipart(params, cb) { - const { bucketName, uploadId, authInfo, - objectKey, requestType, log } = params; + const { bucketName, uploadId, authInfo, objectKey, requestType, log } = params; assert.strictEqual(typeof bucketName, 'string'); // This checks whether the mpu bucket exists. @@ -716,13 +732,11 @@ const services = { const mpuBucketName = `${constants.mpuBucketPrefix}${bucketName}`; metadata.getBucket(mpuBucketName, log, (err, mpuBucket) => { if (err?.is?.NoSuchBucket) { - log.debug('bucket not found in metadata', { error: err, - method: 'services.metadataValidateMultipart' }); + log.debug('bucket not found in metadata', { error: err, method: 'services.metadataValidateMultipart' }); return cb(errors.NoSuchUpload); } if (err) { - log.error('error from metadata', { error: err, - method: 'services.metadataValidateMultipart' }); + log.error('error from metadata', { error: err, method: 'services.metadataValidateMultipart' }); return cb(err); } @@ -731,84 +745,75 @@ const services = { if (mpuBucket.getMdBucketModelVersion() < 2) { splitter = constants.oldSplitter; } - const mpuOverviewKey = - `overview${splitter}${objectKey}${splitter}${uploadId}`; + const mpuOverviewKey = `overview${splitter}${objectKey}${splitter}${uploadId}`; - metadata.getObjectMD(mpuBucket.getName(), mpuOverviewKey, - {}, log, (err, storedMetadata) => { - if (err) { - if (err.is.NoSuchKey) { - return cb(errors.NoSuchUpload); - } - log.error('error from metadata', { error: err }); - return cb(err); + metadata.getObjectMD(mpuBucket.getName(), mpuOverviewKey, {}, log, (err, storedMetadata) => { + if (err) { + if (err.is.NoSuchKey) { + return cb(errors.NoSuchUpload); } + log.error('error from metadata', { error: err }); + return cb(err); + } - const initiatorID = storedMetadata.initiator.ID; - const ownerID = storedMetadata['owner-id']; - const mpuOverview = { - key: storedMetadata.key, - id: storedMetadata.id, - eventualStorageBucket: - storedMetadata.eventualStorageBucket, - initiatorID, - initiatorDisplayName: - storedMetadata.initiator.DisplayName, - ownerID, - ownerDisplayName: - storedMetadata['owner-display-name'], - storageClass: - storedMetadata['x-amz-storage-class'], - initiated: storedMetadata.initiated, - controllingLocationConstraint: - storedMetadata.controllingLocationConstraint, - }; + const initiatorID = storedMetadata.initiator.ID; + const ownerID = storedMetadata['owner-id']; + const mpuOverview = { + key: storedMetadata.key, + id: storedMetadata.id, + eventualStorageBucket: storedMetadata.eventualStorageBucket, + initiatorID, + initiatorDisplayName: storedMetadata.initiator.DisplayName, + ownerID, + ownerDisplayName: storedMetadata['owner-display-name'], + storageClass: storedMetadata['x-amz-storage-class'], + initiated: storedMetadata.initiated, + controllingLocationConstraint: storedMetadata.controllingLocationConstraint, + }; - const tagging = storedMetadata['x-amz-tagging']; - if (tagging) { - mpuOverview.tagging = tagging; - } - // If access was provided by the destination bucket's - // bucket policies, go ahead. - if (requestType === 'bucketPolicyGoAhead') { - return cb(null, mpuBucket, mpuOverview, storedMetadata); - } + const tagging = storedMetadata['x-amz-tagging']; + if (tagging) { + mpuOverview.tagging = tagging; + } + // If access was provided by the destination bucket's + // bucket policies, go ahead. + if (requestType === 'bucketPolicyGoAhead') { + return cb(null, mpuBucket, mpuOverview, storedMetadata); + } - const requesterID = authInfo.isRequesterAnIAMUser() ? - authInfo.getArn() : authInfo.getCanonicalID(); - const isRequesterInitiator = - initiatorID === requesterID; - const isRequesterParentAccountOfInitiator = - ownerID === authInfo.getCanonicalID(); - if (requestType === 'putPart or complete') { - // Only the initiator of the multipart - // upload can upload a part or complete the mpu - if (!isRequesterInitiator) { - return cb(errors.AccessDenied); - } + const requesterID = authInfo.isRequesterAnIAMUser() ? authInfo.getArn() : authInfo.getCanonicalID(); + const isRequesterInitiator = initiatorID === requesterID; + const isRequesterParentAccountOfInitiator = ownerID === authInfo.getCanonicalID(); + if (requestType === 'putPart or complete') { + // Only the initiator of the multipart + // upload can upload a part or complete the mpu + if (!isRequesterInitiator) { + return cb(errors.AccessDenied); } - if (requestType === 'deleteMPU' - || requestType === 'listParts') { - // In order for account/user to be - // authorized must either be the - // bucket owner or intitator of - // the multipart upload request - // (or parent account of initiator). - // In addition if the bucket policy - // designates someone else with - // s3:AbortMultipartUpload or - // s3:ListMultipartUploadPartsrights, - // as applicable, that account/user will have the right. - // If got to this step, it means there is - // no bucket policy on this. - if (mpuBucket.getOwner() !== authInfo.getCanonicalID() - && !isRequesterInitiator - && !isRequesterParentAccountOfInitiator) { - return cb(errors.AccessDenied); - } + } + if (requestType === 'deleteMPU' || requestType === 'listParts') { + // In order for account/user to be + // authorized must either be the + // bucket owner or intitator of + // the multipart upload request + // (or parent account of initiator). + // In addition if the bucket policy + // designates someone else with + // s3:AbortMultipartUpload or + // s3:ListMultipartUploadPartsrights, + // as applicable, that account/user will have the right. + // If got to this step, it means there is + // no bucket policy on this. + if ( + mpuBucket.getOwner() !== authInfo.getCanonicalID() && + !isRequesterInitiator && + !isRequesterParentAccountOfInitiator + ) { + return cb(errors.AccessDenied); } - return cb(null, mpuBucket, mpuOverview, storedMetadata); - }); + } + return cb(null, mpuBucket, mpuOverview, storedMetadata); + }); return undefined; }); }, @@ -830,13 +835,11 @@ const services = { * @param {function} cb - callback to send error or move to next task * @return {undefined} */ - metadataStorePart(mpuBucketName, partLocations, - metaStoreParams, log, cb) { + metadataStorePart(mpuBucketName, partLocations, metaStoreParams, log, cb) { assert.strictEqual(typeof mpuBucketName, 'string'); - const { partNumber, contentMD5, size, uploadId, lastModified, splitter, overheadField, ownerId } - = metaStoreParams; - const dateModified = typeof lastModified === 'string' ? - lastModified : new Date().toJSON(); + const { partNumber, contentMD5, size, uploadId, lastModified, splitter, overheadField, ownerId } = + metaStoreParams; + const dateModified = typeof lastModified === 'string' ? lastModified : new Date().toJSON(); assert.strictEqual(typeof splitter, 'string'); const partKey = `${uploadId}${splitter}${partNumber}`; const omVal = { @@ -844,7 +847,7 @@ const services = { // from an object to an array 'md-model-version': 3, partLocations, - 'key': partKey, + key: partKey, 'last-modified': dateModified, 'content-md5': contentMD5, 'content-length': size, @@ -866,14 +869,14 @@ const services = { }, /** - * Gets list of open multipart uploads in bucket - * @param {object} MPUbucketName - bucket in which objectMetadata is stored - * @param {object} listingParams - params object passing on - * needed items from request object - * @param {object} log - Werelogs logger - * @param {function} cb - callback to listMultipartUploads.js - * @return {undefined} - */ + * Gets list of open multipart uploads in bucket + * @param {object} MPUbucketName - bucket in which objectMetadata is stored + * @param {object} listingParams - params object passing on + * needed items from request object + * @param {object} log - Werelogs logger + * @param {function} cb - callback to listMultipartUploads.js + * @return {undefined} + */ getMultipartUploadListing(MPUbucketName, listingParams, log, cb) { assert.strictEqual(typeof MPUbucketName, 'string'); assert.strictEqual(typeof listingParams.splitter, 'string'); @@ -900,8 +903,7 @@ const services = { if (bucket.getMdBucketModelVersion() < 2) { listParams.splitter = constants.oldSplitter; } - metadata.listMultipartUploads(MPUbucketName, listParams, log, - cb); + metadata.listMultipartUploads(MPUbucketName, listParams, log, cb); return undefined; }); }, @@ -923,24 +925,38 @@ const services = { if (err?.is?.NoSuchBucket) { log.trace('no buckets found'); const creationDate = new Date().toJSON(); - const mpuBucket = new BucketInfo(MPUBucketName, + const mpuBucket = new BucketInfo( + MPUBucketName, destinationBucket.getOwner(), - destinationBucket.getOwnerDisplayName(), creationDate, - BucketInfo.currentModelVersion()); + destinationBucket.getOwnerDisplayName(), + creationDate, + BucketInfo.currentModelVersion(), + ); // Note that unlike during the creation of a normal bucket, // we do NOT add this bucket to the lists of a user's buckets. // By not adding this bucket to the lists of a user's buckets, // a getService request should not return a reference to this // bucket. This is the desired behavior since this should be // a hidden bucket. - return metadata.createBucket(MPUBucketName, mpuBucket, log, - err => { - if (err) { - log.error('error from metadata', { error: err }); - return cb(err); - } - return cb(null, mpuBucket); - }); + return metadata.createBucket(MPUBucketName, mpuBucket, log, err => { + if (err?.is?.BucketAlreadyExists) { + return metadata.getBucket(MPUBucketName, log, (getErr, existingBucket) => { + if (getErr) { + log.error('error from metadata', { + error: getErr, + method: 'services.getMPUBucket', + }); + return cb(getErr); + } + return cb(null, existingBucket); + }); + } + if (err) { + log.error('error from metadata', { error: err }); + return cb(err); + } + return cb(null, mpuBucket); + }); } if (err) { log.error('error from metadata', { @@ -965,8 +981,7 @@ const services = { }, getSomeMPUparts(params, cb) { - const { uploadId, mpuBucketName, maxParts, partNumberMarker, log } = - params; + const { uploadId, mpuBucketName, maxParts, partNumberMarker, log } = params; assert.strictEqual(typeof mpuBucketName, 'string'); assert.strictEqual(typeof params.splitter, 'string'); const paddedPartNumber = `000000${partNumberMarker}`.substr(-5); @@ -983,9 +998,14 @@ const services = { // If have efficient way to batch delete metadata, should so this // all at once in production implementation assert.strictEqual(typeof mpuBucketName, 'string'); - async.eachLimit(keysToDelete, 5, (key, callback) => { - metadata.deleteObjectMD(mpuBucketName, key, { overheadField: constants.overheadField }, log, callback); - }, cb); + async.eachLimit( + keysToDelete, + 5, + (key, callback) => { + metadata.deleteObjectMD(mpuBucketName, key, { overheadField: constants.overheadField }, log, callback); + }, + cb, + ); }, }; diff --git a/package.json b/package.json index e330afd66a..f1cebcf33d 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "@azure/storage-blob": "^12.28.0", "@hapi/joi": "^17.1.1", "@smithy/node-http-handler": "^3.0.0", - "arsenal": "git+https://github.com/scality/Arsenal#8.4.16", + "arsenal": "git+https://github.com/scality/Arsenal#0bd1b04c", "async": "2.6.4", "bucketclient": "scality/bucketclient#8.2.7", "bufferutil": "^4.0.8", diff --git a/tests/unit/bucket/bucketCreation.js b/tests/unit/bucket/bucketCreation.js index 6ae385d17c..106aa6a38c 100644 --- a/tests/unit/bucket/bucketCreation.js +++ b/tests/unit/bucket/bucketCreation.js @@ -1,8 +1,12 @@ const assert = require('assert'); +const sinon = require('sinon'); +const { errors } = require('arsenal'); +const BucketInfo = require('arsenal').models.BucketInfo; +const constants = require('../../../constants'); +const metadata = require('../../../lib/metadata/wrapper'); const { cleanup, DummyRequestLogger } = require('../helpers'); -const { createBucket } = - require('../../../lib/api/apiUtils/bucket/bucketCreation'); +const { createBucket } = require('../../../lib/api/apiUtils/bucket/bucketCreation'); const { makeAuthInfo } = require('../helpers'); const bucketName = 'creationbucket'; @@ -15,34 +19,30 @@ const specialBehaviorLocationConstraint = 'us-east-1'; describe('bucket creation', () => { it('should create a bucket', done => { - createBucket(authInfo, bucketName, headers, - normalBehaviorLocationConstraint, log, err => { - assert.ifError(err); - done(); - }); + createBucket(authInfo, bucketName, headers, normalBehaviorLocationConstraint, log, err => { + assert.ifError(err); + done(); + }); }); describe('when you already created the bucket in us-east-1', () => { beforeEach(done => { cleanup(); - createBucket(authInfo, bucketName, headers, - specialBehaviorLocationConstraint, log, err => { - assert.ifError(err); - done(); - }); + createBucket(authInfo, bucketName, headers, specialBehaviorLocationConstraint, log, err => { + assert.ifError(err); + done(); + }); }); it('should return 200 if try to recreate in us-east-1', done => { - createBucket(authInfo, bucketName, headers, - specialBehaviorLocationConstraint, log, err => { + createBucket(authInfo, bucketName, headers, specialBehaviorLocationConstraint, log, err => { assert.ifError(err); done(); }); }); it('should return 409 if try to recreate in non-us-east-1', done => { - createBucket(authInfo, bucketName, headers, - normalBehaviorLocationConstraint, log, err => { + createBucket(authInfo, bucketName, headers, normalBehaviorLocationConstraint, log, err => { assert.strictEqual(err.is.BucketAlreadyOwnedByYou, true); done(); }); @@ -52,32 +52,72 @@ describe('bucket creation', () => { describe('when you already created the bucket in non-us-east-1', () => { beforeEach(done => { cleanup(); - createBucket(authInfo, bucketName, headers, - normalBehaviorLocationConstraint, log, err => { - assert.ifError(err); - done(); - }); + createBucket(authInfo, bucketName, headers, normalBehaviorLocationConstraint, log, err => { + assert.ifError(err); + done(); + }); }); it('should return 409 if try to recreate in us-east-1', done => { - createBucket(authInfo, bucketName, headers, - specialBehaviorLocationConstraint, log, err => { + createBucket(authInfo, bucketName, headers, specialBehaviorLocationConstraint, log, err => { assert.strictEqual(err.is.BucketAlreadyOwnedByYou, true); done(); }); }); }); }); + +describe('bucket creation when createBucket races', () => { + const raceBucketName = 'race-creation-bucket'; + let sandbox; + + beforeEach(() => { + cleanup(); + sandbox = sinon.createSandbox(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should complete creation when metadata returns BucketAlreadyExists', done => { + const usersBucketMD = new BucketInfo(constants.usersBucket, 'admin', 'admin', new Date().toJSON()); + + sandbox.stub(metadata, 'getBucket').callsFake((name, log, cb) => { + if (name === raceBucketName) { + return cb(errors.NoSuchBucket); + } + if (name === constants.usersBucket) { + return cb(null, usersBucketMD); + } + return cb(errors.NoSuchBucket); + }); + sandbox.stub(metadata, 'createBucket').callsFake((name, bucket, log, cb) => { + if (name === raceBucketName) { + return cb(errors.BucketAlreadyExists); + } + return cb(null); + }); + sandbox.stub(metadata, 'putObjectMD').yields(null); + sandbox.stub(metadata, 'updateBucket').yields(null); + + createBucket(authInfo, raceBucketName, headers, normalBehaviorLocationConstraint, log, err => { + assert.ifError(err); + sandbox.assert.calledOnce(metadata.createBucket); + done(); + }); + }); +}); + describe('bucket creation with object lock', () => { it('should return 200 when creating a bucket with object lock', done => { const bucketName = 'test-bucket-with-objectlock'; const headers = { 'x-amz-bucket-object-lock-enabled': 'true', }; - createBucket(authInfo, bucketName, headers, - normalBehaviorLocationConstraint, log, err => { - assert.ifError(err); - done(); - }); + createBucket(authInfo, bucketName, headers, normalBehaviorLocationConstraint, log, err => { + assert.ifError(err); + done(); + }); }); }); diff --git a/tests/unit/lib/services.spec.js b/tests/unit/lib/services.spec.js index 3c7b28fde7..a5ba918c13 100644 --- a/tests/unit/lib/services.spec.js +++ b/tests/unit/lib/services.spec.js @@ -91,8 +91,12 @@ describe('services', () => { IsTruncated: false, }); - services.findObjectVersionByUploadId(bucketName, objectKey, 'non-existent-upload-id', - log, (err, foundVersion) => { + services.findObjectVersionByUploadId( + bucketName, + objectKey, + 'non-existent-upload-id', + log, + (err, foundVersion) => { assert.ifError(err); sinon.assert.calledTwice(getObjectListingStub); @@ -101,7 +105,8 @@ describe('services', () => { assert.strictEqual(secondCallParams.versionIdMarker, 'version-marker'); assert.strictEqual(foundVersion, null); done(); - }); + }, + ); }); it('should find a version on the first page of many and stop listing', done => { @@ -154,4 +159,61 @@ describe('services', () => { }); }); }); + + describe('getMPUBucket', () => { + const { errors } = require('arsenal'); + const BucketInfo = require('arsenal').models.BucketInfo; + const metadata = require('../../../lib/metadata/wrapper'); + const constants = require('../../../constants'); + const destinationBucket = new BucketInfo('dest-bucket', 'owner', 'ownerDisplay', new Date().toJSON()); + let getBucketStub; + let createBucketStub; + + beforeEach(() => { + getBucketStub = sinon.stub(metadata, 'getBucket'); + createBucketStub = sinon.stub(metadata, 'createBucket'); + }); + + it('should return existing MPU bucket when createBucket races', done => { + const mpuBucketName = `${constants.mpuBucketPrefix}${bucketName}`; + const existingMpuBucket = new BucketInfo(mpuBucketName, 'owner', 'ownerDisplay', new Date().toJSON()); + getBucketStub.onFirstCall().yields(errors.NoSuchBucket); + createBucketStub.yields(errors.BucketAlreadyExists); + getBucketStub.onSecondCall().yields(null, existingMpuBucket); + + services.getMPUBucket(destinationBucket, bucketName, log, (err, bucket) => { + assert.ifError(err); + assert.strictEqual(bucket, existingMpuBucket); + sinon.assert.calledTwice(getBucketStub); + sinon.assert.calledOnce(createBucketStub); + done(); + }); + }); + + it('should create MPU bucket when it does not exist', done => { + getBucketStub.yields(errors.NoSuchBucket); + createBucketStub.yields(null); + + services.getMPUBucket(destinationBucket, bucketName, log, (err, bucket) => { + assert.ifError(err); + assert.strictEqual(bucket.getName(), `${constants.mpuBucketPrefix}${bucketName}`); + sinon.assert.calledOnce(createBucketStub); + done(); + }); + }); + + it('should return error when refetch fails after BucketAlreadyExists', done => { + const testError = errors.InternalError; + getBucketStub.onFirstCall().yields(errors.NoSuchBucket); + createBucketStub.yields(errors.BucketAlreadyExists); + getBucketStub.onSecondCall().yields(testError); + + services.getMPUBucket(destinationBucket, bucketName, log, err => { + assert.strictEqual(err, testError); + sinon.assert.calledTwice(getBucketStub); + sinon.assert.calledOnce(createBucketStub); + done(); + }); + }); + }); }); diff --git a/yarn.lock b/yarn.lock index f815159f9c..f745e698f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6508,6 +6508,55 @@ arraybuffer.prototype.slice@^1.0.4: get-intrinsic "^1.2.6" is-array-buffer "^3.0.4" +"arsenal@git+https://github.com/scality/Arsenal#0bd1b04c": + version "8.4.20" + resolved "git+https://github.com/scality/Arsenal#0bd1b04cdfe15f5e4a48953c4efc6259f2e11377" + dependencies: + "@aws-sdk/client-kms" "^3.975.0" + "@aws-sdk/client-s3" "^3.975.0" + "@aws-sdk/credential-providers" "^3.975.0" + "@aws-sdk/lib-storage" "^3.975.0" + "@azure/identity" "^4.13.0" + "@azure/storage-blob" "^12.31.0" + "@js-sdsl/ordered-set" "^4.4.2" + "@opentelemetry/api" "^1.9.1" + "@opentelemetry/exporter-trace-otlp-http" "^0.219.0" + "@opentelemetry/resources" "^2.8.0" + "@opentelemetry/sdk-node" "^0.219.0" + "@opentelemetry/sdk-trace-base" "^2.8.0" + "@scality/hdclient" "^1.3.2" + "@smithy/node-http-handler" "^4.3.0" + "@smithy/protocol-http" "^5.3.5" + JSONStream "^1.3.5" + agentkeepalive "^4.6.0" + ajv "6.12.3" + async "~2.6.4" + backo "^1.1.0" + base-x "3.0.8" + base62 "^2.0.2" + debug "^4.4.3" + fcntl "github:scality/node-fcntl#0.3.0" + httpagent scality/httpagent#1.1.0 + https-proxy-agent "^7.0.6" + ioredis "^5.8.1" + ipaddr.js "^2.2.0" + joi "^18.0.1" + level "~5.0.1" + level-sublevel "~6.6.5" + mongodb "^6.20.0" + node-forge "^1.3.1" + prom-client "^15.1.3" + simple-glob "^0.2.0" + socket.io "^4.8.0" + socket.io-client "^4.8.0" + sproxydclient "github:scality/sproxydclient#8.2.1" + utf8 "^3.0.0" + uuid "^10.0.0" + werelogs scality/werelogs#8.2.4 + xml2js "^0.6.2" + optionalDependencies: + ioctl "^2.0.2" + "arsenal@git+https://github.com/scality/Arsenal#8.2.28": version "8.2.28" resolved "git+https://github.com/scality/Arsenal#7df5088715bb26a62ff1db2045e611029ff17de1" @@ -6591,55 +6640,6 @@ arraybuffer.prototype.slice@^1.0.4: optionalDependencies: ioctl "^2.0.2" -"arsenal@git+https://github.com/scality/Arsenal#8.4.16": - version "8.4.16" - resolved "git+https://github.com/scality/Arsenal#65223fdfc37f4471332c44775def9e985b320855" - dependencies: - "@aws-sdk/client-kms" "^3.975.0" - "@aws-sdk/client-s3" "^3.975.0" - "@aws-sdk/credential-providers" "^3.975.0" - "@aws-sdk/lib-storage" "^3.975.0" - "@azure/identity" "^4.13.0" - "@azure/storage-blob" "^12.31.0" - "@js-sdsl/ordered-set" "^4.4.2" - "@opentelemetry/api" "^1.9.1" - "@opentelemetry/exporter-trace-otlp-http" "^0.219.0" - "@opentelemetry/resources" "^2.8.0" - "@opentelemetry/sdk-node" "^0.219.0" - "@opentelemetry/sdk-trace-base" "^2.8.0" - "@scality/hdclient" "^1.3.2" - "@smithy/node-http-handler" "^4.3.0" - "@smithy/protocol-http" "^5.3.5" - JSONStream "^1.3.5" - agentkeepalive "^4.6.0" - ajv "6.12.3" - async "~2.6.4" - backo "^1.1.0" - base-x "3.0.8" - base62 "^2.0.2" - debug "^4.4.3" - fcntl "github:scality/node-fcntl#0.3.0" - httpagent scality/httpagent#1.1.0 - https-proxy-agent "^7.0.6" - ioredis "^5.8.1" - ipaddr.js "^2.2.0" - joi "^18.0.1" - level "~5.0.1" - level-sublevel "~6.6.5" - mongodb "^6.20.0" - node-forge "^1.3.1" - prom-client "^15.1.3" - simple-glob "^0.2.0" - socket.io "^4.8.0" - socket.io-client "^4.8.0" - sproxydclient "github:scality/sproxydclient#8.2.1" - utf8 "^3.0.0" - uuid "^10.0.0" - werelogs scality/werelogs#8.2.4 - xml2js "^0.6.2" - optionalDependencies: - ioctl "^2.0.2" - asn1@~0.2.3: version "0.2.6" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.6.tgz#0d3a7bb6e64e02a90c0303b31f292868ea09a08d"