Cloudformation: Various attributes (#5732)
This commit is contained in:
parent
aeb507f091
commit
d10a8e9900
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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"])
|
||||
|
@ -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():
|
||||
|
Loading…
Reference in New Issue
Block a user