Cloudformation: Various attributes (#5732)

This commit is contained in:
Bert Blommers 2022-12-03 21:21:52 -01:00 committed by GitHub
parent aeb507f091
commit d10a8e9900
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 230 additions and 94 deletions

View File

@ -565,7 +565,7 @@
## cloudformation
<details>
<summary>34% implemented</summary>
<summary>50% implemented</summary>
- [ ] activate_type
- [ ] batch_describe_type_configurations
@ -586,13 +586,13 @@
- [ ] describe_change_set_hooks
- [ ] describe_publisher
- [ ] describe_stack_drift_detection_status
- [ ] describe_stack_events
- [X] describe_stack_events
- [X] describe_stack_instance
- [ ] describe_stack_resource
- [X] describe_stack_resource
- [ ] describe_stack_resource_drifts
- [ ] describe_stack_resources
- [ ] describe_stack_set
- [ ] describe_stack_set_operation
- [X] describe_stack_resources
- [X] describe_stack_set
- [X] describe_stack_set_operation
- [X] describe_stacks
- [ ] describe_type
- [ ] describe_type_registration
@ -602,7 +602,7 @@
- [ ] estimate_template_cost
- [X] execute_change_set
- [X] get_stack_policy
- [ ] get_template
- [X] get_template
- [ ] get_template_summary
- [ ] import_stacks_to_stack_set
- [X] list_change_sets
@ -610,9 +610,9 @@
- [ ] list_imports
- [X] list_stack_instances
- [X] list_stack_resources
- [ ] list_stack_set_operation_results
- [ ] list_stack_set_operations
- [ ] list_stack_sets
- [X] list_stack_set_operation_results
- [X] list_stack_set_operations
- [X] list_stack_sets
- [X] list_stacks
- [ ] list_type_registrations
- [ ] list_type_versions
@ -626,7 +626,7 @@
- [ ] set_type_configuration
- [ ] set_type_default_version
- [ ] signal_resource
- [ ] stop_stack_set_operation
- [X] stop_stack_set_operation
- [ ] test_type
- [X] update_stack
- [X] update_stack_instances

View File

@ -62,13 +62,13 @@ cloudformation
- [ ] describe_change_set_hooks
- [ ] describe_publisher
- [ ] describe_stack_drift_detection_status
- [ ] describe_stack_events
- [X] describe_stack_events
- [X] describe_stack_instance
- [ ] describe_stack_resource
- [X] describe_stack_resource
- [ ] describe_stack_resource_drifts
- [ ] describe_stack_resources
- [ ] describe_stack_set
- [ ] describe_stack_set_operation
- [X] describe_stack_resources
- [X] describe_stack_set
- [X] describe_stack_set_operation
- [X] describe_stacks
- [ ] describe_type
- [ ] describe_type_registration
@ -78,7 +78,7 @@ cloudformation
- [ ] estimate_template_cost
- [X] execute_change_set
- [X] get_stack_policy
- [ ] get_template
- [X] get_template
- [ ] get_template_summary
- [ ] import_stacks_to_stack_set
- [X] list_change_sets
@ -91,9 +91,9 @@ cloudformation
- [X] list_stack_resources
- [ ] list_stack_set_operation_results
- [ ] list_stack_set_operations
- [ ] list_stack_sets
- [X] list_stack_set_operation_results
- [X] list_stack_set_operations
- [X] list_stack_sets
- [X] list_stacks
- [ ] list_type_registrations
- [ ] list_type_versions
@ -111,7 +111,7 @@ cloudformation
- [ ] set_type_configuration
- [ ] set_type_default_version
- [ ] signal_resource
- [ ] stop_stack_set_operation
- [X] stop_stack_set_operation
- [ ] test_type
- [X] update_stack
- [X] update_stack_instances

View File

@ -48,6 +48,16 @@ class StackSetNotEmpty(RESTError):
)
class StackSetNotFoundException(RESTError):
def __init__(self, name: str):
template = Template(ERROR_RESPONSE)
message = f"StackSet {name} not found"
super().__init__(error_type="StackSetNotFoundException", message=message)
self.description = template.render(
code="StackSetNotFoundException", message=message
)
class UnsupportedAttribute(ValidationError):
def __init__(self, resource: str, attr: str):
template = Template(ERROR_RESPONSE)

View File

@ -26,7 +26,7 @@ from .utils import (
yaml_tag_constructor,
validate_template_cfn_lint,
)
from .exceptions import ValidationError, StackSetNotEmpty
from .exceptions import ValidationError, StackSetNotEmpty, StackSetNotFoundException
class FakeStackSet(BaseModel):
@ -40,9 +40,9 @@ class FakeStackSet(BaseModel):
description: Optional[str],
parameters: Dict[str, str],
permission_model: str,
tags: Optional[Dict[str, str]] = None,
admin_role: str = "AWSCloudFormationStackSetAdministrationRole",
execution_role: str = "AWSCloudFormationStackSetExecutionRole",
tags: Optional[Dict[str, str]],
admin_role: Optional[str],
execution_role: Optional[str],
):
self.id = stackset_id
self.arn = generate_stackset_arn(stackset_id, region, account_id)
@ -53,7 +53,7 @@ class FakeStackSet(BaseModel):
self.tags = tags
self.admin_role = admin_role
self.admin_role_arn = f"arn:aws:iam::{account_id}:role/{self.admin_role}"
self.execution_role = execution_role
self.execution_role = execution_role or "AWSCloudFormationStackSetExecutionRole"
self.status = "ACTIVE"
self.instances = FakeStackInstances(
account_id, template, parameters, self.id, self.name
@ -76,8 +76,10 @@ class FakeStackSet(BaseModel):
"OperationId": operation_id,
"Action": action,
"Status": status,
"CreationTimestamp": datetime.now(),
"EndTimestamp": datetime.now() + timedelta(minutes=2),
"CreationTimestamp": datetime.now().strftime("%Y-%m-%dT%H:%M:%S.%f"),
"EndTimestamp": (datetime.now() + timedelta(minutes=2)).strftime(
"%Y-%m-%dT%H:%M:%S.%f"
),
"Instances": [
{account: region} for account in accounts for region in regions
],
@ -368,6 +370,8 @@ class FakeStack(BaseModel):
role_arn: Optional[str] = None,
cross_stack_resources: Optional[Dict[str, Export]] = None,
enable_termination_protection: Optional[bool] = False,
timeout_in_mins: Optional[int] = None,
stack_policy_body: Optional[str] = None,
):
self.stack_id = stack_id
self.name = name
@ -385,7 +389,8 @@ class FakeStack(BaseModel):
self.role_arn = role_arn
self.tags = tags if tags else {}
self.events: List[FakeEvent] = []
self.policy = ""
self.timeout_in_mins = timeout_in_mins
self.policy = stack_policy_body or ""
self.cross_stack_resources: Dict[str, Export] = cross_stack_resources or {}
self.enable_termination_protection: bool = (
@ -716,6 +721,9 @@ class CloudFormationBackend(BaseBackend):
parameters: Dict[str, str],
tags: Dict[str, str],
permission_model: str,
admin_role: Optional[str],
exec_role: Optional[str],
description: Optional[str],
) -> FakeStackSet:
"""
The following parameters are not yet implemented: StackId, AdministrationRoleARN, AutoDeployment, ExecutionRoleName, CallAs, ClientRequestToken, ManagedExecution
@ -728,21 +736,26 @@ class CloudFormationBackend(BaseBackend):
region=self.region_name,
template=template,
parameters=parameters,
description=None,
description=description,
tags=tags,
permission_model=permission_model,
admin_role=admin_role,
execution_role=exec_role,
)
self.stacksets[stackset_id] = new_stackset
return new_stackset
def get_stack_set(self, name: str) -> FakeStackSet:
def describe_stack_set(self, name: str) -> FakeStackSet:
stacksets = self.stacksets.keys()
if name in stacksets:
if name in stacksets and self.stacksets[name].status != "DELETED":
return self.stacksets[name]
for stackset in stacksets:
if self.stacksets[stackset].name == name:
if (
self.stacksets[stackset].name == name
and self.stacksets[stackset].status != "DELETED"
):
return self.stacksets[stackset]
raise ValidationError(name)
raise StackSetNotFoundException(name)
def delete_stack_set(self, name: str) -> None:
stackset_to_delete: Optional[FakeStackSet] = None
@ -755,8 +768,33 @@ class CloudFormationBackend(BaseBackend):
if stackset_to_delete is not None:
if stackset_to_delete.stack_instances:
raise StackSetNotEmpty()
# We don't remove StackSets from the list - they still show up when calling list_stack_sets
stackset_to_delete.delete()
def list_stack_sets(self) -> Iterable[FakeStackSet]:
return self.stacksets.values()
def list_stack_set_operations(self, stackset_name: str) -> List[Dict[str, Any]]:
stackset = self.describe_stack_set(stackset_name)
return stackset.operations
def stop_stack_set_operation(self, stackset_name: str, operation_id: str) -> None:
stackset = self.describe_stack_set(stackset_name)
stackset.update_operation(operation_id, "STOPPED")
def describe_stack_set_operation(
self, stackset_name: str, operation_id: str
) -> Tuple[FakeStackSet, Dict[str, Any]]:
stackset = self.describe_stack_set(stackset_name)
operation = stackset.get_operation(operation_id)
return stackset, operation
def list_stack_set_operation_results(
self, stackset_name: str, operation_id: str
) -> Dict[str, Any]:
stackset = self.describe_stack_set(stackset_name)
return stackset.get_operation(operation_id)
def create_stack_instances(
self,
stackset_name: str,
@ -768,7 +806,7 @@ class CloudFormationBackend(BaseBackend):
"""
The following parameters are not yet implemented: DeploymentTargets.AccountFilterType, DeploymentTargets.AccountsUrl, OperationPreferences, CallAs
"""
stackset = self.get_stack_set(stackset_name)
stackset = self.describe_stack_set(stackset_name)
operation_id = stackset.create_stack_instances(
accounts=accounts,
@ -788,7 +826,7 @@ class CloudFormationBackend(BaseBackend):
"""
Calling this will update the parameters, but the actual resources are not updated
"""
stack_set = self.get_stack_set(stackset_name)
stack_set = self.describe_stack_set(stackset_name)
return stack_set.update_instances(accounts, regions, parameters)
def update_stack_set(
@ -804,7 +842,7 @@ class CloudFormationBackend(BaseBackend):
regions: List[str],
operation_id: str,
) -> Dict[str, Any]:
stackset = self.get_stack_set(stackset_name)
stackset = self.describe_stack_set(stackset_name)
resolved_parameters = self._resolve_update_parameters(
instance=stackset, incoming_params=parameters
)
@ -827,7 +865,7 @@ class CloudFormationBackend(BaseBackend):
"""
The following parameters are not yet implemented: DeploymentTargets, OperationPreferences, RetainStacks, OperationId, CallAs
"""
stackset = self.get_stack_set(stackset_name)
stackset = self.describe_stack_set(stackset_name)
stackset.delete_stack_instances(accounts, regions)
return stackset
@ -840,6 +878,8 @@ class CloudFormationBackend(BaseBackend):
tags: Optional[Dict[str, str]] = None,
role_arn: Optional[str] = None,
enable_termination_protection: Optional[bool] = False,
timeout_in_mins: Optional[int] = None,
stack_policy_body: Optional[str] = None,
) -> FakeStack:
"""
The functionality behind EnableTerminationProtection is not yet implemented.
@ -857,6 +897,8 @@ class CloudFormationBackend(BaseBackend):
role_arn=role_arn,
cross_stack_resources=self.exports,
enable_termination_protection=enable_termination_protection,
timeout_in_mins=timeout_in_mins,
stack_policy_body=stack_policy_body,
)
self.stacks[stack_id] = new_stack
self._validate_export_uniqueness(new_stack)
@ -1012,7 +1054,7 @@ class CloudFormationBackend(BaseBackend):
def describe_stack_instance(
self, stack_set_name: str, account_id: str, region: str
) -> Dict[str, Any]:
stack_set = self.get_stack_set(stack_set_name)
stack_set = self.describe_stack_set(stack_set_name)
return stack_set.instances.get_instance(account_id, region).to_dict()
def list_stack_instances(self, stackset_name: str) -> List[Dict[str, Any]]:
@ -1020,7 +1062,7 @@ class CloudFormationBackend(BaseBackend):
Pagination is not yet implemented.
The parameters StackInstanceAccount/StackInstanceRegion are not yet implemented.
"""
stack_set = self.get_stack_set(stackset_name)
stack_set = self.describe_stack_set(stackset_name)
return [i.to_dict() for i in stack_set.instances.stack_instances]
def list_change_sets(self) -> Iterable[FakeChangeSet]:
@ -1076,6 +1118,26 @@ class CloudFormationBackend(BaseBackend):
raise ValidationError(message=f"Stack: {stack_name} does not exist")
stack.policy = policy_body
def describe_stack_resource(
self, stack_name: str, logical_resource_id: str
) -> Tuple[FakeStack, Type[CloudFormationModel]]:
stack = self.get_stack(stack_name)
for stack_resource in stack.stack_resources:
if stack_resource.logical_resource_id == logical_resource_id: # type: ignore[attr-defined]
return stack, stack_resource
message = (
f"Resource {logical_resource_id} does not exist for stack {stack_name}"
)
raise ValidationError(stack_name, message)
def describe_stack_resources(
self, stack_name: str
) -> Tuple[FakeStack, Iterable[Type[CloudFormationModel]]]:
stack = self.get_stack(stack_name)
return stack, stack.stack_resources
def list_stack_resources(
self, stack_name_or_id: str
) -> Iterable[Type[CloudFormationModel]]:
@ -1111,6 +1173,12 @@ class CloudFormationBackend(BaseBackend):
next_token = str(token + 100) if len(all_exports) > token + 100 else None
return exports, next_token
def describe_stack_events(self, stack_name: str) -> List[FakeEvent]:
return self.get_stack(stack_name).events
def get_template(self, name_or_stack_id: str) -> Union[str, Dict[str, Any]]:
return self.get_stack(name_or_stack_id).template
def validate_template(self, template: str) -> List[Any]:
return validate_template_cfn_lint(template)

View File

@ -125,6 +125,8 @@ class CloudFormationResponse(BaseResponse):
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"])
@ -151,6 +153,8 @@ class CloudFormationResponse(BaseResponse):
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(
@ -271,37 +275,29 @@ class CloudFormationResponse(BaseResponse):
def describe_stack_resource(self) -> str:
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: # type: ignore[attr-defined]
resource = stack_resource
break
if not resource:
message = (
f"Resource {logical_resource_id} does not exist for stack {stack_name}"
)
raise ValidationError(stack_name, message)
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 = self.cloudformation_backend.get_stack(stack_name)
stack, resources = self.cloudformation_backend.describe_stack_resources(
stack_name
)
template = self.response_template(DESCRIBE_STACK_RESOURCES_RESPONSE)
return template.render(stack=stack)
return template.render(stack=stack, resources=resources)
def describe_stack_events(self) -> str:
stack_name = self._get_param("StackName")
stack = self.cloudformation_backend.get_stack(stack_name)
events = self.cloudformation_backend.describe_stack_events(stack_name)
template = self.response_template(DESCRIBE_STACK_EVENTS_RESPONSE)
return template.render(stack=stack)
return template.render(events=events)
def list_change_sets(self) -> str:
change_sets = self.cloudformation_backend.list_change_sets()
@ -323,14 +319,14 @@ class CloudFormationResponse(BaseResponse):
def get_template(self) -> str:
name_or_stack_id = self.querystring.get("StackName")[0] # type: ignore[index]
stack = self.cloudformation_backend.get_stack(name_or_stack_id)
stack_template = self.cloudformation_backend.get_template(name_or_stack_id)
if self.request_json:
return json.dumps(
{
"GetTemplateResponse": {
"GetTemplateResult": {
"TemplateBody": stack.template,
"TemplateBody": stack_template,
"ResponseMetadata": {
"RequestId": "2d06e36c-ac1d-11e0-a958-f9382b6eb86bEXAMPLE"
},
@ -340,7 +336,7 @@ class CloudFormationResponse(BaseResponse):
)
else:
template = self.response_template(GET_TEMPLATE_RESPONSE_TEMPLATE)
return template.render(stack=stack)
return template.render(stack_template=stack_template)
def get_template_summary(self) -> str:
stack_name = self._get_param("StackName")
@ -471,6 +467,9 @@ class CloudFormationResponse(BaseResponse):
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")
@ -492,6 +491,9 @@ class CloudFormationResponse(BaseResponse):
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(
@ -549,7 +551,7 @@ class CloudFormationResponse(BaseResponse):
def describe_stack_set(self) -> str:
stackset_name = self._get_param("StackSetName")
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
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"
@ -572,7 +574,7 @@ class CloudFormationResponse(BaseResponse):
return rendered
def list_stack_sets(self) -> str:
stacksets = self.cloudformation_backend.stacksets
stacksets = self.cloudformation_backend.list_stack_sets()
template = self.response_template(LIST_STACK_SETS_TEMPLATE)
return template.render(stacksets=stacksets)
@ -584,31 +586,36 @@ class CloudFormationResponse(BaseResponse):
def list_stack_set_operations(self) -> str:
stackset_name = self._get_param("StackSetName")
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
operations = self.cloudformation_backend.list_stack_set_operations(
stackset_name
)
template = self.response_template(LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE)
return template.render(stackset=stackset)
return template.render(operations=operations)
def stop_stack_set_operation(self) -> str:
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")
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 = self.cloudformation_backend.get_stack_set(stackset_name)
operation = stackset.get_operation(operation_id)
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")
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
operation = stackset.get_operation(operation_id)
operation = self.cloudformation_backend.list_stack_set_operation_results(
stackset_name, operation_id
)
template = self.response_template(
LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE
)
@ -814,7 +821,7 @@ DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResponse>
<StackName>{{ stack.name }}</StackName>
<StackId>{{ stack.stack_id }}</StackId>
{% if stack.change_set_id %}
<ChangeSetId>{{ stack.change_set_id }}</ChangeSetId>
<ChangeSetId>{{ stack.change_set_id }}</stack.timeout_in_minsChangeSetId>
{% endif %}
<Description><![CDATA[{{ stack.description }}]]></Description>
<CreationTime>{{ stack.creation_time_iso_8601 }}</CreationTime>
@ -861,6 +868,9 @@ DESCRIBE_STACKS_TEMPLATE = """<DescribeStacksResponse>
{% endfor %}
</Tags>
<EnableTerminationProtection>{{ stack.enable_termination_protection }}</EnableTerminationProtection>
{% if stack.timeout_in_mins %}
<TimeoutInMinutes>{{ stack.timeout_in_mins }}</TimeoutInMinutes>
{% endif %}
</member>
{% endfor %}
</Stacks>
@ -887,7 +897,7 @@ DESCRIBE_STACK_RESOURCE_RESPONSE_TEMPLATE = """<DescribeStackResourceResponse>
DESCRIBE_STACK_RESOURCES_RESPONSE = """<DescribeStackResourcesResponse>
<DescribeStackResourcesResult>
<StackResources>
{% for resource in stack.stack_resources %}
{% for resource in resources %}
<member>
<StackId>{{ stack.stack_id }}</StackId>
<StackName>{{ stack.name }}</StackName>
@ -905,7 +915,7 @@ DESCRIBE_STACK_RESOURCES_RESPONSE = """<DescribeStackResourcesResponse>
DESCRIBE_STACK_EVENTS_RESPONSE = """<DescribeStackEventsResponse xmlns="http://cloudformation.amazonaws.com/doc/2010-05-15/">
<DescribeStackEventsResult>
<StackEvents>
{% for event in stack.events[::-1] %}
{% for event in events[::-1] %}
<member>
<Timestamp>{{ event.timestamp.strftime('%Y-%m-%dT%H:%M:%S.%fZ') }}</Timestamp>
<ResourceStatus>{{ event.resource_status }}</ResourceStatus>
@ -983,7 +993,7 @@ LIST_STACKS_RESOURCES_RESPONSE = """<ListStackResourcesResponse>
GET_TEMPLATE_RESPONSE_TEMPLATE = """<GetTemplateResponse>
<GetTemplateResult>
<TemplateBody>{{ stack.template }}</TemplateBody>
<TemplateBody>{{ stack_template }}</TemplateBody>
</GetTemplateResult>
<ResponseMetadata>
<RequestId>b9b4b068-3a41-11e5-94eb-example</RequestId>
@ -1054,6 +1064,10 @@ DESCRIBE_STACK_SET_RESPONSE_TEMPLATE = """<DescribeStackSetResponse xmlns="http:
{% endfor %}
</Tags>
<Status>{{ stackset.status }}</Status>
<PermissionModel>{{ stackset.permission_model }}</PermissionModel>
{% if stackset.description %}
<Description>{{ stackset.description }}</Description>
{% endif %}
</StackSet>
</DescribeStackSetResult>
<ResponseMetadata>
@ -1142,11 +1156,11 @@ DESCRIBE_STACK_INSTANCE_TEMPLATE = """<DescribeStackInstanceResponse xmlns="http
LIST_STACK_SETS_TEMPLATE = """<ListStackSetsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<ListStackSetsResult>
<Summaries>
{% for key, value in stacksets.items() %}
{% for stackset in stacksets %}
<member>
<StackSetName>{{ value.name }}</StackSetName>
<StackSetId>{{ value.id }}</StackSetId>
<Status>{{ value.status }}</Status>
<StackSetName>{{ stackset.name }}</StackSetName>
<StackSetId>{{ stackset.id }}</StackSetId>
<Status>{{ stackset.status }}</Status>
</member>
{% endfor %}
</Summaries>
@ -1180,7 +1194,7 @@ UPDATE_STACK_SET_RESPONSE_TEMPLATE = """<UpdateStackSetResponse xmlns="http://in
LIST_STACK_SET_OPERATIONS_RESPONSE_TEMPLATE = """<ListStackSetOperationsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<ListStackSetOperationsResult>
<Summaries>
{% for operation in stackset.operations %}
{% for operation in operations %}
<member>
<CreationTimestamp>{{ operation.CreationTimestamp }}</CreationTimestamp>
<OperationId>{{ operation.OperationId }}</OperationId>

View File

@ -61,6 +61,24 @@ batch:
- TestAccBatchJobQueue_ComputeEnvironments_externalOrderUpdate
ce:
- TestAccCECostCategory
cloudformation:
- TestAccCloudFormationExportDataSource
- TestAccCloudFormationStackDataSource_DataSource
- TestAccCloudFormationStackSet_basic
- TestAccCloudFormationStackSet_templateBody
- TestAccCloudFormationStackSet_templateURL
- TestAccCloudFormationStackSet_description
- TestAccCloudFormationStackSet_operationPreferences
- TestAccCloudFormationStackSet_name
- TestAccCloudFormationStackSet_executionRoleName
- TestAccCloudFormationStackSet_disappears
- TestAccCloudFormationStack_basic
- TestAccCloudFormationStack_disappears
- TestAccCloudFormationStack_onFailure
- TestAccCloudFormationStack_yaml
- TestAccCloudFormationStack_withTransform
- TestAccCloudFormationStack_WithURLWithParams_withYAML
- TestAccCloudFormationStack_WithURL_withParams
cloudfront:
- TestAccCloudFrontDistributionDataSource_basic
- TestAccCloudFrontDistribution_isIPV6Enabled

View File

@ -321,19 +321,18 @@ def test_create_stack():
@mock_cloudformation
def test_create_stack_with_termination_protection():
def test_create_stack_with_additional_properties():
cf_conn = boto3.client("cloudformation", region_name="us-east-1")
cf_conn.create_stack(
StackName="test_stack",
TemplateBody=dummy_template_json,
EnableTerminationProtection=True,
TimeoutInMinutes=25,
)
stack = cf_conn.describe_stacks()["Stacks"][0]
stack.should.have.key("StackName").equal("test_stack")
stack.should.have.key("EnableTerminationProtection").equal(True)
template = cf_conn.get_template(StackName="test_stack")["TemplateBody"]
template.should.equal(dummy_template)
stack.should.have.key("TimeoutInMinutes").equals(25)
@mock_cloudformation
@ -765,9 +764,16 @@ def test_delete_stack_set_by_name():
)
cf_conn.delete_stack_set(StackSetName="teststackset")
cf_conn.describe_stack_set(StackSetName="teststackset")["StackSet"][
"Status"
].should.equal("DELETED")
stacks = cf_conn.list_stack_sets()["Summaries"]
stacks.should.have.length_of(1)
stacks[0].should.have.key("StackSetName").equals("teststackset")
stacks[0].should.have.key("Status").equals("DELETED")
with pytest.raises(ClientError) as exc:
cf_conn.describe_stack_set(StackSetName="teststackset")
err = exc.value.response["Error"]
err["Code"].should.equal("StackSetNotFoundException")
err["Message"].should.equal("StackSet teststackset not found")
@mock_cloudformation
@ -779,9 +785,10 @@ def test_delete_stack_set_by_id():
stack_set_id = response["StackSetId"]
cf_conn.delete_stack_set(StackSetName=stack_set_id)
cf_conn.describe_stack_set(StackSetName="teststackset")["StackSet"][
"Status"
].should.equal("DELETED")
stacks = cf_conn.list_stack_sets()["Summaries"]
stacks.should.have.length_of(1)
stacks[0].should.have.key("StackSetName").equals("teststackset")
stacks[0].should.have.key("Status").equals("DELETED")
@mock_cloudformation
@ -814,14 +821,20 @@ def test_delete_stack_set__while_instances_are_running():
def test_create_stack_set():
cf_conn = boto3.client("cloudformation", region_name="us-east-1")
response = cf_conn.create_stack_set(
StackSetName="teststackset", TemplateBody=dummy_template_json
StackSetName="teststackset",
TemplateBody=dummy_template_json,
Description="desc",
AdministrationRoleARN="admin/role/arn:asdfasdfadsf",
)
cf_conn.describe_stack_set(StackSetName="teststackset")["StackSet"][
"TemplateBody"
].should.equal(dummy_template_json)
response["StackSetId"].should_not.equal(None)
stack_set = cf_conn.describe_stack_set(StackSetName="teststackset")["StackSet"]
stack_set["TemplateBody"].should.equal(dummy_template_json)
stack_set.should.have.key("AdministrationRoleARN").should.equal(
"admin/role/arn:asdfasdfadsf"
)
stack_set.should.have.key("Description").equals("desc")
@mock_cloudformation
@pytest.mark.parametrize("name", ["1234", "stack_set", "-set"])

View File

@ -71,6 +71,19 @@ def test_set_stack_policy_with_body():
resp.should.have.key("StackPolicyBody").equals(policy)
@mock_cloudformation
def test_set_stack_policy_on_create():
cf_conn = boto3.client("cloudformation", region_name="us-east-1")
cf_conn.create_stack(
StackName="test_stack",
TemplateBody=dummy_template_json,
StackPolicyBody="stack_policy_body",
)
resp = cf_conn.get_stack_policy(StackName="test_stack")
resp.should.have.key("StackPolicyBody").equals("stack_policy_body")
@mock_cloudformation
@mock_s3
def test_set_stack_policy_with_url():