🏄 AWS CDK 101 🌸 - lambda & CDK watch
Tags: typescript, aws, serverless, lambda
Published: 2022-03-11 18:07:44

In this article let us build necessary components which we need to setup a basic lambda stack which will be used in later in subsequent articles to act us a web hook taking some data into our ecosystem from other demo projects.

## Beginners new to AWS CDK, please do look at my previous articles in this series.

If incase missed the previous article do find it with the below links. 

:repeat: Original previous post at :link: [Dev Post](https://devpost.hashnode.dev/aws-cdk-101)

:repeat: Reposted previous post at :link: [dev to @aravindvcyber](https://dev.to/aravindvcyber/aws-cdk-101-4922)

First before we start moving forward let us try to do some cleanup on the current demo workshop stack and understand how it reflects this changes. This will give us good understanding on certain concepts before we move forward.

## Removing the unwanted resources :hourglass:

Open `lib/cdk-workshop-stack.ts`.

Here let us simply comment out the components initially created which is the SQS queue and the SNS topic with subscriptions, it should look like the below.

```typescript

export class CdkWorkshopStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // const queue = new sqs.Queue(this, 'CdkWorkshopQueue', {
    //   visibilityTimeout: Duration.seconds(300)
    // });

    // const topic = new sns.Topic(this, 'CdkWorkshopTopic');

    // topic.addSubscription(new subs.SqsSubscription(queue));
  }
}

```

What we have achieved is like something we can understand with the `cdk diff` command.

```log
IAM Statement Changes
┌───┬─────────────────────────────────┬────────┬─────────────────┬───────────────────────────┬─────────────────────────────────────────────────────────────────┐
│   │ Resource                        │ Effect │ Action          │ Principal                 │ Condition                                                       │
├───┼─────────────────────────────────┼────────┼─────────────────┼───────────────────────────┼─────────────────────────────────────────────────────────────────┤
│ - │ ${CdkWorkshopQueue50D9D426.Arn} │ Allow  │ sqs:SendMessage │ Service:sns.amazonaws.com │ "ArnEquals": {                                                  │
│   │                                 │        │                 │                           │   "aws:SourceArn": "${CdkWorkshopTopicD368A42F}"                │
│   │                                 │        │                 │                           │ }                                                               │
└───┴─────────────────────────────────┴────────┴─────────────────┴───────────────────────────┴─────────────────────────────────────────────────────────────────┘
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)

Resources
[-] AWS::SQS::Queue CdkWorkshopQueue50D9D426 destroy
[-] AWS::SQS::QueuePolicy CdkWorkshopQueuePolicyAF2494A5 destroy
[-] AWS::SNS::Subscription CdkWorkshopQueueCdkWork
[-] AWS::SNS::Topic CdkWorkshopTopicD368A42F destroy
```

This signifies that while we delete the SQS and SNS, it eventually removes the newly created queue policy and the topic subscription associated to this.

Make sure you run `cdk deploy` to cleanup the environment changes.

## Simple lambda stack :telescope:

Now we have cleaned our environment, let us start now from basics by trying to build a simple lambda stack which will be acting as a common gateway for the messages coming into our environment as we build this forward.

`import { Construct } from 'constructs';`

> The AWS CDK is shipped with an extensive library of constructs called the AWS Construct Library. The construct library is divided into modules, one for each AWS service. For example, if you want to define an AWS Lambda function, we will need to use the AWS Lambda construct library.

Before we proceed let us create a new stack by following the below steps

In the lib folder create a new file like `CommonEventStack.ts`, where we will add the basic template as follows.

```typescript

import { Stack, StackProps } from 'aws-cdk-lib';
import { Construct } from 'constructs';

export class CommonEventStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    
  }
}

```

Then go to your `bin\cdk-workshop.ts` and import the new stack we just created.

```typescript
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { CdkWorkshopStack } from '../lib/cdk-workshop-stack';
// importing our new stack
import { CommonEventStack } from '../lib/common-event-stack';

const app = new cdk.App();
new CdkWorkshopStack(app, 'CdkWorkshopStack');
// initializing our new stack
new CommonEventStack(app, 'CommonEventStack');

```

It is time to run `npm run build` to make sure we never introduced any error in the process.

Finally we can run `cdk ls` to see the below output.

```log
CdkWorkshopStack
CommonEventStack
```

And by the way I just gave the prefix `CommonEvent*` to our new stack resources, which we are about create also to demonstrate that we can have multiple stacks in the same project.

It is now time to create our lambda function, let us create a folder called `lambda` in the root of the project and add a new file `event-entry.ts`, as shown below. Note I have started with TypeScript file and it needs to be transpiled to JavaScript when we deploy it into lambda.

```ts
exports.receiver = async function(event:any) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: `Message Received: ${event}\n`
  };
};
```

Once you run npm build, you can notice the javascript files are created in the same folder, `event-entry.js`

```js
"use strict";
exports.receiver = async function (event) {
    console.log("request:", JSON.stringify(event, undefined, 2));
    return {
        statusCode: 200,
        headers: { "Content-Type": "text/plain" },
        body: `Message Received: ${event}\n`
    };
};
//# sourceMappingURL*********

```

Let us use this function to build our lambda function inside our new stack `CommonEventStack.ts`.

```ts
    //  A simple lambda Lambda resource definition
    const eventEntry = new lambda.Function(this, 'EventEntryHandler', {
      runtime: lambda.Runtime.NODEJS_14_X,    // execution environment
      code: lambda.Code.fromAsset('lambda'),  // code loaded from "lambda" directory
      handler: 'event-entry.receiver'                // file is "event-entry", function is "receiver"
    });
  }
```

## Don't forget to add the necessary imports if it complains  :paperclip:

```ts
import * as lambda from 'aws-cdk-lib/aws-lambda'
```

Here we are using NodeJs 14x runtime and specifying the handler function to be from the file named `event-entry` with function name like `receiver`.

It is time to run `cdk synth CommonEventStack`, to find the changes in the yaml template or run `cdk diff CommonEventStack` to find the changes more readable.


This to lookout for is listed below in the log below:

```log
IAM Statement Changes
┌───┬──────────────────────────────────────┬────────┬────────────────┬──────────────────────────────┬───────────┐
│   │ Resource                             │ Effect │ Action         │ Principal                    │ Condition │
├───┼──────────────────────────────────────┼────────┼────────────────┼──────────────────────────────┼───────────┤
│ + │ ${EventEntryHandler/ServiceRole.Arn} │ Allow  │ sts:AssumeRole │ Service:lambda.amazonaws.com │           │
└───┴──────────────────────────────────────┴────────┴────────────────┴──────────────────────────────┴───────────┘
IAM Policy Changes
┌───┬──────────────────────────────────┬────────────────────────────────────────────────────────────────────────────────┐
│   │ Resource                         │ Managed Policy ARN                                                             │
├───┼──────────────────────────────────┼────────────────────────────────────────────────────────────────────────────────┤
│ + │ ${EventEntryHandler/ServiceRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole │
└───┴──────────────────────────────────┴────────────────────────────────────────────────────────────────────────────────┘

Resources
[+] AWS::IAM::Role EventEntryHandler/ServiceRole EventEntryHandlerServiceRoleF9517B59 
[+] AWS::Lambda::Function EventEntryHandler EventEntryHandler0826D724 
```

Here two resources are created `IAM:role` and `Lambda:function`. Basically a new role is created to be assumed by the lambda function we created for its proper execution.

Let us deploy it, by running `cdk deploy CommonEventStack` and confirming with `y` for the above IAM policy changes.

```log

CommonEventStack: creating CloudFormation changeset...

 ✅  CommonEventStack

✨  Deployment time: 46.9s

Stack ARN:
arn:aws:cloudformation:ap-south-1:********:stack/CommonEventStack/93688c10-a10a-11ec-b942-0ad3136ae540

✨  Total time: 60.19s

```

## Inspection in AWS console :pushpin:

Now we can navigate to the lambda in AWS console into your default region and you will be able to identify the lambda.

![Deployed Lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sd4d4wbvacdd8sxkj1yj.png)

## Let us test this lambda now :snowboarder:

For which we have to have some sample test event as follows.

![Sample Test event ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/49ya3q7po3bx3le78sih.png)

Let us create run the same test, we have created above.

 ![Sample Test event Execution logs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iizs9srrcqc7o6m5k3nn.png)

 You can also find this logged inside the cloud watch logs.

 ![Cloud watch logs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vphz9j1zkdp6eh53xgkx.png)

 And that is it, we have a new lambda function running which is ready to receive our messages. So what we have achieved in this article is only tech demonstration, which can be further vertically integrated based on the use cases and is not considered to be the best approach, rather it is only a know-how.

 Before we complete this article let us understand one more cdk cli which we would find to be very much helpful in your development environment.

## Hot swapping deployments :trumpet:

 `cdk deploy --hotswap`

> This command deliberately introduces drift in CloudFormation stacks in order to speed up deployments. For this reason, only use it for development purposes. Never use hotswap for your production deployments!

 Let us make a simple change in our code for lambda in the entry

```ts
exports.receiver = async function(event:any) {
  console.log("request:", JSON.stringify(event, undefined, 2));
  return {
    statusCode: 200,
    headers: { "Content-Type": "text/plain" },
    body: `Hotswapped now! \nMessage Received: ${JSON.stringify(event)}\n`
  };
};
```

Just be mindful of the fact this only publishes the assets already built for lambda, in certain cases you will have used typescript in lambda and forget to build it before hotswaping, in that case the distributable files configured will be published but the `js` files would never have been updated yet. I make sure to add a script in my package json to run it like a script in `package.json`. 

`"fast": "npm run build && cdk deploy CommonEventStack --hotswap"`

The total time taken is reduced, but what is significant to notice here is the `deployment time` which is reduced to very small value. Earlier it is 45+s for full deployment, now here it is only 2+ seconds, would be useful in the development environment.

```log
✨ hotswapping resources:
   ✨ Lambda Function 'CommonEventStack-EventEntryHandler0826D724-xaVWqcUsxERH'
✨ Lambda Function 'CommonEventStack-EventEntryHandler0826D724-xaVWqcUsxERH' hotswapped!

 ✅  CommonEventStack

✨  Deployment time: 2.22s

Stack ARN:
arn:aws:cloudformation:ap-south-1:575066707855:stack/CommonEventStack/93688c10-a10a-11ec-b942-0ad3136ae540

✨  Total time: 15.81s

```

![Hotswapped Changes in lambda](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2inl9xrnaz3dk4qwxjrs.png)



Here I introduced some jargon `drift`, let us see what it means and how it can be helpful. To enable this you can select a stack or a resource under it and enable drift detection to capture it for tracking and audit.

## Drift detection :roller_coaster:

Drift detection enables you to detect whether a stack's actual configuration differs, or has drifted, from its expected configuration. Use CloudFormation to detect drift on an entire stack, or on individual resources within the stack.

A resource is considered to have drifted if any of its actual property values differ from the expected property values. This even includes if the property or resource has been deleted. A stack is considered to have drifted if one or more of its resources have drifted.

Here instead of using native cloud formation deployment, a more simple swift deployment is carried out by hot-swapping deployment by making use of the drifting changes on top of the initial stack.

![Sample Drift status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyzd6tsk3wb787u06y82.png)

## Resource drift status code and description :statue_of_liberty:

This is basically a comparison with the recent cloudformation deployment.

* DELETED
  The resource differs from its expected template configuration because the resource has been deleted.
* MODIFIED
  The resource differs from its expected template configuration.
* NOT_CHECKED
  CloudFormation has not checked if the resource differs from its expected template configuration.
* IN_SYNC
  The resource's current configuration matches its expected template configuration.


Before we wrap up let us see one more deploy cli command below.

## CDK Watch :circus_tent:

CDK watch helps to monitor the file changes and invoke cdk deploy when it is needed. It monitors the files specified in the include and the exclude list in the `cdk.json` file.

```json

{
    "watch": {
    "include": [
      "**"
    ],
    "exclude": [
      "README.md",
      "cdk*.json",
      "**/*.d.ts",
      "lambda/*.ts",
      "tsconfig.json",
      "package*.json",
      "yarn.lock",
      "node_modules",
      "test"
    ]
  },
}


```

In the above snippet, I have purposefully updated the `**/*.js` to `lambda/*.ts`. This change is done to make sure the `ts` file changes in our lambda source not detectable at the same time it will detect the .js file which be emitted by  `tsc`.

So I will be launching couple of windows to run `tsc -w` and `cdk watch CommonEventStack --hotswap`. Working in parallel this will automatically deploy the expect lambda code changes, basically these are developer friendly customizations.

> `tsc -w` this rewrites the js files.

```log
[4:26:07 PM] File change detected. Starting incremental compilation...

[4:26:11 PM] Found 0 errors. Watching for file changes.
```

> `cdk watch CommonEventStack --hotswap` published my js files automatically and swiftly.

```log
Detected change to 'lambda/event-entry.js' (type: change). Triggering 'cdk deploy'

✨  Synthesis time: 21.92s

✨ hotswapping resources:
   ✨ Lambda Function 'CommonEventStack-EventEntryHandler0826D724-xaVWqcUsxERH'
✨ Lambda Function 'CommonEventStack-EventEntryHandler0826D724-xaVWqcUsxERH' hotswapped!

 ✅  CommonEventStack

✨  Deployment time: 2.17s

Stack ARN:
arn:aws:cloudformation:ap-south-1:575066707855:stack/CommonEventStack/93688c10-a10a-11ec-b942-0ad3136ae540
current credentials could not be used to assume 'arn:aws:iam::575066707855:role/cdk-hnb659fds-lookup-role-575066707855-ap-south-1', but are for the right account. Proceeding anyway.
(To get rid of this warning, please upgrade to bootstrap version >= 8)

✨  Total time: 24.09s

```

We will add more connections to this lambda and make it more usable in the coming articles stay subscribed.

⏭ We have our next article in serverless, do check out

[aws-cdk-101-api-gateway-construct-usage-throttle-quota-usage-plans-api-keys](https://dev.to/aravindvcyber/aws-cdk-101-api-gateway-construct-usage-throttle-quota-usage-plans-api-keys-59ch)


Thanks for supporting! 🙏

Would be really great if you like to [:coffee: Buy Me a Coffee](https://www.buymeacoffee.com/AravindVCyber),  to help boost my efforts. 

<a href='https://ko-fi.com/X8X0CITDJ' target='_blank'><img height='36' style='border:0px;height:36px;' src='https://cdn.ko-fi.com/cdn/kofi3.png?v=3' border='0' alt='Buy Me a Coffee at ko-fi.com' /></a>

<a href="https://www.buymeacoffee.com/AravindVCyber"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=AravindVCyber&button_colour=BD5FFF&font_colour=ffffff&font_family=Cookie&outline_colour=000000&coffee_colour=FFDD00" /></a>

Also follow my posts in the platform of your choice listed below.

:repeat: Original post at :link: [Dev Post](https://devpost.hashnode.dev/aws-cdk-101-lambda-and-cdk-watch)

:repeat: Reposted at :link: [dev to @aravindvcyber](https://dev.to/aravindvcyber/aws-cdk-101-lambda-cdk-watch-le9)