@ -113,6 +113,11 @@ options:
must be specified ( but only one of them ) . If ' state ' ispresent , the stack does exist , and neither ' template ' ,
must be specified ( but only one of them ) . If ' state ' ispresent , the stack does exist , and neither ' template ' ,
' template_body ' nor ' template_url ' are specified , the previous template will be reused .
' template_body ' nor ' template_url ' are specified , the previous template will be reused .
version_added : " 2.5 "
version_added : " 2.5 "
events_limit :
description :
- Maximum number of CloudFormation events to fetch from a stack when creating or updating it .
default : 200
version_added : " 2.7 "
author : " James S. Martin (@jsmartin) "
author : " James S. Martin (@jsmartin) "
extends_documentation_fragment :
extends_documentation_fragment :
@ -276,7 +281,7 @@ from ansible.module_utils.basic import AnsibleModule
from ansible . module_utils . _text import to_bytes , to_native
from ansible . module_utils . _text import to_bytes , to_native
def get_stack_events ( cfn , stack_name , token_filter= None ) :
def get_stack_events ( cfn , stack_name , events_limit, token_filter= None ) :
''' This event data was never correct, it worked as a side effect. So the v2.3 format is different. '''
''' This event data was never correct, it worked as a side effect. So the v2.3 format is different. '''
ret = { ' events ' : [ ] , ' log ' : [ ] }
ret = { ' events ' : [ ] , ' log ' : [ ] }
@ -284,7 +289,8 @@ def get_stack_events(cfn, stack_name, token_filter=None):
pg = cfn . get_paginator (
pg = cfn . get_paginator (
' describe_stack_events '
' describe_stack_events '
) . paginate (
) . paginate (
StackName = stack_name
StackName = stack_name ,
PaginationConfig = { ' MaxItems ' : events_limit }
)
)
if token_filter is not None :
if token_filter is not None :
events = list ( pg . search (
events = list ( pg . search (
@ -312,7 +318,7 @@ def get_stack_events(cfn, stack_name, token_filter=None):
return ret
return ret
def create_stack ( module , stack_params , cfn ):
def create_stack ( module , stack_params , cfn , events_limit ):
if ' TemplateBody ' not in stack_params and ' TemplateURL ' not in stack_params :
if ' TemplateBody ' not in stack_params and ' TemplateURL ' not in stack_params :
module . fail_json ( msg = " Either ' template ' , ' template_body ' or ' template_url ' is required when the stack does not exist. " )
module . fail_json ( msg = " Either ' template ' , ' template_body ' or ' template_url ' is required when the stack does not exist. " )
@ -329,7 +335,7 @@ def create_stack(module, stack_params, cfn):
try :
try :
cfn . create_stack ( * * stack_params )
cfn . create_stack ( * * stack_params )
result = stack_operation ( cfn , stack_params [ ' StackName ' ] , ' CREATE ' , stack_params. get ( ' ClientRequestToken ' , None ) )
result = stack_operation ( cfn , stack_params [ ' StackName ' ] , ' CREATE ' , events_limit, stack_params. get ( ' ClientRequestToken ' , None ) )
except Exception as err :
except Exception as err :
error_msg = boto_exception ( err )
error_msg = boto_exception ( err )
module . fail_json ( msg = " Failed to create stack {0} : {1} . " . format ( stack_params . get ( ' StackName ' ) , error_msg ) , exception = traceback . format_exc ( ) )
module . fail_json ( msg = " Failed to create stack {0} : {1} . " . format ( stack_params . get ( ' StackName ' ) , error_msg ) , exception = traceback . format_exc ( ) )
@ -343,7 +349,7 @@ def list_changesets(cfn, stack_name):
return [ cs [ ' ChangeSetName ' ] for cs in res [ ' Summaries ' ] ]
return [ cs [ ' ChangeSetName ' ] for cs in res [ ' Summaries ' ] ]
def create_changeset ( module , stack_params , cfn ):
def create_changeset ( module , stack_params , cfn , events_limit ):
if ' TemplateBody ' not in stack_params and ' TemplateURL ' not in stack_params :
if ' TemplateBody ' not in stack_params and ' TemplateURL ' not in stack_params :
module . fail_json ( msg = " Either ' template ' or ' template_url ' is required. " )
module . fail_json ( msg = " Either ' template ' or ' template_url ' is required. " )
if module . params [ ' changeset_name ' ] is not None :
if module . params [ ' changeset_name ' ] is not None :
@ -382,7 +388,7 @@ def create_changeset(module, stack_params, cfn):
break
break
# Lets not hog the cpu/spam the AWS API
# Lets not hog the cpu/spam the AWS API
time . sleep ( 1 )
time . sleep ( 1 )
result = stack_operation ( cfn , stack_params [ ' StackName ' ] , ' CREATE_CHANGESET ' )
result = stack_operation ( cfn , stack_params [ ' StackName ' ] , ' CREATE_CHANGESET ' , events_limit )
result [ ' warnings ' ] = [ ' Created changeset named %s for stack %s ' % ( changeset_name , stack_params [ ' StackName ' ] ) ,
result [ ' warnings ' ] = [ ' Created changeset named %s for stack %s ' % ( changeset_name , stack_params [ ' StackName ' ] ) ,
' You can execute it using: aws cloudformation execute-change-set --change-set-name %s ' % cs [ ' Id ' ] ,
' You can execute it using: aws cloudformation execute-change-set --change-set-name %s ' % cs [ ' Id ' ] ,
' NOTE that dependencies on this stack might fail due to pending changes! ' ]
' NOTE that dependencies on this stack might fail due to pending changes! ' ]
@ -398,7 +404,7 @@ def create_changeset(module, stack_params, cfn):
return result
return result
def update_stack ( module , stack_params , cfn ):
def update_stack ( module , stack_params , cfn , events_limit ):
if ' TemplateBody ' not in stack_params and ' TemplateURL ' not in stack_params :
if ' TemplateBody ' not in stack_params and ' TemplateURL ' not in stack_params :
stack_params [ ' UsePreviousTemplate ' ] = True
stack_params [ ' UsePreviousTemplate ' ] = True
@ -407,7 +413,7 @@ def update_stack(module, stack_params, cfn):
# don't need to be updated.
# don't need to be updated.
try :
try :
cfn . update_stack ( * * stack_params )
cfn . update_stack ( * * stack_params )
result = stack_operation ( cfn , stack_params [ ' StackName ' ] , ' UPDATE ' , stack_params. get ( ' ClientRequestToken ' , None ) )
result = stack_operation ( cfn , stack_params [ ' StackName ' ] , ' UPDATE ' , events_limit, stack_params. get ( ' ClientRequestToken ' , None ) )
except Exception as err :
except Exception as err :
error_msg = boto_exception ( err )
error_msg = boto_exception ( err )
if ' No updates are to be performed. ' in error_msg :
if ' No updates are to be performed. ' in error_msg :
@ -439,7 +445,7 @@ def boto_supports_termination_protection(cfn):
return hasattr ( cfn , " update_termination_protection " )
return hasattr ( cfn , " update_termination_protection " )
def stack_operation ( cfn , stack_name , operation , op_token= None ) :
def stack_operation ( cfn , stack_name , operation , events_limit, op_token= None ) :
''' gets the status of a stack while it is created/updated/deleted '''
''' gets the status of a stack while it is created/updated/deleted '''
existed = [ ]
existed = [ ]
while True :
while True :
@ -450,15 +456,15 @@ def stack_operation(cfn, stack_name, operation, op_token=None):
# If the stack previously existed, and now can't be found then it's
# If the stack previously existed, and now can't be found then it's
# been deleted successfully.
# been deleted successfully.
if ' yes ' in existed or operation == ' DELETE ' : # stacks may delete fast, look in a few ways.
if ' yes ' in existed or operation == ' DELETE ' : # stacks may delete fast, look in a few ways.
ret = get_stack_events ( cfn , stack_name , op_token)
ret = get_stack_events ( cfn , stack_name , events_limit, op_token)
ret . update ( { ' changed ' : True , ' output ' : ' Stack Deleted ' } )
ret . update ( { ' changed ' : True , ' output ' : ' Stack Deleted ' } )
return ret
return ret
else :
else :
return { ' changed ' : True , ' failed ' : True , ' output ' : ' Stack Not Found ' , ' exception ' : traceback . format_exc ( ) }
return { ' changed ' : True , ' failed ' : True , ' output ' : ' Stack Not Found ' , ' exception ' : traceback . format_exc ( ) }
ret = get_stack_events ( cfn , stack_name , op_token)
ret = get_stack_events ( cfn , stack_name , events_limit, op_token)
if not stack :
if not stack :
if ' yes ' in existed or operation == ' DELETE ' : # stacks may delete fast, look in a few ways.
if ' yes ' in existed or operation == ' DELETE ' : # stacks may delete fast, look in a few ways.
ret = get_stack_events ( cfn , stack_name , op_token)
ret = get_stack_events ( cfn , stack_name , events_limit, op_token)
ret . update ( { ' changed ' : True , ' output ' : ' Stack Deleted ' } )
ret . update ( { ' changed ' : True , ' output ' : ' Stack Deleted ' } )
return ret
return ret
else :
else :
@ -567,7 +573,8 @@ def main():
changeset_name = dict ( default = None , required = False ) ,
changeset_name = dict ( default = None , required = False ) ,
role_arn = dict ( default = None , required = False ) ,
role_arn = dict ( default = None , required = False ) ,
tags = dict ( default = None , type = ' dict ' ) ,
tags = dict ( default = None , type = ' dict ' ) ,
termination_protection = dict ( default = None , type = ' bool ' )
termination_protection = dict ( default = None , type = ' bool ' ) ,
events_limit = dict ( default = 200 , type = ' int ' ) ,
)
)
)
)
@ -665,14 +672,14 @@ def main():
if state == ' present ' :
if state == ' present ' :
if not stack_info :
if not stack_info :
result = create_stack ( module , stack_params , cfn )
result = create_stack ( module , stack_params , cfn , module . params . get ( ' events_limit ' ) )
elif module . params . get ( ' create_changeset ' ) :
elif module . params . get ( ' create_changeset ' ) :
result = create_changeset ( module , stack_params , cfn )
result = create_changeset ( module , stack_params , cfn , module . params . get ( ' events_limit ' ) )
else :
else :
if module . params . get ( ' termination_protection ' ) is not None :
if module . params . get ( ' termination_protection ' ) is not None :
update_termination_protection ( module , cfn , stack_params [ ' StackName ' ] ,
update_termination_protection ( module , cfn , stack_params [ ' StackName ' ] ,
bool ( module . params . get ( ' termination_protection ' ) ) )
bool ( module . params . get ( ' termination_protection ' ) ) )
result = update_stack ( module , stack_params , cfn )
result = update_stack ( module , stack_params , cfn , module . params . get ( ' events_limit ' ) )
# format the stack output
# format the stack output
@ -709,7 +716,8 @@ def main():
cfn . delete_stack ( StackName = stack_params [ ' StackName ' ] )
cfn . delete_stack ( StackName = stack_params [ ' StackName ' ] )
else :
else :
cfn . delete_stack ( StackName = stack_params [ ' StackName ' ] , RoleARN = stack_params [ ' RoleARN ' ] )
cfn . delete_stack ( StackName = stack_params [ ' StackName ' ] , RoleARN = stack_params [ ' RoleARN ' ] )
result = stack_operation ( cfn , stack_params [ ' StackName ' ] , ' DELETE ' , stack_params . get ( ' ClientRequestToken ' , None ) )
result = stack_operation ( cfn , stack_params [ ' StackName ' ] , ' DELETE ' , module . params . get ( ' events_limit ' ) ,
stack_params . get ( ' ClientRequestToken ' , None ) )
except Exception as err :
except Exception as err :
module . fail_json ( msg = boto_exception ( err ) , exception = traceback . format_exc ( ) )
module . fail_json ( msg = boto_exception ( err ) , exception = traceback . format_exc ( ) )