Compare commits
17 Commits
developmen
...
user/jonat
Author | SHA1 | Date |
---|---|---|
Jonathan Gramain | 93c2c4b1d6 | |
Jonathan Gramain | 2a09de676b | |
Jonathan Gramain | 03cee655b0 | |
Jonathan Gramain | dcf13fa930 | |
Jonathan Gramain | b5349a0fd0 | |
Jonathan Gramain | 65063d2fc8 | |
Jonathan Gramain | ef9debbb8b | |
Jonathan Gramain | 151ed8fffe | |
Jonathan Gramain | cdb8c495a4 | |
Jonathan Gramain | 0e517396ec | |
Jonathan Gramain | 06df252b1f | |
Jonathan Gramain | aca9509b36 | |
Jonathan Gramain | da91f149c1 | |
Jonathan Gramain | c8671ffc77 | |
Jonathan Gramain | 22f8d28f4b | |
Jonathan Gramain | 6fe0b700e7 | |
Jonathan Gramain | 6bb7c73f99 |
|
@ -257,6 +257,7 @@ function createAndStoreObject(bucketName, bucketMD, objectKey, objMD, authInfo,
|
||||||
metadataStoreParams.versionId = options.versionId;
|
metadataStoreParams.versionId = options.versionId;
|
||||||
metadataStoreParams.versioning = options.versioning;
|
metadataStoreParams.versioning = options.versioning;
|
||||||
metadataStoreParams.isNull = options.isNull;
|
metadataStoreParams.isNull = options.isNull;
|
||||||
|
metadataStoreParams.deleteNullKey = options.deleteNullKey;
|
||||||
if (options.extraMD) {
|
if (options.extraMD) {
|
||||||
Object.assign(metadataStoreParams, options.extraMD);
|
Object.assign(metadataStoreParams, options.extraMD);
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,17 +62,34 @@ function checkQueryVersionId(query) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function _storeNullVersionMD(bucketName, objKey, objMD, options, log, cb) {
|
function _storeNullVersionMD(bucketName, objKey, nullVersionId, objMD, log, cb) {
|
||||||
metadata.putObjectMD(bucketName, objKey, objMD, options, log, err => {
|
// In compatibility mode, create null versioned keys instead of null keys
|
||||||
|
let versionId;
|
||||||
|
let nullVersionMD;
|
||||||
|
if (config.nullVersionCompatMode) {
|
||||||
|
versionId = nullVersionId;
|
||||||
|
nullVersionMD = Object.assign({}, objMD, {
|
||||||
|
versionId: nullVersionId,
|
||||||
|
isNull: true,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
versionId = 'null';
|
||||||
|
nullVersionMD = Object.assign({}, objMD, {
|
||||||
|
versionId: nullVersionId,
|
||||||
|
isNull: true,
|
||||||
|
isNull2: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
metadata.putObjectMD(bucketName, objKey, nullVersionMD, { versionId }, log, err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.debug('error from metadata storing null version as new version',
|
log.debug('error from metadata storing null version as new version',
|
||||||
{ error: err });
|
{ error: err });
|
||||||
}
|
}
|
||||||
cb(err, options);
|
cb(err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** get location of null version data for deletion
|
/** check existence and get location of null version data for deletion
|
||||||
* @param {string} bucketName - name of bucket
|
* @param {string} bucketName - name of bucket
|
||||||
* @param {string} objKey - name of object key
|
* @param {string} objKey - name of object key
|
||||||
* @param {object} options - metadata options for getting object MD
|
* @param {object} options - metadata options for getting object MD
|
||||||
|
@ -83,49 +100,53 @@ function _storeNullVersionMD(bucketName, objKey, objMD, options, log, cb) {
|
||||||
* @param {function} cb - callback
|
* @param {function} cb - callback
|
||||||
* @return {undefined} - and call callback with (err, dataToDelete)
|
* @return {undefined} - and call callback with (err, dataToDelete)
|
||||||
*/
|
*/
|
||||||
function _getNullVersionsToDelete(bucketName, objKey, options, mst, log, cb) {
|
function _prepareNullVersionDeletion(bucketName, objKey, options, mst, log, cb) {
|
||||||
|
const nullOptions = {};
|
||||||
|
if (!options.deleteData) {
|
||||||
|
return process.nextTick(cb, null, nullOptions);
|
||||||
|
}
|
||||||
if (options.versionId === mst.versionId) {
|
if (options.versionId === mst.versionId) {
|
||||||
// no need to get delete location, we already have the master's metadata
|
// no need to get another key as the master is the target
|
||||||
const dataToDelete = mst.objLocation;
|
nullOptions.dataToDelete = mst.objLocation;
|
||||||
return process.nextTick(cb, null, dataToDelete);
|
return process.nextTick(cb, null, nullOptions);
|
||||||
|
}
|
||||||
|
if (options.versionId === 'null') {
|
||||||
|
// deletion of the null key will be done by the main metadata
|
||||||
|
// PUT via this option
|
||||||
|
nullOptions.deleteNullKey = true;
|
||||||
}
|
}
|
||||||
return metadata.getObjectMD(bucketName, objKey, options, log,
|
return metadata.getObjectMD(bucketName, objKey, options, log,
|
||||||
(err, versionMD) => {
|
(err, versionMD) => {
|
||||||
|
// the null key may not exist, hence it's a normal
|
||||||
|
// situation to have a NoSuchKey error, in which case
|
||||||
|
// there is nothing to delete
|
||||||
|
if (err && err.is.NoSuchKey) {
|
||||||
|
return cb(null, {});
|
||||||
|
}
|
||||||
if (err) {
|
if (err) {
|
||||||
log.debug('err from metadata getting specified version', {
|
log.warn('could not get null version metadata', {
|
||||||
error: err,
|
error: err,
|
||||||
method: '_getNullVersionsToDelete',
|
method: '_prepareNullVersionDeletion',
|
||||||
});
|
});
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
if (!versionMD.location) {
|
if (versionMD.location) {
|
||||||
return cb();
|
|
||||||
}
|
|
||||||
const dataToDelete = Array.isArray(versionMD.location) ?
|
const dataToDelete = Array.isArray(versionMD.location) ?
|
||||||
versionMD.location : [versionMD.location];
|
versionMD.location : [versionMD.location];
|
||||||
return cb(null, dataToDelete);
|
nullOptions.dataToDelete = dataToDelete;
|
||||||
|
}
|
||||||
|
return cb(null, nullOptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function _deleteNullVersionMD(bucketName, objKey, options, mst, log, cb) {
|
function _deleteNullVersionMD(bucketName, objKey, options, log, cb) {
|
||||||
return _getNullVersionsToDelete(bucketName, objKey, options, mst, log,
|
return metadata.deleteObjectMD(bucketName, objKey, options, log, err => {
|
||||||
(err, nullDataToDelete) => {
|
|
||||||
if (err) {
|
if (err) {
|
||||||
log.warn('could not find null version metadata', {
|
log.warn('metadata error deleting null versioned key',
|
||||||
error: err,
|
{ bucketName, objKey, error: err, method: '_deleteNullVersionMD' });
|
||||||
method: '_deleteNullVersionMD',
|
|
||||||
});
|
|
||||||
return cb(err);
|
return cb(err);
|
||||||
}
|
}
|
||||||
return metadata.deleteObjectMD(bucketName, objKey, options, log,
|
return cb();
|
||||||
err => {
|
|
||||||
if (err) {
|
|
||||||
log.warn('metadata error deleting null version',
|
|
||||||
{ error: err, method: '_deleteNullVersionMD' });
|
|
||||||
return cb(err);
|
|
||||||
}
|
|
||||||
return cb(null, nullDataToDelete);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +157,10 @@ function _deleteNullVersionMD(bucketName, objKey, options, mst, log, cb) {
|
||||||
* @param {object} mst - state of master version, as returned by
|
* @param {object} mst - state of master version, as returned by
|
||||||
* getMasterState()
|
* getMasterState()
|
||||||
* @param {string} vstat - bucket versioning status: 'Enabled' or 'Suspended'
|
* @param {string} vstat - bucket versioning status: 'Enabled' or 'Suspended'
|
||||||
|
* @param {boolean} nullVersionCompatMode - if true, behaves in null
|
||||||
|
* version compatibility mode and return appropriate values: this mode
|
||||||
|
* does not attempt to create null keys but create null versioned keys
|
||||||
|
* instead
|
||||||
*
|
*
|
||||||
* @return {object} result object with the following attributes:
|
* @return {object} result object with the following attributes:
|
||||||
* - {object} options: versioning-related options to pass to the
|
* - {object} options: versioning-related options to pass to the
|
||||||
|
@ -145,7 +170,7 @@ function _deleteNullVersionMD(bucketName, objKey, options, mst, log, cb) {
|
||||||
* - {object} [delOptions]: options for metadata to delete the null
|
* - {object} [delOptions]: options for metadata to delete the null
|
||||||
version key, if needed
|
version key, if needed
|
||||||
*/
|
*/
|
||||||
function processVersioningState(mst, vstat) {
|
function processVersioningState(mst, vstat, nullVersionCompatMode) {
|
||||||
const versioningSuspended = (vstat === 'Suspended');
|
const versioningSuspended = (vstat === 'Suspended');
|
||||||
const masterIsNull = mst.exists && (mst.isNull || !mst.versionId);
|
const masterIsNull = mst.exists && (mst.isNull || !mst.versionId);
|
||||||
|
|
||||||
|
@ -157,29 +182,48 @@ function processVersioningState(mst, vstat) {
|
||||||
if (mst.objLocation) {
|
if (mst.objLocation) {
|
||||||
options.dataToDelete = mst.objLocation;
|
options.dataToDelete = mst.objLocation;
|
||||||
}
|
}
|
||||||
if (mst.isNull) {
|
// backward-compat: a null version key may exist even with
|
||||||
|
// a null master (due to S3C-7526), if so, delete it (its
|
||||||
|
// data will be deleted as part of the master cleanup, so
|
||||||
|
// no "deleteData" param is needed)
|
||||||
|
//
|
||||||
|
// "isNull2" attribute is set in master metadata when
|
||||||
|
// null keys are used, which is used as an optimization to
|
||||||
|
// avoid having to check the versioned key since there can
|
||||||
|
// be no more versioned key to clean up
|
||||||
|
if (mst.isNull && !mst.isNull2) {
|
||||||
const delOptions = { versionId: mst.versionId };
|
const delOptions = { versionId: mst.versionId };
|
||||||
if (mst.uploadId) {
|
|
||||||
delOptions.replayId = mst.uploadId;
|
|
||||||
}
|
|
||||||
return { options, delOptions };
|
return { options, delOptions };
|
||||||
}
|
}
|
||||||
return { options };
|
return { options };
|
||||||
}
|
}
|
||||||
if (mst.nullVersionId) {
|
if (mst.nullVersionId) {
|
||||||
const delOptions = { versionId: mst.nullVersionId };
|
// backward-compat: delete the null versioned key and data
|
||||||
|
const delOptions = { versionId: mst.nullVersionId, deleteData: true };
|
||||||
if (mst.nullUploadId) {
|
if (mst.nullUploadId) {
|
||||||
delOptions.replayId = mst.nullUploadId;
|
delOptions.replayId = mst.nullUploadId;
|
||||||
}
|
}
|
||||||
return { options, delOptions };
|
return { options, delOptions };
|
||||||
}
|
}
|
||||||
return { options };
|
// clean up the eventual null key's location data prior to put
|
||||||
|
|
||||||
|
// NOTE: due to metadata v1 internal format, we cannot guess
|
||||||
|
// from the master key whether there is an associated null
|
||||||
|
// key, because the master key may be removed whenever the
|
||||||
|
// latest version becomes a delete marker. Hence we need to
|
||||||
|
// pessimistically try to get the null key metadata and delete
|
||||||
|
// it if it exists.
|
||||||
|
const delOptions = { versionId: 'null', deleteData: true };
|
||||||
|
return { options, delOptions };
|
||||||
}
|
}
|
||||||
|
|
||||||
// versioning is enabled: create a new version
|
// versioning is enabled: create a new version
|
||||||
const options = { versioning: true };
|
const options = { versioning: true };
|
||||||
if (masterIsNull) {
|
if (masterIsNull) {
|
||||||
// store master version in a new key
|
// if master is a null version or a non-versioned key,
|
||||||
|
// copy it to a new null key
|
||||||
const nullVersionId = mst.isNull ? mst.versionId : nonVersionedObjId;
|
const nullVersionId = mst.isNull ? mst.versionId : nonVersionedObjId;
|
||||||
|
if (nullVersionCompatMode) {
|
||||||
options.extraMD = {
|
options.extraMD = {
|
||||||
nullVersionId,
|
nullVersionId,
|
||||||
};
|
};
|
||||||
|
@ -188,7 +232,21 @@ function processVersioningState(mst, vstat) {
|
||||||
}
|
}
|
||||||
return { options, nullVersionId };
|
return { options, nullVersionId };
|
||||||
}
|
}
|
||||||
// versioning is enabled, copy reference to null version ID if it exists
|
if (mst.isNull && !mst.isNull2) {
|
||||||
|
// if master null version was put with an older
|
||||||
|
// Cloudserver (or in compat mode), there is a
|
||||||
|
// possibility that it also has a null versioned key
|
||||||
|
// associated, so we need to delete it as we write the
|
||||||
|
// null key
|
||||||
|
const delOptions = {
|
||||||
|
versionId: nullVersionId,
|
||||||
|
};
|
||||||
|
return { options, nullVersionId, delOptions };
|
||||||
|
}
|
||||||
|
return { options, nullVersionId };
|
||||||
|
}
|
||||||
|
// backward-compat: keep a reference to the existing null
|
||||||
|
// versioned key
|
||||||
if (mst.nullVersionId) {
|
if (mst.nullVersionId) {
|
||||||
options.extraMD = {
|
options.extraMD = {
|
||||||
nullVersionId: mst.nullVersionId,
|
nullVersionId: mst.nullVersionId,
|
||||||
|
@ -222,6 +280,7 @@ function getMasterState(objMD) {
|
||||||
versionId: objMD.versionId,
|
versionId: objMD.versionId,
|
||||||
uploadId: objMD.uploadId,
|
uploadId: objMD.uploadId,
|
||||||
isNull: objMD.isNull,
|
isNull: objMD.isNull,
|
||||||
|
isNull2: objMD.isNull2,
|
||||||
nullVersionId: objMD.nullVersionId,
|
nullVersionId: objMD.nullVersionId,
|
||||||
nullUploadId: objMD.nullUploadId,
|
nullUploadId: objMD.nullUploadId,
|
||||||
};
|
};
|
||||||
|
@ -245,9 +304,6 @@ function getMasterState(objMD) {
|
||||||
* ('' overwrites the master version)
|
* ('' overwrites the master version)
|
||||||
* options.versioning - (true/undefined) metadata instruction to create new ver
|
* options.versioning - (true/undefined) metadata instruction to create new ver
|
||||||
* options.isNull - (true/undefined) whether new version is null or not
|
* 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
|
|
||||||
* options.deleteNullVersionData - whether to delete the data of the null ver
|
|
||||||
*/
|
*/
|
||||||
function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD,
|
function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD,
|
||||||
log, callback) {
|
log, callback) {
|
||||||
|
@ -260,39 +316,38 @@ function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD,
|
||||||
}
|
}
|
||||||
// bucket is versioning configured
|
// bucket is versioning configured
|
||||||
const { options, nullVersionId, delOptions } =
|
const { options, nullVersionId, delOptions } =
|
||||||
processVersioningState(mst, vCfg.Status);
|
processVersioningState(mst, vCfg.Status, config.nullVersionCompatMode);
|
||||||
return async.series([
|
return async.series([
|
||||||
function storeVersion(next) {
|
function storeNullVersionMD(next) {
|
||||||
if (!nullVersionId) {
|
if (!nullVersionId) {
|
||||||
return process.nextTick(next);
|
return process.nextTick(next);
|
||||||
}
|
}
|
||||||
const versionMD = Object.assign({}, objMD, { versionId: nullVersionId, isNull: true });
|
return _storeNullVersionMD(bucketName, objectKey, nullVersionId, objMD, log, next);
|
||||||
const params = { versionId: nullVersionId };
|
|
||||||
return _storeNullVersionMD(bucketName, objectKey, versionMD, params, log, next);
|
|
||||||
},
|
},
|
||||||
function deleteNullVersion(next) {
|
function prepareNullVersionDeletion(next) {
|
||||||
if (!delOptions) {
|
if (!delOptions) {
|
||||||
return process.nextTick(next);
|
return process.nextTick(next);
|
||||||
}
|
}
|
||||||
return _deleteNullVersionMD(bucketName, objectKey, delOptions, mst,
|
return _prepareNullVersionDeletion(
|
||||||
log, (err, nullDataToDelete) => {
|
bucketName, objectKey, delOptions, mst, log,
|
||||||
|
(err, nullOptions) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
log.warn('unexpected error deleting null version md', {
|
return next(err);
|
||||||
error: err,
|
|
||||||
method: 'versioningPreprocessing',
|
|
||||||
});
|
|
||||||
// it's possible there was a concurrent request to
|
|
||||||
// delete the null version, so proceed with putting a
|
|
||||||
// new version
|
|
||||||
if (err.is.NoSuchKey) {
|
|
||||||
return next(null, options);
|
|
||||||
}
|
}
|
||||||
return next(errors.InternalError);
|
Object.assign(options, nullOptions);
|
||||||
}
|
|
||||||
Object.assign(options, { dataToDelete: nullDataToDelete });
|
|
||||||
return next();
|
return next();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
function deleteNullVersionMD(next) {
|
||||||
|
if (delOptions &&
|
||||||
|
delOptions.versionId &&
|
||||||
|
delOptions.versionId !== 'null') {
|
||||||
|
// backward-compat: delete old null versioned key
|
||||||
|
return _deleteNullVersionMD(
|
||||||
|
bucketName, objectKey, { versionId: delOptions.versionId }, log, next);
|
||||||
|
}
|
||||||
|
return process.nextTick(next);
|
||||||
|
},
|
||||||
], err => callback(err, options));
|
], err => callback(err, options));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,12 +357,19 @@ function versioningPreprocessing(bucketName, bucketMD, objectKey, objMD,
|
||||||
* @param {object} bucketMD - bucket metadata
|
* @param {object} bucketMD - bucket metadata
|
||||||
* @param {object} objectMD - obj metadata
|
* @param {object} objectMD - obj metadata
|
||||||
* @param {string} [reqVersionId] - specific version ID sent as part of request
|
* @param {string} [reqVersionId] - specific version ID sent as part of request
|
||||||
|
* @param {boolean} nullVersionCompatMode - if true, behaves in null
|
||||||
|
* version compatibility mode and return appropriate values:
|
||||||
|
* - in normal mode, returns an 'isNull' boolean sent to Metadata (true or false)
|
||||||
|
* - in compatibility mode, does not return an 'isNull' property
|
||||||
* @return {object} options object with params:
|
* @return {object} options object with params:
|
||||||
* options.deleteData - (true/undefined) whether to delete data (if undefined
|
* {boolean} [options.deleteData=true|undefined] - whether to delete data (if undefined
|
||||||
* means creating a delete marker instead)
|
* means creating a delete marker instead)
|
||||||
* options.versionId - specific versionId to delete
|
* {string} [options.versionId] - specific versionId to delete
|
||||||
|
* {boolean} [options.isNull=true|false|undefined] - if set, tells the
|
||||||
|
* Metadata backend if we're deleting a null version or not a null
|
||||||
|
* version. Not set if `nullVersionCompatMode` is true.
|
||||||
*/
|
*/
|
||||||
function preprocessingVersioningDelete(bucketName, bucketMD, objectMD, reqVersionId) {
|
function preprocessingVersioningDelete(bucketName, bucketMD, objectMD, reqVersionId, nullVersionCompatMode) {
|
||||||
const options = {};
|
const options = {};
|
||||||
if (!bucketMD.getVersioningConfiguration() || reqVersionId) {
|
if (!bucketMD.getVersioningConfiguration() || reqVersionId) {
|
||||||
// delete data if bucket is non-versioned or the request
|
// delete data if bucket is non-versioned or the request
|
||||||
|
@ -316,14 +378,31 @@ function preprocessingVersioningDelete(bucketName, bucketMD, objectMD, reqVersio
|
||||||
}
|
}
|
||||||
if (bucketMD.getVersioningConfiguration() && reqVersionId) {
|
if (bucketMD.getVersioningConfiguration() && reqVersionId) {
|
||||||
if (reqVersionId === 'null') {
|
if (reqVersionId === 'null') {
|
||||||
// deleting the 'null' version if it exists: use its
|
// deleting the 'null' version if it exists:
|
||||||
// internal versionId if it exists
|
//
|
||||||
|
// - use its internal versionId if it is a "real" null
|
||||||
|
// version (not non-versioned)
|
||||||
|
//
|
||||||
|
// - send the "isNull" param to Metadata if:
|
||||||
|
//
|
||||||
|
// - in non-compat mode (mandatory for v1 format)
|
||||||
|
//
|
||||||
|
// - OR if the version is already a null key put by a
|
||||||
|
// non-compat mode Cloudserver, to let Metadata know that
|
||||||
|
// the null key is to be deleted. This is the case if the
|
||||||
|
// "isNull2" param is set.
|
||||||
if (objectMD.versionId !== undefined) {
|
if (objectMD.versionId !== undefined) {
|
||||||
options.versionId = objectMD.versionId;
|
options.versionId = objectMD.versionId;
|
||||||
|
if (objectMD.isNull2) {
|
||||||
|
options.isNull = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// deleting a specific version
|
// deleting a specific version
|
||||||
options.versionId = reqVersionId;
|
options.versionId = reqVersionId;
|
||||||
|
if (!nullVersionCompatMode) {
|
||||||
|
options.isNull = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return options;
|
return options;
|
||||||
|
|
|
@ -337,6 +337,7 @@ function completeMultipartUpload(authInfo, request, log, callback) {
|
||||||
metaStoreParams.versionId = options.versionId;
|
metaStoreParams.versionId = options.versionId;
|
||||||
metaStoreParams.versioning = options.versioning;
|
metaStoreParams.versioning = options.versioning;
|
||||||
metaStoreParams.isNull = options.isNull;
|
metaStoreParams.isNull = options.isNull;
|
||||||
|
metaStoreParams.deleteNullKey = options.deleteNullKey;
|
||||||
if (options.extraMD) {
|
if (options.extraMD) {
|
||||||
Object.assign(metaStoreParams, options.extraMD);
|
Object.assign(metaStoreParams, options.extraMD);
|
||||||
}
|
}
|
||||||
|
|
|
@ -285,7 +285,8 @@ function getObjMetadataAndDelete(authInfo, canonicalID, request,
|
||||||
return callback(null, objMD, versionId);
|
return callback(null, objMD, versionId);
|
||||||
},
|
},
|
||||||
(objMD, versionId, callback) => {
|
(objMD, versionId, callback) => {
|
||||||
const options = preprocessingVersioningDelete(bucketName, bucket, objMD, versionId);
|
const options = preprocessingVersioningDelete(
|
||||||
|
bucketName, bucket, objMD, versionId, config.nullVersionCompatMode);
|
||||||
const deleteInfo = {};
|
const deleteInfo = {};
|
||||||
if (options && options.deleteData) {
|
if (options && options.deleteData) {
|
||||||
deleteInfo.deleted = true;
|
deleteInfo.deleted = true;
|
||||||
|
|
|
@ -214,6 +214,7 @@ function objectCopy(authInfo, request, sourceBucket,
|
||||||
bucketName: sourceBucket,
|
bucketName: sourceBucket,
|
||||||
objectKey: sourceObject,
|
objectKey: sourceObject,
|
||||||
versionId: sourceVersionId,
|
versionId: sourceVersionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
requestType: 'objectGet',
|
requestType: 'objectGet',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
|
@ -138,11 +138,10 @@ function objectDelete(authInfo, request, log, cb) {
|
||||||
},
|
},
|
||||||
function deleteOperation(bucketMD, objectMD, next) {
|
function deleteOperation(bucketMD, objectMD, next) {
|
||||||
const delOptions = preprocessingVersioningDelete(
|
const delOptions = preprocessingVersioningDelete(
|
||||||
bucketName, bucketMD, objectMD, reqVersionId);
|
bucketName, bucketMD, objectMD, reqVersionId, config.nullVersionCompatMode);
|
||||||
const deleteInfo = {
|
const deleteInfo = {
|
||||||
removeDeleteMarker: false,
|
removeDeleteMarker: false,
|
||||||
newDeleteMarker: false,
|
newDeleteMarker: false,
|
||||||
isNull: delOptions.isNull,
|
|
||||||
};
|
};
|
||||||
if (delOptions && delOptions.deleteData) {
|
if (delOptions && delOptions.deleteData) {
|
||||||
if (objectMD.isDeleteMarker) {
|
if (objectMD.isDeleteMarker) {
|
||||||
|
|
|
@ -11,6 +11,7 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
|
const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
|
||||||
const { data } = require('../data/wrapper');
|
const { data } = require('../data/wrapper');
|
||||||
|
const { config } = require('../Config');
|
||||||
const REPLICATION_ACTION = 'DELETE_TAGGING';
|
const REPLICATION_ACTION = 'DELETE_TAGGING';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -41,8 +42,9 @@ function objectDeleteTagging(authInfo, request, log, callback) {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'objectDeleteTagging',
|
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
|
requestType: 'objectDeleteTagging',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,6 +66,8 @@ function objectDeleteTagging(authInfo, request, log, callback) {
|
||||||
if (objectMD.isDeleteMarker) {
|
if (objectMD.isDeleteMarker) {
|
||||||
log.trace('version is a delete marker',
|
log.trace('version is a delete marker',
|
||||||
{ method: 'objectDeleteTagging' });
|
{ method: 'objectDeleteTagging' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.MethodNotAllowed, bucket);
|
return next(errors.MethodNotAllowed, bucket);
|
||||||
}
|
}
|
||||||
return next(null, bucket, objectMD);
|
return next(null, bucket, objectMD);
|
||||||
|
@ -71,8 +75,13 @@ function objectDeleteTagging(authInfo, request, log, callback) {
|
||||||
(bucket, objectMD, next) => {
|
(bucket, objectMD, next) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
objectMD.tags = {};
|
objectMD.tags = {};
|
||||||
const params = objectMD.versionId ? { versionId:
|
const params = {};
|
||||||
objectMD.versionId } : {};
|
if (objectMD.versionId) {
|
||||||
|
params.versionId = objectMD.versionId;
|
||||||
|
if (!config.nullVersionCompatMode) {
|
||||||
|
params.isNull = objectMD.isNull || false;
|
||||||
|
}
|
||||||
|
}
|
||||||
const replicationInfo = getReplicationInfo(objectKey, bucket, true,
|
const replicationInfo = getReplicationInfo(objectKey, bucket, true,
|
||||||
0, REPLICATION_ACTION, objectMD);
|
0, REPLICATION_ACTION, objectMD);
|
||||||
if (replicationInfo) {
|
if (replicationInfo) {
|
||||||
|
|
|
@ -45,6 +45,7 @@ function objectGet(authInfo, request, returnTagCount, log, callback) {
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
versionId,
|
versionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
requestType: 'objectGet',
|
requestType: 'objectGet',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
|
@ -54,6 +54,8 @@ function objectGetACL(authInfo, request, log, callback) {
|
||||||
}
|
}
|
||||||
const versionId = decodedVidResult;
|
const versionId = decodedVidResult;
|
||||||
|
|
||||||
|
// FIXME pass 'getDeleteMarker: true' option to set
|
||||||
|
// 'x-amz-delete-marker' header (see S3C-7592)
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
|
@ -90,10 +92,14 @@ function objectGetACL(authInfo, request, log, callback) {
|
||||||
if (versionId) {
|
if (versionId) {
|
||||||
log.trace('requested version is delete marker',
|
log.trace('requested version is delete marker',
|
||||||
{ method: 'objectGetACL' });
|
{ method: 'objectGetACL' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.MethodNotAllowed);
|
return next(errors.MethodNotAllowed);
|
||||||
}
|
}
|
||||||
log.trace('most recent version is delete marker',
|
log.trace('most recent version is delete marker',
|
||||||
{ method: 'objectGetACL' });
|
{ method: 'objectGetACL' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.NoSuchKey);
|
return next(errors.NoSuchKey);
|
||||||
}
|
}
|
||||||
return next(null, bucket, objectMD);
|
return next(null, bucket, objectMD);
|
||||||
|
|
|
@ -33,12 +33,14 @@ function objectGetLegalHold(authInfo, request, log, callback) {
|
||||||
}
|
}
|
||||||
const versionId = decodedVidResult;
|
const versionId = decodedVidResult;
|
||||||
|
|
||||||
|
// FIXME pass 'getDeleteMarker: true' option to set
|
||||||
|
// 'x-amz-delete-marker' header (see S3C-7592)
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'objectGetLegalHold',
|
|
||||||
versionId,
|
versionId,
|
||||||
|
requestType: 'objectGetLegalHold',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,10 +63,14 @@ function objectGetLegalHold(authInfo, request, log, callback) {
|
||||||
if (versionId) {
|
if (versionId) {
|
||||||
log.trace('requested version is delete marker',
|
log.trace('requested version is delete marker',
|
||||||
{ method: 'objectGetLegalHold' });
|
{ method: 'objectGetLegalHold' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.MethodNotAllowed);
|
return next(errors.MethodNotAllowed);
|
||||||
}
|
}
|
||||||
log.trace('most recent version is delete marker',
|
log.trace('most recent version is delete marker',
|
||||||
{ method: 'objectGetLegalHold' });
|
{ method: 'objectGetLegalHold' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.NoSuchKey);
|
return next(errors.NoSuchKey);
|
||||||
}
|
}
|
||||||
if (!bucket.isObjectLockEnabled()) {
|
if (!bucket.isObjectLockEnabled()) {
|
||||||
|
|
|
@ -33,12 +33,14 @@ function objectGetRetention(authInfo, request, log, callback) {
|
||||||
}
|
}
|
||||||
const reqVersionId = decodedVidResult;
|
const reqVersionId = decodedVidResult;
|
||||||
|
|
||||||
|
// FIXME pass 'getDeleteMarker: true' option to set
|
||||||
|
// 'x-amz-delete-marker' header (see S3C-7592)
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'objectGetRetention',
|
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
|
requestType: 'objectGetRetention',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -61,10 +63,14 @@ function objectGetRetention(authInfo, request, log, callback) {
|
||||||
if (reqVersionId) {
|
if (reqVersionId) {
|
||||||
log.trace('requested version is delete marker',
|
log.trace('requested version is delete marker',
|
||||||
{ method: 'objectGetRetention' });
|
{ method: 'objectGetRetention' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.MethodNotAllowed);
|
return next(errors.MethodNotAllowed);
|
||||||
}
|
}
|
||||||
log.trace('most recent version is delete marker',
|
log.trace('most recent version is delete marker',
|
||||||
{ method: 'objectGetRetention' });
|
{ method: 'objectGetRetention' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.NoSuchKey);
|
return next(errors.NoSuchKey);
|
||||||
}
|
}
|
||||||
if (!bucket.isObjectLockEnabled()) {
|
if (!bucket.isObjectLockEnabled()) {
|
||||||
|
|
|
@ -34,12 +34,14 @@ function objectGetTagging(authInfo, request, log, callback) {
|
||||||
}
|
}
|
||||||
const reqVersionId = decodedVidResult;
|
const reqVersionId = decodedVidResult;
|
||||||
|
|
||||||
|
// FIXME pass 'getDeleteMarker: true' option to set
|
||||||
|
// 'x-amz-delete-marker' header (see S3C-7592)
|
||||||
const metadataValParams = {
|
const metadataValParams = {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'objectGetTagging',
|
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
|
requestType: 'objectGetTagging',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,9 +65,13 @@ function objectGetTagging(authInfo, request, log, callback) {
|
||||||
log.trace('requested version is delete marker',
|
log.trace('requested version is delete marker',
|
||||||
{ method: 'objectGetTagging' });
|
{ method: 'objectGetTagging' });
|
||||||
return next(errors.MethodNotAllowed);
|
return next(errors.MethodNotAllowed);
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
}
|
}
|
||||||
log.trace('most recent version is delete marker',
|
log.trace('most recent version is delete marker',
|
||||||
{ method: 'objectGetTagging' });
|
{ method: 'objectGetTagging' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.NoSuchKey);
|
return next(errors.NoSuchKey);
|
||||||
}
|
}
|
||||||
return next(null, bucket, objectMD);
|
return next(null, bucket, objectMD);
|
||||||
|
|
|
@ -45,6 +45,7 @@ function objectHead(authInfo, request, log, callback) {
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
versionId,
|
versionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
requestType: 'objectHead',
|
requestType: 'objectHead',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,6 +11,7 @@ const { decodeVersionId, getVersionIdResHeader }
|
||||||
= require('./apiUtils/object/versioning');
|
= require('./apiUtils/object/versioning');
|
||||||
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
|
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
|
||||||
const monitoring = require('../utilities/metrics');
|
const monitoring = require('../utilities/metrics');
|
||||||
|
const { config } = require('../Config');
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Format of xml request:
|
Format of xml request:
|
||||||
|
@ -85,8 +86,9 @@ function objectPutACL(authInfo, request, log, cb) {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'objectPutACL',
|
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
|
requestType: 'objectPutACL',
|
||||||
};
|
};
|
||||||
|
|
||||||
const possibleGrants = ['FULL_CONTROL', 'WRITE_ACP', 'READ', 'READ_ACP'];
|
const possibleGrants = ['FULL_CONTROL', 'WRITE_ACP', 'READ', 'READ_ACP'];
|
||||||
|
@ -123,13 +125,14 @@ function objectPutACL(authInfo, request, log, cb) {
|
||||||
if (objectMD.isDeleteMarker) {
|
if (objectMD.isDeleteMarker) {
|
||||||
log.trace('delete marker detected',
|
log.trace('delete marker detected',
|
||||||
{ method: 'objectPutACL' });
|
{ method: 'objectPutACL' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.MethodNotAllowed, bucket);
|
return next(errors.MethodNotAllowed, bucket);
|
||||||
}
|
}
|
||||||
return next(null, bucket, objectMD);
|
return next(null, bucket, objectMD);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
function parseAclFromXml(bucket, objectMD, next) {
|
function parseAclFromXml(bucket, objectMD, next) {
|
||||||
metadataValParams.versionId = objectMD.versionId;
|
|
||||||
// If not setting acl through headers, parse body
|
// If not setting acl through headers, parse body
|
||||||
let jsonGrants;
|
let jsonGrants;
|
||||||
let aclOwnerID;
|
let aclOwnerID;
|
||||||
|
@ -278,8 +281,13 @@ function objectPutACL(authInfo, request, log, cb) {
|
||||||
},
|
},
|
||||||
function addAclsToObjMD(bucket, objectMD, ACLParams, next) {
|
function addAclsToObjMD(bucket, objectMD, ACLParams, next) {
|
||||||
// Add acl's to object metadata
|
// Add acl's to object metadata
|
||||||
const params = metadataValParams.versionId ?
|
const params = {};
|
||||||
{ versionId: metadataValParams.versionId } : {};
|
if (objectMD.versionId) {
|
||||||
|
params.versionId = objectMD.versionId;
|
||||||
|
if (!config.nullVersionCompatMode) {
|
||||||
|
params.isNull = objectMD.isNull || false;
|
||||||
|
}
|
||||||
|
}
|
||||||
acl.addObjectACL(bucket, objectKey, objectMD,
|
acl.addObjectACL(bucket, objectKey, objectMD,
|
||||||
ACLParams, params, log, err => next(err, bucket, objectMD));
|
ACLParams, params, log, err => next(err, bucket, objectMD));
|
||||||
},
|
},
|
||||||
|
|
|
@ -44,6 +44,7 @@ function objectPutCopyPart(authInfo, request, sourceBucket,
|
||||||
bucketName: sourceBucket,
|
bucketName: sourceBucket,
|
||||||
objectKey: sourceObject,
|
objectKey: sourceObject,
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
requestType: 'objectGet',
|
requestType: 'objectGet',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,6 +8,7 @@ const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
|
const { metadataValidateBucketAndObj } = require('../metadata/metadataUtils');
|
||||||
const { pushMetric } = require('../utapi/utilities');
|
const { pushMetric } = require('../utapi/utilities');
|
||||||
|
const { config } = require('../Config');
|
||||||
|
|
||||||
const { parseLegalHoldXml } = s3middleware.objectLegalHold;
|
const { parseLegalHoldXml } = s3middleware.objectLegalHold;
|
||||||
|
|
||||||
|
@ -40,8 +41,9 @@ function objectPutLegalHold(authInfo, request, log, callback) {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'objectPutLegalHold',
|
|
||||||
versionId,
|
versionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
|
requestType: 'objectPutLegalHold',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,6 +65,8 @@ function objectPutLegalHold(authInfo, request, log, callback) {
|
||||||
if (objectMD.isDeleteMarker) {
|
if (objectMD.isDeleteMarker) {
|
||||||
log.trace('version is a delete marker',
|
log.trace('version is a delete marker',
|
||||||
{ method: 'objectPutLegalHold' });
|
{ method: 'objectPutLegalHold' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.MethodNotAllowed, bucket);
|
return next(errors.MethodNotAllowed, bucket);
|
||||||
}
|
}
|
||||||
if (!bucket.isObjectLockEnabled()) {
|
if (!bucket.isObjectLockEnabled()) {
|
||||||
|
@ -82,8 +86,13 @@ function objectPutLegalHold(authInfo, request, log, callback) {
|
||||||
(bucket, legalHold, objectMD, next) => {
|
(bucket, legalHold, objectMD, next) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
objectMD.legalHold = legalHold;
|
objectMD.legalHold = legalHold;
|
||||||
const params = objectMD.versionId ?
|
const params = {};
|
||||||
{ versionId: objectMD.versionId } : {};
|
if (objectMD.versionId) {
|
||||||
|
params.versionId = objectMD.versionId;
|
||||||
|
if (!config.nullVersionCompatMode) {
|
||||||
|
params.isNull = objectMD.isNull || false;
|
||||||
|
}
|
||||||
|
}
|
||||||
const replicationInfo = getReplicationInfo(objectKey, bucket, true,
|
const replicationInfo = getReplicationInfo(objectKey, bucket, true,
|
||||||
0, REPLICATION_ACTION, objectMD);
|
0, REPLICATION_ACTION, objectMD);
|
||||||
if (replicationInfo) {
|
if (replicationInfo) {
|
||||||
|
|
|
@ -10,6 +10,7 @@ const { pushMetric } = require('../utapi/utilities');
|
||||||
const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
|
const getReplicationInfo = require('./apiUtils/object/getReplicationInfo');
|
||||||
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
|
const { config } = require('../Config');
|
||||||
|
|
||||||
const { parseRetentionXml } = s3middleware.retention;
|
const { parseRetentionXml } = s3middleware.retention;
|
||||||
const REPLICATION_ACTION = 'PUT_RETENTION';
|
const REPLICATION_ACTION = 'PUT_RETENTION';
|
||||||
|
@ -41,8 +42,9 @@ function objectPutRetention(authInfo, request, log, callback) {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'objectPutRetention',
|
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
|
requestType: 'objectPutRetention',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -64,6 +66,8 @@ function objectPutRetention(authInfo, request, log, callback) {
|
||||||
if (objectMD.isDeleteMarker) {
|
if (objectMD.isDeleteMarker) {
|
||||||
log.trace('version is a delete marker',
|
log.trace('version is a delete marker',
|
||||||
{ method: 'objectPutRetention' });
|
{ method: 'objectPutRetention' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.MethodNotAllowed, bucket);
|
return next(errors.MethodNotAllowed, bucket);
|
||||||
}
|
}
|
||||||
if (!bucket.isObjectLockEnabled()) {
|
if (!bucket.isObjectLockEnabled()) {
|
||||||
|
@ -112,8 +116,13 @@ function objectPutRetention(authInfo, request, log, callback) {
|
||||||
/* eslint-disable no-param-reassign */
|
/* eslint-disable no-param-reassign */
|
||||||
objectMD.retentionMode = retentionInfo.mode;
|
objectMD.retentionMode = retentionInfo.mode;
|
||||||
objectMD.retentionDate = retentionInfo.date;
|
objectMD.retentionDate = retentionInfo.date;
|
||||||
const params = objectMD.versionId ?
|
const params = {};
|
||||||
{ versionId: objectMD.versionId } : {};
|
if (objectMD.versionId) {
|
||||||
|
params.versionId = objectMD.versionId;
|
||||||
|
if (!config.nullVersionCompatMode) {
|
||||||
|
params.isNull = objectMD.isNull || false;
|
||||||
|
}
|
||||||
|
}
|
||||||
const replicationInfo = getReplicationInfo(objectKey, bucket, true,
|
const replicationInfo = getReplicationInfo(objectKey, bucket, true,
|
||||||
0, REPLICATION_ACTION, objectMD);
|
0, REPLICATION_ACTION, objectMD);
|
||||||
if (replicationInfo) {
|
if (replicationInfo) {
|
||||||
|
|
|
@ -12,6 +12,7 @@ const collectCorsHeaders = require('../utilities/collectCorsHeaders');
|
||||||
const metadata = require('../metadata/wrapper');
|
const metadata = require('../metadata/wrapper');
|
||||||
const { data } = require('../data/wrapper');
|
const { data } = require('../data/wrapper');
|
||||||
const { parseTagXml } = s3middleware.tagging;
|
const { parseTagXml } = s3middleware.tagging;
|
||||||
|
const { config } = require('../Config');
|
||||||
const REPLICATION_ACTION = 'PUT_TAGGING';
|
const REPLICATION_ACTION = 'PUT_TAGGING';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -42,8 +43,9 @@ function objectPutTagging(authInfo, request, log, callback) {
|
||||||
authInfo,
|
authInfo,
|
||||||
bucketName,
|
bucketName,
|
||||||
objectKey,
|
objectKey,
|
||||||
requestType: 'objectPutTagging',
|
|
||||||
versionId: reqVersionId,
|
versionId: reqVersionId,
|
||||||
|
getDeleteMarker: true,
|
||||||
|
requestType: 'objectPutTagging',
|
||||||
request,
|
request,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -65,6 +67,8 @@ function objectPutTagging(authInfo, request, log, callback) {
|
||||||
if (objectMD.isDeleteMarker) {
|
if (objectMD.isDeleteMarker) {
|
||||||
log.trace('version is a delete marker',
|
log.trace('version is a delete marker',
|
||||||
{ method: 'objectPutTagging' });
|
{ method: 'objectPutTagging' });
|
||||||
|
// FIXME wee should return a `x-amz-delete-marker: true` header,
|
||||||
|
// see S3C-7592
|
||||||
return next(errors.MethodNotAllowed, bucket);
|
return next(errors.MethodNotAllowed, bucket);
|
||||||
}
|
}
|
||||||
return next(null, bucket, objectMD);
|
return next(null, bucket, objectMD);
|
||||||
|
@ -77,8 +81,13 @@ function objectPutTagging(authInfo, request, log, callback) {
|
||||||
(bucket, tags, objectMD, next) => {
|
(bucket, tags, objectMD, next) => {
|
||||||
// eslint-disable-next-line no-param-reassign
|
// eslint-disable-next-line no-param-reassign
|
||||||
objectMD.tags = tags;
|
objectMD.tags = tags;
|
||||||
const params = objectMD.versionId ? { versionId:
|
const params = {};
|
||||||
objectMD.versionId } : {};
|
if (objectMD.versionId) {
|
||||||
|
params.versionId = objectMD.versionId;
|
||||||
|
if (!config.nullVersionCompatMode) {
|
||||||
|
params.isNull = objectMD.isNull || false;
|
||||||
|
}
|
||||||
|
}
|
||||||
const replicationInfo = getReplicationInfo(objectKey, bucket, true,
|
const replicationInfo = getReplicationInfo(objectKey, bucket, true,
|
||||||
0, REPLICATION_ACTION, objectMD);
|
0, REPLICATION_ACTION, objectMD);
|
||||||
if (replicationInfo) {
|
if (replicationInfo) {
|
||||||
|
|
|
@ -66,7 +66,7 @@ function getNullVersionFromMaster(bucketName, objectKey, log, cb) {
|
||||||
*/
|
*/
|
||||||
function metadataGetObject(bucketName, objectKey, versionId, log, cb) {
|
function metadataGetObject(bucketName, objectKey, versionId, log, cb) {
|
||||||
// versionId may be 'null', which asks metadata to fetch the null key specifically
|
// versionId may be 'null', which asks metadata to fetch the null key specifically
|
||||||
const options = { versionId };
|
const options = { versionId, getDeleteMarker: true };
|
||||||
return metadata.getObjectMD(bucketName, objectKey, options, log,
|
return metadata.getObjectMD(bucketName, objectKey, options, log,
|
||||||
(err, objMD) => {
|
(err, objMD) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -140,11 +140,15 @@ function validateBucket(bucket, params, log) {
|
||||||
* @return {undefined} - and call callback with params err, bucket md
|
* @return {undefined} - and call callback with params err, bucket md
|
||||||
*/
|
*/
|
||||||
function metadataValidateBucketAndObj(params, log, callback) {
|
function metadataValidateBucketAndObj(params, log, callback) {
|
||||||
const { authInfo, bucketName, objectKey, versionId, requestType, request } = params;
|
const { authInfo, bucketName, objectKey, versionId, getDeleteMarker,
|
||||||
|
requestType, request } = params;
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
next => {
|
next => {
|
||||||
// versionId may be 'null', which asks metadata to fetch the null key specifically
|
// versionId may be 'null', which asks metadata to fetch the null key specifically
|
||||||
const getOptions = { versionId };
|
const getOptions = { versionId };
|
||||||
|
if (getDeleteMarker) {
|
||||||
|
getOptions.getDeleteMarker = true;
|
||||||
|
}
|
||||||
return metadata.getBucketAndObjectMD(bucketName, objectKey, getOptions, log, next);
|
return metadata.getBucketAndObjectMD(bucketName, objectKey, getOptions, log, next);
|
||||||
},
|
},
|
||||||
(getResult, next) => {
|
(getResult, next) => {
|
||||||
|
|
|
@ -7,6 +7,7 @@ const ObjectMD = require('arsenal').models.ObjectMD;
|
||||||
const BucketInfo = require('arsenal').models.BucketInfo;
|
const BucketInfo = require('arsenal').models.BucketInfo;
|
||||||
const acl = require('./metadata/acl');
|
const acl = require('./metadata/acl');
|
||||||
const constants = require('../constants');
|
const constants = require('../constants');
|
||||||
|
const { config } = require('./Config');
|
||||||
const { data } = require('./data/wrapper');
|
const { data } = require('./data/wrapper');
|
||||||
const metadata = require('./metadata/wrapper');
|
const metadata = require('./metadata/wrapper');
|
||||||
const logger = require('./utilities/logger');
|
const logger = require('./utilities/logger');
|
||||||
|
@ -97,7 +98,7 @@ const services = {
|
||||||
lastModifiedDate, versioning, versionId, uploadId,
|
lastModifiedDate, versioning, versionId, uploadId,
|
||||||
tagging, taggingCopy, replicationInfo, defaultRetention,
|
tagging, taggingCopy, replicationInfo, defaultRetention,
|
||||||
dataStoreName, retentionMode, retentionDate, legalHold,
|
dataStoreName, retentionMode, retentionDate, legalHold,
|
||||||
originOp, oldReplayId } = params;
|
originOp, oldReplayId, deleteNullKey } = params;
|
||||||
log.trace('storing object in metadata');
|
log.trace('storing object in metadata');
|
||||||
assert.strictEqual(typeof bucketName, 'string');
|
assert.strictEqual(typeof bucketName, 'string');
|
||||||
const md = new ObjectMD();
|
const md = new ObjectMD();
|
||||||
|
@ -165,8 +166,15 @@ const services = {
|
||||||
options.oldReplayId = oldReplayId;
|
options.oldReplayId = oldReplayId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (deleteNullKey) {
|
||||||
|
options.deleteNullKey = deleteNullKey;
|
||||||
|
}
|
||||||
|
|
||||||
// information to store about the version and the null version id
|
// information to store about the version and the null version id
|
||||||
// in the object metadata
|
// in the object metadata
|
||||||
|
|
||||||
|
// NOTE nullVersionId and nullUploadId are only maintained in
|
||||||
|
// v0 metadata compatibility mode
|
||||||
const { isNull, nullVersionId, nullUploadId, isDeleteMarker } = params;
|
const { isNull, nullVersionId, nullUploadId, isDeleteMarker } = params;
|
||||||
md.setIsNull(isNull)
|
md.setIsNull(isNull)
|
||||||
.setNullVersionId(nullVersionId)
|
.setNullVersionId(nullVersionId)
|
||||||
|
@ -175,6 +183,9 @@ const services = {
|
||||||
if (versionId && versionId !== 'null') {
|
if (versionId && versionId !== 'null') {
|
||||||
md.setVersionId(versionId);
|
md.setVersionId(versionId);
|
||||||
}
|
}
|
||||||
|
if (isNull && !config.nullVersionCompatMode) {
|
||||||
|
md.setIsNull2(true);
|
||||||
|
}
|
||||||
if (taggingCopy) {
|
if (taggingCopy) {
|
||||||
// If copying tags to an object (taggingCopy) we do not
|
// If copying tags to an object (taggingCopy) we do not
|
||||||
// need to validate them again
|
// need to validate them again
|
||||||
|
|
|
@ -20,7 +20,7 @@
|
||||||
"homepage": "https://github.com/scality/S3#readme",
|
"homepage": "https://github.com/scality/S3#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/joi": "^17.1.0",
|
"@hapi/joi": "^17.1.0",
|
||||||
"arsenal": "git+https://github.com/scality/arsenal#7.10.46",
|
"arsenal": "git+https://github.com/scality/arsenal#4c39ef64",
|
||||||
"async": "~2.5.0",
|
"async": "~2.5.0",
|
||||||
"aws-sdk": "2.905.0",
|
"aws-sdk": "2.905.0",
|
||||||
"azure-storage": "^2.1.0",
|
"azure-storage": "^2.1.0",
|
||||||
|
|
|
@ -82,8 +82,17 @@ function enableVersioningThenPutObject(bucket, object, callback) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** createDualNullVersion - create a null version that is stored in metadata
|
/** createDualNullVersion
|
||||||
|
*
|
||||||
|
* - PREVIOUSLY: created a null version that was stored in metadata
|
||||||
* both in the master version and a separate version
|
* both in the master version and a separate version
|
||||||
|
*
|
||||||
|
* - CURRENTLY: only one null version key is present in metadata
|
||||||
|
* after the second put, as the first null key is cleaned up
|
||||||
|
*
|
||||||
|
* Even though there is only one key at the end, it is still useful
|
||||||
|
* to keep this function for regression testing
|
||||||
|
*
|
||||||
* @param {AWS.S3} s3 - aws sdk s3 instance
|
* @param {AWS.S3} s3 - aws sdk s3 instance
|
||||||
* @param {string} bucketName - name of bucket in versioning suspended state
|
* @param {string} bucketName - name of bucket in versioning suspended state
|
||||||
* @param {string} keyName - name of key
|
* @param {string} keyName - name of key
|
||||||
|
|
|
@ -107,6 +107,19 @@ describe('GET object legal hold', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return NoSuchKey if latest version is delete marker', done => {
|
||||||
|
s3.deleteObject({ Bucket: bucket, Key: key }, err => {
|
||||||
|
assert.ifError(err);
|
||||||
|
s3.getObjectLegalHold({
|
||||||
|
Bucket: bucket,
|
||||||
|
Key: key,
|
||||||
|
}, err => {
|
||||||
|
checkError(err, 'NoSuchKey', 404);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return InvalidRequest error getting legal hold of object ' +
|
it('should return InvalidRequest error getting legal hold of object ' +
|
||||||
'inside object lock disabled bucket', done => {
|
'inside object lock disabled bucket', done => {
|
||||||
s3.getObjectLegalHold({
|
s3.getObjectLegalHold({
|
||||||
|
|
|
@ -688,7 +688,7 @@ describe('Object Version Copy', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return NoSuchKey if attempt to copy version with ' +
|
it('should return NoSuchKey if attempt to copy version with ' +
|
||||||
' delete marker', done => {
|
'delete marker', done => {
|
||||||
s3.deleteObject({
|
s3.deleteObject({
|
||||||
Bucket: sourceBucketName,
|
Bucket: sourceBucketName,
|
||||||
Key: sourceObjName,
|
Key: sourceObjName,
|
||||||
|
|
|
@ -138,7 +138,7 @@ describe('aws-node-sdk test delete object', () => {
|
||||||
// delete bucket after testing
|
// delete bucket after testing
|
||||||
after(done => {
|
after(done => {
|
||||||
removeAllVersions({ Bucket: bucket }, err => {
|
removeAllVersions({ Bucket: bucket }, err => {
|
||||||
if (err.code === 'NoSuchBucket') {
|
if (err && err.code === 'NoSuchBucket') {
|
||||||
return done();
|
return done();
|
||||||
} else if (err) {
|
} else if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
|
@ -348,7 +348,7 @@ describe('aws-node-sdk test delete object', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete the versionned object', done => {
|
it('should delete the versioned object', done => {
|
||||||
const version = versionIds.shift();
|
const version = versionIds.shift();
|
||||||
s3.deleteObject({
|
s3.deleteObject({
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
|
@ -370,12 +370,15 @@ describe('aws-node-sdk test delete object', () => {
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
VersionId: version,
|
VersionId: version,
|
||||||
}, (err, res) => {
|
}, function test(err, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return done(err);
|
return done(err);
|
||||||
}
|
}
|
||||||
assert.strictEqual(res.VersionId, version);
|
assert.strictEqual(res.VersionId, version);
|
||||||
assert.equal(res.DeleteMarker, true);
|
assert.equal(res.DeleteMarker, true);
|
||||||
|
// deleting a delete marker should set the x-amz-delete-marker header
|
||||||
|
const headers = this.httpResponse.headers;
|
||||||
|
assert.strictEqual(headers['x-amz-delete-marker'], 'true');
|
||||||
return done();
|
return done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -131,7 +131,7 @@ describe('Delete object tagging with versioning', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return 405 MethodNotAllowed deletting tag set without ' +
|
it('should return 405 MethodNotAllowed deleting tag set without ' +
|
||||||
'version id if version specified is a delete marker', done => {
|
'version id if version specified is a delete marker', done => {
|
||||||
async.waterfall([
|
async.waterfall([
|
||||||
next => s3.putBucketVersioning({ Bucket: bucketName,
|
next => s3.putBucketVersioning({ Bucket: bucketName,
|
||||||
|
|
|
@ -137,9 +137,11 @@ describe('get behavior on versioning-enabled bucket', () => {
|
||||||
s3.getObject({
|
s3.getObject({
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
VersionId: this.test.deleteVersionId },
|
VersionId: this.test.deleteVersionId,
|
||||||
err => {
|
}, function test1(err) {
|
||||||
_assertError(err, 405, 'MethodNotAllowed');
|
_assertError(err, 405, 'MethodNotAllowed');
|
||||||
|
const headers = this.httpResponse.headers;
|
||||||
|
assert.strictEqual(headers['x-amz-delete-marker'], 'true');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -148,9 +150,11 @@ describe('get behavior on versioning-enabled bucket', () => {
|
||||||
'latest version is a delete marker', done => {
|
'latest version is a delete marker', done => {
|
||||||
s3.getObject({
|
s3.getObject({
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key },
|
Key: key,
|
||||||
err => {
|
}, function test2(err) {
|
||||||
_assertError(err, 404, 'NoSuchKey');
|
_assertError(err, 404, 'NoSuchKey');
|
||||||
|
const headers = this.httpResponse.headers;
|
||||||
|
assert.strictEqual(headers['x-amz-delete-marker'], 'true');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -175,9 +179,11 @@ describe('get behavior on versioning-enabled bucket', () => {
|
||||||
s3.getObject({
|
s3.getObject({
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key,
|
Key: key,
|
||||||
VersionId: this.test.deleteVersionId },
|
VersionId: this.test.deleteVersionId,
|
||||||
err => {
|
}, function test3(err) {
|
||||||
_assertError(err, 405, 'MethodNotAllowed');
|
_assertError(err, 405, 'MethodNotAllowed');
|
||||||
|
const headers = this.httpResponse.headers;
|
||||||
|
assert.strictEqual(headers['x-amz-delete-marker'], 'true');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -200,9 +206,11 @@ describe('get behavior on versioning-enabled bucket', () => {
|
||||||
done => {
|
done => {
|
||||||
s3.getObject({
|
s3.getObject({
|
||||||
Bucket: bucket,
|
Bucket: bucket,
|
||||||
Key: key },
|
Key: key,
|
||||||
err => {
|
}, function test4(err) {
|
||||||
_assertError(err, 404, 'NoSuchKey');
|
_assertError(err, 404, 'NoSuchKey');
|
||||||
|
const headers = this.httpResponse.headers;
|
||||||
|
assert.strictEqual(headers['x-amz-delete-marker'], 'true');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -218,7 +218,7 @@ describe('put and get object with versioning', function testSuite() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create new versions but still keep nullVersionId',
|
it('should create new versions but still keep the null version',
|
||||||
done => {
|
done => {
|
||||||
const versionIds = [];
|
const versionIds = [];
|
||||||
const params = { Bucket: bucket, Key: key };
|
const params = { Bucket: bucket, Key: key };
|
||||||
|
|
|
@ -24,6 +24,25 @@ describe('versioning helpers', () => {
|
||||||
isNull: true,
|
isNull: true,
|
||||||
versionId: '',
|
versionId: '',
|
||||||
},
|
},
|
||||||
|
delOptions: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'null',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
delOptions: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'null',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -41,6 +60,25 @@ describe('versioning helpers', () => {
|
||||||
isNull: true,
|
isNull: true,
|
||||||
versionId: '',
|
versionId: '',
|
||||||
},
|
},
|
||||||
|
delOptions: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'null',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
delOptions: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'null',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -59,10 +97,29 @@ describe('versioning helpers', () => {
|
||||||
isNull: true,
|
isNull: true,
|
||||||
versionId: '',
|
versionId: '',
|
||||||
},
|
},
|
||||||
|
delOptions: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'null',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
delOptions: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'null',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'prior null object version exists',
|
description: 'prior legacy null object version exists',
|
||||||
objMD: {
|
objMD: {
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
isNull: true,
|
isNull: true,
|
||||||
|
@ -70,13 +127,15 @@ describe('versioning helpers', () => {
|
||||||
versioningEnabledExpectedRes: {
|
versioningEnabledExpectedRes: {
|
||||||
options: {
|
options: {
|
||||||
versioning: true,
|
versioning: true,
|
||||||
extraMD: {
|
|
||||||
nullVersionId: 'vnull',
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// instruct to first copy the null version onto a
|
// instruct to first copy the null version onto a
|
||||||
// newly created version key preserving the version ID
|
// newly created null key with version ID in its metadata
|
||||||
nullVersionId: 'vnull',
|
nullVersionId: 'vnull',
|
||||||
|
// delete possibly existing null versioned key
|
||||||
|
// that is identical to the null master
|
||||||
|
delOptions: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
versioningSuspendedExpectedRes: {
|
versioningSuspendedExpectedRes: {
|
||||||
options: {
|
options: {
|
||||||
|
@ -87,15 +146,96 @@ describe('versioning helpers', () => {
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
extraMD: {
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// instruct to first copy the null version onto a
|
||||||
|
// newly created version key preserving the version ID
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
delOptions: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'prior MPU object null version exists',
|
description: 'prior non-legacy null object version exists',
|
||||||
|
objMD: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
isNull: true,
|
||||||
|
isNull2: true, // flag marking that it's a non-legacy null version
|
||||||
|
},
|
||||||
|
versioningEnabledExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
},
|
||||||
|
// instruct to first copy the null version onto a
|
||||||
|
// newly created null key with version ID in its metadata
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
versioningSuspendedExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
extraMD: {
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// instruct to first copy the null version onto a
|
||||||
|
// newly created version key preserving the version ID
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'prior MPU object legacy null version exists',
|
||||||
objMD: {
|
objMD: {
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
isNull: true,
|
isNull: true,
|
||||||
uploadId: 'fooUploadId',
|
uploadId: 'fooUploadId',
|
||||||
},
|
},
|
||||||
versioningEnabledExpectedRes: {
|
versioningEnabledExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
},
|
||||||
|
// instruct to first copy the null version onto a
|
||||||
|
// newly created null key with version ID in its metadata
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
// delete possibly existing null versioned key
|
||||||
|
// that is identical to the null master
|
||||||
|
delOptions: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningSuspendedExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
delOptions: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
options: {
|
options: {
|
||||||
versioning: true,
|
versioning: true,
|
||||||
extraMD: {
|
extraMD: {
|
||||||
|
@ -107,30 +247,66 @@ describe('versioning helpers', () => {
|
||||||
// newly created version key preserving the version ID
|
// newly created version key preserving the version ID
|
||||||
nullVersionId: 'vnull',
|
nullVersionId: 'vnull',
|
||||||
},
|
},
|
||||||
versioningSuspendedExpectedRes: {
|
versioningSuspendedCompatExpectedRes: {
|
||||||
options: {
|
options: {
|
||||||
isNull: true,
|
isNull: true,
|
||||||
versionId: '',
|
versionId: '',
|
||||||
},
|
},
|
||||||
delOptions: {
|
delOptions: {
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
replayId: 'fooUploadId',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description:
|
description: 'prior MPU object non-legacy null version exists',
|
||||||
'prior object exists, put before versioning was first enabled',
|
objMD: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
isNull: true,
|
||||||
|
isNull2: true, // flag marking that it's a non-legacy null version
|
||||||
|
uploadId: 'fooUploadId',
|
||||||
|
},
|
||||||
|
versioningEnabledExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
},
|
||||||
|
// instruct to first copy the null version onto a
|
||||||
|
// newly created null key with version ID in its metadata
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
versioningSuspendedExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
extraMD: {
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
nullUploadId: 'fooUploadId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// instruct to first copy the null version onto a
|
||||||
|
// newly created version key preserving the version ID
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'prior object exists, put before versioning was first enabled',
|
||||||
objMD: {},
|
objMD: {},
|
||||||
versioningEnabledExpectedRes: {
|
versioningEnabledExpectedRes: {
|
||||||
options: {
|
options: {
|
||||||
versioning: true,
|
versioning: true,
|
||||||
extraMD: {
|
|
||||||
nullVersionId: INF_VID,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
// instruct to first copy the null version onto a
|
// instruct to first copy the null version onto a
|
||||||
// newly created version key as the oldest version
|
// newly created null key as the oldest version
|
||||||
nullVersionId: INF_VID,
|
nullVersionId: INF_VID,
|
||||||
},
|
},
|
||||||
versioningSuspendedExpectedRes: {
|
versioningSuspendedExpectedRes: {
|
||||||
|
@ -139,14 +315,44 @@ describe('versioning helpers', () => {
|
||||||
versionId: '',
|
versionId: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
extraMD: {
|
||||||
|
nullVersionId: INF_VID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// instruct to first copy the null version onto a
|
||||||
|
// newly created version key as the oldest version
|
||||||
|
nullVersionId: INF_VID,
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'prior MPU object exists, put before versioning ' +
|
description: 'prior MPU object exists, put before versioning was first enabled',
|
||||||
'was first enabled',
|
|
||||||
objMD: {
|
objMD: {
|
||||||
uploadId: 'fooUploadId',
|
uploadId: 'fooUploadId',
|
||||||
},
|
},
|
||||||
versioningEnabledExpectedRes: {
|
versioningEnabledExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
},
|
||||||
|
// instruct to first copy the null version onto a
|
||||||
|
// newly created null key as the oldest version
|
||||||
|
nullVersionId: INF_VID,
|
||||||
|
},
|
||||||
|
versioningSuspendedExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
options: {
|
options: {
|
||||||
versioning: true,
|
versioning: true,
|
||||||
extraMD: {
|
extraMD: {
|
||||||
|
@ -158,7 +364,7 @@ describe('versioning helpers', () => {
|
||||||
// newly created version key as the oldest version
|
// newly created version key as the oldest version
|
||||||
nullVersionId: INF_VID,
|
nullVersionId: INF_VID,
|
||||||
},
|
},
|
||||||
versioningSuspendedExpectedRes: {
|
versioningSuspendedCompatExpectedRes: {
|
||||||
options: {
|
options: {
|
||||||
isNull: true,
|
isNull: true,
|
||||||
versionId: '',
|
versionId: '',
|
||||||
|
@ -166,8 +372,7 @@ describe('versioning helpers', () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description:
|
description: 'prior non-null object version exists with ref to null version',
|
||||||
'prior non-null object version exists with ref to null version',
|
|
||||||
objMD: {
|
objMD: {
|
||||||
versionId: 'v1',
|
versionId: 'v1',
|
||||||
nullVersionId: 'vnull',
|
nullVersionId: 'vnull',
|
||||||
|
@ -185,14 +390,34 @@ describe('versioning helpers', () => {
|
||||||
isNull: true,
|
isNull: true,
|
||||||
versionId: '',
|
versionId: '',
|
||||||
},
|
},
|
||||||
|
// backward-compat: delete old null version key
|
||||||
delOptions: {
|
delOptions: {
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
|
deleteData: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
extraMD: {
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
// backward-compat: delete old null version key
|
||||||
|
delOptions: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
deleteData: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'prior MPU object non-null version exists with ' +
|
description: 'prior MPU object non-null version exists with ref to null version',
|
||||||
'ref to null version',
|
|
||||||
objMD: {
|
objMD: {
|
||||||
versionId: 'v1',
|
versionId: 'v1',
|
||||||
uploadId: 'fooUploadId',
|
uploadId: 'fooUploadId',
|
||||||
|
@ -211,14 +436,34 @@ describe('versioning helpers', () => {
|
||||||
isNull: true,
|
isNull: true,
|
||||||
versionId: '',
|
versionId: '',
|
||||||
},
|
},
|
||||||
|
// backward-compat: delete old null version key
|
||||||
delOptions: {
|
delOptions: {
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
|
deleteData: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
extraMD: {
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
// backward-compat: delete old null version key
|
||||||
|
delOptions: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
deleteData: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'prior object non-null version exists with ' +
|
description: 'prior object non-null version exists with ref to MPU null version',
|
||||||
'ref to MPU null version',
|
|
||||||
objMD: {
|
objMD: {
|
||||||
versionId: 'v1',
|
versionId: 'v1',
|
||||||
nullVersionId: 'vnull',
|
nullVersionId: 'vnull',
|
||||||
|
@ -238,21 +483,48 @@ describe('versioning helpers', () => {
|
||||||
isNull: true,
|
isNull: true,
|
||||||
versionId: '',
|
versionId: '',
|
||||||
},
|
},
|
||||||
|
// backward-compat: delete old null version key
|
||||||
delOptions: {
|
delOptions: {
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
replayId: 'nullFooUploadId',
|
replayId: 'nullFooUploadId',
|
||||||
|
deleteData: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningEnabledCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
versioning: true,
|
||||||
|
extraMD: {
|
||||||
|
nullVersionId: 'vnull',
|
||||||
|
nullUploadId: 'nullFooUploadId',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
versioningSuspendedCompatExpectedRes: {
|
||||||
|
options: {
|
||||||
|
isNull: true,
|
||||||
|
versionId: '',
|
||||||
|
},
|
||||||
|
// backward-compat: delete old null version key
|
||||||
|
delOptions: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
replayId: 'nullFooUploadId',
|
||||||
|
deleteData: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
].forEach(testCase =>
|
].forEach(testCase =>
|
||||||
|
[false, true].forEach(nullVersionCompatMode =>
|
||||||
['Enabled', 'Suspended'].forEach(versioningStatus => it(
|
['Enabled', 'Suspended'].forEach(versioningStatus => it(
|
||||||
`${testCase.description}, versioning Status=${versioningStatus}`,
|
`${testCase.description}${nullVersionCompatMode ? ' (null compat)' : ''}` +
|
||||||
|
`, versioning Status=${versioningStatus}`,
|
||||||
() => {
|
() => {
|
||||||
const mst = getMasterState(testCase.objMD);
|
const mst = getMasterState(testCase.objMD);
|
||||||
const res = processVersioningState(mst, versioningStatus);
|
const res = processVersioningState(mst, versioningStatus, nullVersionCompatMode);
|
||||||
const expectedRes = testCase[`versioning${versioningStatus}ExpectedRes`];
|
const resultName = `versioning${versioningStatus}` +
|
||||||
|
`${nullVersionCompatMode ? 'Compat' : ''}ExpectedRes`;
|
||||||
|
const expectedRes = testCase[resultName];
|
||||||
assert.deepStrictEqual(res, expectedRes);
|
assert.deepStrictEqual(res, expectedRes);
|
||||||
})));
|
}))));
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('preprocessingVersioningDelete', () => {
|
describe('preprocessingVersioningDelete', () => {
|
||||||
|
@ -263,6 +535,7 @@ describe('versioning helpers', () => {
|
||||||
versionId: 'v1',
|
versionId: 'v1',
|
||||||
},
|
},
|
||||||
expectedRes: {},
|
expectedRes: {},
|
||||||
|
expectedResCompat: {},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'delete non-null object version',
|
description: 'delete non-null object version',
|
||||||
|
@ -273,10 +546,15 @@ describe('versioning helpers', () => {
|
||||||
expectedRes: {
|
expectedRes: {
|
||||||
deleteData: true,
|
deleteData: true,
|
||||||
versionId: 'v1',
|
versionId: 'v1',
|
||||||
|
isNull: false,
|
||||||
|
},
|
||||||
|
expectedResCompat: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'v1',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'delete null object version',
|
description: 'delete legacy null object version',
|
||||||
objMD: {
|
objMD: {
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
isNull: true,
|
isNull: true,
|
||||||
|
@ -286,6 +564,29 @@ describe('versioning helpers', () => {
|
||||||
deleteData: true,
|
deleteData: true,
|
||||||
versionId: 'vnull',
|
versionId: 'vnull',
|
||||||
},
|
},
|
||||||
|
expectedResCompat: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'vnull',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'delete null object version in null key',
|
||||||
|
objMD: {
|
||||||
|
versionId: 'vnull',
|
||||||
|
isNull: true,
|
||||||
|
isNull2: true,
|
||||||
|
},
|
||||||
|
reqVersionId: 'null',
|
||||||
|
expectedRes: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'vnull',
|
||||||
|
isNull: true,
|
||||||
|
},
|
||||||
|
expectedResCompat: {
|
||||||
|
deleteData: true,
|
||||||
|
versionId: 'vnull',
|
||||||
|
isNull: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
description: 'delete object put before versioning was first enabled',
|
description: 'delete object put before versioning was first enabled',
|
||||||
|
@ -293,15 +594,26 @@ describe('versioning helpers', () => {
|
||||||
reqVersionId: 'null',
|
reqVersionId: 'null',
|
||||||
expectedRes: {
|
expectedRes: {
|
||||||
deleteData: true,
|
deleteData: true,
|
||||||
|
// no 'isNull' parameter, as there is no 'versionId', the code will
|
||||||
|
// not use the version-specific DELETE route but a regular DELETE
|
||||||
|
},
|
||||||
|
expectedResCompat: {
|
||||||
|
deleteData: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
].forEach(testCase => it(testCase.description, () => {
|
].forEach(testCase =>
|
||||||
|
[false, true].forEach(nullVersionCompatMode =>
|
||||||
|
it(`${testCase.description}${nullVersionCompatMode ? ' (null compat)' : ''}`,
|
||||||
|
() => {
|
||||||
const mockBucketMD = {
|
const mockBucketMD = {
|
||||||
getVersioningConfiguration: () => ({ Status: 'Enabled' }),
|
getVersioningConfiguration: () => ({ Status: 'Enabled' }),
|
||||||
};
|
};
|
||||||
const options = preprocessingVersioningDelete(
|
const options = preprocessingVersioningDelete(
|
||||||
'foobucket', mockBucketMD, testCase.objMD, testCase.reqVersionId);
|
'foobucket', mockBucketMD, testCase.objMD, testCase.reqVersionId,
|
||||||
assert.deepStrictEqual(options, testCase.expectedRes);
|
nullVersionCompatMode);
|
||||||
}));
|
const expectedResAttr = nullVersionCompatMode ?
|
||||||
|
'expectedResCompat' : 'expectedRes';
|
||||||
|
assert.deepStrictEqual(options, testCase[expectedResAttr]);
|
||||||
|
})));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1937,13 +1937,13 @@ describe('complete mpu with versioning', () => {
|
||||||
err => next(err, testUploadId));
|
err => next(err, testUploadId));
|
||||||
},
|
},
|
||||||
(testUploadId, next) => {
|
(testUploadId, next) => {
|
||||||
const origDeleteObject = metadataBackend.deleteObject;
|
const origPutObject = metadataBackend.putObject;
|
||||||
metadataBackend.deleteObject =
|
metadataBackend.putObject =
|
||||||
(bucketName, objName, params, log, cb) => {
|
(putBucketName, objName, objVal, params, log, cb) => {
|
||||||
assert.strictEqual(params.replayId, testUploadId);
|
assert.strictEqual(params.oldReplayId, testUploadId);
|
||||||
metadataBackend.deleteObject = origDeleteObject;
|
metadataBackend.putObject = origPutObject;
|
||||||
metadataBackend.deleteObject(
|
origPutObject(
|
||||||
bucketName, objName, params, log, cb);
|
putBucketName, objName, objVal, params, log, cb);
|
||||||
};
|
};
|
||||||
// overwrite null version with a non-MPU object
|
// overwrite null version with a non-MPU object
|
||||||
objectPut(authInfo, testPutObjectRequests[1],
|
objectPut(authInfo, testPutObjectRequests[1],
|
||||||
|
@ -2012,13 +2012,13 @@ describe('complete mpu with versioning', () => {
|
||||||
versioningTestUtils.assertDataStoreValues(
|
versioningTestUtils.assertDataStoreValues(
|
||||||
ds, [undefined, objData[1], objData[2]]);
|
ds, [undefined, objData[1], objData[2]]);
|
||||||
|
|
||||||
const origDeleteObject = metadataBackend.deleteObject;
|
const origPutObject = metadataBackend.putObject;
|
||||||
metadataBackend.deleteObject =
|
metadataBackend.putObject =
|
||||||
(bucketName, objName, params, log, cb) => {
|
(putBucketName, objName, objVal, params, log, cb) => {
|
||||||
assert.strictEqual(params.replayId, testUploadId);
|
assert.strictEqual(params.oldReplayId, testUploadId);
|
||||||
metadataBackend.deleteObject = origDeleteObject;
|
metadataBackend.putObject = origPutObject;
|
||||||
metadataBackend.deleteObject(
|
origPutObject(
|
||||||
bucketName, objName, params, log, cb);
|
putBucketName, objName, objVal, params, log, cb);
|
||||||
};
|
};
|
||||||
// overwrite null version with a non-MPU object
|
// overwrite null version with a non-MPU object
|
||||||
objectPut(authInfo, testPutObjectRequests[1],
|
objectPut(authInfo, testPutObjectRequests[1],
|
||||||
|
|
|
@ -19,7 +19,8 @@ function changeObjectLock(objects, newConfig, cb) {
|
||||||
objMD.retentionMode = newConfig.mode;
|
objMD.retentionMode = newConfig.mode;
|
||||||
objMD.retentionDate = newConfig.date;
|
objMD.retentionDate = newConfig.date;
|
||||||
objMD.legalHold = false;
|
objMD.legalHold = false;
|
||||||
metadata.putObjectMD(bucket, key, objMD, { versionId: objMD.versionId }, log, err => {
|
const params = { versionId: objMD.versionId, isNull: false };
|
||||||
|
metadata.putObjectMD(bucket, key, objMD, params, log, err => {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
|
@ -388,7 +388,6 @@ arraybuffer.slice@~0.0.7:
|
||||||
|
|
||||||
"arsenal@git+https://github.com/scality/Arsenal#7.10.46":
|
"arsenal@git+https://github.com/scality/Arsenal#7.10.46":
|
||||||
version "7.10.46"
|
version "7.10.46"
|
||||||
uid bd76402586f1b5117ada9857a9e437727562d55a
|
|
||||||
resolved "git+https://github.com/scality/Arsenal#bd76402586f1b5117ada9857a9e437727562d55a"
|
resolved "git+https://github.com/scality/Arsenal#bd76402586f1b5117ada9857a9e437727562d55a"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/async" "^3.2.12"
|
"@types/async" "^3.2.12"
|
||||||
|
@ -427,9 +426,9 @@ arraybuffer.slice@~0.0.7:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
ioctl "^2.0.2"
|
ioctl "^2.0.2"
|
||||||
|
|
||||||
"arsenal@git+https://github.com/scality/arsenal#7.10.46":
|
"arsenal@git+https://github.com/scality/arsenal#4c39ef64":
|
||||||
version "7.10.46"
|
version "7.70.4"
|
||||||
resolved "git+https://github.com/scality/arsenal#bd76402586f1b5117ada9857a9e437727562d55a"
|
resolved "git+https://github.com/scality/arsenal#4c39ef6469d4d7b630ac03bb8ba41104c46ae36e"
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/async" "^3.2.12"
|
"@types/async" "^3.2.12"
|
||||||
"@types/utf8" "^3.0.1"
|
"@types/utf8" "^3.0.1"
|
||||||
|
|
Loading…
Reference in New Issue