Support 'termination protection' for cloudformation stacks (#31675)

* Support 'termination protection' for cloudformation stacks

- Pass in the stack_name and desired termination protection state to update_termination_protection

* Fix for failing cloudformation unit test

* Check if cfn has update_termination_protection attr

* Use hasattr to test if cfn supports update_termination_protection

* termination_protection shouldn't prevent update_stack call for existing stacks
pull/31838/head
Prasad Katti 7 years ago committed by Sloane Hertel
parent a8bc6f69d9
commit b9e15d0df1

@ -118,6 +118,10 @@ options:
required: false required: false
default: null default: null
version_added: "2.3" version_added: "2.3"
termination_protection:
description:
- enable or disable termination protection on the stack. Only works with botocore >= 1.7.18.
version_added: "2.5"
author: "James S. Martin (@jsmartin)" author: "James S. Martin (@jsmartin)"
extends_documentation_fragment: extends_documentation_fragment:
@ -173,6 +177,16 @@ EXAMPLES = '''
ClusterSize: 3 ClusterSize: 3
tags: tags:
Stack: ansible-cloudformation Stack: ansible-cloudformation
# Enable termination protection on a stack.
# If the stack already exists, this will update its termination protection
- name: enable termination protection during stack creation
cloudformation:
stack_name: my_stack
state: present
template_url: https://s3.amazonaws.com/my-bucket/cloudformation.template
termination_protection: yes
''' '''
RETURN = ''' RETURN = '''
@ -256,8 +270,14 @@ def create_stack(module, stack_params, cfn):
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 when the stack does not exist.") module.fail_json(msg="Either 'template' or 'template_url' is required when the stack does not exist.")
# 'disablerollback' only applies on creation, not update. # 'disablerollback' and 'EnableTerminationProtection' only
# apply on creation, not update.
stack_params['DisableRollback'] = module.params['disable_rollback'] stack_params['DisableRollback'] = module.params['disable_rollback']
if module.params.get('termination_protection') is not None:
if boto_supports_termination_protection(cfn):
stack_params['EnableTerminationProtection'] = bool(module.params.get('termination_protection'))
else:
module.fail_json(msg="termination_protection parameter requires botocore >= 1.7.18")
try: try:
cfn.create_stack(**stack_params) cfn.create_stack(**stack_params)
@ -328,6 +348,26 @@ def update_stack(module, stack_params, cfn):
return result return result
def update_termination_protection(module, cfn, stack_name, desired_termination_protection_state):
'''updates termination protection of a stack'''
if not boto_supports_termination_protection(cfn):
module.fail_json(msg="termination_protection parameter requires botocore >= 1.7.18")
stack = get_stack_facts(cfn, stack_name)
if stack:
if stack['EnableTerminationProtection'] is not desired_termination_protection_state:
try:
cfn.update_termination_protection(
EnableTerminationProtection=desired_termination_protection_state,
StackName=stack_name)
except botocore.exceptions.ClientError as e:
module.fail_json(msg=boto_exception(e), exception=traceback.format_exc())
def boto_supports_termination_protection(cfn):
'''termination protection was added in botocore 1.7.18'''
return hasattr(cfn, "update_termination_protection")
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''' '''gets the status of a stack while it is created/updated/deleted'''
existed = [] existed = []
@ -450,7 +490,8 @@ def main():
create_changeset=dict(default=False, type='bool'), create_changeset=dict(default=False, type='bool'),
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')
) )
) )
@ -511,6 +552,8 @@ def main():
cfn.describe_stacks = backoff_wrapper(cfn.describe_stacks) cfn.describe_stacks = backoff_wrapper(cfn.describe_stacks)
cfn.list_stack_resources = backoff_wrapper(cfn.list_stack_resources) cfn.list_stack_resources = backoff_wrapper(cfn.list_stack_resources)
cfn.delete_stack = backoff_wrapper(cfn.delete_stack) cfn.delete_stack = backoff_wrapper(cfn.delete_stack)
if boto_supports_termination_protection(cfn):
cfn.update_termination_protection = backoff_wrapper(cfn.update_termination_protection)
stack_info = get_stack_facts(cfn, stack_params['StackName']) stack_info = get_stack_facts(cfn, stack_params['StackName'])
@ -530,6 +573,9 @@ def main():
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)
else: else:
if module.params.get('termination_protection') is not None:
update_termination_protection(module, cfn, stack_params['StackName'],
bool(module.params.get('termination_protection')))
result = update_stack(module, stack_params, cfn) result = update_stack(module, stack_params, cfn)
# format the stack output # format the stack output

Loading…
Cancel
Save