Skip to content
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

Support subscribing to SNS ARNs #1048

Merged
merged 2 commits into from
Jan 17, 2019
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
2 changes: 2 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Next Release (TBD)

* Fix packaging multiple local directories as dependencies
(`#1047 <https://github.com/aws/chalice/pull/1047>`__)
* Add support for passing SNS ARNs to ``on_sns_message``
(`#1048 <https://github.com/aws/chalice/pull/1048>`__)


1.6.2
Expand Down
56 changes: 33 additions & 23 deletions chalice/deploy/planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -439,31 +439,41 @@ def _plan_snslambdasubscription(self, resource):
)
topic_arn_varname = '%s_topic_arn' % resource.resource_name
subscribe_varname = '%s_subscription_arn' % resource.resource_name
# To keep the user API simple, we only require the topic
# name and not the ARN. However, the APIs require the topic
# ARN so we need to reconstruct it here in the planner.
instruction_for_topic_arn = [
models.BuiltinFunction(
'parse_arn',
[function_arn],
output_var='parsed_lambda_arn',
),
models.JPSearch('account_id',
input_var='parsed_lambda_arn',
output_var='account_id'),
models.JPSearch('region',
input_var='parsed_lambda_arn',
output_var='region_name'),
models.StoreValue(
name=topic_arn_varname,
value=StringFormat(
'arn:aws:sns:{region_name}:{account_id}:%s' % (
resource.topic

instruction_for_topic_arn = [] # type: List[InstructionMsg]
if resource.topic.startswith('arn:aws:sns:'):
instruction_for_topic_arn += [
models.StoreValue(
name=topic_arn_varname,
value=resource.topic,
)
]
else:
# To keep the user API simple, we only require the topic
# name and not the ARN. However, the APIs require the topic
# ARN so we need to reconstruct it here in the planner.
instruction_for_topic_arn += [
models.BuiltinFunction(
'parse_arn',
[function_arn],
output_var='parsed_lambda_arn',
),
models.JPSearch('account_id',
input_var='parsed_lambda_arn',
output_var='account_id'),
models.JPSearch('region',
input_var='parsed_lambda_arn',
output_var='region_name'),
models.StoreValue(
name=topic_arn_varname,
value=StringFormat(
'arn:aws:sns:{region_name}:{account_id}:%s' % (
resource.topic
),
['region_name', 'account_id'],
),
['region_name', 'account_id'],
),
),
] # type: List[InstructionMsg]
]
if self._remote_state.resource_exists(resource):
# Given there's nothing about an SNS subscription you can
# configure for now, if the resource exists, we don't do
Expand Down
19 changes: 12 additions & 7 deletions chalice/package.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
import copy

from typing import Any, Dict, List, Set # noqa
from typing import Any, Dict, List, Set, Union # noqa
from typing import cast

from chalice.deploy.swagger import CFNSwaggerGenerator
Expand Down Expand Up @@ -289,16 +289,21 @@ def _generate_snslambdasubscription(self, resource, template):
function_cfn = template['Resources'][function_cfn_name]
sns_cfn_name = self._register_cfn_resource_name(
resource.resource_name)

if resource.topic.startswith('arn:aws:sns:'):
topic_arn = resource.topic # type: Union[str, Dict[str, str]]
else:
topic_arn = {
'Fn::Sub': (
'arn:aws:sns:${AWS::Region}:${AWS::AccountId}:%s' %
resource.topic
)
}
function_cfn['Properties']['Events'] = {
sns_cfn_name: {
'Type': 'SNS',
'Properties': {
'Topic': {
'Fn::Sub': (
'arn:aws:sns:${AWS::Region}:${AWS::AccountId}:%s' %
resource.topic
)
}
'Topic': topic_arn,
}
}
}
Expand Down
3 changes: 1 addition & 2 deletions docs/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,7 @@ Chalice
app.log.info("SNS subject: %s", event.subject)
app.log.info("SNS message: %s", event.message)

:param topic: The name of the SNS topic you want to subscribe to.
This is the name of the topic, not the topic ARN.
:param topic: The name or ARN of the SNS topic you want to subscribe to.

:param name: The name of the function to use. This name is combined
with the chalice app name as well as the stage name to create the
Expand Down
4 changes: 4 additions & 0 deletions docs/source/topics/events.rst
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ command::
2018-06-28 17:49:30.513000 547e0f chalice-demo-sns - DEBUG - Received message with subject: TestSubject1, message: TestMessage1
2018-06-28 17:49:40.391000 547e0f chalice-demo-sns - DEBUG - Received message with subject: TestSubject2, message: TestMessage2

In this example we used the SNS topic name to register our handler, but you can
also use the topic arn. This can be useful if your topic is in another region
or account.


.. _sqs-events:

Expand Down
57 changes: 57 additions & 0 deletions tests/unit/deploy/test_planner.py
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,63 @@ def test_can_plan_sns_subscription(self):
),
]

def test_can_plan_sns_arn_subscription(self):
function = create_function_resource('function_name')
topic_arn = 'arn:aws:sns:mars-west-2:123456789:mytopic'
sns_subscription = models.SNSLambdaSubscription(
resource_name='function_name-sns-subscription',
topic=topic_arn,
lambda_function=function
)
plan = self.determine_plan(sns_subscription)
plan_parse_arn = plan[0]
assert plan_parse_arn == models.StoreValue(
name='function_name-sns-subscription_topic_arn',
value=topic_arn,
)
topic_arn_var = Variable("function_name-sns-subscription_topic_arn")
assert plan[1:] == [
models.APICall(
method_name='add_permission_for_sns_topic',
params={
'function_arn': Variable("function_name_lambda_arn"),
'topic_arn': topic_arn_var,
},
output_var=None
),
models.APICall(
method_name='subscribe_function_to_topic',
params={
'function_arn': Variable("function_name_lambda_arn"),
'topic_arn': topic_arn_var,
},
output_var='function_name-sns-subscription_subscription_arn'
),
models.RecordResourceValue(
resource_type='sns_event',
resource_name='function_name-sns-subscription',
name='topic',
value=topic_arn),
models.RecordResourceVariable(
resource_type='sns_event',
resource_name='function_name-sns-subscription',
name='lambda_arn',
variable_name='function_name_lambda_arn'
),
models.RecordResourceVariable(
resource_type='sns_event',
resource_name='function_name-sns-subscription',
name='subscription_arn',
variable_name='function_name-sns-subscription_subscription_arn'
),
models.RecordResourceVariable(
resource_type='sns_event',
resource_name='function_name-sns-subscription',
name='topic_arn',
variable_name='function_name-sns-subscription_topic_arn',
),
]

def test_sns_subscription_exists_is_noop_for_planner(self):
function = create_function_resource('function_name')
sns_subscription = models.SNSLambdaSubscription(
Expand Down
21 changes: 21 additions & 0 deletions tests/unit/test_package.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,27 @@ def handler(event):
}
}

def test_can_package_sns_arn_handler(self, sample_app):
arn = 'arn:aws:sns:space-leo-1:1234567890:foo'

@sample_app.on_sns_message(topic=arn)
def handler(event):
pass

config = Config.create(chalice_app=sample_app,
project_dir='.',
api_gateway_stage='api')
template = self.generate_template(config, 'dev')
sns_handler = template['Resources']['Handler']
assert sns_handler['Properties']['Events'] == {
'HandlerSnsSubscription': {
'Type': 'SNS',
'Properties': {
'Topic': arn,
}
}
}

def test_can_package_sqs_handler(self, sample_app):
@sample_app.on_sqs_message(queue='foo', batch_size=5)
def handler(event):
Expand Down