diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index c3bf64620..46adf1540 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -7,6 +7,7 @@ from moto.ec2 import models as ec2_models from moto.elb import models as elb_models from moto.iam import models as iam_models from moto.sqs import models as sqs_models +from .utils import random_suffix MODEL_MAP = { "AWS::AutoScaling::AutoScalingGroup": autoscaling_models.FakeAutoScalingGroup, @@ -29,6 +30,20 @@ MODEL_MAP = { "AWS::SQS::Queue": sqs_models.Queue, } +# http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html +NAME_TYPE_MAP = { + "AWS::CloudWatch::Alarm": "Alarm", + "AWS::DynamoDB::Table": "TableName", + "AWS::ElastiCache::CacheCluster": "ClusterName", + "AWS::ElasticBeanstalk::Application": "ApplicationName", + "AWS::ElasticBeanstalk::Environment": "EnvironmentName", + "AWS::ElasticLoadBalancing::LoadBalancer": "LoadBalancerName", + "AWS::RDS::DBInstance": "DBInstanceIdentifier", + "AWS::S3::Bucket": "BucketName", + "AWS::SNS::Topic": "TopicName", + "AWS::SQS::Queue": "QueueName" +} + # Just ignore these models types for now NULL_MODELS = [ "AWS::CloudFormation::WaitCondition", @@ -73,6 +88,12 @@ def resource_class_from_type(resource_type): return MODEL_MAP.get(resource_type) +def resource_name_property_from_type(resource_type): + if resource_type not in NAME_TYPE_MAP: + return None + return NAME_TYPE_MAP.get(resource_type) + + def parse_resource(resource_name, resource_json, resources_map): resource_type = resource_json['Type'] resource_class = resource_class_from_type(resource_type) @@ -80,6 +101,15 @@ def parse_resource(resource_name, resource_json, resources_map): return None resource_json = clean_json(resource_json, resources_map) + resource_name_property = resource_name_property_from_type(resource_type) + if resource_name_property: + if not 'Properties' in resource_json: + resource_json['Properties'] = dict() + if not resource_name_property in resource_json['Properties']: + resource_json['Properties'][resource_name_property] = '{0}-{1}-{2}'.format( + resources_map.get('AWS::StackName'), + resource_name, + random_suffix()) resource = resource_class.create_from_cloudformation_json(resource_name, resource_json) resource.type = resource_type resource.logical_resource_id = resource_name diff --git a/moto/cloudformation/utils.py b/moto/cloudformation/utils.py index 09cffd6a0..1d629c76b 100644 --- a/moto/cloudformation/utils.py +++ b/moto/cloudformation/utils.py @@ -1,7 +1,15 @@ from __future__ import unicode_literals import uuid +import six +import random def generate_stack_id(stack_name): random_id = uuid.uuid4() return "arn:aws:cloudformation:us-east-1:123456789:stack/{0}/{1}".format(stack_name, random_id) + + +def random_suffix(): + size = 12 + chars = list(range(10)) + ['A-Z'] + return ''.join(six.text_type(random.choice(chars)) for x in range(size)) diff --git a/tests/test_cloudformation/test_stack_parsing.py b/tests/test_cloudformation/test_stack_parsing.py index 4b99039ae..8df64f5ef 100644 --- a/tests/test_cloudformation/test_stack_parsing.py +++ b/tests/test_cloudformation/test_stack_parsing.py @@ -25,7 +25,24 @@ dummy_template = { }, } +name_type_template = { + "AWSTemplateFormatVersion": "2010-09-09", + + "Description": "Create a multi-az, load balanced, Auto Scaled sample web site. The Auto Scaling trigger is based on the CPU utilization of the web servers. The AMI is chosen based on the region in which the stack is run. This example creates a web service running across all availability zones in a region. The instances are load balanced with a simple health check. The web site is available on port 80, however, the instances can be configured to listen on any port (8888 by default). **WARNING** This template creates one or more Amazon EC2 instances. You will be billed for the AWS resources used if you create a stack from this template.", + + "Resources": { + "WebServerGroup": { + + "Type": "AWS::SQS::Queue", + "Properties": { + "VisibilityTimeout": 60, + } + }, + }, +} + dummy_template_json = json.dumps(dummy_template) +name_type_template_json = json.dumps(name_type_template) def test_parse_stack_resources(): @@ -46,3 +63,15 @@ def test_parse_stack_resources(): def test_missing_resource_logs(logger): resource_class_from_type("foobar") logger.warning.assert_called_with('No Moto CloudFormation support for %s', 'foobar') + + +def test_parse_stack_with_name_type_resource(): + stack = FakeStack( + stack_id="test_id", + name="test_stack", + template=name_type_template_json) + + stack.resource_map.should.have.length_of(1) + list(stack.resource_map.keys())[0].should.equal('WebServerGroup') + queue = list(stack.resource_map.values())[0] + queue.should.be.a(Queue)