diff --git a/sns.py b/sns.py index a18c3279173..fc400bac5e0 100644 --- a/sns.py +++ b/sns.py @@ -134,22 +134,7 @@ pass # Handled by AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule - - -def arn_topic_lookup(module, client, short_topic): - lookup_topic = ':{0}'.format(short_topic) - - try: - paginator = client.get_paginator('list_topics') - topic_iterator = paginator.paginate() - for response in topic_iterator: - for topic in response['Topics']: - if topic['TopicArn'].endswith(lookup_topic): - return topic['TopicArn'] - except (BotoCoreError, ClientError) as e: - module.fail_json_aws(e, msg='Failed to look up topic ARN') - - return None +from ansible_collections.community.aws.plugins.module_utils.sns import topic_arn_lookup def main(): @@ -205,7 +190,7 @@ def main(): # Short names can't contain ':' so we'll assume this is the full ARN sns_kwargs['TopicArn'] = topic else: - sns_kwargs['TopicArn'] = arn_topic_lookup(module, client, topic) + sns_kwargs['TopicArn'] = topic_arn_lookup(client, module, topic) if not sns_kwargs['TopicArn']: module.fail_json(msg='Could not find topic: {0}'.format(topic)) diff --git a/sns_topic.py b/sns_topic.py index 37cf573ce58..817729c33e8 100644 --- a/sns_topic.py +++ b/sns_topic.py @@ -284,8 +284,6 @@ ''' import json -import re -import copy try: import botocore @@ -293,11 +291,14 @@ pass # handled by AnsibleAWSModule from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule -from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code from ansible_collections.amazon.aws.plugins.module_utils.core import scrub_none_parameters from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_policies -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry -from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict +from ansible_collections.community.aws.plugins.module_utils.sns import list_topics +from ansible_collections.community.aws.plugins.module_utils.sns import topic_arn_lookup +from ansible_collections.community.aws.plugins.module_utils.sns import compare_delivery_policies +from ansible_collections.community.aws.plugins.module_utils.sns import list_topic_subscriptions +from ansible_collections.community.aws.plugins.module_utils.sns import canonicalize_endpoint +from ansible_collections.community.aws.plugins.module_utils.sns import get_info class SnsTopicManager(object): @@ -334,36 +335,6 @@ def __init__(self, self.topic_arn = None self.attributes_set = [] - @AWSRetry.jittered_backoff() - def _list_topics_with_backoff(self): - paginator = self.connection.get_paginator('list_topics') - return paginator.paginate().build_full_result()['Topics'] - - @AWSRetry.jittered_backoff(catch_extra_error_codes=['NotFound']) - def _list_topic_subscriptions_with_backoff(self): - paginator = self.connection.get_paginator('list_subscriptions_by_topic') - return paginator.paginate(TopicArn=self.topic_arn).build_full_result()['Subscriptions'] - - @AWSRetry.jittered_backoff(catch_extra_error_codes=['NotFound']) - def _list_subscriptions_with_backoff(self): - paginator = self.connection.get_paginator('list_subscriptions') - return paginator.paginate().build_full_result()['Subscriptions'] - - def _list_topics(self): - try: - topics = self._list_topics_with_backoff() - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - self.module.fail_json_aws(e, msg="Couldn't get topic list") - return [t['TopicArn'] for t in topics] - - def _topic_arn_lookup(self): - # topic names cannot have colons, so this captures the full topic name - all_topics = self._list_topics() - lookup_topic = ':%s' % self.name - for topic in all_topics: - if topic.endswith(lookup_topic): - return topic - def _create_topic(self): attributes = {'FifoTopic': 'false'} tags = [] @@ -381,20 +352,6 @@ def _create_topic(self): self.topic_arn = response['TopicArn'] return True - def _compare_delivery_policies(self, policy_a, policy_b): - _policy_a = copy.deepcopy(policy_a) - _policy_b = copy.deepcopy(policy_b) - # AWS automatically injects disableSubscriptionOverrides if you set an - # http policy - if 'http' in policy_a: - if 'disableSubscriptionOverrides' not in policy_a['http']: - _policy_a['http']['disableSubscriptionOverrides'] = False - if 'http' in policy_b: - if 'disableSubscriptionOverrides' not in policy_b['http']: - _policy_b['http']['disableSubscriptionOverrides'] = False - comparison = (_policy_a != _policy_b) - return comparison - def _set_topic_attrs(self): changed = False try: @@ -423,7 +380,7 @@ def _set_topic_attrs(self): self.module.fail_json_aws(e, msg="Couldn't set topic policy") if self.delivery_policy and ('DeliveryPolicy' not in topic_attributes or - self._compare_delivery_policies(self.delivery_policy, json.loads(topic_attributes['DeliveryPolicy']))): + compare_delivery_policies(self.delivery_policy, json.loads(topic_attributes['DeliveryPolicy']))): changed = True self.attributes_set.append('delivery_policy') if not self.check_mode: @@ -434,22 +391,14 @@ def _set_topic_attrs(self): self.module.fail_json_aws(e, msg="Couldn't set topic delivery policy") return changed - def _canonicalize_endpoint(self, protocol, endpoint): - # AWS SNS expects phone numbers in - # and canonicalizes to E.164 format - # See - if protocol == 'sms': - return re.sub('[^0-9+]*', '', endpoint) - return endpoint - def _set_topic_subs(self): changed = False subscriptions_existing_list = set() desired_subscriptions = [(sub['protocol'], - self._canonicalize_endpoint(sub['protocol'], sub['endpoint'])) for sub in + canonicalize_endpoint(sub['protocol'], sub['endpoint'])) for sub in self.subscriptions] - for sub in self._list_topic_subscriptions(): + for sub in list_topic_subscriptions(self.connection, self.module, self.topic_arn): sub_key = (sub['Protocol'], sub['Endpoint']) subscriptions_existing_list.add(sub_key) if (self.purge_subscriptions and sub_key not in desired_subscriptions and @@ -472,23 +421,10 @@ def _set_topic_subs(self): self.module.fail_json_aws(e, msg="Couldn't subscribe to topic %s" % self.topic_arn) return changed - def _list_topic_subscriptions(self): - try: - return self._list_topic_subscriptions_with_backoff() - except is_boto3_error_code('AuthorizationError'): - try: - # potentially AuthorizationError when listing subscriptions for third party topic - return [sub for sub in self._list_subscriptions_with_backoff() - if sub['TopicArn'] == self.topic_arn] - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: - self.module.fail_json_aws(e, msg="Couldn't get subscriptions list for topic %s" % self.topic_arn) - except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except - self.module.fail_json_aws(e, msg="Couldn't get subscriptions list for topic %s" % self.topic_arn) - def _delete_subscriptions(self): # NOTE: subscriptions in 'PendingConfirmation' timeout in 3 days # https://forums.aws.amazon.com/thread.jspa?threadID=85993 - subscriptions = self._list_topic_subscriptions() + subscriptions = list_topic_subscriptions(self.connection, self.module, self.topic_arn) if not subscriptions: return False for sub in subscriptions: @@ -518,10 +454,10 @@ def ensure_ok(self): if self._name_is_arn(): self.topic_arn = self.name else: - self.topic_arn = self._topic_arn_lookup() + self.topic_arn = topic_arn_lookup(self.connection, self.module, self.name) if not self.topic_arn: changed = self._create_topic() - if self.topic_arn in self._list_topics(): + if self.topic_arn in list_topics(self.connection, self.module): changed |= self._set_topic_attrs() elif self.display_name or self.policy or self.delivery_policy: self.module.fail_json(msg="Cannot set display name, policy or delivery policy for SNS topics not owned by this account") @@ -533,37 +469,14 @@ def ensure_gone(self): if self._name_is_arn(): self.topic_arn = self.name else: - self.topic_arn = self._topic_arn_lookup() + self.topic_arn = topic_arn_lookup(self.connection, self.module, self.name) if self.topic_arn: - if self.topic_arn not in self._list_topics(): + if self.topic_arn not in list_topics(self.connection, self.module): self.module.fail_json(msg="Cannot use state=absent with third party ARN. Use subscribers=[] to unsubscribe") changed = self._delete_subscriptions() changed |= self._delete_topic() return changed - def get_info(self): - info = { - 'name': self.name, - 'topic_type': self.topic_type, - 'state': self.state, - 'subscriptions_new': self.subscriptions, - 'subscriptions_existing': self.subscriptions_existing, - 'subscriptions_deleted': self.subscriptions_deleted, - 'subscriptions_added': self.subscriptions_added, - 'subscriptions_purge': self.purge_subscriptions, - 'check_mode': self.check_mode, - 'topic_created': self.topic_created, - 'topic_deleted': self.topic_deleted, - 'attributes_set': self.attributes_set, - } - if self.state != 'absent': - if self.topic_arn in self._list_topics(): - info.update(camel_dict_to_snake_dict(self.connection.get_topic_attributes(TopicArn=self.topic_arn)['Attributes'])) - info['delivery_policy'] = info.pop('effective_delivery_policy') - info['subscriptions'] = [camel_dict_to_snake_dict(sub) for sub in self._list_topic_subscriptions()] - - return info - def main(): @@ -635,7 +548,7 @@ def main(): sns_facts = dict(changed=changed, sns_arn=sns_topic.topic_arn, - sns_topic=sns_topic.get_info()) + sns_topic=get_info(sns_topic.connection, module, sns_topic.topic_arn)) module.exit_json(**sns_facts)