Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(s3): add noncurrentVersionsToRetain property to lifecycle rule #20348

Merged
merged 7 commits into from
May 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions packages/@aws-cdk/aws-s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -551,3 +551,39 @@ bucket.transferAccelerationUrlForObject('objectname');
});

```

## Lifecycle Rule

[Managing lifecycle](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lifecycle-mgmt.html) can be configured transition or expiration actions.

```ts
const bucket = new s3.Bucket(this, 'MyBucket', {
lifecycleRules: [{
abortIncompleteMultipartUploadAfter: cdk.Duration.minutes(30),
enabled: false,
expiration: cdk.Duration.days(30),
expirationDate: new Date(),
expiredObjectDeleteMarker: false,
id: 'id',
noncurrentVersionExpiration: cdk.Duration.days(30),

// the properties below are optional
noncurrentVersionsToRetain: 123,
noncurrentVersionTransitions: [{
storageClass: s3.StorageClass.GLACIER,
transitionAfter: cdk.Duration.days(30),

// the properties below are optional
noncurrentVersionsToRetain: 123,
}],
prefix: 'prefix',
transitions: [{
storageClass: s3.StorageClass.GLACIER,

// the properties below are optional
transitionAfter: cdk.Duration.days(30),
transitionDate: new Date(),
}],
}]
});
```
5 changes: 4 additions & 1 deletion packages/@aws-cdk/aws-s3/lib/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1905,7 +1905,10 @@ export class Bucket extends BucketBase {
expirationDate: rule.expirationDate,
expirationInDays: rule.expiration?.toDays(),
id: rule.id,
noncurrentVersionExpirationInDays: rule.noncurrentVersionExpiration && rule.noncurrentVersionExpiration.toDays(),
noncurrentVersionExpiration: rule.noncurrentVersionExpiration && {
noncurrentDays: rule.noncurrentVersionExpiration.toDays(),
newerNoncurrentVersions: rule.noncurrentVersionsToRetain,
},
noncurrentVersionTransitions: mapOrUndefined(rule.noncurrentVersionTransitions, t => ({
storageClass: t.storageClass.value,
transitionInDays: t.transitionAfter.toDays(),
Expand Down
10 changes: 10 additions & 0 deletions packages/@aws-cdk/aws-s3/lib/rule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,16 @@ export interface LifecycleRule {
*/
readonly noncurrentVersionExpiration?: Duration;

/**
* Indicates a maximum number of noncurrent versions to retain.
*
* If there are this many more noncurrent versions,
* Amazon S3 permanently deletes them.
*
* @default No noncurrent versions to retain
*/
readonly noncurrentVersionsToRetain?: number;

/**
* One or more transition rules that specify when non-current objects transition to a specified storage class.
*
Expand Down
16 changes: 16 additions & 0 deletions packages/@aws-cdk/aws-s3/test/integ.lifecycle-expiration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { App, Duration, Stack } from '@aws-cdk/core';
import { Bucket } from '../lib';

const app = new App();

const stack = new Stack(app, 'aws-cdk-s3');

new Bucket(stack, 'MyBucket', {
lifecycleRules: [{
noncurrentVersionExpiration: Duration.days(30),
noncurrentVersionsToRetain: 123,
}],
versioned: true,
});

app.synth();
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"Resources": {
"MyBucketF68F3FF0": {
"Type": "AWS::S3::Bucket",
"Properties": {
"LifecycleConfiguration": {
"Rules": [
{
"NoncurrentVersionExpiration": {
"NewerNoncurrentVersions": 123,
"NoncurrentDays": 30
},
"Status": "Enabled"
}
]
},
"VersioningConfiguration": {
"Status": "Enabled"
}
},
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"version":"19.0.0"}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"version": "19.0.0",
"testCases": {
"aws-s3/test/integ.lifecycle-expiration": {
"stacks": [
"aws-cdk-s3"
],
"diffAssets": false,
"stackUpdateWorkflow": true
}
},
"synthContext": {},
"enableLookups": false
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"version": "19.0.0",
"artifacts": {
"Tree": {
"type": "cdk:tree",
"properties": {
"file": "tree.json"
}
},
"aws-cdk-s3": {
"type": "aws:cloudformation:stack",
"environment": "aws://unknown-account/unknown-region",
"properties": {
"templateFile": "aws-cdk-s3.template.json",
"validateOnSynth": false
},
"metadata": {
"/aws-cdk-s3/MyBucket/Resource": [
{
"type": "aws:cdk:logicalId",
"data": "MyBucketF68F3FF0"
}
]
},
"displayName": "aws-cdk-s3"
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"version": "tree-0.1",
"tree": {
"id": "App",
"path": "",
"children": {
"Tree": {
"id": "Tree",
"path": "Tree",
"constructInfo": {
"fqn": "@aws-cdk/core.Construct",
"version": "0.0.0"
}
},
"aws-cdk-s3": {
"id": "aws-cdk-s3",
"path": "aws-cdk-s3",
"children": {
"MyBucket": {
"id": "MyBucket",
"path": "aws-cdk-s3/MyBucket",
"children": {
"Resource": {
"id": "Resource",
"path": "aws-cdk-s3/MyBucket/Resource",
"attributes": {
"aws:cdk:cloudformation:type": "AWS::S3::Bucket",
"aws:cdk:cloudformation:props": {
"lifecycleConfiguration": {
"rules": [
{
"noncurrentVersionExpiration": {
"noncurrentDays": 30,
"newerNoncurrentVersions": 123
},
"status": "Enabled"
}
]
},
"versioningConfiguration": {
"status": "Enabled"
}
}
},
"constructInfo": {
"fqn": "@aws-cdk/aws-s3.CfnBucket",
"version": "0.0.0"
}
}
},
"constructInfo": {
"fqn": "@aws-cdk/aws-s3.Bucket",
"version": "0.0.0"
}
}
},
"constructInfo": {
"fqn": "@aws-cdk/core.Stack",
"version": "0.0.0"
}
}
},
"constructInfo": {
"fqn": "@aws-cdk/core.App",
"version": "0.0.0"
}
}
}
84 changes: 82 additions & 2 deletions packages/@aws-cdk/aws-s3/test/rules.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,9 @@ describe('rules', () => {
Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', {
LifecycleConfiguration: {
Rules: [{
NoncurrentVersionExpirationInDays: 10,
NoncurrentVersionExpiration: {
NoncurrentDays: 10,
},
NoncurrentVersionTransitions: [
{
NewerNoncurrentVersions: 1,
Expand Down Expand Up @@ -199,7 +201,85 @@ describe('rules', () => {
Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', {
LifecycleConfiguration: {
Rules: [{
NoncurrentVersionExpirationInDays: 10,
NoncurrentVersionExpiration: {
NoncurrentDays: 10,
},
NoncurrentVersionTransitions: [
{
StorageClass: 'GLACIER_IR',
TransitionInDays: 10,
},
],
Status: 'Enabled',
}],
},
});
});

test('Noncurrent expiration rule with versions to retain', () => {
// GIVEN
const stack = new Stack();

// WHEN: Noncurrent version to retain available
new Bucket(stack, 'Bucket1', {
versioned: true,
lifecycleRules: [{
noncurrentVersionExpiration: Duration.days(10),
noncurrentVersionsToRetain: 1,
noncurrentVersionTransitions: [
{
storageClass: StorageClass.GLACIER_INSTANT_RETRIEVAL,
transitionAfter: Duration.days(10),
},
],
}],
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', {
LifecycleConfiguration: {
Rules: [{
NoncurrentVersionExpiration: {
NoncurrentDays: 10,
NewerNoncurrentVersions: 1,
},
NoncurrentVersionTransitions: [
{
StorageClass: 'GLACIER_IR',
TransitionInDays: 10,
},
],
Status: 'Enabled',
}],
},
});
});

test('Noncurrent expiration rule without versions to retain', () => {
// GIVEN
const stack = new Stack();

// WHEN: Noncurrent version to retain not set
new Bucket(stack, 'Bucket1', {
versioned: true,
lifecycleRules: [{
noncurrentVersionExpiration: Duration.days(10),
noncurrentVersionTransitions: [
{
storageClass: StorageClass.GLACIER_INSTANT_RETRIEVAL,
transitionAfter: Duration.days(10),
},
],
}],
});

// THEN
Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', {
LifecycleConfiguration: {
Rules: [{
NoncurrentVersionExpiration: {
NoncurrentDays: 10,
},
NoncurrentVersionTransitions: [
{
StorageClass: 'GLACIER_IR',
Expand Down