diff --git a/.gitignore b/.gitignore
index 6816a9431..24a7e7863 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,9 +2,11 @@ moto.egg-info/*
dist/*
.tox
.coverage
+cover/
*.pyc
*~
.noseids
build/
.idea/
*.swp
+.DS_Store
diff --git a/Makefile b/Makefile
index 2b83f8948..a7f08b146 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,8 @@ init:
test:
rm -f .coverage
- @nosetests -sv --with-coverage ./tests/
+ rm -rf cover
+ @nosetests -sv --with-coverage --cover-html ./tests/
publish:
python setup.py sdist bdist_wheel upload
diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py
index 126cf7991..8f25d406e 100644
--- a/moto/cloudformation/parsing.py
+++ b/moto/cloudformation/parsing.py
@@ -379,7 +379,8 @@ class ResourceMap(collections.Mapping):
def delete(self):
for resource in self.resources:
parsed_resource = self._parsed_resources.pop(resource)
- parsed_resource.delete(self._region_name)
+ if parsed_resource and hasattr(parsed_resource, 'delete'):
+ parsed_resource.delete(self._region_name)
class OutputMap(collections.Mapping):
diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py
index 64f4293d3..5df2751d6 100644
--- a/moto/cloudformation/responses.py
+++ b/moto/cloudformation/responses.py
@@ -230,21 +230,23 @@ DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE = """
"""
-DESCRIBE_STACK_RESOURCES_RESPONSE = """
-
- {% for resource in stack.stack_resources %}
-
- {{ stack.stack_id }}
- {{ stack.name }}
- {{ resource.logical_resource_id }}
- {{ resource.physical_resource_id }}
- {{ resource.type }}
- 2010-07-27T22:27:28Z
- {{ stack.status }}
-
- {% endfor %}
-
-"""
+DESCRIBE_STACK_RESOURCES_RESPONSE = """
+
+
+ {% for resource in stack.stack_resources %}
+
+ {{ stack.stack_id }}
+ {{ stack.name }}
+ {{ resource.logical_resource_id }}
+ {{ resource.physical_resource_id }}
+ {{ resource.type }}
+ 2010-07-27T22:27:28Z
+ {{ stack.status }}
+
+ {% endfor %}
+
+
+"""
LIST_STACKS_RESPONSE = """
diff --git a/moto/s3bucket_path/utils.py b/moto/s3bucket_path/utils.py
index 36aac78eb..aa7dc12f0 100644
--- a/moto/s3bucket_path/utils.py
+++ b/moto/s3bucket_path/utils.py
@@ -16,7 +16,9 @@ def parse_key_name(path):
def is_delete_keys(request, path, bucket_name):
- return path == u'/' + bucket_name + u'/?delete' or (
- path == u'/' + bucket_name and
- getattr(request, "query_string", "") == "delete"
+ return (
+ path == u'/' + bucket_name + u'/?delete' or
+ path == u'/' + bucket_name + u'?delete' or
+ (path == u'/' + bucket_name and
+ getattr(request, "query_string", "") == "delete")
)
diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
index 94d645a43..46f3fb2be 100644
--- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
+++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
@@ -16,11 +16,31 @@ from nose.tools import assert_raises
dummy_template = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Stack 1",
- "Resources": {},
+ "Resources": {
+ "EC2Instance1": {
+ "Type": "AWS::EC2::Instance",
+ "Properties": {
+ "ImageId": "ami-d3adb33f",
+ "KeyName": "dummy",
+ "InstanceType": "t2.micro",
+ "Tags": [
+ {
+ "Key": "Description",
+ "Value": "Test tag"
+ },
+ {
+ "Key": "Name",
+ "Value": "Name tag for tests"
+ }
+ ]
+ }
+ }
+ }
}
dummy_template_json = json.dumps(dummy_template)
+
@mock_cloudformation
def test_boto3_create_stack():
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
@@ -29,7 +49,8 @@ def test_boto3_create_stack():
TemplateBody=dummy_template_json,
)
- cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(dummy_template)
+ cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal(
+ dummy_template)
@mock_cloudformation
@@ -59,7 +80,8 @@ def test_create_stack_with_notification_arn():
)
stack = list(cf.stacks.all())[0]
- stack.notification_arns.should.contain('arn:aws:sns:us-east-1:123456789012:fake-queue')
+ stack.notification_arns.should.contain(
+ 'arn:aws:sns:us-east-1:123456789012:fake-queue')
@mock_cloudformation
@@ -78,9 +100,27 @@ def test_create_stack_from_s3_url():
TemplateURL=key_url,
)
- cf_conn.get_template(StackName="stack_from_url")['TemplateBody'].should.equal(dummy_template)
+ cf_conn.get_template(StackName="stack_from_url")[
+ 'TemplateBody'].should.equal(dummy_template)
+@mock_cloudformation
+def test_describe_stack_resources():
+ cf_conn = boto3.client('cloudformation', region_name='us-east-1')
+ cf_conn.create_stack(
+ StackName="test_stack",
+ TemplateBody=dummy_template_json,
+ )
+
+ stack = cf_conn.describe_stacks(StackName="test_stack")['Stacks'][0]
+
+ response = cf_conn.describe_stack_resources(StackName=stack['StackName'])
+ resource = response['StackResources'][0]
+ resource['LogicalResourceId'].should.equal('EC2Instance1')
+ resource['ResourceStatus'].should.equal('CREATE_COMPLETE')
+ resource['ResourceType'].should.equal('AWS::EC2::Instance')
+ resource['StackId'].should.equal(stack['StackId'])
+
@mock_cloudformation
def test_describe_stack_by_name():
cf_conn = boto3.client('cloudformation', region_name='us-east-1')
@@ -102,7 +142,8 @@ def test_describe_stack_by_stack_id():
)
stack = cf_conn.describe_stacks(StackName="test_stack")['Stacks'][0]
- stack_by_id = cf_conn.describe_stacks(StackName=stack['StackId'])['Stacks'][0]
+ stack_by_id = cf_conn.describe_stacks(StackName=stack['StackId'])['Stacks'][
+ 0]
stack_by_id['StackId'].should.equal(stack['StackId'])
stack_by_id['StackName'].should.equal("test_stack")
@@ -198,9 +239,9 @@ def test_cloudformation_params():
StackName='test_stack',
TemplateBody=dummy_template_with_params_json,
Parameters=[{
- "ParameterKey": "APPNAME",
- "ParameterValue": "testing123",
- }],
+ "ParameterKey": "APPNAME",
+ "ParameterValue": "testing123",
+ }],
)
stack.parameters.should.have.length_of(1)
@@ -227,6 +268,8 @@ def test_stack_tags():
TemplateBody=dummy_template_json,
Tags=tags,
)
- observed_tag_items = set(item for items in [tag.items() for tag in stack.tags] for item in items)
- expected_tag_items = set(item for items in [tag.items() for tag in tags] for item in items)
+ observed_tag_items = set(
+ item for items in [tag.items() for tag in stack.tags] for item in items)
+ expected_tag_items = set(
+ item for items in [tag.items() for tag in tags] for item in items)
observed_tag_items.should.equal(expected_tag_items)