Merge pull request #2475 from edekadigital/add-sns-tags

Add sns tags
This commit is contained in:
Mike Grima 2019-10-12 15:42:25 -07:00 committed by GitHub
commit e7f1ab3290
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 342 additions and 7 deletions

View file

@ -10,6 +10,14 @@ class SNSNotFoundError(RESTError):
"NotFound", message)
class ResourceNotFoundError(RESTError):
code = 404
def __init__(self):
super(ResourceNotFoundError, self).__init__(
'ResourceNotFound', 'Resource does not exist')
class DuplicateSnsEndpointError(RESTError):
code = 400
@ -42,6 +50,14 @@ class InvalidParameterValue(RESTError):
"InvalidParameterValue", message)
class TagLimitExceededError(RESTError):
code = 400
def __init__(self):
super(TagLimitExceededError, self).__init__(
'TagLimitExceeded', 'Could not complete request: tag quota of per resource exceeded')
class InternalError(RESTError):
code = 500

View file

@ -18,7 +18,7 @@ from moto.awslambda import lambda_backends
from .exceptions import (
SNSNotFoundError, DuplicateSnsEndpointError, SnsEndpointDisabled, SNSInvalidParameter,
InvalidParameterValue, InternalError
InvalidParameterValue, InternalError, ResourceNotFoundError, TagLimitExceededError
)
from .utils import make_arn_for_topic, make_arn_for_subscription
@ -44,6 +44,8 @@ class Topic(BaseModel):
self.subscriptions_confimed = 0
self.subscriptions_deleted = 0
self._tags = {}
def publish(self, message, subject=None, message_attributes=None):
message_id = six.text_type(uuid.uuid4())
subscriptions, _ = self.sns_backend.list_subscriptions(self.arn)
@ -277,7 +279,7 @@ class SNSBackend(BaseBackend):
def update_sms_attributes(self, attrs):
self.sms_attributes.update(attrs)
def create_topic(self, name, attributes=None):
def create_topic(self, name, attributes=None, tags=None):
fails_constraints = not re.match(r'^[a-zA-Z0-9_-]{1,256}$', name)
if fails_constraints:
raise InvalidParameterValue("Topic names must be made up of only uppercase and lowercase ASCII letters, numbers, underscores, and hyphens, and must be between 1 and 256 characters long.")
@ -285,6 +287,8 @@ class SNSBackend(BaseBackend):
if attributes:
for attribute in attributes:
setattr(candidate_topic, camelcase_to_underscores(attribute), attributes[attribute])
if tags:
candidate_topic._tags = tags
if candidate_topic.arn in self.topics:
return self.topics[candidate_topic.arn]
else:
@ -499,6 +503,31 @@ class SNSBackend(BaseBackend):
raise SNSInvalidParameter("Invalid parameter: FilterPolicy: Match value must be String, number, true, false, or null")
def list_tags_for_resource(self, resource_arn):
if resource_arn not in self.topics:
raise ResourceNotFoundError
return self.topics[resource_arn]._tags
def tag_resource(self, resource_arn, tags):
if resource_arn not in self.topics:
raise ResourceNotFoundError
updated_tags = self.topics[resource_arn]._tags.copy()
updated_tags.update(tags)
if len(updated_tags) > 50:
raise TagLimitExceededError
self.topics[resource_arn]._tags = updated_tags
def untag_resource(self, resource_arn, tag_keys):
if resource_arn not in self.topics:
raise ResourceNotFoundError
for key in tag_keys:
self.topics[resource_arn]._tags.pop(key, None)
sns_backends = {}
for region in Session().get_available_regions('sns'):

View file

@ -30,6 +30,10 @@ class SNSResponse(BaseResponse):
in attributes
)
def _get_tags(self):
tags = self._get_list_prefix('Tags.member')
return {tag['key']: tag['value'] for tag in tags}
def _parse_message_attributes(self, prefix='', value_namespace='Value.'):
message_attributes = self._get_object_map(
'MessageAttributes.entry',
@ -85,7 +89,8 @@ class SNSResponse(BaseResponse):
def create_topic(self):
name = self._get_param('Name')
attributes = self._get_attributes()
topic = self.backend.create_topic(name, attributes)
tags = self._get_tags()
topic = self.backend.create_topic(name, attributes, tags)
if self.request_json:
return json.dumps({
@ -691,6 +696,30 @@ class SNSResponse(BaseResponse):
template = self.response_template(CONFIRM_SUBSCRIPTION_TEMPLATE)
return template.render(sub_arn='{0}:68762e72-e9b1-410a-8b3b-903da69ee1d5'.format(arn))
def list_tags_for_resource(self):
arn = self._get_param('ResourceArn')
result = self.backend.list_tags_for_resource(arn)
template = self.response_template(LIST_TAGS_FOR_RESOURCE_TEMPLATE)
return template.render(tags=result)
def tag_resource(self):
arn = self._get_param('ResourceArn')
tags = self._get_tags()
self.backend.tag_resource(arn, tags)
return self.response_template(TAG_RESOURCE_TEMPLATE).render()
def untag_resource(self):
arn = self._get_param('ResourceArn')
tag_keys = self._get_multi_param('TagKeys.member')
self.backend.untag_resource(arn, tag_keys)
return self.response_template(UNTAG_RESOURCE_TEMPLATE).render()
CREATE_TOPIC_TEMPLATE = """<CreateTopicResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<CreateTopicResult>
@ -1072,3 +1101,33 @@ CONFIRM_SUBSCRIPTION_TEMPLATE = """<ConfirmSubscriptionResponse xmlns="http://sn
<RequestId>16eb4dde-7b3c-5b3e-a22a-1fe2a92d3293</RequestId>
</ResponseMetadata>
</ConfirmSubscriptionResponse>"""
LIST_TAGS_FOR_RESOURCE_TEMPLATE = """<ListTagsForResourceResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<ListTagsForResourceResult>
<Tags>
{% for name, value in tags.items() %}
<member>
<Key>{{ name }}</Key>
<Value>{{ value }}</Value>
</member>
{% endfor %}
</Tags>
</ListTagsForResourceResult>
<ResponseMetadata>
<RequestId>97fa763f-861b-5223-a946-20251f2a42e2</RequestId>
</ResponseMetadata>
</ListTagsForResourceResponse>"""
TAG_RESOURCE_TEMPLATE = """<TagResourceResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<TagResourceResult/>
<ResponseMetadata>
<RequestId>fd4ab1da-692f-50a7-95ad-e7c665877d98</RequestId>
</ResponseMetadata>
</TagResourceResponse>"""
UNTAG_RESOURCE_TEMPLATE = """<UntagResourceResponse xmlns="http://sns.amazonaws.com/doc/2010-03-31/">
<UntagResourceResult/>
<ResponseMetadata>
<RequestId>14eb7b1a-4cbd-5a56-80db-2d06412df769</RequestId>
</ResponseMetadata>
</UntagResourceResponse>"""