From 46e9dd4aaf4add382cdfc2594a6da4b2f1521d24 Mon Sep 17 00:00:00 2001 From: Mandar Kulkarni Date: Fri, 1 Sep 2023 16:02:07 -0700 Subject: [PATCH] cloudformation: add support for DisableRollback to upadte stack (#1681) cloudformation: add support for DisableRollback to upadte stack SUMMARY Update stack operation supports DisableRollback. Fixes #1655 ISSUE TYPE Bugfix Pull Request COMPONENT NAME cloudformation ADDITIONAL INFORMATION https://botocore.amazonaws.com/v1/documentation/api/latest/reference/services/cloudformation/client/update_stack.html Reviewed-by: Alina Buzachis Reviewed-by: Mandar Kulkarni Reviewed-by: Helen Bailey --- ...support-for-disable_rollback-to-update.yml | 2 + plugins/modules/cloudformation.py | 4 +- .../targets/cloudformation/defaults/main.yml | 2 + .../targets/cloudformation/tasks/main.yml | 4 + .../tasks/test_disable_rollback.yml | 218 ++++++++++++++++++ 5 files changed, 229 insertions(+), 1 deletion(-) create mode 100644 changelogs/fragments/1681-cloudformation-add-support-for-disable_rollback-to-update.yml create mode 100644 tests/integration/targets/cloudformation/tasks/test_disable_rollback.yml diff --git a/changelogs/fragments/1681-cloudformation-add-support-for-disable_rollback-to-update.yml b/changelogs/fragments/1681-cloudformation-add-support-for-disable_rollback-to-update.yml new file mode 100644 index 00000000000..91098b6797c --- /dev/null +++ b/changelogs/fragments/1681-cloudformation-add-support-for-disable_rollback-to-update.yml @@ -0,0 +1,2 @@ +minor_changes: +- cloudformation - Add support for ``disable_rollback`` to update stack operation (https://github.com/ansible-collections/amazon.aws/issues/1681). diff --git a/plugins/modules/cloudformation.py b/plugins/modules/cloudformation.py index 6b17ef800ac..f242a6cc5c4 100644 --- a/plugins/modules/cloudformation.py +++ b/plugins/modules/cloudformation.py @@ -384,7 +384,7 @@ def create_stack(module, stack_params, cfn, events_limit): msg="Either 'template', 'template_body' or 'template_url' is required when the stack does not exist." ) - # 'DisableRollback', 'TimeoutInMinutes', 'EnableTerminationProtection' and + # 'TimeoutInMinutes', 'EnableTerminationProtection' and # 'OnFailure' only apply on creation, not update. if module.params.get("on_create_failure") is not None: stack_params["OnFailure"] = module.params["on_create_failure"] @@ -483,6 +483,8 @@ def update_stack(module, stack_params, cfn, events_limit): if module.params["stack_policy_on_update_body"] is not None: stack_params["StackPolicyDuringUpdateBody"] = module.params["stack_policy_on_update_body"] + stack_params["DisableRollback"] = module.params["disable_rollback"] + # 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. diff --git a/tests/integration/targets/cloudformation/defaults/main.yml b/tests/integration/targets/cloudformation/defaults/main.yml index 2f2a70c5580..a406244b35a 100644 --- a/tests/integration/targets/cloudformation/defaults/main.yml +++ b/tests/integration/targets/cloudformation/defaults/main.yml @@ -1,4 +1,6 @@ stack_name: "{{ resource_prefix }}" +stack_name_disable_rollback_true: "{{ resource_prefix }}-drb-true" +stack_name_disable_rollback_false: "{{ resource_prefix }}-drb-false" availability_zone: '{{ ec2_availability_zone_names[0] }}' diff --git a/tests/integration/targets/cloudformation/tasks/main.yml b/tests/integration/targets/cloudformation/tasks/main.yml index f7228f3a5be..3794c3e6383 100644 --- a/tests/integration/targets/cloudformation/tasks/main.yml +++ b/tests/integration/targets/cloudformation/tasks/main.yml @@ -25,6 +25,10 @@ az: "{{ availability_zone }}" register: testing_subnet + # ==== Cloudformation tests with disable_rollback ==================== + + - import_tasks: test_disable_rollback.yml + # ==== Cloudformation tests =============================================== # 1. Basic stack creation (check mode, actual run and idempotency) diff --git a/tests/integration/targets/cloudformation/tasks/test_disable_rollback.yml b/tests/integration/targets/cloudformation/tasks/test_disable_rollback.yml new file mode 100644 index 00000000000..c38dcfa8483 --- /dev/null +++ b/tests/integration/targets/cloudformation/tasks/test_disable_rollback.yml @@ -0,0 +1,218 @@ +--- +- name: Run cloudformation tests for `disable_rollback` parameter + block: + + # disable rollback to true + - name: create a cloudformation stack (disable_rollback=true) (check mode) + amazon.aws.cloudformation: + stack_name: "{{ stack_name_disable_rollback_true }}" + state: present + disable_rollback: true + template_body: "{{ lookup('file','cf_template.json') }}" + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + check_mode: yes + + - name: check task return attributes + assert: + that: + - cf_stack.changed + - "'msg' in cf_stack and 'New stack would be created' in cf_stack.msg" + + - name: create a cloudformation stack (disable_rollback=true) + amazon.aws.cloudformation: + stack_name: "{{ stack_name_disable_rollback_true }}" + state: present + disable_rollback: true + template_body: "{{ lookup('file','cf_template.json') }}" + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + + - name: get stack details + cloudformation_info: + stack_name: "{{ stack_name_disable_rollback_true }}" + register: stack_info + + - name: assert stack info + assert: + that: + - "'cloudformation' in stack_info" + - stack_info.cloudformation | length == 1 + - stack_info.cloudformation[stack_name_disable_rollback_true].stack_description.disable_rollback == true + + # disable rollback to false + - name: create a cloudformation stack (disable_rollback=false) (check mode) + amazon.aws.cloudformation: + stack_name: "{{ stack_name_disable_rollback_false }}" + state: present + disable_rollback: false + template_body: "{{ lookup('file','cf_template.json') }}" + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + check_mode: yes + + - name: check task return attributes + assert: + that: + - cf_stack.changed + - "'msg' in cf_stack and 'New stack would be created' in cf_stack.msg" + + - name: create a cloudformation stack (disable_rollback=false) + amazon.aws.cloudformation: + stack_name: "{{ stack_name_disable_rollback_false }}" + state: present + disable_rollback: false + template_body: "{{ lookup('file','cf_template.json') }}" + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + + - name: get stack details + cloudformation_info: + stack_name: "{{ stack_name_disable_rollback_false }}" + register: stack_info + + - name: assert stack info + assert: + that: + - "'cloudformation' in stack_info" + - "stack_info.cloudformation | length == 1" + - "stack_info.cloudformation[stack_name_disable_rollback_false].stack_description.disable_rollback == false" + + # disable rollback not set + - name: create a cloudformation stack (disable_rollback not set) (check mode) + amazon.aws.cloudformation: + stack_name: "{{ stack_name }}" + state: present + template_body: "{{ lookup('file','cf_template.json') }}" + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + check_mode: yes + + - name: check task return attributes + assert: + that: + - cf_stack.changed + - "'msg' in cf_stack and 'New stack would be created' in cf_stack.msg" + + - name: create a cloudformation stack (disable_rollback not set) + amazon.aws.cloudformation: + stack_name: "{{ stack_name }}" + state: present + template_body: "{{ lookup('file','cf_template.json') }}" + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + + - name: get stack details + cloudformation_info: + stack_name: "{{ stack_name }}" + register: stack_info + + - name: assert stack info + assert: + that: + - "'cloudformation' in stack_info" + - "stack_info.cloudformation | length == 1" + - "stack_info.cloudformation[stack_name].stack_description.disable_rollback == false" + + # ============================================================================================= + # Test Scenario + # 1. create a cloudformation stack + # 2. try update, FAILED by providing wrong ami id (disable_rollback=true, do not delete failed stack) + # 3. Fix the ami id, retry update, fails as disable_rollback=False + # 4. Try (3) with disable_rollback=true, update completes + # ============================================================================================= + + - name: Create a cloudformation stack + amazon.aws.cloudformation: + stack_name: "{{ stack_name }}-failtest" + state: present + template_body: "{{ lookup('file','cf_template.json') }}" + disable_rollback: false + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + ignore_errors: true + + - name: Update the cloudformation stack with wrong ami (fails, does not delete failed as disable_rollback=true) + amazon.aws.cloudformation: + stack_name: "{{ stack_name }}-failtest" + state: present + template_body: "{{ lookup('file','cf_template.json') }}" + disable_rollback: true + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}1" # wrong ami provided + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + ignore_errors: true + + # update stack by correcting AMI ID + - name: Fix the AMI ID and retry updating the cloudformation stack (fails with disable_rollback=false) + amazon.aws.cloudformation: + stack_name: "{{ stack_name }}-failtest" + state: present + template_body: "{{ lookup('file','cf_template.json') }}" + disable_rollback: false + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + ignore_errors: true + + - name: Fix the AMI ID and retry updating the cloudformation stack (passes with disable_rollback=true) + amazon.aws.cloudformation: + stack_name: "{{ stack_name }}-failtest" + state: present + template_body: "{{ lookup('file','cf_template.json') }}" + disable_rollback: true + template_parameters: + InstanceType: "t3.nano" + ImageId: "{{ ec2_ami_id }}" + SubnetId: "{{ testing_subnet.subnet.id }}" + register: cf_stack + + - name: get stack details + cloudformation_info: + stack_name: "{{ stack_name }}-failtest" + register: stack_info + + - name: Assert that update was successful + assert: + that: + - cf_stack.changed + - cf_stack.output == "Stack UPDATE complete" + - stack_info.cloudformation["{{ stack_name }}-failtest"].stack_description.stack_status == "UPDATE_COMPLETE" + + always: + + - name: delete stack + cloudformation: + stack_name: "{{ item }}" + state: absent + ignore_errors: true + with_items: + - "{{ stack_name_disable_rollback_true }}" + - "{{ stack_name_disable_rollback_false }}" + - "{{ stack_name }}-failtest" + - "{{ stack_name }}"