AWS Custom Resource using CDK `AwsCustomResource` construct
Tags: aws, cdk, devops, cloudskills
Published: 2021-05-24 11:23:59

# Prerequisite
- Understanding of AWS CDK (Cloud development kit)
- Understanding of AWS Custom resources

### INTRODUCTION
> All through the blog I will be mentioning terms **custom resource lambda** and **custom resource**. Both terms look similar but actually are different. **Custom resource lambda** refers to the lambda that is responsible for implementing the logic. **Custom resource** is the resource itself that is created by invoking the custom resource lambda.

In this blog I am covering a niche scenario related to AWS custom resources when all your custom resource lambda needs is to call an AWS API. Usual use case of doing so would be when cloudformation does not support a resource and it is only available with an API.

> For all other use cases, please refer to my earlier blog [Custom Resources with AWS CDK](https://nikhil-zadoo.com/custom-resources-with-aws-cdk).

CDK Construct [AwsCustomResource](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_custom-resources.AwsCustomResource.html) helps us achieve that. It takes care of creating custom resource lambda and permissions needed under the hood. As a custom resource author, all you need to do is pass the **AWS Service** and **API action**.

### Why should you use yet another way of creating custom resource?
Although you can achieve the same results using [Provider](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_custom-resources.Provider.html) cdk construct described in my [previous blog](https://nikhil-zadoo.com/custom-resources-with-aws-cdk), the reasons why you might choose this method are:

> You will see all these points in action in the example below. So revisit these points again after going through the example for better understanding)

- You do not need to even create a Custom resource lambda explicitly
- You do not need to manage the permissions needed by custom resource lambda anymore. CDK construct takes care of it. Also it follows the least privilege principle by design.
- It seamlessly blends with your CDK code
- CDK interfaces and enums definitions can be leveraged since all you code is part of CDK application and not a separate lambda
- You need lesser lines of code to achieve the same results
- Code readability is better

### EXAMPLE

Below is an example of a use case not possible to achieve with Cloudformation.

If you want to create a subscription when the SNS topic and the endpoint are in different regions, it is not possible with Cloudformation at the moment.

Let us try and create a custom resource for it using CDK construct **AwsCustomResource**. This construct will be responsible for calling AWS **subscribe** and **unsubscribe** API's.

```
import * as lambda from '@aws-cdk/aws-lambda';
import * as sns from '@aws-cdk/aws-sns';
import * as cdk from '@aws-cdk/core';
import * as cr from '@aws-cdk/custom-resources';

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

    const topicArnInAnotherRegion = '<ARN_TOPIC_IN_DIFFERENT_REGION>';

    // Just a dummy lambda to act as an endpoint for SNS. Don't confuse it as lambda for CDK resource
    const dummyLambdaEndpoint = new lambda.Function(this, 'Function', {
      code: lambda.Code.fromInline('exports.handler = async (event) => {console.log(event)};'),
      handler: 'index.handler',
      runtime: lambda.Runtime.NODEJS_12_X,
    });

    // Construct that takes care of creating the custom resource
    new cr.AwsCustomResource(this, 'SNSCustomResource', {
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
        resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
      }),
      onCreate: {
        action: 'subscribe',
        service: 'SNS',
        physicalResourceId: cr.PhysicalResourceId.fromResponse('SubscriptionArn'),
        region: '<TOPIC_REGION>',
        parameters: {
          Protocol: sns.SubscriptionProtocol.LAMBDA,
          TopicArn: topicArnInAnotherRegion,
          Endpoint: dummyLambdaEndpoint.functionArn,
        },
      },
      onUpdate: {
        action: 'subscribe',
        service: 'SNS',
        physicalResourceId: cr.PhysicalResourceId.fromResponse('SubscriptionArn'),
        region: '<TOPIC_REGION>',
        parameters: {
          Protocol: sns.SubscriptionProtocol.LAMBDA,
          TopicArn: topicArnInAnotherRegion,
          Endpoint: dummyLambdaEndpoint.functionArn,
        },
      },
      onDelete: {
        action: 'unsubscribe',
        service: 'SNS',
        region: '<TOPIC_REGION>',
        parameters: {
          SubscriptionArn: new cr.PhysicalResourceIdReference(), // Passes the physical resource ID set during CREATE/UPDATE
        },
      },
    });
  }
}
```

### Understanding the code

You might have already noticed some differences with the [Provider](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_custom-resources.Provider.html) CDK construct.

The [Provider](https://docs.aws.amazon.com/cdk/api/latest/docs/@aws-cdk_custom-resources.Provider.html) CDK construct just like [Cloudformation custom resources](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/template-custom-resources.html) first creates a custom resource lambda, which you later reference using **serviceToken** property in your custom resource. The semantics of **AwsCustomResource** work a little differently. Here everything is done in 1 shot. You do not create a custom resource lambda separately and then reference it in your custom resource. Both the custom resource lambda and custom resource itself are created by the same AwsCustomResource construct.

As understandable by their names, **onCreate, onUpdate** and **onDelete** properties represent the lifecycle events of the custom resource and are invoked when the custom resource is created, updated or deleted respectively.

#### Let us go through the interesting parts in the code one by one:

```
    ...
      policy: cr.AwsCustomResourcePolicy.fromSdkCalls({
        resources: cr.AwsCustomResourcePolicy.ANY_RESOURCE,
      }),
    ...
```
Property `policy` configures the IAM permissions for your custom resource lambda role. Method `fromSdkCalls` adds actions configured in `onCreate`, `onUpdate` and `onDelete` properties, to the custom resource lambda IAM policy. Based on the above example, custom resource lambda IAM policy will have `sns:subscribe` and `sns:unsubscribe` permissions.

Property `resources` controls which resources the custom resource lambda role will be given access to. `AwsCustomResourcePolicy.ANY_RESOURCE` will simply pass `"*"` wildcard as resource in the custom resource lambda IAM policy. However you should always aim to explicitly pass the resources so as to follow the least privilege principle. In our example we could have used it like:
```
resources: [topicArnInAnotherRegion],
```

---
```
    ...
      onCreate: {
        action: 'subscribe',
        service: 'SNS',
        physicalResourceId: cr.PhysicalResourceId.fromResponse('SubscriptionArn'),
        region: '<TOPIC_REGION>',
        parameters: {
          Protocol: sns.SubscriptionProtocol.LAMBDA,
          TopicArn: topicArnInAnotherRegion,
          Endpoint: dummyLambdaEndpoint.functionArn,
        },
      },
    ...
```

This block is configuring the various parameters that we intend to pass to the  [AWS SUBSCRIBE API](https://docs.aws.amazon.com/sns/latest/api/API_Subscribe.html).

`physicalResourceId: PhysicalResourceId.fromResponse('SubscriptionArn')` This piece of code tells lambda to fetch `SubscriptionArn` from the response of sns subscribe api call and set it as physical resource id for the custom resource created.

The `onUpdate` block also works similarly.

---

```
    ...
      onDelete: {
        action: 'unsubscribe',
        service: 'SNS',
        region: '<TOPIC_REGION>',
        parameters: {
          SubscriptionArn: new cr.PhysicalResourceIdReference(),
        },
      },
    ...
```
in the `onDelete` block the only interesting part of code is `SubscriptionArn: new cr.PhysicalResourceIdReference()`. Class `PhysicalResourceIdReference` is responsible here to return the current physical resource id set for the custom resource.

### CONCLUSION
In conclusion, the aim of the construct **AwsCustomResource** is to abstract away all the infrastructure parts and let you as a custom resource author just care about the parameters needed to be passed to the underlying AWS API. It creates a lambda for you and also manages the permissions needed by lambda.

Although as mentioned at the beginning of the article also, this method serves a very specific use case. You might not use it that often. Do check out my other post on [Custom Resources with AWS CDK](https://nikhil-zadoo.com/custom-resources-with-aws-cdk). That is how you will most likely be using the custom resources.