DEV Community

Cover image for AWS Custom Resource using CDK
Emma Moinat for AWS Community Builders

Posted on

AWS Custom Resource using CDK

If you are new to AWS' Cloud Development Kit (CDK), here's a quick explanation of what exactly it is:

The AWS Cloud Development Kit (CDK) is a software development framework that allows you to define and provision cloud infrastructure using familiar programming languages such as TypeScript, Python, and Java.

Traditionally, infrastructure provisioning has been done using templates or scripts that are difficult to read and understand. CDK simplifies this process by allowing you to define your infrastructure in code, using the same programming constructs that you use to build applications.

With CDK, you can define your infrastructure as a series of reusable components called "constructs." These constructs can be shared across your organization and easily reused in multiple projects.

Overall, AWS CDK makes it easier and faster to build and manage cloud infrastructure, with less room for error and greater potential for reuse.

Thanks ChatGPT for that great explanation! 😂

So, now we have the basics of what CDK is and what it does for us, I want to look at building up a Custom Resource.

Why build a Custom Resource?

Custom Resources is a very useful feature of CDK that allows you to define and manage resources that are not available as a resource type in AWS CloudFormation.

There are a few different use cases for such a thing, these include:

  1. Provisioning resources not supported by CDK
  2. Implementing custom logic/configuration
  3. Integrating with third-party services

Using resource types for third-party resources provides you a way to reliably manage these resources using a single tool, without having to resort to time-consuming and error-prone methods like manual configuration or custom scripts.

AWS Documentation

Using a Custom Resource allows us to automate the creation, deletion and updating of these resources across multiple environments as and when we need.

How to build a Custom Resource?

To build a Custom Resource we need 3 things:

  1. A Lambda function for onEvent handling create, delete and update events.
  2. A Provider which points the Custom Resource to the lambda.
  3. The CustomResource itself with any props you need for your third party resource or otherwise.

You can also provide an additional Lambda function to handle isComplete.
This is used when the lifecycle operation cannot be completed immediately.
The isComplete handler will be retried asynchronously after onEvent until it returns { IsComplete: true }, or until it times out.

onEvent Lambda

This function will be invoked for all resource lifecycle operations (Create/Update/Delete).

Here is how this handler might look:

import {
  CloudFormationCustomResourceCreateEvent,
  CloudFormationCustomResourceDeleteEvent,
  CloudFormationCustomResourceEvent,
  CloudFormationCustomResourceResponse,
  CloudFormationCustomResourceUpdateEvent,
} from "aws-lambda";

export const handler = async (event: CloudFormationCustomResourceEvent): Promise<CloudFormationCustomResourceResponse> => {
  switch (event.RequestType) {
    case "Create":
      return await createSomeResource(event as CloudFormationCustomResourceCreateEvent);
    case "Update":
      return await updateSomeResource(event as CloudFormationCustomResourceUpdateEvent);
    case "Delete":
      return await deleteSomeResource(event as CloudFormationCustomResourceDeleteEvent);
  }
};
Enter fullscreen mode Exit fullscreen mode

For each of these cases, we want to respond according.

In other words, for a Create event we would want to, well, create something. Update we might want to delete something and create a new item if our use case doesn't offer a way to directly update the resource. For Delete, we should be deleting our resource. Pretty straight forward.

If you were, for example, using this Custom Resource to integrate with a third party service, you may want to make some specific API calls for creating, updating and deleting.

Understanding Lifecycle Events

It is important to understand how these events will be handled by CloudFormation.

If onEvent returns successfully, CloudFormation will show you the nice green tick of CREATE_COMPLETE for the CustomResource.

However, if onEvent throws an error, CloudFormation will let you know something went wrong and the CDK deploy will fail.

There are some important cases to think about when errors occur:

  • Do you need to tidy up some other resources if some step fails in your create flow?
  • What happens if you hit the delete event but there is nothing to delete?

Should be noted, if a Delete event happens to fail, CloudFormation will just abandon that resource moving forward.

You can find more detail on these cases here.

Lambda in CDK

A super simple way to declare this Lambda in CDK is:

readonly onEventHandlerFunction = new NodejsFunction(this, "CustomResourceOnEventHandlerFunction", {
  timeout: Duration.seconds(30),
  runtime: Runtime.NODEJS_18_X,
  entry: "/path/to/CustomResourceOnEventHandler.ts"
});
Enter fullscreen mode Exit fullscreen mode

NodejsFunction creates a Node.js Lambda function bundled using esbuild. This means you can directly pass in your TypeScript file. Cool, right?

Provider

We just need to tell the Provider (from aws-cdk-lib/custom-resources) to point the custom resource to the above function.

readonly customResourceProvider = new Provider(this, "CustomResourceProvider", {
  onEventHandler: this.onEventHandlerFunction,
  logRetention: RetentionDays.ONE_DAY
});
Enter fullscreen mode Exit fullscreen mode

Custom Resource

Finally, we just need to tell the Custom Resource who its provider is and give it a type starting with Custom:: :

readonly resource = new CustomResource(this, "YourCustomResource", {
  serviceToken: this.myProvider.serviceToken,
  properties: {...this.props, id: this.id},
  resourceType: "Custom::YourCustomResource",
});
Enter fullscreen mode Exit fullscreen mode

Result

Bringing these 3 things together, you will create a class similar to:

import {Construct} from "constructs";
import {Provider} from "aws-cdk-lib/custom-resources";
import {RetentionDays} from "aws-cdk-lib/aws-logs";
import {CustomResource, Duration} from "aws-cdk-lib";
import {NodejsFunction} from "aws-cdk-lib/aws-lambda-nodejs";
import {SomeProps} from "../models/SomeProps";
import {Runtime} from "aws-cdk-lib/aws-lambda";

export class YourCustomResource extends Construct {
  constructor(private scope: Construct, private id: string, private props: Omit<SomeProps, "id">) {
    super(scope, id);
  };

  readonly onEventHandlerFunction = new NodejsFunction(this, "CustomResourceOnEventHandlerFunction", {
    timeout: Duration.seconds(30),
    runtime: Runtime.NODEJS_18_X,
    entry: "/path/to/CustomResourceOnEventHandler.ts"
  });

  readonly customResourceProvider = new Provider(this, "CustomResourceProvider", {
    onEventHandler: this.onEventHandlerFunction,
    logRetention: RetentionDays.ONE_DAY
  });

  readonly resource = new CustomResource(this, "YourCustomResource", {
    serviceToken: this.customResourceProvider.serviceToken,
    properties: {...this.props, id: this.id},
    resourceType: "Custom::YourCustomResource",
  });
}
Enter fullscreen mode Exit fullscreen mode

You can see here we are passing through all the props from the YourCustomResource into the Custom Resource.

In the example of making API calls using the Custom Resource we might need something like an API Key to be passed through from our CDK stack into the resource.

Using this YourCustomResource class you can now build up something like this in one of your CDK Stacks:

readonly exampleCustomResource = new YourCustomResource(this, "YourCustomResourceExample", {
    enabled: true,
    apiKey: "api-key", // This prop could differ per environment
    name: "Example Custom Resource"
});
Enter fullscreen mode Exit fullscreen mode

Your CDK diff for this stack would look something like this:

CDK diff for Custom Resource

Now we have an understanding of how to build up the infrastructure for this Custom Resource, we now just need to determine how the onEvent Lambda will handle each of the lifecycle events.

This depends on your use case but the possibilities are endless really!

This part I will leave up to you as it is very specific per scenario, but I hope this has helped you on your journey of getting a CustomResource up and running.

Top comments (0)