Skip to content

Commit

Permalink
chore(s3): add validation on required properties for lifecycle rules
Browse files Browse the repository at this point in the history
  • Loading branch information
go-to-k committed Oct 18, 2024
1 parent 2ae6bd4 commit ea02015
Show file tree
Hide file tree
Showing 2 changed files with 174 additions and 1 deletion.
14 changes: 14 additions & 0 deletions packages/aws-cdk-lib/aws-s3/lib/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,20 @@ export class Bucket extends BucketBase {
if (!this.lifecycleRules || this.lifecycleRules.length === 0) {
return undefined;
}
const isValid = this.lifecycleRules.every(
(rule: LifecycleRule): boolean =>
rule.abortIncompleteMultipartUploadAfter !== undefined ||
rule.expiration !== undefined ||
rule.expirationDate !== undefined ||
rule.expiredObjectDeleteMarker !== undefined ||
rule.noncurrentVersionExpiration !== undefined ||
rule.noncurrentVersionsToRetain !== undefined ||
rule.noncurrentVersionTransitions !== undefined ||
rule.transitions !== undefined,
);
if (!isValid) {
throw new Error('All rules for `lifecycleRules` must have at least one of the following properties: `abortIncompleteMultipartUploadAfter`, `expiration`, `expirationDate`, `expiredObjectDeleteMarker`, `noncurrentVersionExpiration`, `noncurrentVersionsToRetain`, `noncurrentVersionTransitions`, or `transitions`');
}

const self = this;

Expand Down
161 changes: 160 additions & 1 deletion packages/aws-cdk-lib/aws-s3/test/rules.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Template } from '../../assertions';
import { Duration, Stack } from '../../core';
import { App, Duration, Stack } from '../../core';
import { Bucket, StorageClass } from '../lib';

describe('rules', () => {
Expand Down Expand Up @@ -334,4 +334,163 @@ describe('rules', () => {
},
});
});

describe('required properties for rules', () => {
test('throw if there is a rule doesn\'t have required properties', () => {
const app = new App();
const stack = new Stack(app);
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
objectSizeLessThan: 300000,
objectSizeGreaterThan: 200000,
},
],
});
expect(() => {
app.synth();
}).toThrow(/All rules for `lifecycleRules` must have at least one of the following properties: `abortIncompleteMultipartUploadAfter`, `expiration`, `expirationDate`, `expiredObjectDeleteMarker`, `noncurrentVersionExpiration`, `noncurrentVersionsToRetain`, `noncurrentVersionTransitions`, or `transitions`/);
});

test('throw if there are a valid rule and a rule that doesn\'t have required properties.', () => {
const app = new App();
const stack = new Stack(app);
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
abortIncompleteMultipartUploadAfter: Duration.days(365),
},
{
objectSizeLessThan: 300000,
objectSizeGreaterThan: 200000,
},
],
});
expect(() => {
app.synth();
}).toThrow(/All rules for `lifecycleRules` must have at least one of the following properties: `abortIncompleteMultipartUploadAfter`, `expiration`, `expirationDate`, `expiredObjectDeleteMarker`, `noncurrentVersionExpiration`, `noncurrentVersionsToRetain`, `noncurrentVersionTransitions`, or `transitions`/);
});

test('don\'t throw with abortIncompleteMultipartUploadAfter', () => {
const stack = new Stack();
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
abortIncompleteMultipartUploadAfter: Duration.days(365),
},
],
});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();
});

test('don\'t throw with expiration', () => {
const stack = new Stack();
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
expiration: Duration.days(365),
},
],
});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();
});

test('don\'t throw with expirationDate', () => {
const stack = new Stack();
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
expirationDate: new Date('2024-01-01'),
},
],
});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();
});

test('don\'t throw with expiredObjectDeleteMarker', () => {
const stack = new Stack();
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
expiredObjectDeleteMarker: true,
},
],
});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();
});

test('don\'t throw with noncurrentVersionExpiration', () => {
const stack = new Stack();
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
noncurrentVersionExpiration: Duration.days(365),
},
],
});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();
});

test('don\'t throw with noncurrentVersionsToRetain', () => {
const stack = new Stack();
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
noncurrentVersionsToRetain: 10,
},
],
});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();
});

test('don\'t throw with noncurrentVersionTransitions', () => {
const stack = new Stack();
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
noncurrentVersionTransitions: [
{
storageClass: StorageClass.GLACIER_INSTANT_RETRIEVAL,
transitionAfter: Duration.days(10),
noncurrentVersionsToRetain: 1,
},
],
},
],
});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();
});

test('don\'t throw with transitions', () => {
const stack = new Stack();
new Bucket(stack, 'MyBucket', {
lifecycleRules: [
{
transitions: [{
storageClass: StorageClass.GLACIER,
transitionAfter: Duration.days(30),
}],
},
],
});
expect(() => {
Template.fromStack(stack);
}).not.toThrow();
});

});
});

0 comments on commit ea02015

Please sign in to comment.