Skip to content

Commit

Permalink
feat(s3): allow user to set Log Group on S3 Bucket autoDeleteObjects …
Browse files Browse the repository at this point in the history
…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*
  • Loading branch information
samson-keung authored Jun 17, 2024
1 parent a5c6d21 commit 95938b2
Show file tree
Hide file tree
Showing 14 changed files with 358 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@
" S3 bucket."
]
]
},
"LoggingConfig": {
"LogGroup": {
"Ref": "MyLogGroup5C0DAD85"
}
}
},
"DependsOn": [
Expand Down Expand Up @@ -447,6 +452,15 @@
"DependsOn": [
"AWS679f53fac002430cb0da5b7982bd2287ServiceRoleC1EA0FF2"
]
},
"MyLogGroup5C0DAD85": {
"Type": "AWS::Logs::LogGroup",
"Properties": {
"LogGroupName": "MyLogGroup",
"RetentionInDays": 731
},
"UpdateReplacePolicy": "Retain",
"DeletionPolicy": "Retain"
}
},
"Mappings": {
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -55,6 +56,10 @@ class TestStack extends Stack {
resources: [bucketThatWillBeRemoved.bucketArn],
}),
});

s3.Bucket.setAutoDeleteObjectsLogGroup(this, new logs.LogGroup(this, 'MyLogGroup', {
logGroupName: 'MyLogGroup',
}));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
});
Expand All @@ -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([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
17 changes: 17 additions & 0 deletions packages/aws-cdk-lib/aws-s3/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions packages/aws-cdk-lib/aws-s3/lib/bucket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 95938b2

Please sign in to comment.