summaryrefslogtreecommitdiff
path: root/lib/ansible/modules/extras/cloud/amazon/sns_topic.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ansible/modules/extras/cloud/amazon/sns_topic.py')
-rw-r--r--lib/ansible/modules/extras/cloud/amazon/sns_topic.py407
1 files changed, 407 insertions, 0 deletions
diff --git a/lib/ansible/modules/extras/cloud/amazon/sns_topic.py b/lib/ansible/modules/extras/cloud/amazon/sns_topic.py
new file mode 100644
index 0000000000..f4916693ed
--- /dev/null
+++ b/lib/ansible/modules/extras/cloud/amazon/sns_topic.py
@@ -0,0 +1,407 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# This is a free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This Ansible library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this library. If not, see <http://www.gnu.org/licenses/>.
+
+
+DOCUMENTATION = """
+module: sns_topic
+short_description: Manages AWS SNS topics and subscriptions
+description:
+ - The M(sns_topic) module allows you to create, delete, and manage subscriptions for AWS SNS topics.
+version_added: 2.0
+author:
+ - "Joel Thompson (@joelthompson)"
+ - "Fernando Jose Pando (@nand0p)"
+options:
+ name:
+ description:
+ - The name or ARN of the SNS topic to converge
+ required: True
+ state:
+ description:
+ - Whether to create or destroy an SNS topic
+ required: False
+ default: present
+ choices: ["absent", "present"]
+ display_name:
+ description:
+ - Display name of the topic
+ required: False
+ default: None
+ policy:
+ description:
+ - Policy to apply to the SNS topic
+ required: False
+ default: None
+ delivery_policy:
+ description:
+ - Delivery policy to apply to the SNS topic
+ required: False
+ default: None
+ subscriptions:
+ description:
+ - List of subscriptions to apply to the topic. Note that AWS requires
+ subscriptions to be confirmed, so you will need to confirm any new
+ subscriptions.
+ required: False
+ default: []
+ purge_subscriptions:
+ description:
+ - "Whether to purge any subscriptions not listed here. NOTE: AWS does not
+ allow you to purge any PendingConfirmation subscriptions, so if any
+ exist and would be purged, they are silently skipped. This means that
+ somebody could come back later and confirm the subscription. Sorry.
+ Blame Amazon."
+ required: False
+ default: True
+extends_documentation_fragment: aws
+requirements: [ "boto" ]
+"""
+
+EXAMPLES = """
+
+- name: Create alarm SNS topic
+ sns_topic:
+ name: "alarms"
+ state: present
+ display_name: "alarm SNS topic"
+ delivery_policy:
+ http:
+ defaultHealthyRetryPolicy:
+ minDelayTarget: 2
+ maxDelayTarget: 4
+ numRetries: 3
+ numMaxDelayRetries: 5
+ backoffFunction: "<linear|arithmetic|geometric|exponential>"
+ disableSubscriptionOverrides: True
+ defaultThrottlePolicy:
+ maxReceivesPerSecond: 10
+ subscriptions:
+ - endpoint: "my_email_address@example.com"
+ protocol: "email"
+ - endpoint: "my_mobile_number"
+ protocol: "sms"
+
+"""
+
+RETURN = '''
+sns_arn:
+ description: The ARN of the topic you are modifying
+ type: string
+ sample: "arn:aws:sns:us-east-1:123456789012:my_topic_name"
+
+sns_topic:
+ description: Dict of sns topic details
+ type: dict
+ sample:
+ name: sns-topic-name
+ state: present
+ display_name: default
+ policy: {}
+ delivery_policy: {}
+ subscriptions_new: []
+ subscriptions_existing: []
+ subscriptions_deleted: []
+ subscriptions_added: []
+ subscriptions_purge': false
+ check_mode: false
+ topic_created: false
+ topic_deleted: false
+ attributes_set: []
+'''
+
+import sys
+import time
+import json
+import re
+
+try:
+ import boto.sns
+ from boto.exception import BotoServerError
+ HAS_BOTO = True
+except ImportError:
+ HAS_BOTO = False
+
+
+class SnsTopicManager(object):
+ """ Handles SNS Topic creation and destruction """
+
+ def __init__(self,
+ module,
+ name,
+ state,
+ display_name,
+ policy,
+ delivery_policy,
+ subscriptions,
+ purge_subscriptions,
+ check_mode,
+ region,
+ **aws_connect_params):
+
+ self.region = region
+ self.aws_connect_params = aws_connect_params
+ self.connection = self._get_boto_connection()
+ self.changed = False
+ self.module = module
+ self.name = name
+ self.state = state
+ self.display_name = display_name
+ self.policy = policy
+ self.delivery_policy = delivery_policy
+ self.subscriptions = subscriptions
+ self.subscriptions_existing = []
+ self.subscriptions_deleted = []
+ self.subscriptions_added = []
+ self.purge_subscriptions = purge_subscriptions
+ self.check_mode = check_mode
+ self.topic_created = False
+ self.topic_deleted = False
+ self.arn_topic = None
+ self.attributes_set = []
+
+ def _get_boto_connection(self):
+ try:
+ return connect_to_aws(boto.sns, self.region,
+ **self.aws_connect_params)
+ except BotoServerError, err:
+ self.module.fail_json(msg=err.message)
+
+ def _get_all_topics(self):
+ next_token = None
+ topics = []
+ while True:
+ try:
+ response = self.connection.get_all_topics(next_token)
+ except BotoServerError, err:
+ module.fail_json(msg=err.message)
+ topics.extend(response['ListTopicsResponse']['ListTopicsResult']['Topics'])
+ next_token = response['ListTopicsResponse']['ListTopicsResult']['NextToken']
+ if not next_token:
+ break
+ return [t['TopicArn'] for t in topics]
+
+
+ def _arn_topic_lookup(self):
+ # topic names cannot have colons, so this captures the full topic name
+ all_topics = self._get_all_topics()
+ lookup_topic = ':%s' % self.name
+ for topic in all_topics:
+ if topic.endswith(lookup_topic):
+ return topic
+
+
+ def _create_topic(self):
+ self.changed = True
+ self.topic_created = True
+ if not self.check_mode:
+ self.connection.create_topic(self.name)
+ self.arn_topic = self._arn_topic_lookup()
+ while not self.arn_topic:
+ time.sleep(3)
+ self.arn_topic = self._arn_topic_lookup()
+
+
+ def _set_topic_attrs(self):
+ topic_attributes = self.connection.get_topic_attributes(self.arn_topic) \
+ ['GetTopicAttributesResponse'] ['GetTopicAttributesResult'] \
+ ['Attributes']
+
+ if self.display_name and self.display_name != topic_attributes['DisplayName']:
+ self.changed = True
+ self.attributes_set.append('display_name')
+ if not self.check_mode:
+ self.connection.set_topic_attributes(self.arn_topic, 'DisplayName',
+ self.display_name)
+
+ if self.policy and self.policy != json.loads(topic_attributes['Policy']):
+ self.changed = True
+ self.attributes_set.append('policy')
+ if not self.check_mode:
+ self.connection.set_topic_attributes(self.arn_topic, 'Policy',
+ json.dumps(self.policy))
+
+ if self.delivery_policy and ('DeliveryPolicy' not in topic_attributes or \
+ self.delivery_policy != json.loads(topic_attributes['DeliveryPolicy'])):
+ self.changed = True
+ self.attributes_set.append('delivery_policy')
+ if not self.check_mode:
+ self.connection.set_topic_attributes(self.arn_topic, 'DeliveryPolicy',
+ json.dumps(self.delivery_policy))
+
+
+ def _canonicalize_endpoint(self, protocol, endpoint):
+ if protocol == 'sms':
+ return re.sub('[^0-9]*', '', endpoint)
+ return endpoint
+
+
+ def _get_topic_subs(self):
+ next_token = None
+ while True:
+ response = self.connection.get_all_subscriptions_by_topic(self.arn_topic, next_token)
+ self.subscriptions_existing.extend(response['ListSubscriptionsByTopicResponse'] \
+ ['ListSubscriptionsByTopicResult']['Subscriptions'])
+ next_token = response['ListSubscriptionsByTopicResponse'] \
+ ['ListSubscriptionsByTopicResult']['NextToken']
+ if not next_token:
+ break
+
+ def _set_topic_subs(self):
+ subscriptions_existing_list = []
+ desired_subscriptions = [(sub['protocol'],
+ self._canonicalize_endpoint(sub['protocol'], sub['endpoint'])) for sub in
+ self.subscriptions]
+
+ if self.subscriptions_existing:
+ for sub in self.subscriptions_existing:
+ sub_key = (sub['Protocol'], sub['Endpoint'])
+ subscriptions_existing_list.append(sub_key)
+ if self.purge_subscriptions and sub_key not in desired_subscriptions and \
+ sub['SubscriptionArn'] != 'PendingConfirmation':
+ self.changed = True
+ self.subscriptions_deleted.append(sub_key)
+ if not self.check_mode:
+ self.connection.unsubscribe(sub['SubscriptionArn'])
+
+ for (protocol, endpoint) in desired_subscriptions:
+ if (protocol, endpoint) not in subscriptions_existing_list:
+ self.changed = True
+ self.subscriptions_added.append(sub)
+ if not self.check_mode:
+ self.connection.subscribe(self.arn_topic, protocol, endpoint)
+
+
+ def _delete_subscriptions(self):
+ # NOTE: subscriptions in 'PendingConfirmation' timeout in 3 days
+ # https://forums.aws.amazon.com/thread.jspa?threadID=85993
+ for sub in self.subscriptions_existing:
+ if sub['SubscriptionArn'] != 'PendingConfirmation':
+ self.subscriptions_deleted.append(sub['SubscriptionArn'])
+ self.changed = True
+ if not self.check_mode:
+ self.connection.unsubscribe(sub['SubscriptionArn'])
+
+
+ def _delete_topic(self):
+ self.topic_deleted = True
+ self.changed = True
+ if not self.check_mode:
+ self.connection.delete_topic(self.arn_topic)
+
+
+ def ensure_ok(self):
+ self.arn_topic = self._arn_topic_lookup()
+ if not self.arn_topic:
+ self._create_topic()
+ self._set_topic_attrs()
+ self._get_topic_subs()
+ self._set_topic_subs()
+
+ def ensure_gone(self):
+ self.arn_topic = self._arn_topic_lookup()
+ if self.arn_topic:
+ self._get_topic_subs()
+ if self.subscriptions_existing:
+ self._delete_subscriptions()
+ self._delete_topic()
+
+
+ def get_info(self):
+ info = {
+ 'name': self.name,
+ 'state': self.state,
+ 'display_name': self.display_name,
+ 'policy': self.policy,
+ 'delivery_policy': self.delivery_policy,
+ '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
+ }
+
+ return info
+
+
+
+def main():
+ argument_spec = ec2_argument_spec()
+ argument_spec.update(
+ dict(
+ name=dict(type='str', required=True),
+ state=dict(type='str', default='present', choices=['present',
+ 'absent']),
+ display_name=dict(type='str', required=False),
+ policy=dict(type='dict', required=False),
+ delivery_policy=dict(type='dict', required=False),
+ subscriptions=dict(default=[], type='list', required=False),
+ purge_subscriptions=dict(type='bool', default=True),
+ )
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec,
+ supports_check_mode=True)
+
+ if not HAS_BOTO:
+ module.fail_json(msg='boto required for this module')
+
+ name = module.params.get('name')
+ state = module.params.get('state')
+ display_name = module.params.get('display_name')
+ policy = module.params.get('policy')
+ delivery_policy = module.params.get('delivery_policy')
+ subscriptions = module.params.get('subscriptions')
+ purge_subscriptions = module.params.get('purge_subscriptions')
+ check_mode = module.check_mode
+
+ region, ec2_url, aws_connect_params = get_aws_connection_info(module)
+ if not region:
+ module.fail_json(msg="region must be specified")
+
+ sns_topic = SnsTopicManager(module,
+ name,
+ state,
+ display_name,
+ policy,
+ delivery_policy,
+ subscriptions,
+ purge_subscriptions,
+ check_mode,
+ region,
+ **aws_connect_params)
+
+ if state == 'present':
+ sns_topic.ensure_ok()
+
+ elif state == 'absent':
+ sns_topic.ensure_gone()
+
+ sns_facts = dict(changed=sns_topic.changed,
+ sns_arn=sns_topic.arn_topic,
+ sns_topic=sns_topic.get_info())
+
+ module.exit_json(**sns_facts)
+
+
+from ansible.module_utils.basic import *
+from ansible.module_utils.ec2 import *
+
+if __name__ == '__main__':
+ main()