Repository: getmoto/moto

Issue:
CloudFormation allows for creating stacks with duplicate names
Using the CloudFormation moto service allows for creating stacks with the same name - the docs state that names must be unique in a region:

> The name must be unique in the region in which you are creating the stack.
https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudformation.html#CloudFormation.Client.create_stack

Version: moto-1.3.7

I would expect that the API return an `AlreadyExistsException` when trying to do this for real.



Discussion:

Resolving patch:
diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py
--- a/moto/cloudformation/responses.py
+++ b/moto/cloudformation/responses.py
@@ -50,6 +50,12 @@ def create_stack(self):
             for item in self._get_list_prefix("Tags.member")
         )
 
+        if self.stack_name_exists(new_stack_name=stack_name):
+            template = self.response_template(
+                CREATE_STACK_NAME_EXISTS_RESPONSE_TEMPLATE
+            )
+            return 400, {"status": 400}, template.render(name=stack_name)
+
         # Hack dict-comprehension
         parameters = dict(
             [
@@ -82,6 +88,12 @@ def create_stack(self):
             template = self.response_template(CREATE_STACK_RESPONSE_TEMPLATE)
             return template.render(stack=stack)
 
+    def stack_name_exists(self, new_stack_name):
+        for stack in self.cloudformation_backend.stacks.values():
+            if stack.name == new_stack_name:
+                return True
+        return False
+
     @amzn_request_id
     def create_change_set(self):
         stack_name = self._get_param("StackName")
@@ -564,6 +576,15 @@ def update_stack_instances(self):
 </CreateStackResponse>
 """
 
+CREATE_STACK_NAME_EXISTS_RESPONSE_TEMPLATE = """<ErrorResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
+  <Error>
+    <Type>Sender</Type>
+    <Code>AlreadyExistsException</Code>
+    <Message>Stack [{{ name }}] already exists</Message>
+  </Error>
+  <RequestId>950ff8d7-812a-44b3-bb0c-9b271b954104</RequestId>
+</ErrorResponse>"""
+
 UPDATE_STACK_RESPONSE_TEMPLATE = """<UpdateStackResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
   <UpdateStackResult>
     <StackId>{{ stack.stack_id }}</StackId>


Test patch:
diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py
--- a/tests/test_cloudformation/test_cloudformation_stack_crud.py
+++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py
@@ -98,12 +98,12 @@ def test_create_stack_hosted_zone_by_id():
         },
     }
     conn.create_stack(
-        "test_stack", template_body=json.dumps(dummy_template), parameters={}.items()
+        "test_stack1", template_body=json.dumps(dummy_template), parameters={}.items()
     )
     r53_conn = boto.connect_route53()
     zone_id = r53_conn.get_zones()[0].id
     conn.create_stack(
-        "test_stack",
+        "test_stack2",
         template_body=json.dumps(dummy_template2),
         parameters={"ZoneId": zone_id}.items(),
     )
diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
--- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
+++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
@@ -919,7 +919,9 @@ def test_execute_change_set_w_name():
 def test_describe_stack_pagination():
     conn = boto3.client("cloudformation", region_name="us-east-1")
     for i in range(100):
-        conn.create_stack(StackName="test_stack", TemplateBody=dummy_template_json)
+        conn.create_stack(
+            StackName="test_stack_{}".format(i), TemplateBody=dummy_template_json
+        )
 
     resp = conn.describe_stacks()
     stacks = resp["Stacks"]
@@ -1211,7 +1213,8 @@ def test_list_exports_with_token():
         # Add index to ensure name is unique
         dummy_output_template["Outputs"]["StackVPC"]["Export"]["Name"] += str(i)
         cf.create_stack(
-            StackName="test_stack", TemplateBody=json.dumps(dummy_output_template)
+            StackName="test_stack_{}".format(i),
+            TemplateBody=json.dumps(dummy_output_template),
         )
     exports = cf.list_exports()
     exports["Exports"].should.have.length_of(100)
@@ -1273,3 +1276,16 @@ def test_non_json_redrive_policy():
 
     stack.Resource("MainQueue").resource_status.should.equal("CREATE_COMPLETE")
     stack.Resource("DeadLetterQueue").resource_status.should.equal("CREATE_COMPLETE")
+
+
+@mock_cloudformation
+def test_boto3_create_duplicate_stack():
+    cf_conn = boto3.client("cloudformation", region_name="us-east-1")
+    cf_conn.create_stack(
+        StackName="test_stack", TemplateBody=dummy_template_json,
+    )
+
+    with assert_raises(ClientError):
+        cf_conn.create_stack(
+            StackName="test_stack", TemplateBody=dummy_template_json,
+        )