Implement CloudFormation Stack deletion for VPCs, Subnets (#6118)
This commit is contained in:
parent
ea96013957
commit
d33fe9e086
@ -108,6 +108,18 @@ class Subnet(TaggedEC2Resource, CloudFormationModel):
|
|||||||
|
|
||||||
return subnet
|
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
|
@property
|
||||||
def available_ip_addresses(self) -> str:
|
def available_ip_addresses(self) -> str:
|
||||||
enis = [
|
enis = [
|
||||||
|
@ -239,6 +239,18 @@ class VPC(TaggedEC2Resource, CloudFormationModel):
|
|||||||
|
|
||||||
return vpc
|
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
|
@property
|
||||||
def physical_resource_id(self) -> str:
|
def physical_resource_id(self) -> str:
|
||||||
return self.id
|
return self.id
|
||||||
|
@ -1785,13 +1785,48 @@ def test_delete_stack_by_name():
|
|||||||
|
|
||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
|
@mock_ec2
|
||||||
def test_delete_stack():
|
def test_delete_stack():
|
||||||
cf = boto3.client("cloudformation", region_name="us-east-1")
|
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)
|
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")
|
cf.delete_stack(StackName="test_stack")
|
||||||
stacks = cf.list_stacks()
|
stacks = cf.list_stacks()
|
||||||
assert stacks["StackSummaries"][0]["StackStatus"] == "DELETE_COMPLETE"
|
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
|
@mock_cloudformation
|
||||||
|
@ -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_ec2
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
@ -84,29 +96,44 @@ def test_vpc_single_instance_in_subnet():
|
|||||||
|
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
@mock_ec2
|
@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")
|
cf = boto3.client("cloudformation", region_name="us-east-1")
|
||||||
ec2 = boto3.client("ec2", region_name="us-east-1")
|
ec2 = boto3.client("ec2", region_name="us-east-1")
|
||||||
name = str(uuid4())[0:6]
|
name = str(uuid4())[0:6]
|
||||||
|
|
||||||
cf.create_stack(StackName=name, TemplateBody=json.dumps(template_vpc))
|
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"]
|
resources = cf.list_stack_resources(StackName=name)["StackResourceSummaries"]
|
||||||
vpc_id = resources[0]["PhysicalResourceId"]
|
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)
|
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_ec2
|
||||||
@mock_cloudformation
|
@mock_cloudformation
|
||||||
|
Loading…
Reference in New Issue
Block a user