Repository: getmoto/moto

Issue:
aws cli throws `KeyError: 'Parameters'` when trying to redeploy existing Cloudformation stack
```sh
# deploy new stack
$ aws-moto cloudformation deploy --template-file template.yml --stack-name static-website

Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - static-website

# re-deploy stack
$ aws-moto cloudformation deploy --debug --template-file template.yml --stack-name static-website

Waiting for changeset to be created..

....
2021-10-23 11:45:00,212 - MainThread - awscli.clidriver - DEBUG - Exception caught in main()
Traceback (most recent call last):
  File "awscli/clidriver.py", line 459, in main
  File "awscli/clidriver.py", line 594, in __call__
  File "awscli/customizations/commands.py", line 191, in __call__
  File "awscli/customizations/cloudformation/deploy.py", line 352, in _run_main
  File "awscli/customizations/cloudformation/deploy.py", line 364, in deploy
  File "awscli/customizations/cloudformation/deployer.py", line 224, in create_and_wait_for_changeset
  File "awscli/customizations/cloudformation/deployer.py", line 106, in create_changeset
KeyError: 'Parameters' 
```

`template.yml`

```yml
AWSTemplateFormatVersion: 2010-09-09
Description: A simple CloudFormation template
Resources:
    Bucket:
        Type: AWS::S3::Bucket
        Properties:
            BucketName: MyBucket
```


Discussion:

Resolving patch:
diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py
--- a/moto/cloudformation/responses.py
+++ b/moto/cloudformation/responses.py
@@ -1186,14 +1186,38 @@ def update_stack_instances(self):
 """
 )
 
+# https://docs.aws.amazon.com/AWSCloudFormation/latest/APIReference/API_GetTemplateSummary.html
+# TODO:implement fields: ResourceIdentifierSummaries, Capabilities, CapabilitiesReason
 GET_TEMPLATE_SUMMARY_TEMPLATE = """<GetTemplateSummaryResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
   <GetTemplateSummaryResult>
     <Description>{{ template_summary.Description }}</Description>
     {% for resource in template_summary.resourceTypes %}
       <ResourceTypes>
-        <ResourceType>{{ resource }}</ResourceType>
+        <member>{{ resource }}</member>
       </ResourceTypes>
     {% endfor %}
+    <Parameters>
+        {% for k,p in template_summary.get('Parameters',{}).items() %}
+        <member>
+            <ParameterKey>{{ k }}</ParameterKey> ,
+            <Description>{{ p.get('Description', '') }}</Description>,
+            {% if p.Default %}
+            <DefaultValue>{{ p.Default }}</DefaultValue>
+            {% endif %}
+            <NoEcho>{{ p.get('NoEcho', False) }}</NoEcho>
+            <ParameterType>{{ p.get('Type', 'String') }}</ParameterType>
+            <ParameterConstraints>
+              {% if p.AllowedValues %}
+              <AllowedValues>
+                {% for v in p.AllowedValues %}
+                <member>{{ v }}</member>
+                {% endfor %}
+              </AllowedValues>
+              {% endif %}
+            </ParameterConstraints>
+        </member>
+        {% endfor %}
+    </Parameters>
     <Version>{{ template_summary.AWSTemplateFormatVersion }}</Version>
   </GetTemplateSummaryResult>
   <ResponseMetadata>


Test patch:
diff --git a/tests/helpers.py b/tests/helpers.py
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -1,6 +1,6 @@
 import boto
 from unittest import SkipTest
-from collections.abc import Iterable
+from collections.abc import Iterable, Mapping
 from sure import assertion
 
 
@@ -45,3 +45,16 @@ def containing_item_with_attributes(context, **kwargs):
     else:
         assert contains, f"{context.obj} does not contain matching item {kwargs}"
     return True
+
+
+@assertion
+def match_dict(context, dict_value):
+    assert isinstance(dict_value, Mapping), f"Invalid match target value: {dict_value}"
+    assert isinstance(
+        context.obj, Mapping
+    ), f"Expected dict like object, but got: {context.obj}"
+
+    for k, v in dict_value.items():
+        assert k in context.obj, f"No such key '{k}' in {context.obj}"
+        context.obj[k].should.equal(v)
+    return True
diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
--- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
+++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py
@@ -55,6 +55,28 @@
     },
 }
 
+
+dummy_template_with_parameters = {
+    "AWSTemplateFormatVersion": "2010-09-09",
+    "Description": "A simple CloudFormation template",
+    "Resources": {
+        "Bucket": {
+            "Type": "AWS::S3::Bucket",
+            "Properties": {"BucketName": {"Ref": "Name"}},
+        }
+    },
+    "Parameters": {
+        "Name": {"Type": "String", "Default": "SomeValue"},
+        "Another": {
+            "Type": "String",
+            "Default": "A",
+            "AllowedValues": ["A", "B"],
+            "Description": "Chose A or B",
+        },
+    },
+}
+
+
 dummy_template_yaml = """---
 AWSTemplateFormatVersion: 2010-09-09
 Description: Stack1 with yaml template
@@ -70,6 +92,7 @@
           Value: Test tag
         - Key: Name
           Value: Name tag for tests
+    Parameters:
 """
 
 dummy_template_yaml_with_short_form_func = """---
@@ -867,6 +890,7 @@ def test_get_template_summary():
     result["ResourceTypes"].should.equal(["AWS::EC2::VPC"])
     result["Version"].should.equal("2010-09-09")
     result["Description"].should.equal("Stack 3")
+    result["Parameters"].should.equal([])
 
     # existing stack
     conn.create_stack(StackName="test_stack", TemplateBody=json.dumps(dummy_template3))
@@ -874,6 +898,7 @@ def test_get_template_summary():
     result["ResourceTypes"].should.equal(["AWS::EC2::VPC"])
     result["Version"].should.equal("2010-09-09")
     result["Description"].should.equal("Stack 3")
+    result["Parameters"].should.equal([])
 
     # json template from s3
     s3_conn.create_bucket(Bucket="foobar")
@@ -896,7 +921,7 @@ def test_get_template_summary():
 
 
 @mock_cloudformation
-def test_get_template_summary_for_stack_createed_by_changeset_execution():
+def test_get_template_summary_for_stack_created_by_changeset_execution():
     conn = boto3.client("cloudformation", region_name="us-east-1")
     conn.create_change_set(
         StackName="stack_from_changeset",
@@ -916,6 +941,49 @@ def test_get_template_summary_for_stack_createed_by_changeset_execution():
     result["Description"].should.equal("Stack 3")
 
 
+@mock_s3
+@mock_cloudformation
+def test_get_template_summary_for_template_containing_parameters():
+    conn = boto3.client("cloudformation", region_name="us-east-1")
+    conn.create_stack(
+        StackName="test_stack", TemplateBody=json.dumps(dummy_template_with_parameters)
+    )
+    result = conn.get_template_summary(StackName="test_stack")
+    result.should.match_dict(
+        {
+            "Parameters": [
+                {
+                    "ParameterKey": "Name",
+                    "DefaultValue": "SomeValue",
+                    "ParameterType": "String",
+                    "NoEcho": False,
+                    "Description": "",
+                    "ParameterConstraints": {},
+                },
+                {
+                    "ParameterKey": "Another",
+                    "DefaultValue": "A",
+                    "ParameterType": "String",
+                    "NoEcho": False,
+                    "Description": "Chose A or B",
+                    "ParameterConstraints": {"AllowedValues": ["A", "B"]},
+                },
+            ],
+            "Description": "A simple CloudFormation template",
+            "ResourceTypes": ["AWS::S3::Bucket"],
+            "Version": "2010-09-09",
+            # TODO: get_template_summary should support ResourceIdentifierSummaries
+            # "ResourceIdentifierSummaries": [
+            #     {
+            #         "ResourceType": "AWS::S3::Bucket",
+            #         "LogicalResourceIds": ["Bucket"],
+            #         "ResourceIdentifiers": ["BucketName"],
+            #     }
+            # ],
+        }
+    )
+
+
 @mock_cloudformation
 def test_boto3_create_stack_with_ref_yaml():
     cf_conn = boto3.client("cloudformation", region_name="us-east-1")