diff --git a/README.md b/README.md index 2a5a8677..1677787c 100644 --- a/README.md +++ b/README.md @@ -994,12 +994,12 @@ parameters: - [createdBeforeDate] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z` `createdBeforeDate` and `days` must have one. - [transition] {Object} Specifies the time when an object is converted to the IA or archive storage class during a valid life cycle. - - storageClass {String} Specifies the storage class that objects that conform to the rule are converted into. allow values: `IA` or `Archive` + - storageClass {String} Specifies the storage class that objects that conform to the rule are converted into. allow values: `IA` or `Archive` or `ColdArchive` or `DeepColdArchive` - [days] {Number|String} expire after the `days` - [createdBeforeDate] {String} expire date, e.g.: `2022-10-11T00:00:00.000Z` `createdBeforeDate` and `days` must have one. - [noncurrentVersionTransition] {Object} Specifies the time when an object is converted to the IA or archive storage class during a valid life cycle. - - storageClass {String} Specifies the storage class that history objects that conform to the rule are converted into. allow values: `IA` or `Archive` + - storageClass {String} Specifies the storage class that history objects that conform to the rule are converted into. allow values: `IA` or `Archive` or `ColdArchive` or `DeepColdArchive` - noncurrentDays {String} expire after the `noncurrentDays` `expiration`、 `abortMultipartUpload`、 `transition`、 `noncurrentVersionTransition` must have one. - [noncurrentVersionExpiration] {Object} specifies the expiration attribute of the lifecycle rules for the history object. diff --git a/lib/common/bucket/putBucketLifecycle.js b/lib/common/bucket/putBucketLifecycle.js index 8cbe97a2..838d80b9 100644 --- a/lib/common/bucket/putBucketLifecycle.js +++ b/lib/common/bucket/putBucketLifecycle.js @@ -73,6 +73,15 @@ function checkDaysAndDate(obj, key) { } } +function checkNoncurrentDays(obj, key) { + const { noncurrentDays } = obj; + if (!noncurrentDays) { + throw new Error(`${key} must includes noncurrentDays`); + } else if (noncurrentDays && !/^[1-9][0-9]*$/.test(noncurrentDays)) { + throw new Error('noncurrentDays must be a positive integer'); + } +} + function handleCheckTag(tag) { if (!isArray(tag) && !isObject(tag)) { throw new Error('tag must be Object or Array'); @@ -87,6 +96,11 @@ function handleCheckTag(tag) { checkObjectTag(tagObj); } +function checkStorageClass(storageClass) { + if (!['IA', 'Archive', 'ColdArchive', 'DeepColdArchive'].includes(storageClass)) + throw new Error(`StorageClass must be IA or Archive or ColdArchive or DeepColdArchive`); +} + function checkRule(rule) { if (rule.id && getStrBytesCount(rule.id) > 255) throw new Error('ID is composed of 255 bytes at most'); @@ -95,8 +109,7 @@ function checkRule(rule) { if (!['Enabled', 'Disabled'].includes(rule.status)) throw new Error('Status must be Enabled or Disabled'); if (rule.transition) { - if (!['IA', 'Archive'].includes(rule.transition.storageClass)) - throw new Error('StorageClass must be IA or Archive'); + checkStorageClass(rule.transition.storageClass); checkDaysAndDate(rule.transition, 'Transition'); } @@ -112,12 +125,27 @@ function checkRule(rule) { checkDaysAndDate(rule.abortMultipartUpload, 'AbortMultipartUpload'); } - if (!rule.expiration && !rule.abortMultipartUpload && !rule.transition && !rule.noncurrentVersionTransition) { + if ( + !rule.expiration && + !rule.noncurrentVersionExpiration && + !rule.abortMultipartUpload && + !rule.transition && + !rule.noncurrentVersionTransition + ) { throw new Error( - 'Rule must includes expiration or abortMultipartUpload or transition or noncurrentVersionTransition' + 'Rule must includes expiration or noncurrentVersionExpiration or abortMultipartUpload or transition or noncurrentVersionTransition' ); } + if (rule.noncurrentVersionTransition) { + checkStorageClass(rule.noncurrentVersionTransition.storageClass); + checkNoncurrentDays(rule.noncurrentVersionTransition, 'NoncurrentVersionTransition'); + } + + if (rule.noncurrentVersionExpiration) { + checkNoncurrentDays(rule.noncurrentVersionExpiration, 'NoncurrentVersionExpiration'); + } + if (rule.tag) { if (rule.abortMultipartUpload) { throw new Error('Tag cannot be used with abortMultipartUpload'); diff --git a/test/node/bucket.test.js b/test/node/bucket.test.js index eb45dcce..83d1c428 100644 --- a/test/node/bucket.test.js +++ b/test/node/bucket.test.js @@ -13,7 +13,7 @@ describe('test/bucket.test.js', () => { const { prefix, includesConf } = utils; let store; let bucket; - const bucketRegion = config.region; + const bucketRegion = 'oss-ap-southeast-1'; // oss-ap-southeast-1 suport PutBucketLifecycle DeepColdArchive const { accountId } = config; [ { @@ -25,7 +25,7 @@ describe('test/bucket.test.js', () => { ].forEach((moreConfigs, idx) => { describe(`test bucket in iterate ${idx}`, () => { before(async () => { - store = oss({ ...config, ...moreConfigs }); + store = oss({ ...config, ...moreConfigs, region: bucketRegion }); bucket = `ali-oss-test-bucket-${prefix.replace(/[/.]/g, '-')}${idx}`; const result = await store.putBucket(bucket, { timeout }); @@ -764,7 +764,25 @@ describe('test/bucket.test.js', () => { }); describe('putBucketLifecycle()', () => { - // todo delete + it('should put the lifecycle throw error', async () => { + try { + await store.putBucketLifecycle(bucket, [ + { + id: 'expiration1', + prefix: 'logs/', + status: 'Enabled', + day: 1 + } + ]); + assert.fail('expected an error to be thrown'); + } catch (e) { + assert.equal( + e.message, + 'Rule must includes expiration or noncurrentVersionExpiration or abortMultipartUpload or transition or noncurrentVersionTransition' + ); + } + }); + it('should put the lifecycle with old api', async () => { const putresult1 = await store.putBucketLifecycle(bucket, [ { @@ -774,6 +792,7 @@ describe('test/bucket.test.js', () => { days: 1 } ]); + assert.equal(putresult1.res.status, 200); const putresult2 = await store.putBucketLifecycle(bucket, [ @@ -864,7 +883,7 @@ describe('test/bucket.test.js', () => { status: 'Enabled', transition: { createdBeforeDate: '2020-02-18T00:00:00.000Z', - storageClass: 'Archive' + storageClass: 'IA' }, expiration: { createdBeforeDate: '2020-02-17T00:00:00.000Z' @@ -893,6 +912,39 @@ describe('test/bucket.test.js', () => { } ]); assert.equal(putresult2.res.status, 200); + const putresult3 = await store.putBucketLifecycle(bucket, [ + { + id: 'transition3', + prefix: 'logs/', + status: 'Enabled', + transition: { + days: 20, + storageClass: 'ColdArchive' + }, + tag: { + key: 'test3', + value: '123' + } + } + ]); + assert.equal(putresult3.res.status, 200); + // Regions that need to support DeepColdArchive + const putresult4 = await store.putBucketLifecycle(bucket, [ + { + id: 'transition4', + prefix: 'logs/', + status: 'Enabled', + transition: { + days: 20, + storageClass: 'DeepColdArchive' + }, + tag: { + key: 'test4', + value: '123' + } + } + ]); + assert.equal(putresult4.res.status, 200); }); it('should put the lifecycle with expiration and Tag', async () => { @@ -1023,7 +1075,7 @@ describe('test/bucket.test.js', () => { ]); assert(false); } catch (error) { - assert(error.message.includes('IA or Archive')); + assert(error.message.includes('IA or Archive or ColdArchive or DeepColdArchive')); } }); @@ -1206,7 +1258,7 @@ describe('test/bucket.test.js', () => { }); it('should throw error when rule have no expiration or abortMultipartUpload', async () => { - const errorMessage = 'expiration or abortMultipartUpload'; + const errorMessage = 'expiration or noncurrentVersionExpiration or abortMultipartUpload'; try { await store.putBucketLifecycle(bucket, [ { diff --git a/test/node/multiversion.test.js b/test/node/multiversion.test.js index c6210b74..8af82d61 100644 --- a/test/node/multiversion.test.js +++ b/test/node/multiversion.test.js @@ -22,7 +22,8 @@ describe('test/multiversion.test.js', () => { ].forEach((moreConfigs, idx) => { describe(`test multiversion in iterate ${idx}`, () => { before(async () => { - store = oss({ ...config, ...moreConfigs }); + // oss-ap-southeast-1 suport PutBucketLifecycle DeepColdArchive + store = oss({ ...config, ...moreConfigs, region: 'oss-ap-southeast-1' }); bucket = `ali-oss-test-bucket-version-${prefix.replace(/[/.]/g, '-')}${idx}`; const result = await store.putBucket(bucket); @@ -117,12 +118,11 @@ describe('test/multiversion.test.js', () => { }); describe('putBucketLifecycle() getBucketLifecycle()', async () => { - it('should putBucketLifecycle with NoncurrentVersionExpiration', async () => { + it('should putBucketLifecycle with noncurrentVersionExpiration', async () => { const putresult1 = await store.putBucketLifecycle( bucket, [ { - id: 'expiration1', prefix: 'logs/', status: 'Enabled', expiration: { @@ -131,6 +131,13 @@ describe('test/multiversion.test.js', () => { noncurrentVersionExpiration: { noncurrentDays: 1 } + }, + { + prefix: 'logss/', + status: 'Enabled', + noncurrentVersionExpiration: { + noncurrentDays: 1 + } } ], { @@ -142,6 +149,7 @@ describe('test/multiversion.test.js', () => { const { rules } = await store.getBucketLifecycle(bucket); assert.strictEqual(rules[0].noncurrentVersionExpiration.noncurrentDays, '1'); }); + it('should putBucketLifecycle with expiredObjectDeleteMarker', async () => { const putresult1 = await store.putBucketLifecycle(bucket, [ { @@ -151,7 +159,7 @@ describe('test/multiversion.test.js', () => { expiration: { expiredObjectDeleteMarker: 'true' }, - NoncurrentVersionExpiration: { + noncurrentVersionExpiration: { noncurrentDays: 1 } } @@ -163,26 +171,55 @@ describe('test/multiversion.test.js', () => { }); it('should putBucketLifecycle with noncurrentVersionTransition', async () => { - const putresult1 = await store.putBucketLifecycle(bucket, [ - { - id: 'expiration1', - prefix: 'logs/', - status: 'Enabled', - noncurrentVersionTransition: { - noncurrentDays: '10', - storageClass: 'IA' + const putresult = await store.putBucketLifecycle( + bucket, + [ + { + prefix: 'log/', + status: 'Enabled', + noncurrentVersionTransition: { + noncurrentDays: '10', + storageClass: 'IA' + } + }, + { + prefix: 'log/', + status: 'Enabled', + noncurrentVersionTransition: { + noncurrentDays: '10', + storageClass: 'Archive' + } + }, + { + prefix: 'log/', + status: 'Enabled', + noncurrentVersionTransition: { + noncurrentDays: '10', + storageClass: 'ColdArchive' + } + }, + { + prefix: 'log/', + status: 'Enabled', + noncurrentVersionTransition: { + noncurrentDays: '10', + storageClass: 'DeepColdArchive' + } } - } - ]); - assert.equal(putresult1.res.status, 200); + ], + { headers: { 'x-oss-allow-same-action-overlap': 'true' } } + ); + assert.equal(putresult.res.status, 200); await utils.sleep(1000); + const { rules } = await store.getBucketLifecycle(bucket); const [ { noncurrentVersionTransition: { noncurrentDays, storageClass } } ] = rules; - assert(noncurrentDays === '10' && storageClass === 'IA'); + + assert(noncurrentDays === '10' && storageClass === 'IA' && rules.length === 4); }); });