Skip to content

Enhance apigw-lambda-sns pattern with production-ready improvements #2793

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 21 additions & 4 deletions apigw-lambda-sns/Readme.md → apigw-lambda-sns/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,22 @@

The SAM template deploys a API Gateway REST API with Lambda function integration, an SNS topic and the IAM permissions required to run the application. Whenever the REST API is invoked, the Lambda function publishes a message to the SNS topic. The AWS SAM template deploys the resources and the IAM permissions required to run the application.

## Features

- **API Gateway REST API** with Lambda integration
- **Lambda function** that publishes messages to SNS
- **SNS topic** for message publishing
- **CloudWatch Alarm** monitoring API errors
- **Amazon CloudWatch Synthetics Canary** for automated API endpoint monitoring
- **AWS X-Ray tracing** enabled for distributed tracing on LAmbda and APIGW (incurs additional costs)
- **S3 bucket** for Synthetics artifacts storage

Learn more about this pattern at Serverless Land Patterns: https://serverlessland.com/patterns/apigw-lambda-sns/.

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.
Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. **Note: AWS X-Ray tracing is enabled which incurs additional charges based on traces recorded and retrieved.** You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Requirements


* [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git Installed](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
Expand Down Expand Up @@ -50,19 +59,27 @@ curl --location --request GET 'https://<api_id>.execute-api.<region>.amazonaws.c
```
In order to receive a notification, please make sure to configure subscription in the SNS topic.

### Additional Features

## Cleanup
- **CloudWatch Alarm**: Monitor the Synthetics Canary failures. The alarm triggers when the canary fails at least once within a 5-minute period.
- **Synthetics Canary**: Automatically tests the API endpoint every minute to ensure availability. If you want to alarm on this, you must manually create a CloudWatch Alarm or update the template
- **X-Ray Tracing**: Distributed tracing is enabled for both API Gateway and Lambda to help with debugging and performance analysis.


## Cleanup

1. Delete the stack
```
aws cloudformation delete-stack —stack-name STACK_NAME
```
2. Confirm the stack has been deleted
2. **Manually delete the S3 bucket** - The Synthetics artifacts bucket must be manually emptied and deleted after stack deletion
3. Confirm the stack has been deleted
```
aws cloudformation list-stacks —query "StackSummaries[?contains(StackName,'STACK_NAME')].StackStatus"
```

**Important**: You must manually delete the S3 bucket created for Synthetics artifacts after deleting the CloudFormation stack, as it will contain canary run artifacts.

----
Copyright 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Expand Down
10 changes: 9 additions & 1 deletion apigw-lambda-sns/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,23 @@ paths:
application/json:
schema:
$ref: "#/components/schemas/Empty"
"400":
description: "400 response"
"500":
description: "500 response"
x-amazon-apigateway-integration:
httpMethod: "POST"
uri: "arn:${AWS::Partition}:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:${AWS::Partition}:lambda:${AWS::Region}:${AWS::AccountId}:function:LambdaFunctionName/invocations"
responses:
default:
statusCode: "200"
".*4\\d{2}.*":
statusCode: "400"
".*5\\d{2}.*":
statusCode: "500"
passthroughBehavior: "when_no_match"
contentHandling: "CONVERT_TO_TEXT"
type: "aws"
type: "aws_proxy"
components:
schemas:
Empty:
Expand Down
66 changes: 66 additions & 0 deletions apigw-lambda-sns/apigw-lambda-sns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"title": "Amazon API Gateway REST API to Lambda to SNS",
"description": "Integration of Amazon API Gateway REST API with Amazon Lambda to publish to Amazon SNS with enhanced monitoring",
"language": "Python",
"level": "200",
"framework": "AWS SAM",
"introBox": {
"headline": "How it works",
"text": [
"This sample project demonstrates how to publish to a SNS Topic whenever the REST API is invoked using Lambda function.",
"This pattern deploys a Amazon API Gateway REST API with Lambda Function integration and SNS Topic with enhanced monitoring features including CloudWatch Alarms, X-Ray tracing, and Synthetics Canary."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/apigw-lambda-sns",
"templateURL": "serverless-patterns/apigw-lambda-sns",
"projectFolder": "apigw-lambda-sns",
"templateFile": "template.yaml"
}
},
"resources": {
"bullets": [
{
"text": "Choosing between messaging services for serverless applications.",
"link": "https://aws.amazon.com/blogs/compute/choosing-between-messaging-services-for-serverless-applications/"
},
{
"text": "Publishing Messages in Amazon SNS",
"link": "https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sns.html#topic"
},
{
"text": "AWS X-Ray Distributed Tracing",
"link": "https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html"
},
{
"text": "Amazon CloudWatch Synthetics",
"link": "https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html"
}
]
},
"deploy": {
"text": [
"sam deploy"
]
},
"testing": {
"text": [
"See the GitHub repo for detailed testing instructions."
]
},
"cleanup": {
"text": [
"Delete the stack: <code>sam delete</code>.",
"Manually delete the S3 bucket created for Synthetics artifacts after deleting the CloudFormation stack."
]
},
"authors": [
{
"name": "Sanskar",
"image": "https://drive.google.com/file/d/1dP8XHHevaOC-eEKOq6Gty75ZBZ428KHT/view?usp=sharing",
"bio": "Sanskar is a Serverless Cloud Engineer at Amazon Web Services and is based in India. Sanskar has avid interest in microservices and serverless patterns.",
"linkedin": "sanskar05"
}
]
}
15 changes: 12 additions & 3 deletions apigw-lambda-sns/example-pattern.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"title": "Amazon API Gateway REST API to Lambda to SNS",
"description": "Integration of Amazon API Gateway REST API with Amazon Lambda to publish to Amazon SNS",
"description": "Integration of Amazon API Gateway REST API with Amazon Lambda to publish to Amazon SNS with enhanced monitoring",
"language": "Python",
"level": "200",
"framework": "AWS SAM",
"introBox": {
"headline": "How it works",
"text": [
"This sample project demonstrates how to publish to a SNS Topic whenever the REST API is invoked using Lambda function.",
"This pattern deploys a Amazon API Gateway REST API with Lambda Function integration and SNS Topic."
"This pattern deploys a Amazon API Gateway REST API with Lambda Function integration and SNS Topic with enhanced monitoring features including CloudWatch Alarms, X-Ray tracing, and Synthetics Canary."
]
},
"gitHub": {
Expand All @@ -29,6 +29,14 @@
"text": "Publishing Messages in Amazon SNS",
"link": "https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sns.html#topic"
},
{
"text": "AWS X-Ray Distributed Tracing",
"link": "https://docs.aws.amazon.com/xray/latest/devguide/aws-xray.html"
},
{
"text": "Amazon CloudWatch Synthetics",
"link": "https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/CloudWatch_Synthetics_Canaries.html"
},
]
},
"deploy": {
Expand All @@ -43,7 +51,8 @@
},
"cleanup": {
"text": [
"Delete the stack: <code>same delete</code>."
"Delete the stack: <code>sam delete</code>.",
"Manually delete the S3 bucket created for Synthetics artifacts after deleting the CloudFormation stack."
]
},
"authors": [
Expand Down
33 changes: 24 additions & 9 deletions apigw-lambda-sns/src/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,38 @@ def lambda_handler(event, context):
logger.setLevel(logging.INFO)
logger.info("request: " + json.dumps(event))

topic_arn = os.environ.get('TOPIC_ARN')

sns_client = boto3.client("sns")

try:
topic_arn = os.environ.get('TOPIC_ARN')
if not topic_arn:
logger.error("Missing TOPIC_ARN environment variable")
return {
"statusCode": 500,
"body": json.dumps({"error": "Server configuration error"})
}

sns_client = boto3.client("sns")
sent_message = sns_client.publish(
TargetArn=topic_arn,
Message=json.dumps({'default': json.dumps(event)})
)

if sent_message is not None:
logger.info(f"Success - Message ID: {sent_message['MessageId']}")
logger.info(f"Success - Message ID: {sent_message['MessageId']}")
return {
"statusCode": 200,
"body": json.dumps("Success")
"body": json.dumps({"status": "Success", "messageId": sent_message['MessageId']})
}

except ClientError as e:
logger.error(e)
return None
error_code = e.response['Error']['Code']
error_message = e.response['Error']['Message']
logger.error(f"ClientError: {error_code} - {error_message}")
return {
"statusCode": 500,
"body": json.dumps({"error": "Failed to publish message to SNS"})
}
except Exception as e:
logger.error(f"Unexpected error: {str(e)}")
return {
"statusCode": 500,
"body": json.dumps({"error": "Internal server error"})
}
Loading