Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(s3): allow user to set Log Group on S3 Bucket autoDeleteObjects provider lambda #30394

Merged
merged 13 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
},
});
Comment on lines +302 to +308
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically you're adding useLogGroup functionality to all custom resource handlers not only the s3 ones right? But the functionality is only exposed to the s3 auto delete handler. It would be nice to expose useLogGroup to other handlers as I believe this issue doesn't only happen on s3 auto delete but also on all custom resource handlers.

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