function objectPutRetention(authInfo, request, log, callback) {
    log.debug('processing request', { method: 'objectPutRetention' });

    const { bucketName, objectKey } = request;

    const decodedVidResult = decodeVersionId(request.query);
    if (decodedVidResult instanceof Error) {
        log.trace('invalid versionId query', {
            versionId: request.query.versionId,
            error: decodedVidResult,
        });
        return process.nextTick(() => callback(decodedVidResult));
    }
    const reqVersionId = decodedVidResult;

    const metadataValParams = {
        authInfo,
        bucketName,
        objectKey,
        versionId: reqVersionId,
        getDeleteMarker: true,
        requestType: request.apiMethods || 'objectPutRetention',
        request,
    };

    const hasGovernanceBypass = hasGovernanceBypassHeader(request.headers);

    return async.waterfall([
        next => {
            log.trace('parsing retention information');
            parseRetentionXml(request.post, log,
                (err, retentionInfo) => {
                    if (err) {
                        log.trace('error parsing retention information',
                            { error: err });
                        return next(err);
                    }
                    const remainingDays = Math.ceil(
                        (new Date(retentionInfo.date) - Date.now()) / (1000 * 3600 * 24));
                    metadataValParams.request.objectLockRetentionDays = remainingDays;
                    return next(null, retentionInfo);
                });
        },
        (retentionInfo, next) => standardMetadataValidateBucketAndObj(metadataValParams,
            request.actionImplicitDenies, log, (err, bucket, objectMD) => {
                if (err) {
                    log.trace('request authorization failed',
                        { method: 'objectPutRetention', error: err });
                    return next(err);
                }
                if (!objectMD) {
                    const err = reqVersionId ? errors.NoSuchVersion :
                        errors.NoSuchKey;
                    log.trace('error no object metadata found',
                        { method: 'objectPutRetention', error: err });
                    return next(err, bucket);
                }
                if (objectMD.isDeleteMarker) {
                    log.trace('version is a delete marker',
                        { method: 'objectPutRetention' });
                    return next(errors.MethodNotAllowed, bucket);
                }
                if (!bucket.isObjectLockEnabled()) {
                    log.trace('object lock not enabled on bucket',
                        { method: 'objectPutRetention' });
                    return next(errorInstances.InvalidRequest.customizeDescription(
                        'Bucket is missing Object Lock Configuration'
                    ), bucket);
                }
                return next(null, bucket, retentionInfo, objectMD);
            }),
        (bucket, retentionInfo, objectMD, next) => {
            const objLockInfo = new ObjectLockInfo({
                mode: objectMD.retentionMode,
                date: objectMD.retentionDate,
                legalHold: objectMD.legalHold,
            });

            if (!objLockInfo.canModifyPolicy(retentionInfo, hasGovernanceBypass)) {
                return next(errors.AccessDenied, bucket);
            }

            return next(null, bucket, retentionInfo, objectMD);
        },
        (bucket, retentionInfo, objectMD, next) => {
            /* eslint-disable no-param-reassign */
            objectMD.retentionMode = retentionInfo.mode;
            objectMD.retentionDate = retentionInfo.date;
            const params = getVersionSpecificMetadataOptions(objectMD, config.nullVersionCompatMode);
            const replicationInfo = getReplicationInfo(config,
                objectKey, bucket, true, 0, REPLICATION_ACTION, objectMD);
            if (replicationInfo) {
                objectMD.replicationInfo = Object.assign({},
                    objectMD.replicationInfo, replicationInfo);
            }
            objectMD.originOp = 's3:ObjectRetention:Put';
            /* eslint-enable no-param-reassign */
            metadata.putObjectMD(bucket.getName(), objectKey, objectMD, params,
                log, err => next(err, bucket, objectMD));
        },
    ], (err, bucket, objectMD) => {
        const additionalResHeaders = collectCorsHeaders(request.headers.origin,
            request.method, bucket);
        if (err) {
            log.trace('error processing request',
                { error: err, method: 'objectPutRetention' });
        } else {
            pushMetric('putObjectRetention', log, {
                authInfo,
                bucket: bucketName,
                keys: [objectKey],
                versionId: objectMD ? objectMD.versionId : undefined,
                location: objectMD ? objectMD.dataStoreName : undefined,
            });
            const verCfg = bucket.getVersioningConfiguration();
            additionalResHeaders['x-amz-version-id'] =
                getVersionIdResHeader(verCfg, objectMD);
        }
        return callback(err, additionalResHeaders);
    });
}
Object Put Retention - Adds retention information to object
@param {AuthInfo} authInfo - Instance of AuthInfo class with requester's info
@param {object} request - http request object
@param {object} log - Werelogs logger
@param {function} callback - callback to server
@return {undefined}