from __future__ import unicode_literals import json import yaml from urllib.parse import urlparse from moto.core.responses import BaseResponse from moto.core.utils import amzn_request_id 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): @property def cloudformation_backend(self): return cloudformation_backends[self.region] def _get_stack_from_s3_url(self, template_url): template_url_parts = urlparse(template_url) if "localhost" in template_url: bucket_name, key_name = template_url_parts.path.lstrip("/").split("/", 1) else: if template_url_parts.netloc.endswith( "amazonaws.com" ) and template_url_parts.netloc.startswith("s3"): # Handle when S3 url uses amazon url with bucket in path # Also handles getting region as technically s3 is region'd # region = template_url.netloc.split('.')[1] bucket_name, key_name = template_url_parts.path.lstrip("/").split( "/", 1 ) else: bucket_name = template_url_parts.netloc.split(".")[0] key_name = template_url_parts.path.lstrip("/") key = s3_backend.get_object(bucket_name, key_name) return key.value.decode("utf-8") def create_stack(self): stack_name = self._get_param("StackName") stack_body = self._get_param("TemplateBody") template_url = self._get_param("TemplateURL") role_arn = self._get_param("RoleARN") 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) # 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) stack_notification_arns = self._get_multi_param("NotificationARNs.member") stack = self.cloudformation_backend.create_stack( name=stack_name, template=stack_body, parameters=parameters, region_name=self.region, notification_arns=stack_notification_arns, tags=tags, role_arn=role_arn, ) 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): 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): 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, region_name=self.region, 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): stack_name = self._get_param("StackName") change_set_name = self._get_param("ChangeSetName") self.cloudformation_backend.delete_change_set( change_set_name=change_set_name, stack_name=stack_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): stack_name = self._get_param("StackName") change_set_name = self._get_param("ChangeSetName") change_set = self.cloudformation_backend.describe_change_set( change_set_name=change_set_name, stack_name=stack_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): 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): stack_name_or_id = None if self._get_param("StackName"): stack_name_or_id = self.querystring.get("StackName")[0] 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): stack_name = self._get_param("StackName") stack = self.cloudformation_backend.get_stack(stack_name) logical_resource_id = self._get_param("LogicalResourceId") resource = None for stack_resource in stack.stack_resources: if stack_resource.logical_resource_id == logical_resource_id: resource = stack_resource break if not resource: message = "Resource {0} does not exist for stack {1}".format( logical_resource_id, stack_name ) raise ValidationError(stack_name, message) template = self.response_template(DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE) return template.render(stack=stack, resource=resource) def describe_stack_resources(self): stack_name = self._get_param("StackName") stack = self.cloudformation_backend.get_stack(stack_name) template = self.response_template(DESCRIBE_STACK_RESOURCES_RESPONSE) return template.render(stack=stack) def describe_stack_events(self): stack_name = self._get_param("StackName") stack = self.cloudformation_backend.get_stack(stack_name) template = self.response_template(DESCRIBE_STACK_EVENTS_RESPONSE) return template.render(stack=stack) def list_change_sets(self): 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): 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): 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): name_or_stack_id = self.querystring.get("StackName")[0] stack = self.cloudformation_backend.get_stack(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=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") 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 = 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) if stack.status == "ROLLBACK_COMPLETE": raise ValidationError( stack.stack_id, message="Stack:{0} is in ROLLBACK_COMPLETE state and can not be updated.".format( stack.stack_id ), ) 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): name_or_stack_id = self.querystring.get("StackName")[0] 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): token = self._get_param("NextToken") exports, next_token = self.cloudformation_backend.list_exports(token=token) template = self.response_template(LIST_EXPORTS_RESPONSE) return template.render(exports=exports, next_token=next_token) def validate_template(self): 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 (yaml.parser.ParserError, yaml.scanner.ScannerError, KeyError): pass template = self.response_template(VALIDATE_STACK_RESPONSE_TEMPLATE) return template.render(description=description) def create_stack_set(self): stackset_name = self._get_param("StackSetName") stack_body = self._get_param("TemplateBody") template_url = self._get_param("TemplateURL") # role_arn = self._get_param('RoleARN') parameters_list = self._get_list_prefix("Parameters.member") 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, # role_arn=role_arn, ) 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): 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") self.cloudformation_backend.create_stack_instances( stackset_name, accounts, regions, parameters ) template = self.response_template(CREATE_STACK_INSTANCES_TEMPLATE) return template.render() def delete_stack_set(self): 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): 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): stackset_name = self._get_param("StackSetName") stackset = self.cloudformation_backend.get_stack_set(stackset_name) if not stackset.admin_role: stackset.admin_role = "arn:aws:iam::{AccountId}:role/AWSCloudFormationStackSetAdministrationRole".format( AccountId=ACCOUNT_ID ) 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): stackset_name = self._get_param("StackSetName") account = self._get_param("StackInstanceAccount") region = self._get_param("StackInstanceRegion") instance = self.cloudformation_backend.get_stack_set( stackset_name ).instances.get_instance(account, region) template = self.response_template(DESCRIBE_STACK_INSTANCE_TEMPLATE) rendered = template.render(instance=instance) return rendered def list_stack_sets(self): stacksets = self.cloudformation_backend.stacksets template = self.response_template(LIST_STACK_SETS_TEMPLATE) return template.render(stacksets=stacksets) def list_stack_instances(self): stackset_name = self._get_param("StackSetName") stackset = self.cloudformation_backend.get_stack_set(stackset_name) template = self.response_template(LIST_STACK_INSTANCES_TEMPLATE) return template.render(stackset=stackset) def list_stack_set_operations(self): stackset_name = self._get_param("StackSetName") stackset = self.cloudformation_backend.get_stack_set(stackset_name) template = self.response_template(LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE) return template.render(stackset=stackset) def stop_stack_set_operation(self): stackset_name = self._get_param("StackSetName") operation_id = self._get_param("OperationId") stackset = self.cloudformation_backend.get_stack_set(stackset_name) stackset.update_operation(operation_id, "STOPPED") template = self.response_template(STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE) return template.render() def describe_stack_set_operation(self): stackset_name = self._get_param("StackSetName") operation_id = self._get_param("OperationId") stackset = self.cloudformation_backend.get_stack_set(stackset_name) operation = stackset.get_operation(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): stackset_name = self._get_param("StackSetName") operation_id = self._get_param("OperationId") stackset = self.cloudformation_backend.get_stack_set(stackset_name) operation = stackset.get_operation(operation_id) template = self.response_template( LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE ) return template.render(operation=operation) def update_stack_set(self): 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): 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.get_stack_set( stackset_name ).update_instances(accounts, regions, parameters) template = self.response_template(UPDATE_STACK_INSTANCES_RESPONSE_TEMPLATE) return template.render(operation=operation) VALIDATE_STACK_RESPONSE_TEMPLATE = """ {{ description }} """ CREATE_STACK_RESPONSE_TEMPLATE = """ {{ stack.stack_id }} b9b4b068-3a41-11e5-94eb-example """ CREATE_STACK_NAME_EXISTS_RESPONSE_TEMPLATE = """ Sender AlreadyExistsException Stack [{{ name }}] already exists 950ff8d7-812a-44b3-bb0c-9b271b954104 """ UPDATE_STACK_RESPONSE_TEMPLATE = """ {{ stack.stack_id }} b9b4b068-3a41-11e5-94eb-example """ CREATE_CHANGE_SET_RESPONSE_TEMPLATE = """ {{change_set_id}} {{ stack_id }} {{ request_id }} """ DELETE_CHANGE_SET_RESPONSE_TEMPLATE = """ 3d3200a1-810e-3023-6cc3-example """ DESCRIBE_CHANGE_SET_RESPONSE_TEMPLATE = """ {{ change_set.change_set_id }} {{ change_set.change_set_name }} {{ change_set.stack_id }} {{ change_set.stack_name }} {{ change_set.description }} {% for param_name, param_value in change_set.parameters.items() %} {{ param_name }} {{ param_value }} {% endfor %} {{ change_set.creation_time_iso_8601 }} {{ change_set.execution_status }} {{ change_set.status }} {{ change_set.status_reason }} {% if change_set.notification_arns %} {% for notification_arn in change_set.notification_arns %} {{ notification_arn }} {% endfor %} {% else %} {% endif %} {% if change_set.role_arn %} {{ change_set.role_arn }} {% endif %} {% if change_set.changes %} {% for change in change_set.changes %} Resource {{ change.action }} {{ change.logical_resource_id }} {{ change.resource_type }} {% endfor %} {% endif %} {% if next_token %} {{ next_token }} {% endif %} """ EXECUTE_CHANGE_SET_RESPONSE_TEMPLATE = """ {{ request_id }} """ DESCRIBE_STACKS_TEMPLATE = """ {% for stack in stacks %} {{ stack.name }} {{ stack.stack_id }} {% if stack.change_set_id %} {{ stack.change_set_id }} {% endif %} {{ stack.creation_time_iso_8601 }} {{ stack.status }} {% if stack.notification_arns %} {% for notification_arn in stack.notification_arns %} {{ notification_arn }} {% endfor %} {% else %} {% endif %} false {% for output in stack.stack_outputs %} {{ output.key }} {{ output.value }} {% endfor %} {% for param_name, param_value in stack.stack_parameters.items() %} {{ param_name }} {% if param_name in stack.resource_map.no_echo_parameter_keys %} **** {% else %} {{ param_value }} {% endif %} {% endfor %} {% if stack.role_arn %} {{ stack.role_arn }} {% endif %} {% for tag_key, tag_value in stack.tags.items() %} {{ tag_key }} {{ tag_value }} {% endfor %} {% endfor %} {% if next_token %} {{ next_token }} {% endif %} """ DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE = """ {{ stack.stack_id }} {{ stack.name }} {{ resource.logical_resource_id }} {{ resource.physical_resource_id }} {{ resource.type }} 2010-07-27T22:27:28Z {{ stack.status }} """ 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_EVENTS_RESPONSE = """ {% for event in stack.events[::-1] %} {{ event.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ') }} {{ event.resource_status }} {{ event.stack_id }} {{ event.event_id }} {{ event.logical_resource_id }} {% if event.resource_status_reason %}{{ event.resource_status_reason }}{% endif %} {{ event.stack_name }} {{ event.physical_resource_id }} {% if event.resource_properties %}{{ event.resource_properties }}{% endif %} {{ event.resource_type }} {% endfor %} b9b4b068-3a41-11e5-94eb-example """ LIST_CHANGE_SETS_RESPONSE = """ {% for change_set in change_sets %} {{ change_set.stack_id }} {{ change_set.stack_name }} {{ change_set.change_set_id }} {{ change_set.change_set_name }} {{ change_set.execution_status }} {{ change_set.status }} {{ change_set.status_reason }} 2011-05-23T15:47:44Z {{ change_set.description }} {% endfor %} """ LIST_STACKS_RESPONSE = """ {% for stack in stacks %} {{ stack.stack_id }} {{ stack.status }} {{ stack.name }} {{ stack.creation_time_iso_8601 }} {{ stack.description }} {% endfor %} """ LIST_STACKS_RESOURCES_RESPONSE = """ {% for resource in resources %} CREATE_COMPLETE {{ resource.logical_resource_id }} 2011-06-21T20:15:58Z {{ resource.physical_resource_id }} {{ resource.type }} {% endfor %} 2d06e36c-ac1d-11e0-a958-f9382b6eb86b """ GET_TEMPLATE_RESPONSE_TEMPLATE = """ {{ stack.template }} b9b4b068-3a41-11e5-94eb-example """ DELETE_STACK_RESPONSE_TEMPLATE = """ 5ccc7dcd-744c-11e5-be70-example """ LIST_EXPORTS_RESPONSE = """ {% for export in exports %} {{ export.exporting_stack_id }} {{ export.name }} {{ export.value }} {% endfor %} {% if next_token %} {{ next_token }} {% endif %} 5ccc7dcd-744c-11e5-be70-example """ CREATE_STACK_SET_RESPONSE_TEMPLATE = """ {{ stackset.id }} f457258c-391d-41d1-861f-example """ DESCRIBE_STACK_SET_RESPONSE_TEMPLATE = """ {{ stackset.arn }} {{ stackset.execution_role }} {{ stackset.admin_role }} {{ stackset.id }} {{ stackset.template }} {{ stackset.name }} {% for param_name, param_value in stackset.parameters.items() %} {{ param_name }} {{ param_value }} {% endfor %} {% for tag_key, tag_value in stackset.tags.items() %} {{ tag_key }} {{ tag_value }} {% endfor %} {{ stackset.status }} d8b64e11-5332-46e1-9603-example """ DELETE_STACK_SET_RESPONSE_TEMPLATE = """ c35ec2d0-d69f-4c4d-9bd7-example """ CREATE_STACK_INSTANCES_TEMPLATE = """ 1459ad6d-63cc-4c96-a73e-example 6b29f7e3-69be-4d32-b374-example """ LIST_STACK_INSTANCES_TEMPLATE = """ {% for instance in stackset.stack_instances %} {{ instance.StackId }} {{ instance.StackSetId }} {{ instance.Region }} {{ instance.Account }} {{ instance.Status }} {% endfor %} 83c27e73-b498-410f-993c-example """ DELETE_STACK_INSTANCES_TEMPLATE = """ {{ operation.OperationId }} e5325090-66f6-4ecd-a531-example """ DESCRIBE_STACK_INSTANCE_TEMPLATE = """ {{ instance.StackId }} {{ instance.StackSetId }} {% if instance.ParameterOverrides %} {% for override in instance.ParameterOverrides %} {% if override['ParameterKey'] or override['ParameterValue'] %} {{ override.ParameterKey }} false {{ override.ParameterValue }} {% endif %} {% endfor %} {% else %} {% endif %} {{ instance.Region }} {{ instance.Account }} {{ instance.Status }} c6c7be10-0343-4319-8a25-example """ LIST_STACK_SETS_TEMPLATE = """ {% for key, value in stacksets.items() %} {{ value.name }} {{ value.id }} {{ value.status }} {% endfor %} 4dcacb73-841e-4ed8-b335-example """ UPDATE_STACK_INSTANCES_RESPONSE_TEMPLATE = """ {{ operation }} bdbf8e94-19b6-4ce4-af85-example """ UPDATE_STACK_SET_RESPONSE_TEMPLATE = """ {{ operation.OperationId }} adac907b-17e3-43e6-a254-example """ LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE = """ {% for operation in stackset.operations %} {{ operation.CreationTimestamp }} {{ operation.OperationId }} {{ operation.Action }} {{ operation.EndTimestamp }} {{ operation.Status }} {% endfor %} 65b9d9be-08bb-4a43-9a21-example """ STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE = """ 2188554a-07c6-4396-b2c5-example """ DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = ( """ {{ stackset.execution_role }} arn:aws:iam::""" + ACCOUNT_ID + """:role/{{ stackset.admin_role }} {{ stackset.id }} {{ operation.CreationTimestamp }} {{ operation.OperationId }} {{ operation.Action }} {{ operation.EndTimestamp }} {{ operation.Status }} 2edc27b6-9ce2-486a-a192-example """ ) LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = ( """ {% for instance in operation.Instances %} {% for account, region in instance.items() %} Function not found: arn:aws:lambda:us-west-2:""" + ACCOUNT_ID + """:function:AWSCloudFormationStackSetAccountGate SKIPPED {{ region }} {{ account }} {{ operation.Status }} {% endfor %} {% endfor %} ac05a9ce-5f98-4197-a29b-example """ ) GET_TEMPLATE_SUMMARY_TEMPLATE = """ {{ template_summary.Description }} {% for resource in template_summary.resourceTypes %} {{ resource }} {% endfor %} {{ template_summary.AWSTemplateFormatVersion }} b9b4b068-3a41-11e5-94eb-example """