Start using ClientRequestTokens in event lists (#31997)

* Start using ClientRequestTokens in event lists

* Include request token in all reqs that support it (basically all but check mode/changeset)

* Update placebo recordings

* Add comments for CRQ popping
This commit is contained in:
Ryan Brown 2017-10-23 14:39:13 -04:00 committed by Sloane Hertel
commit 11c225e039
33 changed files with 550 additions and 913 deletions

View file

@ -223,6 +223,7 @@ stack_outputs:
import json
import time
import uuid
import traceback
from hashlib import sha1
@ -237,15 +238,25 @@ import ansible.module_utils.ec2
# import a class, otherwise we'll use a fully qualified path
from ansible.module_utils.ec2 import AWSRetry, boto_exception
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_bytes
from ansible.module_utils._text import to_bytes, to_native
def get_stack_events(cfn, stack_name):
def get_stack_events(cfn, stack_name, token_filter=None):
'''This event data was never correct, it worked as a side effect. So the v2.3 format is different.'''
ret = {'events':[], 'log':[]}
try:
events = cfn.describe_stack_events(StackName=stack_name)
pg = cfn.get_paginator(
'describe_stack_events'
).paginate(
StackName=stack_name
)
if token_filter is not None:
events = list(pg.search(
"StackEvents[?ClientRequestToken == '{0}']".format(token_filter)
))
else:
events = list(pg)
except (botocore.exceptions.ValidationError, botocore.exceptions.ClientError) as err:
error_msg = boto_exception(err)
if 'does not exist' in error_msg:
@ -255,7 +266,7 @@ def get_stack_events(cfn, stack_name):
ret['log'].append('Unknown error: ' + str(error_msg))
return ret
for e in events.get('StackEvents', []):
for e in events:
eventline = 'StackEvent {ResourceType} {LogicalResourceId} {ResourceStatus}'.format(**e)
ret['events'].append(eventline)
@ -281,7 +292,7 @@ def create_stack(module, stack_params, cfn):
try:
cfn.create_stack(**stack_params)
result = stack_operation(cfn, stack_params['StackName'], 'CREATE')
result = stack_operation(cfn, stack_params['StackName'], 'CREATE', stack_params['ClientRequestToken'])
except Exception as 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())
@ -300,6 +311,10 @@ def create_changeset(module, stack_params, cfn):
module.fail_json(msg="Either 'template' or 'template_url' is required.")
if module.params['changeset_name'] is not None:
stack_params['ChangeSetName'] = module.params['changeset_name']
# changesets don't accept ClientRequestToken parameters
stack_params.pop('ClientRequestToken', None)
try:
changeset_name = build_changeset_name(stack_params)
stack_params['ChangeSetName'] = changeset_name
@ -336,7 +351,7 @@ def update_stack(module, stack_params, cfn):
# don't need to be updated.
try:
cfn.update_stack(**stack_params)
result = stack_operation(cfn, stack_params['StackName'], 'UPDATE')
result = stack_operation(cfn, stack_params['StackName'], 'UPDATE', stack_params['ClientRequestToken'])
except Exception as err:
error_msg = boto_exception(err)
if 'No updates are to be performed.' in error_msg:
@ -368,7 +383,7 @@ def boto_supports_termination_protection(cfn):
return hasattr(cfn, "update_termination_protection")
def stack_operation(cfn, stack_name, operation):
def stack_operation(cfn, stack_name, operation, op_token=None):
'''gets the status of a stack while it is created/updated/deleted'''
existed = []
while True:
@ -379,15 +394,15 @@ def stack_operation(cfn, stack_name, operation):
# If the stack previously existed, and now can't be found then it's
# been deleted successfully.
if 'yes' in existed or operation == 'DELETE': # stacks may delete fast, look in a few ways.
ret = get_stack_events(cfn, stack_name)
ret = get_stack_events(cfn, stack_name, op_token)
ret.update({'changed': True, 'output': 'Stack Deleted'})
return ret
else:
return {'changed': True, 'failed': True, 'output': 'Stack Not Found', 'exception': traceback.format_exc()}
ret = get_stack_events(cfn, stack_name)
ret = get_stack_events(cfn, stack_name, op_token)
if not stack:
if 'yes' in existed or operation == 'DELETE': # stacks may delete fast, look in a few ways.
ret = get_stack_events(cfn, stack_name)
ret = get_stack_events(cfn, stack_name, op_token)
ret.update({'changed': True, 'output': 'Stack Deleted'})
return ret
else:
@ -430,6 +445,9 @@ def build_changeset_name(stack_params):
def check_mode_changeset(module, stack_params, cfn):
"""Create a change set, describe it and delete it before returning check mode outputs."""
stack_params['ChangeSetName'] = build_changeset_name(stack_params)
# changesets don't accept ClientRequestToken parameters
stack_params.pop('ClientRequestToken', None)
try:
change_set = cfn.create_change_set(**stack_params)
for i in range(60): # total time 5 min
@ -506,6 +524,7 @@ def main():
# collect the parameters that are passed to boto3. Keeps us from having so many scalars floating around.
stack_params = {
'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'],
'ClientRequestToken': to_native(uuid.uuid4()),
}
state = module.params['state']
stack_params['StackName'] = module.params['stack_name']
@ -610,7 +629,7 @@ def main():
result = {'changed': False, 'output': 'Stack not found.'}
else:
cfn.delete_stack(StackName=stack_params['StackName'])
result = stack_operation(cfn, stack_params['StackName'], 'DELETE')
result = stack_operation(cfn, stack_params['StackName'], 'DELETE', stack_params['ClientRequestToken'])
except Exception as err:
module.fail_json(msg=boto_exception(err), exception=traceback.format_exc())