diff --git a/constants.js b/constants.js index 8aab74e040..535a2fb16e 100644 --- a/constants.js +++ b/constants.js @@ -153,7 +153,7 @@ const constants = { // for external backends, don't call unless at least 1 minute // (60,000 milliseconds) since last call externalBackendHealthCheckInterval: 60000, - versioningNotImplBackends: { azure: true, gcp: true }, + versioningNotImplBackends: { azure: true }, mpuMDStoredExternallyBackend: { aws_s3: true, gcp: true }, skipBatchDeleteBackends: { azure: true, gcp: true }, s3HandledBackends: { azure: true, gcp: true }, diff --git a/lib/api/apiUtils/object/createAndStoreObject.js b/lib/api/apiUtils/object/createAndStoreObject.js index 82f26ccfc5..eb4f071a4d 100644 --- a/lib/api/apiUtils/object/createAndStoreObject.js +++ b/lib/api/apiUtils/object/createAndStoreObject.js @@ -25,7 +25,7 @@ const { const { externalBackends, versioningNotImplBackends } = constants; const externalVersioningErrorMessage = - 'We do not currently support putting a versioned object to a location-constraint of type Azure or GCP.'; + 'We do not currently support putting a versioned object to a location-constraint of type Azure.'; /** * Validate and compute the checksum for a zero-size object body. @@ -322,7 +322,10 @@ function createAndStoreObject( } } - const headerChecksum = areChecksumsEnabled() ? getChecksumDataFromHeaders(request.headers) : null; + // A delete marker has no body: an x-amz-checksum-* header + // describes the request payload (e.g. a multi-object delete XML). + const headerChecksum = + isDeleteMarker || !areChecksumsEnabled() ? null : getChecksumDataFromHeaders(request.headers); if (headerChecksum && headerChecksum.error) { return next(arsenalErrorFromChecksumError(headerChecksum)); } diff --git a/lib/api/bucketPutVersioning.js b/lib/api/bucketPutVersioning.js index 5f872cc0dd..695c469857 100644 --- a/lib/api/bucketPutVersioning.js +++ b/lib/api/bucketPutVersioning.js @@ -6,23 +6,23 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders'); const metadata = require('../metadata/wrapper'); const { standardMetadataValidateBucket } = require('../metadata/metadataUtils'); const { pushMetric } = require('../utapi/utilities'); -const versioningNotImplBackends = - require('../../constants').versioningNotImplBackends; +const versioningNotImplBackends = require('../../constants').versioningNotImplBackends; const { config } = require('../Config'); const monitoring = require('../utilities/monitoringHandler'); -const externalVersioningErrorMessage = 'We do not currently support putting ' + -'a versioned object to a location-constraint of type Azure or GCP.'; +const externalVersioningErrorMessage = + 'We do not currently support putting ' + 'a versioned object to a location-constraint of type Azure.'; -const replicationVersioningErrorMessage = 'A replication configuration is ' + -'present on this bucket, so you cannot change the versioning state. To ' + -'change the versioning state, first delete the replication configuration.'; +const replicationVersioningErrorMessage = + 'A replication configuration is ' + + 'present on this bucket, so you cannot change the versioning state. To ' + + 'change the versioning state, first delete the replication configuration.'; -const ingestionVersioningErrorMessage = 'Versioning cannot be suspended for ' -+ 'buckets setup with Out of Band updates from a location'; +const ingestionVersioningErrorMessage = + 'Versioning cannot be suspended for ' + 'buckets setup with Out of Band updates from a location'; -const objectLockErrorMessage = 'An Object Lock configuration is present on ' + - 'this bucket, so the versioning state cannot be changed.'; +const objectLockErrorMessage = + 'An Object Lock configuration is present on ' + 'this bucket, so the versioning state cannot be changed.'; /** * Format of xml request: @@ -47,21 +47,17 @@ function _parseXML(request, log, cb) { return cb(errors.MalformedXML); } const versioningConf = result.VersioningConfiguration; - const status = versioningConf.Status ? - versioningConf.Status[0] : undefined; - const mfaDelete = versioningConf.MfaDelete ? - versioningConf.MfaDelete[0] : undefined; + const status = versioningConf.Status ? versioningConf.Status[0] : undefined; + const mfaDelete = versioningConf.MfaDelete ? versioningConf.MfaDelete[0] : undefined; const validStatuses = ['Enabled', 'Suspended']; const validMfaDeletes = [undefined, 'Enabled', 'Disabled']; - if (validStatuses.indexOf(status) < 0 || - validMfaDeletes.indexOf(mfaDelete) < 0) { + if (validStatuses.indexOf(status) < 0 || validMfaDeletes.indexOf(mfaDelete) < 0) { log.debug('illegal versioning configuration'); return cb(errors.IllegalVersioningConfigurationException); } if (versioningConf && mfaDelete === 'Enabled') { log.debug('mfa deletion is not implemented'); - return cb(errorInstances.NotImplemented - .customizeDescription('MFA Deletion is not supported yet.')); + return cb(errorInstances.NotImplemented.customizeDescription('MFA Deletion is not supported yet.')); } return process.nextTick(() => cb(null)); }); @@ -103,90 +99,89 @@ function bucketPutVersioning(authInfo, request, log, callback) { requestType: request.apiMethods || 'bucketPutVersioning', request, }; - return waterfall([ - next => _parseXML(request, log, next), - next => standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, - (err, bucket) => next(err, bucket)), // ignore extra null object, - (bucket, next) => parseString(request.post, (err, result) => { - // just for linting; there should not be any parsing error here - if (err) { - return next(err, bucket); - } - // prevent enabling versioning on an nfs exported bucket - if (bucket.isNFS()) { - const error = new Error(); - error.code = 'NFSBUCKET'; - return next(error); - } - // _checkBackendVersioningImplemented returns false if versioning - // is not implemented on the bucket backend - if (!_checkBackendVersioningImplemented(bucket)) { - log.debug(externalVersioningErrorMessage, - { method: 'bucketPutVersioning', - error: errors.NotImplemented }); - const error = errorInstances.NotImplemented.customizeDescription( - externalVersioningErrorMessage); - return next(error, bucket); - } - const versioningConfiguration = {}; - if (result.VersioningConfiguration.Status) { - versioningConfiguration.Status = - result.VersioningConfiguration.Status[0]; - } - if (result.VersioningConfiguration.MfaDelete) { - versioningConfiguration.MfaDelete = - result.VersioningConfiguration.MfaDelete[0]; - } - // the configuration has been checked before - return next(null, bucket, versioningConfiguration); - }), - (bucket, versioningConfiguration, next) => { - // check if replication is enabled if versioning is being suspended - const replicationConfig = bucket.getReplicationConfiguration(); - const isIngestionBucket = bucket.isIngestionBucket && bucket.isIngestionBucket(); - const invalidAction = - versioningConfiguration.Status === 'Suspended' - && (isIngestionBucket || replicationConfig?.rules?.some(r => r.enabled)); - if (invalidAction) { - const errorMsg = isIngestionBucket ? - ingestionVersioningErrorMessage : replicationVersioningErrorMessage; - next(errorInstances.InvalidBucketState - .customizeDescription(errorMsg)); - return; + return waterfall( + [ + next => _parseXML(request, log, next), + next => + standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, (err, bucket) => + next(err, bucket), + ), // ignore extra null object, + (bucket, next) => + parseString(request.post, (err, result) => { + // just for linting; there should not be any parsing error here + if (err) { + return next(err, bucket); + } + // prevent enabling versioning on an nfs exported bucket + if (bucket.isNFS()) { + const error = new Error(); + error.code = 'NFSBUCKET'; + return next(error); + } + // _checkBackendVersioningImplemented returns false if versioning + // is not implemented on the bucket backend + if (!_checkBackendVersioningImplemented(bucket)) { + log.debug(externalVersioningErrorMessage, { + method: 'bucketPutVersioning', + error: errors.NotImplemented, + }); + const error = + errorInstances.NotImplemented.customizeDescription(externalVersioningErrorMessage); + return next(error, bucket); + } + const versioningConfiguration = {}; + if (result.VersioningConfiguration.Status) { + versioningConfiguration.Status = result.VersioningConfiguration.Status[0]; + } + if (result.VersioningConfiguration.MfaDelete) { + versioningConfiguration.MfaDelete = result.VersioningConfiguration.MfaDelete[0]; + } + // the configuration has been checked before + return next(null, bucket, versioningConfiguration); + }), + (bucket, versioningConfiguration, next) => { + // check if replication is enabled if versioning is being suspended + const replicationConfig = bucket.getReplicationConfiguration(); + const isIngestionBucket = bucket.isIngestionBucket && bucket.isIngestionBucket(); + const invalidAction = + versioningConfiguration.Status === 'Suspended' && + (isIngestionBucket || replicationConfig?.rules?.some(r => r.enabled)); + if (invalidAction) { + const errorMsg = isIngestionBucket + ? ingestionVersioningErrorMessage + : replicationVersioningErrorMessage; + next(errorInstances.InvalidBucketState.customizeDescription(errorMsg)); + return; + } + const objectLockEnabled = bucket.isObjectLockEnabled(); + if (objectLockEnabled) { + next(errorInstances.InvalidBucketState.customizeDescription(objectLockErrorMessage)); + return; + } + bucket.setVersioningConfiguration(versioningConfiguration); + // TODO all metadata updates of bucket should be using CAS + metadata.updateBucket(bucket.getName(), bucket, log, err => next(err, bucket)); + }, + ], + (err, bucket) => { + const corsHeaders = collectCorsHeaders(request.headers.origin, request.method, bucket); + if (err && err.code === 'NFSBUCKET') { + log.trace('skipping versioning for nfs exported bucket'); + return callback(null, corsHeaders); } - const objectLockEnabled = bucket.isObjectLockEnabled(); - if (objectLockEnabled) { - next(errorInstances.InvalidBucketState - .customizeDescription(objectLockErrorMessage)); - return; + if (err) { + log.trace('error processing request', { error: err, method: 'bucketPutVersioning' }); + monitoring.promMetrics('PUT', bucketName, err.code, 'putBucketVersioning'); + } else { + pushMetric('putBucketVersioning', log, { + authInfo, + bucket: bucketName, + }); + monitoring.promMetrics('PUT', bucketName, '200', 'putBucketVersioning'); } - bucket.setVersioningConfiguration(versioningConfiguration); - // TODO all metadata updates of bucket should be using CAS - metadata.updateBucket(bucket.getName(), bucket, log, err => - next(err, bucket)); + return callback(err, corsHeaders); }, - ], (err, bucket) => { - const corsHeaders = collectCorsHeaders(request.headers.origin, - request.method, bucket); - if (err && err.code === 'NFSBUCKET') { - log.trace('skipping versioning for nfs exported bucket'); - return callback(null, corsHeaders); - } - if (err) { - log.trace('error processing request', { error: err, - method: 'bucketPutVersioning' }); - monitoring.promMetrics( - 'PUT', bucketName, err.code, 'putBucketVersioning'); - } else { - pushMetric('putBucketVersioning', log, { - authInfo, - bucket: bucketName, - }); - monitoring.promMetrics( - 'PUT', bucketName, '200', 'putBucketVersioning'); - } - return callback(err, corsHeaders); - }); + ); } module.exports = bucketPutVersioning; diff --git a/lib/api/multiObjectDelete.js b/lib/api/multiObjectDelete.js index 08d65c5cfa..07a55efebe 100644 --- a/lib/api/multiObjectDelete.js +++ b/lib/api/multiObjectDelete.js @@ -428,7 +428,7 @@ function getObjMetadataAndDelete( overheadField, log, 's3:ObjectRemoved:DeleteMarkerCreated', - (err, result) => callback(err, objMD, deleteInfo, result.versionId), + (err, result) => callback(err, objMD, deleteInfo, result?.versionId), ); }, ], diff --git a/lib/api/objectCopy.js b/lib/api/objectCopy.js index 153aa21f03..9b9c2cddd1 100644 --- a/lib/api/objectCopy.js +++ b/lib/api/objectCopy.js @@ -40,7 +40,7 @@ const versionIdUtils = versioning.VersionID; const locationHeader = constants.objectLocationConstraintHeader; const versioningNotImplBackends = constants.versioningNotImplBackends; const externalVersioningErrorMessage = - 'We do not currently support putting a versioned object to a location-constraint of type AWS or Azure or GCP.'; + 'We do not currently support putting a versioned object to a location-constraint of type Azure.'; /** * Compute the prior data locations that are orphaned. diff --git a/package.json b/package.json index a9320e0d44..f2c20aaf65 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "@opentelemetry/instrumentation-ioredis": "~0.64.0", "@opentelemetry/instrumentation-mongodb": "~0.69.0", "@smithy/node-http-handler": "^3.0.0", - "arsenal": "git+https://github.com/scality/arsenal#8.5.15", + "arsenal": "git+https://github.com/scality/arsenal#2136c02ff1d3dfd43a5ff7ca59f955d105946dba", "async": "2.6.4", "aws-crt": "^1.24.0", "bucketclient": "scality/bucketclient#8.2.7", diff --git a/tests/unit/api/bucketPutVersioning.js b/tests/unit/api/bucketPutVersioning.js index 462fadcf74..26eb07ae70 100644 --- a/tests/unit/api/bucketPutVersioning.js +++ b/tests/unit/api/bucketPutVersioning.js @@ -6,46 +6,44 @@ const { bucketPut } = require('../../../lib/api/bucketPut'); const bucketPutVersioning = require('../../../lib/api/bucketPutVersioning'); const bucketPutReplication = require('../../../lib/api/bucketPutReplication'); -const { cleanup, - DummyRequestLogger, - makeAuthInfo } = require('../helpers'); +const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../helpers'); const metadata = require('../../../lib/metadata/wrapper'); const xmlEnableVersioning = -'' + -'Enabled' + -''; + '' + + 'Enabled' + + ''; const xmlSuspendVersioning = -'' + -'Suspended' + -''; + '' + + 'Suspended' + + ''; const locConstraintVersioned = -'' + -'withversioning' + -''; + '' + + 'withversioning' + + ''; const locConstraintNonVersioned = -'' + -'withoutversioning' + -''; + '' + + 'withoutversioning' + + ''; const xmlReplicationConfiguration = -'' + + '' + 'arn:aws:iam::account-id:role/src-resource' + '' + - '' + - 'Enabled' + - '' + - 'arn:aws:s3:::destination-bucket' + - 'us-east-2' + - '' + + '' + + 'Enabled' + + '' + + 'arn:aws:s3:::destination-bucket' + + 'us-east-2' + + '' + '' + -''; + ''; -const externalVersioningErrorMessage = 'We do not currently support putting ' + -'a versioned object to a location-constraint of type Azure or GCP.'; +const externalVersioningErrorMessage = + 'We do not currently support putting ' + 'a versioned object to a location-constraint of type Azure.'; const log = new DummyRequestLogger(); const bucketName = 'bucketname'; @@ -94,55 +92,58 @@ describe('bucketPutVersioning API', () => { const tests = [ { - msg: 'should successfully enable versioning on location ' + - 'constraint with supportsVersioning set to true', + msg: + 'should successfully enable versioning on location ' + + 'constraint with supportsVersioning set to true', input: xmlEnableVersioning, output: { Status: 'Enabled' }, }, { - msg: 'should successfully suspend versioning on location ' + - 'constraint with supportsVersioning set to true', + msg: + 'should successfully suspend versioning on location ' + + 'constraint with supportsVersioning set to true', input: xmlSuspendVersioning, output: { Status: 'Suspended' }, }, ]; - tests.forEach(test => it(test.msg, done => { - const request = _putVersioningRequest(test.input); - bucketPutVersioning(authInfo, request, log, err => { - assert.ifError(err, - `Expected success, but got err: ${err}`); - metadata.getBucket(bucketName, log, (err, bucket) => { - assert.ifError(err, - `Expected success, but got err: ${err}`); - assert.deepStrictEqual(bucket._versioningConfiguration, - test.output); - done(); + tests.forEach(test => + it(test.msg, done => { + const request = _putVersioningRequest(test.input); + bucketPutVersioning(authInfo, request, log, err => { + assert.ifError(err, `Expected success, but got err: ${err}`); + metadata.getBucket(bucketName, log, (err, bucket) => { + assert.ifError(err, `Expected success, but got err: ${err}`); + assert.deepStrictEqual(bucket._versioningConfiguration, test.output); + done(); + }); }); - }); - })); + }), + ); it('should not suspend versioning on bucket with replication', done => { - async.series([ - // Enable versioning to allow putting a replication config. - next => { - const request = _putVersioningRequest(xmlEnableVersioning); - bucketPutVersioning(authInfo, request, log, next); - }, - // Put the replication config on the bucket. - next => { - const request = - _putReplicationRequest(xmlReplicationConfiguration); - bucketPutReplication(authInfo, request, log, next); - }, - // Attempt to suspend versioning. - next => { - const request = _putVersioningRequest(xmlSuspendVersioning); - bucketPutVersioning(authInfo, request, log, err => { - assert(err.is.InvalidBucketState); - next(); - }); - }, - ], done); + async.series( + [ + // Enable versioning to allow putting a replication config. + next => { + const request = _putVersioningRequest(xmlEnableVersioning); + bucketPutVersioning(authInfo, request, log, next); + }, + // Put the replication config on the bucket. + next => { + const request = _putReplicationRequest(xmlReplicationConfiguration); + bucketPutReplication(authInfo, request, log, next); + }, + // Attempt to suspend versioning. + next => { + const request = _putVersioningRequest(xmlSuspendVersioning); + bucketPutVersioning(authInfo, request, log, err => { + assert(err.is.InvalidBucketState); + next(); + }); + }, + ], + done, + ); }); }); @@ -154,28 +155,28 @@ describe('bucketPutVersioning API', () => { const tests = [ { - msg: 'should return error if enabling versioning on location ' + - 'constraint with supportsVersioning set to false', + msg: + 'should return error if enabling versioning on location ' + + 'constraint with supportsVersioning set to false', input: xmlEnableVersioning, - output: { error: errorInstances.NotImplemented.customizeDescription( - externalVersioningErrorMessage) }, + output: { error: errorInstances.NotImplemented.customizeDescription(externalVersioningErrorMessage) }, }, { - msg: 'should return error if suspending versioning on ' + - ' location constraint with supportsVersioning set to false', + msg: + 'should return error if suspending versioning on ' + + ' location constraint with supportsVersioning set to false', input: xmlSuspendVersioning, - output: { error: errorInstances.NotImplemented.customizeDescription( - externalVersioningErrorMessage) }, + output: { error: errorInstances.NotImplemented.customizeDescription(externalVersioningErrorMessage) }, }, ]; - tests.forEach(test => it(test.msg, done => { - const putBucketVersioningRequest = - _putVersioningRequest(test.input); - bucketPutVersioning(authInfo, putBucketVersioningRequest, log, - err => { - assert.deepStrictEqual(err, test.output.error); - done(); - }); - })); + tests.forEach(test => + it(test.msg, done => { + const putBucketVersioningRequest = _putVersioningRequest(test.input); + bucketPutVersioning(authInfo, putBucketVersioningRequest, log, err => { + assert.deepStrictEqual(err, test.output.error); + done(); + }); + }), + ); }); }); diff --git a/tests/unit/api/createAndStoreObject.js b/tests/unit/api/createAndStoreObject.js index 1c28cce4f1..d30fcd9243 100644 --- a/tests/unit/api/createAndStoreObject.js +++ b/tests/unit/api/createAndStoreObject.js @@ -9,6 +9,7 @@ const { storage } = require('arsenal'); const sinon = require('sinon'); const { bucketPut } = require('../../../lib/api/bucketPut'); +const { data } = require('../../../lib/data/wrapper'); const { cleanup, DummyRequestLogger, makeAuthInfo } = require('../helpers'); const metadata = require('../metadataswitch'); const rawCreateAndStoreObject = require('../../../lib/api/apiUtils/object/createAndStoreObject'); @@ -23,15 +24,16 @@ const canonicalID = authInfo.getCanonicalID(); const bucketName = 'test-bucket'; const objectKey = 'test-object'; -const getObjectMDAsync = (bucket, key, params = {}) => new Promise((resolve, reject) => { - metadata.getObjectMD(bucket, key, params, log, (err, data) => { - if (err) { - reject(err); - } else { - resolve(data); - } +const getObjectMDAsync = (bucket, key, params = {}) => + new Promise((resolve, reject) => { + metadata.getObjectMD(bucket, key, params, log, (err, data) => { + if (err) { + reject(err); + } else { + resolve(data); + } + }); }); -}); describe('createAndStoreObject', () => { let testBucket; @@ -64,17 +66,32 @@ describe('createAndStoreObject', () => { describe('Regular object creation', () => { it('should create object successfully', async () => { - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'content-type': 'text/plain' }, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('test data', 'utf8'), + ); + + const result = await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'content-type': 'text/plain' }, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('test data', 'utf8')); - - const result = await createAndStoreObject(bucketName, testBucket, objectKey, null, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + null, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); assert(result.contentMD5); @@ -83,18 +100,33 @@ describe('createAndStoreObject', () => { }); it('should handle zero-byte object', async () => { - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'content-type': 'text/plain' }, + parsedContentLength: 0, + url: `/${bucketName}/${objectKey}`, + }, + '', + ); + + const result = await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'content-type': 'text/plain' }, - parsedContentLength: 0, - url: `/${bucketName}/${objectKey}`, - }, ''); - - const result = await createAndStoreObject(bucketName, testBucket, objectKey, null, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + null, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); assert(result.contentMD5); }); @@ -103,17 +135,32 @@ describe('createAndStoreObject', () => { const authInfo2 = makeAuthInfo('accessKey2'); sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: {}, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('test', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: {}, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('test', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, null, - authInfo2, authInfo2.getCanonicalID(), null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + null, + authInfo2, + authInfo2.getCanonicalID(), + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); assert.strictEqual(storedObjMD.bucketOwnerId, canonicalID); @@ -130,15 +177,77 @@ describe('createAndStoreObject', () => { url: `/${bucketName}/${objectKey}`, }); - await createAndStoreObject(bucketName, testBucket, objectKey, null, - authInfo, canonicalID, null, request, true, null, - ['overhead'], log, 's3:ObjectRemoved:DeleteMarkerCreated'); + await createAndStoreObject( + bucketName, + testBucket, + objectKey, + null, + authInfo, + canonicalID, + null, + request, + true, + null, + ['overhead'], + log, + 's3:ObjectRemoved:DeleteMarkerCreated', + ); assert.deepStrictEqual(ds, []); const objMD = await getObjectMDAsync(bucketName, objectKey, {}); assert(objMD.isDeleteMarker); }); + + // The marker must still reach the backend, which uses it to drop the + // live version there. + it('should ignore the request checksum for a delete marker on an external location', async () => { + const putStub = sinon.stub(data.client, 'put').yields(null, 'key', 'versionId'); + const externalBucket = 'test-bucket-external'; + const bucketRequest = new DummyRequest({ + bucketName: externalBucket, + namespace: 'default', + headers: { host: `${externalBucket}.s3.amazonaws.com` }, + url: '/', + post: + '' + + '' + + 'awsbackend' + + '', + }); + await promisify(bucketPut)(authInfo, bucketRequest, log); + const bucket = await promisify(metadata.getBucket.bind(metadata))(externalBucket, log); + + const request = new DummyRequest({ + bucketName: externalBucket, + namespace: 'default', + objectKey, + headers: { 'x-amz-checksum-crc32': 'AAAAAQ==' }, + url: `/${externalBucket}/${objectKey}`, + }); + + await createAndStoreObject( + externalBucket, + bucket, + objectKey, + null, + authInfo, + canonicalID, + null, + request, + true, + null, + ['overhead'], + log, + 's3:ObjectRemoved:DeleteMarkerCreated', + ); + + assert(putStub.calledOnce, 'delete marker must still reach the backend'); + assert(putStub.firstCall.args[2].isDeleteMarker); + const objMD = await getObjectMDAsync(externalBucket, objectKey, {}); + assert(objMD.isDeleteMarker); + }); }); describe('Archived object replacement', () => { @@ -146,24 +255,39 @@ describe('createAndStoreObject', () => { const archivedObjMD = { 'content-md5': 'abc123', 'content-length': 100, - 'archive': { - 'archiveInfo': { 'archiveID': 'archive-123' }, + archive: { + archiveInfo: { archiveID: 'archive-123' }, }, }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: {}, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('new data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: {}, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('new data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const options = getStoredOptions(); assert.strictEqual(options.needOplogUpdate, true); @@ -174,26 +298,41 @@ describe('createAndStoreObject', () => { const archivedObjMD = { 'content-md5': 'abc123', 'content-length': 100, - 'versionId': 'v1', - 'archive': { - 'archiveInfo': { 'archiveID': 'archive-123' }, + versionId: 'v1', + archive: { + archiveInfo: { archiveID: 'archive-123' }, }, }; sinon.stub(testBucket, 'isVersioningEnabled').returns(true); sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: {}, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('new data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: {}, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('new data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const options = getStoredOptions(); assert.strictEqual(options.needOplogUpdate, undefined); @@ -212,17 +351,32 @@ describe('createAndStoreObject', () => { sinon.stub(testBucket, 'isVersioningEnabled').returns(false); sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: {}, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('new data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: {}, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('new data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const options = getStoredOptions(); assert.strictEqual(options.needOplogUpdate, true); @@ -239,17 +393,32 @@ describe('createAndStoreObject', () => { }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: {}, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('new data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: {}, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('new data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const options = getStoredOptions(); assert.strictEqual(options.needOplogUpdate, undefined); @@ -261,34 +430,49 @@ describe('createAndStoreObject', () => { it('should restore object with x-scal-s3-version-id header', async () => { const now = Date.now(); const archivedObjMD = { - 'key': objectKey, - 'versionId': 'v123', + key: objectKey, + versionId: 'v123', 'content-md5': 'original-hash', 'content-length': 100, 'x-amz-storage-class': 'cold-location', - 'dataStoreName': 'cold-location', + dataStoreName: 'cold-location', 'x-amz-meta-custom': 'preserved-value', - 'tags': { 'tagkey': 'tagvalue' }, - 'archive': { - 'archiveInfo': { 'archiveID': 'archive-123' }, - 'restoreRequestedAt': new Date(now).toISOString(), - 'restoreRequestedDays': 7, + tags: { tagkey: 'tagvalue' }, + archive: { + archiveInfo: { archiveID: 'archive-123' }, + restoreRequestedAt: new Date(now).toISOString(), + restoreRequestedDays: 7, }, }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'x-scal-s3-version-id': 'v123' }, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('restored data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'x-scal-s3-version-id': 'v123' }, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('restored data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); const options = getStoredOptions(); @@ -296,7 +480,7 @@ describe('createAndStoreObject', () => { assert(storedObjMD.archive.restoreWillExpireAt, 'restoreWillExpireAt should be set'); assert.strictEqual(storedObjMD.archive.restoreRequestedDays, 7); assert.strictEqual(storedObjMD['x-amz-meta-custom'], 'preserved-value'); - assert.deepStrictEqual(storedObjMD.tags, { 'tagkey': 'tagvalue' }); + assert.deepStrictEqual(storedObjMD.tags, { tagkey: 'tagvalue' }); assert.strictEqual(storedObjMD.originOp, 's3:ObjectRestore:Completed'); assert.strictEqual(options.needOplogUpdate, undefined); assert.strictEqual(options.originOp, undefined); @@ -304,36 +488,49 @@ describe('createAndStoreObject', () => { it('should preserve original etag for MPU restoration with different part count', async () => { const archivedObjMD = { - 'versionId': 'v123', + versionId: 'v123', 'content-md5': 'original-abc123-5', // Original had 5 parts - 'archive': { - 'archiveInfo': { 'archiveID': 'archive-123' }, - 'restoreRequestedAt': new Date().toISOString(), - 'restoreRequestedDays': 7, + archive: { + archiveInfo: { archiveID: 'archive-123' }, + restoreRequestedAt: new Date().toISOString(), + restoreRequestedDays: 7, }, }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'x-scal-s3-version-id': 'v123' }, + url: `/${bucketName}/${objectKey}`, + calculatedHash: 'restored-def456-3', // Restored with 3 parts + }, + Buffer.from('restored data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'x-scal-s3-version-id': 'v123' }, - url: `/${bucketName}/${objectKey}`, - calculatedHash: 'restored-def456-3', // Restored with 3 parts - }, Buffer.from('restored data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); - assert.strictEqual(storedObjMD['content-md5'], 'original-abc123-5', - 'Original etag should be preserved'); + assert.strictEqual(storedObjMD['content-md5'], 'original-abc123-5', 'Original etag should be preserved'); assert(storedObjMD['x-amz-restore']['content-md5']); - assert.notStrictEqual(storedObjMD['x-amz-restore']['content-md5'], - storedObjMD['content-md5']); + assert.notStrictEqual(storedObjMD['x-amz-restore']['content-md5'], storedObjMD['content-md5']); }); it('should preserve replication info during restoration', async () => { @@ -343,156 +540,237 @@ describe('createAndStoreObject', () => { }; const archivedObjMD = { - 'versionId': 'v123', + versionId: 'v123', replicationInfo, - 'archive': { - 'archiveInfo': { 'archiveID': 'archive-123' }, - 'restoreRequestedAt': new Date().toISOString(), - 'restoreRequestedDays': 7, + archive: { + archiveInfo: { archiveID: 'archive-123' }, + restoreRequestedAt: new Date().toISOString(), + restoreRequestedDays: 7, }, }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'x-scal-s3-version-id': 'v123' }, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('restored', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'x-scal-s3-version-id': 'v123' }, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('restored', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); - assert.strictEqual(storedObjMD.replicationInfo.status, replicationInfo.status, - 'Replication status should be preserved'); - assert.deepStrictEqual(storedObjMD.replicationInfo.backends, replicationInfo.backends, - 'Replication backends should be preserved'); + assert.strictEqual( + storedObjMD.replicationInfo.status, + replicationInfo.status, + 'Replication status should be preserved', + ); + assert.deepStrictEqual( + storedObjMD.replicationInfo.backends, + replicationInfo.backends, + 'Replication backends should be preserved', + ); }); it('should preserve legal hold during restoration', async () => { const archivedObjMD = { - 'versionId': 'v123', - 'legalHold': true, - 'archive': { - 'archiveInfo': { 'archiveID': 'archive-123' }, - 'restoreRequestedAt': new Date().toISOString(), - 'restoreRequestedDays': 7, + versionId: 'v123', + legalHold: true, + archive: { + archiveInfo: { archiveID: 'archive-123' }, + restoreRequestedAt: new Date().toISOString(), + restoreRequestedDays: 7, }, }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'x-scal-s3-version-id': 'v123' }, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('restored', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'x-scal-s3-version-id': 'v123' }, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('restored', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); - assert.strictEqual(storedObjMD.legalHold, true, - 'Legal hold should be preserved'); + assert.strictEqual(storedObjMD.legalHold, true, 'Legal hold should be preserved'); }); it('should preserve ACLs during restoration', async () => { const acl = { - 'Canned': '', - 'FULL_CONTROL': ['canonical-id-1'], - 'READ': ['canonical-id-2'], + Canned: '', + FULL_CONTROL: ['canonical-id-1'], + READ: ['canonical-id-2'], }; const archivedObjMD = { - 'versionId': 'v123', + versionId: 'v123', acl, - 'archive': { - 'archiveInfo': { 'archiveID': 'archive-123' }, - 'restoreRequestedAt': new Date().toISOString(), - 'restoreRequestedDays': 7, + archive: { + archiveInfo: { archiveID: 'archive-123' }, + restoreRequestedAt: new Date().toISOString(), + restoreRequestedDays: 7, }, }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'x-scal-s3-version-id': 'v123' }, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('restored', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'x-scal-s3-version-id': 'v123' }, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('restored', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); - assert.deepStrictEqual(storedObjMD.acl, acl, - 'ACLs should be preserved'); + assert.deepStrictEqual(storedObjMD.acl, acl, 'ACLs should be preserved'); }); it('should not preserve x-amz-meta-scal-s3-restore-attempt metadata', async () => { const archivedObjMD = { - 'versionId': 'v123', + versionId: 'v123', 'x-amz-meta-custom': 'keep-this', 'x-amz-meta-scal-s3-restore-attempt': '3', - 'archive': { - 'archiveInfo': { 'archiveID': 'archive-123' }, - 'restoreRequestedAt': new Date().toISOString(), - 'restoreRequestedDays': 7, + archive: { + archiveInfo: { archiveID: 'archive-123' }, + restoreRequestedAt: new Date().toISOString(), + restoreRequestedDays: 7, }, }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'x-scal-s3-version-id': 'v123' }, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('restored', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'x-scal-s3-version-id': 'v123' }, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('restored', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); - assert.strictEqual(storedObjMD['x-amz-meta-custom'], 'keep-this', - 'Custom metadata should be preserved'); - assert.strictEqual(storedObjMD['x-amz-meta-scal-s3-restore-attempt'], undefined, - 'Restore attempt metadata should NOT be preserved'); + assert.strictEqual(storedObjMD['x-amz-meta-custom'], 'keep-this', 'Custom metadata should be preserved'); + assert.strictEqual( + storedObjMD['x-amz-meta-scal-s3-restore-attempt'], + undefined, + 'Restore attempt metadata should NOT be preserved', + ); }); }); describe('MPU scenarios', () => { it('should set oldReplayId when overwriting MPU object', async () => { const mpuObjMD = { - 'uploadId': 'mpu-upload-123', + uploadId: 'mpu-upload-123', 'content-md5': 'abc123', }; sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: {}, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('new data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: {}, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('new data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, mpuObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + mpuObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const options = getStoredOptions(); assert.strictEqual(options.oldReplayId, 'mpu-upload-123'); @@ -508,17 +786,32 @@ describe('createAndStoreObject', () => { sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: {}, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('new data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: {}, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('new data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, existingObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + existingObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); assert.strictEqual(storedObjMD['creation-time'], '2024-01-01T00:00:00.000Z'); @@ -531,17 +824,32 @@ describe('createAndStoreObject', () => { sinon.spy(metadata, 'putObjectMD'); - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: {}, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('new data', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: {}, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('new data', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, existingObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + existingObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); assert.strictEqual(storedObjMD['creation-time'], '2024-02-01T00:00:00.000Z'); @@ -562,17 +870,32 @@ describe('createAndStoreObject', () => { }, }; - const request = new DummyRequest({ + const request = new DummyRequest( + { + bucketName, + namespace: 'default', + objectKey, + headers: { 'x-scal-s3-version-id': putVersionId }, + url: `/${bucketName}/${objectKey}`, + }, + Buffer.from('restored', 'utf8'), + ); + + await createAndStoreObject( bucketName, - namespace: 'default', + testBucket, objectKey, - headers: { 'x-scal-s3-version-id': putVersionId }, - url: `/${bucketName}/${objectKey}`, - }, Buffer.from('restored', 'utf8')); - - await createAndStoreObject(bucketName, testBucket, objectKey, archivedObjMD, - authInfo, canonicalID, null, request, false, null, - ['overhead'], log, 's3:ObjectCreated:Put'); + archivedObjMD, + authInfo, + canonicalID, + null, + request, + false, + null, + ['overhead'], + log, + 's3:ObjectCreated:Put', + ); const storedObjMD = getStoredObjectData(); assert.strictEqual(storedObjMD['x-amz-meta-scal-version-id'], putVersionId); diff --git a/tests/unit/multipleBackend/VersioningBackendClient.js b/tests/unit/multipleBackend/VersioningBackendClient.js index 60d8527602..b65a00b0b5 100644 --- a/tests/unit/multipleBackend/VersioningBackendClient.js +++ b/tests/unit/multipleBackend/VersioningBackendClient.js @@ -7,8 +7,7 @@ const DummyService = require('../DummyService'); const { DummyRequestLogger } = require('../helpers'); const missingVerIdInternalError = errorInstances.InternalError.customizeDescription( - 'Invalid state. Please ensure versioning is enabled ' + - 'in AWS for the location constraint and try again.' + 'Invalid state. Please ensure versioning is enabled ' + 'in AWS for the location constraint and try again.', ); const log = new DummyRequestLogger(); @@ -43,8 +42,7 @@ const s3Config = { }; const assertSuccess = (err, cb) => { - assert.ifError(err, - `Expected success, but got error ${err}`); + assert.ifError(err, `Expected success, but got error ${err}`); cb(); }; @@ -54,26 +52,22 @@ const assertFailure = (err, cb) => { }; const genTests = [ { - msg: 'should return success if supportsVersioning === true ' + - 'and backend versioning is enabled', + msg: 'should return success if supportsVersioning === true ' + 'and backend versioning is enabled', input: { supportsVersioning: true, enableMockVersioning: true }, callback: assertSuccess, }, { - msg: 'should return success if supportsVersioning === false ' + - 'and backend versioning is enabled', + msg: 'should return success if supportsVersioning === false ' + 'and backend versioning is enabled', input: { supportsVersioning: false, enableMockVersioning: true }, callback: assertSuccess, }, { - msg: 'should return error if supportsVersioning === true ' + - 'and backend versioning is disabled', + msg: 'should return error if supportsVersioning === true ' + 'and backend versioning is disabled', input: { supportsVersioning: true, enableMockVersioning: false }, callback: assertFailure, }, { - msg: 'should return success if supportsVersioning === false ' + - 'and backend versioning is disabled', + msg: 'should return success if supportsVersioning === false ' + 'and backend versioning is disabled', input: { supportsVersioning: false, enableMockVersioning: false }, callback: assertSuccess, }, @@ -86,12 +80,13 @@ describe('AwsClient::putObject', () => { testClient = new AwsClient(s3Config); testClient._client = new DummyService({ versioning: true }); }); - genTests.forEach(test => it(test.msg, done => { - testClient._supportsVersioning = test.input.supportsVersioning; - testClient._client.versioning = test.input.enableMockVersioning; - testClient.put('', 0, { bucketName: bucket, objectKey: key }, - reqUID, err => test.callback(err, done)); - })); + genTests.forEach(test => + it(test.msg, done => { + testClient._supportsVersioning = test.input.supportsVersioning; + testClient._client.versioning = test.input.enableMockVersioning; + testClient.put('', 0, { bucketName: bucket, objectKey: key }, reqUID, err => test.callback(err, done)); + }), + ); }); describe('AwsClient::copyObject', () => { @@ -102,13 +97,23 @@ describe('AwsClient::copyObject', () => { testClient._client = new DummyService({ versioning: true }); }); - genTests.forEach(test => it(test.msg, done => { - testClient._supportsVersioning = test.input.supportsVersioning; - testClient._client.versioning = test.input.enableMockVersioning; - testClient.copyObject(copyObjectRequest, null, key, - sourceLocationConstraint, null, config, log, - err => test.callback(err, done)); - })); + genTests.forEach(test => + it(test.msg, done => { + testClient._supportsVersioning = test.input.supportsVersioning; + testClient._client.versioning = test.input.enableMockVersioning; + testClient.copyObject( + copyObjectRequest, + null, + key, + undefined, + sourceLocationConstraint, + null, + config, + log, + err => test.callback(err, done), + ); + }), + ); }); describe('AwsClient::completeMPU', () => { @@ -118,13 +123,14 @@ describe('AwsClient::completeMPU', () => { testClient = new AwsClient(s3Config); testClient._client = new DummyService({ versioning: true }); }); - genTests.forEach(test => it(test.msg, done => { - testClient._supportsVersioning = test.input.supportsVersioning; - testClient._client.versioning = test.input.enableMockVersioning; - const uploadId = 'externalBackendTestUploadId'; - testClient.completeMPU(jsonList, null, key, uploadId, - bucket, log, err => test.callback(err, done)); - })); + genTests.forEach(test => + it(test.msg, done => { + testClient._supportsVersioning = test.input.supportsVersioning; + testClient._client.versioning = test.input.enableMockVersioning; + const uploadId = 'externalBackendTestUploadId'; + testClient.completeMPU(jsonList, null, key, uploadId, bucket, log, err => test.callback(err, done)); + }), + ); }); describe('AwsClient::healthcheck', () => { @@ -159,34 +165,31 @@ describe('AwsClient::healthcheck', () => { const tests = [ { - msg: 'should return success if supportsVersioning === true ' + - 'and backend versioning is enabled', + msg: 'should return success if supportsVersioning === true ' + 'and backend versioning is enabled', input: { supportsVersioning: true, enableMockVersioning: true }, callback: assertSuccessVersioned, }, { - msg: 'should return success if supportsVersioning === false ' + - 'and backend versioning is enabled', + msg: 'should return success if supportsVersioning === false ' + 'and backend versioning is enabled', input: { supportsVersioning: false, enableMockVersioning: true }, callback: assertSuccessNonVersioned, }, { - msg: 'should return error if supportsVersioning === true ' + - ' and backend versioning is disabled', + msg: 'should return error if supportsVersioning === true ' + ' and backend versioning is disabled', input: { supportsVersioning: true, enableMockVersioning: false }, callback: assertFailure, }, { - msg: 'should return success if supportsVersioning === false ' + - 'and backend versioning is disabled', + msg: 'should return success if supportsVersioning === false ' + 'and backend versioning is disabled', input: { supportsVersioning: false, enableMockVersioning: false }, callback: assertSuccessNonVersioned, }, ]; - tests.forEach(test => it(test.msg, done => { - testClient._supportsVersioning = test.input.supportsVersioning; - testClient._client.versioning = test.input.enableMockVersioning; - testClient.healthcheck('backend', - (err, resp) => test.callback(resp.backend, done)); - })); + tests.forEach(test => + it(test.msg, done => { + testClient._supportsVersioning = test.input.supportsVersioning; + testClient._client.versioning = test.input.enableMockVersioning; + testClient.healthcheck('backend', (err, resp) => test.callback(resp.backend, done)); + }), + ); }); diff --git a/yarn.lock b/yarn.lock index 854e838c90..cb7d9e6903 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5838,9 +5838,9 @@ arraybuffer.prototype.slice@^1.0.4: optionalDependencies: ioctl "^2.0.2" -"arsenal@git+https://github.com/scality/arsenal#8.5.15": +"arsenal@git+https://github.com/scality/arsenal#2136c02ff1d3dfd43a5ff7ca59f955d105946dba": version "8.5.15" - resolved "git+https://github.com/scality/arsenal#0bbe970dd72b2e235c47910474883af9b9c13eb1" + resolved "git+https://github.com/scality/arsenal#2136c02ff1d3dfd43a5ff7ca59f955d105946dba" dependencies: "@aws-sdk/client-kms" "^3.975.0" "@aws-sdk/client-s3" "^3.975.0"