Skip to content
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
35 changes: 35 additions & 0 deletions examples/2016-10-31/lambda_sns_filter_policy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Lambda function + Filtered SNS Subscription Example

This example shows you how to create a Lambda function with a SNS event source.

The Lambda function does not receive all messages published to the SNS topic but only a subset. The messages are filtered based on the attributes attached to
the message.

## Running the example

Deploy the example into your account:

```bash
# Replace YOUR_S3_ARTIFACTS_BUCKET with the name of a bucket which already exists in your account
aws cloudformation package --template-file template.yaml --output-template-file template.packaged.yaml --s3-bucket YOUR_S3_ARTIFACTS_BUCKET

aws cloudformation deploy --template-file ./template.packaged.yaml --stack-name sam-example-lambda-sns-filter-policy --capabilities CAPABILITY_IAM
```

The Lambda function will only receive messages with the attribute `sport` set to `football`.

In the AWS console go to the topic sam-example-lambda-sns-filter-policy and push the Publish to Topic button.
At the bottom of the Publish page you can add message attributes. Add one attribute:
- key: sport
- Attribute type: String
- value: football

Enter an arbitrary message body and publish the message.
In Cloudwatch the log group /aws/lambda/sam-example-lambda-sns-filter-policy-notification-logger appears and the logging contains the message attributes of
the received message.

Now publish a couple of other messages with other values for the attribute `sport` or without the attribute `sport`.
The Lambda function will not receive these messages.

## Additional resources
https://docs.aws.amazon.com/sns/latest/dg/message-filtering.html
8 changes: 8 additions & 0 deletions examples/2016-10-31/lambda_sns_filter_policy/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
'use strict';


exports.handler = async (event, context, callback) => {
console.log("Message attributes: " + JSON.stringify(event.Records[0].Sns.MessageAttributes));

callback(null, "Success");
};
24 changes: 24 additions & 0 deletions examples/2016-10-31/lambda_sns_filter_policy/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Lambda function with SNS filter policy
Resources:
NotificationLogger:
Type: AWS::Serverless::Function
Properties:
CodeUri: ./src
Handler: index.handler
Runtime: nodejs8.10
FunctionName: sam-example-lambda-sns-filter-policy-notification-logger
Events:
NotificationTopic:
Type: SNS
Properties:
Topic: !Ref Notifications
FilterPolicy:
sport:
- football

Notifications:
Type: AWS::SNS::Topic
Properties:
TopicName: sam-example-lambda-sns-filter-policy
10 changes: 7 additions & 3 deletions samtranslator/model/eventsources/push.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,8 @@ class SNS(PushEventSource):
resource_type = 'SNS'
principal = 'sns.amazonaws.com'
property_types = {
'Topic': PropertyType(True, is_str())
'Topic': PropertyType(True, is_str()),
'FilterPolicy': PropertyType(False, dict_of(is_str(), list_of(one_of(is_str(), is_type(dict)))))
}

def to_cloudformation(self, **kwargs):
Expand All @@ -315,14 +316,17 @@ def to_cloudformation(self, **kwargs):
raise TypeError("Missing required keyword argument: function")

return [self._construct_permission(function, source_arn=self.Topic),
self._inject_subscription(function, self.Topic)]
self._inject_subscription(function, self.Topic, self.FilterPolicy)]

def _inject_subscription(self, function, topic):
def _inject_subscription(self, function, topic, filterPolicy):
subscription = SNSSubscription(self.logical_id)
subscription.Protocol = 'lambda'
subscription.Endpoint = function.get_runtime_attr("arn")
subscription.TopicArn = topic

if filterPolicy is not None:
subscription.FilterPolicy = filterPolicy

return subscription


Expand Down
5 changes: 3 additions & 2 deletions samtranslator/model/sns.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from samtranslator.model import PropertyType, Resource
from samtranslator.model.types import is_str
from samtranslator.model.types import is_type, is_str


class SNSSubscription(Resource):
resource_type = 'AWS::SNS::Subscription'
property_types = {
'Endpoint': PropertyType(True, is_str()),
'Protocol': PropertyType(True, is_str()),
'TopicArn': PropertyType(True, is_str())
'TopicArn': PropertyType(True, is_str()),
'FilterPolicy': PropertyType(False, is_type(dict))
}
3 changes: 3 additions & 0 deletions samtranslator/validator/sam_schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,9 @@
"properties": {
"Topic": {
"type": "string"
},
"FilterPolicy": {
"type": "object"
}
},
"required": [
Expand Down
50 changes: 50 additions & 0 deletions tests/model/eventsources/test_sns_event_source.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
from mock import Mock
from unittest import TestCase
from samtranslator.model.eventsources.push import SNS


class SnsEventSource(TestCase):

def setUp(self):
self.logical_id = 'NotificationsProcessor'

self.sns_event_source = SNS(self.logical_id)
self.sns_event_source.Topic = 'arn:aws:sns:MyTopic'

self.function = Mock()
self.function.get_runtime_attr = Mock()
self.function.get_runtime_attr.return_value = 'arn:aws:lambda:mock'

def test_to_cloudformation_returns_permission_and_subscription_resources(self):
resources = self.sns_event_source.to_cloudformation(
function=self.function)
self.assertEquals(len(resources), 2)
self.assertEquals(resources[0].resource_type,
'AWS::Lambda::Permission')
self.assertEquals(resources[1].resource_type,
'AWS::SNS::Subscription')

subscription = resources[1]
self.assertEquals(subscription.TopicArn, 'arn:aws:sns:MyTopic')
self.assertEquals(subscription.Protocol, 'lambda')
self.assertEquals(subscription.Endpoint, 'arn:aws:lambda:mock')
self.assertIsNone(subscription.FilterPolicy)

def test_to_cloudformation_passes_the_filter_policy(self):
filterPolicy = {
'attribute1': ['value1'],
'attribute2': ['value2', 'value3'],
'attribute3': {'numeric': ['>=', '100']}
}
self.sns_event_source.FilterPolicy = filterPolicy

resources = self.sns_event_source.to_cloudformation(
function=self.function)
self.assertEquals(len(resources), 2)
self.assertEquals(resources[1].resource_type,
'AWS::SNS::Subscription')
subscription = resources[1]
self.assertEquals(subscription.FilterPolicy, filterPolicy)

def test_to_cloudformation_throws_when_no_function(self):
self.assertRaises(TypeError, self.sns_event_source.to_cloudformation)
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Resources:
MyAwesomeFunction:
Type: 'AWS::Serverless::Function'
Properties:
CodeUri: s3://sam-demo-bucket/hello.zip
Handler: hello.handler
Runtime: python2.7

Events:
NotificationTopic:
Type: SNS
Properties:
Topic: topicArn
FilterPolicy:
store:
- example_corp
event:
- anything-but: order_cancelled
customer_interests:
- rugby
- football
- baseball
price_usd:
- numeric:
- ">="
- 100


Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
{
"Resources": {
"MyAwesomeFunctionNotificationTopic": {
"Type": "AWS::SNS::Subscription",
"Properties": {
"FilterPolicy": {
"store": [
"example_corp"
],
"event": [
{
"anything-but": "order_cancelled"
}
],
"customer_interests": [
"rugby",
"football",
"baseball"
],
"price_usd": [
{
"numeric": [
">=",
100
]
}
]
},
"Endpoint": {
"Fn::GetAtt": [
"MyAwesomeFunction",
"Arn"
]
},
"Protocol": "lambda",
"TopicArn": "topicArn"
}
},
"MyAwesomeFunctionNotificationTopicPermission": {
"Type": "AWS::Lambda::Permission",
"Properties": {
"Action": "lambda:invokeFunction",
"Principal": "sns.amazonaws.com",
"FunctionName": {
"Ref": "MyAwesomeFunction"
},
"SourceArn": "topicArn"
}
},
"MyAwesomeFunctionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"ManagedPolicyArns": [
"arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
],
"AssumeRolePolicyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"sts:AssumeRole"
],
"Effect": "Allow",
"Principal": {
"Service": [
"lambda.amazonaws.com"
]
}
}
]
}
}
},
"MyAwesomeFunction": {
"Type": "AWS::Lambda::Function",
"Properties": {
"Code": {
"S3Bucket": "sam-demo-bucket",
"S3Key": "hello.zip"
},
"Handler": "hello.handler",
"Role": {
"Fn::GetAtt": [
"MyAwesomeFunctionRole",
"Arn"
]
},
"Runtime": "python2.7",
"Tags": [
{
"Value": "SAM",
"Key": "lambda:createdBy"
}
]
}
}
}
}
Loading