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)