Skip to content

Commit 95938b2

Browse files
authoredJun 17, 2024··
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*
1 parent a5c6d21 commit 95938b2

File tree

14 files changed

+358
-17
lines changed

14 files changed

+358
-17
lines changed
 

‎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

+14
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,11 @@
144144
" S3 bucket."
145145
]
146146
]
147+
},
148+
"LoggingConfig": {
149+
"LogGroup": {
150+
"Ref": "MyLogGroup5C0DAD85"
151+
}
147152
}
148153
},
149154
"DependsOn": [
@@ -447,6 +452,15 @@
447452
"DependsOn": [
448453
"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2"
449454
]
455+
},
456+
"MyLogGroup5C0DAD85": {
457+
"Type": "AWS::Logs::LogGroup",
458+
"Properties": {
459+
"LogGroupName": "MyLogGroup",
460+
"RetentionInDays": 731
461+
},
462+
"UpdateReplacePolicy": "Retain",
463+
"DeletionPolicy": "Retain"
450464
}
451465
},
452466
"Mappings": {

‎packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/manifest.json

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.js.snapshot/tree.json

+25
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎packages/@aws-cdk-testing/framework-integ/test/aws-s3/test/integ.bucket-auto-delete-objects.ts

+5
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { App, CustomResource, CustomResourceProvider, RemovalPolicy, Stack, Stac
33
import { IntegTest } from '@aws-cdk/integ-tests-alpha';
44
import { Construct } from 'constructs';
55
import * as s3 from 'aws-cdk-lib/aws-s3';
6+
import * as logs from 'aws-cdk-lib/aws-logs';
67
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';
78
import { STANDARD_CUSTOM_RESOURCE_PROVIDER_RUNTIME } from '../../config';
89

@@ -55,6 +56,10 @@ class TestStack extends Stack {
5556
resources: [bucketThatWillBeRemoved.bucketArn],
5657
}),
5758
});
59+
60+
s3.Bucket.setAutoDeleteObjectsLogGroup(this, new logs.LogGroup(this, 'MyLogGroup', {
61+
logGroupName: 'MyLogGroup',
62+
}));
5863
}
5964
}
6065

‎packages/@aws-cdk/custom-resource-handlers/lib/custom-resources-framework/classes.ts

+63-5
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,59 @@ export abstract class HandlerFrameworkClass extends ClassType {
274274
stmt.ret(expr.directCode('this.getOrCreateProvider(scope, uniqueid, props).serviceToken')),
275275
);
276276

277+
const idStatement = stmt.constVar(expr.ident('id'), expr.directCode('`${uniqueid}CustomResourceProvider`'));
278+
const stackFromScopeStatement = stmt.constVar(expr.ident('stack'), expr.directCode('Stack.of(scope)'));
279+
const logContextKeyStatement = stmt.constVar(expr.ident('key'), expr.directCode('`${uniqueid}CustomResourceLogGroup`'));
280+
const getProviderMethod = this.addMethod({
281+
name: 'getProvider',
282+
static: true,
283+
returnType: this.type,
284+
docs: {
285+
summary: 'Returns the stack-level singleton provider or undefined',
286+
},
287+
});
288+
getProviderMethod.addParameter({
289+
name: 'scope',
290+
type: CONSTRUCTS_MODULE.Construct,
291+
});
292+
getProviderMethod.addParameter({
293+
name: 'uniqueid',
294+
type: Type.STRING,
295+
});
296+
getProviderMethod.addBody(
297+
idStatement,
298+
stackFromScopeStatement,
299+
stmt.ret(expr.directCode(`stack.node.tryFindChild(id) as ${this.type}`)),
300+
);
301+
302+
const useLogGroupMethod = this.addMethod({
303+
name: 'useLogGroup',
304+
static: true,
305+
docs: {
306+
summary: 'Set the log group to be used by the singleton provider',
307+
},
308+
});
309+
useLogGroupMethod.addParameter({
310+
name: 'scope',
311+
type: CONSTRUCTS_MODULE.Construct,
312+
});
313+
useLogGroupMethod.addParameter({
314+
name: 'uniqueid',
315+
type: Type.STRING,
316+
});
317+
useLogGroupMethod.addParameter({
318+
name: 'logGroupName',
319+
type: Type.STRING,
320+
});
321+
useLogGroupMethod.addBody(
322+
stackFromScopeStatement,
323+
logContextKeyStatement,
324+
expr.directCode('stack.node.addMetadata(key, logGroupName)'),
325+
stmt.constVar(expr.ident('existing'), expr.directCode('this.getProvider(scope, uniqueid)')),
326+
stmt.if_(expr.directCode('existing'))
327+
.then(expr.directCode('existing.configureLambdaLogGroup(logGroupName)')),
328+
);
329+
277330
const getOrCreateProviderMethod = this.addMethod({
278331
name: 'getOrCreateProvider',
279332
static: true,
@@ -282,7 +335,7 @@ export abstract class HandlerFrameworkClass extends ClassType {
282335
summary: 'Returns a stack-level singleton for the custom resource provider.',
283336
},
284337
});
285-
const _scope = getOrCreateProviderMethod.addParameter({
338+
getOrCreateProviderMethod.addParameter({
286339
name: 'scope',
287340
type: CONSTRUCTS_MODULE.Construct,
288341
});
@@ -296,10 +349,15 @@ export abstract class HandlerFrameworkClass extends ClassType {
296349
optional: true,
297350
});
298351
getOrCreateProviderMethod.addBody(
299-
stmt.constVar(expr.ident('id'), expr.directCode('`${uniqueid}CustomResourceProvider`')),
300-
stmt.constVar(expr.ident('stack'), $T(CORE_MODULE.Stack).of(expr.directCode(_scope.spec.name))),
301-
stmt.constVar(expr.ident('existing'), expr.directCode(`stack.node.tryFindChild(id) as ${this.type}`)),
302-
stmt.ret(expr.directCode(`existing ?? new ${this.name}(stack, id, props)`)),
352+
idStatement,
353+
stackFromScopeStatement,
354+
stmt.constVar(expr.ident('provider'), expr.directCode(`this.getProvider(scope, uniqueid) ?? new ${this.name}(stack, id, props)`)),
355+
logContextKeyStatement,
356+
stmt.constVar(expr.ident('logGroupMetadata'),
357+
expr.directCode('stack.node.metadata.find(m => m.type === key)')),
358+
stmt.if_(expr.directCode('logGroupMetadata?.data'))
359+
.then(expr.directCode('provider.configureLambdaLogGroup(logGroupMetadata.data)')),
360+
stmt.ret(expr.directCode('provider')),
303361
);
304362

305363
const superProps = new ObjectLiteral([

‎packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider-core.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,37 @@ export class TestProvider extends CustomResourceProviderBase {
1212
return this.getOrCreateProvider(scope, uniqueid, props).serviceToken;
1313
}
1414

15+
/**
16+
* Returns the stack-level singleton provider or undefined
17+
*/
18+
public static getProvider(scope: Construct, uniqueid: string): TestProvider {
19+
const id = `${uniqueid}CustomResourceProvider`;
20+
const stack = Stack.of(scope);
21+
return stack.node.tryFindChild(id) as TestProvider;
22+
}
23+
24+
/**
25+
* Set the log group to be used by the singleton provider
26+
*/
27+
public static useLogGroup(scope: Construct, uniqueid: string, logGroupName: string): void {
28+
const stack = Stack.of(scope);
29+
const key = `${uniqueid}CustomResourceLogGroup`;
30+
stack.node.addMetadata(key, logGroupName);
31+
const existing = this.getProvider(scope, uniqueid);
32+
if (existing) existing.configureLambdaLogGroup(logGroupName);
33+
}
34+
1535
/**
1636
* Returns a stack-level singleton for the custom resource provider.
1737
*/
1838
public static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): TestProvider {
1939
const id = `${uniqueid}CustomResourceProvider`;
2040
const stack = Stack.of(scope);
21-
const existing = stack.node.tryFindChild(id) as TestProvider;
22-
return existing ?? new TestProvider(stack, id, props);
41+
const provider = this.getProvider(scope, uniqueid) ?? new TestProvider(stack, id, props);
42+
const key = `${uniqueid}CustomResourceLogGroup`;
43+
const logGroupMetadata = stack.node.metadata.find(m => m.type === key);
44+
if (logGroupMetadata?.data) provider.configureLambdaLogGroup(logGroupMetadata.data);
45+
return provider;
2346
}
2447

2548
public constructor(scope: Construct, id: string, props?: CustomResourceProviderOptions) {

‎packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/node-runtime/custom-resource-provider.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,37 @@ export class TestProvider extends CustomResourceProviderBase {
1111
return this.getOrCreateProvider(scope, uniqueid, props).serviceToken;
1212
}
1313

14+
/**
15+
* Returns the stack-level singleton provider or undefined
16+
*/
17+
public static getProvider(scope: Construct, uniqueid: string): TestProvider {
18+
const id = `${uniqueid}CustomResourceProvider`;
19+
const stack = Stack.of(scope);
20+
return stack.node.tryFindChild(id) as TestProvider;
21+
}
22+
23+
/**
24+
* Set the log group to be used by the singleton provider
25+
*/
26+
public static useLogGroup(scope: Construct, uniqueid: string, logGroupName: string): void {
27+
const stack = Stack.of(scope);
28+
const key = `${uniqueid}CustomResourceLogGroup`;
29+
stack.node.addMetadata(key, logGroupName);
30+
const existing = this.getProvider(scope, uniqueid);
31+
if (existing) existing.configureLambdaLogGroup(logGroupName);
32+
}
33+
1434
/**
1535
* Returns a stack-level singleton for the custom resource provider.
1636
*/
1737
public static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): TestProvider {
1838
const id = `${uniqueid}CustomResourceProvider`;
1939
const stack = Stack.of(scope);
20-
const existing = stack.node.tryFindChild(id) as TestProvider;
21-
return existing ?? new TestProvider(stack, id, props);
40+
const provider = this.getProvider(scope, uniqueid) ?? new TestProvider(stack, id, props);
41+
const key = `${uniqueid}CustomResourceLogGroup`;
42+
const logGroupMetadata = stack.node.metadata.find(m => m.type === key);
43+
if (logGroupMetadata?.data) provider.configureLambdaLogGroup(logGroupMetadata.data);
44+
return provider;
2245
}
2346

2447
public constructor(scope: Construct, id: string, props?: CustomResourceProviderOptions) {

‎packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider-core.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,37 @@ export class TestProvider extends CustomResourceProviderBase {
1212
return this.getOrCreateProvider(scope, uniqueid, props).serviceToken;
1313
}
1414

15+
/**
16+
* Returns the stack-level singleton provider or undefined
17+
*/
18+
public static getProvider(scope: Construct, uniqueid: string): TestProvider {
19+
const id = `${uniqueid}CustomResourceProvider`;
20+
const stack = Stack.of(scope);
21+
return stack.node.tryFindChild(id) as TestProvider;
22+
}
23+
24+
/**
25+
* Set the log group to be used by the singleton provider
26+
*/
27+
public static useLogGroup(scope: Construct, uniqueid: string, logGroupName: string): void {
28+
const stack = Stack.of(scope);
29+
const key = `${uniqueid}CustomResourceLogGroup`;
30+
stack.node.addMetadata(key, logGroupName);
31+
const existing = this.getProvider(scope, uniqueid);
32+
if (existing) existing.configureLambdaLogGroup(logGroupName);
33+
}
34+
1535
/**
1636
* Returns a stack-level singleton for the custom resource provider.
1737
*/
1838
public static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): TestProvider {
1939
const id = `${uniqueid}CustomResourceProvider`;
2040
const stack = Stack.of(scope);
21-
const existing = stack.node.tryFindChild(id) as TestProvider;
22-
return existing ?? new TestProvider(stack, id, props);
41+
const provider = this.getProvider(scope, uniqueid) ?? new TestProvider(stack, id, props);
42+
const key = `${uniqueid}CustomResourceLogGroup`;
43+
const logGroupMetadata = stack.node.metadata.find(m => m.type === key);
44+
if (logGroupMetadata?.data) provider.configureLambdaLogGroup(logGroupMetadata.data);
45+
return provider;
2346
}
2447

2548
public constructor(scope: Construct, id: string, props?: CustomResourceProviderOptions) {

‎packages/@aws-cdk/custom-resource-handlers/test/custom-resources-framework/expected/python-runtime/custom-resource-provider.ts

+25-2
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,37 @@ export class TestProvider extends CustomResourceProviderBase {
1111
return this.getOrCreateProvider(scope, uniqueid, props).serviceToken;
1212
}
1313

14+
/**
15+
* Returns the stack-level singleton provider or undefined
16+
*/
17+
public static getProvider(scope: Construct, uniqueid: string): TestProvider {
18+
const id = `${uniqueid}CustomResourceProvider`;
19+
const stack = Stack.of(scope);
20+
return stack.node.tryFindChild(id) as TestProvider;
21+
}
22+
23+
/**
24+
* Set the log group to be used by the singleton provider
25+
*/
26+
public static useLogGroup(scope: Construct, uniqueid: string, logGroupName: string): void {
27+
const stack = Stack.of(scope);
28+
const key = `${uniqueid}CustomResourceLogGroup`;
29+
stack.node.addMetadata(key, logGroupName);
30+
const existing = this.getProvider(scope, uniqueid);
31+
if (existing) existing.configureLambdaLogGroup(logGroupName);
32+
}
33+
1434
/**
1535
* Returns a stack-level singleton for the custom resource provider.
1636
*/
1737
public static getOrCreateProvider(scope: Construct, uniqueid: string, props?: CustomResourceProviderOptions): TestProvider {
1838
const id = `${uniqueid}CustomResourceProvider`;
1939
const stack = Stack.of(scope);
20-
const existing = stack.node.tryFindChild(id) as TestProvider;
21-
return existing ?? new TestProvider(stack, id, props);
40+
const provider = this.getProvider(scope, uniqueid) ?? new TestProvider(stack, id, props);
41+
const key = `${uniqueid}CustomResourceLogGroup`;
42+
const logGroupMetadata = stack.node.metadata.find(m => m.type === key);
43+
if (logGroupMetadata?.data) provider.configureLambdaLogGroup(logGroupMetadata.data);
44+
return provider;
2245
}
2346

2447
public constructor(scope: Construct, id: string, props?: CustomResourceProviderOptions) {

‎packages/aws-cdk-lib/aws-s3/README.md

+17
Original file line numberDiff line numberDiff line change
@@ -656,6 +656,23 @@ switching this to `false` in a CDK version _before_ `1.126.0` will lead to
656656
all objects in the bucket being deleted. Be sure to update your bucket resources
657657
by deploying with CDK version `1.126.0` or later **before** switching this value to `false`.
658658

659+
Enabling `autoDeleteObjects` creates a stack-wide singleton Lambda that is responsible for deleting objects.
660+
To configure the lambda to use a different log group, use the `Bucket.setAutoDeleteObjectsLogGroup()` method:
661+
662+
```ts
663+
import * as logs from 'aws-cdk-lib/aws-logs';
664+
665+
const bucket = new s3.Bucket(this, 'MyTempFileBucket', {
666+
removalPolicy: cdk.RemovalPolicy.DESTROY,
667+
autoDeleteObjects: true,
668+
});
669+
670+
s3.Bucket.setAutoDeleteObjectsLogGroup(this, new logs.LogGroup(this, 'MyLogGroup', {
671+
logGroupName: 'MyLogGroup',
672+
retention: logs.RetentionDays.FIVE_YEARS
673+
}))
674+
```
675+
659676
## Transfer Acceleration
660677

661678
[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:

‎packages/aws-cdk-lib/aws-s3/lib/bucket.ts

+14
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { parseBucketArn, parseBucketName } from './util';
1010
import * as events from '../../aws-events';
1111
import * as iam from '../../aws-iam';
1212
import * as kms from '../../aws-kms';
13+
import * as logs from '../../aws-logs';
1314
import {
1415
CustomResource,
1516
Duration,
@@ -1466,6 +1467,9 @@ export interface BucketProps {
14661467
*
14671468
* Requires the `removalPolicy` to be set to `RemovalPolicy.DESTROY`.
14681469
*
1470+
* A custom resource along with a provider lambda will be created for
1471+
* emptying the bucket.
1472+
*
14691473
* **Warning** if you have deployed a bucket with `autoDeleteObjects: true`,
14701474
* switching this to `false` in a CDK version *before* `1.126.0` will lead to
14711475
* all objects in the bucket being deleted. Be sure to update your bucket resources
@@ -1882,6 +1886,16 @@ export class Bucket extends BucketBase {
18821886
}
18831887
}
18841888

1889+
/**
1890+
* Set the log group on the stack wide singleton AutoDeleteObjects provider lambda.
1891+
*
1892+
* @param stack the stack with the singleton AutoDeleteObjects provider lambda.
1893+
* @param logGroup the log group to use on the lambda.
1894+
*/
1895+
public static setAutoDeleteObjectsLogGroup(stack: Stack, logGroup: logs.ILogGroup): void {
1896+
AutoDeleteObjectsProvider.useLogGroup(stack, AUTO_DELETE_OBJECTS_RESOURCE_TYPE, logGroup.logGroupName);
1897+
}
1898+
18851899
public readonly bucketArn: string;
18861900
public readonly bucketName: string;
18871901
public readonly bucketDomainName: string;

0 commit comments

Comments
 (0)
Please sign in to comment.