diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/cdk-s3-bucket-auto-delete-objects.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/cdk-s3-bucket-auto-delete-objects.assets.json index 5e6f2b22588b6..4b48afd645d6c 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/cdk-s3-bucket-auto-delete-objects.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/cdk-s3-bucket-auto-delete-objects.assets.json @@ -40,7 +40,7 @@ } } }, - "ca52d72e974e69c0fdda925830ae1f79d21d91a258406c8f760bee6d1c6411f2": { + "3ae504fafa243b1ad3ae91b8096d5e2677f2cf7f3c8b8a8fc2a7a95faae09ad9": { "source": { "path": "cdk-s3-bucket-auto-delete-objects.template.json", "packaging": "file" @@ -48,7 +48,7 @@ "destinations": { "current_account-current_region": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "ca52d72e974e69c0fdda925830ae1f79d21d91a258406c8f760bee6d1c6411f2.json", + "objectKey": "3ae504fafa243b1ad3ae91b8096d5e2677f2cf7f3c8b8a8fc2a7a95faae09ad9.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/cdk-s3-bucket-auto-delete-objects.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/cdk-s3-bucket-auto-delete-objects.template.json index 384a0ee128718..476952e8a29e2 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/cdk-s3-bucket-auto-delete-objects.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/cdk-s3-bucket-auto-delete-objects.template.json @@ -232,6 +232,15 @@ "UpdateReplacePolicy": "Delete", "DeletionPolicy": "Delete" }, + "LogGroupF5B46931": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "AutoDeleteObjectsLambdaLogs", + "RetentionInDays": 3 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" + }, "RemovedBucket4FCCEBAD": { "Type": "AWS::S3::Bucket", "Properties": { diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/manifest.json index 95910cda16d9b..cf3f18d1b6070 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "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}/ca52d72e974e69c0fdda925830ae1f79d21d91a258406c8f760bee6d1c6411f2.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3ae504fafa243b1ad3ae91b8096d5e2677f2cf7f3c8b8a8fc2a7a95faae09ad9.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -82,6 +82,12 @@ "data": "PutObjectsCustomResource" } ], + "/cdk-s3-bucket-auto-delete-objects/LogGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "LogGroupF5B46931" + } + ], "/cdk-s3-bucket-auto-delete-objects/RemovedBucket/Resource": [ { "type": "aws:cdk:logicalId", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/tree.json index 87bfd2098e2c1..ef0ab4f1d9a77 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/tree.json @@ -211,6 +211,31 @@ "version": "0.0.0" } }, + "LogGroup": { + "id": "LogGroup", + "path": "cdk-s3-bucket-auto-delete-objects/LogGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-s3-bucket-auto-delete-objects/LogGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": { + "logGroupName": "AutoDeleteObjectsLambdaLogs", + "retentionInDays": 3 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.LogGroup", + "version": "0.0.0" + } + }, "RemovedBucket": { "id": "RemovedBucket", "path": "cdk-s3-bucket-auto-delete-objects/RemovedBucket", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.ts index 9e2ef6ecbd938..9ab660d362df5 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.ts @@ -3,6 +3,7 @@ import { App, CustomResource, CustomResourceProvider, RemovalPolicy, Stack, Stac import { IntegTest } from '@aws-cdk/integ-tests-alpha'; import { Construct } from 'constructs'; import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as logs from 'aws-cdk-lib/aws-logs'; import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources'; import { STANDARD_CUSTOM_RESOURCE_PROVIDER_RUNTIME } from '../../config'; @@ -38,6 +39,10 @@ class TestStack extends Stack { const bucketThatWillBeRemoved = new s3.Bucket(this, 'RemovedBucket', { removalPolicy: RemovalPolicy.DESTROY, autoDeleteObjects: true, + autoDeleteObjectsLogGroup: new logs.LogGroup(this, 'LogGroup', { + logGroupName: 'AutoDeleteObjectsLambdaLogs', + retention: logs.RetentionDays.THREE_DAYS, + }), }); // Remove this bucket immediately diff --git a/packages/aws-cdk-lib/aws-s3/README.md b/packages/aws-cdk-lib/aws-s3/README.md index 20df480ede05c..e6d7e02d2648a 100644 --- a/packages/aws-cdk-lib/aws-s3/README.md +++ b/packages/aws-cdk-lib/aws-s3/README.md @@ -624,10 +624,17 @@ enable the`autoDeleteObjects` option. When `autoDeleteObjects` is enabled, `s3:PutBucketPolicy` is added to the bucket policy. This is done to allow the custom resource this feature is built on to add a deny policy for `s3:PutObject` to the bucket policy when a delete stack event occurs. Adding this deny policy prevents new objects from being written to the bucket. Doing this prevents race conditions with external bucket writers during the deletion process. +Pass a custom log group via the `autoDeleteObjectsLogGroup` option, which will be used by the custom resource lambda. + ```ts +import { ILogGroup } from 'aws-cdk-lib/aws-logs'; + +declare const logGroup: ILogGroup; + const bucket = new s3.Bucket(this, 'MyTempFileBucket', { removalPolicy: cdk.RemovalPolicy.DESTROY, autoDeleteObjects: true, + autoDeleteObjectsLogGroup: logGroup }); ``` diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts index a822d571ec59a..a4e9dbfcc6ca9 100644 --- a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts +++ b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts @@ -10,6 +10,7 @@ import { parseBucketArn, parseBucketName } from './util'; import * as events from '../../aws-events'; import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; +import * as logs from '../../aws-logs'; import { CustomResource, Duration, @@ -1464,6 +1465,8 @@ export interface BucketProps { * Whether all objects should be automatically deleted when the bucket is * removed from the stack or when the stack is deleted. * + * A custom resource will be created when set to `true`. + * * Requires the `removalPolicy` to be set to `RemovalPolicy.DESTROY`. * * **Warning** if you have deployed a bucket with `autoDeleteObjects: true`, @@ -1480,6 +1483,16 @@ export interface BucketProps { */ readonly autoDeleteObjects?: boolean; + /** + * The log group to use for the custom resource lambda backing the `autoDeleteObjects` feature + * + * When `autoDeleteObjects` is set to true, a customer resource backed by a Lambda function + * is created. The lambda will use the log group passed in to this prop if defined. + * + * @default the default log group created by Lambda + */ + readonly autoDeleteObjectsLogGroup?: logs.ILogGroup; + /** * Whether this bucket should have versioning turned on or not. * @@ -2008,7 +2021,7 @@ export class Bucket extends BucketBase { throw new Error('Cannot use \'autoDeleteObjects\' property on a bucket without setting removal policy to \'DESTROY\'.'); } - this.enableAutoDeleteObjects(); + this.enableAutoDeleteObjects(props.autoDeleteObjectsLogGroup); } if (this.eventBridgeEnabled) { @@ -2541,10 +2554,11 @@ export class Bucket extends BucketBase { }); } - private enableAutoDeleteObjects() { + private enableAutoDeleteObjects(logGroup?: logs.ILogGroup) { const provider = AutoDeleteObjectsProvider.getOrCreateProvider(this, AUTO_DELETE_OBJECTS_RESOURCE_TYPE, { useCfnResponseWrapper: false, description: `Lambda function for auto-deleting objects in ${this.bucketName} S3 bucket.`, + logGroupName: logGroup?.logGroupName, }); // Use a bucket policy to allow the custom resource to delete 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 35ca6cddcc956..c893072cab472 100644 --- a/packages/aws-cdk-lib/aws-s3/test/bucket.test.ts +++ b/packages/aws-cdk-lib/aws-s3/test/bucket.test.ts @@ -3,6 +3,7 @@ import { testDeprecated } from '@aws-cdk/cdk-build-tools'; import { Annotations, Match, Template } from '../../assertions'; import * as iam from '../../aws-iam'; import * as kms from '../../aws-kms'; +import * as logs from '../../aws-logs'; import * as cdk from '../../core'; import * as s3 from '../lib'; @@ -3463,6 +3464,26 @@ describe('bucket', () => { Template.fromStack(stack).resourceCountIs('AWS::Lambda::Function', 1); }); + test('with autoDeleteObjectsLogGroup', () => { + const stack = new cdk.Stack(); + + new s3.Bucket(stack, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + autoDeleteObjectsLogGroup: new logs.LogGroup(stack, 'LogGroup1', { + logGroupName: 'MyLogGroup', + }), + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + LoggingConfig: { + LogGroup: { + 'Ref': 'LogGroup106AAD846', + }, + }, + }); + }); + test('autoDeleteObjects throws if RemovalPolicy is not DESTROY', () => { const stack = new cdk.Stack(); diff --git a/packages/aws-cdk-lib/core/lib/custom-resource-provider/custom-resource-provider-base.ts b/packages/aws-cdk-lib/core/lib/custom-resource-provider/custom-resource-provider-base.ts index 7bcc24269207f..a260083dcb437 100644 --- a/packages/aws-cdk-lib/core/lib/custom-resource-provider/custom-resource-provider-base.ts +++ b/packages/aws-cdk-lib/core/lib/custom-resource-provider/custom-resource-provider-base.ts @@ -140,6 +140,7 @@ export abstract class CustomResourceProviderBase extends Construct { Runtime: props.runtimeName, Environment: this.renderEnvironmentVariables(props.environment), Description: props.description ?? undefined, + LoggingConfig: props.logGroupName ? { LogGroup: props.logGroupName } : undefined, }, }); diff --git a/packages/aws-cdk-lib/core/lib/custom-resource-provider/shared.ts b/packages/aws-cdk-lib/core/lib/custom-resource-provider/shared.ts index 0f5f13e250afc..f4c6fed73dd35 100644 --- a/packages/aws-cdk-lib/core/lib/custom-resource-provider/shared.ts +++ b/packages/aws-cdk-lib/core/lib/custom-resource-provider/shared.ts @@ -69,4 +69,11 @@ export interface CustomResourceProviderOptions { * @default - No description. */ readonly description?: string; + + /** + * Name of the Cloudwatch log group to be used by the provider AWS Lambda function. + * + * @default - a default log group created by AWS Lambda. + */ + readonly logGroupName?: string; } diff --git a/packages/aws-cdk-lib/core/test/custom-resource-provider/custom-resource-provider.test.ts b/packages/aws-cdk-lib/core/test/custom-resource-provider/custom-resource-provider.test.ts index 2348ae717ad3e..4f0c4244029c0 100644 --- a/packages/aws-cdk-lib/core/test/custom-resource-provider/custom-resource-provider.test.ts +++ b/packages/aws-cdk-lib/core/test/custom-resource-provider/custom-resource-provider.test.ts @@ -391,7 +391,7 @@ describe('custom resource provider', () => { }]); }); - test('memorySize, timeout and description', () => { + test('memorySize, timeout, description and logGroupName', () => { // GIVEN const stack = new Stack(); @@ -402,6 +402,7 @@ describe('custom resource provider', () => { memorySize: Size.gibibytes(2), timeout: Duration.minutes(5), description: 'veni vidi vici', + logGroupName: 'some log group name', }); // THEN @@ -410,7 +411,7 @@ describe('custom resource provider', () => { expect(lambda.Properties.MemorySize).toEqual(2048); expect(lambda.Properties.Timeout).toEqual(300); expect(lambda.Properties.Description).toEqual('veni vidi vici'); - + expect(lambda.Properties.LoggingConfig).toEqual({ LogGroup: 'some log group name' }); }); test('environment variables', () => {