CDK Stack Notification Options
Tags: cdk, aws, infrastructureascode
Published: 2024-03-07 09:18:45

Today, I discovered yet again that there are countless ways to tackle a single task as a developer.

I was tasked with automating a workflow that involved an AWS Lambda Function triggered by an SNS event source. The goal was to publish a message to an SNS topic in a different AWS account when the status of a CloudFormation stack updated.
We use [AWS CDK](https://docs.aws.amazon.com/cdk/v2/guide/home.html) for infrastructure as code (IAC). While exploring the documentation and blog posts, I found that there is no direct equivalent of the Notification Policy in CloudFormation to publish notifications to an SNS topic on a CloudFormation stack status change. Instead, there are several common patterns to achieve this. Let's start with solution diagram. Here's a simplified version of the architecture diagram of what I implemented:
 If this is what you are looking for, you can simply achieve it in 3 different ways:

## Using AWS Event Bridge 

- Create SNS Topic as a Stack B CDK resource:

```
import { Topic } from "aws-cdk-lib/aws-sns";
 
   const SNSTopic = new Topic(this, "SNS_TOPIC_ID", {
      displayName: "YOUR DISPLAY NAME",
    }); 
```
_Note: you need to add an event source to your function or any other resources that is going to subscribe to this topic. in my case I needed to configure a lambda event source as _bellow:

```
MyFunction.addEventSource(new SnsEventSource(SNSTopic));
```

- Add Event Rule

```
 new Rule(this, "Trigger", {
      eventPattern: {
        source: ["aws.cloudformation"],
        detailType: ["CloudFormation Stack Status Change"],
        detail: {
          eventName: ["CREATE_COMPLETE", "UPDATE_COMPLETE", "DELETE_COMPLETE"],
          requestParameters: {
            stackName: [this.stackName],
          },
        },
      },
      targets: [new SnsTopic(SNSTopic)],
    });
```

## Using AWS Custom Resources
Second approach to achieve this is by using AWS Custom Resources.

- Create an AWS Custom Resource within the Stack B CDK: 

```
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";    

const Trigger = new AwsCustomResource(this, "TriggerOnSuccess", {
      onUpdate: {
        service: "SNS",
        action: "publish",
        parameters: {
          TopicArn: "YOUR_TOPIC_ARN",
          Message: "Stack updated successfully",
        },
        physicalResourceId: PhysicalResourceId.of("TriggerOnSuccess"),
      },
      onDelete: {
        service: "SNS",
        action: "publish",
        parameters: {
          TopicArn: "YOUR_TOPIC_ARN",
          Message: "Stack deleted successfully",
        },
        physicalResourceId: PhysicalResourceId.of("TriggerOnSuccess"),
      },
       policy: AwsCustomResourcePolicy.fromStatements( [new PolicyStatement({
        actions: ["sns:Publish"],
        effect: Effect.ALLOW,
        resources: [SNSTopic.topicArn],
      })]),
    });
```

- Add Dependency order so that CDK doesn't return Dependency Cycle error

```
 Trigger.node.addDependency(YOUR_FUNCTION);
 Trigger.node.addDependency(SNSS_TOPIC); 
```

## Using AWS Custom Resource with Lambda Invoke Action

There is also a 3rd solution for this as well which I am not a big fan of it and that is. I personally prefer to use fan out approach, to populate events to Lambda via an "Event Service" such as SNS or EventBridge. If you look for a simplified Custom Resource, here is what you should update your AWS Custom Resource to:

```
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from "aws-cdk-lib/custom-resources";    

const Trigger = new AwsCustomResource(this, "TriggerOnSuccess", {
      onUpdate: {
        service: "Lambda",
        action: "invoke",
        parameters: {
          FunctionName: "YOUR_FUNCTION_ARN",
          InvokationType: "Event"
        },
        physicalResourceId: PhysicalResourceId.of("TriggerOnSuccess"),
      },
      onDelete: {
        service: "Lambda",
        action: "invoke",
        parameters: {
          FunctionName: "YOUR_FUNCTION_ARN",
          InvokationType: "Event"
        },
        physicalResourceId: PhysicalResourceId.of("TriggerOnSuccess"),
      },
       policy: AwsCustomResourcePolicy.fromStatements( [new PolicyStatement({
        actions: ["lambda:InvokeFunction"],
        effect: Effect.ALLOW,
        resources: [YOUR_FUNCTION_ARN],
      })]),
    });
```
This concludes our brief discussion. While I'm still hopeful about discovering if CDK offers an API for configuring Stack Notification Options, I wanted to share these workarounds in the meantime.