From e59e6ac0b135972a93d699b7db5ba85b8bc4441f Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 25 Aug 2025 15:03:25 +0200 Subject: [PATCH 01/21] =?UTF-8?q?=E2=9C=A8=20add=20the=20versioningPreproc?= =?UTF-8?q?essing=20call=20to=20copy=20object=20when=20needed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/api/apiUtils/object/createAndStoreObject.js | 6 ++++++ lib/api/apiUtils/object/versioning.js | 5 +++-- lib/routes/routeBackbeat.js | 14 ++++++++------ 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/api/apiUtils/object/createAndStoreObject.js b/lib/api/apiUtils/object/createAndStoreObject.js index fcf3ce4c0e..dc9b322f8d 100644 --- a/lib/api/apiUtils/object/createAndStoreObject.js +++ b/lib/api/apiUtils/object/createAndStoreObject.js @@ -218,6 +218,7 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo, metadataStoreParams.contentMD5 = constants.emptyFileMd5; return next(null, null, null); } + // Handle mdOnlyHeader as a metadata only operation. If // the object in question is actually 0 byte or has a body size // then handle normally. @@ -244,6 +245,7 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo, return next(null, dataGetInfo, _md5); } } + return dataStore(objectKeyContext, cipherBundle, request, size, streamingV4Params, backendInfo, log, next); }, @@ -280,10 +282,12 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo, const options = overwritingVersioning(objMD, metadataStoreParams); return process.nextTick(() => next(null, options, infoArr)); } + if (!bucketMD.isVersioningEnabled() && objMD?.archive?.archiveInfo) { // Ensure we trigger a "delete" event in the oplog for the previously archived object metadataStoreParams.needOplogUpdate = 's3:ReplaceArchivedObject'; } + return versioningPreprocessing(bucketName, bucketMD, metadataStoreParams.objectKey, objMD, log, (err, options) => { if (err) { @@ -316,9 +320,11 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo, metadataStoreParams.versioning = options.versioning; metadataStoreParams.isNull = options.isNull; metadataStoreParams.deleteNullKey = options.deleteNullKey; + if (options.extraMD) { Object.assign(metadataStoreParams, options.extraMD); } + return _storeInMDandDeleteData(bucketName, infoArr, cipherBundle, metadataStoreParams, options.dataToDelete, log, requestMethod, next); diff --git a/lib/api/apiUtils/object/versioning.js b/lib/api/apiUtils/object/versioning.js index 9747f7f015..c6e41fafc7 100644 --- a/lib/api/apiUtils/object/versioning.js +++ b/lib/api/apiUtils/object/versioning.js @@ -329,14 +329,15 @@ function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD, log, callback) { const mst = getMasterState(objMD); const vCfg = bucketMD.getVersioningConfiguration(); - // bucket is not versioning configured + if (!vCfg) { const options = { dataToDelete: mst.objLocation }; return process.nextTick(callback, null, options); } - // bucket is versioning configured + const { options, nullVersionId, delOptions } = processVersioningState(mst, vCfg.Status, config.nullVersionCompatMode); + return async.series([ function storeNullVersionMD(next) { if (!nullVersionId) { diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index fcd4c6a7c1..7f489a0a2c 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -40,6 +40,8 @@ const { listLifecycleOrphanDeleteMarkers } = require('../api/backbeat/listLifecy const { objectDeleteInternal } = require('../api/objectDelete'); const quotaUtils = require('../api/apiUtils/quotas/quotaUtils'); const { handleAuthorizationResults } = require('../api/api'); +const { versioningPreprocessing } + = require('../api/apiUtils/object/versioning'); const { CURRENT_TYPE, NON_CURRENT_TYPE, ORPHAN_DM_TYPE } = constants.lifecycleListing; @@ -508,23 +510,22 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { if (err) { return callback(err); } + let omVal; + try { omVal = JSON.parse(payload); } catch { - // FIXME: add error type MalformedJSON return callback(errors.MalformedPOSTRequest); } + const { headers, bucketName, objectKey } = request; - // check if it's metadata only operation + if (headers['x-scal-replication-content'] === 'METADATA') { if (!objMd) { - // if the target does not exist, return an error to - // backbeat, who will have to retry the operation as a - // complete replication return callback(errors.ObjNotFound); } - // use original data locations and encryption info + [ 'location', 'x-amz-server-side-encryption', @@ -630,6 +631,7 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { } return async.series([ + next => versioningPreprocessing(bucketName, bucketInfo, objectKey, objMd, log, next), // Zenko's CRR delegates replacing the account // information to the destination's Cloudserver, as // Vault admin APIs are not exposed externally. From f6c75df7284aa1629c8303ace3f201dce2212065 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 25 Aug 2025 15:04:15 +0200 Subject: [PATCH 02/21] =?UTF-8?q?=E2=9C=85=20re-enable=20test=20that=20wer?= =?UTF-8?q?e=20disable=20because=20of=20the=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routes/routeBackbeatForReplication.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/multipleBackend/routes/routeBackbeatForReplication.js b/tests/multipleBackend/routes/routeBackbeatForReplication.js index be92f69282..d9998ffbc8 100644 --- a/tests/multipleBackend/routes/routeBackbeatForReplication.js +++ b/tests/multipleBackend/routes/routeBackbeatForReplication.js @@ -76,6 +76,7 @@ const scenarios = [ { name: 'same account', src, dst: src }, { name: 'cross account', src, dst }, ]; +const itSkipIfNotS3C = process.env.S3_END_TO_END ? it : it.skip; scenarios.forEach(({ name, src, dst }) => { describe(`backbeat routes for replication (${name})`, () => { @@ -941,9 +942,7 @@ describe(`backbeat routes for replication (${name})`, () => { }); }); - // TODO fix and unskip by CLDSRV-632 - const itSkipNotS3C = process.env.S3_END_TO_END ? it : it.skip; - itSkipNotS3C('should replicate/put metadata to a destination that has a null version', done => { + it('should replicate/put metadata to a destination that has a null version', done => { let objMD; let versionId; @@ -1017,7 +1016,7 @@ describe(`backbeat routes for replication (${name})`, () => { }); }); - itSkipNotS3C('should replicate/put metadata to a destination that has a suspended null version', done => { + itSkipIfNotS3C('should replicate/put metadata to a destination that has a suspended null version', done => { let objMD; let versionId; @@ -1092,7 +1091,7 @@ describe(`backbeat routes for replication (${name})`, () => { }); }); - itSkipNotS3C('should replicate/put metadata to a destination that has a previously updated null version', done => { + it('should replicate/put metadata to a destination that has a previously updated null version', done => { let objMD; let objMDNull; let versionId; @@ -1193,7 +1192,7 @@ describe(`backbeat routes for replication (${name})`, () => { }); }); - itSkipNotS3C( + itSkipIfNotS3C( 'should replicate/put metadata to a destination that has a suspended null version with internal version', done => { const tagSet = [ @@ -1283,7 +1282,7 @@ describe(`backbeat routes for replication (${name})`, () => { }); }); - itSkipNotS3C('should mimic null version replication by crrExistingObjects, then replicate version', done => { + it('should mimic null version replication by crrExistingObjects, then replicate version', done => { let objMDNull; let objMDNullReplicated; let objMDVersion; From fffb8a579c132a87b4e3b55b33fc360a6c55b276 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 25 Aug 2025 16:14:42 +0200 Subject: [PATCH 03/21] =?UTF-8?q?=E2=9C=85=20update=20test=20logic=20to=20?= =?UTF-8?q?use=20putObjectMD=20onCall=201=20only=20and=20manage=20local=20?= =?UTF-8?q?timezone=20difference?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 4 +- tests/unit/api/apiUtils/objectLockHelpers.js | 2 +- tests/unit/routes/routeBackbeat.js | 43 ++++++++++++++++---- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 7f489a0a2c..a40ae8b80e 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -631,7 +631,6 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { } return async.series([ - next => versioningPreprocessing(bucketName, bucketInfo, objectKey, objMd, log, next), // Zenko's CRR delegates replacing the account // information to the destination's Cloudserver, as // Vault admin APIs are not exposed externally. @@ -652,6 +651,9 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { return next(); }); }, + next => { + versioningPreprocessing(bucketName, bucketInfo, objectKey, objMd, log, next); + }, next => { log.trace('putting object version', { objectKey: request.objectKey, omVal, options }); diff --git a/tests/unit/api/apiUtils/objectLockHelpers.js b/tests/unit/api/apiUtils/objectLockHelpers.js index 912e42f171..c0c445cef6 100644 --- a/tests/unit/api/apiUtils/objectLockHelpers.js +++ b/tests/unit/api/apiUtils/objectLockHelpers.js @@ -159,7 +159,7 @@ describe('objectLockHelpers: calculateRetainUntilDate', () => { }; const date = moment(); const expectedRetainUntilDate - = date.add(mockConfigWithDays.days, 'days'); + = date.add(mockConfigWithDays.days * 86400000, 'ms'); const retainUntilDate = calculateRetainUntilDate(mockConfigWithDays); assert.strictEqual(retainUntilDate.slice(0, 16), expectedRetainUntilDate.toISOString().slice(0, 16)); diff --git a/tests/unit/routes/routeBackbeat.js b/tests/unit/routes/routeBackbeat.js index 5dc3e8576e..4c762eb56c 100644 --- a/tests/unit/routes/routeBackbeat.js +++ b/tests/unit/routes/routeBackbeat.js @@ -229,8 +229,11 @@ describe('routeBackbeat', () => { it('should put metadata after updating account info', async () => { mockRequest.url = '/_/backbeat/metadata/bucket0/key0?accountId=123456789012'; - - sandbox.stub(metadata, 'putObjectMD').callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); + putObjectMDStub.onCall(0).callsFake( + (_bucketName, _objectKey, _omVal, _options, _logParam, cb) => cb(null, {}) + ); + putObjectMDStub.onCall(1).callsFake((_bucketName, _objectKey, omVal, _options, _logParam, cb) => { assert.strictEqual(omVal['owner-display-name'], 'Bart'); assert.strictEqual(omVal['owner-id'], '79a59df900b949e55d96a1e698fbacedfd6e09d98eacf8f8d5218e7cd47ef2be'); @@ -291,7 +294,11 @@ describe('routeBackbeat', () => { }); it('should handle error when putting metadata', async () => { - sandbox.stub(metadata, 'putObjectMD').callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); + putObjectMDStub.onCall(0).callsFake( + (_bucketName, _objectKey, _omVal, _options, _logParam, cb) => cb(null, {}) + ); + putObjectMDStub.onCall(1).callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { cb(new Error('error')); }); @@ -321,7 +328,11 @@ describe('routeBackbeat', () => { callback(null, bucketInfo, existingMd); }); - sandbox.stub(metadata, 'putObjectMD').callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); + putObjectMDStub.onCall(0).callsFake( + (_bucketName, _objectKey, _omVal, _options, _logParam, cb) => cb(null, {}) + ); + putObjectMDStub.onCall(1).callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { assert.deepStrictEqual(omVal.location, undefined); cb(null, {}); }); @@ -390,7 +401,11 @@ describe('routeBackbeat', () => { callback(null, bucketInfo, existingMd); }); - sandbox.stub(metadata, 'putObjectMD').callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); + putObjectMDStub.onCall(0).callsFake( + (_bucketName, _objectKey, _omVal, _options, _logParam, cb) => cb(null, {}) + ); + putObjectMDStub.onCall(1).callsFake((_bucketName, _objectKey, omVal, _options, _logParam, cb) => { // Verify that the location array is empty, indicating data deletion assert.deepStrictEqual(omVal.location, []); cb(null, {}); @@ -433,7 +448,11 @@ describe('routeBackbeat', () => { mockRequest = preparePutMetadataRequest(reqBody); mockRequest.url += '?versionId=aIXVkw5Tw2Pd00000000001I4j3QKsvf'; - sandbox.stub(metadata, 'putObjectMD').callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); + putObjectMDStub.onCall(0).callsFake( + (_bucketName, _objectKey, _omVal, _options, _logParam, cb) => cb(null, {}) + ); + putObjectMDStub.onCall(1).callsFake((_bucketName, __objectKey, omVal, _options, _logParam, cb) => { // Verify that the location array contains the new location assert.deepStrictEqual(omVal.location, reqBody.location); cb(null, {}); @@ -481,7 +500,11 @@ describe('routeBackbeat', () => { mockRequest = preparePutMetadataRequest(reqBody); mockRequest.url += '?versionId=aIXVkw5Tw2Pd00000000001I4j3QKsvf'; - sandbox.stub(metadata, 'putObjectMD').callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); + putObjectMDStub.onCall(0).callsFake( + (_bucketName, _objectKey, _omVal, _options, _logParam, cb) => cb(null, {}) + ); + putObjectMDStub.onCall(1).callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { // Verify that the location array contains the new location assert.deepStrictEqual(omVal.location, reqBody.location); cb(null, {}); @@ -516,7 +539,11 @@ describe('routeBackbeat', () => { })); mockRequest.url += '?versionId=aIXVkw5Tw2Pd00000000001I4j3QKsvf'; - sandbox.stub(metadata, 'putObjectMD').callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); + putObjectMDStub.onCall(0).callsFake( + (_bucketName, _objectKey, _omVal, _options, _logParam, cb) => cb(null, {}) + ); + putObjectMDStub.onCall(1).callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { // Verify that the location array is empty assert.deepStrictEqual(omVal.location, undefined); // Content length should be preserved From b4f9b600d9ebe1c22be14887f7116812701ef2b3 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 25 Aug 2025 17:20:30 +0200 Subject: [PATCH 04/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20move=20code=20to=20a?= =?UTF-8?q?sync=20await=20to=20management=20concurrent=20delete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- .../aws-node-sdk/lib/utility/bucket-util.js | 293 +++++++++--------- 1 file changed, 146 insertions(+), 147 deletions(-) diff --git a/tests/functional/aws-node-sdk/lib/utility/bucket-util.js b/tests/functional/aws-node-sdk/lib/utility/bucket-util.js index 7f5b6410e0..12df051e49 100644 --- a/tests/functional/aws-node-sdk/lib/utility/bucket-util.js +++ b/tests/functional/aws-node-sdk/lib/utility/bucket-util.js @@ -5,161 +5,160 @@ const projectFixture = require('../fixtures/project'); const getConfig = require('../../test/support/config'); class BucketUtility { - constructor(profile = 'default', config = {}) { - const s3Config = getConfig(profile, config); - - this.s3 = new S3(s3Config); - this.s3.config.setPromisesDependency(Promise); - this.s3.config.update({ - maxRetries: 0, - }); - } - - bucketExists(bucketName) { - return this.s3 - .headBucket({ Bucket: bucketName }).promise() - .then(() => true) - .catch(err => { - if (err.code === 'NotFound') { - return false; - } - throw err; - }); - } - - createOne(bucketName) { - return this.s3 - .createBucket({ Bucket: bucketName }).promise() - .then(() => bucketName); - } - - createOneWithLock(bucketName) { - return this.s3.createBucket({ - Bucket: bucketName, - ObjectLockEnabledForBucket: true, - }).promise() - .then(() => bucketName); - } - - createMany(bucketNames) { - const promises = bucketNames.map( - bucketName => this.createOne(bucketName) - ); - - return Promise.all(promises); - } - - createRandom(nBuckets = 1) { - if (nBuckets === 1) { - const bucketName = projectFixture.generateBucketName(); - - return this.createOne(bucketName); + constructor(profile = 'default', config = {}) { + const s3Config = getConfig(profile, config); + + this.s3 = new S3(s3Config); + this.s3.config.setPromisesDependency(Promise); + this.s3.config.update({ + maxRetries: 0, + }); + } + + bucketExists(bucketName) { + return this.s3 + .headBucket({ Bucket: bucketName }) + .promise() + .then(() => true) + .catch(err => { + if (err.code === 'NotFound') { + return false; } - - const bucketNames = projectFixture - .generateManyBucketNames(nBuckets) - .sort(() => 0.5 - Math.random()); // Simply shuffle array - - return this.createMany(bucketNames); - } - - deleteOne(bucketName) { - return this.s3 - .deleteBucket({ Bucket: bucketName }).promise(); - } - - deleteMany(bucketNames) { - const promises = bucketNames.map( - bucketName => this.deleteOne(bucketName) - ); - - return Promise.all(promises); + throw err; + }); + } + + createOne(bucketName) { + return this.s3 + .createBucket({ Bucket: bucketName }) + .promise() + .then(() => bucketName); + } + + createOneWithLock(bucketName) { + return this.s3 + .createBucket({ + Bucket: bucketName, + ObjectLockEnabledForBucket: true, + }) + .promise() + .then(() => bucketName); + } + + createMany(bucketNames) { + const promises = bucketNames.map(bucketName => this.createOne(bucketName)); + + return Promise.all(promises); + } + + createRandom(nBuckets = 1) { + if (nBuckets === 1) { + const bucketName = projectFixture.generateBucketName(); + + return this.createOne(bucketName); } - /** - * Recursively delete all versions of all objects within the bucket - * @param bucketName - * @returns {Promise.} - */ - - empty(bucketName, BypassGovernanceRetention = false) { - const param = { - Bucket: bucketName, - }; - - return this.s3 - .listObjectVersions(param).promise() - .then(data => - Promise.all( - data.Versions - .filter(object => !object.Key.endsWith('/')) - // remove all objects - .map(object => - this.s3.deleteObject({ - Bucket: bucketName, - Key: object.Key, - VersionId: object.VersionId, - ...(BypassGovernanceRetention && { BypassGovernanceRetention }), - }).promise() - .then(() => object) - ) - .concat(data.Versions - .filter(object => object.Key.endsWith('/')) - // remove all directories - .map(object => - this.s3.deleteObject({ - Bucket: bucketName, - Key: object.Key, - VersionId: object.VersionId, - ...(BypassGovernanceRetention && { BypassGovernanceRetention }), - }).promise() - .then(() => object) - ) - ) - .concat(data.DeleteMarkers - .map(object => - this.s3.deleteObject({ - Bucket: bucketName, - Key: object.Key, - VersionId: object.VersionId, - ...(BypassGovernanceRetention && { BypassGovernanceRetention }), - }).promise() - .then(() => object))) - ) - ); + const bucketNames = projectFixture + .generateManyBucketNames(nBuckets) + .sort(() => 0.5 - Math.random()); // Simply shuffle array + + return this.createMany(bucketNames); + } + + deleteOne(bucketName) { + return this.s3.deleteBucket({ Bucket: bucketName }).promise(); + } + + deleteMany(bucketNames) { + const promises = bucketNames.map(bucketName => this.deleteOne(bucketName)); + + return Promise.all(promises); + } + + /** + * Recursively delete all versions of all objects within the bucket + * @param bucketName + * @returns {Promise.} + */ + + async empty(bucketName, BypassGovernanceRetention = false) { + const param = { + Bucket: bucketName, + }; + + const listedObjects = await this.s3.listObjectVersions(param).promise(); + + for (const version of listedObjects.Versions) { + if (version.Key.endsWith('/')) { + continue; + } + + await this.s3 + .deleteObject({ + Bucket: bucketName, + Key: version.Key, + VersionId: version.VersionId, + ...(BypassGovernanceRetention && { BypassGovernanceRetention }), + }) + .promise(); } - emptyMany(bucketNames) { - const promises = bucketNames.map( - bucketName => this.empty(bucketName) - ); - - return Promise.all(promises); - } - - emptyIfExists(bucketName) { - return this.bucketExists(bucketName) - .then(exists => { - if (exists) { - return this.empty(bucketName); - } - return undefined; - }); - } - - emptyManyIfExists(bucketNames) { - const promises = bucketNames.map( - bucketName => this.emptyIfExists(bucketName) - ); - - return Promise.all(promises); + for (const version of listedObjects.Versions) { + if (!version.Key.endsWith('/')) { + continue; + } + + await this.s3 + .deleteObject({ + Bucket: bucketName, + Key: version.Key, + VersionId: version.VersionId, + ...(BypassGovernanceRetention && { BypassGovernanceRetention }), + }) + .promise(); } - getOwner() { - return this.s3 - .listBuckets().promise() - .then(data => data.Owner); + for (const marker of listedObjects.DeleteMarkers) { + await this.s3 + .deleteObject({ + Bucket: bucketName, + Key: marker.Key, + VersionId: marker.VersionId, + ...(BypassGovernanceRetention && { BypassGovernanceRetention }), + }) + .promise(); } + } + + emptyMany(bucketNames) { + const promises = bucketNames.map(bucketName => this.empty(bucketName)); + + return Promise.all(promises); + } + + emptyIfExists(bucketName) { + return this.bucketExists(bucketName).then(exists => { + if (exists) { + return this.empty(bucketName); + } + return undefined; + }); + } + + emptyManyIfExists(bucketNames) { + const promises = bucketNames.map(bucketName => + this.emptyIfExists(bucketName), + ); + + return Promise.all(promises); + } + + getOwner() { + return this.s3 + .listBuckets() + .promise() + .then(data => data.Owner); + } } module.exports = BucketUtility; From 40a2ad1214dad84302b8e6afcc91f9e4180259a0 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 25 Aug 2025 17:25:07 +0200 Subject: [PATCH 05/21] =?UTF-8?q?=E2=9C=A8=20skip=20logic=20if=20versionId?= =?UTF-8?q?=20is=20defined=20(update=20a=20specific=20version)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 6 +++++- tests/unit/routes/routeBackbeat.js | 5 +---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index a40ae8b80e..90e9d539e8 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -652,7 +652,11 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { }); }, next => { - versioningPreprocessing(bucketName, bucketInfo, objectKey, objMd, log, next); + if (!versionId) { + return next(); + } + + return versioningPreprocessing(bucketName, bucketInfo, objectKey, objMd, log, next); }, next => { log.trace('putting object version', { diff --git a/tests/unit/routes/routeBackbeat.js b/tests/unit/routes/routeBackbeat.js index 4c762eb56c..6f5a8e9ae9 100644 --- a/tests/unit/routes/routeBackbeat.js +++ b/tests/unit/routes/routeBackbeat.js @@ -295,10 +295,7 @@ describe('routeBackbeat', () => { it('should handle error when putting metadata', async () => { const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); - putObjectMDStub.onCall(0).callsFake( - (_bucketName, _objectKey, _omVal, _options, _logParam, cb) => cb(null, {}) - ); - putObjectMDStub.onCall(1).callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + putObjectMDStub.onCall(0).callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { cb(new Error('error')); }); From c3e9a00a03dd2a6189526ff1fe69c15ccea4c304 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Tue, 26 Aug 2025 15:21:22 +0200 Subject: [PATCH 06/21] =?UTF-8?q?=E2=9C=A8=20retrieve=20the=20master=20obj?= =?UTF-8?q?ect=20to=20copy=20it=20when=20needed=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/api/apiUtils/object/versioning.js | 5 +++++ lib/routes/routeBackbeat.js | 17 ++++++++++++----- .../routes/routeBackbeatForReplication.js | 2 ++ 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/api/apiUtils/object/versioning.js b/lib/api/apiUtils/object/versioning.js index c6e41fafc7..4a262e6b8a 100644 --- a/lib/api/apiUtils/object/versioning.js +++ b/lib/api/apiUtils/object/versioning.js @@ -103,6 +103,7 @@ function _storeNullVersionMD(bucketName, objKey, nullVersionId, objMD, log, cb) log.debug('error from metadata storing null version as new version', { error: err }); } + cb(err); }); } @@ -265,6 +266,7 @@ function processVersioningState(mst, vstat, nullVersionCompatMode) { } return { options, nullVersionId }; } + // backward-compat: keep a reference to the existing null // versioned key if (mst.nullVersionId) { @@ -295,6 +297,7 @@ function getMasterState(objMD) { if (!objMD) { return {}; } + const mst = { exists: true, versionId: objMD.versionId, @@ -304,10 +307,12 @@ function getMasterState(objMD) { nullVersionId: objMD.nullVersionId, nullUploadId: objMD.nullUploadId, }; + if (objMD.location) { mst.objLocation = Array.isArray(objMD.location) ? objMD.location : [objMD.location]; } + return mst; } /** versioningPreprocessing - return versioning information for S3 to handle diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 90e9d539e8..7b738ea093 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -42,6 +42,10 @@ const quotaUtils = require('../api/apiUtils/quotas/quotaUtils'); const { handleAuthorizationResults } = require('../api/api'); const { versioningPreprocessing } = require('../api/apiUtils/object/versioning'); +const {promisify} = require('util'); + +const versioningPreprocessingPromised = promisify(versioningPreprocessing); +metadata.getBucketAndObjectMDPromised = promisify(metadata.getBucketAndObjectMD); const { CURRENT_TYPE, NON_CURRENT_TYPE, ORPHAN_DM_TYPE } = constants.lifecycleListing; @@ -612,6 +616,7 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { // To prevent this, the versionId field is only included in options when it is defined. if (versionId !== undefined) { options.versionId = versionId; + // In the MongoDB metadata backend, setting the versionId option leads to the creation // or update of the version object, the master object is only updated if its versionId // is the same as the version. This can lead to inconsistencies when replicating objects @@ -642,6 +647,7 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { if (!request.query?.accountId) { return next(); } + return getCanonicalIdsByAccountId(request.query.accountId, log, (err, res) => { if (err) { return next(err); @@ -651,12 +657,13 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { return next(); }); }, - next => { - if (!versionId) { - return next(); + async () => { + if (versioning && !objMd) { + const masterObjectAndBucket = + await metadata.getBucketAndObjectMDPromised(bucketName, objectKey, {}, log); + const masterObject = JSON.parse(masterObjectAndBucket.obj); + await versioningPreprocessingPromised(bucketName, bucketInfo, objectKey, masterObject, log); } - - return versioningPreprocessing(bucketName, bucketInfo, objectKey, objMd, log, next); }, next => { log.trace('putting object version', { diff --git a/tests/multipleBackend/routes/routeBackbeatForReplication.js b/tests/multipleBackend/routes/routeBackbeatForReplication.js index d9998ffbc8..4584bb3284 100644 --- a/tests/multipleBackend/routes/routeBackbeatForReplication.js +++ b/tests/multipleBackend/routes/routeBackbeatForReplication.js @@ -958,6 +958,7 @@ describe(`backbeat routes for replication (${name})`, () => { if (err) { return next(err); } + versionId = data.VersionId; return next(); }), @@ -974,6 +975,7 @@ describe(`backbeat routes for replication (${name})`, () => { if (err) { return next(err); } + objMD = objectMDWithUpdatedAccountInfo(data, src === dst ? null : dstAccountInfo); return next(); }), From 214cb41614592ae1532f09a0d397a2ce6a04bc0b Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Tue, 2 Sep 2025 15:14:06 +0200 Subject: [PATCH 07/21] =?UTF-8?q?=E2=AC=86=EF=B8=8F=20bump=20arsenal=20ver?= =?UTF-8?q?sion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- yarn.lock | 150 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 121 insertions(+), 29 deletions(-) diff --git a/yarn.lock b/yarn.lock index 0e88a9d91b..e0c889a1c0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -88,7 +88,7 @@ dependencies: tslib "^2.6.2" -"@azure/core-auth@^1.10.0", "@azure/core-auth@^1.8.0", "@azure/core-auth@^1.9.0": +"@azure/core-auth@^1.10.0", "@azure/core-auth@^1.4.0", "@azure/core-auth@^1.8.0", "@azure/core-auth@^1.9.0": version "1.10.1" resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.10.1.tgz#68a17fa861ebd14f6fd314055798355ef6bedf1b" integrity sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg== @@ -97,7 +97,7 @@ "@azure/core-util" "^1.13.0" tslib "^2.6.2" -"@azure/core-client@^1.10.0", "@azure/core-client@^1.9.2", "@azure/core-client@^1.9.3": +"@azure/core-client@^1.10.0", "@azure/core-client@^1.3.0", "@azure/core-client@^1.6.2", "@azure/core-client@^1.9.2": version "1.10.1" resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.10.1.tgz#83d78f97d647ab22e6811a7a68bb4223e7a1d019" integrity sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w== @@ -110,7 +110,20 @@ "@azure/logger" "^1.3.0" tslib "^2.6.2" -"@azure/core-http-compat@^2.2.0": +"@azure/core-client@^1.9.3": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.10.0.tgz#9f4ec9c89a63516927840ae620c60e811a0b54a3" + integrity sha512-O4aP3CLFNodg8eTHXECaH3B3CjicfzkxVtnrfLkOq0XNP7TIECGfHpK/C6vADZkWP75wzmdBnsIA8ksuJMk18g== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.4.0" + "@azure/core-rest-pipeline" "^1.20.0" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.6.1" + "@azure/logger" "^1.0.0" + tslib "^2.6.2" + +"@azure/core-http-compat@^2.0.0": version "2.3.1" resolved "https://registry.yarnpkg.com/@azure/core-http-compat/-/core-http-compat-2.3.1.tgz#2182e39a31c062800d4e3ad69bcf0109d87713dc" integrity sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g== @@ -119,6 +132,15 @@ "@azure/core-client" "^1.10.0" "@azure/core-rest-pipeline" "^1.22.0" +"@azure/core-http-compat@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@azure/core-http-compat/-/core-http-compat-2.3.0.tgz#e9d396299211e742308827674082c13bd638c6bf" + integrity sha512-qLQujmUypBBG0gxHd0j6/Jdmul6ttl24c8WGiLXIk7IHXdBlfoBqW27hyz3Xn6xbfdyVSarl1Ttbk0AwnZBYCw== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-client" "^1.3.0" + "@azure/core-rest-pipeline" "^1.20.0" + "@azure/core-lro@^2.2.0": version "2.7.2" resolved "https://registry.yarnpkg.com/@azure/core-lro/-/core-lro-2.7.2.tgz#787105027a20e45c77651a98b01a4d3b01b75a08" @@ -129,13 +151,26 @@ "@azure/logger" "^1.0.0" tslib "^2.6.2" -"@azure/core-paging@^1.6.2": +"@azure/core-paging@^1.1.1", "@azure/core-paging@^1.6.2": version "1.6.2" resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.6.2.tgz#40d3860dc2df7f291d66350b2cfd9171526433e7" integrity sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA== dependencies: tslib "^2.6.2" +"@azure/core-rest-pipeline@^1.10.1", "@azure/core-rest-pipeline@^1.22.0": + version "1.22.1" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.1.tgz#f47bc02ff9a79f62e6a32aa375420b1b86dcbccd" + integrity sha512-UVZlVLfLyz6g3Hy7GNDpooMQonUygH7ghdiSASOOHy97fKj/mPLqgDX7aidOijn+sCMU+WU8NjlPlNTgnvbcGA== + dependencies: + "@azure/abort-controller" "^2.1.2" + "@azure/core-auth" "^1.10.0" + "@azure/core-tracing" "^1.3.0" + "@azure/core-util" "^1.13.0" + "@azure/logger" "^1.3.0" + "@typespec/ts-http-runtime" "^0.3.0" + tslib "^2.6.2" + "@azure/core-rest-pipeline@^1.17.0": version "1.19.1" resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.19.1.tgz#e740676444777a04dc55656d8660131dfd926924" @@ -150,16 +185,16 @@ https-proxy-agent "^7.0.0" tslib "^2.6.2" -"@azure/core-rest-pipeline@^1.19.1", "@azure/core-rest-pipeline@^1.22.0": - version "1.22.1" - resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.1.tgz#f47bc02ff9a79f62e6a32aa375420b1b86dcbccd" - integrity sha512-UVZlVLfLyz6g3Hy7GNDpooMQonUygH7ghdiSASOOHy97fKj/mPLqgDX7aidOijn+sCMU+WU8NjlPlNTgnvbcGA== +"@azure/core-rest-pipeline@^1.19.1", "@azure/core-rest-pipeline@^1.20.0": + version "1.22.0" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.0.tgz#76e44a75093a2f477fc54b84f46049dc2ce65800" + integrity sha512-OKHmb3/Kpm06HypvB3g6Q3zJuvyXcpxDpCS1PnU8OV6AJgSFaee/covXBcPbWc6XDDxtEPlbi3EMQ6nUiPaQtw== dependencies: - "@azure/abort-controller" "^2.1.2" - "@azure/core-auth" "^1.10.0" - "@azure/core-tracing" "^1.3.0" - "@azure/core-util" "^1.13.0" - "@azure/logger" "^1.3.0" + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.8.0" + "@azure/core-tracing" "^1.0.1" + "@azure/core-util" "^1.11.0" + "@azure/logger" "^1.0.0" "@typespec/ts-http-runtime" "^0.3.0" tslib "^2.6.2" @@ -170,14 +205,21 @@ dependencies: tslib "^2.6.2" -"@azure/core-tracing@^1.2.0", "@azure/core-tracing@^1.3.0": +"@azure/core-tracing@^1.1.2", "@azure/core-tracing@^1.3.0": version "1.3.1" resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.3.1.tgz#e971045c901ea9c110616b0e1db272507781d5f6" integrity sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ== dependencies: tslib "^2.6.2" -"@azure/core-util@^1.11.0", "@azure/core-util@^1.2.0": +"@azure/core-tracing@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.3.0.tgz#341153f5b2927539eb898577651ee48ce98dda25" + integrity sha512-+XvmZLLWPe67WXNZo9Oc9CrPj/Tm8QnHR92fFAFdnbzwNdCH1h+7UdpaQgRSBsMY+oW1kHXNUZQLdZ1gHX3ROw== + dependencies: + tslib "^2.6.2" + +"@azure/core-util@^1.11.0", "@azure/core-util@^1.2.0", "@azure/core-util@^1.6.1": version "1.11.0" resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.11.0.tgz#f530fc67e738aea872fbdd1cc8416e70219fada7" integrity sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g== @@ -194,7 +236,7 @@ "@typespec/ts-http-runtime" "^0.3.0" tslib "^2.6.2" -"@azure/core-xml@^1.4.5": +"@azure/core-xml@^1.4.3", "@azure/core-xml@^1.4.5": version "1.5.0" resolved "https://registry.yarnpkg.com/@azure/core-xml/-/core-xml-1.5.0.tgz#cd82d511d7bcc548d206f5627c39724c5d5a4434" integrity sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw== @@ -202,10 +244,10 @@ fast-xml-parser "^5.0.7" tslib "^2.8.1" -"@azure/identity@^4.10.2", "@azure/identity@^4.5.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.12.0.tgz#272e39a8742191aac0b9f5ec683984bf4d88e7cb" - integrity sha512-6vuh2R3Cte6SD6azNalLCjIDoryGdcvDVEV7IDRPtm5lHX5ffkDlIalaoOp5YJU08e4ipjJENel20kSMDLAcug== +"@azure/identity@^4.10.2": + version "4.11.1" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.11.1.tgz#19ba5b7601ae4f2ded010c55ca55200ffa6c79ec" + integrity sha512-0ZdsLRaOyLxtCYgyuqyWqGU5XQ9gGnjxgfoNTt1pvELGkkUFrMATABZFIq8gusM7N1qbqpVtwLOhk0d/3kacLg== dependencies: "@azure/abort-controller" "^2.0.0" "@azure/core-auth" "^1.9.0" @@ -236,6 +278,23 @@ open "^10.1.0" tslib "^2.2.0" +"@azure/identity@^4.5.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.12.0.tgz#272e39a8742191aac0b9f5ec683984bf4d88e7cb" + integrity sha512-6vuh2R3Cte6SD6azNalLCjIDoryGdcvDVEV7IDRPtm5lHX5ffkDlIalaoOp5YJU08e4ipjJENel20kSMDLAcug== + dependencies: + "@azure/abort-controller" "^2.0.0" + "@azure/core-auth" "^1.9.0" + "@azure/core-client" "^1.9.2" + "@azure/core-rest-pipeline" "^1.17.0" + "@azure/core-tracing" "^1.0.0" + "@azure/core-util" "^1.11.0" + "@azure/logger" "^1.0.0" + "@azure/msal-browser" "^4.2.0" + "@azure/msal-node" "^3.5.0" + open "^10.1.0" + tslib "^2.2.0" + "@azure/logger@^1.0.0": version "1.1.4" resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.1.4.tgz#223cbf2b424dfa66478ce9a4f575f59c6f379768" @@ -277,7 +336,26 @@ jsonwebtoken "^9.0.0" uuid "^8.3.0" -"@azure/storage-blob@^12.25.0", "@azure/storage-blob@^12.27.0", "@azure/storage-blob@^12.28.0": +"@azure/storage-blob@^12.25.0": + version "12.27.0" + resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.27.0.tgz#3062930411173a28468bd380e0ad2c6328d7288a" + integrity sha512-IQjj9RIzAKatmNca3D6bT0qJ+Pkox1WZGOg2esJF2YLHb45pQKOwGPIAV+w3rfgkj7zV3RMxpn/c6iftzSOZJQ== + dependencies: + "@azure/abort-controller" "^2.1.2" + "@azure/core-auth" "^1.4.0" + "@azure/core-client" "^1.6.2" + "@azure/core-http-compat" "^2.0.0" + "@azure/core-lro" "^2.2.0" + "@azure/core-paging" "^1.1.1" + "@azure/core-rest-pipeline" "^1.10.1" + "@azure/core-tracing" "^1.1.2" + "@azure/core-util" "^1.6.1" + "@azure/core-xml" "^1.4.3" + "@azure/logger" "^1.0.0" + events "^3.0.0" + tslib "^2.2.0" + +"@azure/storage-blob@^12.27.0", "@azure/storage-blob@^12.28.0": version "12.28.0" resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.28.0.tgz#a64ce49f0fe9fe08f1f7c1b36164033678d38cf6" integrity sha512-VhQHITXXO03SURhDiGuHhvc/k/sD2WvJUS7hqhiVNbErVCuQoLtWql7r97fleBlIRKHJaa9R7DpBjfE0pfLYcA== @@ -628,7 +706,7 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.4.2.tgz#1860473de7dfa1546767448f333db80cb0ff2161" integrity sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ== -"@ioredis/commands@1.4.0", "@ioredis/commands@^1.3.0": +"@ioredis/commands@1.4.0": version "1.4.0" resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.4.0.tgz#9f657d51cdd5d2fdb8889592aa4a355546151f25" integrity sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ== @@ -638,6 +716,11 @@ resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== +"@ioredis/commands@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.3.1.tgz#b6ecce79a6c464b5e926e92baaef71f47496f627" + integrity sha512-bYtU8avhGIcje3IhvF9aSjsa5URMZBHnwKtOvXsT4sfYy9gppW11gLPT/9oNqlJZD47yPKveQFTAFWpHjKvUoQ== + "@isaacs/cliui@^8.0.2": version "8.0.2" resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" @@ -718,9 +801,9 @@ sparse-bitfield "^3.0.3" "@mongodb-js/saslprep@^1.3.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.3.0.tgz#75bb770b4b0908047b6c6ac2ec841047660e1c82" - integrity sha512-zlayKCsIjYb7/IdfqxorK5+xUMyi4vOKcFy10wKJYc63NSdKI8mNME+uJqfatkPmOSMMUiojrL58IePKBm3gvQ== + version "1.3.1" + resolved "https://registry.yarnpkg.com/@mongodb-js/saslprep/-/saslprep-1.3.1.tgz#a7d6cfc085f801e51ba5cbec6aa84cad858e1d2d" + integrity sha512-6nZrq5kfAz0POWyhljnbWQQJQ5uT8oE2ddX303q1uY0tWsivWKgBDXBBvuFPwOqRRalXJuVO9EjOdVtuhLX0zg== dependencies: sparse-bitfield "^3.0.3" @@ -1091,9 +1174,9 @@ "@types/webidl-conversions" "*" "@typespec/ts-http-runtime@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.1.tgz#2fa94050f25b4d85d0bc8b9d97874b8d347a9173" - integrity sha512-SnbaqayTVFEA6/tYumdF0UmybY0KHyKwGPBXnyckFlrrKdhWFrL3a2HIPXHjht5ZOElKGcXfD2D63P36btb+ww== + version "0.3.0" + resolved "https://registry.yarnpkg.com/@typespec/ts-http-runtime/-/ts-http-runtime-0.3.0.tgz#f506ff2170e594a257f8e78aa196088f3a46a22d" + integrity sha512-sOx1PKSuFwnIl7z4RN0Ls7N9AQawmR9r66eI5rFCzLDIs8HTIYrIpH9QjYWoX0lkgGrkLxXhi4QnK7MizPRrIg== dependencies: http-proxy-agent "^7.0.0" https-proxy-agent "^7.0.0" @@ -5085,7 +5168,16 @@ mongodb@^6.11.0: bson "^6.10.3" mongodb-connection-string-url "^3.0.0" -mongodb@^6.17.0, mongodb@^6.20.0: +mongodb@^6.17.0: + version "6.19.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.19.0.tgz#d28df0ae4cb3bea4381206e2d9efc3c7b77531fe" + integrity sha512-H3GtYujOJdeKIMLKBT9PwlDhGrQfplABNF1G904w6r5ZXKWyv77aB0X9B+rhmaAwjtllHzaEkvi9mkGVZxs2Bw== + dependencies: + "@mongodb-js/saslprep" "^1.1.9" + bson "^6.10.4" + mongodb-connection-string-url "^3.0.0" + +mongodb@^6.20.0: version "6.20.0" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-6.20.0.tgz#5212dcf512719385287aa4574265352eefb01d8e" integrity sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ== From 277c22c7507940def81ee731fb844567ba27fa7e Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Tue, 2 Sep 2025 16:23:57 +0200 Subject: [PATCH 08/21] =?UTF-8?q?=F0=9F=92=9A=20fix=20unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- tests/unit/routes/routeBackbeat.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/tests/unit/routes/routeBackbeat.js b/tests/unit/routes/routeBackbeat.js index 6f5a8e9ae9..6875a16291 100644 --- a/tests/unit/routes/routeBackbeat.js +++ b/tests/unit/routes/routeBackbeat.js @@ -261,14 +261,18 @@ describe('routeBackbeat', () => { mockRequest.url = '/_/backbeat/metadata/bucket0/key0' + '?accountId=123456789012&versionId=aIXVkw5Tw2Pd00000000001I4j3QKsvf'; - sandbox.stub(metadata, 'putObjectMD').callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + const putObjectMDStub = sandbox.stub(metadata, 'putObjectMD'); + putObjectMDStub.onCall(0).callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + assert.strictEqual(options.repairMaster, undefined); + cb(null, {}); + }); + putObjectMDStub.onCall(1).callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { assert.strictEqual(options.repairMaster, true); cb(null, {}); }); - // Override default callback to return undefined for objMd to simulate new version - metadataUtils.standardMetadataValidateBucketAndObj.callsFake((params, denies, log, callback) => { - callback(null, bucketInfo, undefined); + metadataUtils.standardMetadataValidateBucketAndObj.onCall(1).callsFake((params, denies, log, callback) => { + callback(null, bucketInfo, {}); }); routeBackbeat('127.0.0.1', mockRequest, mockResponse, log); From ceb98aa53b2e78d0e74a583de44a2c313c17aa63 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Tue, 2 Sep 2025 16:38:21 +0200 Subject: [PATCH 09/21] =?UTF-8?q?=E2=9C=85=20make=20sure=20master=20object?= =?UTF-8?q?=20to=20parse=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 7b738ea093..7fc107afde 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -661,6 +661,11 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { if (versioning && !objMd) { const masterObjectAndBucket = await metadata.getBucketAndObjectMDPromised(bucketName, objectKey, {}, log); + + if (!masterObjectAndBucket.obj) { + return; + } + const masterObject = JSON.parse(masterObjectAndBucket.obj); await versioningPreprocessingPromised(bucketName, bucketInfo, objectKey, masterObject, log); } From d6acceff3e66efae0069d1ab67b480d928a98ba2 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Wed, 3 Sep 2025 14:49:05 +0200 Subject: [PATCH 10/21] =?UTF-8?q?=E2=9C=A8=20add=20nullVersionId=20to=20ma?= =?UTF-8?q?ster=20and=20current=20version=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/apiUtils/object/versioning.js | 3 +++ lib/routes/routeBackbeat.js | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/api/apiUtils/object/versioning.js b/lib/api/apiUtils/object/versioning.js index 4a262e6b8a..9b02782689 100644 --- a/lib/api/apiUtils/object/versioning.js +++ b/lib/api/apiUtils/object/versioning.js @@ -253,6 +253,7 @@ function processVersioningState(mst, vstat, nullVersionCompatMode) { } return { options, nullVersionId }; } + if (mst.isNull && !mst.isNull2) { // if master null version was put with an older // Cloudserver (or in compat mode), there is a @@ -348,6 +349,8 @@ function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD, if (!nullVersionId) { return process.nextTick(next); } + + options.nullVersionId = nullVersionId; return _storeNullVersionMD(bucketName, objectKey, nullVersionId, objMD, log, next); }, function prepareNullVersionDeletion(next) { diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 7fc107afde..0d9dddbe21 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -667,7 +667,12 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { } const masterObject = JSON.parse(masterObjectAndBucket.obj); - await versioningPreprocessingPromised(bucketName, bucketInfo, objectKey, masterObject, log); + const versioningPreprocessingResult = + await versioningPreprocessingPromised(bucketName, bucketInfo, objectKey, masterObject, log); + + if (versioningPreprocessingResult?.nullVersionId) { + omVal.nullVersionId = versioningPreprocessingResult.nullVersionId; + } } }, next => { From 618f29de2dc17a4a4fe1671a3b8d45570fe3a3a7 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Wed, 3 Sep 2025 15:49:31 +0200 Subject: [PATCH 11/21] =?UTF-8?q?=F0=9F=90=9B=20fix=20case=202=20by=20addi?= =?UTF-8?q?ng=20data=20to=20the=20new=20object?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/routes/routeBackbeat.js | 5 +++++ tests/multipleBackend/routes/routeBackbeat.js | 1 + tests/multipleBackend/routes/routeBackbeatForReplication.js | 5 ++--- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 0d9dddbe21..746165b76f 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -616,6 +616,11 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { // To prevent this, the versionId field is only included in options when it is defined. if (versionId !== undefined) { options.versionId = versionId; + omVal.versionId = versionId; + + if (isNull) { + omVal.isNull = isNull; + } // In the MongoDB metadata backend, setting the versionId option leads to the creation // or update of the version object, the master object is only updated if its versionId diff --git a/tests/multipleBackend/routes/routeBackbeat.js b/tests/multipleBackend/routes/routeBackbeat.js index d6e8f4af1a..1a6d5f14ec 100644 --- a/tests/multipleBackend/routes/routeBackbeat.js +++ b/tests/multipleBackend/routes/routeBackbeat.js @@ -2466,6 +2466,7 @@ describe('backbeat routes', () => { }), ], done); }); + it('should put tags if the source is Azure and tags are provided ' + 'when completing the multipart upload', done => { const containerName = getAzureContainerName(azureLocation); diff --git a/tests/multipleBackend/routes/routeBackbeatForReplication.js b/tests/multipleBackend/routes/routeBackbeatForReplication.js index 4584bb3284..b70be387f4 100644 --- a/tests/multipleBackend/routes/routeBackbeatForReplication.js +++ b/tests/multipleBackend/routes/routeBackbeatForReplication.js @@ -76,7 +76,6 @@ const scenarios = [ { name: 'same account', src, dst: src }, { name: 'cross account', src, dst }, ]; -const itSkipIfNotS3C = process.env.S3_END_TO_END ? it : it.skip; scenarios.forEach(({ name, src, dst }) => { describe(`backbeat routes for replication (${name})`, () => { @@ -1018,7 +1017,7 @@ describe(`backbeat routes for replication (${name})`, () => { }); }); - itSkipIfNotS3C('should replicate/put metadata to a destination that has a suspended null version', done => { + it('should replicate/put metadata to a destination that has a suspended null version', done => { let objMD; let versionId; @@ -1194,7 +1193,7 @@ describe(`backbeat routes for replication (${name})`, () => { }); }); - itSkipIfNotS3C( + it( 'should replicate/put metadata to a destination that has a suspended null version with internal version', done => { const tagSet = [ From f9e0f16184439965d19836f4e5f8ed02efc3caac Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 8 Sep 2025 15:03:18 +0200 Subject: [PATCH 12/21] =?UTF-8?q?=F0=9F=93=8C=20pin=20arsenal=20to=20speci?= =?UTF-8?q?fic=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- yarn.lock | 118 ++++++++++++++---------------------------------------- 1 file changed, 30 insertions(+), 88 deletions(-) diff --git a/yarn.lock b/yarn.lock index e0c889a1c0..afe0784d2e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -88,7 +88,7 @@ dependencies: tslib "^2.6.2" -"@azure/core-auth@^1.10.0", "@azure/core-auth@^1.4.0", "@azure/core-auth@^1.8.0", "@azure/core-auth@^1.9.0": +"@azure/core-auth@^1.10.0", "@azure/core-auth@^1.8.0", "@azure/core-auth@^1.9.0": version "1.10.1" resolved "https://registry.yarnpkg.com/@azure/core-auth/-/core-auth-1.10.1.tgz#68a17fa861ebd14f6fd314055798355ef6bedf1b" integrity sha512-ykRMW8PjVAn+RS6ww5cmK9U2CyH9p4Q88YJwvUslfuMmN98w/2rdGRLPqJYObapBCdzBVeDgYWdJnFPFb7qzpg== @@ -97,7 +97,7 @@ "@azure/core-util" "^1.13.0" tslib "^2.6.2" -"@azure/core-client@^1.10.0", "@azure/core-client@^1.3.0", "@azure/core-client@^1.6.2", "@azure/core-client@^1.9.2": +"@azure/core-client@^1.3.0", "@azure/core-client@^1.9.2", "@azure/core-client@^1.9.3": version "1.10.1" resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.10.1.tgz#83d78f97d647ab22e6811a7a68bb4223e7a1d019" integrity sha512-Nh5PhEOeY6PrnxNPsEHRr9eimxLwgLlpmguQaHKBinFYA/RU9+kOYVOQqOrTsCL+KSxrLLl1gD8Dk5BFW/7l/w== @@ -110,28 +110,6 @@ "@azure/logger" "^1.3.0" tslib "^2.6.2" -"@azure/core-client@^1.9.3": - version "1.10.0" - resolved "https://registry.yarnpkg.com/@azure/core-client/-/core-client-1.10.0.tgz#9f4ec9c89a63516927840ae620c60e811a0b54a3" - integrity sha512-O4aP3CLFNodg8eTHXECaH3B3CjicfzkxVtnrfLkOq0XNP7TIECGfHpK/C6vADZkWP75wzmdBnsIA8ksuJMk18g== - dependencies: - "@azure/abort-controller" "^2.0.0" - "@azure/core-auth" "^1.4.0" - "@azure/core-rest-pipeline" "^1.20.0" - "@azure/core-tracing" "^1.0.0" - "@azure/core-util" "^1.6.1" - "@azure/logger" "^1.0.0" - tslib "^2.6.2" - -"@azure/core-http-compat@^2.0.0": - version "2.3.1" - resolved "https://registry.yarnpkg.com/@azure/core-http-compat/-/core-http-compat-2.3.1.tgz#2182e39a31c062800d4e3ad69bcf0109d87713dc" - integrity sha512-az9BkXND3/d5VgdRRQVkiJb2gOmDU8Qcq4GvjtBmDICNiQ9udFmDk4ZpSB5Qq1OmtDJGlQAfBaS4palFsazQ5g== - dependencies: - "@azure/abort-controller" "^2.1.2" - "@azure/core-client" "^1.10.0" - "@azure/core-rest-pipeline" "^1.22.0" - "@azure/core-http-compat@^2.2.0": version "2.3.0" resolved "https://registry.yarnpkg.com/@azure/core-http-compat/-/core-http-compat-2.3.0.tgz#e9d396299211e742308827674082c13bd638c6bf" @@ -151,26 +129,13 @@ "@azure/logger" "^1.0.0" tslib "^2.6.2" -"@azure/core-paging@^1.1.1", "@azure/core-paging@^1.6.2": +"@azure/core-paging@^1.6.2": version "1.6.2" resolved "https://registry.yarnpkg.com/@azure/core-paging/-/core-paging-1.6.2.tgz#40d3860dc2df7f291d66350b2cfd9171526433e7" integrity sha512-YKWi9YuCU04B55h25cnOYZHxXYtEvQEbKST5vqRga7hWY9ydd3FZHdeQF8pyh+acWZvppw13M/LMGx0LABUVMA== dependencies: tslib "^2.6.2" -"@azure/core-rest-pipeline@^1.10.1", "@azure/core-rest-pipeline@^1.22.0": - version "1.22.1" - resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.1.tgz#f47bc02ff9a79f62e6a32aa375420b1b86dcbccd" - integrity sha512-UVZlVLfLyz6g3Hy7GNDpooMQonUygH7ghdiSASOOHy97fKj/mPLqgDX7aidOijn+sCMU+WU8NjlPlNTgnvbcGA== - dependencies: - "@azure/abort-controller" "^2.1.2" - "@azure/core-auth" "^1.10.0" - "@azure/core-tracing" "^1.3.0" - "@azure/core-util" "^1.13.0" - "@azure/logger" "^1.3.0" - "@typespec/ts-http-runtime" "^0.3.0" - tslib "^2.6.2" - "@azure/core-rest-pipeline@^1.17.0": version "1.19.1" resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.19.1.tgz#e740676444777a04dc55656d8660131dfd926924" @@ -198,6 +163,19 @@ "@typespec/ts-http-runtime" "^0.3.0" tslib "^2.6.2" +"@azure/core-rest-pipeline@^1.22.0": + version "1.22.1" + resolved "https://registry.yarnpkg.com/@azure/core-rest-pipeline/-/core-rest-pipeline-1.22.1.tgz#f47bc02ff9a79f62e6a32aa375420b1b86dcbccd" + integrity sha512-UVZlVLfLyz6g3Hy7GNDpooMQonUygH7ghdiSASOOHy97fKj/mPLqgDX7aidOijn+sCMU+WU8NjlPlNTgnvbcGA== + dependencies: + "@azure/abort-controller" "^2.1.2" + "@azure/core-auth" "^1.10.0" + "@azure/core-tracing" "^1.3.0" + "@azure/core-util" "^1.13.0" + "@azure/logger" "^1.3.0" + "@typespec/ts-http-runtime" "^0.3.0" + tslib "^2.6.2" + "@azure/core-tracing@^1.0.0", "@azure/core-tracing@^1.0.1": version "1.2.0" resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.2.0.tgz#7be5d53c3522d639cf19042cbcdb19f71bc35ab2" @@ -205,13 +183,6 @@ dependencies: tslib "^2.6.2" -"@azure/core-tracing@^1.1.2", "@azure/core-tracing@^1.3.0": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.3.1.tgz#e971045c901ea9c110616b0e1db272507781d5f6" - integrity sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ== - dependencies: - tslib "^2.6.2" - "@azure/core-tracing@^1.2.0": version "1.3.0" resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.3.0.tgz#341153f5b2927539eb898577651ee48ce98dda25" @@ -219,7 +190,14 @@ dependencies: tslib "^2.6.2" -"@azure/core-util@^1.11.0", "@azure/core-util@^1.2.0", "@azure/core-util@^1.6.1": +"@azure/core-tracing@^1.3.0": + version "1.3.1" + resolved "https://registry.yarnpkg.com/@azure/core-tracing/-/core-tracing-1.3.1.tgz#e971045c901ea9c110616b0e1db272507781d5f6" + integrity sha512-9MWKevR7Hz8kNzzPLfX4EAtGM2b8mr50HPDBvio96bURP/9C+HjdH3sBlLSNNrvRAr5/k/svoH457gB5IKpmwQ== + dependencies: + tslib "^2.6.2" + +"@azure/core-util@^1.11.0", "@azure/core-util@^1.2.0": version "1.11.0" resolved "https://registry.yarnpkg.com/@azure/core-util/-/core-util-1.11.0.tgz#f530fc67e738aea872fbdd1cc8416e70219fada7" integrity sha512-DxOSLua+NdpWoSqULhjDyAZTXFdP/LKkqtYuxxz1SCN289zk3OG8UOpnCQAz/tygyACBtWp/BoO72ptK7msY8g== @@ -236,7 +214,7 @@ "@typespec/ts-http-runtime" "^0.3.0" tslib "^2.6.2" -"@azure/core-xml@^1.4.3", "@azure/core-xml@^1.4.5": +"@azure/core-xml@^1.4.5": version "1.5.0" resolved "https://registry.yarnpkg.com/@azure/core-xml/-/core-xml-1.5.0.tgz#cd82d511d7bcc548d206f5627c39724c5d5a4434" integrity sha512-D/sdlJBMJfx7gqoj66PKVmhDDaU6TKA49ptcolxdas29X7AfvLTmfAGLjAcIMBK7UZ2o4lygHIqVckOlQU3xWw== @@ -244,10 +222,10 @@ fast-xml-parser "^5.0.7" tslib "^2.8.1" -"@azure/identity@^4.10.2": - version "4.11.1" - resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.11.1.tgz#19ba5b7601ae4f2ded010c55ca55200ffa6c79ec" - integrity sha512-0ZdsLRaOyLxtCYgyuqyWqGU5XQ9gGnjxgfoNTt1pvELGkkUFrMATABZFIq8gusM7N1qbqpVtwLOhk0d/3kacLg== +"@azure/identity@^4.10.2", "@azure/identity@^4.5.0": + version "4.12.0" + resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.12.0.tgz#272e39a8742191aac0b9f5ec683984bf4d88e7cb" + integrity sha512-6vuh2R3Cte6SD6azNalLCjIDoryGdcvDVEV7IDRPtm5lHX5ffkDlIalaoOp5YJU08e4ipjJENel20kSMDLAcug== dependencies: "@azure/abort-controller" "^2.0.0" "@azure/core-auth" "^1.9.0" @@ -278,23 +256,6 @@ open "^10.1.0" tslib "^2.2.0" -"@azure/identity@^4.5.0": - version "4.12.0" - resolved "https://registry.yarnpkg.com/@azure/identity/-/identity-4.12.0.tgz#272e39a8742191aac0b9f5ec683984bf4d88e7cb" - integrity sha512-6vuh2R3Cte6SD6azNalLCjIDoryGdcvDVEV7IDRPtm5lHX5ffkDlIalaoOp5YJU08e4ipjJENel20kSMDLAcug== - dependencies: - "@azure/abort-controller" "^2.0.0" - "@azure/core-auth" "^1.9.0" - "@azure/core-client" "^1.9.2" - "@azure/core-rest-pipeline" "^1.17.0" - "@azure/core-tracing" "^1.0.0" - "@azure/core-util" "^1.11.0" - "@azure/logger" "^1.0.0" - "@azure/msal-browser" "^4.2.0" - "@azure/msal-node" "^3.5.0" - open "^10.1.0" - tslib "^2.2.0" - "@azure/logger@^1.0.0": version "1.1.4" resolved "https://registry.yarnpkg.com/@azure/logger/-/logger-1.1.4.tgz#223cbf2b424dfa66478ce9a4f575f59c6f379768" @@ -336,26 +297,7 @@ jsonwebtoken "^9.0.0" uuid "^8.3.0" -"@azure/storage-blob@^12.25.0": - version "12.27.0" - resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.27.0.tgz#3062930411173a28468bd380e0ad2c6328d7288a" - integrity sha512-IQjj9RIzAKatmNca3D6bT0qJ+Pkox1WZGOg2esJF2YLHb45pQKOwGPIAV+w3rfgkj7zV3RMxpn/c6iftzSOZJQ== - dependencies: - "@azure/abort-controller" "^2.1.2" - "@azure/core-auth" "^1.4.0" - "@azure/core-client" "^1.6.2" - "@azure/core-http-compat" "^2.0.0" - "@azure/core-lro" "^2.2.0" - "@azure/core-paging" "^1.1.1" - "@azure/core-rest-pipeline" "^1.10.1" - "@azure/core-tracing" "^1.1.2" - "@azure/core-util" "^1.6.1" - "@azure/core-xml" "^1.4.3" - "@azure/logger" "^1.0.0" - events "^3.0.0" - tslib "^2.2.0" - -"@azure/storage-blob@^12.27.0", "@azure/storage-blob@^12.28.0": +"@azure/storage-blob@^12.25.0", "@azure/storage-blob@^12.27.0", "@azure/storage-blob@^12.28.0": version "12.28.0" resolved "https://registry.yarnpkg.com/@azure/storage-blob/-/storage-blob-12.28.0.tgz#a64ce49f0fe9fe08f1f7c1b36164033678d38cf6" integrity sha512-VhQHITXXO03SURhDiGuHhvc/k/sD2WvJUS7hqhiVNbErVCuQoLtWql7r97fleBlIRKHJaa9R7DpBjfE0pfLYcA== From f2524f9f3d11ffcd6ac552e404aeda029fef0a33 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Tue, 23 Sep 2025 14:10:01 +0200 Subject: [PATCH 13/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20don't=20fetch=20buck?= =?UTF-8?q?et=20data=20all=20the=20time?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 746165b76f..194ab6dcbc 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -45,6 +45,7 @@ const { versioningPreprocessing } const {promisify} = require('util'); const versioningPreprocessingPromised = promisify(versioningPreprocessing); +metadata.getObjectMDPromised = promisify(metadata.getObjectMD); metadata.getBucketAndObjectMDPromised = promisify(metadata.getBucketAndObjectMD); const { CURRENT_TYPE, NON_CURRENT_TYPE, ORPHAN_DM_TYPE } = constants.lifecycleListing; @@ -664,16 +665,14 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { }, async () => { if (versioning && !objMd) { - const masterObjectAndBucket = - await metadata.getBucketAndObjectMDPromised(bucketName, objectKey, {}, log); + const masterMD = await metadata.getObjectMDPromised(bucketName, objectKey, {}, log); - if (!masterObjectAndBucket.obj) { + if (!masterMD) { return; } - const masterObject = JSON.parse(masterObjectAndBucket.obj); const versioningPreprocessingResult = - await versioningPreprocessingPromised(bucketName, bucketInfo, objectKey, masterObject, log); + await versioningPreprocessingPromised(bucketName, bucketInfo, objectKey, masterMD, log); if (versioningPreprocessingResult?.nullVersionId) { omVal.nullVersionId = versioningPreprocessingResult.nullVersionId; From 6d8ef875cda67b25765b0cb457a56567f1fa6542 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 6 Oct 2025 11:07:54 +0200 Subject: [PATCH 14/21] =?UTF-8?q?=F0=9F=9A=A8=20use=204=20spaces=20indenta?= =?UTF-8?q?tion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../aws-node-sdk/lib/utility/bucket-util.js | 302 +++++++++--------- 1 file changed, 156 insertions(+), 146 deletions(-) diff --git a/tests/functional/aws-node-sdk/lib/utility/bucket-util.js b/tests/functional/aws-node-sdk/lib/utility/bucket-util.js index 12df051e49..2a0b5b7a97 100644 --- a/tests/functional/aws-node-sdk/lib/utility/bucket-util.js +++ b/tests/functional/aws-node-sdk/lib/utility/bucket-util.js @@ -5,160 +5,170 @@ const projectFixture = require('../fixtures/project'); const getConfig = require('../../test/support/config'); class BucketUtility { - constructor(profile = 'default', config = {}) { - const s3Config = getConfig(profile, config); - - this.s3 = new S3(s3Config); - this.s3.config.setPromisesDependency(Promise); - this.s3.config.update({ - maxRetries: 0, - }); - } - - bucketExists(bucketName) { - return this.s3 - .headBucket({ Bucket: bucketName }) - .promise() - .then(() => true) - .catch(err => { - if (err.code === 'NotFound') { - return false; + constructor(profile = 'default', config = {}) { + const s3Config = getConfig(profile, config); + + this.s3 = new S3(s3Config); + this.s3.config.setPromisesDependency(Promise); + this.s3.config.update({ + maxRetries: 0, + }); + } + + bucketExists(bucketName) { + return this.s3 + .headBucket({ Bucket: bucketName }) + .promise() + .then(() => true) + .catch(err => { + if (err.code === 'NotFound') { + return false; + } + throw err; + }); + } + + createOne(bucketName) { + return this.s3 + .createBucket({ Bucket: bucketName }) + .promise() + .then(() => bucketName); + } + + createOneWithLock(bucketName) { + return this.s3 + .createBucket({ + Bucket: bucketName, + ObjectLockEnabledForBucket: true, + }) + .promise() + .then(() => bucketName); + } + + createMany(bucketNames) { + const promises = bucketNames.map(bucketName => + this.createOne(bucketName), + ); + + return Promise.all(promises); + } + + createRandom(nBuckets = 1) { + if (nBuckets === 1) { + const bucketName = projectFixture.generateBucketName(); + + return this.createOne(bucketName); + } + + const bucketNames = projectFixture + .generateManyBucketNames(nBuckets) + .sort(() => 0.5 - Math.random()); // Simply shuffle array + + return this.createMany(bucketNames); + } + + deleteOne(bucketName) { + return this.s3.deleteBucket({ Bucket: bucketName }).promise(); + } + + deleteMany(bucketNames) { + const promises = bucketNames.map(bucketName => + this.deleteOne(bucketName), + ); + + return Promise.all(promises); + } + + /** + * Recursively delete all versions of all objects within the bucket + * @param bucketName + * @returns {Promise.} + */ + + async empty(bucketName, BypassGovernanceRetention = false) { + const param = { + Bucket: bucketName, + }; + + const listedObjects = await this.s3.listObjectVersions(param).promise(); + + for (const version of listedObjects.Versions) { + if (version.Key.endsWith('/')) { + continue; + } + + await this.s3 + .deleteObject({ + Bucket: bucketName, + Key: version.Key, + VersionId: version.VersionId, + ...(BypassGovernanceRetention && { + BypassGovernanceRetention, + }), + }) + .promise(); + } + + for (const version of listedObjects.Versions) { + if (!version.Key.endsWith('/')) { + continue; + } + + await this.s3 + .deleteObject({ + Bucket: bucketName, + Key: version.Key, + VersionId: version.VersionId, + ...(BypassGovernanceRetention && { + BypassGovernanceRetention, + }), + }) + .promise(); + } + + for (const marker of listedObjects.DeleteMarkers) { + await this.s3 + .deleteObject({ + Bucket: bucketName, + Key: marker.Key, + VersionId: marker.VersionId, + ...(BypassGovernanceRetention && { + BypassGovernanceRetention, + }), + }) + .promise(); } - throw err; - }); - } - - createOne(bucketName) { - return this.s3 - .createBucket({ Bucket: bucketName }) - .promise() - .then(() => bucketName); - } - - createOneWithLock(bucketName) { - return this.s3 - .createBucket({ - Bucket: bucketName, - ObjectLockEnabledForBucket: true, - }) - .promise() - .then(() => bucketName); - } - - createMany(bucketNames) { - const promises = bucketNames.map(bucketName => this.createOne(bucketName)); - - return Promise.all(promises); - } - - createRandom(nBuckets = 1) { - if (nBuckets === 1) { - const bucketName = projectFixture.generateBucketName(); - - return this.createOne(bucketName); } - const bucketNames = projectFixture - .generateManyBucketNames(nBuckets) - .sort(() => 0.5 - Math.random()); // Simply shuffle array - - return this.createMany(bucketNames); - } - - deleteOne(bucketName) { - return this.s3.deleteBucket({ Bucket: bucketName }).promise(); - } - - deleteMany(bucketNames) { - const promises = bucketNames.map(bucketName => this.deleteOne(bucketName)); - - return Promise.all(promises); - } - - /** - * Recursively delete all versions of all objects within the bucket - * @param bucketName - * @returns {Promise.} - */ - - async empty(bucketName, BypassGovernanceRetention = false) { - const param = { - Bucket: bucketName, - }; - - const listedObjects = await this.s3.listObjectVersions(param).promise(); - - for (const version of listedObjects.Versions) { - if (version.Key.endsWith('/')) { - continue; - } - - await this.s3 - .deleteObject({ - Bucket: bucketName, - Key: version.Key, - VersionId: version.VersionId, - ...(BypassGovernanceRetention && { BypassGovernanceRetention }), - }) - .promise(); + emptyMany(bucketNames) { + const promises = bucketNames.map(bucketName => this.empty(bucketName)); + + return Promise.all(promises); } - for (const version of listedObjects.Versions) { - if (!version.Key.endsWith('/')) { - continue; - } - - await this.s3 - .deleteObject({ - Bucket: bucketName, - Key: version.Key, - VersionId: version.VersionId, - ...(BypassGovernanceRetention && { BypassGovernanceRetention }), - }) - .promise(); + emptyIfExists(bucketName) { + return this.bucketExists(bucketName).then(exists => { + if (exists) { + return this.empty(bucketName); + } + return undefined; + }); + } + + emptyManyIfExists(bucketNames) { + const promises = bucketNames.map(bucketName => + this.emptyIfExists(bucketName), + ); + + return Promise.all(promises); } - for (const marker of listedObjects.DeleteMarkers) { - await this.s3 - .deleteObject({ - Bucket: bucketName, - Key: marker.Key, - VersionId: marker.VersionId, - ...(BypassGovernanceRetention && { BypassGovernanceRetention }), - }) - .promise(); + getOwner() { + return this.s3 + .listBuckets() + .promise() + .then(data => data.Owner); } - } - - emptyMany(bucketNames) { - const promises = bucketNames.map(bucketName => this.empty(bucketName)); - - return Promise.all(promises); - } - - emptyIfExists(bucketName) { - return this.bucketExists(bucketName).then(exists => { - if (exists) { - return this.empty(bucketName); - } - return undefined; - }); - } - - emptyManyIfExists(bucketNames) { - const promises = bucketNames.map(bucketName => - this.emptyIfExists(bucketName), - ); - - return Promise.all(promises); - } - - getOwner() { - return this.s3 - .listBuckets() - .promise() - .then(data => data.Owner); - } } module.exports = BucketUtility; From 304215fc7de50a02e1fdbcf0ef6327fde774b5b5 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Mon, 6 Oct 2025 11:42:57 +0200 Subject: [PATCH 15/21] =?UTF-8?q?=F0=9F=92=9A=20try=20catch=20getObject=20?= =?UTF-8?q?as=20the=20function=20return=20an=20error=20if=20the=20object?= =?UTF-8?q?=20does=20not=20exists?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 194ab6dcbc..7d654ecf5d 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -665,7 +665,21 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { }, async () => { if (versioning && !objMd) { - const masterMD = await metadata.getObjectMDPromised(bucketName, objectKey, {}, log); + let masterMD; + + try { + masterMD = await metadata.getObjectMDPromised(bucketName, objectKey, {}, log); + } catch (err) { + if (err.is?.NoSuchKey) { + log.debug('no master found for versioned object', { + method: 'putMetadata', + bucketName, + objectKey, + }); + } else { + throw err; + } + } if (!masterMD) { return; From fb1e2b7ad6d491dbcf832aa9f94bb3bfe6d71f5b Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Wed, 8 Oct 2025 17:38:02 +0200 Subject: [PATCH 16/21] =?UTF-8?q?=E2=9C=A8=20manage=20isNull2=20fields=20o?= =?UTF-8?q?f=20versioningPreprocessing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 7d654ecf5d..654d45b3c9 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -620,6 +620,10 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { omVal.versionId = versionId; if (isNull) { + if (!nullVersionCompatMode) { + omVal.isNull2 = true; + } + omVal.isNull = isNull; } From 8e47a3c98f3981b122e1d55402c70ea7a205500c Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Thu, 9 Oct 2025 12:29:40 +0200 Subject: [PATCH 17/21] =?UTF-8?q?=E2=9C=A8=20manage=20more=20fields=20from?= =?UTF-8?q?=20versioningPreprocessing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 654d45b3c9..17fcd02ef0 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -694,6 +694,11 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { if (versioningPreprocessingResult?.nullVersionId) { omVal.nullVersionId = versioningPreprocessingResult.nullVersionId; + options.deleteNullKey = versioningPreprocessingResult.deleteNullKey; + + if (versioningPreprocessingResult.extraMD) { + Object.assign(omVal, options.extraMD); + } } } }, From 504cde34a13ffef32d2a6b00a58979894b72dcaf Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Fri, 10 Oct 2025 13:53:00 +0200 Subject: [PATCH 18/21] =?UTF-8?q?=E2=9C=85=20add=20a=20new=20test=20for=20?= =?UTF-8?q?the=20versioningPreprocess?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 4 ++ tests/unit/routes/routeBackbeat.js | 62 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 17fcd02ef0..0f0ea2985f 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -668,6 +668,10 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { }); }, async () => { + // If we create a new version of an object (so objMd is null), + // we should make sure that the masterVersion is versionned. + // If an object already exists, we just want to update the metadata + // of the existing object and not create a new one if (versioning && !objMd) { let masterMD; diff --git a/tests/unit/routes/routeBackbeat.js b/tests/unit/routes/routeBackbeat.js index 6875a16291..3d465974c2 100644 --- a/tests/unit/routes/routeBackbeat.js +++ b/tests/unit/routes/routeBackbeat.js @@ -14,6 +14,7 @@ const { auth, errors } = require('arsenal'); const AuthInfo = auth.AuthInfo; const { config } = require('../../../lib/Config'); const quotaUtils = require('../../../lib/api/apiUtils/quotas/quotaUtils'); +const versioningUtils = require('../../../lib/api/apiUtils/object/versioning'); const { bucketPut } = require('../../../lib/api/bucketPut'); const bucketDelete = require('../../../lib/api/bucketDelete'); const bucketPutVersioning = require('../../../lib/api/bucketPutVersioning'); @@ -559,6 +560,67 @@ describe('routeBackbeat', () => { assert.deepStrictEqual(mockResponse.body, {}); assert.strictEqual(dataDeleteSpy.called, false); }); + + it('should transform a non-versioned object to a versioned' + + ' one and create a new object if the bucket is versioned', async () => { + const bucketInfo = { + getVersioningConfiguration: () => ({ Status: 'Enabled' }), + isVersioningEnabled: () => true, + }; + const notFoundObject = undefined; + const nonVersionedObject = { + 'content-length': 100, + }; + + const metadataGetObjectMDPromisedStub = sandbox.stub(metadata, 'getObjectMDPromised'); + metadataGetObjectMDPromisedStub.callsFake((bucketName, objectKey, options, log) => Promise.resolve({ + data: nonVersionedObject, + })); + const metadataPutObjectMDStub = sandbox.stub(metadata, 'putObjectMD') + .callsFake((bucketName, objectKey, omVal, options, logParam, cb) => { + cb(null, {}); + }); + + mockRequest = prepareDummyRequest(); + mockRequest.method = 'PUT'; + mockRequest.url = '/_/backbeat/metadata/bucket0/key0'; + + metadataUtils.standardMetadataValidateBucketAndObj.callsFake((params, denies, log, callback) => { + callback(null, bucketInfo, notFoundObject); + }); + + routeBackbeat('127.0.0.1', mockRequest, mockResponse, log); + await endPromise; + + sinon.assert.calledOnce(metadataGetObjectMDPromisedStub); + sinon.assert.calledTwice(metadataPutObjectMDStub); + sinon.assert.calledWith( + metadataPutObjectMDStub.firstCall,// Transform the non versioned object to a versioned one + 'bucket0', + 'key0', + sinon.match({ + data: nonVersionedObject, + versionId: '99999999999999999999RG001 ', + isNull: true, + isNull2: true + }), + sinon.match({ versionId: 'null' }), + log, + ); + sinon.assert.calledWith( + metadataPutObjectMDStub.secondCall, // Create the new object + 'bucket0', + 'key0', + sinon.match({ + nullVersionId: '99999999999999999999RG001 ', + }), + sinon.match({ versioning: true, isNull: false }), + log, + ); + + assert.strictEqual(mockResponse.statusCode, 200); + assert.deepStrictEqual(mockResponse.body, {}); + }); }); describe('batchDelete', () => { From affeae3d14b6f9afc6311c7130254814195080d4 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Fri, 10 Oct 2025 13:53:39 +0200 Subject: [PATCH 19/21] =?UTF-8?q?=F0=9F=A7=AA=20update=20the=20test=20to?= =?UTF-8?q?=20don't=20fail=20if=20the=20seconds=20/=20minutes=20/=20...=20?= =?UTF-8?q?is=20different?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/api/objectPut.js | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/tests/unit/api/objectPut.js b/tests/unit/api/objectPut.js index 946c6a930c..ed10531c5d 100644 --- a/tests/unit/api/objectPut.js +++ b/tests/unit/api/objectPut.js @@ -247,8 +247,6 @@ describe('objectPut API', () => { }); }); - const formatTime = time => time.slice(0, 20); - const testObjectLockConfigs = [ { testMode: 'COMPLIANCE', @@ -261,8 +259,8 @@ describe('objectPut API', () => { type: 'Years', }, ]; - testObjectLockConfigs.forEach(config => { - const { testMode, type, val } = config; + testObjectLockConfigs.forEach(lockConfig => { + const { testMode, type, val } = lockConfig; it('should put an object with default retention if object does not ' + 'have retention configuration but bucket has', done => { const testPutObjectRequest = new DummyRequest({ @@ -288,17 +286,18 @@ describe('objectPut API', () => { assert.strictEqual(headers.ETag, `"${correctMD5}"`); metadata.getObjectMD(bucketName, objectName, {}, log, (err, md) => { - const mode = md.retentionMode; - const retainDate = md.retentionDate; - const date = moment(); - const days - = type === 'Days' ? val : val * 365; - const expectedDate - = date.add(days, 'days'); assert.ifError(err); + + const mode = md.retentionMode; assert.strictEqual(mode, testMode); - assert.strictEqual(formatTime(retainDate), - formatTime(expectedDate.toISOString())); + + const retainDate = moment(md.retentionDate); + const days = type === 'Days' ? val : val * 365; + const { scaledMsPerDay } = config.getTimeOptions(); + const date = moment().add(days * scaledMsPerDay, 'ms'); + const dateDiff = retainDate.diff(date, 'ms'); + assert.ok(dateDiff < 10); + done(); }); }); @@ -307,7 +306,6 @@ describe('objectPut API', () => { }); }); - it('should successfully put an object with legal hold ON', done => { const request = new DummyRequest({ bucketName, @@ -413,8 +411,7 @@ describe('objectPut API', () => { }, postBody); bucketPut(authInfo, testPutBucketRequest, log, () => { - const config = require('../../../lib/Config'); - config.config.testingMode = true; + config.testingMode = true; objectPut(authInfo, testPutObjectRequest, undefined, log, (err, resHeaders) => { assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); @@ -430,7 +427,7 @@ describe('objectPut API', () => { // The header should be removed after being treated. assert(md[lastModifiedHeader] === undefined); - config.config.testingMode = false; + config.testingMode = false; done(); }); }); @@ -452,8 +449,7 @@ describe('objectPut API', () => { }, postBody); bucketPut(authInfo, testPutBucketRequest, log, () => { - const config = require('../../../lib/Config'); - config.config.testingMode = false; + config.testingMode = false; objectPut(authInfo, testPutObjectRequest, undefined, log, (err, resHeaders) => { assert.strictEqual(resHeaders.ETag, `"${correctMD5}"`); From e3843527955334429fb83fa905ff8e60be5037ff Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Fri, 10 Oct 2025 14:10:10 +0200 Subject: [PATCH 20/21] =?UTF-8?q?=F0=9F=9A=A8=20remove=20unused=20paramete?= =?UTF-8?q?r=20and=20unused=20import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/unit/routes/routeBackbeat.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/routes/routeBackbeat.js b/tests/unit/routes/routeBackbeat.js index 3d465974c2..98360d1a1b 100644 --- a/tests/unit/routes/routeBackbeat.js +++ b/tests/unit/routes/routeBackbeat.js @@ -14,7 +14,6 @@ const { auth, errors } = require('arsenal'); const AuthInfo = auth.AuthInfo; const { config } = require('../../../lib/Config'); const quotaUtils = require('../../../lib/api/apiUtils/quotas/quotaUtils'); -const versioningUtils = require('../../../lib/api/apiUtils/object/versioning'); const { bucketPut } = require('../../../lib/api/bucketPut'); const bucketDelete = require('../../../lib/api/bucketDelete'); const bucketPutVersioning = require('../../../lib/api/bucketPutVersioning'); @@ -573,7 +572,7 @@ describe('routeBackbeat', () => { }; const metadataGetObjectMDPromisedStub = sandbox.stub(metadata, 'getObjectMDPromised'); - metadataGetObjectMDPromisedStub.callsFake((bucketName, objectKey, options, log) => Promise.resolve({ + metadataGetObjectMDPromisedStub.callsFake(() => Promise.resolve({ data: nonVersionedObject, })); const metadataPutObjectMDStub = sandbox.stub(metadata, 'putObjectMD') From 3d7d86abe87c83a7e682873dcc3602329204f347 Mon Sep 17 00:00:00 2001 From: DarkIsDude Date: Fri, 17 Oct 2025 11:08:29 +0200 Subject: [PATCH 21/21] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20fix=20some=20typo=20?= =?UTF-8?q?on=20comment=20and=20indentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Issue: CLDSRV-632 --- lib/routes/routeBackbeat.js | 4 ++-- tests/unit/routes/routeBackbeat.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/routes/routeBackbeat.js b/lib/routes/routeBackbeat.js index 0f0ea2985f..383da9bac9 100644 --- a/lib/routes/routeBackbeat.js +++ b/lib/routes/routeBackbeat.js @@ -621,7 +621,7 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { if (isNull) { if (!nullVersionCompatMode) { - omVal.isNull2 = true; + omVal.isNull2 = true; } omVal.isNull = isNull; @@ -669,7 +669,7 @@ function putMetadata(request, response, bucketInfo, objMd, log, callback) { }, async () => { // If we create a new version of an object (so objMd is null), - // we should make sure that the masterVersion is versionned. + // we should make sure that the masterVersion is versioned. // If an object already exists, we just want to update the metadata // of the existing object and not create a new one if (versioning && !objMd) { diff --git a/tests/unit/routes/routeBackbeat.js b/tests/unit/routes/routeBackbeat.js index 98360d1a1b..3805f742b2 100644 --- a/tests/unit/routes/routeBackbeat.js +++ b/tests/unit/routes/routeBackbeat.js @@ -594,7 +594,7 @@ describe('routeBackbeat', () => { sinon.assert.calledOnce(metadataGetObjectMDPromisedStub); sinon.assert.calledTwice(metadataPutObjectMDStub); sinon.assert.calledWith( - metadataPutObjectMDStub.firstCall,// Transform the non versioned object to a versioned one + metadataPutObjectMDStub.firstCall, // Transform the non versioned object to a versioned one 'bucket0', 'key0', sinon.match({