From 90191675a29d9627e99b7cceb85d3b3b02b988e6 Mon Sep 17 00:00:00 2001 From: Joseph Lawson Date: Thu, 23 Oct 2014 14:39:15 -0400 Subject: [PATCH 1/6] Deleted Stack Fix and ValidationError for DescribeStacks. * stop backed from trying to iterate over empty list of deleted stacks. * Update DescribeStacks to throw ValidationError if stack name or id doesn't exist. --- moto/cloudformation/exceptions.py | 20 ++++++++++++++++++++ moto/cloudformation/models.py | 11 +++++++---- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/moto/cloudformation/exceptions.py b/moto/cloudformation/exceptions.py index a0a4eb63..c8bb5b39 100644 --- a/moto/cloudformation/exceptions.py +++ b/moto/cloudformation/exceptions.py @@ -1,6 +1,26 @@ from __future__ import unicode_literals +from boto.exception import BotoServerError +from jinja2 import Template + class UnformattedGetAttTemplateException(Exception): description = 'Template error: resource {0} does not support attribute type {1} in Fn::GetAtt' status_code = 400 + + +class ValidationError(BotoServerError): + def __init__(self, name_or_id): + template = Template(STACK_DOES_NOT_EXIST_RESPONSE) + super(ValidationError, self).__init__(status=400, reason='Bad Request', + body=template.render(name_or_id=name_or_id)) + +STACK_DOES_NOT_EXIST_RESPONSE = """ + + Sender + ValidationError + Stack:{{ name_or_id }} does not exist + + cf4c737e-5ae2-11e4-a7c9-ad44eEXAMPLE + +""" diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index f5418217..3df740e6 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -5,6 +5,7 @@ from moto.core import BaseBackend from .parsing import ResourceMap, OutputMap from .utils import generate_stack_id +from .exceptions import ValidationError class FakeStack(object): @@ -50,10 +51,12 @@ class CloudFormationBackend(BaseBackend): for stack in stacks: if stack.name == name_or_stack_id or stack.stack_id == name_or_stack_id: return [stack] - deleted_stacks = self.deleted_stacks.values() - for stack in deleted_stacks: - if stack.stack_id == name_or_stack_id: - return [stack] + if self.deleted_stacks: + deleted_stacks = self.deleted_stacks.values() + for stack in deleted_stacks: + if stack.stack_id == name_or_stack_id: + return [stack] + raise ValidationError(name_or_stack_id) else: return stacks From 04b5389e312c2cc2e2bec56c36f25d2814abd6ab Mon Sep 17 00:00:00 2001 From: Joseph Lawson Date: Thu, 23 Oct 2014 14:46:54 -0400 Subject: [PATCH 2/6] Add test for bad describe stack request --- .../test_cloudformation_stack_crud.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index f818e42e..27941e88 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -3,8 +3,10 @@ import json import boto import sure # noqa +from nose.tools import assert_raises from moto import mock_cloudformation +from moto.cloudformation.exceptions import ValidationError dummy_template = { "AWSTemplateFormatVersion": "2010-09-09", @@ -133,6 +135,17 @@ def test_delete_stack_by_id(): conn.list_stacks().should.have.length_of(0) +@mock_cloudformation +def test_bad_describe_stack(): + conn = boto.connect_cloudformation() + with assert_raises(ValidationError) as ve: + conn.describe_stacks("bad_stack") + + ve.exception.code.should.equal('ValidationError') + ve.exception.status.should.equal(400) + ve.exception.message.should.contain('bad_stack') + + # @mock_cloudformation # def test_update_stack(): # conn = boto.connect_cloudformation() From 0aec1d0f9271be2e9ce1ef6198337258aea7953c Mon Sep 17 00:00:00 2001 From: Joseph Lawson Date: Thu, 23 Oct 2014 14:55:40 -0400 Subject: [PATCH 3/6] create_stack returns stack_id --- moto/cloudformation/responses.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index 4267a6e4..1e4b7376 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -20,7 +20,7 @@ class CloudFormationResponse(BaseResponse): stack_body = { 'CreateStackResponse': { 'CreateStackResult': { - 'StackId': stack.name, + 'StackId': stack.stack_id, } } } From 08b1c3bc0cf7c6e08ba35ce990b7e4d5e3d73aac Mon Sep 17 00:00:00 2001 From: Joseph Lawson Date: Thu, 23 Oct 2014 14:57:46 -0400 Subject: [PATCH 4/6] test delete stack --- .../test_cloudformation/test_cloudformation_stack_crud.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index 27941e88..808e65aa 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -133,6 +133,13 @@ def test_delete_stack_by_id(): conn.list_stacks().should.have.length_of(1) conn.delete_stack(stack_id) conn.list_stacks().should.have.length_of(0) + with assert_raises(ValidationError) as ve: + conn.describe_stacks("test_stack") + + ve.exception.code.should.equal('ValidationError') + ve.exception.status.should.equal(400) + ve.exception.message.should.contain('test_stack') + conn.describe_stacks(stack_id).should.have.length_of(1) @mock_cloudformation From 3015fd0216408ba94a74e7d8195e4870fd66f189 Mon Sep 17 00:00:00 2001 From: Joseph Lawson Date: Thu, 23 Oct 2014 22:21:00 -0400 Subject: [PATCH 5/6] python 2.6 assert_raises --- tests/test_cloudformation/test_cloudformation_stack_crud.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index 808e65aa..8cdbf96e 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -1,8 +1,9 @@ from __future__ import unicode_literals import json - import boto import sure # noqa +# Ensure 'assert_raises' context manager support for Python 2.6 +import tests.backport_assert_raises from nose.tools import assert_raises from moto import mock_cloudformation From 04d22082c4ef936527059f0fc19b59aa46647ddf Mon Sep 17 00:00:00 2001 From: Joseph Lawson Date: Thu, 23 Oct 2014 22:32:20 -0400 Subject: [PATCH 6/6] remove some test details for 2.6 --- .../test_cloudformation_stack_crud.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index 8cdbf96e..477272a3 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -134,25 +134,18 @@ def test_delete_stack_by_id(): conn.list_stacks().should.have.length_of(1) conn.delete_stack(stack_id) conn.list_stacks().should.have.length_of(0) - with assert_raises(ValidationError) as ve: + with assert_raises(ValidationError): conn.describe_stacks("test_stack") - ve.exception.code.should.equal('ValidationError') - ve.exception.status.should.equal(400) - ve.exception.message.should.contain('test_stack') conn.describe_stacks(stack_id).should.have.length_of(1) @mock_cloudformation def test_bad_describe_stack(): conn = boto.connect_cloudformation() - with assert_raises(ValidationError) as ve: + with assert_raises(ValidationError): conn.describe_stacks("bad_stack") - ve.exception.code.should.equal('ValidationError') - ve.exception.status.should.equal(400) - ve.exception.message.should.contain('bad_stack') - # @mock_cloudformation # def test_update_stack():