* * Implements Change Sets on updating a cloudformation stack when create_changeset=true (#23490) * * Silence test complaints ;) * * Added optional changeset_name parameter. * Check if changeset with the requested name already exist. * Documentation fix * * Added warning when cloudformation stack has pending changesets. * Fix documentation
This commit is contained in:
parent
7bed60ba5f
commit
dd07d11ae5
1 changed files with 64 additions and 0 deletions
|
@ -93,6 +93,23 @@ options:
|
||||||
present, the stack does exist, and neither 'template' nor 'template_url' are specified, the previous template will be reused.
|
present, the stack does exist, and neither 'template' nor 'template_url' are specified, the previous template will be reused.
|
||||||
required: false
|
required: false
|
||||||
version_added: "2.0"
|
version_added: "2.0"
|
||||||
|
create_changeset:
|
||||||
|
description:
|
||||||
|
- "If stack already exists create a changeset instead of directly applying changes.
|
||||||
|
See the AWS Change Sets docs U(http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html).
|
||||||
|
WARNING: if the stack does not exist, it will be created without changeset. If the state is absent, the stack will be deleted immediately with no
|
||||||
|
changeset."
|
||||||
|
required: false
|
||||||
|
default: false
|
||||||
|
version_added: "2.4"
|
||||||
|
changeset_name:
|
||||||
|
description:
|
||||||
|
- Name given to the changeset when creating a changeset, only used when create_changeset is true. By default a name prefixed with Ansible-STACKNAME
|
||||||
|
is generated based on input parameters.
|
||||||
|
See the AWS Change Sets docs U(http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-changesets.html)
|
||||||
|
required: false
|
||||||
|
default: null
|
||||||
|
version_added: "2.4"
|
||||||
template_format:
|
template_format:
|
||||||
description:
|
description:
|
||||||
- (deprecated) For local templates, allows specification of json or yaml format. Templates are now passed raw to CloudFormation regardless of format.
|
- (deprecated) For local templates, allows specification of json or yaml format. Templates are now passed raw to CloudFormation regardless of format.
|
||||||
|
@ -239,6 +256,7 @@ except ImportError:
|
||||||
# import a class, otherwise we'll use a fully qualified path
|
# import a class, otherwise we'll use a fully qualified path
|
||||||
from ansible.module_utils.ec2 import AWSRetry
|
from ansible.module_utils.ec2 import AWSRetry
|
||||||
from ansible.module_utils.basic import AnsibleModule
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.utils.hashing import secure_hash_s
|
||||||
import ansible.module_utils.ec2
|
import ansible.module_utils.ec2
|
||||||
|
|
||||||
def boto_exception(err):
|
def boto_exception(err):
|
||||||
|
@ -297,6 +315,45 @@ def create_stack(module, stack_params, cfn):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def list_changesets(cfn, stack_name):
|
||||||
|
res = cfn.list_change_sets(StackName=stack_name)
|
||||||
|
changesets = []
|
||||||
|
for cs in res['Summaries']:
|
||||||
|
changesets.append(cs['ChangeSetName'])
|
||||||
|
return changesets
|
||||||
|
|
||||||
|
def create_changeset(module, stack_params, cfn):
|
||||||
|
if 'TemplateBody' not in stack_params and 'TemplateURL' not in stack_params:
|
||||||
|
module.fail_json(msg="Either 'template' or 'template_url' is required.")
|
||||||
|
|
||||||
|
try:
|
||||||
|
if not 'ChangeSetName' in stack_params:
|
||||||
|
# Determine ChangeSetName using hash of parameters.
|
||||||
|
changeset_name = 'Ansible-' + stack_params['StackName'] + '-' + secure_hash_s(json.dumps(stack_params, sort_keys=True))
|
||||||
|
stack_params['ChangeSetName'] = changeset_name
|
||||||
|
# Determine if this changeset already exists
|
||||||
|
pending_changesets = list_changesets(cfn, stack_params['StackName'])
|
||||||
|
if changeset_name in pending_changesets:
|
||||||
|
warning = 'WARNING: '+str(len(pending_changesets))+' pending changeset(s) exist(s) for this stack!'
|
||||||
|
result = dict(changed=False, output='ChangeSet ' + changeset_name + ' already exists.', warnings=[warning])
|
||||||
|
else:
|
||||||
|
cs = cfn.create_change_set(**stack_params)
|
||||||
|
result = stack_operation(cfn, stack_params['StackName'], 'UPDATE')
|
||||||
|
result['warnings'] = [('Created changeset named ' + changeset_name + ' for stack ' + stack_params['StackName']),
|
||||||
|
('You can execute it using: aws cloudformation execute-change-set --change-set-name ' + cs['Id']),
|
||||||
|
('NOTE that dependencies on this stack might fail due to pending changes!')]
|
||||||
|
except Exception as err:
|
||||||
|
error_msg = boto_exception(err)
|
||||||
|
if 'No updates are to be performed.' in error_msg:
|
||||||
|
result = dict(changed=False, output='Stack is already up-to-date.')
|
||||||
|
else:
|
||||||
|
module.fail_json(msg=error_msg)
|
||||||
|
|
||||||
|
if not result:
|
||||||
|
module.fail_json(msg="empty result")
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def update_stack(module, stack_params, cfn):
|
def update_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:
|
||||||
stack_params['UsePreviousTemplate'] = True
|
stack_params['UsePreviousTemplate'] = True
|
||||||
|
@ -402,6 +459,8 @@ def main():
|
||||||
disable_rollback=dict(default=False, type='bool'),
|
disable_rollback=dict(default=False, type='bool'),
|
||||||
template_url=dict(default=None, required=False),
|
template_url=dict(default=None, required=False),
|
||||||
template_format=dict(default=None, choices=['json', 'yaml'], required=False),
|
template_format=dict(default=None, choices=['json', 'yaml'], required=False),
|
||||||
|
create_changeset=dict(default=False, type='bool'),
|
||||||
|
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')
|
||||||
)
|
)
|
||||||
|
@ -434,6 +493,9 @@ def main():
|
||||||
if module.params['stack_policy'] is not None:
|
if module.params['stack_policy'] is not None:
|
||||||
stack_params['StackPolicyBody'] = open(module.params['stack_policy'], 'r').read()
|
stack_params['StackPolicyBody'] = open(module.params['stack_policy'], 'r').read()
|
||||||
|
|
||||||
|
if module.params['changeset_name'] is not None:
|
||||||
|
stack_params['ChangeSetName'] = module.params['changeset_name']
|
||||||
|
|
||||||
template_parameters = module.params['template_parameters']
|
template_parameters = module.params['template_parameters']
|
||||||
stack_params['Parameters'] = [{'ParameterKey':k, 'ParameterValue':str(v)} for k, v in template_parameters.items()]
|
stack_params['Parameters'] = [{'ParameterKey':k, 'ParameterValue':str(v)} for k, v in template_parameters.items()]
|
||||||
|
|
||||||
|
@ -456,6 +518,8 @@ 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)
|
||||||
|
elif create_changeset:
|
||||||
|
result = create_changeset(module, stack_params, cfn)
|
||||||
else:
|
else:
|
||||||
result = update_stack(module, stack_params, cfn)
|
result = update_stack(module, stack_params, cfn)
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue