Implement CloudFormation Stack deletion for VPCs, Subnets (#6118)

This commit is contained in:
Jordan Sanders 2023-03-24 12:49:03 -05:00 committed by GitHub
parent ea96013957
commit d33fe9e086
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 99 additions and 13 deletions

View File

@ -108,6 +108,18 @@ class Subnet(TaggedEC2Resource, CloudFormationModel):
return subnet
@classmethod
def delete_from_cloudformation_json( # type: ignore[misc]
cls,
resource_name: str,
cloudformation_json: Dict[str, Any],
account_id: str,
region_name: str,
) -> None:
from ..models import ec2_backends
ec2_backends[account_id][region_name].delete_subnet(resource_name)
@property
def available_ip_addresses(self) -> str:
enis = [

View File

@ -239,6 +239,18 @@ class VPC(TaggedEC2Resource, CloudFormationModel):
return vpc
@classmethod
def delete_from_cloudformation_json( # type: ignore[misc]
cls,
resource_name: str,
cloudformation_json: Dict[str, Any],
account_id: str,
region_name: str,
) -> None:
from ..models import ec2_backends
ec2_backends[account_id][region_name].delete_vpc(resource_name)
@property
def physical_resource_id(self) -> str:
return self.id

View File

@ -1785,13 +1785,48 @@ def test_delete_stack_by_name():
@mock_cloudformation
@mock_ec2
def test_delete_stack():
cf = boto3.client("cloudformation", region_name="us-east-1")
ec2 = boto3.client("ec2", region_name="us-east-1")
cf.create_stack(StackName="test_stack", TemplateBody=dummy_template_json)
state = ec2.describe_instances()["Reservations"][0]["Instances"][0]["State"]
cf.delete_stack(StackName="test_stack")
stacks = cf.list_stacks()
assert stacks["StackSummaries"][0]["StackStatus"] == "DELETE_COMPLETE"
ec2.describe_instances()["Reservations"][0]["Instances"][0]["State"].shouldnt.equal(
state
)
@mock_cloudformation
@mock_ec2
@pytest.mark.skipif(
settings.TEST_SERVER_MODE,
reason="Can't patch model delete attributes in server mode.",
)
def test_delete_stack_delete_not_implemented(monkeypatch):
monkeypatch.delattr(
"moto.ec2.models.instances.Instance.delete_from_cloudformation_json"
)
monkeypatch.delattr("moto.ec2.models.instances.Instance.delete")
cf = boto3.client("cloudformation", region_name="us-east-1")
ec2 = boto3.client("ec2", region_name="us-east-1")
cf.create_stack(StackName="test_stack", TemplateBody=dummy_template_json)
state = ec2.describe_instances()["Reservations"][0]["Instances"][0]["State"]
# Mock stack deletion succeeds
cf.delete_stack(StackName="test_stack")
stacks = cf.list_stacks()
assert stacks["StackSummaries"][0]["StackStatus"] == "DELETE_COMPLETE"
# But the underlying resource is untouched
ec2.describe_instances()["Reservations"][0]["Instances"][0]["State"].should.equal(
state
)
@mock_cloudformation

View File

@ -21,6 +21,18 @@ template_vpc = {
},
}
template_subnet = {
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "Create VPC",
"Resources": {
"VPC": {"Properties": {"CidrBlock": "192.168.0.0/16"}, "Type": "AWS::EC2::VPC"},
"Subnet": {
"Properties": {"VpcId": {"Ref": "VPC"}, "CidrBlock": "192.168.0.0/18"},
"Type": "AWS::EC2::Subnet",
},
},
}
@mock_ec2
@mock_cloudformation
@ -84,29 +96,44 @@ def test_vpc_single_instance_in_subnet():
@mock_cloudformation
@mock_ec2
def test_delete_stack_with_resource_missing_delete_attr():
def test_delete_stack_with_vpc():
cf = boto3.client("cloudformation", region_name="us-east-1")
ec2 = boto3.client("ec2", region_name="us-east-1")
name = str(uuid4())[0:6]
cf.create_stack(StackName=name, TemplateBody=json.dumps(template_vpc))
cf.describe_stacks(StackName=name)["Stacks"].should.have.length_of(1)
resources = cf.list_stack_resources(StackName=name)["StackResourceSummaries"]
vpc_id = resources[0]["PhysicalResourceId"]
cf.delete_stack(
StackName=name
) # should succeed, despite the fact that the resource itself cannot be deleted
with pytest.raises(ClientError) as exc:
cf.describe_stacks(StackName=name)
err = exc.value.response["Error"]
err.should.have.key("Code").equals("ValidationError")
err.should.have.key("Message").equals(f"Stack with id {name} does not exist")
# We still have our VPC, as the VPC-object does not have a delete-method yet
ec2.describe_vpcs(VpcIds=[vpc_id])["Vpcs"].should.have.length_of(1)
cf.delete_stack(StackName=name)
with pytest.raises(ec2.exceptions.ClientError):
ec2.describe_vpcs(VpcIds=[vpc_id])
@mock_cloudformation
@mock_ec2
def test_delete_stack_with_subnet():
cf = boto3.client("cloudformation", region_name="us-east-1")
ec2 = boto3.client("ec2", region_name="us-east-1")
name = str(uuid4())[0:6]
cf.create_stack(StackName=name, TemplateBody=json.dumps(template_subnet))
subnet_ids = [
resource["PhysicalResourceId"]
for resource in cf.list_stack_resources(StackName=name)[
"StackResourceSummaries"
]
if resource["ResourceType"] == "AWS::EC2::Subnet"
]
ec2.describe_subnets(SubnetIds=subnet_ids)["Subnets"].should.have.length_of(1)
cf.delete_stack(StackName=name)
with pytest.raises(ec2.exceptions.ClientError):
ec2.describe_subnets(SubnetIds=subnet_ids)
@mock_ec2
@mock_cloudformation