diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index 17b76854a..cceedc86e 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -50,6 +50,12 @@ class CloudFormationResponse(BaseResponse): 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 @@ class CloudFormationResponse(BaseResponse): 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 @@ CREATE_STACK_RESPONSE_TEMPLATE = """ """ +CREATE_STACK_NAME_EXISTS_RESPONSE_TEMPLATE = """ + + Sender + AlreadyExistsException + Stack [{{ name }}] already exists + + 950ff8d7-812a-44b3-bb0c-9b271b954104 +""" + UPDATE_STACK_RESPONSE_TEMPLATE = """ {{ stack.stack_id }} diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index 8a0a0b11c..8749d4cfb 100644 --- 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 index cd76743dd..43f63dca2 100644 --- 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, + )