aws / aws/aws-cdk

aws-s3-deployment: custom `logGroup` of `BucketDeployment` is prevented from deletion

Open
#35,632 3 comments 0 reactions 0 assignees View on GitHub
@aws-cdk/aws-s3-deployment bug effort/small good first issue p2
Dominant language
TypeScript
Stars
12.9k
Forks
4.6k
Avg merge
1d 19h
Merged PRs (30d)
71

Description

### Describe the bug

When a custom `logGroup` is passed to `BucketDeployment`, the log group is not reliably deleted with the Cloudformation Stack, despite having "delete" deletion policy and "DELETE_COMPLETE" status in Cloudformation resources list.

### Regression Issue

- [ ] Select this option if this issue appears to be a regression.

### Last Known Working CDK Library Version

_No response_

### Expected Behavior

A custom `logGroup` with "delete" deletion policy passed to `BucketDeployment` is deleted, when corresponding Cloudformation Stack is deleted.

### Current Behavior

To me it seems like the log group was actually recreated a couple of minutes after deletion (see the screenshot). Probably it happens because the BucketDeletion Lambda was executed after the Log Group was deleted. The Lambda was not triggered by us.

Image

### Reproduction Steps

1. Create the stack described below (requires also an "../assets" folder with files to upload)
2. Delete the stack
3. Eventually the log group is not deleted

We create two BucketDeployments, which upload files to the same bucket. One of them contains Cloudfront invalidation path.

Code snippet:

```ts
import * as path from 'path';
import { Construct } from 'constructs';
import * as cloudfront from 'aws-cdk-lib/aws-cloudfront';
import * as bucket from 'aws-cdk-lib/aws-s3';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as cdk from 'aws-cdk-lib';
import * as cloudfrontOrigins from 'aws-cdk-lib/aws-cloudfront-origins';
import * as logs from 'aws-cdk-lib/aws-logs';

export class ApplicationStack extends cdk.Stack {
public constructor(scope?: Construct, id?: string, props?: cdk.StackProps) {
super(scope, id, props);

const assetsBucket = new bucket.Bucket(this, 'assets-bucket', {
publicReadAccess: false,
bucketName: 'assets',
autoDeleteObjects: true,
removalPolicy: cdk.RemovalPolicy.DESTROY,
encryption: bucket.BucketEncryption.S3_MANAGED,
enforceSSL: true
});

const bucketOrigin = cloudfrontOrigins.S3BucketOrigin.withOriginAccessControl(assetsBucket);
const cloudfrontDistribution = new cloudfront.Distribution(this, 'cloudfront-distribution', {
priceClass: cloudfront.PriceClass.PRICE_CLASS_100,
minimumProtocolVersion: cloudfront.SecurityPolicyProtocol.TLS_V1_2_2021,
defaultBehavior: {
origin: bucketOrigin,
viewerProtocolPolicy: cloudfront.ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: cloudfront.AllowedMethods.ALLOW_GET_HEAD,
originRequestPolicy: cloudfront.OriginRequestPolicy.ALL_VIEWER_EXCEPT_HOST_HEADER
}
});

const logGroup = new logs.LogGroup(this, 'upload-assets-log-group', {
logGroupName: `/aws/lambda/upload-assets-log-group`,
removalPolicy: cdk.RemovalPolicy.DESTROY,
retention: logs.RetentionDays.FIVE_DAYS
});

new s3deploy.BucketDeployment(this, 'upload-assets-svg', {
sources: [s3deploy.Source.asset(path.resolve(__dirname, '..', 'assets'))],
include: ['*.svg'],
destinationBucket: assetsBucket,
cacheControl: [
s3deploy.CacheControl.maxAge(cdk.Duration.hours(1)),
s3deploy.CacheControl.staleWhileRevalidate(cdk.Duration.minutes(10)),
s3deploy.CacheControl.staleIfError(cdk.Duration.days(1))
],
memoryLimit: 1024,
prune: false,
logGroup
});

new s3deploy.BucketDeployment(this, 'upload-assets', {
sources: [s3deploy.Source.asset(path.resolve(__dirname, '..', 'assets'))],
exclude: ['*.svg'],
destinationBucket: assetsBucket,
distribution: cloudfrontDistribution,
distributionPaths: ['/*'], // invalidate the whole cache on redeployment
cacheControl: [
s3deploy.CacheControl.maxAge(cdk.Duration.days(365)),
s3deploy.CacheControl.staleWhileRevalidate(cdk.Duration.minutes(10)),
s3deploy.CacheControl.staleIfError(cdk.Duration.days(1))
],
memoryLimit: 1024,
prune: true,
logGroup
});
}
}
```

### Possible Solution

We solved it by adding the Log Group to the dependencies of BucketDeployment's Lambda. I'd suggest to do it inside BucketDeployment in the CDK itself, when the `logGroup` property is passed.

```ts
import * as cdk from 'aws-cdk-lib';
import * as s3deploy from 'aws-cdk-lib/aws-s3-deployment';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import { IDependable } from 'constructs';

export class CustomBucketDeployment extends s3deploy.BucketDeployment {
constructor(scope: cdk.Stack, id: string, options: s3deploy.BucketDeploymentProps) {
super(scope, id, options);

if (options.logGroup) {
// Ensure that the log group won't be deleted before the custom resource lambda.
// Otherwise the lambda can be called during the stack deletion after the log group is deleted,
// which can lead to log group recreation even though it'll be marked as deleted in the stack.
this.addCustomResourceHandlerLambdaDependency(options.logGroup);
}
}

private addCustomResourceHandlerLambdaDependency(...deps: IDependable[]) {
const customResourceHandler = this.node.tryFindChild('CustomResourceHandler');

if (
customResourceHandler &&
'lambdaFunction' in customResourceHandler &&
customResourceHandler.lambdaFunction instanceof lambda.Function
) {
customResourceHandler.lambdaFunction.node.addDependency(...deps);
}
}
}

```

### Additional Information/Context

_No response_

### AWS CDK Library version (aws-cdk-lib)

2.211.0

### AWS CDK CLI version

2.1025.0

### Node.js Version

20.19.2

### OS

Ubuntu 24.04.3 LTS

### Language

TypeScript

### Language Version

_No response_

### Other information

_No response_

Contributor guide

Open the contributing guide

Research direction

Start at the aws-s3-deployment BucketDeployment entry point and inspect how its custom resource handler and supplied logGroup are connected during deletion. Reproduce the stack deletion scenario with two BucketDeployments, then verify that the custom log group remains available until the handler finishes and is ultimately deleted.

Written by the indexing model from the issue text.

Assessment

Tech stack
aws, typescript
Domain
cloud, infrastructure
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.