Building CDK Custom Resource Constructs with projen and Typescript

Building CDK Custom Resource Constructs with projen and Typescript
SHARE

Note: This has been updated here

CDK Constructs are a great way to create reusable and flexible code for yourself and others. projen makes it easy to create and publish the project. CDK Custom Resources are a way to do complex deployments and deploy AWS resources not native to CDK within a CDK. In this example, an AWS Lambda will be used as a Custom Resource from a published package.

Setting Up the Project

To do this yourself: npx projen new awscdk-construct

This will create a projen project to be used. More information is available here when working with AWS CDK Constructs. Additionally, this can be set up to publish to the very useful constructs.dev so that others can use your Construct.

Publishing the Project

Create an Access Token within npm to allow GitHub to publish to npm. Add this Access Token to GitHub in the Secrets section of the repository Settings as NPM_TOKEN

npx projen build git add . git commit -m 'commit comment' git push

This will start a GitHub workflow action that will publish to npm.

Example Construct Code

Standard Imports:

import * as path from 'path'; import * as cdk from 'aws-cdk-lib'; import * as iam from 'aws-cdk-lib/aws-iam'; import * as lambda from 'aws-cdk-lib/aws-lambda'; import { Construct } from 'constructs';

The Interface that will be used to take input from the deployable CDK. Because this is Typescript, these will be enforced but can be a wide range of types.

export interface CdkCustomResourceExampleProps extends cdk.ResourceProps { readonly customResourceNumber: number; }

This is the object that will be returned to the deployable CDK as the output from the Custom Resource:

export class CustomResourceExample extends Construct { public readonly customResourceResult: string; constructor( scope: Construct, id: string, props: CdkCustomResourceExampleProps, ) { super(scope, id);

The IAM Role used by the Lambda:

const role = new iam.Role(this, 'CustomResourceRole', { description: 'Custom Resource Construct Example', assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), managedPolicies: [ iam.ManagedPolicy.fromAwsManagedPolicyName( 'service-role/AWSLambdaBasicExecutionRole', ), ], });

The Lambda that will be created to be used as the Custom Resource:

const fn = new lambda.Function(this, 'CustomResourceLambda', { runtime: lambda.Runtime.NODEJS_14_X, code: lambda.Code.fromAsset(path.join(__dirname, '@/resources')), handler: 'index.handler', architecture: lambda.Architecture.ARM_64, role: role, timeout: cdk.Duration.minutes(1), });

The Custom Resource that will be invoked using the above Lambda taking in the properties supplied by the deployable CDK.

const customResourceResult = new cdk.CustomResource( this, 'customResourceResult', { serviceToken: fn.functionArn, properties: { customResourceNumber: props.customResourceNumber, }, }, ); this.customResourceResult = customResourceResult.getAttString('Result'); } }

The Lambda code being invoked is a simple multiplication but can be used to create complex deployments that are not native to CDK. In order to simplify deployment, NodeJS has been used for the Lambda instead of Typescript. Typescript must be transpiled to NodeJS to run as a Lambda and using Typescript for the Construct allows us to take advantage of Types within the deployable CDK while still allowing for an easy-to-use package. Python could also be used for the same reasons.

Demo CDK Code

To add the newly created package to the deployable CDK yarn add cdk-custom-resource-construct-example.

This will add it to the package.json like other installed dependencies.

"dependencies": { "aws-cdk-lib": "2.3.0", "cdk-custom-resource-construct-example": "*", "constructs": "^10.0.0", "source-map-support": "^0.5.16" }

In this simple example, the only resource being created is the output from the Custom Resource. customResourceNumber is required based on the configuration of the Interface in the Construct.

Testing the CDK

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

This will deploy the CDK and produce the output of the Custom Resource.

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, }); } }

Resources