diff --git a/lib/api/bucketPutLifecycle.js b/lib/api/bucketPutLifecycle.js index b1a30e27bf..7c20cec519 100644 --- a/lib/api/bucketPutLifecycle.js +++ b/lib/api/bucketPutLifecycle.js @@ -1,7 +1,6 @@ -const { waterfall } = require('async'); +const { promisify } = require('util'); const uuid = require('uuid').v4; -const LifecycleConfiguration = - require('arsenal').models.LifecycleConfiguration; +const LifecycleConfiguration = require('arsenal').models.LifecycleConfiguration; const config = require('../Config').config; const parseXML = require('../utilities/parseXML'); @@ -16,11 +15,16 @@ const monitoring = require('../utilities/monitoringHandler'); * @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} + * @param {function} [callback] - callback to server + * @return {Promise} - resolves with the CORS response headers */ +async function bucketPutLifecycle(authInfo, request, log, callback) { + if (callback) { + return bucketPutLifecycle(authInfo, request, log) + .then(corsHeaders => callback(null, corsHeaders)) + .catch(err => callback(err, err.additionalResHeaders)); + } -function bucketPutLifecycle(authInfo, request, log, callback) { log.debug('processing request', { method: 'bucketPutLifecycle' }); const { bucketName } = request; @@ -30,53 +34,34 @@ function bucketPutLifecycle(authInfo, request, log, callback) { requestType: request.apiMethods || 'bucketPutLifecycle', request, }; - return waterfall([ - next => parseXML(request.post, log, next), - (parsedXml, next) => { - const lcConfigClass = - new LifecycleConfiguration(parsedXml, config); - // if there was an error getting lifecycle configuration, - // returned configObj will contain 'error' key - process.nextTick(() => { - const configObj = lcConfigClass.getLifecycleConfiguration(); - if (configObj.error) { - return next(configObj.error); - } - return next(null, configObj); - }); - }, - (lcConfig, next) => standardMetadataValidateBucket(metadataValParams, request.actionImplicitDenies, log, - (err, bucket) => { - if (err) { - return next(err, bucket); - } - return next(null, bucket, lcConfig); - }), - (bucket, lcConfig, next) => { - if (!bucket.getUid()) { - bucket.setUid(uuid()); - } - bucket.setLifecycleConfiguration(lcConfig); - metadata.updateBucket(bucket.getName(), bucket, log, err => - next(err, bucket)); - }, - ], (err, bucket) => { - const corsHeaders = collectCorsHeaders(request.headers.origin, - request.method, bucket); - if (err) { - log.trace('error processing request', { error: err, - method: 'bucketPutLifecycle' }); - monitoring.promMetrics( - 'PUT', bucketName, err.code, 'putBucketLifecycle'); - return callback(err, corsHeaders); + + let bucket; + try { + const parsedXml = await promisify(parseXML)(request.post, log); + const lcConfig = new LifecycleConfiguration(parsedXml, log, config).getLifecycleConfiguration(); + if (lcConfig.error) { + throw lcConfig.error; + } + bucket = await promisify(standardMetadataValidateBucket)(metadataValParams, request.actionImplicitDenies, log); + if (!bucket.getUid()) { + bucket.setUid(uuid()); } - pushMetric('putBucketLifecycle', log, { - authInfo, - bucket: bucketName, - }); - monitoring.promMetrics('PUT', bucketName, '200', 'putBucketLifecycle'); - return callback(null, corsHeaders); + bucket.setLifecycleConfiguration(lcConfig); + await promisify(metadata.updateBucket).call(metadata, bucket.getName(), bucket, log); + } catch (err) { + log.trace('error processing request', { error: err, method: 'bucketPutLifecycle' }); + monitoring.promMetrics('PUT', bucketName, err.code, 'putBucketLifecycle'); + err.additionalResHeaders = + err.additionalResHeaders || collectCorsHeaders(request.headers.origin, request.method, bucket); + throw err; + } + + pushMetric('putBucketLifecycle', log, { + authInfo, + bucket: bucketName, }); + monitoring.promMetrics('PUT', bucketName, '200', 'putBucketLifecycle'); + return collectCorsHeaders(request.headers.origin, request.method, bucket); } module.exports = bucketPutLifecycle; diff --git a/package.json b/package.json index 3dd6f905b0..07b62eea86 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "@opentelemetry/instrumentation-ioredis": "~0.64.0", "@opentelemetry/instrumentation-mongodb": "~0.69.0", "@smithy/node-http-handler": "^3.0.0", - "arsenal": "git+https://github.com/scality/arsenal#8.5.2", + "arsenal": "git+https://github.com/scality/arsenal#8b8d0ae0983d28e6bbfca12c8512add839fcd996", "async": "2.6.4", "bucketclient": "scality/bucketclient#8.2.7", "bufferutil": "^4.0.8", diff --git a/tests/functional/aws-node-sdk/test/bucket/putBucketLifecycle.js b/tests/functional/aws-node-sdk/test/bucket/putBucketLifecycle.js index ecec515963..e8f9fa9306 100644 --- a/tests/functional/aws-node-sdk/test/bucket/putBucketLifecycle.js +++ b/tests/functional/aws-node-sdk/test/bucket/putBucketLifecycle.js @@ -1,9 +1,12 @@ const assert = require('assert'); const { errors } = require('arsenal'); -const { S3Client, +const { + S3Client, CreateBucketCommand, DeleteBucketCommand, - PutBucketLifecycleConfigurationCommand } = require('@aws-sdk/client-s3'); + PutBucketLifecycleConfigurationCommand, + GetBucketLifecycleConfigurationCommand, +} = require('@aws-sdk/client-s3'); const getConfig = require('../support/config'); const BucketUtility = require('../../lib/utility/bucket-util'); @@ -26,11 +29,17 @@ function assertError(err, expectedErr) { if (expectedErr === null) { assert.strictEqual(err, null, `expected no error but got '${err}'`); } else { - assert.strictEqual(err.name, expectedErr, 'incorrect error response ' + - `code: should be '${expectedErr}' but got '${err.name}'`); - assert.strictEqual(err.$metadata.httpStatusCode, errors[expectedErr].code, + assert.strictEqual( + err.name, + expectedErr, + 'incorrect error response ' + `code: should be '${expectedErr}' but got '${err.name}'`, + ); + assert.strictEqual( + err.$metadata.httpStatusCode, + errors[expectedErr].code, 'incorrect error status code: should be ' + - `${errors[expectedErr].code}, but got '${err.$metadata.httpStatusCode}'`); + `${errors[expectedErr].code}, but got '${err.$metadata.httpStatusCode}'`, + ); } } @@ -89,40 +98,111 @@ describe('aws-sdk test put bucket lifecycle', () => { const params = getLifecycleParams(); await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); - - it('should not allow lifecycle configuration with duplicated rule id ' + - 'and with Origin header set', async () => { - const origin = 'http://www.allowedwebsite.com'; - const lifecycleConfig = { - Rules: [expirationRule, expirationRule], + + it('should allow Expiration Days=0 (explicit bucket-emptying intent) ' + 'and round-trip it', async () => { + const params = getLifecycleParams({ key: 'Expiration', value: { Days: 0 } }); + await s3.send(new PutBucketLifecycleConfigurationCommand(params)); + const got = await s3.send(new GetBucketLifecycleConfigurationCommand({ Bucket: bucket })); + assert.strictEqual(got.Rules[0].Expiration.Days, 0); + }); + + it('should not allow negative Expiration Days', async () => { + const params = getLifecycleParams({ key: 'Expiration', value: { Days: -1 } }); + try { + await s3.send(new PutBucketLifecycleConfigurationCommand(params)); + throw new Error('Expected InvalidArgument error'); + } catch (err) { + assert.strictEqual(err.name, 'InvalidArgument'); + assert.strictEqual(err.message, "'Days' in Expiration action must be nonnegative"); + } + }); + + it(`should not allow Expiration Days exceeding ${MAX_DAYS}`, async () => { + const params = getLifecycleParams({ + key: 'Expiration', + value: { Days: MAX_DAYS + 1 }, + }); + try { + await s3.send(new PutBucketLifecycleConfigurationCommand(params)); + throw new Error('Expected MalformedXML error'); + } catch (err) { + assert.strictEqual(err.name, 'MalformedXML'); + } + }); + + it('should allow NoncurrentVersionExpiration NoncurrentDays=0', async () => { + const params = { + Bucket: bucket, + LifecycleConfiguration: { + Rules: [ + { + ID: 'test-id', + Status: 'Enabled', + Prefix: '', + NoncurrentVersionExpiration: { NoncurrentDays: 0 }, + }, + ], + }, }; + await s3.send(new PutBucketLifecycleConfigurationCommand(params)); + const got = await s3.send(new GetBucketLifecycleConfigurationCommand({ Bucket: bucket })); + assert.strictEqual(got.Rules[0].NoncurrentVersionExpiration.NoncurrentDays, 0); + }); + + it('should allow AbortIncompleteMultipartUpload DaysAfterInitiation=0', async () => { const params = { Bucket: bucket, - LifecycleConfiguration: lifecycleConfig, + LifecycleConfiguration: { + Rules: [ + { + ID: 'test-id', + Status: 'Enabled', + Prefix: '', + AbortIncompleteMultipartUpload: { DaysAfterInitiation: 0 }, + }, + ], + }, }; + await s3.send(new PutBucketLifecycleConfigurationCommand(params)); + const got = await s3.send(new GetBucketLifecycleConfigurationCommand({ Bucket: bucket })); + assert.strictEqual(got.Rules[0].AbortIncompleteMultipartUpload.DaysAfterInitiation, 0); + }); - const clientConfig = getConfig('default', { signatureVersion: 'v4' }); - const clientWithOrigin = new S3Client({ - ...clientConfig, - requestHandler: { - handle: async request => { - if (!request.headers) { + it( + 'should not allow lifecycle configuration with duplicated rule id ' + 'and with Origin header set', + async () => { + const origin = 'http://www.allowedwebsite.com'; + const lifecycleConfig = { + Rules: [expirationRule, expirationRule], + }; + const params = { + Bucket: bucket, + LifecycleConfiguration: lifecycleConfig, + }; + + const clientConfig = getConfig('default', { signatureVersion: 'v4' }); + const clientWithOrigin = new S3Client({ + ...clientConfig, + requestHandler: { + handle: async request => { + if (!request.headers) { + // eslint-disable-next-line no-param-reassign + request.headers = {}; + } // eslint-disable-next-line no-param-reassign - request.headers = {}; - } - // eslint-disable-next-line no-param-reassign - request.headers.origin = origin; - return clientConfig.requestHandler.handle(request); - } + request.headers.origin = origin; + return clientConfig.requestHandler.handle(request); + }, + }, + }); + try { + await clientWithOrigin.send(new PutBucketLifecycleConfigurationCommand(params)); + throw new Error('Expected InvalidRequest error'); + } catch (err) { + assertError(err, 'InvalidRequest'); } - }); - try { - await clientWithOrigin.send(new PutBucketLifecycleConfigurationCommand(params)); - throw new Error('Expected InvalidRequest error'); - } catch (err) { - assertError(err, 'InvalidRequest'); - } - }); + }, + ); it('should not allow lifecycle config with no Status', async () => { const params = getLifecycleParams({ key: 'Status', value: '' }); @@ -155,8 +235,7 @@ describe('aws-sdk test put bucket lifecycle', () => { }); it('should not allow lifecycle config with ID longer than 255 char', async () => { - const params = - getLifecycleParams({ key: 'ID', value: 'a'.repeat(256) }); + const params = getLifecycleParams({ key: 'ID', value: 'a'.repeat(256) }); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected InvalidArgument error'); @@ -166,20 +245,17 @@ describe('aws-sdk test put bucket lifecycle', () => { }); it('should allow lifecycle config with Prefix length < 1024', async () => { - const params = - getLifecycleParams({ key: 'Prefix', value: 'a'.repeat(1023) }); + const params = getLifecycleParams({ key: 'Prefix', value: 'a'.repeat(1023) }); await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); it('should allow lifecycle config with Prefix length === 1024', async () => { - const params = - getLifecycleParams({ key: 'Prefix', value: 'a'.repeat(1024) }); + const params = getLifecycleParams({ key: 'Prefix', value: 'a'.repeat(1024) }); await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); it('should not allow lifecycle config with Prefix length > 1024', async () => { - const params = - getLifecycleParams({ key: 'Prefix', value: 'a'.repeat(1025) }); + const params = getLifecycleParams({ key: 'Prefix', value: 'a'.repeat(1025) }); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected InvalidRequest error'); @@ -202,8 +278,7 @@ describe('aws-sdk test put bucket lifecycle', () => { } }); - it('should not allow lifecycle config with Filter.And.Prefix length ' + - '> 1024', async () => { + it('should not allow lifecycle config with Filter.And.Prefix length ' + '> 1024', async () => { const params = getLifecycleParams({ key: 'Filter', value: { @@ -287,8 +362,7 @@ describe('aws-sdk test put bucket lifecycle', () => { }); it('should not allow lifecycle config with Prefix and Filter', async () => { - const params = getLifecycleParams( - { key: 'Filter', value: { Prefix: 'foo' } }); + const params = getLifecycleParams({ key: 'Filter', value: { Prefix: 'foo' } }); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected MalformedXML error'); @@ -310,7 +384,6 @@ describe('aws-sdk test put bucket lifecycle', () => { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); - describe('with Rule.Filter not Rule.Prefix', () => { before(done => { expirationRule.Prefix = null; @@ -323,8 +396,7 @@ describe('aws-sdk test put bucket lifecycle', () => { }); it('should not allow config with And & Prefix', async () => { - const params = getLifecycleParams( - { key: 'Filter', value: { Prefix: 'foo', And: {} } }); + const params = getLifecycleParams({ key: 'Filter', value: { Prefix: 'foo', And: {} } }); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected MalformedXML error'); @@ -360,8 +432,7 @@ describe('aws-sdk test put bucket lifecycle', () => { }); it('should allow config with only Prefix', async () => { - const params = getLifecycleParams( - { key: 'Filter', value: { Prefix: 'foo' } }); + const params = getLifecycleParams({ key: 'Filter', value: { Prefix: 'foo' } }); await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); @@ -374,8 +445,7 @@ describe('aws-sdk test put bucket lifecycle', () => { }); it('should not allow config with And.Prefix & no And.Tags', async () => { - const params = getLifecycleParams( - { key: 'Filter', value: { And: { Prefix: 'foo' } } }); + const params = getLifecycleParams({ key: 'Filter', value: { And: { Prefix: 'foo' } } }); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected MalformedXML error'); @@ -400,9 +470,14 @@ describe('aws-sdk test put bucket lifecycle', () => { it('should allow config with And.Tags & no And.Prefix', async () => { const params = getLifecycleParams({ key: 'Filter', - value: { And: { Tags: - [{ Key: 'foo', Value: 'bar' }, - { Key: 'foo2', Value: 'bar2' }] } }, + value: { + And: { + Tags: [ + { Key: 'foo', Value: 'bar' }, + { Key: 'foo2', Value: 'bar2' }, + ], + }, + }, }); await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); @@ -410,9 +485,15 @@ describe('aws-sdk test put bucket lifecycle', () => { it('should allow config with And.Tags & And.Prefix', async () => { const params = getLifecycleParams({ key: 'Filter', - value: { And: { Prefix: 'foo', Tags: - [{ Key: 'foo', Value: 'bar' }, - { Key: 'foo2', Value: 'bar2' }] } }, + value: { + And: { + Prefix: 'foo', + Tags: [ + { Key: 'foo', Value: 'bar' }, + { Key: 'foo2', Value: 'bar2' }, + ], + }, + }, }); await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); @@ -423,12 +504,14 @@ describe('aws-sdk test put bucket lifecycle', () => { return { Bucket: bucket, LifecycleConfiguration: { - Rules: [{ - ID: 'test', - Status: 'Enabled', - Prefix: '', - noncurrentVersionTransition, - }], + Rules: [ + { + ID: 'test', + Status: 'Enabled', + Prefix: '', + noncurrentVersionTransition, + }, + ], }, }; } @@ -464,9 +547,10 @@ describe('aws-sdk test put bucket lifecycle', () => { throw new Error('Expected InvalidArgument error'); } catch (err) { assert.strictEqual(err.name, 'InvalidArgument'); - assert.strictEqual(err.message, - "'NoncurrentDays' in NoncurrentVersionExpiration " + - 'action must be nonnegative'); + assert.strictEqual( + err.message, + "'NoncurrentDays' in NoncurrentVersionExpiration " + 'action must be nonnegative', + ); } }); @@ -487,21 +571,25 @@ describe('aws-sdk test put bucket lifecycle', () => { return { Bucket: bucket, LifecycleConfiguration: { - Rules: [{ - ID: 'test', - Status: 'Enabled', - Prefix: '', - NoncurrentVersionTransitions: noncurrentVersionTransitions, - }], + Rules: [ + { + ID: 'test', + Status: 'Enabled', + Prefix: '', + NoncurrentVersionTransitions: noncurrentVersionTransitions, + }, + ], }, }; } it('should allow config', async () => { - const noncurrentVersionTransitions = [{ - NoncurrentDays: 1, - StorageClass: 'us-east-2', - }]; + const noncurrentVersionTransitions = [ + { + NoncurrentDays: 1, + StorageClass: 'us-east-2', + }, + ]; const params = getParams(noncurrentVersionTransitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); @@ -514,13 +602,16 @@ describe('aws-sdk test put bucket lifecycle', () => { }); it.skip('should not allow duplicate StorageClass', async () => { - const noncurrentVersionTransitions = [{ - NoncurrentDays: 1, - StorageClass: 'us-east-2', - }, { - NoncurrentDays: 2, - StorageClass: 'us-east-2', - }]; + const noncurrentVersionTransitions = [ + { + NoncurrentDays: 1, + StorageClass: 'us-east-2', + }, + { + NoncurrentDays: 2, + StorageClass: 'us-east-2', + }, + ]; const params = getParams(noncurrentVersionTransitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); @@ -532,88 +623,111 @@ describe('aws-sdk test put bucket lifecycle', () => { return; } assert.strictEqual(err.name, 'InvalidRequest'); - assert.strictEqual(err.message, + assert.strictEqual( + err.message, "'StorageClass' must be different for " + - "'NoncurrentVersionTransition' actions in same " + - "'Rule' with prefix ''"); + "'NoncurrentVersionTransition' actions in same " + + "'Rule' with prefix ''", + ); } }); it('should not allow unknown StorageClass', async () => { - const noncurrentVersionTransitions = [{ - NoncurrentDays: 1, - StorageClass: 'unknown', - }]; + const noncurrentVersionTransitions = [ + { + NoncurrentDays: 1, + StorageClass: 'unknown', + }, + ]; const params = getParams(noncurrentVersionTransitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected MalformedXML error'); } catch (err) { - assert(err.name === 'MalformedXML' || err.name === 'NotImplemented', - `Expected MalformedXML or NotImplemented, got ${err.name}`); + assert( + err.name === 'MalformedXML' || err.name === 'NotImplemented', + `Expected MalformedXML or NotImplemented, got ${err.name}`, + ); } }); it(`should not allow NoncurrentDays value exceeding ${MAX_DAYS}`, async () => { - const noncurrentVersionTransitions = [{ - NoncurrentDays: MAX_DAYS + 1, - StorageClass: 'us-east-2', - }]; + const noncurrentVersionTransitions = [ + { + NoncurrentDays: MAX_DAYS + 1, + StorageClass: 'us-east-2', + }, + ]; const params = getParams(noncurrentVersionTransitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected MalformedXML error'); } catch (err) { - assert(err.name === 'MalformedXML' || err.name === 'NotImplemented', - `Expected MalformedXML or NotImplemented, got ${err.name}`); + assert( + err.name === 'MalformedXML' || err.name === 'NotImplemented', + `Expected MalformedXML or NotImplemented, got ${err.name}`, + ); } }); it('should not allow negative NoncurrentDays', async () => { - const noncurrentVersionTransitions = [{ - NoncurrentDays: -1, - StorageClass: 'us-east-2', - }]; + const noncurrentVersionTransitions = [ + { + NoncurrentDays: -1, + StorageClass: 'us-east-2', + }, + ]; const params = getParams(noncurrentVersionTransitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected error'); } catch (err) { - assert(err.name === 'InvalidArgument' || err.name === 'NotImplemented', - `Expected InvalidArgument or NotImplemented, got ${err.name}`); + assert( + err.name === 'InvalidArgument' || err.name === 'NotImplemented', + `Expected InvalidArgument or NotImplemented, got ${err.name}`, + ); if (err.name === 'InvalidArgument') { - assert.strictEqual(err.message, - "'NoncurrentDays' in NoncurrentVersionTransition " + - 'action must be nonnegative'); + assert.strictEqual( + err.message, + "'NoncurrentDays' in NoncurrentVersionTransition " + 'action must be nonnegative', + ); } } }); it('should not allow config missing NoncurrentDays', async () => { - const noncurrentVersionTransitions = [{ - StorageClass: 'us-east-2', - }]; + const noncurrentVersionTransitions = [ + { + StorageClass: 'us-east-2', + }, + ]; const params = getParams(noncurrentVersionTransitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected error'); } catch (err) { - assert(err.name === 'MalformedXML' || err.name === 'NotImplemented', - `Expected MalformedXML or NotImplemented, got ${err.name}`); + assert( + err.name === 'MalformedXML' || err.name === 'NotImplemented', + `Expected MalformedXML or NotImplemented, got ${err.name}`, + ); } }); it('should not allow config missing StorageClass', async () => { - const noncurrentVersionTransitions = [{ - NoncurrentDays: 1, - }]; + const noncurrentVersionTransitions = [ + { + NoncurrentDays: 1, + }, + ]; const params = getParams(noncurrentVersionTransitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected error'); } catch (err) { - assert(err.name === 'MalformedXML' || err.name === 'NotImplemented', - `Expected MalformedXML or NotImplemented, got ${err.name}`); + assert( + err.name === 'MalformedXML' || err.name === 'NotImplemented', + `Expected MalformedXML or NotImplemented, got ${err.name}`, + ); } }); }); @@ -626,15 +740,19 @@ describe('aws-sdk test put bucket lifecycle', () => { const params = { Bucket: bucket, LifecycleConfiguration: { - Rules: [{ - ID: 'test', - Status: 'Enabled', - Prefix: '', - Transitions: [{ - Days: 2, - StorageClass: 'us-east-2', - }], - }], + Rules: [ + { + ID: 'test', + Status: 'Enabled', + Prefix: '', + Transitions: [ + { + Days: 2, + StorageClass: 'us-east-2', + }, + ], + }, + ], }, }; try { @@ -652,60 +770,72 @@ describe('aws-sdk test put bucket lifecycle', () => { return { Bucket: bucket, LifecycleConfiguration: { - Rules: [{ - ID: 'test', - Status: 'Enabled', - Prefix: '', - Transitions: transitions, - }], + Rules: [ + { + ID: 'test', + Status: 'Enabled', + Prefix: '', + Transitions: transitions, + }, + ], }, }; } it('should allow config', async () => { - const transitions = [{ - Days: 1, - StorageClass: 'us-east-2', - }]; + const transitions = [ + { + Days: 1, + StorageClass: 'us-east-2', + }, + ]; const params = getParams(transitions); await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); it('should not allow duplicate StorageClass', async () => { - const transitions = [{ - Days: 1, - StorageClass: 'us-east-2', - }, { - Days: 2, - StorageClass: 'us-east-2', - }]; + const transitions = [ + { + Days: 1, + StorageClass: 'us-east-2', + }, + { + Days: 2, + StorageClass: 'us-east-2', + }, + ]; const params = getParams(transitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); throw new Error('Expected InvalidRequest error'); } catch (err) { assert.strictEqual(err.name, 'InvalidRequest'); - assert.strictEqual(err.message, - "'StorageClass' must be different for 'Transition' " + - "actions in same 'Rule' with prefix ''"); + assert.strictEqual( + err.message, + "'StorageClass' must be different for 'Transition' " + "actions in same 'Rule' with prefix ''", + ); } }); it('should allow Date', async () => { - const transitions = [{ - Date: new Date('2016-01-01T00:00:00.000Z'), - StorageClass: 'us-east-2', - }]; + const transitions = [ + { + Date: new Date('2016-01-01T00:00:00.000Z'), + StorageClass: 'us-east-2', + }, + ]; const params = getParams(transitions); await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); it('should not allow speficying both Days and Date value', async () => { - const transitions = [{ - Date: new Date('2016-01-01T00:00:00.000Z'), - Days: 1, - StorageClass: 'us-east-2', - }]; + const transitions = [ + { + Date: new Date('2016-01-01T00:00:00.000Z'), + Days: 1, + StorageClass: 'us-east-2', + }, + ]; const params = getParams(transitions); try { await s3.send(new PutBucketLifecycleConfigurationCommand(params)); @@ -716,87 +846,104 @@ describe('aws-sdk test put bucket lifecycle', () => { }); // TODO: Upgrade to aws-sdk >= 2.60.0 for correct Date field support - it.skip('should not allow speficying both Days and Date value ' + - 'across transitions', done => { - const transitions = [{ - Date: '2016-01-01T00:00:00.000Z', - StorageClass: 'us-east-2', - }, { - Days: 1, - StorageClass: 'zenko', - }]; + it.skip('should not allow speficying both Days and Date value ' + 'across transitions', done => { + const transitions = [ + { + Date: '2016-01-01T00:00:00.000Z', + StorageClass: 'us-east-2', + }, + { + Days: 1, + StorageClass: 'zenko', + }, + ]; const params = getParams(transitions); s3.putBucketLifecycleConfiguration(params, err => { assert.strictEqual(err.code, 'InvalidRequest'); - assert.strictEqual(err.message, - "Found mixed 'Date' and 'Days' based Transition " + - "actions in lifecycle rule for prefix ''"); + assert.strictEqual( + err.message, + "Found mixed 'Date' and 'Days' based Transition " + "actions in lifecycle rule for prefix ''", + ); done(); }); }); - it('should not allow speficying both Days and Date value ' + - 'across transitions and expiration', async () => { - const transitions = [{ - Days: 1, - StorageClass: 'us-east-2', - }]; - const params = getParams(transitions); - params.LifecycleConfiguration.Rules[0].Expiration = { - Date: new Date('2016-01-01T00:00:00.000Z') // Use proper Date object - }; - try { - await s3.send(new PutBucketLifecycleConfigurationCommand(params)); - throw new Error('Expected InvalidRequest error'); - } catch (err) { - assert.strictEqual(err.name, 'InvalidRequest'); - assert.strictEqual(err.message, - "Found mixed 'Date' and 'Days' based Expiration and " + - "Transition actions in lifecycle rule for prefix ''"); - } - }); + it( + 'should not allow speficying both Days and Date value ' + 'across transitions and expiration', + async () => { + const transitions = [ + { + Days: 1, + StorageClass: 'us-east-2', + }, + ]; + const params = getParams(transitions); + params.LifecycleConfiguration.Rules[0].Expiration = { + Date: new Date('2016-01-01T00:00:00.000Z'), // Use proper Date object + }; + try { + await s3.send(new PutBucketLifecycleConfigurationCommand(params)); + throw new Error('Expected InvalidRequest error'); + } catch (err) { + assert.strictEqual(err.name, 'InvalidRequest'); + assert.strictEqual( + err.message, + "Found mixed 'Date' and 'Days' based Expiration and " + + "Transition actions in lifecycle rule for prefix ''", + ); + } + }, + ); }); // NoncurrentVersionTransitions not implemented - describe.skip('with NoncurrentVersionTransitions and Transitions', - () => { + describe.skip('with NoncurrentVersionTransitions and Transitions', () => { it('should allow config', async () => { const params = { Bucket: bucket, LifecycleConfiguration: { - Rules: [{ - ID: 'test', - Status: 'Enabled', - Prefix: '', - NoncurrentVersionTransitions: [{ - NoncurrentDays: 1, - StorageClass: 'us-east-2', - }], - Transitions: [{ - Days: 1, - StorageClass: 'us-east-2', - }], - }], + Rules: [ + { + ID: 'test', + Status: 'Enabled', + Prefix: '', + NoncurrentVersionTransitions: [ + { + NoncurrentDays: 1, + StorageClass: 'us-east-2', + }, + ], + Transitions: [ + { + Days: 1, + StorageClass: 'us-east-2', + }, + ], + }, + ], }, }; await s3.send(new PutBucketLifecycleConfigurationCommand(params)); }); }); - it.skip('should not allow config when specifying ' + - 'NoncurrentVersionTransitions', async () => { + it.skip('should not allow config when specifying ' + 'NoncurrentVersionTransitions', async () => { const params = { Bucket: bucket, LifecycleConfiguration: { - Rules: [{ - ID: 'test', - Status: 'Enabled', - Prefix: '', - NoncurrentVersionTransitions: [{ - NoncurrentDays: 1, - StorageClass: 'us-east-2', - }], - }], + Rules: [ + { + ID: 'test', + Status: 'Enabled', + Prefix: '', + NoncurrentVersionTransitions: [ + { + NoncurrentDays: 1, + StorageClass: 'us-east-2', + }, + ], + }, + ], }, }; try { diff --git a/yarn.lock b/yarn.lock index cf10359a70..b8f4d08ede 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6595,9 +6595,9 @@ arraybuffer.prototype.slice@^1.0.4: optionalDependencies: ioctl "^2.0.2" -"arsenal@git+https://github.com/scality/arsenal#8.5.2": - version "8.5.2" - resolved "git+https://github.com/scality/arsenal#cd9ddfca043b522c9199a0edbf23a73dbc7973fd" +"arsenal@git+https://github.com/scality/arsenal#8b8d0ae0983d28e6bbfca12c8512add839fcd996": + version "8.5.3" + resolved "git+https://github.com/scality/arsenal#8b8d0ae0983d28e6bbfca12c8512add839fcd996" dependencies: "@aws-sdk/client-kms" "^3.975.0" "@aws-sdk/client-s3" "^3.975.0" @@ -6607,6 +6607,10 @@ arraybuffer.prototype.slice@^1.0.4: "@azure/storage-blob" "^12.31.0" "@js-sdsl/ordered-set" "^4.4.2" "@opentelemetry/api" "^1.9.1" + "@opentelemetry/exporter-trace-otlp-http" "^0.219.0" + "@opentelemetry/resources" "^2.8.0" + "@opentelemetry/sdk-node" "^0.219.0" + "@opentelemetry/sdk-trace-base" "^2.8.0" "@scality/hdclient" "^1.3.2" "@smithy/node-http-handler" "^4.3.0" "@smithy/protocol-http" "^5.3.5" @@ -6632,16 +6636,12 @@ arraybuffer.prototype.slice@^1.0.4: simple-glob "^0.2.0" socket.io "^4.8.0" socket.io-client "^4.8.0" - sproxydclient "github:scality/sproxydclient#8.1.0" + sproxydclient "github:scality/sproxydclient#8.2.1" utf8 "^3.0.0" uuid "^10.0.0" - werelogs scality/werelogs#8.2.2 + werelogs scality/werelogs#8.2.4 xml2js "^0.6.2" optionalDependencies: - "@opentelemetry/exporter-trace-otlp-http" "^0.219.0" - "@opentelemetry/resources" "^2.8.0" - "@opentelemetry/sdk-node" "^0.219.0" - "@opentelemetry/sdk-trace-base" "^2.8.0" ioctl "^2.0.2" asn1@~0.2.3: @@ -12085,6 +12085,14 @@ sprintf-js@~1.0.2: httpagent "github:scality/httpagent#1.1.0" werelogs scality/werelogs#8.2.0 +"sproxydclient@github:scality/sproxydclient#8.2.1": + version "8.2.1" + resolved "https://codeload.github.com/scality/sproxydclient/tar.gz/501829f5521787e7e946de6792f5806ffa6ec437" + dependencies: + async "^3.2.6" + httpagent "github:scality/httpagent#1.1.0" + werelogs scality/werelogs#8.2.0 + sql-where-parser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/sql-where-parser/-/sql-where-parser-2.2.1.tgz#d9af68c20ebfdffe9a115a119f65abc4fbb7c2e5" @@ -12844,7 +12852,6 @@ webidl-conversions@^7.0.0: "werelogs@github:scality/werelogs#8.2.2", werelogs@scality/werelogs#8.2.2: version "8.2.2" - uid e53bef5145697bf8af940dcbe59408988d64854f resolved "https://codeload.github.com/scality/werelogs/tar.gz/e53bef5145697bf8af940dcbe59408988d64854f" dependencies: fast-safe-stringify "^2.1.1" @@ -12857,6 +12864,13 @@ werelogs@scality/werelogs#8.2.0: fast-safe-stringify "^2.1.1" safe-json-stringify "^1.2.0" +werelogs@scality/werelogs#8.2.4: + version "8.2.4" + resolved "https://codeload.github.com/scality/werelogs/tar.gz/a7bbb5917a08b035d3763b24b070d517483d6982" + dependencies: + fast-safe-stringify "^2.1.1" + safe-json-stringify "^1.2.0" + whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"