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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user