diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index 8672c706..c7ced018 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -10,6 +10,31 @@ from moto.s3 import s3_backend from moto.core import ACCOUNT_ID from .models import cloudformation_backends from .exceptions import ValidationError +from .utils import yaml_tag_constructor + + +def get_template_summary_response_from_template(template_body): + def get_resource_types(template_dict): + resources = {} + for key, value in template_dict.items(): + if key == "Resources": + resources = value + + resource_types = [] + for key, value in resources.items(): + resource_types.append(value["Type"]) + return resource_types + + yaml.add_multi_constructor("", yaml_tag_constructor) + + try: + template_dict = yaml.load(template_body, Loader=yaml.Loader) + except (yaml.parser.ParserError, yaml.scanner.ScannerError): + template_dict = json.loads(template_body) + + resources_types = get_resource_types(template_dict) + template_dict["resourceTypes"] = resources_types + return template_dict class CloudFormationResponse(BaseResponse): @@ -269,6 +294,20 @@ class CloudFormationResponse(BaseResponse): template = self.response_template(GET_TEMPLATE_RESPONSE_TEMPLATE) return template.render(stack=stack) + def get_template_summary(self): + stack_name = self._get_param("StackName") + template_url = self._get_param("TemplateURL") + stack_body = self._get_param("TemplateBody") + + if stack_name: + stack_body = self.cloudformation_backend.get_stack(stack_name).template + elif template_url: + stack_body = self._get_stack_from_s3_url(template_url) + + template_summary = get_template_summary_response_from_template(stack_body) + template = self.response_template(GET_TEMPLATE_SUMMARY_TEMPLATE) + return template.render(template_summary=template_summary) + def update_stack(self): stack_name = self._get_param("StackName") role_arn = self._get_param("RoleARN") @@ -743,7 +782,6 @@ DESCRIBE_STACKS_TEMPLATE = """ """ - DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE = """ @@ -758,7 +796,6 @@ DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE = """ """ - DESCRIBE_STACK_RESOURCES_RESPONSE = """ @@ -777,7 +814,6 @@ DESCRIBE_STACK_RESOURCES_RESPONSE = """ """ - DESCRIBE_STACK_EVENTS_RESPONSE = """ @@ -802,7 +838,6 @@ DESCRIBE_STACK_EVENTS_RESPONSE = """ @@ -823,7 +858,6 @@ LIST_CHANGE_SETS_RESPONSE = """ """ - LIST_STACKS_RESPONSE = """ @@ -840,7 +874,6 @@ LIST_STACKS_RESPONSE = """ """ - LIST_STACKS_RESOURCES_RESPONSE = """ @@ -860,7 +893,6 @@ LIST_STACKS_RESOURCES_RESPONSE = """ """ - GET_TEMPLATE_RESPONSE_TEMPLATE = """ {{ stack.template }} @@ -870,7 +902,6 @@ GET_TEMPLATE_RESPONSE_TEMPLATE = """ """ - DELETE_STACK_RESPONSE_TEMPLATE = """ 5ccc7dcd-744c-11e5-be70-example @@ -878,7 +909,6 @@ DELETE_STACK_RESPONSE_TEMPLATE = """ """ - LIST_EXPORTS_RESPONSE = """ @@ -1139,3 +1169,19 @@ LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = ( """ ) + +GET_TEMPLATE_SUMMARY_TEMPLATE = """ + + {{ template_summary.Description }} + {% for resource in template_summary.resourceTypes %} + + {{ resource }} + + {% endfor %} + {{ template_summary.AWSTemplateFormatVersion }} + + + b9b4b068-3a41-11e5-94eb-example + + +""" diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py index 1ebce46d..0bfaf9f0 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py @@ -35,6 +35,14 @@ dummy_template = { }, } +dummy_template3 = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Stack 3", + "Resources": { + "VPC": {"Properties": {"CidrBlock": "192.168.0.0/16"}, "Type": "AWS::EC2::VPC"} + }, +} + dummy_template_yaml = """--- AWSTemplateFormatVersion: 2010-09-09 Description: Stack1 with yaml template @@ -668,6 +676,48 @@ def test_boto3_create_stack_with_short_form_func_yaml(): ) +@mock_s3 +@mock_cloudformation +def test_get_template_summary(): + s3 = boto3.client("s3") + s3_conn = boto3.resource("s3", region_name="us-east-1") + + conn = boto3.client("cloudformation", region_name="us-east-1") + result = conn.get_template_summary(TemplateBody=json.dumps(dummy_template3)) + + result["ResourceTypes"].should.equal(["AWS::EC2::VPC"]) + result["Version"].should.equal("2010-09-09") + result["Description"].should.equal("Stack 3") + + conn.create_stack(StackName="test_stack", TemplateBody=json.dumps(dummy_template3)) + + result = conn.get_template_summary(StackName="test_stack") + + result["ResourceTypes"].should.equal(["AWS::EC2::VPC"]) + result["Version"].should.equal("2010-09-09") + result["Description"].should.equal("Stack 3") + + s3_conn.create_bucket(Bucket="foobar") + s3_conn.Object("foobar", "template-key").put(Body=json.dumps(dummy_template3)) + + key_url = s3.generate_presigned_url( + ClientMethod="get_object", Params={"Bucket": "foobar", "Key": "template-key"} + ) + + conn.create_stack(StackName="stack_from_url", TemplateURL=key_url) + result = conn.get_template_summary(TemplateURL=key_url) + result["ResourceTypes"].should.equal(["AWS::EC2::VPC"]) + result["Version"].should.equal("2010-09-09") + result["Description"].should.equal("Stack 3") + + conn = boto3.client("cloudformation", region_name="us-east-1") + result = conn.get_template_summary(TemplateBody=dummy_template_yaml) + + result["ResourceTypes"].should.equal(["AWS::EC2::Instance"]) + result["Version"].should.equal("2010-09-09") + result["Description"].should.equal("Stack1 with yaml template") + + @mock_cloudformation def test_boto3_create_stack_with_ref_yaml(): cf_conn = boto3.client("cloudformation", region_name="us-east-1")