diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index c823bae2..f569f6a8 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -403,19 +403,18 @@ class ResourceMap(collections.Mapping): while remaining_resources and tries < 5: for resource in remaining_resources.copy(): parsed_resource = self._parsed_resources.get(resource) - if parsed_resource: - try: + try: + if parsed_resource and hasattr(parsed_resource, 'delete'): parsed_resource.delete(self._region_name) - except Exception as e: - # skip over dependency violations, and try again in a second pass - last_exception = e - else: - remaining_resources.remove(resource) + except Exception as e: + # skip over dependency violations, and try again in a second pass + last_exception = e + else: + remaining_resources.remove(resource) tries += 1 if tries == 5: raise last_exception - class OutputMap(collections.Mapping): def __init__(self, resources, template): self._template = template diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index e516d92d..bbf65fae 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -27,8 +27,23 @@ dummy_template2 = { "Resources": {}, } +# template with resource which has no delete attribute defined +dummy_template3 = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Stack 3", + "Resources": { + "VPC": { + "Properties": { + "CidrBlock": "192.168.0.0/16", + }, + "Type": "AWS::EC2::VPC" + } + }, +} + dummy_template_json = json.dumps(dummy_template) dummy_template_json2 = json.dumps(dummy_template2) +dummy_template_json3 = json.dumps(dummy_template3) @mock_cloudformation @@ -220,6 +235,19 @@ def test_delete_stack_by_id(): conn.describe_stacks(stack_id).should.have.length_of(1) +@mock_cloudformation +def test_delete_stack_with_resource_missing_delete_attr(): + conn = boto.connect_cloudformation() + conn.create_stack( + "test_stack", + template_body=dummy_template_json3, + ) + + conn.list_stacks().should.have.length_of(1) + conn.delete_stack("test_stack") + conn.list_stacks().should.have.length_of(0) + + @mock_cloudformation def test_bad_describe_stack(): conn = boto.connect_cloudformation()