Fixed bug in module_fail. Consistent error handling. Adding comments. Removal of unused variable. Removed unecessary wait_for option. was never been used to begin with. Trim down the stack_outputs. Don't need to include stack name and region since they are already required parameters. Rollback supported in status operations. Using dict when possible.

This commit is contained in:
James Martin 2013-06-28 18:25:17 -04:00
parent 36aa5943d1
commit 481266ae9f

View file

@ -60,13 +60,7 @@ options:
required: true required: true
default: null default: null
aliases: [] aliases: []
wait_for:
description:
- Wait while the stack is being created/updated/deleted.
required: false
default: "yes"
choices: [ "yes", "no" ]
aliases: []
requirements: [ "boto" ] requirements: [ "boto" ]
author: James S. Martin author: James S. Martin
''' '''
@ -89,30 +83,29 @@ tasks:
import boto.cloudformation.connection import boto.cloudformation.connection
import json import json
import time
class Region: class Region:
def __init__(self, region): def __init__(self, region):
'''connects boto to the region specified in the cloudformation template'''
self.name = region self.name = region
self.endpoint = 'cloudformation.%s.amazonaws.com' % region self.endpoint = 'cloudformation.%s.amazonaws.com' % region
def boto_exception(err): def boto_exception(err):
'''generic error message handler'''
if hasattr(err, 'error_message'):
error = err.error_message
elif hasattr(err, 'message'):
error = err.message
else:
error = '%s: %s' % (Exception, err)
if hasattr(err, 'error_message'): return error
error = err.error_message
elif hasattr(err, 'message'):
error = err.message
else:
error = '%s: %s' % (Exception, err)
try:
error_msg = json.loads(error)
except:
error_msg = {'Error': error}
return error_msg
def stack_operation(cfn, stack_name, operation): def stack_operation(cfn, stack_name, operation):
'''gets the status of a stack while it is created/updated/deleted'''
existed = [] existed = []
result = {} result = {}
operation_complete = False operation_complete = False
@ -122,20 +115,26 @@ def stack_operation(cfn, stack_name, operation):
existed.append('yes') existed.append('yes')
except: except:
if 'yes' in existed: if 'yes' in existed:
result = {'changed': True, 'output': 'Stack Deleted'} result = dict(changed=True,
result['events'] = map(str, list(stack.describe_events())) output='Stack Deleted',
events=map(str, list(stack.describe_events())))
else: else:
result = {'changed': True, 'output': 'Stack Not Found'} result = dict (changed= True, output='Stack Not Found')
break break
if '%s_COMPLETE' % operation == stack.stack_status: if '%s_COMPLETE' % operation == stack.stack_status:
result['changed'] = True result = dict(changed=True,
result['events'] = map(str, list(stack.describe_events())) events = map(str, list(stack.describe_events())),
result['output'] = 'Stack %s complete' % operation output = 'Stack %s complete' % operation )
break
if '%s_ROLLBACK_COMPLETE' % operation == stack.stack_status:
result = dict(changed=True,
events = map(str, list(stack.describe_events())),
output = 'Problem with %s. Rollback complete' % operation )
break break
elif '%s_FAILED' % operation == stack.stack_status: elif '%s_FAILED' % operation == stack.stack_status:
result['changed'] = False result = dict(changed=False,
result['events'] = map(str, list(stack.describe_events())) events = map(str, list(stack.describe_events())),
result['output'] = 'Stack %s failed' % operation output = 'Stack %s failed' % operation )
break break
else: else:
time.sleep(5) time.sleep(5)
@ -154,12 +153,10 @@ def main():
'us-west-2']), 'us-west-2']),
state=dict(default='present', choices=['present', 'absent']), state=dict(default='present', choices=['present', 'absent']),
template=dict(default=None, required=True), template=dict(default=None, required=True),
disable_rollback=dict(default=False), disable_rollback=dict(default=False)
wait_for=dict(default=True)
) )
) )
wait_for = module.params['wait_for']
state = module.params['state'] state = module.params['state']
stack_name = module.params['stack_name'] stack_name = module.params['stack_name']
region = Region(module.params['region']) region = Region(module.params['region'])
@ -167,10 +164,9 @@ def main():
disable_rollback = module.params['disable_rollback'] disable_rollback = module.params['disable_rollback']
template_parameters = module.params['template_parameters'] template_parameters = module.params['template_parameters']
# convert the template parameters ansible passes into a tuple for boto
template_parameters_tup = [(k, v) for k, v in template_parameters.items()] template_parameters_tup = [(k, v) for k, v in template_parameters.items()]
stack_outputs = {} stack_outputs = {}
stack_outputs[module.params['region']] = {}
stack_outputs[module.params['region']][stack_name] = {}
try: try:
cfn = boto.cloudformation.connection.CloudFormationConnection( cfn = boto.cloudformation.connection.CloudFormationConnection(
@ -178,11 +174,11 @@ def main():
except boto.exception.NoAuthHandlerFound, e: except boto.exception.NoAuthHandlerFound, e:
module.fail_json(msg=str(e)) module.fail_json(msg=str(e))
update = False update = False
stack_events = []
result = {} result = {}
operation = None operation = None
output = ''
# if state is present we are going to ensure that the stack is either
# created or updated
if state == 'present': if state == 'present':
try: try:
cfn.create_stack(stack_name, parameters=template_parameters_tup, cfn.create_stack(stack_name, parameters=template_parameters_tup,
@ -192,13 +188,16 @@ def main():
operation = 'CREATE' operation = 'CREATE'
except Exception, err: except Exception, err:
error_msg = boto_exception(err) error_msg = boto_exception(err)
if error_msg['Error']['Code'] == 'AlreadyExistsException': if 'AlreadyExistsException' in error_msg:
update = True update = True
else: else:
result = {'changed': False, 'output': error_msg} module.fail_json(msg=error_msg)
module.fail_json(**result)
if not update: if not update:
result = stack_operation(cfn, stack_name, operation) result = stack_operation(cfn, stack_name, operation)
# if the state is present and the stack already exists, we try to update it
# AWS will tell us if the stack template and parameters are the same and
# don't need to be updated.
if update: if update:
try: try:
cfn.update_stack(stack_name, parameters=template_parameters_tup, cfn.update_stack(stack_name, parameters=template_parameters_tup,
@ -208,22 +207,26 @@ def main():
operation = 'UPDATE' operation = 'UPDATE'
except Exception, err: except Exception, err:
error_msg = boto_exception(err) error_msg = boto_exception(err)
if error_msg['Error']['Message'] == 'No updates are to be performed.': if 'No updates are to be performed.' in error_msg:
output = error_msg['Error']['Message'] result = dict(changed=False, output='Stack is already up-to-date.')
result = {'changed': False, 'output': output} else:
module.fail_json(msg=error_msg)
if operation == 'UPDATE': if operation == 'UPDATE':
result = stack_operation(cfn, stack_name, operation) result = stack_operation(cfn, stack_name, operation)
# check the status of the stack while we are creating/updating it.
# and get the outputs of the stack
if state == 'present' or update: if state == 'present' or update:
stack = cfn.describe_stacks(stack_name)[0] stack = cfn.describe_stacks(stack_name)[0]
for output in stack.outputs: for output in stack.outputs:
stack_outputs[module.params['region']][stack_name][ stack_outputs[output.key] = output.value
output.key] = output.value
result['stack_outputs'] = stack_outputs result['stack_outputs'] = stack_outputs
# absent state is different because of the way delete_stack works. # absent state is different because of the way delete_stack works.
# problem is it it doesn't give an error if stack isn't found # problem is it it doesn't give an error if stack isn't found
# so must describe the stack first # so must describe the stack first
if state == 'absent': if state == 'absent':
try: try:
@ -231,8 +234,10 @@ def main():
operation = 'DELETE' operation = 'DELETE'
except Exception, err: except Exception, err:
error_msg = boto_exception(err) error_msg = boto_exception(err)
result = {'changed': False, 'output': error_msg} if 'Stack:%s does not exist' % stack_name in error_msg:
module.fail_json(result) result = dict(changed=False, output='Stack not found.')
else:
module.fail_json(msg=error_msg)
if operation == 'DELETE': if operation == 'DELETE':
cfn.delete_stack(stack_name) cfn.delete_stack(stack_name)
result = stack_operation(cfn, stack_name, operation) result = stack_operation(cfn, stack_name, operation)