Allow SNS topics to be created without subscriptions. Also added better error handling around boto calls.

This commit is contained in:
Rob White 2016-02-01 21:21:00 +11:00
parent b51efc51bc
commit ce9aed9c52

View file

@ -132,8 +132,8 @@ import json
import re import re
try: try:
import boto
import boto.sns import boto.sns
from boto.exception import BotoServerError
HAS_BOTO = True HAS_BOTO = True
except ImportError: except ImportError:
HAS_BOTO = False HAS_BOTO = False
@ -146,12 +146,15 @@ def canonicalize_endpoint(protocol, endpoint):
return endpoint return endpoint
def get_all_topics(connection, module):
def get_all_topics(connection):
next_token = None next_token = None
topics = [] topics = []
while True: while True:
try:
response = connection.get_all_topics(next_token) response = connection.get_all_topics(next_token)
except BotoServerError, e:
module.fail_json(msg=e.message)
topics.extend(response['ListTopicsResponse']['ListTopicsResult']['Topics']) topics.extend(response['ListTopicsResponse']['ListTopicsResult']['Topics'])
next_token = \ next_token = \
response['ListTopicsResponse']['ListTopicsResult']['NextToken'] response['ListTopicsResponse']['ListTopicsResult']['NextToken']
@ -160,15 +163,16 @@ def get_all_topics(connection):
return [t['TopicArn'] for t in topics] return [t['TopicArn'] for t in topics]
def arn_topic_lookup(connection, short_topic): def arn_topic_lookup(connection, short_topic, module):
# topic names cannot have colons, so this captures the full topic name # topic names cannot have colons, so this captures the full topic name
all_topics = get_all_topics(connection) all_topics = get_all_topics(connection, module)
lookup_topic = ':%s' % short_topic lookup_topic = ':%s' % short_topic
for topic in all_topics: for topic in all_topics:
if topic.endswith(lookup_topic): if topic.endswith(lookup_topic):
return topic return topic
return None return None
def main(): def main():
argument_spec = ec2_argument_spec() argument_spec = ec2_argument_spec()
argument_spec.update( argument_spec.update(
@ -179,7 +183,7 @@ def main():
display_name=dict(type='str', required=False), display_name=dict(type='str', required=False),
policy=dict(type='dict', required=False), policy=dict(type='dict', required=False),
delivery_policy=dict(type='dict', required=False), delivery_policy=dict(type='dict', required=False),
subscriptions=dict(type='list', required=False), subscriptions=dict(default=[], type='list', required=False),
purge_subscriptions=dict(type='bool', default=True), purge_subscriptions=dict(type='bool', default=True),
) )
) )
@ -214,7 +218,7 @@ def main():
# topics cannot contain ':', so thats the decider # topics cannot contain ':', so thats the decider
if ':' in name: if ':' in name:
all_topics = get_all_topics(connection) all_topics = get_all_topics(connection, module)
if name in all_topics: if name in all_topics:
arn_topic = name arn_topic = name
elif state == 'absent': elif state == 'absent':
@ -223,7 +227,7 @@ def main():
module.fail_json(msg="specified an ARN for a topic but it doesn't" module.fail_json(msg="specified an ARN for a topic but it doesn't"
" exist") " exist")
else: else:
arn_topic = arn_topic_lookup(connection, name) arn_topic = arn_topic_lookup(connection, name, module)
if not arn_topic: if not arn_topic:
if state == 'absent': if state == 'absent':
module.exit_json(changed=False) module.exit_json(changed=False)
@ -234,15 +238,22 @@ def main():
changed=True changed=True
topic_created = True topic_created = True
try:
connection.create_topic(name) connection.create_topic(name)
arn_topic = arn_topic_lookup(connection, name) except BotoServerError, e:
module.fail_json(msg=e.message)
arn_topic = arn_topic_lookup(connection, name, module)
while not arn_topic: while not arn_topic:
time.sleep(3) time.sleep(3)
arn_topic = arn_topic_lookup(connection, name) arn_topic = arn_topic_lookup(connection, name, module)
if arn_topic and state == "absent": if arn_topic and state == "absent":
if not check_mode: if not check_mode:
try:
connection.delete_topic(arn_topic) connection.delete_topic(arn_topic)
except BotoServerError, e:
module.fail_json(msg=e.message)
module.exit_json(changed=True) module.exit_json(changed=True)
topic_attributes = connection.get_topic_attributes(arn_topic) \ topic_attributes = connection.get_topic_attributes(arn_topic) \
@ -252,30 +263,40 @@ def main():
changed = True changed = True
attributes_set.append('display_name') attributes_set.append('display_name')
if not check_mode: if not check_mode:
connection.set_topic_attributes(arn_topic, 'DisplayName', try:
display_name) connection.set_topic_attributes(arn_topic, 'DisplayName', display_name)
except BotoServerError, e:
module.fail_json(msg=e.message)
if policy and policy != json.loads(topic_attributes['policy']): if policy and policy != json.loads(topic_attributes['policy']):
changed = True changed = True
attributes_set.append('policy') attributes_set.append('policy')
if not check_mode: if not check_mode:
connection.set_topic_attributes(arn_topic, 'Policy', try:
json.dumps(policy)) connection.set_topic_attributes(arn_topic, 'Policy', json.dumps(policy))
except BotoServerError, e:
module.fail_json(msg=e.message)
if delivery_policy and ('DeliveryPolicy' not in topic_attributes or \ if delivery_policy and ('DeliveryPolicy' not in topic_attributes or \
delivery_policy != json.loads(topic_attributes['DeliveryPolicy'])): delivery_policy != json.loads(topic_attributes['DeliveryPolicy'])):
changed = True changed = True
attributes_set.append('delivery_policy') attributes_set.append('delivery_policy')
if not check_mode: if not check_mode:
connection.set_topic_attributes(arn_topic, 'DeliveryPolicy', try:
json.dumps(delivery_policy)) connection.set_topic_attributes(arn_topic, 'DeliveryPolicy',json.dumps(delivery_policy))
except BotoServerError, e:
module.fail_json(msg=e.message)
next_token = None next_token = None
aws_subscriptions = [] aws_subscriptions = []
while True: while True:
try:
response = connection.get_all_subscriptions_by_topic(arn_topic, response = connection.get_all_subscriptions_by_topic(arn_topic,
next_token) next_token)
except BotoServerError, e:
module.fail_json(msg=e.message)
aws_subscriptions.extend(response['ListSubscriptionsByTopicResponse'] \ aws_subscriptions.extend(response['ListSubscriptionsByTopicResponse'] \
['ListSubscriptionsByTopicResult']['Subscriptions']) ['ListSubscriptionsByTopicResult']['Subscriptions'])
next_token = response['ListSubscriptionsByTopicResponse'] \ next_token = response['ListSubscriptionsByTopicResponse'] \
@ -286,6 +307,7 @@ def main():
desired_subscriptions = [(sub['protocol'], desired_subscriptions = [(sub['protocol'],
canonicalize_endpoint(sub['protocol'], sub['endpoint'])) for sub in canonicalize_endpoint(sub['protocol'], sub['endpoint'])) for sub in
subscriptions] subscriptions]
aws_subscriptions_list = [] aws_subscriptions_list = []
for sub in aws_subscriptions: for sub in aws_subscriptions:
@ -296,14 +318,20 @@ def main():
changed = True changed = True
subscriptions_deleted.append(sub_key) subscriptions_deleted.append(sub_key)
if not check_mode: if not check_mode:
try:
connection.unsubscribe(sub['SubscriptionArn']) connection.unsubscribe(sub['SubscriptionArn'])
except BotoServerError, e:
module.fail_json(msg=e.message)
for (protocol, endpoint) in desired_subscriptions: for (protocol, endpoint) in desired_subscriptions:
if (protocol, endpoint) not in aws_subscriptions_list: if (protocol, endpoint) not in aws_subscriptions_list:
changed = True changed = True
subscriptions_added.append(sub) subscriptions_added.append(sub)
if not check_mode: if not check_mode:
try:
connection.subscribe(arn_topic, protocol, endpoint) connection.subscribe(arn_topic, protocol, endpoint)
except BotoServerError, e:
module.fail_json(msg=e.message)
module.exit_json(changed=changed, topic_created=topic_created, module.exit_json(changed=changed, topic_created=topic_created,
attributes_set=attributes_set, attributes_set=attributes_set,
@ -313,4 +341,5 @@ def main():
from ansible.module_utils.basic import * from ansible.module_utils.basic import *
from ansible.module_utils.ec2 import * from ansible.module_utils.ec2 import *
if __name__ == '__main__':
main() main()