moto/moto/cloudformation/responses.py
2023-11-30 14:55:51 -01:00

1308 lines
52 KiB
Python

import json
import re
from typing import Any, Dict, List, Optional, Tuple, Union
import yaml
from yaml.parser import ParserError # pylint:disable=c-extension-no-member
from yaml.scanner import ScannerError # pylint:disable=c-extension-no-member
from moto.core.responses import BaseResponse
from moto.s3.exceptions import S3ClientError
from moto.utilities.aws_headers import amzn_request_id
from .exceptions import MissingParameterError, ValidationError
from .models import CloudFormationBackend, FakeStack, cloudformation_backends
from .utils import get_stack_from_s3_url, yaml_tag_constructor
def get_template_summary_response_from_template(template_body: str) -> Dict[str, Any]:
def get_resource_types(template_dict: Dict[str, Any]) -> List[Any]:
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 (ParserError, 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):
def __init__(self) -> None:
super().__init__(service_name="cloudformation")
@property
def cloudformation_backend(self) -> CloudFormationBackend:
return cloudformation_backends[self.current_account][self.region]
@classmethod
def cfnresponse(cls, *args: Any, **kwargs: Any) -> Any: # type: ignore[misc] # pylint: disable=unused-argument
request, full_url, headers = args
full_url += "&Action=ProcessCfnResponse"
return cls.dispatch(request=request, full_url=full_url, headers=headers)
def _get_stack_from_s3_url(self, template_url: str) -> str:
return get_stack_from_s3_url(template_url, account_id=self.current_account)
def _get_params_from_list(
self, parameters_list: List[Dict[str, Any]]
) -> Dict[str, Any]:
# Hack dict-comprehension
return dict(
[
(parameter["parameter_key"], parameter["parameter_value"])
for parameter in parameters_list
]
)
def _get_param_values(
self, parameters_list: List[Dict[str, str]], existing_params: Dict[str, str]
) -> Dict[str, Any]:
result = {}
for parameter in parameters_list:
if parameter.keys() >= {"parameter_key", "parameter_value"}:
result[parameter["parameter_key"]] = parameter["parameter_value"]
elif (
parameter.keys() >= {"parameter_key", "use_previous_value"}
and parameter["parameter_key"] in existing_params
):
result[parameter["parameter_key"]] = existing_params[
parameter["parameter_key"]
]
else:
raise MissingParameterError(parameter["parameter_key"])
return result
def process_cfn_response(self) -> Tuple[int, Dict[str, int], str]:
status = self._get_param("Status")
if status == "SUCCESS":
stack_id = self._get_param("StackId")
logical_resource_id = self._get_param("LogicalResourceId")
outputs = self._get_param("Data")
stack = self.cloudformation_backend.get_stack(stack_id)
custom_resource = stack.get_custom_resource(logical_resource_id)
custom_resource.set_data(outputs)
stack.verify_readiness()
return 200, {"status": 200}, json.dumps("{}")
def create_stack(self) -> Union[str, Tuple[int, Dict[str, int], str]]:
stack_name = self._get_param("StackName")
stack_body = self._get_param("TemplateBody")
template_url = self._get_param("TemplateURL")
role_arn = self._get_param("RoleARN")
enable_termination_protection = self._get_param("EnableTerminationProtection")
timeout_in_mins = self._get_param("TimeoutInMinutes")
stack_policy_body = self._get_param("StackPolicyBody")
parameters_list = self._get_list_prefix("Parameters.member")
tags = dict(
(item["key"], item["value"])
for item in self._get_list_prefix("Tags.member")
)
if self.stack_name_exists(new_stack_name=stack_name):
template = self.response_template(
CREATE_STACK_NAME_EXISTS_RESPONSE_TEMPLATE
)
return 400, {"status": 400}, template.render(name=stack_name)
parameters = self._get_params_from_list(parameters_list)
if template_url:
stack_body = self._get_stack_from_s3_url(template_url)
stack_notification_arns = self._get_multi_param("NotificationARNs.member")
stack = self.cloudformation_backend.create_stack(
name=stack_name,
template=stack_body,
parameters=parameters,
notification_arns=stack_notification_arns,
tags=tags,
role_arn=role_arn,
enable_termination_protection=enable_termination_protection,
timeout_in_mins=timeout_in_mins,
stack_policy_body=stack_policy_body,
)
if self.request_json:
return json.dumps(
{
"CreateStackResponse": {
"CreateStackResult": {"StackId": stack.stack_id}
}
}
)
else:
template = self.response_template(CREATE_STACK_RESPONSE_TEMPLATE)
return template.render(stack=stack)
def stack_name_exists(self, new_stack_name: str) -> bool:
for stack in self.cloudformation_backend.stacks.values():
if stack.name == new_stack_name:
return True
return False
@amzn_request_id
def create_change_set(self) -> str:
stack_name = self._get_param("StackName")
change_set_name = self._get_param("ChangeSetName")
stack_body = self._get_param("TemplateBody")
template_url = self._get_param("TemplateURL")
description = self._get_param("Description")
role_arn = self._get_param("RoleARN")
update_or_create = self._get_param("ChangeSetType", "CREATE")
parameters_list = self._get_list_prefix("Parameters.member")
tags = dict(
(item["key"], item["value"])
for item in self._get_list_prefix("Tags.member")
)
parameters = {
param["parameter_key"]: param["parameter_value"]
for param in parameters_list
}
if template_url:
stack_body = self._get_stack_from_s3_url(template_url)
stack_notification_arns = self._get_multi_param("NotificationARNs.member")
change_set_id, stack_id = self.cloudformation_backend.create_change_set(
stack_name=stack_name,
change_set_name=change_set_name,
template=stack_body,
parameters=parameters,
description=description,
notification_arns=stack_notification_arns,
tags=tags,
role_arn=role_arn,
change_set_type=update_or_create,
)
if self.request_json:
return json.dumps(
{
"CreateChangeSetResponse": {
"CreateChangeSetResult": {
"Id": change_set_id,
"StackId": stack_id,
}
}
}
)
else:
template = self.response_template(CREATE_CHANGE_SET_RESPONSE_TEMPLATE)
return template.render(stack_id=stack_id, change_set_id=change_set_id)
def delete_change_set(self) -> str:
change_set_name = self._get_param("ChangeSetName")
self.cloudformation_backend.delete_change_set(change_set_name=change_set_name)
if self.request_json:
return json.dumps(
{"DeleteChangeSetResponse": {"DeleteChangeSetResult": {}}}
)
else:
template = self.response_template(DELETE_CHANGE_SET_RESPONSE_TEMPLATE)
return template.render()
def describe_change_set(self) -> str:
change_set_name = self._get_param("ChangeSetName")
change_set = self.cloudformation_backend.describe_change_set(
change_set_name=change_set_name
)
template = self.response_template(DESCRIBE_CHANGE_SET_RESPONSE_TEMPLATE)
return template.render(change_set=change_set)
@amzn_request_id
def execute_change_set(self) -> str:
stack_name = self._get_param("StackName")
change_set_name = self._get_param("ChangeSetName")
self.cloudformation_backend.execute_change_set(
stack_name=stack_name, change_set_name=change_set_name
)
if self.request_json:
return json.dumps(
{"ExecuteChangeSetResponse": {"ExecuteChangeSetResult": {}}}
)
else:
template = self.response_template(EXECUTE_CHANGE_SET_RESPONSE_TEMPLATE)
return template.render()
def describe_stacks(self) -> str:
stack_name_or_id = self._get_param("StackName")
token = self._get_param("NextToken")
stacks = self.cloudformation_backend.describe_stacks(stack_name_or_id)
stack_ids = [stack.stack_id for stack in stacks]
if token:
start = stack_ids.index(token) + 1
else:
start = 0
max_results = 50 # using this to mske testing of paginated stacks more convenient than default 1 MB
stacks_resp = stacks[start : start + max_results]
next_token = None
if len(stacks) > (start + max_results):
next_token = stacks_resp[-1].stack_id
template = self.response_template(DESCRIBE_STACKS_TEMPLATE)
return template.render(stacks=stacks_resp, next_token=next_token)
def describe_stack_resource(self) -> str:
stack_name = self._get_param("StackName")
logical_resource_id = self._get_param("LogicalResourceId")
stack, resource = self.cloudformation_backend.describe_stack_resource(
stack_name, logical_resource_id
)
template = self.response_template(DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE)
return template.render(stack=stack, resource=resource)
def describe_stack_resources(self) -> str:
stack_name = self._get_param("StackName")
stack, resources = self.cloudformation_backend.describe_stack_resources(
stack_name
)
template = self.response_template(DESCRIBE_STACK_RESOURCES_RESPONSE)
return template.render(stack=stack, resources=resources)
def describe_stack_events(self) -> str:
stack_name = self._get_param("StackName")
events = self.cloudformation_backend.describe_stack_events(stack_name)
template = self.response_template(DESCRIBE_STACK_EVENTS_RESPONSE)
return template.render(events=events)
def list_change_sets(self) -> str:
change_sets = self.cloudformation_backend.list_change_sets()
template = self.response_template(LIST_CHANGE_SETS_RESPONSE)
return template.render(change_sets=change_sets)
def list_stacks(self) -> str:
status_filter = self._get_multi_param("StackStatusFilter.member")
stacks = self.cloudformation_backend.list_stacks(status_filter)
template = self.response_template(LIST_STACKS_RESPONSE)
return template.render(stacks=stacks)
def list_stack_resources(self) -> str:
stack_name_or_id = self._get_param("StackName")
resources = self.cloudformation_backend.list_stack_resources(stack_name_or_id)
template = self.response_template(LIST_STACKS_RESOURCES_RESPONSE)
return template.render(resources=resources)
def get_template(self) -> str:
name_or_stack_id = self.querystring.get("StackName")[0] # type: ignore[index]
stack_template = self.cloudformation_backend.get_template(name_or_stack_id)
if self.request_json:
return json.dumps(
{
"GetTemplateResponse": {
"GetTemplateResult": {
"TemplateBody": stack_template,
"ResponseMetadata": {
"RequestId": "2d06e36c-ac1d-11e0-a958-f9382b6eb86bEXAMPLE"
},
}
}
}
)
else:
template = self.response_template(GET_TEMPLATE_RESPONSE_TEMPLATE)
return template.render(stack_template=stack_template)
def get_template_summary(self) -> str:
stack_name = self._get_param("StackName")
template_url = self._get_param("TemplateURL")
stack_body = self._get_param("TemplateBody")
if stack_name:
stack = self.cloudformation_backend.get_stack(stack_name)
if stack.status == "REVIEW_IN_PROGRESS":
raise ValidationError(
message="GetTemplateSummary cannot be called on REVIEW_IN_PROGRESS stacks."
)
stack_body = stack.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 _validate_different_update(
self,
incoming_params: Optional[List[Dict[str, Any]]],
stack_body: str,
old_stack: FakeStack,
) -> None:
if incoming_params and stack_body:
new_params = self._get_param_values(incoming_params, old_stack.parameters)
if old_stack.template == stack_body and old_stack.parameters == new_params:
raise ValidationError(
old_stack.name, message="No updates are to be performed."
)
def _validate_status(self, stack: FakeStack) -> None:
if stack.status == "ROLLBACK_COMPLETE":
raise ValidationError(
stack.stack_id,
message=f"Stack:{stack.stack_id} is in ROLLBACK_COMPLETE state and can not be updated.",
)
def update_stack(self) -> str:
stack_name = self._get_param("StackName")
role_arn = self._get_param("RoleARN")
template_url = self._get_param("TemplateURL")
stack_body = self._get_param("TemplateBody")
stack = self.cloudformation_backend.get_stack(stack_name)
if self._get_param("UsePreviousTemplate") == "true":
stack_body = stack.template
elif not stack_body and template_url:
stack_body = self._get_stack_from_s3_url(template_url)
incoming_params = self._get_list_prefix("Parameters.member")
# boto3 is supposed to let you clear the tags by passing an empty value, but the request body doesn't
# end up containing anything we can use to differentiate between passing an empty value versus not
# passing anything. so until that changes, moto won't be able to clear tags, only update them.
tags: Optional[Dict[str, str]] = dict(
(item["key"], item["value"])
for item in self._get_list_prefix("Tags.member")
)
# so that if we don't pass the parameter, we don't clear all the tags accidentally
if not tags:
tags = None
stack = self.cloudformation_backend.get_stack(stack_name)
self._validate_different_update(incoming_params, stack_body, stack)
self._validate_status(stack)
stack = self.cloudformation_backend.update_stack(
name=stack_name,
template=stack_body,
role_arn=role_arn,
parameters=incoming_params,
tags=tags,
)
if self.request_json:
stack_body = {
"UpdateStackResponse": {"UpdateStackResult": {"StackId": stack.name}}
}
return json.dumps(stack_body)
else:
template = self.response_template(UPDATE_STACK_RESPONSE_TEMPLATE)
return template.render(stack=stack)
def delete_stack(self) -> str:
name_or_stack_id = self.querystring.get("StackName")[0] # type: ignore[index]
self.cloudformation_backend.delete_stack(name_or_stack_id)
if self.request_json:
return json.dumps({"DeleteStackResponse": {"DeleteStackResult": {}}})
else:
template = self.response_template(DELETE_STACK_RESPONSE_TEMPLATE)
return template.render()
def list_exports(self) -> str:
token = self._get_param("NextToken")
exports, next_token = self.cloudformation_backend.list_exports(tokenstr=token)
template = self.response_template(LIST_EXPORTS_RESPONSE)
return template.render(exports=exports, next_token=next_token)
def validate_template(self) -> str:
template_body = self._get_param("TemplateBody")
template_url = self._get_param("TemplateURL")
if template_url:
template_body = self._get_stack_from_s3_url(template_url)
cfn_lint = self.cloudformation_backend.validate_template(template_body)
if cfn_lint:
raise ValidationError(cfn_lint[0].message)
description = ""
try:
description = json.loads(template_body)["Description"]
except (ValueError, KeyError):
pass
try:
description = yaml.load(template_body, Loader=yaml.Loader)["Description"]
except (ParserError, ScannerError, KeyError):
pass
template = self.response_template(VALIDATE_STACK_RESPONSE_TEMPLATE)
return template.render(description=description)
def create_stack_set(self) -> str:
stackset_name = self._get_param("StackSetName")
if not re.match(r"^[a-zA-Z][-a-zA-Z0-9]*$", stackset_name):
raise ValidationError(
message=f"1 validation error detected: Value '{stackset_name}' at 'stackSetName' failed to satisfy constraint: Member must satisfy regular expression pattern: [a-zA-Z][-a-zA-Z0-9]*"
)
stack_body = self._get_param("TemplateBody")
template_url = self._get_param("TemplateURL")
permission_model = self._get_param("PermissionModel")
parameters_list = self._get_list_prefix("Parameters.member")
admin_role = self._get_param("AdministrationRoleARN")
exec_role = self._get_param("ExecutionRoleName")
description = self._get_param("Description")
tags = dict(
(item["key"], item["value"])
for item in self._get_list_prefix("Tags.member")
)
# Copy-Pasta - Hack dict-comprehension
parameters = dict(
[
(parameter["parameter_key"], parameter["parameter_value"])
for parameter in parameters_list
]
)
if template_url:
stack_body = self._get_stack_from_s3_url(template_url)
stackset = self.cloudformation_backend.create_stack_set(
name=stackset_name,
template=stack_body,
parameters=parameters,
tags=tags,
permission_model=permission_model,
admin_role=admin_role,
exec_role=exec_role,
description=description,
)
if self.request_json:
return json.dumps(
{
"CreateStackSetResponse": {
"CreateStackSetResult": {"StackSetId": stackset.id}
}
}
)
else:
template = self.response_template(CREATE_STACK_SET_RESPONSE_TEMPLATE)
return template.render(stackset=stackset)
def create_stack_instances(self) -> str:
stackset_name = self._get_param("StackSetName")
accounts = self._get_multi_param("Accounts.member")
regions = self._get_multi_param("Regions.member")
parameters = self._get_multi_param("ParameterOverrides.member")
deployment_targets = self._get_params().get("DeploymentTargets")
if deployment_targets and "OrganizationalUnitIds" in deployment_targets:
for ou_id in deployment_targets.get("OrganizationalUnitIds", []):
if not re.match(
r"^(ou-[a-z0-9]{4,32}-[a-z0-9]{8,32}|r-[a-z0-9]{4,32})$", ou_id
):
raise ValidationError(
message=f"1 validation error detected: Value '[{ou_id}]' at 'deploymentTargets.organizationalUnitIds' failed to satisfy constraint: Member must satisfy constraint: [Member must have length less than or equal to 68, Member must have length greater than or equal to 6, Member must satisfy regular expression pattern: ^(ou-[a-z0-9]{{4,32}}-[a-z0-9]{{8,32}}|r-[a-z0-9]{{4,32}})$]"
)
operation_id = self.cloudformation_backend.create_stack_instances(
stackset_name,
accounts,
regions,
parameters,
deployment_targets=deployment_targets,
)
template = self.response_template(CREATE_STACK_INSTANCES_TEMPLATE)
return template.render(operation_id=operation_id)
def delete_stack_set(self) -> str:
stackset_name = self._get_param("StackSetName")
self.cloudformation_backend.delete_stack_set(stackset_name)
template = self.response_template(DELETE_STACK_SET_RESPONSE_TEMPLATE)
return template.render()
def delete_stack_instances(self) -> str:
stackset_name = self._get_param("StackSetName")
accounts = self._get_multi_param("Accounts.member")
regions = self._get_multi_param("Regions.member")
operation = self.cloudformation_backend.delete_stack_instances(
stackset_name, accounts, regions
)
template = self.response_template(DELETE_STACK_INSTANCES_TEMPLATE)
return template.render(operation=operation)
def describe_stack_set(self) -> str:
stackset_name = self._get_param("StackSetName")
stackset = self.cloudformation_backend.describe_stack_set(stackset_name)
if not stackset.admin_role:
stackset.admin_role = f"arn:aws:iam::{self.current_account}:role/AWSCloudFormationStackSetAdministrationRole"
if not stackset.execution_role:
stackset.execution_role = "AWSCloudFormationStackSetExecutionRole"
template = self.response_template(DESCRIBE_STACK_SET_RESPONSE_TEMPLATE)
return template.render(stackset=stackset)
def describe_stack_instance(self) -> str:
stackset_name = self._get_param("StackSetName")
account = self._get_param("StackInstanceAccount")
region = self._get_param("StackInstanceRegion")
instance = self.cloudformation_backend.describe_stack_instance(
stackset_name, account, region
)
template = self.response_template(DESCRIBE_STACK_INSTANCE_TEMPLATE)
rendered = template.render(instance=instance)
return rendered
def list_stack_sets(self) -> str:
stacksets = self.cloudformation_backend.list_stack_sets()
template = self.response_template(LIST_STACK_SETS_TEMPLATE)
return template.render(stacksets=stacksets)
def list_stack_instances(self) -> str:
stackset_name = self._get_param("StackSetName")
instances = self.cloudformation_backend.list_stack_instances(stackset_name)
template = self.response_template(LIST_STACK_INSTANCES_TEMPLATE)
return template.render(instances=instances)
def list_stack_set_operations(self) -> str:
stackset_name = self._get_param("StackSetName")
operations = self.cloudformation_backend.list_stack_set_operations(
stackset_name
)
template = self.response_template(LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE)
return template.render(operations=operations)
def stop_stack_set_operation(self) -> str:
stackset_name = self._get_param("StackSetName")
operation_id = self._get_param("OperationId")
self.cloudformation_backend.stop_stack_set_operation(
stackset_name, operation_id
)
template = self.response_template(STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE)
return template.render()
def describe_stack_set_operation(self) -> str:
stackset_name = self._get_param("StackSetName")
operation_id = self._get_param("OperationId")
stackset, operation = self.cloudformation_backend.describe_stack_set_operation(
stackset_name, operation_id
)
template = self.response_template(DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE)
return template.render(stackset=stackset, operation=operation)
def list_stack_set_operation_results(self) -> str:
stackset_name = self._get_param("StackSetName")
operation_id = self._get_param("OperationId")
operation = self.cloudformation_backend.list_stack_set_operation_results(
stackset_name, operation_id
)
template = self.response_template(
LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE
)
return template.render(operation=operation)
def update_stack_set(self) -> str:
stackset_name = self._get_param("StackSetName")
operation_id = self._get_param("OperationId")
description = self._get_param("Description")
execution_role = self._get_param("ExecutionRoleName")
admin_role = self._get_param("AdministrationRoleARN")
accounts = self._get_multi_param("Accounts.member")
regions = self._get_multi_param("Regions.member")
template_body = self._get_param("TemplateBody")
template_url = self._get_param("TemplateURL")
if template_url:
template_body = self._get_stack_from_s3_url(template_url)
tags = dict(
(item["key"], item["value"])
for item in self._get_list_prefix("Tags.member")
)
parameters_list = self._get_list_prefix("Parameters.member")
operation = self.cloudformation_backend.update_stack_set(
stackset_name=stackset_name,
template=template_body,
description=description,
parameters=parameters_list,
tags=tags,
admin_role=admin_role,
execution_role=execution_role,
accounts=accounts,
regions=regions,
operation_id=operation_id,
)
template = self.response_template(UPDATE_STACK_SET_RESPONSE_TEMPLATE)
return template.render(operation=operation)
def update_stack_instances(self) -> str:
stackset_name = self._get_param("StackSetName")
accounts = self._get_multi_param("Accounts.member")
regions = self._get_multi_param("Regions.member")
parameters = self._get_multi_param("ParameterOverrides.member")
operation = self.cloudformation_backend.update_stack_instances(
stackset_name, accounts, regions, parameters
)
template = self.response_template(UPDATE_STACK_INSTANCES_RESPONSE_TEMPLATE)
return template.render(operation=operation)
def get_stack_policy(self) -> str:
stack_name = self._get_param("StackName")
policy = self.cloudformation_backend.get_stack_policy(stack_name)
template = self.response_template(GET_STACK_POLICY_RESPONSE)
return template.render(policy=policy)
def set_stack_policy(self) -> str:
stack_name = self._get_param("StackName")
policy_url = self._get_param("StackPolicyURL")
policy_body = self._get_param("StackPolicyBody")
if policy_body and policy_url:
raise ValidationError(
message="You cannot specify both StackPolicyURL and StackPolicyBody"
)
if policy_url:
try:
policy_body = self._get_stack_from_s3_url(policy_url)
except S3ClientError as s3_e:
raise ValidationError(
message=f"S3 error: Access Denied: {s3_e.error_type}"
)
self.cloudformation_backend.set_stack_policy(
stack_name, policy_body=policy_body
)
return SET_STACK_POLICY_RESPONSE
VALIDATE_STACK_RESPONSE_TEMPLATE = """<ValidateTemplateResponse>
<ValidateTemplateResult>
<Capabilities></Capabilities>
<CapabilitiesReason></CapabilitiesReason>
<DeclaredTransforms></DeclaredTransforms>
<Description>{{ description }}</Description>
<Parameters></Parameters>
</ValidateTemplateResult>
</ValidateTemplateResponse>"""
CREATE_STACK_RESPONSE_TEMPLATE = """<CreateStackResponse>
<CreateStackResult>
<StackId>{{ stack.stack_id }}</StackId>
</CreateStackResult>
<ResponseMetadata>
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>
</ResponseMetadata>
</CreateStackResponse>
"""
CREATE_STACK_NAME_EXISTS_RESPONSE_TEMPLATE = """<ErrorResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<Error>
<Type>Sender</Type>
<Code>AlreadyExistsException</Code>
<Message>Stack [{{ name }}] already exists</Message>
</Error>
<RequestId>950ff8d7-812a-44b3-bb0c-9b271b954104</RequestId>
</ErrorResponse>"""
UPDATE_STACK_RESPONSE_TEMPLATE = """<UpdateStackResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<UpdateStackResult>
<StackId>{{ stack.stack_id }}</StackId>
</UpdateStackResult>
<ResponseMetadata>
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>
</ResponseMetadata>
</UpdateStackResponse>
"""
CREATE_CHANGE_SET_RESPONSE_TEMPLATE = """<CreateStackResponse>
<CreateChangeSetResult>
<Id>{{change_set_id}}</Id>
<StackId>{{ stack_id }}</StackId>
</CreateChangeSetResult>
<ResponseMetadata>
<RequestId>{{ request_id }}</RequestId>
</ResponseMetadata>
</CreateStackResponse>
"""
DELETE_CHANGE_SET_RESPONSE_TEMPLATE = """<DeleteChangeSetResponse>
<DeleteChangeSetResult>
</DeleteChangeSetResult>
<ResponseMetadata>
<RequestId>3d3200a1-810e-3023-6cc3-example</RequestId>
</ResponseMetadata>
</DeleteChangeSetResponse>
"""
DESCRIBE_CHANGE_SET_RESPONSE_TEMPLATE = """<DescribeChangeSetResponse>
<DescribeChangeSetResult>
<ChangeSetId>{{ change_set.change_set_id }}</ChangeSetId>
<ChangeSetName>{{ change_set.change_set_name }}</ChangeSetName>
<StackId>{{ change_set.stack_id }}</StackId>
<StackName>{{ change_set.stack_name }}</StackName>
<Description>{{ change_set.description }}</Description>
<Parameters>
{% for param_name, param_value in change_set.parameters.items() %}
<member>
<ParameterKey>{{ param_name }}</ParameterKey>
<ParameterValue>{{ param_value }}</ParameterValue>
</member>
{% endfor %}
</Parameters>
<CreationTime>{{ change_set.creation_time_iso_8601 }}</CreationTime>
<ExecutionStatus>{{ change_set.execution_status }}</ExecutionStatus>
<Status>{{ change_set.status }}</Status>
<StatusReason>{{ change_set.status_reason }}</StatusReason>
{% if change_set.notification_arns %}
<NotificationARNs>
{% for notification_arn in change_set.notification_arns %}
<member>{{ notification_arn }}</member>
{% endfor %}
</NotificationARNs>
{% else %}
<NotificationARNs/>
{% endif %}
{% if change_set.role_arn %}
<RoleARN>{{ change_set.role_arn }}</RoleARN>
{% endif %}
{% if change_set.changes %}
<Changes>
{% for change in change_set.changes %}
<member>
<Type>Resource</Type>
<ResourceChange>
<Action>{{ change.action }}</Action>
<LogicalResourceId>{{ change.logical_resource_id }}</LogicalResourceId>
<ResourceType>{{ change.resource_type }}</ResourceType>
</ResourceChange>
</member>
{% endfor %}
</Changes>
{% endif %}
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</DescribeChangeSetResult>
</DescribeChangeSetResponse>"""
EXECUTE_CHANGE_SET_RESPONSE_TEMPLATE = """<ExecuteChangeSetResponse>
<ExecuteChangeSetResult>
<ExecuteChangeSetResult/>
</ExecuteChangeSetResult>
<ResponseMetadata>
<RequestId>{{ request_id }}</RequestId>
</ResponseMetadata>
</ExecuteChangeSetResponse>
"""
DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResponse>
<DescribeStacksResult>
<Stacks>
{% for stack in stacks %}
<member>
<StackName>{{ stack.name }}</StackName>
<StackId>{{ stack.stack_id }}</StackId>
{% if stack.change_set_id %}
<ChangeSetId>{{ stack.change_set_id }}</stack.timeout_in_minsChangeSetId>
{% endif %}
<Description><![CDATA[{{ stack.description }}]]></Description>
<CreationTime>{{ stack.creation_time_iso_8601 }}</CreationTime>
<StackStatus>{{ stack.status }}</StackStatus>
{% if stack.notification_arns %}
<NotificationARNs>
{% for notification_arn in stack.notification_arns %}
<member>{{ notification_arn }}</member>
{% endfor %}
</NotificationARNs>
{% else %}
<NotificationARNs/>
{% endif %}
<DisableRollback>false</DisableRollback>
<Outputs>
{% for output in stack.stack_outputs %}
<member>
<OutputKey>{{ output.key }}</OutputKey>
<OutputValue>{{ output.value }}</OutputValue>
</member>
{% endfor %}
</Outputs>
<Parameters>
{% for param_name, param_value in stack.stack_parameters.items() %}
<member>
<ParameterKey>{{ param_name }}</ParameterKey>
{% if param_name in stack.resource_map.no_echo_parameter_keys %}
<ParameterValue>****</ParameterValue>
{% else %}
<ParameterValue>{{ param_value }}</ParameterValue>
{% endif %}
</member>
{% endfor %}
</Parameters>
{% if stack.role_arn %}
<RoleARN>{{ stack.role_arn }}</RoleARN>
{% endif %}
<Tags>
{% for tag_key, tag_value in stack.tags.items() %}
<member>
<Key>{{ tag_key }}</Key>
<Value>{{ tag_value }}</Value>
</member>
{% endfor %}
</Tags>
<EnableTerminationProtection>{{ stack.enable_termination_protection }}</EnableTerminationProtection>
{% if stack.timeout_in_mins %}
<TimeoutInMinutes>{{ stack.timeout_in_mins }}</TimeoutInMinutes>
{% endif %}
</member>
{% endfor %}
</Stacks>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</DescribeStacksResult>
</DescribeStacksResponse>"""
DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE = """<DescribeStackResourceResponse>
<DescribeStackResourceResult>
<StackResourceDetail>
<StackId>{{ stack.stack_id }}</StackId>
<StackName>{{ stack.name }}</StackName>
<LogicalResourceId>{{ resource.logical_resource_id }}</LogicalResourceId>
<PhysicalResourceId>{{ resource.physical_resource_id }}</PhysicalResourceId>
<ResourceType>{{ resource.type }}</ResourceType>
<Timestamp>2010-07-27T22:27:28Z</Timestamp>
<ResourceStatus>{{ stack.status }}</ResourceStatus>
</StackResourceDetail>
</DescribeStackResourceResult>
</DescribeStackResourceResponse>"""
DESCRIBE_STACK_RESOURCES_RESPONSE = """<DescribeStackResourcesResponse>
<DescribeStackResourcesResult>
<StackResources>
{% for resource in resources %}
<member>
<StackId>{{ stack.stack_id }}</StackId>
<StackName>{{ stack.name }}</StackName>
<LogicalResourceId>{{ resource.logical_resource_id }}</LogicalResourceId>
<PhysicalResourceId>{{ resource.physical_resource_id }}</PhysicalResourceId>
<ResourceType>{{ resource.type }}</ResourceType>
<Timestamp>2010-07-27T22:27:28Z</Timestamp>
<ResourceStatus>{{ stack.status }}</ResourceStatus>
</member>
{% endfor %}
</StackResources>
</DescribeStackResourcesResult>
</DescribeStackResourcesResponse>"""
DESCRIBE_STACK_EVENTS_RESPONSE = """<DescribeStackEventsResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<DescribeStackEventsResult>
<StackEvents>
{% for event in events[::-1] %}
<member>
<Timestamp>{{ event.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ') }}</Timestamp>
<ResourceStatus>{{ event.resource_status }}</ResourceStatus>
<StackId>{{ event.stack_id }}</StackId>
<EventId>{{ event.event_id }}</EventId>
<LogicalResourceId>{{ event.logical_resource_id }}</LogicalResourceId>
{% if event.resource_status_reason %}<ResourceStatusReason>{{ event.resource_status_reason }}</ResourceStatusReason>{% endif %}
<StackName>{{ event.stack_name }}</StackName>
<PhysicalResourceId>{{ event.physical_resource_id }}</PhysicalResourceId>
{% if event.resource_properties %}<ResourceProperties>{{ event.resource_properties }}</ResourceProperties>{% endif %}
<ResourceType>{{ event.resource_type }}</ResourceType>
</member>
{% endfor %}
</StackEvents>
</DescribeStackEventsResult>
<ResponseMetadata>
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>
</ResponseMetadata>
</DescribeStackEventsResponse>"""
LIST_CHANGE_SETS_RESPONSE = """<ListChangeSetsResponse>
<ListChangeSetsResult>
<Summaries>
{% for change_set in change_sets %}
<member>
<StackId>{{ change_set.stack_id }}</StackId>
<StackName>{{ change_set.stack_name }}</StackName>
<ChangeSetId>{{ change_set.change_set_id }}</ChangeSetId>
<ChangeSetName>{{ change_set.change_set_name }}</ChangeSetName>
<ExecutionStatus>{{ change_set.execution_status }}</ExecutionStatus>
<Status>{{ change_set.status }}</Status>
<StatusReason>{{ change_set.status_reason }}</StatusReason>
<CreationTime>2011-05-23T15:47:44Z</CreationTime>
<Description>{{ change_set.description }}</Description>
</member>
{% endfor %}
</Summaries>
</ListChangeSetsResult>
</ListChangeSetsResponse>"""
LIST_STACKS_RESPONSE = """<ListStacksResponse>
<ListStacksResult>
<StackSummaries>
{% for stack in stacks %}
<member>
<StackId>{{ stack.stack_id }}</StackId>
<StackStatus>{{ stack.status }}</StackStatus>
<StackName>{{ stack.name }}</StackName>
<CreationTime>{{ stack.creation_time_iso_8601 }}</CreationTime>
<TemplateDescription>{{ stack.description }}</TemplateDescription>
</member>
{% endfor %}
</StackSummaries>
</ListStacksResult>
</ListStacksResponse>"""
LIST_STACKS_RESOURCES_RESPONSE = """<ListStackResourcesResponse>
<ListStackResourcesResult>
<StackResourceSummaries>
{% for resource in resources %}
<member>
<ResourceStatus>CREATE_COMPLETE</ResourceStatus>
<LogicalResourceId>{{ resource.logical_resource_id }}</LogicalResourceId>
<LastUpdatedTimestamp>2011-06-21T20:15:58Z</LastUpdatedTimestamp>
<PhysicalResourceId>{{ resource.physical_resource_id }}</PhysicalResourceId>
<ResourceType>{{ resource.type }}</ResourceType>
</member>
{% endfor %}
</StackResourceSummaries>
</ListStackResourcesResult>
<ResponseMetadata>
<RequestId>2d06e36c-ac1d-11e0-a958-f9382b6eb86b</RequestId>
</ResponseMetadata>
</ListStackResourcesResponse>"""
GET_TEMPLATE_RESPONSE_TEMPLATE = """<GetTemplateResponse>
<GetTemplateResult>
<TemplateBody>{{ stack_template }}</TemplateBody>
</GetTemplateResult>
<ResponseMetadata>
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>
</ResponseMetadata>
</GetTemplateResponse>"""
DELETE_STACK_RESPONSE_TEMPLATE = """<DeleteStackResponse>
<ResponseMetadata>
<RequestId>5ccc7dcd-744c-11e5-be70-example</RequestId>
</ResponseMetadata>
</DeleteStackResponse>
"""
LIST_EXPORTS_RESPONSE = """<ListExportsResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<ListExportsResult>
<Exports>
{% for export in exports %}
<member>
<ExportingStackId>{{ export.exporting_stack_id }}</ExportingStackId>
<Name>{{ export.name }}</Name>
<Value>{{ export.value }}</Value>
</member>
{% endfor %}
</Exports>
{% if next_token %}
<NextToken>{{ next_token }}</NextToken>
{% endif %}
</ListExportsResult>
<ResponseMetadata>
<RequestId>5ccc7dcd-744c-11e5-be70-example</RequestId>
</ResponseMetadata>
</ListExportsResponse>"""
CREATE_STACK_SET_RESPONSE_TEMPLATE = """<CreateStackSetResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<CreateStackSetResult>
<StackSetId>{{ stackset.id }}</StackSetId>
</CreateStackSetResult>
<ResponseMetadata>
<RequestId>f457258c-391d-41d1-861f-example</RequestId>
</ResponseMetadata>
</CreateStackSetResponse>
"""
DESCRIBE_STACK_SET_RESPONSE_TEMPLATE = """<DescribeStackSetResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<DescribeStackSetResult>
<StackSet>
<Capabilities/>
<StackSetARN>{{ stackset.arn }}</StackSetARN>
<ExecutionRoleName>{{ stackset.execution_role }}</ExecutionRoleName>
<AdministrationRoleARN>{{ stackset.admin_role }}</AdministrationRoleARN>
<StackSetId>{{ stackset.id }}</StackSetId>
<TemplateBody>{{ stackset.template }}</TemplateBody>
<StackSetName>{{ stackset.name }}</StackSetName>
<Parameters>
{% for param_name, param_value in stackset.parameters.items() %}
<member>
<ParameterKey>{{ param_name }}</ParameterKey>
<ParameterValue>{{ param_value }}</ParameterValue>
</member>
{% endfor %}
</Parameters>
<Tags>
{% for tag_key, tag_value in stackset.tags.items() %}
<member>
<Key>{{ tag_key }}</Key>
<Value>{{ tag_value }}</Value>
</member>
{% endfor %}
</Tags>
<Status>{{ stackset.status }}</Status>
<PermissionModel>{{ stackset.permission_model }}</PermissionModel>
{% if stackset.description %}
<Description>{{ stackset.description }}</Description>
{% endif %}
<ManagedExecution><Active>false</Active></ManagedExecution>
</StackSet>
</DescribeStackSetResult>
<ResponseMetadata>
<RequestId>d8b64e11-5332-46e1-9603-example</RequestId>
</ResponseMetadata>
</DescribeStackSetResponse>"""
DELETE_STACK_SET_RESPONSE_TEMPLATE = """<DeleteStackSetResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<DeleteStackSetResult/>
<ResponseMetadata>
<RequestId>c35ec2d0-d69f-4c4d-9bd7-example</RequestId>
</ResponseMetadata>
</DeleteStackSetResponse>"""
CREATE_STACK_INSTANCES_TEMPLATE = """<CreateStackInstancesResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<CreateStackInstancesResult>
<OperationId>{{ operation_id }}</OperationId>
</CreateStackInstancesResult>
<ResponseMetadata>
<RequestId>6b29f7e3-69be-4d32-b374-example</RequestId>
</ResponseMetadata>
</CreateStackInstancesResponse>
"""
LIST_STACK_INSTANCES_TEMPLATE = """<ListStackInstancesResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<ListStackInstancesResult>
<Summaries>
{% for instance in instances %}
<member>
<StackId>{{ instance["StackId"] }}</StackId>
<StackSetId>{{ instance["StackSetId"] }}</StackSetId>
<Region>{{ instance["Region"] }}</Region>
<Account>{{ instance["Account"] }}</Account>
<Status>{{ instance["Status"] }}</Status>
</member>
{% endfor %}
</Summaries>
</ListStackInstancesResult>
<ResponseMetadata>
<RequestId>83c27e73-b498-410f-993c-example</RequestId>
</ResponseMetadata>
</ListStackInstancesResponse>
"""
DELETE_STACK_INSTANCES_TEMPLATE = """<DeleteStackInstancesResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<DeleteStackInstancesResult>
<OperationId>{{ operation.OperationId }}</OperationId>
</DeleteStackInstancesResult>
<ResponseMetadata>
<RequestId>e5325090-66f6-4ecd-a531-example</RequestId>
</ResponseMetadata>
</DeleteStackInstancesResponse>
"""
DESCRIBE_STACK_INSTANCE_TEMPLATE = """<DescribeStackInstanceResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<DescribeStackInstanceResult>
<StackInstance>
<StackId>{{ instance["StackId"] }}</StackId>
<StackSetId>{{ instance["StackSetId"] }}</StackSetId>
{% if instance["ParameterOverrides"] %}
<ParameterOverrides>
{% for override in instance["ParameterOverrides"] %}
{% if override['ParameterKey'] or override['ParameterValue'] %}
<member>
<ParameterKey>{{ override["ParameterKey"] }}</ParameterKey>
<UsePreviousValue>false</UsePreviousValue>
<ParameterValue>{{ override["ParameterValue"] }}</ParameterValue>
</member>
{% endif %}
{% endfor %}
</ParameterOverrides>
{% else %}
<ParameterOverrides/>
{% endif %}
<Region>{{ instance["Region"] }}</Region>
<Account>{{ instance["Account"] }}</Account>
<Status>{{ instance["Status"] }}</Status>
<StackInstanceStatus>
<DetailedStatus>{{ instance["StackInstanceStatus"]["DetailedStatus"] }}</DetailedStatus>
</StackInstanceStatus>
</StackInstance>
</DescribeStackInstanceResult>
<ResponseMetadata>
<RequestId>c6c7be10-0343-4319-8a25-example</RequestId>
</ResponseMetadata>
</DescribeStackInstanceResponse>
"""
LIST_STACK_SETS_TEMPLATE = """<ListStackSetsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<ListStackSetsResult>
<Summaries>
{% for stackset in stacksets %}
<member>
<StackSetName>{{ stackset.name }}</StackSetName>
<StackSetId>{{ stackset.id }}</StackSetId>
<Status>{{ stackset.status }}</Status>
</member>
{% endfor %}
</Summaries>
</ListStackSetsResult>
<ResponseMetadata>
<RequestId>4dcacb73-841e-4ed8-b335-example</RequestId>
</ResponseMetadata>
</ListStackSetsResponse>
"""
UPDATE_STACK_INSTANCES_RESPONSE_TEMPLATE = """<UpdateStackInstancesResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<UpdateStackInstancesResult>
<OperationId>{{ operation }}</OperationId>
</UpdateStackInstancesResult>
<ResponseMetadata>
<RequestId>bdbf8e94-19b6-4ce4-af85-example</RequestId>
</ResponseMetadata>
</UpdateStackInstancesResponse>
"""
UPDATE_STACK_SET_RESPONSE_TEMPLATE = """<UpdateStackSetResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<UpdateStackSetResult>
<OperationId>{{ operation.OperationId }}</OperationId>
</UpdateStackSetResult>
<ResponseMetadata>
<RequestId>adac907b-17e3-43e6-a254-example</RequestId>
</ResponseMetadata>
</UpdateStackSetResponse>
"""
LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE = """<ListStackSetOperationsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<ListStackSetOperationsResult>
<Summaries>
{% for operation in operations %}
<member>
<CreationTimestamp>{{ operation.CreationTimestamp }}</CreationTimestamp>
<OperationId>{{ operation.OperationId }}</OperationId>
<Action>{{ operation.Action }}</Action>
<EndTimestamp>{{ operation.EndTimestamp }}</EndTimestamp>
<Status>{{ operation.Status }}</Status>
</member>
{% endfor %}
</Summaries>
</ListStackSetOperationsResult>
<ResponseMetadata>
<RequestId>65b9d9be-08bb-4a43-9a21-example</RequestId>
</ResponseMetadata>
</ListStackSetOperationsResponse>
"""
STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE = """<StopStackSetOperationResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<StopStackSetOperationResult/>
<ResponseMetadata>
<RequestId>2188554a-07c6-4396-b2c5-example</RequestId>
</ResponseMetadata>
</StopStackSetOperationResponse>
"""
DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = """<DescribeStackSetOperationResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<DescribeStackSetOperationResult>
<StackSetOperation>
<ExecutionRoleName>{{ stackset.execution_role }}</ExecutionRoleName>
<AdministrationRoleARN>{{ stackset.admin_role_arn }}</AdministrationRoleARN>
<StackSetId>{{ stackset.id }}</StackSetId>
<CreationTimestamp>{{ operation.CreationTimestamp }}</CreationTimestamp>
<OperationId>{{ operation.OperationId }}</OperationId>
<Action>{{ operation.Action }}</Action>
<OperationPreferences>
<RegionOrder/>
</OperationPreferences>
<EndTimestamp>{{ operation.EndTimestamp }}</EndTimestamp>
<Status>{{ operation.Status }}</Status>
</StackSetOperation>
</DescribeStackSetOperationResult>
<ResponseMetadata>
<RequestId>2edc27b6-9ce2-486a-a192-example</RequestId>
</ResponseMetadata>
</DescribeStackSetOperationResponse>
"""
LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = """<ListStackSetOperationResultsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<ListStackSetOperationResultsResult>
<Summaries>
{% for instance in operation.Instances %}
{% for account, region in instance.items() %}
<member>
<AccountGateResult>
<StatusReason>Function not found: arn:aws:lambda:us-west-2:{{ account }}:function:AWSCloudFormationStackSetAccountGate</StatusReason>
<Status>SKIPPED</Status>
</AccountGateResult>
<Region>{{ region }}</Region>
<Account>{{ account }}</Account>
<Status>{{ operation.Status }}</Status>
</member>
{% endfor %}
{% endfor %}
</Summaries>
</ListStackSetOperationResultsResult>
<ResponseMetadata>
<RequestId>ac05a9ce-5f98-4197-a29b-example</RequestId>
</ResponseMetadata>
</ListStackSetOperationResultsResponse>
"""
# 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>
<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>
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>
</ResponseMetadata>
</GetTemplateSummaryResponse>
"""
SET_STACK_POLICY_RESPONSE = """<SetStackPolicyResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<ResponseMetadata>
<RequestId>abe48993-e23f-4167-b703-5b0f1b6aa84f</RequestId>
</ResponseMetadata>
</SetStackPolicyResponse>"""
GET_STACK_POLICY_RESPONSE = """<GetStackPolicyResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<GetStackPolicyResult>
{% if policy %}
<StackPolicyBody>{{ policy }}</StackPolicyBody>
{% endif %}
</GetStackPolicyResult>
<ResponseMetadata>
<RequestId>e9e39eb6-1c05-4f0e-958a-b63f420e0a07</RequestId>
</ResponseMetadata>
</GetStackPolicyResponse>"""