Using a Typescript Lambda as a CDK Construct Custom Resource

Using a Typescript Lambda as a CDK Construct Custom Resource
SHARE

Note: This has been updated here

Building off of the previous example of building CDK Custom Resources Constructs, in this post, we will look at the steps required to convert this from a JavaScript Lambda to a Typescript Lambda using some of the magic of projen.

CDK Construct Code

.
├── .eslintrc.json
├── .gitattributes
├── .gitignore
├── .jsii
├── .mergify.yml
├── .npmignore
├── .projenrc.js
├── API.md
├── LICENSE
├── README.md
├── package.json
├── src
│   ├── custom-resource-function.ts
│   ├── custom-resource.lambda.ts
│   └── index.ts
├── test
│   └── customResource.test.ts
├── tsconfig.dev.json
├── tsconfig.json
└── yarn.lock

Lambda Function

In this example, we will be using projen AWS Lambda Functions to automatically create the Lambda function that will be used by the Custom Resource. In order to create this function, we will create custom-resource.lambda.ts in the src directory. This will include the Lambda code to be executed during the CDK deployment.

After creating this file, running npx projen will create the custom-resource-function.ts file in the src directory. This is what we will be using in the index.ts file as the Lambda function for the Custom Resource.

After importing the function:

import { CustomResourceFunction } from './custom-resource-function';

We can reference it here:

const customResourceLambda = new CustomResourceFunction( this, 'customResourceLambda', { role: customResourceRole, architecture: Architecture.ARM_64, timeout: Duration.seconds(60), }, );

To define the runtime of the Lambda function, in the .projenrc file:

lambdaOptions: { runtime: awscdk.LambdaRuntime.NODEJS_18_X, bundlingOptions: { externals: ['aws-sdk'], sourcemap: true, }, },

Be sure to check out projen documentation for details on how to use this. https://projen.io/awscdk.html#aws-lambda-functions

NOTE: NODEJS_18_X is not currently available and this will default to NODEJS_14_X for now.

Provider

Next, we will include a Provider The Provider Framework will simplify some of the responses in the Lambda function. This is the recommended approach when using Custom Resources in CDKs.

const customResourceProvider = new Provider(this, 'customResourceProvider', { onEventHandler: customResourceLambda, });

Custom Resource

Finally, we will call this Custom Resource

const customResourceResult = new CustomResource(this, 'customResourceResult', { serviceToken: customResourceProvider.serviceToken, properties: { customResourceNumber: props.customResourceNumber, }, });

Custom Resource Lambda Code

The Typescript Lambda will use the type definitions from DefinitelyTyped to make things easier in the Lambda code.

import { CdkCustomResourceEvent, CdkCustomResourceResponse, Context, } from 'aws-lambda'; export const handler = async ( event: CdkCustomResourceEvent, context: Context, ): Promise<CdkCustomResourceResponse> => { console.log('Lambda is invoked with:' + JSON.stringify(event)); const response: CdkCustomResourceResponse = { StackId: event.StackId, RequestId: event.RequestId, LogicalResourceId: event.LogicalResourceId, PhysicalResourceId: context.logGroupName, }; if (event.RequestType == 'Delete') { response.Status = 'SUCCESS'; response.Data = { Result: 'None' }; return response; } try { const multiplyResult = event.ResourceProperties.customResourceNumber * 2; response.Status = 'SUCCESS'; response.Data = { Result: multiplyResult }; return response; } catch (error) { if (error instanceof Error) { response.Reason = error.message; } response.Status = 'FAILED'; response.Data = { Result: error }; return response; } };

Because we used the Provider when creating this Custom Resource, the response is significantly easier. We just need to return a SUCCESS or FAILED to the onEvent. We can do this with the CdkCustomResourceResponse.

Using the Construct

No actual changes are needed in the demo.

CDK Stack Code

import { CfnOutput, Stack, StackProps } from 'aws-cdk-lib'; import { Construct } from 'constructs'; import { CustomResourceExample } from 'cdk-custom-resource-construct-example'; export class CdkCustomResourceConstructDemoStack extends Stack { constructor(scope: Construct, id: string, props?: StackProps) { super(scope, id, props); const result = new CustomResourceExample(this, 'customResourceResult', { customResourceNumber: 5, }); new CfnOutput(this, 'customResourceOutput', { value: result.customResourceResult, }); } }

Deploy

git clone https://github.com/schuettc/cdk-custom-resource-construct-demo cd cdk-custom-resource-construct-demo yarn yarn run build yarn cdk deploy

Result

CdkCustomResourceConstructDemoStack: deploying...
 ✅  CdkCustomResourceConstructDemoStack

✨  Deployment time: 81.79s

Outputs:
CdkCustomResourceConstructDemoStack.customResourceOutput = 10
Stack ARN:
arn:aws:cloudformation:us-east-1:112233445566:stack/CdkCustomResourceConstructDemoStack/e47e0d90-742d-11ed-ab03-128d8230a997

✨  Total time: 84.66s

Resources