Skip to content

Commit 6d1dc5b

Browse files
authored
feat(synthetics): enable auto delete lambdas via custom resource (#26580)
Synthetics [used](https://aws.amazon.com/about-aws/whats-new/2022/05/amazon-cloudwatch-synthetics-support-canary-resources-deletion/) to have a property `deleteLambdaResourceOnCanaryDeletion` that has since been deprecated and erased from cloudformation docs. Although this property still works today synthetics makes no promises that this is supported in the future. Here in CDK land, this PR serves as a replacement to the `deleteLambdaResourceOnCanaryDeletion` property (called `enableAutoDeleteLambdas` on the L2 Canary) by implementing a custom resource similar to what we have in S3 and ECR. **This PR deprecates `enableAutoDeleteLambdas` in favor of `cleanup: cleanup.LAMBDA`, an enum that achieves the same thing but via custom resource** Closes #18448 ---- *By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license*
1 parent 27b7201 commit 6d1dc5b

File tree

33 files changed

+4270
-1271
lines changed

33 files changed

+4270
-1271
lines changed

packages/@aws-cdk/aws-synthetics-alpha/README.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -102,27 +102,35 @@ const schedule = synthetics.Schedule.cron({
102102

103103
If you want the canary to run just once upon deployment, you can use `Schedule.once()`.
104104

105-
### Canary DeleteLambdaResourcesOnCanaryDeletion
105+
### Deleting underlying resources on canary deletion
106106

107-
You can specify whether the AWS CloudFormation is to also delete the Lambda functions and layers used by this canary, when the canary is deleted.
107+
When you delete a lambda, the following underlying resources are isolated in your AWS account:
108108

109-
This can be provisioned by setting the `enableAutoDeleteLambdas` property to `true` when we define the canary.
109+
- Lambda Function that runs your canary script
110+
- S3 Bucket for artifact storage
111+
- IAM roles and policies
112+
- Log Groups in CloudWatch Logs.
110113

111-
```ts
112-
const stack = new Stack();
114+
To learn more about these underlying resources, see
115+
[Synthetics Canaries Deletion](https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/synthetics_canaries_deletion.html).
116+
117+
In the CDK, you can configure your canary to delete the underlying lambda function when the canary is deleted.
118+
This can be provisioned by setting `cleanup: Cleanup.LAMBDA`. Note that this
119+
will create a custom resource under the hood that takes care of the lambda deletion for you.
113120

114-
const canary = new synthetics.Canary(stack, 'Canary', {
121+
```ts
122+
const canary = new synthetics.Canary(this, 'Canary', {
115123
test: synthetics.Test.custom({
116124
handler: 'index.handler',
117125
code: synthetics.Code.fromInline('/* Synthetics handler code'),
118126
}),
119-
enableAutoDeleteLambdas: true,
127+
cleanup: synthetics.Cleanup.LAMBDA,
120128
runtime: synthetics.Runtime.SYNTHETICS_NODEJS_PUPPETEER_4_0,
121129
});
122130
```
123131

124-
Synthetic Canaries create additional resources under the hood beyond Lambda functions. Setting `enableAutoDeleteLambdas: true` will take care of
125-
cleaning up Lambda functions on deletion, but you still have to manually delete other resources like S3 buckets and CloudWatch logs.
132+
> Note: To properly clean up your canary on deletion, you still have to manually delete other resources
133+
> like S3 buckets and CloudWatch logs.
126134
127135
### Configuring the Canary Script
128136

packages/@aws-cdk/aws-synthetics-alpha/lib/canary.ts

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ import { Runtime } from './runtime';
1010
import { Schedule } from './schedule';
1111
import { CloudWatchSyntheticsMetrics } from 'aws-cdk-lib/aws-synthetics/lib/synthetics-canned-metrics.generated';
1212
import { CfnCanary } from 'aws-cdk-lib/aws-synthetics';
13+
import { CustomResource, CustomResourceProvider, CustomResourceProviderRuntime } from 'aws-cdk-lib/core';
14+
import * as path from 'path';
15+
16+
const AUTO_DELETE_UNDERLYING_RESOURCES_RESOURCE_TYPE = 'Custom::SyntheticsAutoDeleteUnderlyingResources';
17+
const AUTO_DELETE_UNDERLYING_RESOURCES_TAG = 'aws-cdk:auto-delete-underlying-resources';
1318

1419
/**
1520
* Specify a test that the canary should run
@@ -50,6 +55,25 @@ export interface CustomTestOptions {
5055
readonly handler: string,
5156
}
5257

58+
/**
59+
* Different ways to clean up underlying Canary resources
60+
* when the Canary is deleted.
61+
*/
62+
export enum Cleanup {
63+
/**
64+
* Clean up nothing. The user is responsible for cleaning up
65+
* all resources left behind by the Canary.
66+
*/
67+
NOTHING = 'nothing',
68+
69+
/**
70+
* Clean up the underlying Lambda function only. The user is
71+
* responsible for cleaning up all other resources left behind
72+
* by the Canary.
73+
*/
74+
LAMBDA = 'lambda',
75+
}
76+
5377
/**
5478
* Options for specifying the s3 location that stores the data of each canary run. The artifacts bucket location **cannot**
5579
* be updated once the canary is created.
@@ -195,9 +219,18 @@ export interface CanaryProps {
195219
* @see https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-synthetics-canary.html#cfn-synthetics-canary-deletelambdaresourcesoncanarydeletion
196220
*
197221
* @default false
222+
* @deprecated this feature has been deprecated by the service team, use `cleanup: Cleanup.LAMBDA` instead which will use a Custom Resource to achieve the same effect.
198223
*/
199224
readonly enableAutoDeleteLambdas?: boolean;
200225

226+
/**
227+
* Specify the underlying resources to be cleaned up when the canary is deleted.
228+
* Using `Cleanup.LAMBDA` will create a Custom Resource to achieve this.
229+
*
230+
* @default Cleanup.NOTHING
231+
*/
232+
readonly cleanup?: Cleanup;
233+
201234
/**
202235
* Lifecycle rules for the generated canary artifact bucket. Has no effect
203236
* if a bucket is passed to `artifactsBucketLocation`. If you pass a bucket
@@ -248,6 +281,7 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
248281
* @internal
249282
*/
250283
private readonly _connections?: ec2.Connections;
284+
private readonly _resource: CfnCanary;
251285

252286
public constructor(scope: Construct, id: string, props: CanaryProps) {
253287
if (props.canaryName && !cdk.Token.isUnresolved(props.canaryName)) {
@@ -285,12 +319,49 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
285319
code: this.createCode(props),
286320
runConfig: this.createRunConfig(props),
287321
vpcConfig: this.createVpcConfig(props),
288-
deleteLambdaResourcesOnCanaryDeletion: props.enableAutoDeleteLambdas,
289322
});
323+
this._resource = resource;
290324

291325
this.canaryId = resource.attrId;
292326
this.canaryState = resource.attrState;
293327
this.canaryName = this.getResourceNameAttribute(resource.ref);
328+
329+
if (props.cleanup === Cleanup.LAMBDA ?? props.enableAutoDeleteLambdas) {
330+
this.cleanupUnderlyingResources();
331+
}
332+
}
333+
334+
private cleanupUnderlyingResources() {
335+
const provider = CustomResourceProvider.getOrCreateProvider(this, AUTO_DELETE_UNDERLYING_RESOURCES_RESOURCE_TYPE, {
336+
codeDirectory: path.join(__dirname, '..', 'custom-resource-handlers', 'dist', 'aws-synthetics-alpha', 'auto-delete-underlying-resources-handler'),
337+
useCfnResponseWrapper: false,
338+
runtime: CustomResourceProviderRuntime.NODEJS_18_X,
339+
description: `Lambda function for auto-deleting underlying resources created by ${this.canaryName}.`,
340+
policyStatements: [{
341+
Effect: 'Allow',
342+
Action: ['lambda:DeleteFunction'],
343+
Resource: this.lambdaArn(),
344+
}, {
345+
Effect: 'Allow',
346+
Action: ['synthetics:GetCanary'],
347+
Resource: '*',
348+
}],
349+
});
350+
351+
new CustomResource(this, 'AutoDeleteUnderlyingResourcesCustomResource', {
352+
resourceType: AUTO_DELETE_UNDERLYING_RESOURCES_RESOURCE_TYPE,
353+
serviceToken: provider.serviceToken,
354+
properties: {
355+
CanaryName: this.canaryName,
356+
},
357+
});
358+
359+
// We also tag the canary to record the fact that we want it autodeleted.
360+
// The custom resource will check this tag before actually doing the delete.
361+
// Because tagging and untagging will ALWAYS happen before the CR is deleted,
362+
// we can set `autoDeleteLambda: false` without the removal of the CR emptying
363+
// the lambda as a side effect.
364+
cdk.Tags.of(this._resource).add(AUTO_DELETE_UNDERLYING_RESOURCES_TAG, 'true');
294365
}
295366

296367
/**
@@ -402,6 +473,15 @@ export class Canary extends cdk.Resource implements ec2.IConnectable {
402473
});
403474
}
404475

476+
private lambdaArn() {
477+
return cdk.Stack.of(this).formatArn({
478+
service: 'lambda',
479+
resource: 'function',
480+
arnFormat: cdk.ArnFormat.COLON_RESOURCE_NAME,
481+
resourceName: 'cwsyn-*',
482+
});
483+
}
484+
405485
/**
406486
* Returns the code object taken in by the canary resource.
407487
*/

packages/@aws-cdk/aws-synthetics-alpha/package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,10 @@
6767
"cdk-build": {
6868
"env": {
6969
"AWSLINT_BASE_CONSTRUCT": true
70-
}
70+
},
71+
"pre": [
72+
"./scripts/airlift-custom-resource-handlers.sh"
73+
]
7174
},
7275
"keywords": [
7376
"aws",
@@ -84,6 +87,7 @@
8487
"license": "Apache-2.0",
8588
"devDependencies": {
8689
"@aws-cdk/cdk-build-tools": "0.0.0",
90+
"@aws-cdk/custom-resource-handlers": "0.0.0",
8791
"@aws-cdk/integ-runner": "0.0.0",
8892
"@aws-cdk/pkglint": "0.0.0",
8993
"@aws-cdk/integ-tests-alpha": "0.0.0",
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
3+
scriptdir=$(cd $(dirname $0) && pwd)
4+
customresourcedir=$(node -p "path.dirname(require.resolve('@aws-cdk/custom-resource-handlers/package.json'))")
5+
awscdklibdir=${scriptdir}/..
6+
7+
list_custom_resources() {
8+
for file in $customresourcedir/dist/aws-synthetics-alpha/*/index.js; do
9+
echo $file | rev | cut -d "/" -f 2-4 | rev
10+
done
11+
}
12+
13+
customresources=$(list_custom_resources)
14+
15+
echo $customresources
16+
17+
cd $awscdklibdir
18+
mkdir -p $awscdklibdir/custom-resource-handlers
19+
20+
for cr in $customresources; do
21+
mkdir -p $awscdklibdir/custom-resource-handlers/$cr
22+
cp $customresourcedir/$cr/index.js $awscdklibdir/custom-resource-handlers/$cr
23+
done

0 commit comments

Comments
 (0)