Compare commits

...

14 Commits

Author SHA1 Message Date
Nicolas Humbert aef0e84081 more tests 2022-05-12 12:25:55 -07:00
Nicolas Humbert 3c08060753 fix 2022-05-12 10:46:57 -07:00
Nicolas Humbert 9d007e7d01 accept empty versionid 2022-05-11 16:33:29 -07:00
Nicolas Humbert 676f6db1cc should work 2022-05-11 16:21:09 -07:00
Nicolas Humbert 11f70f8a70 fix lint 2022-05-11 16:17:12 -07:00
Nicolas Humbert 88d09a5307 cleanup 2022-05-11 16:17:12 -07:00
Nicolas Humbert feb67531aa more tests 2022-05-11 16:17:12 -07:00
Nicolas Humbert 52d6ce2795 tests 2022-05-11 16:17:12 -07:00
Nicolas Humbert 1104d466ca tests started passing 2022-05-11 16:17:11 -07:00
Nicolas Humbert f52f9a0de9 tests 2022-05-11 16:17:11 -07:00
Nicolas Humbert 9b14d0acb1 REFACTO 2022-05-11 16:17:11 -07:00
Nicolas Humbert 46fadbc8f4 WORKING 2022-05-11 16:17:11 -07:00
Nicolas Humbert fc06498df0 WORKISH 2022-05-11 16:17:11 -07:00
Nicolas Humbert 43c0ea5dc9 CLDSRV-192 Introduce s3:PutObjectVersion permission 2022-05-11 16:17:11 -07:00
7 changed files with 647 additions and 5 deletions

View File

@ -118,6 +118,12 @@ function prepareRequestContexts(apiMethod, request, sourceBucket,
generateRequestContext('objectPutACL');
requestContexts.push(putAclRequestContext);
}
// if put object with version
if (request.headers['x-scal-s3-version-id']) {
const putVersionRequestContext =
generateRequestContext('objectPutVersion');
requestContexts.push(putVersionRequestContext);
}
} else {
const requestContext =
generateRequestContext(apiMethodAfterVersionCheck);

View File

@ -8,7 +8,7 @@ const services = require('../../../services');
const logger = require('../../../utilities/logger');
const { dataStore } = require('./storeObject');
const locationConstraintCheck = require('./locationConstraintCheck');
const { versioningPreprocessing } = require('./versioning');
const { versioningPreprocessing, overwrittingVersioning } = require('./versioning');
const removeAWSChunked = require('./removeAWSChunked');
const getReplicationInfo = require('./getReplicationInfo');
const { config } = require('../../../Config');
@ -60,6 +60,9 @@ function _storeInMDandDeleteData(bucketName, dataGetInfo, cipherBundle,
function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
canonicalID, cipherBundle, request, isDeleteMarker, streamingV4Params,
log, callback) {
const putVersionId = request.headers['x-scal-s3-version-id'];
const isPutVersion = putVersionId || putVersionId === '';
const size = isDeleteMarker ? 0 : request.parsedContentLength;
// although the request method may actually be 'DELETE' if creating a
// delete marker, for our purposes we consider this to be a 'PUT'
@ -257,6 +260,15 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
return next(null, dataGetInfoArr);
},
function getVersioningInfo(infoArr, next) {
if (isPutVersion) {
return overwrittingVersioning(bucketName, putVersionId, objMD, metadataStoreParams, log,
(err, options) => {
if (err) {
return next(err);
}
return next(null, options, infoArr);
});
}
return versioningPreprocessing(bucketName, bucketMD,
metadataStoreParams.objectKey, objMD, log, (err, options) => {
if (err) {
@ -278,6 +290,7 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
metadataStoreParams.isNull = options.isNull;
metadataStoreParams.nullVersionId = options.nullVersionId;
metadataStoreParams.nullUploadId = options.nullUploadId;
metadataStoreParams.masterVersionId = options.masterVersionId;
return _storeInMDandDeleteData(bucketName, infoArr,
cipherBundle, metadataStoreParams,
options.dataToDelete, requestLogger, requestMethod, next);

View File

@ -10,6 +10,38 @@ const versionIdUtils = versioning.VersionID;
const nonVersionedObjId =
versionIdUtils.getInfVid(config.replicationGroupId);
/** _decodePutVersionId - decode the version id from x-scal-s3-version-id header
* @param {string} [versionId] - version id from x-scal-s3-version-id header
* @return {object} response
* {string} response.decodedVid - decoded version ID
* {boolean} response.isNull - if true, version is null or empty, false otherwise.
* {Error} response.err - error
*/
function _decodePutVersionId(versionId) {
const invalidErr = errors.InvalidArgument
.customizeDescription('Invalid version id specified in x-scal-s3-version-id header');
if (versionId || versionId === '') {
if (versionId === '' || versionId === 'null') {
return { decodedVid: nonVersionedObjId, isNull: true, err: null };
}
let decoded;
try {
decoded = versionIdUtils.decode(versionId);
} catch (err) {
return { decodedVid: null, isNull: false, err: invalidErr };
}
if (decoded instanceof Error) {
return { decodedVid: null, isNull: false, err: invalidErr };
}
return { decodedVid: decoded, isNull: false, err: null };
}
return { decodedVid: undefined, isNull: false, err: null };
}
/** decodedVidResult - decode the version id from a query object
* @param {object} [reqQuery] - request query object
* @param {string} [reqQuery.versionId] - version ID sent in request query
@ -149,6 +181,7 @@ function processVersioningState(mst, vstat) {
const options = {};
const storeOptions = {};
const delOptions = {};
// object does not exist or is not versioned (before versioning)
if (mst.versionId === undefined || mst.isNull) {
// versioning is suspended, overwrite existing master version
@ -370,6 +403,68 @@ function preprocessingVersioningDelete(bucketName, bucketMD, objectMD,
return callback(null, options);
}
/** overwrittingVersioning - return versioning information for S3 to handle
* storing version metadata with a specific version id.
* @param {string} bucketName - name of the bucket.
* @param {string} putVersionId - version id from x-scal-s3-version-id header
* @param {object} objMD - obj metadata of the master version
* @param {object} metadataStoreParams - custom built object containing resource details.
* @param {RequestLogger} log - logger instance
* @param {function} cb - cb(err, options)
* options.versionId - specific versionId to overwrite in metadata
* ('' overwrites the master version)
* options.versioning - (true/undefined) metadata instruction to create new ver
* options.isNull - (true/undefined) whether new version is null or not
* options.nullVersionId - if storing a null version in version history, the
* version id of the null version
* @return {undefined}
*/
function overwrittingVersioning(bucketName, putVersionId, objMD, metadataStoreParams, log, cb) {
const { err, decodedVid: decodedPutVersionId, isNull } = _decodePutVersionId(putVersionId);
if (err) {
log.error('invalid x-scal-s3-version-id header value', { error: err, putVersionId });
return cb(err);
}
// CASE 1: overwritting the master null version or,
// overwritting a non-versioned object.
if (isNull && (objMD.isNull || !objMD.versionId)) {
metadataStoreParams.creationTime = objMD['creation-time'];
metadataStoreParams.lastModifiedDate = objMD['last-modified'];
// masterVersionId is not undefined: overwrite the master version.
const options = { versionId: '', masterVersionId: objMD.versionId || '' };
if (objMD.versionId) {
// NOTE: if object is not versioned,
// we do not store the isNull property.
options.isNull = true;
}
return process.nextTick(() => cb(null, options));
}
// CASE 2: overwritting a version id.
const options = {
versionId: decodedPutVersionId,
};
// NOTE: We need to retrieve the specific object version ("versionMD") to ensure
// that the rest of the original object properties remain intact.
// NOTE: "objMD" holds the master version properties,
return metadata.getObjectMD(bucketName, metadataStoreParams.objectKey, options, log,
(err, versionMD) => {
if (err) {
log.error('version from x-scal-s3-version-id header does not exist',
{ error: err, putVersionId });
return cb(err);
}
metadataStoreParams.creationTime = versionMD['creation-time'];
metadataStoreParams.lastModifiedDate = versionMD['last-modified'];
// NOTE: set isNull property to true if overwritting null version.
options.isNull = versionMD.isNull;
options.nullVersionId = versionMD.nullVersionId;
return cb(null, options);
});
}
module.exports = {
decodeVersionId,
getVersionIdResHeader,
@ -378,4 +473,5 @@ module.exports = {
getMasterState,
versioningPreprocessing,
preprocessingVersioningDelete,
overwrittingVersioning,
};

View File

@ -97,7 +97,7 @@ const services = {
lastModifiedDate, versioning, versionId, uploadId,
tagging, taggingCopy, replicationInfo, defaultRetention,
dataStoreName, creationTime, retentionMode, retentionDate,
legalHold, originOp } = params;
legalHold, originOp, masterVersionId } = params;
log.trace('storing object in metadata');
assert.strictEqual(typeof bucketName, 'string');
const md = new ObjectMD();
@ -163,6 +163,10 @@ const services = {
md.setUploadId(uploadId);
options.replayId = uploadId;
}
// used to overwrite master version.
if (masterVersionId || masterVersionId === '') {
options.masterVersionId = masterVersionId;
}
// information to store about the version and the null version id
// in the object metadata
const { isNull, nullVersionId, nullUploadId, isDeleteMarker } = params;

View File

@ -20,7 +20,7 @@
"homepage": "https://github.com/scality/S3#readme",
"dependencies": {
"@hapi/joi": "^17.1.0",
"arsenal": "git+https://github.com/scality/Arsenal.git#8.1.47",
"arsenal": "git+https://github.com/scality/Arsenal.git#feature/ARSN-202/overwrite",
"async": "~2.5.0",
"aws-sdk": "2.905.0",
"azure-storage": "^2.1.0",

View File

@ -0,0 +1,523 @@
const assert = require('assert');
const async = require('async');
const { versioning } = require('arsenal');
const { config } = require('../../../../../lib/Config');
const withV4 = require('../support/withV4');
const BucketUtility = require('../../lib/utility/bucket-util');
const metadata = require('../../../../../lib/metadata/wrapper');
const { DummyRequestLogger } = require('../../../../unit/helpers');
const checkError = require('../../lib/utility/checkError');
const versionIdUtils = versioning.VersionID;
const log = new DummyRequestLogger();
const nonVersionedObjId =
versionIdUtils.getInfVid(config.replicationGroupId);
const bucketName = 'bucket1putversion27';
const objectName = 'object1putversion';
const mdListingParams = { listingType: 'DelimiterVersions', maxKeys: 1000 };
function _getMetadata(bucketName, objectName, versionId, cb) {
let decodedVersionId;
if (versionId) {
if (versionId === 'null') {
decodedVersionId = nonVersionedObjId;
} else {
decodedVersionId = versionIdUtils.decode(versionId);
}
if (decodedVersionId instanceof Error) {
return cb(new Error('Invalid version id specified'));
}
}
return metadata.getObjectMD(bucketName, objectName, { versionId: decodedVersionId },
log, (err, objMD) => {
if (err) {
assert.equal(err, null, 'Getting object metadata: expected success, ' +
`got error ${JSON.stringify(err)}`);
}
return cb(null, objMD);
});
}
function putObjectVersion(s3, params, vid, next) {
const request = s3.putObject(params);
request.on('build', () => {
request.httpRequest.headers['x-scal-s3-version-id'] = vid;
});
return request.send(next);
}
describe('PUT object with x-scal-s3-version-id header', () => {
withV4(sigCfg => {
let bucketUtil;
let s3;
beforeEach(done => {
bucketUtil = new BucketUtility('default', sigCfg);
s3 = bucketUtil.s3;
return metadata.setup(() =>
s3.createBucket({ Bucket: bucketName }, err => {
if (err) {
assert.equal(err, null, 'Creating bucket: Expected success, ' +
`got error ${JSON.stringify(err)}`);
}
done();
}));
});
afterEach(() => {
process.stdout.write('Emptying bucket');
return bucketUtil.empty(bucketName)
.then(() => {
process.stdout.write('Deleting bucket');
return bucketUtil.deleteOne(bucketName);
})
.catch(err => {
process.stdout.write('Error in afterEach');
throw err;
});
});
it('should overwrite an object', done => {
const params = { Bucket: bucketName, Key: objectName };
let objMDBefore;
async.waterfall([
next => s3.putObject(params, next),
(res, next) => _getMetadata(bucketName, objectName, undefined, next),
(objMD, next) => {
objMDBefore = objMD;
return putObjectVersion(s3, params, '', next);
},
(res, next) => _getMetadata(bucketName, objectName, undefined, next),
], (err, objMDAfter) => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
it('should overwrite a versioned object', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const params = { Bucket: bucketName, Key: objectName };
let objMDBefore;
let objMDAfter;
let versionsBefore;
let versionsAfter;
let vId;
async.waterfall([
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, (err, res) => {
vId = res.VersionId;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsBefore = res.Versions;
next(err);
}),
next => _getMetadata(bucketName, objectName, undefined, (err, objMD) => {
objMDBefore = objMD;
return next(err);
}),
next => putObjectVersion(s3, params, vId, err => next(err)),
next => _getMetadata(bucketName, objectName, undefined, (err, objMD) => {
objMDAfter = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsAfter = res.Versions;
next(err);
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(versionsAfter, versionsBefore);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
it('should fail if version specified is invalid', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const params = { Bucket: bucketName, Key: objectName };
async.waterfall([
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, err => next(err)),
next => putObjectVersion(s3, params, 'aJLWKz4Ko9IjBBgXKj5KQT.G9UHv0g7P', err => {
checkError(err, 'InvalidArgument', 400);
return next();
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
return done();
});
});
it('should fail if version specified does not exist', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const params = { Bucket: bucketName, Key: objectName };
async.waterfall([
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, err => next(err)),
next => putObjectVersion(s3, params, '', err => {
checkError(err, 'NoSuchKey', 404);
return next();
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
return done();
});
});
it('should overwrite a non-current null version', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const params = { Bucket: bucketName, Key: objectName };
let versionsBefore;
let versionsAfter;
let objMDBefore;
let objMDAfter;
async.waterfall([
next => s3.putObject(params, err => next(err)),
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, err => next(err)),
next => _getMetadata(bucketName, objectName, 'null', (err, objMD) => {
objMDBefore = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsBefore = res.Versions;
next(err);
}),
next => putObjectVersion(s3, params, 'null', err => next(err)),
next => _getMetadata(bucketName, objectName, 'null', (err, objMD) => {
objMDAfter = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsAfter = res.Versions;
next(err);
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(versionsAfter, versionsBefore);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
it('should overwrite the lastest version and keep nullVersionId', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const params = { Bucket: bucketName, Key: objectName };
let versionsBefore;
let versionsAfter;
let objMDBefore;
let objMDAfter;
let vId;
async.waterfall([
next => s3.putObject(params, err => next(err)),
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, (err, res) => {
vId = res.VersionId;
return next(err);
}),
next => _getMetadata(bucketName, objectName, vId, (err, objMD) => {
objMDBefore = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsBefore = res.Versions;
next(err);
}),
next => putObjectVersion(s3, params, vId, err => next(err)),
next => _getMetadata(bucketName, objectName, vId, (err, objMD) => {
objMDAfter = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsAfter = res.Versions;
next(err);
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(versionsAfter, versionsBefore);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
it('should overwrite a current null version', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const sParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Suspended',
}
};
const params = { Bucket: bucketName, Key: objectName };
let objMDBefore;
let objMDAfter;
let versionsBefore;
let versionsAfter;
async.waterfall([
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, err => next(err)),
next => s3.putBucketVersioning(sParams, err => next(err)),
next => s3.putObject(params, err => next(err)),
next => _getMetadata(bucketName, objectName, undefined, (err, objMD) => {
objMDBefore = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsBefore = res.Versions;
next(err);
}),
next => putObjectVersion(s3, params, '', err => next(err)),
next => _getMetadata(bucketName, objectName, undefined, (err, objMD) => {
objMDAfter = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsAfter = res.Versions;
next(err);
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(versionsAfter, versionsBefore);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
it('should overwrite a non-current versioned object', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const params = { Bucket: bucketName, Key: objectName };
let objMDBefore;
let objMDAfter;
let versionsBefore;
let versionsAfter;
let vId;
async.waterfall([
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, err => next(err)),
next => s3.putObject(params, (err, res) => {
vId = res.VersionId;
return next(err);
}),
next => s3.putObject(params, err => next(err)),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsBefore = res.Versions;
next(err);
}),
next => _getMetadata(bucketName, objectName, vId, (err, objMD) => {
objMDBefore = objMD;
return next(err);
}),
next => putObjectVersion(s3, params, vId, err => next(err)),
next => _getMetadata(bucketName, objectName, vId, (err, objMD) => {
objMDAfter = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsAfter = res.Versions;
next(err);
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(versionsAfter, versionsBefore);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
it('should overwrite the current versioned object', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const params = { Bucket: bucketName, Key: objectName };
let objMDBefore;
let objMDAfter;
let versionsBefore;
let versionsAfter;
let vId;
async.waterfall([
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, err => next(err)),
next => s3.putObject(params, (err, res) => {
vId = res.VersionId;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsBefore = res.Versions;
next(err);
}),
next => _getMetadata(bucketName, objectName, vId, (err, objMD) => {
objMDBefore = objMD;
return next(err);
}),
next => putObjectVersion(s3, params, vId, err => next(err)),
next => _getMetadata(bucketName, objectName, vId, (err, objMD) => {
objMDAfter = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsAfter = res.Versions;
next(err);
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(versionsAfter, versionsBefore);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
it('should overwrite the current versioned object after bucket version suspended', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const sParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Suspended',
}
};
const params = { Bucket: bucketName, Key: objectName };
let objMDBefore;
let objMDAfter;
let versionsBefore;
let versionsAfter;
let vId;
async.waterfall([
next => s3.putBucketVersioning(vParams, err => next(err)),
next => s3.putObject(params, err => next(err)),
next => s3.putObject(params, (err, res) => {
vId = res.VersionId;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsBefore = res.Versions;
next(err);
}),
next => _getMetadata(bucketName, objectName, vId, (err, objMD) => {
objMDBefore = objMD;
return next(err);
}),
next => s3.putBucketVersioning(sParams, err => next(err)),
next => putObjectVersion(s3, params, vId, err => next(err)),
next => _getMetadata(bucketName, objectName, vId, (err, objMD) => {
objMDAfter = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsAfter = res.Versions;
next(err);
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(versionsAfter, versionsBefore);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
it('should overwrite the current versioned object after bucket version enabled', done => {
const vParams = {
Bucket: bucketName,
VersioningConfiguration: {
Status: 'Enabled',
}
};
const params = { Bucket: bucketName, Key: objectName };
let objMDBefore;
let objMDAfter;
let versionsBefore;
let versionsAfter;
async.waterfall([
next => s3.putObject(params, err => next(err)),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsBefore = res.Versions;
next(err);
}),
next => _getMetadata(bucketName, objectName, undefined, (err, objMD) => {
objMDBefore = objMD;
return next(err);
}),
next => s3.putBucketVersioning(vParams, err => next(err)),
next => putObjectVersion(s3, params, 'null', err => next(err)),
next => _getMetadata(bucketName, objectName, undefined, (err, objMD) => {
objMDAfter = objMD;
return next(err);
}),
next => metadata.listObject(bucketName, mdListingParams, log, (err, res) => {
versionsAfter = res.Versions;
next(err);
}),
], err => {
assert.equal(err, null, `Expected success got error ${JSON.stringify(err)}`);
assert.deepStrictEqual(versionsAfter, versionsBefore);
assert.deepStrictEqual(objMDAfter, objMDBefore);
return done();
});
});
});
});

View File

@ -510,9 +510,9 @@ arraybuffer.slice@~0.0.7:
optionalDependencies:
ioctl "^2.0.2"
"arsenal@git+https://github.com/scality/Arsenal.git#8.1.47":
"arsenal@git+https://github.com/scality/Arsenal.git#feature/ARSN-202/overwrite":
version "8.1.47"
resolved "git+https://github.com/scality/Arsenal.git#e37712e94f747de6c2118d0280da7370e8aedd69"
resolved "git+https://github.com/scality/Arsenal.git#4d6cd5642f88e70b93f5e4265ec9afa92b736299"
dependencies:
"@types/async" "^3.2.12"
"@types/utf8" "^3.0.1"