From 4aa117b34c95555ea7d53dfa748a048196bf4044 Mon Sep 17 00:00:00 2001 From: "Kenta Goto (k.goto)" <24818752+go-to-k@users.noreply.github.com> Date: Sat, 19 Oct 2024 01:28:01 +0900 Subject: [PATCH] feat(s3): support `transitionDefaultMinimumObjectSize` for life cycle (#31778) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ### Issue # (if applicable) Closes #31777. ### Reason for this change TransitionDefaultMinimumObjectSize for lifecycles has been supported. It can be possible to indicate which default minimum object size behavior is applied to the lifecycle configuration. what's new: https://aws.amazon.com/about-aws/whats-new/2024/09/amazon-s3-default-minimum-object-size-lifecycle-transition-rules UserGuide: https://docs.aws.amazon.com/AmazonS3/latest/userguide/lifecycle-transition-general-considerations.html#lifecycle-configuration-constraints Cfn doc: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-s3-bucket-lifecycleconfiguration.html#cfn-s3-bucket-lifecycleconfiguration-transitiondefaultminimumobjectsize API: https://docs.aws.amazon.com/cli/latest/reference/s3api/put-bucket-lifecycle-configuration.html Still CFn documentation says “Property description not available”, so it is good to look at the API documentation for the description. ``` --transition-default-minimum-object-size (string) Indicates which default minimum object size behavior is applied to the lifecycle configuration. all_storage_classes_128K - Objects smaller than 128 KB will not transition to any storage class by default. varies_by_storage_class - Objects smaller than 128 KB will transition to Glacier Flexible Retrieval or Glacier Deep Archive storage classes. By default, all other storage classes will prevent transitions smaller than 128 KB. To customize the minimum object size for any transition you can add a filter that specifies a custom ObjectSizeGreaterThan or ObjectSizeLessThan in the body of your transition rule. Custom filters always take precedence over the default transition behavior. Possible values: varies_by_storage_class all_storage_classes_128K ``` ### Description of changes Add `TransitionDefaultMinimumObjectSize` to BucketProps. ### Description of how you validated changes Both unit and integ tests. ### Checklist - [x] My code adheres to the [CONTRIBUTING GUIDE](https://github.com/aws/aws-cdk/blob/main/CONTRIBUTING.md) and [DESIGN GUIDELINES](https://github.com/aws/aws-cdk/blob/main/docs/DESIGN_GUIDELINES.md) ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license* --- .../cdk.out | 1 + ...efaultTestDeployAssert9A611ED6.assets.json | 19 ++ ...aultTestDeployAssert9A611ED6.template.json | 36 ++++ .../integ.json | 12 ++ .../manifest.json | 121 +++++++++++ .../s3-lifecycle-transitions.assets.json | 19 ++ .../s3-lifecycle-transitions.template.json | 102 +++++++++ .../tree.json | 197 ++++++++++++++++++ .../test/integ.lifecycle-transitions.ts | 53 +++++ packages/aws-cdk-lib/aws-s3/README.md | 34 +++ packages/aws-cdk-lib/aws-s3/lib/bucket.ts | 37 +++- .../aws-cdk-lib/aws-s3/test/bucket.test.ts | 24 +++ 12 files changed, 654 insertions(+), 1 deletion(-) create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdk.out create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/integ.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/manifest.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/s3-lifecycle-transitions.assets.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/s3-lifecycle-transitions.template.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/tree.json create mode 100644 packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.ts diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdk.out new file mode 100644 index 0000000000000..c6e612584e352 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdk.out @@ -0,0 +1 @@ +{"version":"38.0.1"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.assets.json new file mode 100644 index 0000000000000..2b97edf118e7d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "source": { + "path": "cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.template.json new file mode 100644 index 0000000000000..ad9d0fb73d1dd --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.template.json @@ -0,0 +1,36 @@ +{ + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/integ.json new file mode 100644 index 0000000000000..891f7148a6a18 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/integ.json @@ -0,0 +1,12 @@ +{ + "version": "38.0.1", + "testCases": { + "cdk-integ-s3-lifecycle-transitions/DefaultTest": { + "stacks": [ + "s3-lifecycle-transitions" + ], + "assertionStack": "cdk-integ-s3-lifecycle-transitions/DefaultTest/DeployAssert", + "assertionStackName": "cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/manifest.json new file mode 100644 index 0000000000000..a3ae1e633c8fb --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/manifest.json @@ -0,0 +1,121 @@ +{ + "version": "38.0.1", + "artifacts": { + "s3-lifecycle-transitions.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "s3-lifecycle-transitions.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "s3-lifecycle-transitions": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "s3-lifecycle-transitions.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "notificationArns": [], + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/ddad2eaffb71d50ee55436f9aff0d51b46f314389ef9056ee42d2c2cf1e0ca83.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "s3-lifecycle-transitions.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "s3-lifecycle-transitions.assets" + ], + "metadata": { + "/s3-lifecycle-transitions/AllStorageClasses128K/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "AllStorageClasses128KB8241761" + } + ], + "/s3-lifecycle-transitions/VariesByStorageClass/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "VariesByStorageClassCD3C88D5" + } + ], + "/s3-lifecycle-transitions/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/s3-lifecycle-transitions/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "s3-lifecycle-transitions" + }, + "cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.assets": { + "type": "cdk:asset-manifest", + "properties": { + "file": "cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.assets.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6": { + "type": "aws:cloudformation:stack", + "environment": "aws://unknown-account/unknown-region", + "properties": { + "templateFile": "cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.template.json", + "terminationProtection": false, + "validateOnSynth": false, + "notificationArns": [], + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", + "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", + "requiresBootstrapStackVersion": 6, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", + "additionalDependencies": [ + "cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.assets" + ], + "lookupRole": { + "arn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-lookup-role-${AWS::AccountId}-${AWS::Region}", + "requiresBootstrapStackVersion": 8, + "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version" + } + }, + "dependencies": [ + "cdkintegs3lifecycletransitionsDefaultTestDeployAssert9A611ED6.assets" + ], + "metadata": { + "/cdk-integ-s3-lifecycle-transitions/DefaultTest/DeployAssert/BootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "BootstrapVersion" + } + ], + "/cdk-integ-s3-lifecycle-transitions/DefaultTest/DeployAssert/CheckBootstrapVersion": [ + { + "type": "aws:cdk:logicalId", + "data": "CheckBootstrapVersion" + } + ] + }, + "displayName": "cdk-integ-s3-lifecycle-transitions/DefaultTest/DeployAssert" + }, + "Tree": { + "type": "cdk:tree", + "properties": { + "file": "tree.json" + } + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/s3-lifecycle-transitions.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/s3-lifecycle-transitions.assets.json new file mode 100644 index 0000000000000..be5669105e6a6 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/s3-lifecycle-transitions.assets.json @@ -0,0 +1,19 @@ +{ + "version": "38.0.1", + "files": { + "ddad2eaffb71d50ee55436f9aff0d51b46f314389ef9056ee42d2c2cf1e0ca83": { + "source": { + "path": "s3-lifecycle-transitions.template.json", + "packaging": "file" + }, + "destinations": { + "current_account-current_region": { + "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", + "objectKey": "ddad2eaffb71d50ee55436f9aff0d51b46f314389ef9056ee42d2c2cf1e0ca83.json", + "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" + } + } + } + }, + "dockerImages": {} +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/s3-lifecycle-transitions.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/s3-lifecycle-transitions.template.json new file mode 100644 index 0000000000000..920525ca1009d --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/s3-lifecycle-transitions.template.json @@ -0,0 +1,102 @@ +{ + "Resources": { + "AllStorageClasses128KB8241761": { + "Type": "AWS::S3::Bucket", + "Properties": { + "LifecycleConfiguration": { + "Rules": [ + { + "Status": "Enabled", + "Transitions": [ + { + "StorageClass": "DEEP_ARCHIVE", + "TransitionInDays": 30 + } + ] + }, + { + "ObjectSizeGreaterThan": 200000, + "ObjectSizeLessThan": 300000, + "Status": "Enabled", + "Transitions": [ + { + "StorageClass": "ONEZONE_IA", + "TransitionInDays": 30 + } + ] + } + ], + "TransitionDefaultMinimumObjectSize": "all_storage_classes_128K" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + }, + "VariesByStorageClassCD3C88D5": { + "Type": "AWS::S3::Bucket", + "Properties": { + "LifecycleConfiguration": { + "Rules": [ + { + "Status": "Enabled", + "Transitions": [ + { + "StorageClass": "DEEP_ARCHIVE", + "TransitionInDays": 30 + } + ] + }, + { + "ObjectSizeGreaterThan": 200000, + "ObjectSizeLessThan": 300000, + "Status": "Enabled", + "Transitions": [ + { + "StorageClass": "ONEZONE_IA", + "TransitionInDays": 30 + } + ] + } + ], + "TransitionDefaultMinimumObjectSize": "varies_by_storage_class" + } + }, + "UpdateReplacePolicy": "Delete", + "DeletionPolicy": "Delete" + } + }, + "Parameters": { + "BootstrapVersion": { + "Type": "AWS::SSM::Parameter::Value", + "Default": "/cdk-bootstrap/hnb659fds/version", + "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]" + } + }, + "Rules": { + "CheckBootstrapVersion": { + "Assertions": [ + { + "Assert": { + "Fn::Not": [ + { + "Fn::Contains": [ + [ + "1", + "2", + "3", + "4", + "5" + ], + { + "Ref": "BootstrapVersion" + } + ] + } + ] + }, + "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI." + } + ] + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/tree.json new file mode 100644 index 0000000000000..de565fbb364c5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.js.snapshot/tree.json @@ -0,0 +1,197 @@ +{ + "version": "tree-0.1", + "tree": { + "id": "App", + "path": "", + "children": { + "s3-lifecycle-transitions": { + "id": "s3-lifecycle-transitions", + "path": "s3-lifecycle-transitions", + "children": { + "AllStorageClasses128K": { + "id": "AllStorageClasses128K", + "path": "s3-lifecycle-transitions/AllStorageClasses128K", + "children": { + "Resource": { + "id": "Resource", + "path": "s3-lifecycle-transitions/AllStorageClasses128K/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "lifecycleConfiguration": { + "rules": [ + { + "status": "Enabled", + "transitions": [ + { + "storageClass": "DEEP_ARCHIVE", + "transitionInDays": 30 + } + ] + }, + { + "status": "Enabled", + "transitions": [ + { + "storageClass": "ONEZONE_IA", + "transitionInDays": 30 + } + ], + "objectSizeLessThan": 300000, + "objectSizeGreaterThan": 200000 + } + ], + "transitionDefaultMinimumObjectSize": "all_storage_classes_128K" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "VariesByStorageClass": { + "id": "VariesByStorageClass", + "path": "s3-lifecycle-transitions/VariesByStorageClass", + "children": { + "Resource": { + "id": "Resource", + "path": "s3-lifecycle-transitions/VariesByStorageClass/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::S3::Bucket", + "aws:cdk:cloudformation:props": { + "lifecycleConfiguration": { + "rules": [ + { + "status": "Enabled", + "transitions": [ + { + "storageClass": "DEEP_ARCHIVE", + "transitionInDays": 30 + } + ] + }, + { + "status": "Enabled", + "transitions": [ + { + "storageClass": "ONEZONE_IA", + "transitionInDays": 30 + } + ], + "objectSizeLessThan": 300000, + "objectSizeGreaterThan": 200000 + } + ], + "transitionDefaultMinimumObjectSize": "varies_by_storage_class" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "s3-lifecycle-transitions/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "s3-lifecycle-transitions/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "cdk-integ-s3-lifecycle-transitions": { + "id": "cdk-integ-s3-lifecycle-transitions", + "path": "cdk-integ-s3-lifecycle-transitions", + "children": { + "DefaultTest": { + "id": "DefaultTest", + "path": "cdk-integ-s3-lifecycle-transitions/DefaultTest", + "children": { + "Default": { + "id": "Default", + "path": "cdk-integ-s3-lifecycle-transitions/DefaultTest/Default", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "DeployAssert": { + "id": "DeployAssert", + "path": "cdk-integ-s3-lifecycle-transitions/DefaultTest/DeployAssert", + "children": { + "BootstrapVersion": { + "id": "BootstrapVersion", + "path": "cdk-integ-s3-lifecycle-transitions/DefaultTest/DeployAssert/BootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + }, + "CheckBootstrapVersion": { + "id": "CheckBootstrapVersion", + "path": "cdk-integ-s3-lifecycle-transitions/DefaultTest/DeployAssert/CheckBootstrapVersion", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", + "version": "0.0.0" + } + }, + "Tree": { + "id": "Tree", + "path": "Tree", + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } + }, + "constructInfo": { + "fqn": "constructs.Construct", + "version": "10.3.0" + } + } +} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.ts new file mode 100644 index 0000000000000..fd192ee4531e5 --- /dev/null +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.lifecycle-transitions.ts @@ -0,0 +1,53 @@ +import { App, Duration, RemovalPolicy, Stack } from 'aws-cdk-lib'; +import { IntegTest } from '@aws-cdk/integ-tests-alpha'; +import { Bucket, StorageClass, TransitionDefaultMinimumObjectSize } from 'aws-cdk-lib/aws-s3'; + +const app = new App(); + +const stack = new Stack(app, 's3-lifecycle-transitions'); + +new Bucket(stack, 'AllStorageClasses128K', { + lifecycleRules: [ + { + transitions: [{ + storageClass: StorageClass.DEEP_ARCHIVE, + transitionAfter: Duration.days(30), + }], + }, + { + objectSizeLessThan: 300000, + objectSizeGreaterThan: 200000, + transitions: [{ + storageClass: StorageClass.ONE_ZONE_INFREQUENT_ACCESS, + transitionAfter: Duration.days(30), + }], + }, + ], + transitionDefaultMinimumObjectSize: TransitionDefaultMinimumObjectSize.ALL_STORAGE_CLASSES_128_K, + removalPolicy: RemovalPolicy.DESTROY, +}); + +new Bucket(stack, 'VariesByStorageClass', { + lifecycleRules: [ + { + transitions: [{ + storageClass: StorageClass.DEEP_ARCHIVE, + transitionAfter: Duration.days(30), + }], + }, + { + objectSizeLessThan: 300000, + objectSizeGreaterThan: 200000, + transitions: [{ + storageClass: StorageClass.ONE_ZONE_INFREQUENT_ACCESS, + transitionAfter: Duration.days(30), + }], + }, + ], + transitionDefaultMinimumObjectSize: TransitionDefaultMinimumObjectSize.VARIES_BY_STORAGE_CLASS, + removalPolicy: RemovalPolicy.DESTROY, +}); + +new IntegTest(app, 'cdk-integ-s3-lifecycle-transitions', { + testCases: [stack], +}); \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-s3/README.md b/packages/aws-cdk-lib/aws-s3/README.md index 3c400997ceba9..c93aa152c1d58 100644 --- a/packages/aws-cdk-lib/aws-s3/README.md +++ b/packages/aws-cdk-lib/aws-s3/README.md @@ -862,6 +862,40 @@ const bucket = new s3.Bucket(this, 'MyBucket', { }); ``` +To indicate which default minimum object size behavior is applied to the lifecycle configuration, use the +`transitionDefaultMinimumObjectSize` property. + +The default value of the property before September 2024 is `TransitionDefaultMinimumObjectSize.VARIES_BY_STORAGE_CLASS` +that allows objects smaller than 128 KB to be transitioned only to the S3 Glacier and S3 Glacier Deep Archive storage classes, +otherwise `TransitionDefaultMinimumObjectSize.ALL_STORAGE_CLASSES_128_K` that prevents objects smaller than 128 KB from being +transitioned to any storage class. + +To customize the minimum object size for any transition you +can add a filter that specifies a custom `objectSizeGreaterThan` or `objectSizeLessThan` for `lifecycleRules` +property. Custom filters always take precedence over the default transition behavior. + +```ts +new s3.Bucket(this, 'MyBucket', { + transitionDefaultMinimumObjectSize: s3.TransitionDefaultMinimumObjectSize.VARIES_BY_STORAGE_CLASS, + lifecycleRules: [ + { + transitions: [{ + storageClass: s3.StorageClass.DEEP_ARCHIVE, + transitionAfter: Duration.days(30), + }], + }, + { + objectSizeLessThan: 300000, + objectSizeGreaterThan: 200000, + transitions: [{ + storageClass: s3.StorageClass.ONE_ZONE_INFREQUENT_ACCESS, + transitionAfter: Duration.days(30), + }], + }, + ], +}); +``` + ## Object Lock Configuration [Object Lock](https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock-overview.html) diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts index 6143cea6b6ce1..6ca50b3fbc4fe 100644 --- a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts +++ b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts @@ -1403,6 +1403,24 @@ export abstract class TargetObjectKeyFormat { public abstract _render(): CfnBucket.LoggingConfigurationProperty['targetObjectKeyFormat']; } +/** + * The transition default minimum object size for lifecycle + */ +export enum TransitionDefaultMinimumObjectSize { + /** + * Objects smaller than 128 KB will not transition to any storage class by default. + */ + ALL_STORAGE_CLASSES_128_K = 'all_storage_classes_128K', + + /** + * Objects smaller than 128 KB will transition to Glacier Flexible Retrieval or Glacier + * Deep Archive storage classes. + * + * By default, all other storage classes will prevent transitions smaller than 128 KB. + */ + VARIES_BY_STORAGE_CLASS = 'varies_by_storage_class', +} + export interface BucketProps { /** * The kind of server-side encryption to apply to this bucket. @@ -1528,6 +1546,18 @@ export interface BucketProps { */ readonly lifecycleRules?: LifecycleRule[]; + /** + * Indicates which default minimum object size behavior is applied to the lifecycle configuration. + * + * To customize the minimum object size for any transition you can add a filter that specifies a custom + * `objectSizeGreaterThan` or `objectSizeLessThan` for `lifecycleRules` property. Custom filters always + * take precedence over the default transition behavior. + * + * @default - TransitionDefaultMinimumObjectSize.VARIES_BY_STORAGE_CLASS before September 2024, + * otherwise TransitionDefaultMinimumObjectSize.ALL_STORAGE_CLASSES_128_K. + */ + readonly transitionDefaultMinimumObjectSize?: TransitionDefaultMinimumObjectSize; + /** * The name of the index document (e.g. "index.html") for the website. Enables static website * hosting for this bucket. @@ -1910,6 +1940,7 @@ export class Bucket extends BucketBase { protected disallowPublicAccess?: boolean; private accessControl?: BucketAccessControl; private readonly lifecycleRules: LifecycleRule[] = []; + private readonly transitionDefaultMinimumObjectSize?: TransitionDefaultMinimumObjectSize; private readonly eventBridgeEnabled?: boolean; private readonly metrics: BucketMetrics[] = []; private readonly cors: CorsRule[] = []; @@ -1934,6 +1965,7 @@ export class Bucket extends BucketBase { const objectLockConfiguration = this.parseObjectLockConfig(props); this.objectOwnership = props.objectOwnership; + this.transitionDefaultMinimumObjectSize = props.transitionDefaultMinimumObjectSize; const resource = new CfnBucket(this, 'Resource', { bucketName: this.physicalName, bucketEncryption, @@ -2244,7 +2276,10 @@ export class Bucket extends BucketBase { const self = this; - return { rules: this.lifecycleRules.map(parseLifecycleRule) }; + return { + rules: this.lifecycleRules.map(parseLifecycleRule), + transitionDefaultMinimumObjectSize: this.transitionDefaultMinimumObjectSize, + }; function parseLifecycleRule(rule: LifecycleRule): CfnBucket.RuleProperty { const enabled = rule.enabled ?? true; diff --git a/packages/aws-cdk-lib/aws-s3/test/bucket.test.ts b/packages/aws-cdk-lib/aws-s3/test/bucket.test.ts index e526d6150d195..0ce54d5a999e4 100644 --- a/packages/aws-cdk-lib/aws-s3/test/bucket.test.ts +++ b/packages/aws-cdk-lib/aws-s3/test/bucket.test.ts @@ -3852,5 +3852,29 @@ describe('bucket', () => { }, }); }); + + test.each([ + [s3.TransitionDefaultMinimumObjectSize.ALL_STORAGE_CLASSES_128_K, 'all_storage_classes_128K'], + [s3.TransitionDefaultMinimumObjectSize.VARIES_BY_STORAGE_CLASS, 'varies_by_storage_class'], + ])('transitionDefaultMinimumObjectSize %s can be specified', (key, value) => { + const stack = new cdk.Stack(); + new s3.Bucket(stack, 'MyBucket', { + transitionDefaultMinimumObjectSize: key, + lifecycleRules: [ + { + expiration: cdk.Duration.days(365), + }, + ], + }); + + Template.fromStack(stack).hasResourceProperties('AWS::S3::Bucket', { + LifecycleConfiguration: { + TransitionDefaultMinimumObjectSize: value, + Rules: [{ + ExpirationInDays: 365, + }], + }, + }); + }); });