From 95938b220ea998f002e16d4ea8e3fb9f4a7901e3 Mon Sep 17 00:00:00 2001 From: Samson Keung Date: Mon, 17 Jun 2024 12:31:01 -0700 Subject: [PATCH] feat(s3): allow user to set Log Group on S3 Bucket autoDeleteObjects provider lambda (#30394) ### Issue # (if applicable) Closes #24815. ### Reason for this change To allow log group customization on the custom resource lambda created for the `autoDeleteObjects` feature. ### Description of changes At the highest level overview, a static method `setAutoDeleteObjectsLogGroup` is added to the `Bucket` class. When it is called, it will set the log group on the `AutoDeleteObjectsProvider` lambda (i.e. setting the [`LoggingConfig.LogGroup`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-lambda-function-loggingconfig.html#cfn-lambda-function-loggingconfig-loggroup). In order to support the above change, 2 underlying changes had to be made: 1. `setAutoDeleteObjectsLogGroup(..)` needs to have a way to find the singleton `AutoDeleteObjectsProvider` lambda. This means a method needs to be added in the `AutoDeleteObjectsProvider` class that returns the singleton. Note that the `AutoDeleteObjectsProvider` class itself is code generated. So I have modified the code gen logic to generate the `getProvider(..)` method, which returns the singleton. 2. With a handle of the singleton of type `AutoDeleteObjectsProvider`, which wraps the actual `AWS::Lambda::Function`, we need a way to set the log group on the lambda. With `AutoDeleteObjectsProvider` extending the `CustomResourceProviderBase` type, a method is added to `CustomResourceProviderBase` class to set the log group. ### Description of how you validated changes Updated the integ test and ran it against my own AWS account ### 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* --- ...3-bucket-auto-delete-objects.template.json | 14 ++++ .../manifest.json | 6 ++ .../tree.json | 25 ++++++ .../test/integ.bucket-auto-delete-objects.ts | 5 ++ .../lib/custom-resources-framework/classes.ts | 68 ++++++++++++++-- .../custom-resource-provider-core.ts | 27 ++++++- .../node-runtime/custom-resource-provider.ts | 27 ++++++- .../custom-resource-provider-core.ts | 27 ++++++- .../custom-resource-provider.ts | 27 ++++++- packages/aws-cdk-lib/aws-s3/README.md | 17 ++++ packages/aws-cdk-lib/aws-s3/lib/bucket.ts | 14 ++++ .../aws-cdk-lib/aws-s3/test/bucket.test.ts | 77 +++++++++++++++++++ .../custom-resource-provider-base.ts | 20 ++++- .../custom-resource-provider.test.ts | 21 +++++ 14 files changed, 358 insertions(+), 17 deletions(-) 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 3dade7cc0e247..9644a55534431 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 @@ -144,6 +144,11 @@ " S3 bucket." ] ] + }, + "LoggingConfig": { + "LogGroup": { + "Ref": "MyLogGroup5C0DAD85" + } } }, "DependsOn": [ @@ -447,6 +452,15 @@ "DependsOn": [ "AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2" ] + }, + "MyLogGroup5C0DAD85": { + "Type": "AWS::Logs::LogGroup", + "Properties": { + "LogGroupName": "MyLogGroup", + "RetentionInDays": 731 + }, + "UpdateReplacePolicy": "Retain", + "DeletionPolicy": "Retain" } }, "Mappings": { 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 fcdfba654b94f..74d87c6fdb89c 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 @@ -130,6 +130,12 @@ "data": "AWS679f53fac002430cb0da5b7982bd22872D164C4C" } ], + "/cdk-s3-bucket-auto-delete-objects/MyLogGroup/Resource": [ + { + "type": "aws:cdk:logicalId", + "data": "MyLogGroup5C0DAD85" + } + ], "/cdk-s3-bucket-auto-delete-objects/BootstrapVersion": [ { "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 4c90ee3e2e476..16d8b8c84c8c3 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 @@ -545,6 +545,31 @@ "version": "0.0.0" } }, + "MyLogGroup": { + "id": "MyLogGroup", + "path": "cdk-s3-bucket-auto-delete-objects/MyLogGroup", + "children": { + "Resource": { + "id": "Resource", + "path": "cdk-s3-bucket-auto-delete-objects/MyLogGroup/Resource", + "attributes": { + "aws:cdk:cloudformation:type": "AWS::Logs::LogGroup", + "aws:cdk:cloudformation:props": { + "logGroupName": "MyLogGroup", + "retentionInDays": 731 + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.CfnLogGroup", + "version": "0.0.0" + } + } + }, + "constructInfo": { + "fqn": "aws-cdk-lib.aws_logs.LogGroup", + "version": "0.0.0" + } + }, "BootstrapVersion": { "id": "BootstrapVersion", "path": "cdk-s3-bucket-auto-delete-objects/BootstrapVersion", 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..d4992a3d2b12a 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'; @@ -55,6 +56,10 @@ class TestStack extends Stack { resources: [bucketThatWillBeRemoved.bucketArn], }), }); + + s3.Bucket.setAutoDeleteObjectsLogGroup(this, new logs.LogGroup(this, 'MyLogGroup', { + logGroupName: 'MyLogGroup', + })); } } diff --git a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts index bee5e57cb3f35..c6fc44e5200ef 100644 --- a/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts +++ b/packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts @@ -274,6 +274,59 @@ export abstract class HandlerFrameworkClass extends ClassType { stmt.ret(expr.directCode('this.getOrCreateProvider(scope, uniqueid, props).serviceToken')), ); + const idStatement = stmt.constVar(expr.ident('id'), expr.directCode('`${uniqueid}CustomResourceProvider`')); + const stackFromScopeStatement = stmt.constVar(expr.ident('stack'), expr.directCode('Stack.of(scope)')); + const logContextKeyStatement = stmt.constVar(expr.ident('key'), expr.directCode('`${uniqueid}CustomResourceLogGroup`')); + const getProviderMethod = this.addMethod({ + name: 'getProvider', + static: true, + returnType: this.type, + docs: { + summary: 'Returns the stack-level singleton provider or undefined', + }, + }); + getProviderMethod.addParameter({ + name: 'scope', + type: CONSTRUCTS_MODULE.Construct, + }); + getProviderMethod.addParameter({ + name: 'uniqueid', + type: Type.STRING, + }); + getProviderMethod.addBody( + idStatement, + stackFromScopeStatement, + stmt.ret(expr.directCode(`stack.node.tryFindChild(id) as ${this.type}`)), + ); + + const useLogGroupMethod = this.addMethod({ + name: 'useLogGroup', + static: true, + docs: { + summary: 'Set the log group to be used by the singleton provider', + }, + }); + useLogGroupMethod.addParameter({ + name: 'scope', + type: CONSTRUCTS_MODULE.Construct, + }); + useLogGroupMethod.addParameter({ + name: 'uniqueid', + type: Type.STRING, + }); + useLogGroupMethod.addParameter({ + name: 'logGroupName', + type: Type.STRING, + }); + useLogGroupMethod.addBody( + stackFromScopeStatement, + logContextKeyStatement, + expr.directCode('stack.node.addMetadata(key, logGroupName)'), + stmt.constVar(expr.ident('existing'), expr.directCode('this.getProvider(scope, uniqueid)')), + stmt.if_(expr.directCode('existing')) + .then(expr.directCode('existing.configureLambdaLogGroup(logGroupName)')), + ); + const getOrCreateProviderMethod = this.addMethod({ name: 'getOrCreateProvider', static: true, @@ -282,7 +335,7 @@ export abstract class HandlerFrameworkClass extends ClassType { summary: 'Returns a stack-level singleton for the custom resource provider.', }, }); - const _scope = getOrCreateProviderMethod.addParameter({ + getOrCreateProviderMethod.addParameter({ name: 'scope', type: CONSTRUCTS_MODULE.Construct, }); @@ -296,10 +349,15 @@ export abstract class HandlerFrameworkClass extends ClassType { optional: true, }); getOrCreateProviderMethod.addBody( - stmt.constVar(expr.ident('id'), expr.directCode('`${uniqueid}CustomResourceProvider`')), - stmt.constVar(expr.ident('stack'), $T(CORE_MODULE.Stack).of(expr.directCode(_scope.spec.name))), - stmt.constVar(expr.ident('existing'), expr.directCode(`stack.node.tryFindChild(id) as ${this.type}`)), - stmt.ret(expr.directCode(`existing ?? new ${this.name}(stack, id, props)`)), + idStatement, + stackFromScopeStatement, + stmt.constVar(expr.ident('provider'), expr.directCode(`this.getProvider(scope, uniqueid) ?? new ${this.name}(stack, id, props)`)), + logContextKeyStatement, + stmt.constVar(expr.ident('logGroupMetadata'), + expr.directCode('stack.node.metadata.find(m => m.type === key)')), + stmt.if_(expr.directCode('logGroupMetadata?.data')) + .then(expr.directCode('provider.configureLambdaLogGroup(logGroupMetadata.data)')), + stmt.ret(expr.directCode('provider')), ); const superProps = new ObjectLiteral([ diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts index 7d233b55451bd..3cc55b91da98d 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts @@ -12,14 +12,37 @@ export class TestProvider extends CustomResourceProviderBase { return this.getOrCreateProvider(scope, uniqueid, props).serviceToken; } + /** + * Returns the stack-level singleton provider or undefined + */ + public static getProvider(scope: Construct, uniqueid: string): TestProvider { + const id = `${uniqueid}CustomResourceProvider`; + const stack = Stack.of(scope); + return stack.node.tryFindChild(id) as TestProvider; + } + + /** + * Set the log group to be used by the singleton provider + */ + public static useLogGroup(scope: Construct, uniqueid: string, logGroupName: string): void { + const stack = Stack.of(scope); + const key = `${uniqueid}CustomResourceLogGroup`; + stack.node.addMetadata(key, logGroupName); + const existing = this.getProvider(scope, uniqueid); + if (existing) existing.configureLambdaLogGroup(logGroupName); + } + /** * Returns a stack-level singleton for the custom resource provider. */ public static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): TestProvider { const id = `${uniqueid}CustomResourceProvider`; const stack = Stack.of(scope); - const existing = stack.node.tryFindChild(id) as TestProvider; - return existing ?? new TestProvider(stack, id, props); + const provider = this.getProvider(scope, uniqueid) ?? new TestProvider(stack, id, props); + const key = `${uniqueid}CustomResourceLogGroup`; + const logGroupMetadata = stack.node.metadata.find(m => m.type === key); + if (logGroupMetadata?.data) provider.configureLambdaLogGroup(logGroupMetadata.data); + return provider; } public constructor(scope: Construct, id: string, props?: CustomResourceProviderOptions) { diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts index 8c45d66c40e93..a3fe3b02e15a6 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts @@ -11,14 +11,37 @@ export class TestProvider extends CustomResourceProviderBase { return this.getOrCreateProvider(scope, uniqueid, props).serviceToken; } + /** + * Returns the stack-level singleton provider or undefined + */ + public static getProvider(scope: Construct, uniqueid: string): TestProvider { + const id = `${uniqueid}CustomResourceProvider`; + const stack = Stack.of(scope); + return stack.node.tryFindChild(id) as TestProvider; + } + + /** + * Set the log group to be used by the singleton provider + */ + public static useLogGroup(scope: Construct, uniqueid: string, logGroupName: string): void { + const stack = Stack.of(scope); + const key = `${uniqueid}CustomResourceLogGroup`; + stack.node.addMetadata(key, logGroupName); + const existing = this.getProvider(scope, uniqueid); + if (existing) existing.configureLambdaLogGroup(logGroupName); + } + /** * Returns a stack-level singleton for the custom resource provider. */ public static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): TestProvider { const id = `${uniqueid}CustomResourceProvider`; const stack = Stack.of(scope); - const existing = stack.node.tryFindChild(id) as TestProvider; - return existing ?? new TestProvider(stack, id, props); + const provider = this.getProvider(scope, uniqueid) ?? new TestProvider(stack, id, props); + const key = `${uniqueid}CustomResourceLogGroup`; + const logGroupMetadata = stack.node.metadata.find(m => m.type === key); + if (logGroupMetadata?.data) provider.configureLambdaLogGroup(logGroupMetadata.data); + return provider; } public constructor(scope: Construct, id: string, props?: CustomResourceProviderOptions) { diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts index 46b3b7bdf532c..3c2f23a4a26e0 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts @@ -12,14 +12,37 @@ export class TestProvider extends CustomResourceProviderBase { return this.getOrCreateProvider(scope, uniqueid, props).serviceToken; } + /** + * Returns the stack-level singleton provider or undefined + */ + public static getProvider(scope: Construct, uniqueid: string): TestProvider { + const id = `${uniqueid}CustomResourceProvider`; + const stack = Stack.of(scope); + return stack.node.tryFindChild(id) as TestProvider; + } + + /** + * Set the log group to be used by the singleton provider + */ + public static useLogGroup(scope: Construct, uniqueid: string, logGroupName: string): void { + const stack = Stack.of(scope); + const key = `${uniqueid}CustomResourceLogGroup`; + stack.node.addMetadata(key, logGroupName); + const existing = this.getProvider(scope, uniqueid); + if (existing) existing.configureLambdaLogGroup(logGroupName); + } + /** * Returns a stack-level singleton for the custom resource provider. */ public static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): TestProvider { const id = `${uniqueid}CustomResourceProvider`; const stack = Stack.of(scope); - const existing = stack.node.tryFindChild(id) as TestProvider; - return existing ?? new TestProvider(stack, id, props); + const provider = this.getProvider(scope, uniqueid) ?? new TestProvider(stack, id, props); + const key = `${uniqueid}CustomResourceLogGroup`; + const logGroupMetadata = stack.node.metadata.find(m => m.type === key); + if (logGroupMetadata?.data) provider.configureLambdaLogGroup(logGroupMetadata.data); + return provider; } public constructor(scope: Construct, id: string, props?: CustomResourceProviderOptions) { diff --git a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts index 0ab4377ec0ebd..673cd5cdbac53 100644 --- a/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts +++ b/packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts @@ -11,14 +11,37 @@ export class TestProvider extends CustomResourceProviderBase { return this.getOrCreateProvider(scope, uniqueid, props).serviceToken; } + /** + * Returns the stack-level singleton provider or undefined + */ + public static getProvider(scope: Construct, uniqueid: string): TestProvider { + const id = `${uniqueid}CustomResourceProvider`; + const stack = Stack.of(scope); + return stack.node.tryFindChild(id) as TestProvider; + } + + /** + * Set the log group to be used by the singleton provider + */ + public static useLogGroup(scope: Construct, uniqueid: string, logGroupName: string): void { + const stack = Stack.of(scope); + const key = `${uniqueid}CustomResourceLogGroup`; + stack.node.addMetadata(key, logGroupName); + const existing = this.getProvider(scope, uniqueid); + if (existing) existing.configureLambdaLogGroup(logGroupName); + } + /** * Returns a stack-level singleton for the custom resource provider. */ public static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): TestProvider { const id = `${uniqueid}CustomResourceProvider`; const stack = Stack.of(scope); - const existing = stack.node.tryFindChild(id) as TestProvider; - return existing ?? new TestProvider(stack, id, props); + const provider = this.getProvider(scope, uniqueid) ?? new TestProvider(stack, id, props); + const key = `${uniqueid}CustomResourceLogGroup`; + const logGroupMetadata = stack.node.metadata.find(m => m.type === key); + if (logGroupMetadata?.data) provider.configureLambdaLogGroup(logGroupMetadata.data); + return provider; } public constructor(scope: Construct, id: string, props?: CustomResourceProviderOptions) { diff --git a/packages/aws-cdk-lib/aws-s3/README.md b/packages/aws-cdk-lib/aws-s3/README.md index 58b7084e620e2..eb652ae61920d 100644 --- a/packages/aws-cdk-lib/aws-s3/README.md +++ b/packages/aws-cdk-lib/aws-s3/README.md @@ -656,6 +656,23 @@ switching this to `false` in a CDK version _before_ `1.126.0` will lead to all objects in the bucket being deleted. Be sure to update your bucket resources by deploying with CDK version `1.126.0` or later **before** switching this value to `false`. +Enabling `autoDeleteObjects` creates a stack-wide singleton Lambda that is responsible for deleting objects. +To configure the lambda to use a different log group, use the `Bucket.setAutoDeleteObjectsLogGroup()` method: + +```ts +import * as logs from 'aws-cdk-lib/aws-logs'; + +const bucket = new s3.Bucket(this, 'MyTempFileBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, +}); + +s3.Bucket.setAutoDeleteObjectsLogGroup(this, new logs.LogGroup(this, 'MyLogGroup', { + logGroupName: 'MyLogGroup', + retention: logs.RetentionDays.FIVE_YEARS +})) +``` + ## Transfer Acceleration [Transfer Acceleration](https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html) can be configured to enable fast, easy, and secure transfers of files over long distances: diff --git a/packages/aws-cdk-lib/aws-s3/lib/bucket.ts b/packages/aws-cdk-lib/aws-s3/lib/bucket.ts index 5497df55798f2..7212e854bbc15 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, @@ -1466,6 +1467,9 @@ export interface BucketProps { * * Requires the `removalPolicy` to be set to `RemovalPolicy.DESTROY`. * + * A custom resource along with a provider lambda will be created for + * emptying the bucket. + * * **Warning** if you have deployed a bucket with `autoDeleteObjects: true`, * switching this to `false` in a CDK version *before* `1.126.0` will lead to * all objects in the bucket being deleted. Be sure to update your bucket resources @@ -1882,6 +1886,16 @@ export class Bucket extends BucketBase { } } + /** + * Set the log group on the stack wide singleton AutoDeleteObjects provider lambda. + * + * @param stack the stack with the singleton AutoDeleteObjects provider lambda. + * @param logGroup the log group to use on the lambda. + */ + public static setAutoDeleteObjectsLogGroup(stack: Stack, logGroup: logs.ILogGroup): void { + AutoDeleteObjectsProvider.useLogGroup(stack, AUTO_DELETE_OBJECTS_RESOURCE_TYPE, logGroup.logGroupName); + } + public readonly bucketArn: string; public readonly bucketName: string; public readonly bucketDomainName: string; 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 4d97390952ee5..d1a485a1534ef 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'; @@ -3493,6 +3494,82 @@ describe('bucket', () => { })).toThrow(/Cannot use \'autoDeleteObjects\' property on a bucket without setting removal policy to \'DESTROY\'/); }); + test('setAutoDeleteObjectsLogGroup to update AutoDeleteObjectsProvider LoggingConfig after Bucket creation', () => { + const stack = new cdk.Stack(); + + new s3.Bucket(stack, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + }); + + s3.Bucket.setAutoDeleteObjectsLogGroup(stack, new logs.LogGroup(stack, 'FirstLogGroup', { + logGroupName: 'MyFirstLogGroup', + })); + + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + LogGroupName: 'MyFirstLogGroup', + }); + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + LoggingConfig: { + LogGroup: { + 'Ref': 'FirstLogGroupFF5C2AA0', + }, + }, + }); + }); + + test('setAutoDeleteObjectsLogGroup before Bucket creation', () => { + const stack = new cdk.Stack(); + + s3.Bucket.setAutoDeleteObjectsLogGroup(stack, new logs.LogGroup(stack, 'FirstLogGroup', { + logGroupName: 'MyFirstLogGroup', + })); + new s3.Bucket(stack, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + }); + + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + LogGroupName: 'MyFirstLogGroup', + }); + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + LoggingConfig: { + LogGroup: { + 'Ref': 'FirstLogGroupFF5C2AA0', + }, + }, + }); + }); + + test('setAutoDeleteObjectsLogGroup multiple times should take the latest Log Group', () => { + const stack = new cdk.Stack(); + + s3.Bucket.setAutoDeleteObjectsLogGroup(stack, new logs.LogGroup(stack, 'FirstLogGroup', { + logGroupName: 'MyFirstLogGroup', + })); + new s3.Bucket(stack, 'MyBucket', { + removalPolicy: cdk.RemovalPolicy.DESTROY, + autoDeleteObjects: true, + }); + s3.Bucket.setAutoDeleteObjectsLogGroup(stack, new logs.LogGroup(stack, 'SecondLogGroup', { + logGroupName: 'MySecondLogGroup', + })); + + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + LogGroupName: 'MyFirstLogGroup', + }); + Template.fromStack(stack).hasResourceProperties('AWS::Logs::LogGroup', { + LogGroupName: 'MySecondLogGroup', + }); + Template.fromStack(stack).hasResourceProperties('AWS::Lambda::Function', { + LoggingConfig: { + LogGroup: { + 'Ref': 'SecondLogGroup8CDA9B9E', + }, + }, + }); + }); + test('bucket with transfer acceleration turned on', () => { const stack = new cdk.Stack(); new s3.Bucket(stack, 'MyBucket', { 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..d722a3948fcf4 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 @@ -51,6 +51,7 @@ export abstract class CustomResourceProviderBase extends Construct { private _codeHash?: string; private policyStatements?: any[]; private role?: CfnResource; + private handler: CfnResource; /** * The ARN of the provider's AWS Lambda function which should be used as the `serviceToken` when defining a custom @@ -129,7 +130,7 @@ export abstract class CustomResourceProviderBase extends Construct { const timeout = props.timeout ?? Duration.minutes(15); const memory = props.memorySize ?? Size.mebibytes(128); - const handler = new CfnResource(this, 'Handler', { + this.handler = new CfnResource(this, 'Handler', { type: 'AWS::Lambda::Function', properties: { Code: code, @@ -144,14 +145,25 @@ export abstract class CustomResourceProviderBase extends Construct { }); if (this.role) { - handler.addDependency(this.role); + this.handler.addDependency(this.role); } if (metadata) { - Object.entries(metadata).forEach(([k, v]) => handler.addMetadata(k, v)); + Object.entries(metadata).forEach(([k, v]) => this.handler.addMetadata(k, v)); } - this.serviceToken = Token.asString(handler.getAtt('Arn')); + this.serviceToken = Token.asString(this.handler.getAtt('Arn')); + } + + /** + * Set the log group used by the lambda function backing this custom resource. + * + * @see http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-lambda-function.html#cfn-lambda-function-loggingconfig + */ + public configureLambdaLogGroup(logGroupName: string) { + this.handler.addPropertyOverride('LoggingConfig', { + LogGroup: logGroupName, + }); } /** 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 f326e4b34ae07..2bb620eef01cd 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 @@ -394,6 +394,27 @@ describe('custom resource provider', () => { }]); }); + test('configureLambdaLogGroup() sets the Lambda LoggingConfig', () => { + // GIVEN + const stack = new Stack(); + + // WHEN + const provider = CustomResourceProvider.getOrCreateProvider(stack, 'Custom:MyResourceType', { + codeDirectory: TEST_HANDLER, + runtime: DEFAULT_PROVIDER_RUNTIME, + policyStatements: [ + { statement1: 123 }, + { statement2: { foo: 111 } }, + ], + }); + provider.configureLambdaLogGroup('MyTestLogGroup'); + + // THEN + const template = toCloudFormation(stack); + const role = template.Resources.CustomMyResourceTypeCustomResourceProviderHandler29FBDD2A; + expect(role.Properties.LoggingConfig).toEqual({ LogGroup: 'MyTestLogGroup' }); + }); + test('memorySize, timeout and description', () => { // GIVEN const stack = new Stack();