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:
parent
36aa5943d1
commit
481266ae9f
1 changed files with 53 additions and 48 deletions
|
@ -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'):
|
if hasattr(err, 'error_message'):
|
||||||
error = err.error_message
|
error = err.error_message
|
||||||
elif hasattr(err, 'message'):
|
elif hasattr(err, 'message'):
|
||||||
error = err.message
|
error = err.message
|
||||||
else:
|
else:
|
||||||
error = '%s: %s' % (Exception, err)
|
error = '%s: %s' % (Exception, err)
|
||||||
try:
|
|
||||||
error_msg = json.loads(error)
|
return 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,17 +207,21 @@ 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.
|
||||||
|
@ -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)
|
||||||
|
|
Loading…
Reference in a new issue