diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 9819b9cf1..00af8d279 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -834,7 +834,12 @@ class ResourceMap(collections_abc.Mapping): # type: ignore[type-arg] raise last_exception def delete(self) -> None: - remaining_resources = set(self.resources) + # Only try to delete resources without a Retain DeletionPolicy + remaining_resources = set( + key + for key, value in self._resource_json_map.items() + if not value.get("DeletionPolicy") == "Retain" + ) tries = 1 while remaining_resources and tries < 5: for resource in remaining_resources.copy(): diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index b0f06e019..2cb9063dd 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -1236,6 +1236,36 @@ def test_delete_stack_containing_cloudwatch_logs_resource_policy(): policies.should.have.length_of(0) +@mock_cloudformation +@mock_sqs +def test_delete_stack_with_deletion_policy_boto3(): + sqs_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "QueueGroup": { + "DeletionPolicy": "Retain", + "Type": "AWS::SQS::Queue", + "Properties": {"QueueName": "my-queue", "VisibilityTimeout": 60}, + } + }, + } + + sqs_template_json = json.dumps(sqs_template) + + cf = boto3.client("cloudformation", region_name="us-west-1") + cf.create_stack( + StackName="test_stack", + TemplateBody=sqs_template_json, + ) + sqs = boto3.client("sqs", region_name="us-west-1") + sqs.list_queues()["QueueUrls"].should.have.length_of(1) + + cf.delete_stack( + StackName="test_stack", + ) + sqs.list_queues()["QueueUrls"].should.have.length_of(1) + + @mock_cloudformation @mock_events def test_stack_events_create_rule_integration():