Fix multiple bugs encountered with boto3

* Fix path detection for deleting keys in S3 bucket
* Fix stack deletion ensure delete method exists on object
  * Previous tests were using a stack with no resources
* Fix DESCRIBE_STACK_RESOURCES_RESPONSE,
  * Previously untested code path
This commit is contained in:
Declan Shanaghy 2016-03-31 13:33:13 -07:00
parent 1ece813131
commit b152c00642
6 changed files with 81 additions and 30 deletions

2
.gitignore vendored
View File

@ -2,9 +2,11 @@ moto.egg-info/*
dist/* dist/*
.tox .tox
.coverage .coverage
cover/
*.pyc *.pyc
*~ *~
.noseids .noseids
build/ build/
.idea/ .idea/
*.swp *.swp
.DS_Store

View File

@ -6,7 +6,8 @@ init:
test: test:
rm -f .coverage rm -f .coverage
@nosetests -sv --with-coverage ./tests/ rm -rf cover
@nosetests -sv --with-coverage --cover-html ./tests/
publish: publish:
python setup.py sdist bdist_wheel upload python setup.py sdist bdist_wheel upload

View File

@ -379,6 +379,7 @@ class ResourceMap(collections.Mapping):
def delete(self): def delete(self):
for resource in self.resources: for resource in self.resources:
parsed_resource = self._parsed_resources.pop(resource) parsed_resource = self._parsed_resources.pop(resource)
if parsed_resource and hasattr(parsed_resource, 'delete'):
parsed_resource.delete(self._region_name) parsed_resource.delete(self._region_name)

View File

@ -230,7 +230,8 @@ DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE = """<DescribeStackResourceResponse>
</DescribeStackResourceResponse>""" </DescribeStackResourceResponse>"""
DESCRIBE_STACK_RESOURCES_RESPONSE = """<DescribeStackResourcesResult> DESCRIBE_STACK_RESOURCES_RESPONSE = """<DescribeStackResourcesResponse>
<DescribeStackResourcesResult>
<StackResources> <StackResources>
{% for resource in stack.stack_resources %} {% for resource in stack.stack_resources %}
<member> <member>
@ -244,7 +245,8 @@ DESCRIBE_STACK_RESOURCES_RESPONSE = """<DescribeStackResourcesResult>
</member> </member>
{% endfor %} {% endfor %}
</StackResources> </StackResources>
</DescribeStackResourcesResult>""" </DescribeStackResourcesResult>
</DescribeStackResourcesResponse>"""
LIST_STACKS_RESPONSE = """<ListStacksResponse> LIST_STACKS_RESPONSE = """<ListStacksResponse>

View File

@ -16,7 +16,9 @@ def parse_key_name(path):
def is_delete_keys(request, path, bucket_name): def is_delete_keys(request, path, bucket_name):
return path == u'/' + bucket_name + u'/?delete' or ( return (
path == u'/' + bucket_name and path == u'/' + bucket_name + u'/?delete' or
getattr(request, "query_string", "") == "delete" path == u'/' + bucket_name + u'?delete' or
(path == u'/' + bucket_name and
getattr(request, "query_string", "") == "delete")
) )

View File

@ -16,11 +16,31 @@ from nose.tools import assert_raises
dummy_template = { dummy_template = {
"AWSTemplateFormatVersion": "2010-09-09", "AWSTemplateFormatVersion": "2010-09-09",
"Description": "Stack 1", "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) dummy_template_json = json.dumps(dummy_template)
@mock_cloudformation @mock_cloudformation
def test_boto3_create_stack(): def test_boto3_create_stack():
cf_conn = boto3.client('cloudformation', region_name='us-east-1') cf_conn = boto3.client('cloudformation', region_name='us-east-1')
@ -29,7 +49,8 @@ def test_boto3_create_stack():
TemplateBody=dummy_template_json, 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 @mock_cloudformation
@ -59,7 +80,8 @@ def test_create_stack_with_notification_arn():
) )
stack = list(cf.stacks.all())[0] 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 @mock_cloudformation
@ -78,9 +100,27 @@ def test_create_stack_from_s3_url():
TemplateURL=key_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 @mock_cloudformation
def test_describe_stack_by_name(): def test_describe_stack_by_name():
cf_conn = boto3.client('cloudformation', region_name='us-east-1') 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 = 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['StackId'].should.equal(stack['StackId'])
stack_by_id['StackName'].should.equal("test_stack") stack_by_id['StackName'].should.equal("test_stack")
@ -227,6 +268,8 @@ def test_stack_tags():
TemplateBody=dummy_template_json, TemplateBody=dummy_template_json,
Tags=tags, Tags=tags,
) )
observed_tag_items = set(item for items in [tag.items() for tag in stack.tags] for item in items) observed_tag_items = set(
expected_tag_items = set(item for items in [tag.items() for tag in tags] for item in items) 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) observed_tag_items.should.equal(expected_tag_items)