CloudFormation: Allow deletion of deeply nested structures (#7413)
This commit is contained in:
parent
c404f0877e
commit
cd40abe0a9
@ -835,8 +835,15 @@ class ResourceMap(collections_abc.Mapping): # type: ignore[type-arg]
|
|||||||
for key, value in self._resource_json_map.items()
|
for key, value in self._resource_json_map.items()
|
||||||
if not value.get("DeletionPolicy") == "Retain"
|
if not value.get("DeletionPolicy") == "Retain"
|
||||||
)
|
)
|
||||||
tries = 1
|
# Keep track of how many resources we deleted before
|
||||||
while remaining_resources and tries < 5:
|
# (set to current + 1 to pretend the number of resources is already going down)
|
||||||
|
previous_remaining_resources = len(remaining_resources) + 1
|
||||||
|
# Delete while we have resources, and while the number of remaining resources is going down
|
||||||
|
while (
|
||||||
|
remaining_resources
|
||||||
|
and len(remaining_resources) < previous_remaining_resources
|
||||||
|
):
|
||||||
|
previous_remaining_resources = len(remaining_resources)
|
||||||
for resource in remaining_resources.copy():
|
for resource in remaining_resources.copy():
|
||||||
parsed_resource = self._parsed_resources.get(resource)
|
parsed_resource = self._parsed_resources.get(resource)
|
||||||
try:
|
try:
|
||||||
@ -858,8 +865,8 @@ class ResourceMap(collections_abc.Mapping): # type: ignore[type-arg]
|
|||||||
last_exception = e
|
last_exception = e
|
||||||
else:
|
else:
|
||||||
remaining_resources.remove(resource)
|
remaining_resources.remove(resource)
|
||||||
tries += 1
|
|
||||||
if tries == 5:
|
if remaining_resources:
|
||||||
raise last_exception
|
raise last_exception
|
||||||
|
|
||||||
def _delete_resource(
|
def _delete_resource(
|
||||||
|
@ -743,7 +743,7 @@ class Role(CloudFormationModel):
|
|||||||
|
|
||||||
for role in backend.roles.values():
|
for role in backend.roles.values():
|
||||||
if role.name == resource_name:
|
if role.name == resource_name:
|
||||||
for arn in role.policies.keys():
|
for arn in list(role.policies.keys()):
|
||||||
role.delete_policy(arn)
|
role.delete_policy(arn)
|
||||||
backend.delete_role(resource_name)
|
backend.delete_role(resource_name)
|
||||||
|
|
||||||
|
@ -1723,6 +1723,49 @@ def test_delete_stack():
|
|||||||
assert ec2.describe_instances()["Reservations"][0]["Instances"][0]["State"] != state
|
assert ec2.describe_instances()["Reservations"][0]["Instances"][0]["State"] != state
|
||||||
|
|
||||||
|
|
||||||
|
@mock_aws
|
||||||
|
@pytest.mark.parametrize("nr_of_resources", [1, 3, 5])
|
||||||
|
def test_delete_stack_with_nested_resources(nr_of_resources):
|
||||||
|
"""
|
||||||
|
Resources can be dependent on eachother
|
||||||
|
Verify that we'll keep deleting resources from a stack until there are none left
|
||||||
|
"""
|
||||||
|
nested_template = {"Resources": {}}
|
||||||
|
for idx in range(nr_of_resources):
|
||||||
|
role_refs = []
|
||||||
|
for idy in range(nr_of_resources):
|
||||||
|
role = {
|
||||||
|
"Type": "AWS::IAM::Role",
|
||||||
|
"Properties": {
|
||||||
|
"AssumeRolePolicyDocument": {
|
||||||
|
"Statement": [
|
||||||
|
{
|
||||||
|
"Action": ["sts:AssumeRole"],
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Principal": {"Service": ["ec2.amazonaws.com"]},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
nested_template["Resources"].update({f"role{idx}_{idy}": role})
|
||||||
|
role_refs.append({"Ref": f"role{idx}_{idy}"})
|
||||||
|
|
||||||
|
instance_profile = {
|
||||||
|
"Type": "AWS::IAM::InstanceProfile",
|
||||||
|
"Properties": {"Path": "/", "Roles": role_refs},
|
||||||
|
}
|
||||||
|
nested_template["Resources"].update({f"ip{idx}": instance_profile})
|
||||||
|
cf = boto3.client("cloudformation", region_name=REGION_NAME)
|
||||||
|
|
||||||
|
cf.create_stack(StackName=TEST_STACK_NAME, TemplateBody=json.dumps(nested_template))
|
||||||
|
cf.delete_stack(StackName=TEST_STACK_NAME)
|
||||||
|
|
||||||
|
iam = boto3.client("iam", REGION_NAME)
|
||||||
|
assert not iam.list_roles()["Roles"]
|
||||||
|
assert not iam.list_instance_profiles()["InstanceProfiles"]
|
||||||
|
|
||||||
|
|
||||||
@mock_aws
|
@mock_aws
|
||||||
@pytest.mark.skipif(
|
@pytest.mark.skipif(
|
||||||
settings.TEST_SERVER_MODE,
|
settings.TEST_SERVER_MODE,
|
||||||
|
Loading…
Reference in New Issue
Block a user