Techdebt: MyPy EC2 (d-e-models) (#5898)
This commit is contained in:
parent
7d7663d703
commit
b41533d42d
@ -71,7 +71,7 @@ class EBSBackend(BaseBackend):
|
|||||||
return ec2_backends[self.account_id][self.region_name]
|
return ec2_backends[self.account_id][self.region_name]
|
||||||
|
|
||||||
def start_snapshot(
|
def start_snapshot(
|
||||||
self, volume_size: str, tags: Optional[List[Dict[str, str]]], description: str
|
self, volume_size: int, tags: Optional[List[Dict[str, str]]], description: str
|
||||||
) -> EBSSnapshot:
|
) -> EBSSnapshot:
|
||||||
zone_name = f"{self.region_name}a"
|
zone_name = f"{self.region_name}a"
|
||||||
vol = self.ec2_backend.create_volume(size=volume_size, zone_name=zone_name)
|
vol = self.ec2_backend.create_volume(size=volume_size, zone_name=zone_name)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
from moto.core.exceptions import RESTError
|
from moto.core.exceptions import RESTError
|
||||||
from typing import List, Optional, Union
|
from typing import Any, List, Optional, Union, Iterable
|
||||||
|
|
||||||
|
|
||||||
# EC2 has a custom root-tag - <Response> vs <ErrorResponse>
|
# EC2 has a custom root-tag - <Response> vs <ErrorResponse>
|
||||||
@ -51,7 +51,7 @@ class MissingParameterError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidDHCPOptionsIdError(EC2ClientError):
|
class InvalidDHCPOptionsIdError(EC2ClientError):
|
||||||
def __init__(self, dhcp_options_id):
|
def __init__(self, dhcp_options_id: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidDhcpOptionID.NotFound",
|
"InvalidDhcpOptionID.NotFound",
|
||||||
f"DhcpOptionID {dhcp_options_id} does not exist.",
|
f"DhcpOptionID {dhcp_options_id} does not exist.",
|
||||||
@ -69,7 +69,7 @@ class InvalidParameterCombination(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class MalformedDHCPOptionsIdError(EC2ClientError):
|
class MalformedDHCPOptionsIdError(EC2ClientError):
|
||||||
def __init__(self, dhcp_options_id):
|
def __init__(self, dhcp_options_id: Optional[str]):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidDhcpOptionsId.Malformed",
|
"InvalidDhcpOptionsId.Malformed",
|
||||||
f'Invalid id: "{dhcp_options_id}" (expecting "dopt-...")',
|
f'Invalid id: "{dhcp_options_id}" (expecting "dopt-...")',
|
||||||
@ -166,7 +166,7 @@ class InvalidCustomerGatewayIdError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidNetworkInterfaceIdError(EC2ClientError):
|
class InvalidNetworkInterfaceIdError(EC2ClientError):
|
||||||
def __init__(self, eni_id):
|
def __init__(self, eni_id: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidNetworkInterfaceID.NotFound",
|
"InvalidNetworkInterfaceID.NotFound",
|
||||||
f"The network interface ID '{eni_id}' does not exist",
|
f"The network interface ID '{eni_id}' does not exist",
|
||||||
@ -174,7 +174,7 @@ class InvalidNetworkInterfaceIdError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidNetworkAttachmentIdError(EC2ClientError):
|
class InvalidNetworkAttachmentIdError(EC2ClientError):
|
||||||
def __init__(self, attachment_id):
|
def __init__(self, attachment_id: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidAttachmentID.NotFound",
|
"InvalidAttachmentID.NotFound",
|
||||||
f"The network interface attachment ID '{attachment_id}' does not exist",
|
f"The network interface attachment ID '{attachment_id}' does not exist",
|
||||||
@ -274,7 +274,7 @@ class UnvailableAMIIdError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidAMIAttributeItemValueError(EC2ClientError):
|
class InvalidAMIAttributeItemValueError(EC2ClientError):
|
||||||
def __init__(self, attribute: str, value: str):
|
def __init__(self, attribute: str, value: Union[str, Iterable[str], None]):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidAMIAttributeItemValue",
|
"InvalidAMIAttributeItemValue",
|
||||||
f'Invalid attribute item value "{value}" for {attribute} item type.',
|
f'Invalid attribute item value "{value}" for {attribute} item type.',
|
||||||
@ -289,13 +289,13 @@ class MalformedAMIIdError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidSnapshotIdError(EC2ClientError):
|
class InvalidSnapshotIdError(EC2ClientError):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
# Note: AWS returns empty message for this, as of 2014.08.22.
|
# Note: AWS returns empty message for this, as of 2014.08.22.
|
||||||
super().__init__("InvalidSnapshot.NotFound", "")
|
super().__init__("InvalidSnapshot.NotFound", "")
|
||||||
|
|
||||||
|
|
||||||
class InvalidSnapshotInUse(EC2ClientError):
|
class InvalidSnapshotInUse(EC2ClientError):
|
||||||
def __init__(self, snapshot_id, ami_id):
|
def __init__(self, snapshot_id: str, ami_id: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidSnapshot.InUse",
|
"InvalidSnapshot.InUse",
|
||||||
f"The snapshot {snapshot_id} is currently in use by {ami_id}",
|
f"The snapshot {snapshot_id} is currently in use by {ami_id}",
|
||||||
@ -303,14 +303,14 @@ class InvalidSnapshotInUse(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidVolumeIdError(EC2ClientError):
|
class InvalidVolumeIdError(EC2ClientError):
|
||||||
def __init__(self, volume_id):
|
def __init__(self, volume_id: Any):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidVolume.NotFound", f"The volume '{volume_id}' does not exist."
|
"InvalidVolume.NotFound", f"The volume '{volume_id}' does not exist."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class InvalidVolumeAttachmentError(EC2ClientError):
|
class InvalidVolumeAttachmentError(EC2ClientError):
|
||||||
def __init__(self, volume_id, instance_id):
|
def __init__(self, volume_id: str, instance_id: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidAttachment.NotFound",
|
"InvalidAttachment.NotFound",
|
||||||
f"Volume {volume_id} can not be detached from {instance_id} because it is not attached",
|
f"Volume {volume_id} can not be detached from {instance_id} because it is not attached",
|
||||||
@ -318,7 +318,7 @@ class InvalidVolumeAttachmentError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidVolumeDetachmentError(EC2ClientError):
|
class InvalidVolumeDetachmentError(EC2ClientError):
|
||||||
def __init__(self, volume_id, instance_id, device):
|
def __init__(self, volume_id: str, instance_id: str, device: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidAttachment.NotFound",
|
"InvalidAttachment.NotFound",
|
||||||
f"The volume {volume_id} is not attached to instance {instance_id} as device {device}",
|
f"The volume {volume_id} is not attached to instance {instance_id} as device {device}",
|
||||||
@ -326,7 +326,7 @@ class InvalidVolumeDetachmentError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class VolumeInUseError(EC2ClientError):
|
class VolumeInUseError(EC2ClientError):
|
||||||
def __init__(self, volume_id, instance_id):
|
def __init__(self, volume_id: str, instance_id: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"VolumeInUse",
|
"VolumeInUse",
|
||||||
f"Volume {volume_id} is currently attached to {instance_id}",
|
f"Volume {volume_id} is currently attached to {instance_id}",
|
||||||
@ -334,7 +334,7 @@ class VolumeInUseError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidAddressError(EC2ClientError):
|
class InvalidAddressError(EC2ClientError):
|
||||||
def __init__(self, ip):
|
def __init__(self, ip: Any):
|
||||||
super().__init__("InvalidAddress.NotFound", f"Address '{ip}' not found.")
|
super().__init__("InvalidAddress.NotFound", f"Address '{ip}' not found.")
|
||||||
|
|
||||||
|
|
||||||
@ -347,7 +347,7 @@ class LogDestinationNotFoundError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidAllocationIdError(EC2ClientError):
|
class InvalidAllocationIdError(EC2ClientError):
|
||||||
def __init__(self, allocation_id):
|
def __init__(self, allocation_id: Any):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidAllocationID.NotFound",
|
"InvalidAllocationID.NotFound",
|
||||||
f"Allocation ID '{allocation_id}' not found.",
|
f"Allocation ID '{allocation_id}' not found.",
|
||||||
@ -355,7 +355,7 @@ class InvalidAllocationIdError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidAssociationIdError(EC2ClientError):
|
class InvalidAssociationIdError(EC2ClientError):
|
||||||
def __init__(self, association_id):
|
def __init__(self, association_id: Any):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidAssociationID.NotFound",
|
"InvalidAssociationID.NotFound",
|
||||||
f"Association ID '{association_id}' not found.",
|
f"Association ID '{association_id}' not found.",
|
||||||
@ -426,7 +426,7 @@ class InvalidAggregationIntervalParameterError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidParameterValueError(EC2ClientError):
|
class InvalidParameterValueError(EC2ClientError):
|
||||||
def __init__(self, parameter_value):
|
def __init__(self, parameter_value: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidParameterValue",
|
"InvalidParameterValue",
|
||||||
f"Value {parameter_value} is invalid for parameter.",
|
f"Value {parameter_value} is invalid for parameter.",
|
||||||
@ -480,7 +480,7 @@ class GatewayNotAttachedError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class ResourceAlreadyAssociatedError(EC2ClientError):
|
class ResourceAlreadyAssociatedError(EC2ClientError):
|
||||||
def __init__(self, resource_id):
|
def __init__(self, resource_id: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"Resource.AlreadyAssociated",
|
"Resource.AlreadyAssociated",
|
||||||
f"Resource {resource_id} is already associated.",
|
f"Resource {resource_id} is already associated.",
|
||||||
@ -662,7 +662,7 @@ class InvalidLaunchTemplateNameNotFoundWithNameError(EC2ClientError):
|
|||||||
|
|
||||||
|
|
||||||
class InvalidParameterDependency(EC2ClientError):
|
class InvalidParameterDependency(EC2ClientError):
|
||||||
def __init__(self, param, param_needed):
|
def __init__(self, param: str, param_needed: str):
|
||||||
super().__init__(
|
super().__init__(
|
||||||
"InvalidParameterDependency",
|
"InvalidParameterDependency",
|
||||||
f"The parameter [{param}] requires the parameter {param_needed} to be set.",
|
f"The parameter [{param}] requires the parameter {param_needed} to be set.",
|
||||||
|
@ -186,7 +186,7 @@ class EC2Backend(
|
|||||||
def raise_error(self, code, message):
|
def raise_error(self, code, message):
|
||||||
raise EC2ClientError(code, message)
|
raise EC2ClientError(code, message)
|
||||||
|
|
||||||
def raise_not_implemented_error(self, blurb):
|
def raise_not_implemented_error(self, blurb: str):
|
||||||
raise MotoNotImplementedError(blurb)
|
raise MotoNotImplementedError(blurb)
|
||||||
|
|
||||||
def do_resources_exist(self, resource_ids):
|
def do_resources_exist(self, resource_ids):
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import itertools
|
import itertools
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
DependencyViolationError,
|
DependencyViolationError,
|
||||||
InvalidDHCPOptionsIdError,
|
InvalidDHCPOptionsIdError,
|
||||||
@ -12,12 +13,12 @@ from ..utils import random_dhcp_option_id, generic_filter
|
|||||||
class DHCPOptionsSet(TaggedEC2Resource):
|
class DHCPOptionsSet(TaggedEC2Resource):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ec2_backend,
|
ec2_backend: Any,
|
||||||
domain_name_servers=None,
|
domain_name_servers: Optional[List[str]] = None,
|
||||||
domain_name=None,
|
domain_name: Optional[str] = None,
|
||||||
ntp_servers=None,
|
ntp_servers: Optional[List[str]] = None,
|
||||||
netbios_name_servers=None,
|
netbios_name_servers: Optional[List[str]] = None,
|
||||||
netbios_node_type=None,
|
netbios_node_type: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
self._options = {
|
self._options = {
|
||||||
@ -30,7 +31,9 @@ class DHCPOptionsSet(TaggedEC2Resource):
|
|||||||
self.id = random_dhcp_option_id()
|
self.id = random_dhcp_option_id()
|
||||||
self.vpc = None
|
self.vpc = None
|
||||||
|
|
||||||
def get_filter_value(self, filter_name):
|
def get_filter_value(
|
||||||
|
self, filter_name: str, method_name: Optional[str] = None
|
||||||
|
) -> Any:
|
||||||
"""
|
"""
|
||||||
API Version 2015-10-01 defines the following filters for DescribeDhcpOptions:
|
API Version 2015-10-01 defines the following filters for DescribeDhcpOptions:
|
||||||
|
|
||||||
@ -54,26 +57,26 @@ class DHCPOptionsSet(TaggedEC2Resource):
|
|||||||
return super().get_filter_value(filter_name, "DescribeDhcpOptions")
|
return super().get_filter_value(filter_name, "DescribeDhcpOptions")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def options(self):
|
def options(self) -> Dict[str, Any]: # type: ignore[misc]
|
||||||
return self._options
|
return self._options
|
||||||
|
|
||||||
|
|
||||||
class DHCPOptionsSetBackend:
|
class DHCPOptionsSetBackend:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.dhcp_options_sets = {}
|
self.dhcp_options_sets: Dict[str, DHCPOptionsSet] = {}
|
||||||
|
|
||||||
def associate_dhcp_options(self, dhcp_options, vpc):
|
def associate_dhcp_options(self, dhcp_options: DHCPOptionsSet, vpc: Any) -> None:
|
||||||
dhcp_options.vpc = vpc
|
dhcp_options.vpc = vpc
|
||||||
vpc.dhcp_options = dhcp_options
|
vpc.dhcp_options = dhcp_options
|
||||||
|
|
||||||
def create_dhcp_options(
|
def create_dhcp_options(
|
||||||
self,
|
self,
|
||||||
domain_name_servers=None,
|
domain_name_servers: Optional[List[str]] = None,
|
||||||
domain_name=None,
|
domain_name: Optional[str] = None,
|
||||||
ntp_servers=None,
|
ntp_servers: Optional[List[str]] = None,
|
||||||
netbios_name_servers=None,
|
netbios_name_servers: Optional[List[str]] = None,
|
||||||
netbios_node_type=None,
|
netbios_node_type: Optional[str] = None,
|
||||||
):
|
) -> DHCPOptionsSet:
|
||||||
|
|
||||||
NETBIOS_NODE_TYPES = [1, 2, 4, 8]
|
NETBIOS_NODE_TYPES = [1, 2, 4, 8]
|
||||||
|
|
||||||
@ -95,7 +98,7 @@ class DHCPOptionsSetBackend:
|
|||||||
self.dhcp_options_sets[options.id] = options
|
self.dhcp_options_sets[options.id] = options
|
||||||
return options
|
return options
|
||||||
|
|
||||||
def delete_dhcp_options_set(self, options_id):
|
def delete_dhcp_options_set(self, options_id: Optional[str]) -> None:
|
||||||
if not (options_id and options_id.startswith("dopt-")):
|
if not (options_id and options_id.startswith("dopt-")):
|
||||||
raise MalformedDHCPOptionsIdError(options_id)
|
raise MalformedDHCPOptionsIdError(options_id)
|
||||||
|
|
||||||
@ -105,10 +108,11 @@ class DHCPOptionsSetBackend:
|
|||||||
self.dhcp_options_sets.pop(options_id)
|
self.dhcp_options_sets.pop(options_id)
|
||||||
else:
|
else:
|
||||||
raise InvalidDHCPOptionsIdError(options_id)
|
raise InvalidDHCPOptionsIdError(options_id)
|
||||||
return True
|
|
||||||
|
|
||||||
def describe_dhcp_options(self, dhcp_options_ids=None, filters=None):
|
def describe_dhcp_options(
|
||||||
dhcp_options_sets = self.dhcp_options_sets.copy().values()
|
self, dhcp_options_ids: Optional[List[str]] = None, filters: Any = None
|
||||||
|
) -> List[DHCPOptionsSet]:
|
||||||
|
dhcp_options_sets = list(self.dhcp_options_sets.copy().values())
|
||||||
|
|
||||||
if dhcp_options_ids:
|
if dhcp_options_ids:
|
||||||
dhcp_options_sets = [
|
dhcp_options_sets = [
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Optional
|
from typing import Any, Dict, List, Optional, Set, Iterable
|
||||||
|
|
||||||
from moto.core import CloudFormationModel
|
from moto.core import CloudFormationModel
|
||||||
from moto.packages.boto.ec2.blockdevicemapping import BlockDeviceType
|
from moto.packages.boto.ec2.blockdevicemapping import BlockDeviceType
|
||||||
@ -26,8 +26,13 @@ IOPS_SUPPORTED_VOLUME_TYPES = ["gp3", "io1", "io2"]
|
|||||||
GP3_DEFAULT_IOPS = 3000
|
GP3_DEFAULT_IOPS = 3000
|
||||||
|
|
||||||
|
|
||||||
class VolumeModification(object):
|
class VolumeModification:
|
||||||
def __init__(self, volume, target_size=None, target_volume_type=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
volume: "Volume",
|
||||||
|
target_size: Optional[int] = None,
|
||||||
|
target_volume_type: Optional[str] = None,
|
||||||
|
):
|
||||||
if not any([target_size, target_volume_type]):
|
if not any([target_size, target_volume_type]):
|
||||||
raise InvalidParameterValueError(
|
raise InvalidParameterValueError(
|
||||||
"Invalid input: Must specify at least one of size or type"
|
"Invalid input: Must specify at least one of size or type"
|
||||||
@ -42,7 +47,7 @@ class VolumeModification(object):
|
|||||||
self.start_time = utc_date_and_time()
|
self.start_time = utc_date_and_time()
|
||||||
self.end_time = utc_date_and_time()
|
self.end_time = utc_date_and_time()
|
||||||
|
|
||||||
def get_filter_value(self, filter_name):
|
def get_filter_value(self, filter_name: str) -> Any:
|
||||||
if filter_name == "original-size":
|
if filter_name == "original-size":
|
||||||
return self.original_size
|
return self.original_size
|
||||||
elif filter_name == "original-volume-type":
|
elif filter_name == "original-volume-type":
|
||||||
@ -56,7 +61,7 @@ class VolumeModification(object):
|
|||||||
|
|
||||||
|
|
||||||
class VolumeAttachment(CloudFormationModel):
|
class VolumeAttachment(CloudFormationModel):
|
||||||
def __init__(self, volume, instance, device, status):
|
def __init__(self, volume: "Volume", instance: Any, device: str, status: str):
|
||||||
self.volume = volume
|
self.volume = volume
|
||||||
self.attach_time = utc_date_and_time()
|
self.attach_time = utc_date_and_time()
|
||||||
self.instance = instance
|
self.instance = instance
|
||||||
@ -64,18 +69,23 @@ class VolumeAttachment(CloudFormationModel):
|
|||||||
self.status = status
|
self.status = status
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_name_type():
|
def cloudformation_name_type() -> str:
|
||||||
return None
|
return ""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_type():
|
def cloudformation_type() -> str:
|
||||||
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volumeattachment.html
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volumeattachment.html
|
||||||
return "AWS::EC2::VolumeAttachment"
|
return "AWS::EC2::VolumeAttachment"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(
|
def create_from_cloudformation_json( # type: ignore[misc]
|
||||||
cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
|
cls,
|
||||||
):
|
resource_name: str,
|
||||||
|
cloudformation_json: Any,
|
||||||
|
account_id: str,
|
||||||
|
region_name: str,
|
||||||
|
**kwargs: Any
|
||||||
|
) -> "VolumeAttachment":
|
||||||
from ..models import ec2_backends
|
from ..models import ec2_backends
|
||||||
|
|
||||||
properties = cloudformation_json["Properties"]
|
properties = cloudformation_json["Properties"]
|
||||||
@ -95,30 +105,34 @@ class VolumeAttachment(CloudFormationModel):
|
|||||||
class Volume(TaggedEC2Resource, CloudFormationModel):
|
class Volume(TaggedEC2Resource, CloudFormationModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ec2_backend,
|
ec2_backend: Any,
|
||||||
volume_id,
|
volume_id: str,
|
||||||
size,
|
size: int,
|
||||||
zone,
|
zone: Any,
|
||||||
snapshot_id=None,
|
snapshot_id: Optional[str] = None,
|
||||||
encrypted=False,
|
encrypted: bool = False,
|
||||||
kms_key_id=None,
|
kms_key_id: Optional[str] = None,
|
||||||
volume_type=None,
|
volume_type: Optional[str] = None,
|
||||||
iops=None,
|
iops: Optional[int] = None,
|
||||||
):
|
):
|
||||||
self.id = volume_id
|
self.id = volume_id
|
||||||
self.volume_type = volume_type or "gp2"
|
self.volume_type = volume_type or "gp2"
|
||||||
self.size = size
|
self.size = size
|
||||||
self.zone = zone
|
self.zone = zone
|
||||||
self.create_time = utc_date_and_time()
|
self.create_time = utc_date_and_time()
|
||||||
self.attachment = None
|
self.attachment: Optional[VolumeAttachment] = None
|
||||||
self.snapshot_id = snapshot_id
|
self.snapshot_id = snapshot_id
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
self.encrypted = encrypted
|
self.encrypted = encrypted
|
||||||
self.kms_key_id = kms_key_id
|
self.kms_key_id = kms_key_id
|
||||||
self.modifications = []
|
self.modifications: List[VolumeModification] = []
|
||||||
self.iops = iops
|
self.iops = iops
|
||||||
|
|
||||||
def modify(self, target_size=None, target_volume_type=None):
|
def modify(
|
||||||
|
self,
|
||||||
|
target_size: Optional[int] = None,
|
||||||
|
target_volume_type: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
modification = VolumeModification(
|
modification = VolumeModification(
|
||||||
volume=self, target_size=target_size, target_volume_type=target_volume_type
|
volume=self, target_size=target_size, target_volume_type=target_volume_type
|
||||||
)
|
)
|
||||||
@ -128,18 +142,23 @@ class Volume(TaggedEC2Resource, CloudFormationModel):
|
|||||||
self.volume_type = modification.target_volume_type
|
self.volume_type = modification.target_volume_type
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_name_type():
|
def cloudformation_name_type() -> str:
|
||||||
return None
|
return ""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_type():
|
def cloudformation_type() -> str:
|
||||||
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volume.html
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-volume.html
|
||||||
return "AWS::EC2::Volume"
|
return "AWS::EC2::Volume"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(
|
def create_from_cloudformation_json( # type: ignore[misc]
|
||||||
cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
|
cls,
|
||||||
):
|
resource_name: str,
|
||||||
|
cloudformation_json: Any,
|
||||||
|
account_id: str,
|
||||||
|
region_name: str,
|
||||||
|
**kwargs: Any
|
||||||
|
) -> "Volume":
|
||||||
from ..models import ec2_backends
|
from ..models import ec2_backends
|
||||||
|
|
||||||
properties = cloudformation_json["Properties"]
|
properties = cloudformation_json["Properties"]
|
||||||
@ -151,27 +170,29 @@ class Volume(TaggedEC2Resource, CloudFormationModel):
|
|||||||
return volume
|
return volume
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self) -> str:
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def status(self):
|
def status(self) -> str:
|
||||||
if self.attachment:
|
if self.attachment:
|
||||||
return "in-use"
|
return "in-use"
|
||||||
else:
|
else:
|
||||||
return "available"
|
return "available"
|
||||||
|
|
||||||
def get_filter_value(self, filter_name):
|
def get_filter_value(
|
||||||
|
self, filter_name: str, method_name: Optional[str] = None
|
||||||
|
) -> Any:
|
||||||
if filter_name.startswith("attachment") and not self.attachment:
|
if filter_name.startswith("attachment") and not self.attachment:
|
||||||
return None
|
return None
|
||||||
elif filter_name == "attachment.attach-time":
|
elif filter_name == "attachment.attach-time":
|
||||||
return self.attachment.attach_time
|
return self.attachment.attach_time # type: ignore[union-attr]
|
||||||
elif filter_name == "attachment.device":
|
elif filter_name == "attachment.device":
|
||||||
return self.attachment.device
|
return self.attachment.device # type: ignore[union-attr]
|
||||||
elif filter_name == "attachment.instance-id":
|
elif filter_name == "attachment.instance-id":
|
||||||
return self.attachment.instance.id
|
return self.attachment.instance.id # type: ignore[union-attr]
|
||||||
elif filter_name == "attachment.status":
|
elif filter_name == "attachment.status":
|
||||||
return self.attachment.status
|
return self.attachment.status # type: ignore[union-attr]
|
||||||
elif filter_name == "create-time":
|
elif filter_name == "create-time":
|
||||||
return self.create_time
|
return self.create_time
|
||||||
elif filter_name == "size":
|
elif filter_name == "size":
|
||||||
@ -193,27 +214,29 @@ class Volume(TaggedEC2Resource, CloudFormationModel):
|
|||||||
class Snapshot(TaggedEC2Resource):
|
class Snapshot(TaggedEC2Resource):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ec2_backend,
|
ec2_backend: Any,
|
||||||
snapshot_id,
|
snapshot_id: str,
|
||||||
volume,
|
volume: Any,
|
||||||
description,
|
description: str,
|
||||||
encrypted=False,
|
encrypted: bool = False,
|
||||||
owner_id=None,
|
owner_id: Optional[str] = None,
|
||||||
from_ami=None,
|
from_ami: Optional[str] = None,
|
||||||
):
|
):
|
||||||
self.id = snapshot_id
|
self.id = snapshot_id
|
||||||
self.volume = volume
|
self.volume = volume
|
||||||
self.description = description
|
self.description = description
|
||||||
self.start_time = utc_date_and_time()
|
self.start_time = utc_date_and_time()
|
||||||
self.create_volume_permission_groups = set()
|
self.create_volume_permission_groups: Set[str] = set()
|
||||||
self.create_volume_permission_userids = set()
|
self.create_volume_permission_userids: Set[str] = set()
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
self.status = "completed"
|
self.status = "completed"
|
||||||
self.encrypted = encrypted
|
self.encrypted = encrypted
|
||||||
self.owner_id = owner_id or ec2_backend.account_id
|
self.owner_id = owner_id or ec2_backend.account_id
|
||||||
self.from_ami = from_ami
|
self.from_ami = from_ami
|
||||||
|
|
||||||
def get_filter_value(self, filter_name):
|
def get_filter_value(
|
||||||
|
self, filter_name: str, method_name: Optional[str] = None
|
||||||
|
) -> Any:
|
||||||
if filter_name == "description":
|
if filter_name == "description":
|
||||||
return self.description
|
return self.description
|
||||||
elif filter_name == "snapshot-id":
|
elif filter_name == "snapshot-id":
|
||||||
@ -235,20 +258,20 @@ class Snapshot(TaggedEC2Resource):
|
|||||||
|
|
||||||
|
|
||||||
class EBSBackend:
|
class EBSBackend:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.volumes = {}
|
self.volumes: Dict[str, Volume] = {}
|
||||||
self.attachments = {}
|
self.attachments: Dict[str, VolumeAttachment] = {}
|
||||||
self.snapshots = {}
|
self.snapshots: Dict[str, Snapshot] = {}
|
||||||
|
|
||||||
def create_volume(
|
def create_volume(
|
||||||
self,
|
self,
|
||||||
size: str,
|
size: int,
|
||||||
zone_name: str,
|
zone_name: str,
|
||||||
snapshot_id: Optional[str] = None,
|
snapshot_id: Optional[str] = None,
|
||||||
encrypted: bool = False,
|
encrypted: bool = False,
|
||||||
kms_key_id: Optional[str] = None,
|
kms_key_id: Optional[str] = None,
|
||||||
volume_type: Optional[str] = None,
|
volume_type: Optional[str] = None,
|
||||||
iops: Optional[str] = None,
|
iops: Optional[int] = None,
|
||||||
) -> Volume:
|
) -> Volume:
|
||||||
if kms_key_id and not encrypted:
|
if kms_key_id and not encrypted:
|
||||||
raise InvalidParameterDependency("KmsKeyId", "Encrypted")
|
raise InvalidParameterDependency("KmsKeyId", "Encrypted")
|
||||||
@ -262,7 +285,7 @@ class EBSBackend:
|
|||||||
raise InvalidParameterDependency("VolumeType", "Iops")
|
raise InvalidParameterDependency("VolumeType", "Iops")
|
||||||
|
|
||||||
volume_id = random_volume_id()
|
volume_id = random_volume_id()
|
||||||
zone = self.get_zone_by_name(zone_name)
|
zone = self.get_zone_by_name(zone_name) # type: ignore[attr-defined]
|
||||||
if snapshot_id:
|
if snapshot_id:
|
||||||
snapshot = self.get_snapshot(snapshot_id)
|
snapshot = self.get_snapshot(snapshot_id)
|
||||||
if size is None:
|
if size is None:
|
||||||
@ -283,23 +306,32 @@ class EBSBackend:
|
|||||||
self.volumes[volume_id] = volume
|
self.volumes[volume_id] = volume
|
||||||
return volume
|
return volume
|
||||||
|
|
||||||
def describe_volumes(self, volume_ids=None, filters=None):
|
def describe_volumes(
|
||||||
matches = self.volumes.copy().values()
|
self, volume_ids: Optional[List[str]] = None, filters: Any = None
|
||||||
|
) -> List[Volume]:
|
||||||
|
matches = list(self.volumes.values())
|
||||||
if volume_ids:
|
if volume_ids:
|
||||||
matches = [vol for vol in matches if vol.id in volume_ids]
|
matches = [vol for vol in matches if vol.id in volume_ids]
|
||||||
if len(volume_ids) > len(matches):
|
if len(volume_ids) > len(matches):
|
||||||
unknown_ids = set(volume_ids) - set(matches)
|
unknown_ids = set(volume_ids) - set(matches) # type: ignore[arg-type]
|
||||||
raise InvalidVolumeIdError(unknown_ids)
|
raise InvalidVolumeIdError(unknown_ids)
|
||||||
if filters:
|
if filters:
|
||||||
matches = generic_filter(filters, matches)
|
matches = generic_filter(filters, matches)
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
def modify_volume(self, volume_id, target_size=None, target_volume_type=None):
|
def modify_volume(
|
||||||
|
self,
|
||||||
|
volume_id: str,
|
||||||
|
target_size: Optional[int] = None,
|
||||||
|
target_volume_type: Optional[str] = None,
|
||||||
|
) -> Volume:
|
||||||
volume = self.get_volume(volume_id)
|
volume = self.get_volume(volume_id)
|
||||||
volume.modify(target_size=target_size, target_volume_type=target_volume_type)
|
volume.modify(target_size=target_size, target_volume_type=target_volume_type)
|
||||||
return volume
|
return volume
|
||||||
|
|
||||||
def describe_volumes_modifications(self, volume_ids=None, filters=None):
|
def describe_volumes_modifications(
|
||||||
|
self, volume_ids: Optional[List[str]] = None, filters: Any = None
|
||||||
|
) -> List[VolumeModification]:
|
||||||
volumes = self.describe_volumes(volume_ids)
|
volumes = self.describe_volumes(volume_ids)
|
||||||
modifications = []
|
modifications = []
|
||||||
for volume in volumes:
|
for volume in volumes:
|
||||||
@ -308,13 +340,13 @@ class EBSBackend:
|
|||||||
modifications = generic_filter(filters, modifications)
|
modifications = generic_filter(filters, modifications)
|
||||||
return modifications
|
return modifications
|
||||||
|
|
||||||
def get_volume(self, volume_id):
|
def get_volume(self, volume_id: str) -> Volume:
|
||||||
volume = self.volumes.get(volume_id, None)
|
volume = self.volumes.get(volume_id, None)
|
||||||
if not volume:
|
if not volume:
|
||||||
raise InvalidVolumeIdError(volume_id)
|
raise InvalidVolumeIdError(volume_id)
|
||||||
return volume
|
return volume
|
||||||
|
|
||||||
def delete_volume(self, volume_id):
|
def delete_volume(self, volume_id: str) -> Volume:
|
||||||
if volume_id in self.volumes:
|
if volume_id in self.volumes:
|
||||||
volume = self.volumes[volume_id]
|
volume = self.volumes[volume_id]
|
||||||
if volume.attachment:
|
if volume.attachment:
|
||||||
@ -323,13 +355,17 @@ class EBSBackend:
|
|||||||
raise InvalidVolumeIdError(volume_id)
|
raise InvalidVolumeIdError(volume_id)
|
||||||
|
|
||||||
def attach_volume(
|
def attach_volume(
|
||||||
self, volume_id, instance_id, device_path, delete_on_termination=False
|
self,
|
||||||
):
|
volume_id: str,
|
||||||
|
instance_id: str,
|
||||||
|
device_path: str,
|
||||||
|
delete_on_termination: bool = False,
|
||||||
|
) -> Optional[VolumeAttachment]:
|
||||||
volume = self.get_volume(volume_id)
|
volume = self.get_volume(volume_id)
|
||||||
instance = self.get_instance(instance_id)
|
instance = self.get_instance(instance_id) # type: ignore[attr-defined]
|
||||||
|
|
||||||
if not volume or not instance:
|
if not volume or not instance:
|
||||||
return False
|
return None
|
||||||
|
|
||||||
volume.attachment = VolumeAttachment(volume, instance, device_path, "attached")
|
volume.attachment = VolumeAttachment(volume, instance, device_path, "attached")
|
||||||
# Modify instance to capture mount of block device.
|
# Modify instance to capture mount of block device.
|
||||||
@ -343,9 +379,11 @@ class EBSBackend:
|
|||||||
instance.block_device_mapping[device_path] = bdt
|
instance.block_device_mapping[device_path] = bdt
|
||||||
return volume.attachment
|
return volume.attachment
|
||||||
|
|
||||||
def detach_volume(self, volume_id, instance_id, device_path):
|
def detach_volume(
|
||||||
|
self, volume_id: str, instance_id: str, device_path: str
|
||||||
|
) -> VolumeAttachment:
|
||||||
volume = self.get_volume(volume_id)
|
volume = self.get_volume(volume_id)
|
||||||
instance = self.get_instance(instance_id)
|
instance = self.get_instance(instance_id) # type: ignore[attr-defined]
|
||||||
|
|
||||||
old_attachment = volume.attachment
|
old_attachment = volume.attachment
|
||||||
if not old_attachment:
|
if not old_attachment:
|
||||||
@ -376,15 +414,17 @@ class EBSBackend:
|
|||||||
params.append(owner_id)
|
params.append(owner_id)
|
||||||
if from_ami:
|
if from_ami:
|
||||||
params.append(from_ami)
|
params.append(from_ami)
|
||||||
snapshot = Snapshot(*params)
|
snapshot = Snapshot(*params) # type: ignore[arg-type]
|
||||||
self.snapshots[snapshot_id] = snapshot
|
self.snapshots[snapshot_id] = snapshot
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
def create_snapshots(self, instance_spec, description, tags):
|
def create_snapshots(
|
||||||
|
self, instance_spec: Dict[str, Any], description: str, tags: Dict[str, str]
|
||||||
|
) -> List[Snapshot]:
|
||||||
"""
|
"""
|
||||||
The CopyTagsFromSource-parameter is not yet implemented.
|
The CopyTagsFromSource-parameter is not yet implemented.
|
||||||
"""
|
"""
|
||||||
instance = self.get_instance(instance_spec["InstanceId"])
|
instance = self.get_instance(instance_spec["InstanceId"]) # type: ignore[attr-defined]
|
||||||
block_device_mappings = instance.block_device_mapping
|
block_device_mappings = instance.block_device_mapping
|
||||||
|
|
||||||
if str(instance_spec.get("ExcludeBootVolume", False)).lower() == "true":
|
if str(instance_spec.get("ExcludeBootVolume", False)).lower() == "true":
|
||||||
@ -403,8 +443,10 @@ class EBSBackend:
|
|||||||
snapshot.add_tags(tags)
|
snapshot.add_tags(tags)
|
||||||
return snapshots
|
return snapshots
|
||||||
|
|
||||||
def describe_snapshots(self, snapshot_ids=None, filters=None):
|
def describe_snapshots(
|
||||||
matches = self.snapshots.copy().values()
|
self, snapshot_ids: Optional[List[str]] = None, filters: Any = None
|
||||||
|
) -> List[Snapshot]:
|
||||||
|
matches = list(self.snapshots.values())
|
||||||
if snapshot_ids:
|
if snapshot_ids:
|
||||||
matches = [snap for snap in matches if snap.id in snapshot_ids]
|
matches = [snap for snap in matches if snap.id in snapshot_ids]
|
||||||
if len(snapshot_ids) > len(matches):
|
if len(snapshot_ids) > len(matches):
|
||||||
@ -413,12 +455,15 @@ class EBSBackend:
|
|||||||
matches = generic_filter(filters, matches)
|
matches = generic_filter(filters, matches)
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
def copy_snapshot(self, source_snapshot_id, source_region, description=None):
|
def copy_snapshot(
|
||||||
|
self, source_snapshot_id: str, source_region: str, description: str
|
||||||
|
) -> Snapshot:
|
||||||
from ..models import ec2_backends
|
from ..models import ec2_backends
|
||||||
|
|
||||||
source_snapshot = ec2_backends[self.account_id][
|
backend = ec2_backends[self.account_id][source_region] # type: ignore[attr-defined]
|
||||||
source_region
|
source_snapshot = backend.describe_snapshots(snapshot_ids=[source_snapshot_id])[
|
||||||
].describe_snapshots(snapshot_ids=[source_snapshot_id])[0]
|
0
|
||||||
|
]
|
||||||
snapshot_id = random_snapshot_id()
|
snapshot_id = random_snapshot_id()
|
||||||
snapshot = Snapshot(
|
snapshot = Snapshot(
|
||||||
self,
|
self,
|
||||||
@ -430,30 +475,32 @@ class EBSBackend:
|
|||||||
self.snapshots[snapshot_id] = snapshot
|
self.snapshots[snapshot_id] = snapshot
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
def get_snapshot(self, snapshot_id):
|
def get_snapshot(self, snapshot_id: str) -> Snapshot:
|
||||||
snapshot = self.snapshots.get(snapshot_id, None)
|
snapshot = self.snapshots.get(snapshot_id, None)
|
||||||
if not snapshot:
|
if not snapshot:
|
||||||
raise InvalidSnapshotIdError()
|
raise InvalidSnapshotIdError()
|
||||||
return snapshot
|
return snapshot
|
||||||
|
|
||||||
def delete_snapshot(self, snapshot_id):
|
def delete_snapshot(self, snapshot_id: str) -> Snapshot:
|
||||||
if snapshot_id in self.snapshots:
|
if snapshot_id in self.snapshots: # type: ignore[attr-defined]
|
||||||
snapshot = self.snapshots[snapshot_id]
|
snapshot = self.snapshots[snapshot_id]
|
||||||
if snapshot.from_ami and snapshot.from_ami in self.amis:
|
if snapshot.from_ami and snapshot.from_ami in self.amis: # type: ignore[attr-defined]
|
||||||
raise InvalidSnapshotInUse(snapshot_id, snapshot.from_ami)
|
raise InvalidSnapshotInUse(snapshot_id, snapshot.from_ami)
|
||||||
return self.snapshots.pop(snapshot_id)
|
return self.snapshots.pop(snapshot_id)
|
||||||
raise InvalidSnapshotIdError()
|
raise InvalidSnapshotIdError()
|
||||||
|
|
||||||
def get_create_volume_permission_groups(self, snapshot_id):
|
def get_create_volume_permission_groups(self, snapshot_id: str) -> Set[str]:
|
||||||
snapshot = self.get_snapshot(snapshot_id)
|
snapshot = self.get_snapshot(snapshot_id) # type: ignore[attr-defined]
|
||||||
return snapshot.create_volume_permission_groups
|
return snapshot.create_volume_permission_groups
|
||||||
|
|
||||||
def get_create_volume_permission_userids(self, snapshot_id):
|
def get_create_volume_permission_userids(self, snapshot_id: str) -> Set[str]:
|
||||||
snapshot = self.get_snapshot(snapshot_id)
|
snapshot = self.get_snapshot(snapshot_id) # type: ignore[attr-defined]
|
||||||
return snapshot.create_volume_permission_userids
|
return snapshot.create_volume_permission_userids
|
||||||
|
|
||||||
def add_create_volume_permission(self, snapshot_id, user_ids=None, groups=None):
|
def add_create_volume_permission(
|
||||||
snapshot = self.get_snapshot(snapshot_id)
|
self, snapshot_id: str, user_ids: List[str], groups: List[str]
|
||||||
|
) -> None:
|
||||||
|
snapshot = self.get_snapshot(snapshot_id) # type: ignore[attr-defined]
|
||||||
if user_ids:
|
if user_ids:
|
||||||
snapshot.create_volume_permission_userids.update(user_ids)
|
snapshot.create_volume_permission_userids.update(user_ids)
|
||||||
|
|
||||||
@ -462,27 +509,28 @@ class EBSBackend:
|
|||||||
else:
|
else:
|
||||||
snapshot.create_volume_permission_groups.update(groups)
|
snapshot.create_volume_permission_groups.update(groups)
|
||||||
|
|
||||||
return True
|
def remove_create_volume_permission(
|
||||||
|
self,
|
||||||
def remove_create_volume_permission(self, snapshot_id, user_ids=None, groups=None):
|
snapshot_id: str,
|
||||||
snapshot = self.get_snapshot(snapshot_id)
|
user_ids: Optional[List[str]] = None,
|
||||||
|
groups: Optional[Iterable[str]] = None,
|
||||||
|
) -> None:
|
||||||
|
snapshot = self.get_snapshot(snapshot_id) # type: ignore[attr-defined]
|
||||||
if user_ids:
|
if user_ids:
|
||||||
snapshot.create_volume_permission_userids.difference_update(user_ids)
|
snapshot.create_volume_permission_userids.difference_update(user_ids)
|
||||||
|
|
||||||
if groups and groups != ["all"]:
|
if groups and groups != ["all"]:
|
||||||
raise InvalidAMIAttributeItemValueError("UserGroup", groups)
|
raise InvalidAMIAttributeItemValueError("UserGroup", groups)
|
||||||
else:
|
else:
|
||||||
snapshot.create_volume_permission_groups.difference_update(groups)
|
snapshot.create_volume_permission_groups.difference_update(groups) # type: ignore[arg-type]
|
||||||
|
|
||||||
return True
|
def _get_default_encryption_key(self) -> str:
|
||||||
|
|
||||||
def _get_default_encryption_key(self):
|
|
||||||
# https://aws.amazon.com/kms/features/#AWS_Service_Integration
|
# https://aws.amazon.com/kms/features/#AWS_Service_Integration
|
||||||
# An AWS managed CMK is created automatically when you first create
|
# An AWS managed CMK is created automatically when you first create
|
||||||
# an encrypted resource using an AWS service integrated with KMS.
|
# an encrypted resource using an AWS service integrated with KMS.
|
||||||
from moto.kms import kms_backends
|
from moto.kms import kms_backends
|
||||||
|
|
||||||
kms = kms_backends[self.account_id][self.region_name]
|
kms = kms_backends[self.account_id][self.region_name] # type: ignore[attr-defined]
|
||||||
ebs_alias = "alias/aws/ebs"
|
ebs_alias = "alias/aws/ebs"
|
||||||
if not kms.alias_exists(ebs_alias):
|
if not kms.alias_exists(ebs_alias):
|
||||||
key = kms.create_key(
|
key = kms.create_key(
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from typing import Any, Dict, List, Optional
|
||||||
from moto.core import CloudFormationModel
|
from moto.core import CloudFormationModel
|
||||||
from .core import TaggedEC2Resource
|
from .core import TaggedEC2Resource
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
@ -16,7 +17,13 @@ from ..utils import (
|
|||||||
|
|
||||||
|
|
||||||
class ElasticAddress(TaggedEC2Resource, CloudFormationModel):
|
class ElasticAddress(TaggedEC2Resource, CloudFormationModel):
|
||||||
def __init__(self, ec2_backend, domain, address=None, tags=None):
|
def __init__(
|
||||||
|
self,
|
||||||
|
ec2_backend: Any,
|
||||||
|
domain: str,
|
||||||
|
address: Optional[str] = None,
|
||||||
|
tags: Optional[Dict[str, str]] = None,
|
||||||
|
):
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
if address:
|
if address:
|
||||||
self.public_ip = address
|
self.public_ip = address
|
||||||
@ -27,22 +34,27 @@ class ElasticAddress(TaggedEC2Resource, CloudFormationModel):
|
|||||||
self.domain = domain
|
self.domain = domain
|
||||||
self.instance = None
|
self.instance = None
|
||||||
self.eni = None
|
self.eni = None
|
||||||
self.association_id = None
|
self.association_id: Optional[str] = None
|
||||||
self.add_tags(tags or {})
|
self.add_tags(tags or {})
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_name_type():
|
def cloudformation_name_type() -> str:
|
||||||
return None
|
return ""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_type():
|
def cloudformation_type() -> str:
|
||||||
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-eip.html
|
||||||
return "AWS::EC2::EIP"
|
return "AWS::EC2::EIP"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(
|
def create_from_cloudformation_json( # type: ignore[misc]
|
||||||
cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
|
cls,
|
||||||
):
|
resource_name: str,
|
||||||
|
cloudformation_json: Any,
|
||||||
|
account_id: str,
|
||||||
|
region_name: str,
|
||||||
|
**kwargs: Any
|
||||||
|
) -> "ElasticAddress":
|
||||||
from ..models import ec2_backends
|
from ..models import ec2_backends
|
||||||
|
|
||||||
ec2_backend = ec2_backends[account_id][region_name]
|
ec2_backend = ec2_backends[account_id][region_name]
|
||||||
@ -64,21 +76,23 @@ class ElasticAddress(TaggedEC2Resource, CloudFormationModel):
|
|||||||
return eip
|
return eip
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self) -> str:
|
||||||
return self.public_ip
|
return self.public_ip
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_cfn_attr(cls, attr):
|
def has_cfn_attr(cls, attr: str) -> bool:
|
||||||
return attr in ["AllocationId"]
|
return attr in ["AllocationId"]
|
||||||
|
|
||||||
def get_cfn_attribute(self, attribute_name):
|
def get_cfn_attribute(self, attribute_name: str) -> Any:
|
||||||
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
||||||
|
|
||||||
if attribute_name == "AllocationId":
|
if attribute_name == "AllocationId":
|
||||||
return self.allocation_id
|
return self.allocation_id
|
||||||
raise UnformattedGetAttTemplateException()
|
raise UnformattedGetAttTemplateException()
|
||||||
|
|
||||||
def get_filter_value(self, filter_name):
|
def get_filter_value(
|
||||||
|
self, filter_name: str, method_name: Optional[str] = None
|
||||||
|
) -> Any:
|
||||||
if filter_name == "allocation-id":
|
if filter_name == "allocation-id":
|
||||||
return self.allocation_id
|
return self.allocation_id
|
||||||
elif filter_name == "association-id":
|
elif filter_name == "association-id":
|
||||||
@ -107,20 +121,27 @@ class ElasticAddress(TaggedEC2Resource, CloudFormationModel):
|
|||||||
|
|
||||||
|
|
||||||
class ElasticAddressBackend:
|
class ElasticAddressBackend:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.addresses = []
|
self.addresses: List[ElasticAddress] = []
|
||||||
|
|
||||||
def allocate_address(self, domain, address=None, tags=None):
|
def allocate_address(
|
||||||
|
self,
|
||||||
|
domain: str,
|
||||||
|
address: Optional[str] = None,
|
||||||
|
tags: Optional[Dict[str, str]] = None,
|
||||||
|
) -> ElasticAddress:
|
||||||
if domain not in ["standard", "vpc"]:
|
if domain not in ["standard", "vpc"]:
|
||||||
domain = "vpc"
|
domain = "vpc"
|
||||||
if address:
|
if address:
|
||||||
address = ElasticAddress(self, domain=domain, address=address, tags=tags)
|
ea = ElasticAddress(self, domain=domain, address=address, tags=tags)
|
||||||
else:
|
else:
|
||||||
address = ElasticAddress(self, domain=domain, tags=tags)
|
ea = ElasticAddress(self, domain=domain, tags=tags)
|
||||||
self.addresses.append(address)
|
self.addresses.append(ea)
|
||||||
return address
|
return ea
|
||||||
|
|
||||||
def address_by_ip(self, ips, fail_if_not_found=True):
|
def address_by_ip(
|
||||||
|
self, ips: List[str], fail_if_not_found: bool = True
|
||||||
|
) -> List[ElasticAddress]:
|
||||||
eips = [
|
eips = [
|
||||||
address for address in self.addresses.copy() if address.public_ip in ips
|
address for address in self.addresses.copy() if address.public_ip in ips
|
||||||
]
|
]
|
||||||
@ -131,7 +152,7 @@ class ElasticAddressBackend:
|
|||||||
|
|
||||||
return eips
|
return eips
|
||||||
|
|
||||||
def address_by_allocation(self, allocation_ids):
|
def address_by_allocation(self, allocation_ids: List[str]) -> List[ElasticAddress]:
|
||||||
eips = [
|
eips = [
|
||||||
address
|
address
|
||||||
for address in self.addresses
|
for address in self.addresses
|
||||||
@ -144,7 +165,9 @@ class ElasticAddressBackend:
|
|||||||
|
|
||||||
return eips
|
return eips
|
||||||
|
|
||||||
def address_by_association(self, association_ids):
|
def address_by_association(
|
||||||
|
self, association_ids: List[str]
|
||||||
|
) -> List[ElasticAddress]:
|
||||||
eips = [
|
eips = [
|
||||||
address
|
address
|
||||||
for address in self.addresses
|
for address in self.addresses
|
||||||
@ -159,12 +182,12 @@ class ElasticAddressBackend:
|
|||||||
|
|
||||||
def associate_address(
|
def associate_address(
|
||||||
self,
|
self,
|
||||||
instance=None,
|
instance: Any = None,
|
||||||
eni=None,
|
eni: Any = None,
|
||||||
address=None,
|
address: Optional[str] = None,
|
||||||
allocation_id=None,
|
allocation_id: Optional[str] = None,
|
||||||
reassociate=False,
|
reassociate: bool = False,
|
||||||
):
|
) -> ElasticAddress:
|
||||||
eips = []
|
eips = []
|
||||||
if address:
|
if address:
|
||||||
eips = self.address_by_ip([address])
|
eips = self.address_by_ip([address])
|
||||||
@ -192,24 +215,31 @@ class ElasticAddressBackend:
|
|||||||
|
|
||||||
raise ResourceAlreadyAssociatedError(eip.public_ip)
|
raise ResourceAlreadyAssociatedError(eip.public_ip)
|
||||||
|
|
||||||
def describe_addresses(self, allocation_ids=None, public_ips=None, filters=None):
|
def describe_addresses(
|
||||||
|
self,
|
||||||
|
allocation_ids: Optional[List[str]] = None,
|
||||||
|
public_ips: Optional[List[str]] = None,
|
||||||
|
filters: Any = None,
|
||||||
|
) -> List[ElasticAddress]:
|
||||||
matches = self.addresses.copy()
|
matches = self.addresses.copy()
|
||||||
if allocation_ids:
|
if allocation_ids:
|
||||||
matches = [addr for addr in matches if addr.allocation_id in allocation_ids]
|
matches = [addr for addr in matches if addr.allocation_id in allocation_ids]
|
||||||
if len(allocation_ids) > len(matches):
|
if len(allocation_ids) > len(matches):
|
||||||
unknown_ids = set(allocation_ids) - set(matches)
|
unknown_ids = set(allocation_ids) - set(matches) # type: ignore[arg-type]
|
||||||
raise InvalidAllocationIdError(unknown_ids)
|
raise InvalidAllocationIdError(unknown_ids)
|
||||||
if public_ips:
|
if public_ips:
|
||||||
matches = [addr for addr in matches if addr.public_ip in public_ips]
|
matches = [addr for addr in matches if addr.public_ip in public_ips]
|
||||||
if len(public_ips) > len(matches):
|
if len(public_ips) > len(matches):
|
||||||
unknown_ips = set(public_ips) - set(matches)
|
unknown_ips = set(public_ips) - set(matches) # type: ignore[arg-type]
|
||||||
raise InvalidAddressError(unknown_ips)
|
raise InvalidAddressError(unknown_ips)
|
||||||
if filters:
|
if filters:
|
||||||
matches = generic_filter(filters, matches)
|
matches = generic_filter(filters, matches)
|
||||||
|
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
def disassociate_address(self, address=None, association_id=None):
|
def disassociate_address(
|
||||||
|
self, address: Optional[str] = None, association_id: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
eips = []
|
eips = []
|
||||||
if address:
|
if address:
|
||||||
eips = self.address_by_ip([address])
|
eips = self.address_by_ip([address])
|
||||||
@ -225,9 +255,10 @@ class ElasticAddressBackend:
|
|||||||
|
|
||||||
eip.instance = None
|
eip.instance = None
|
||||||
eip.association_id = None
|
eip.association_id = None
|
||||||
return True
|
|
||||||
|
|
||||||
def release_address(self, address=None, allocation_id=None):
|
def release_address(
|
||||||
|
self, address: Optional[str] = None, allocation_id: Optional[str] = None
|
||||||
|
) -> None:
|
||||||
eips = []
|
eips = []
|
||||||
if address:
|
if address:
|
||||||
eips = self.address_by_ip([address])
|
eips = self.address_by_ip([address])
|
||||||
@ -238,4 +269,3 @@ class ElasticAddressBackend:
|
|||||||
self.disassociate_address(address=eip.public_ip)
|
self.disassociate_address(address=eip.public_ip)
|
||||||
eip.allocation_id = None
|
eip.allocation_id = None
|
||||||
self.addresses.remove(eip)
|
self.addresses.remove(eip)
|
||||||
return True
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
from typing import Any, Dict, Optional, List, Union
|
||||||
from moto.core import CloudFormationModel
|
from moto.core import CloudFormationModel
|
||||||
from ..exceptions import InvalidNetworkAttachmentIdError, InvalidNetworkInterfaceIdError
|
from ..exceptions import InvalidNetworkAttachmentIdError, InvalidNetworkInterfaceIdError
|
||||||
from .core import TaggedEC2Resource
|
from .core import TaggedEC2Resource
|
||||||
@ -15,16 +16,16 @@ from ..utils import (
|
|||||||
class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ec2_backend,
|
ec2_backend: Any,
|
||||||
subnet,
|
subnet: Any,
|
||||||
private_ip_address,
|
private_ip_address: Union[List[str], str],
|
||||||
private_ip_addresses=None,
|
private_ip_addresses: Optional[List[Dict[str, Any]]] = None,
|
||||||
device_index=0,
|
device_index: int = 0,
|
||||||
public_ip_auto_assign=False,
|
public_ip_auto_assign: bool = False,
|
||||||
group_ids=None,
|
group_ids: Optional[List[str]] = None,
|
||||||
description=None,
|
description: Optional[str] = None,
|
||||||
tags=None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
**kwargs,
|
**kwargs: Any,
|
||||||
):
|
):
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
self.id = random_eni_id()
|
self.id = random_eni_id()
|
||||||
@ -32,7 +33,7 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
if isinstance(private_ip_address, list) and private_ip_address:
|
if isinstance(private_ip_address, list) and private_ip_address:
|
||||||
private_ip_address = private_ip_address[0]
|
private_ip_address = private_ip_address[0]
|
||||||
self.private_ip_address = private_ip_address or None
|
self.private_ip_address = private_ip_address or None
|
||||||
self.private_ip_addresses = private_ip_addresses or []
|
self.private_ip_addresses: List[Dict[str, Any]] = private_ip_addresses or []
|
||||||
self.ipv6_addresses = kwargs.get("ipv6_addresses") or []
|
self.ipv6_addresses = kwargs.get("ipv6_addresses") or []
|
||||||
|
|
||||||
self.subnet = subnet
|
self.subnet = subnet
|
||||||
@ -45,7 +46,7 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
self.description = description
|
self.description = description
|
||||||
self.source_dest_check = True
|
self.source_dest_check = True
|
||||||
|
|
||||||
self.public_ip = None
|
self.public_ip: Optional[str] = None
|
||||||
self.public_ip_auto_assign = public_ip_auto_assign
|
self.public_ip_auto_assign = public_ip_auto_assign
|
||||||
self.start()
|
self.start()
|
||||||
self.add_tags(tags or {})
|
self.add_tags(tags or {})
|
||||||
@ -60,7 +61,7 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
association = list(self.subnet.ipv6_cidr_block_associations.values())[0]
|
association = list(self.subnet.ipv6_cidr_block_associations.values())[0]
|
||||||
subnet_ipv6_cidr_block = association.get("ipv6CidrBlock")
|
subnet_ipv6_cidr_block = association.get("ipv6CidrBlock")
|
||||||
if kwargs.get("ipv6_address_count"):
|
if kwargs.get("ipv6_address_count"):
|
||||||
while len(self.ipv6_addresses) < kwargs.get("ipv6_address_count"):
|
while len(self.ipv6_addresses) < kwargs["ipv6_address_count"]:
|
||||||
ip = random_private_ip(subnet_ipv6_cidr_block, ipv6=True)
|
ip = random_private_ip(subnet_ipv6_cidr_block, ipv6=True)
|
||||||
if ip not in self.ipv6_addresses:
|
if ip not in self.ipv6_addresses:
|
||||||
self.ipv6_addresses.append(ip)
|
self.ipv6_addresses.append(ip)
|
||||||
@ -80,9 +81,9 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
|
|
||||||
if not self.private_ip_address:
|
if not self.private_ip_address:
|
||||||
if self.private_ip_addresses:
|
if self.private_ip_addresses:
|
||||||
for ip in self.private_ip_addresses:
|
for private_ip in self.private_ip_addresses:
|
||||||
if isinstance(ip, dict) and ip.get("Primary"):
|
if isinstance(private_ip, dict) and private_ip.get("Primary"):
|
||||||
self.private_ip_address = ip.get("PrivateIpAddress")
|
self.private_ip_address = private_ip.get("PrivateIpAddress")
|
||||||
break
|
break
|
||||||
if not self.private_ip_addresses:
|
if not self.private_ip_addresses:
|
||||||
self.private_ip_address = random_private_ip(self.subnet.cidr_block)
|
self.private_ip_address = random_private_ip(self.subnet.cidr_block)
|
||||||
@ -133,12 +134,12 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
self._group_set.append(group)
|
self._group_set.append(group)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def owner_id(self):
|
def owner_id(self) -> str:
|
||||||
return self.ec2_backend.account_id
|
return self.ec2_backend.account_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def association(self):
|
def association(self) -> Dict[str, Any]: # type: ignore[misc]
|
||||||
association = {}
|
association: Dict[str, Any] = {}
|
||||||
if self.public_ip:
|
if self.public_ip:
|
||||||
eips = self.ec2_backend.address_by_ip(
|
eips = self.ec2_backend.address_by_ip(
|
||||||
[self.public_ip], fail_if_not_found=False
|
[self.public_ip], fail_if_not_found=False
|
||||||
@ -150,18 +151,23 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
return association
|
return association
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_name_type():
|
def cloudformation_name_type() -> str:
|
||||||
return None
|
return ""
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def cloudformation_type():
|
def cloudformation_type() -> str:
|
||||||
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html
|
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ec2-networkinterface.html
|
||||||
return "AWS::EC2::NetworkInterface"
|
return "AWS::EC2::NetworkInterface"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(
|
def create_from_cloudformation_json( # type: ignore[misc]
|
||||||
cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
|
cls,
|
||||||
):
|
resource_name: str,
|
||||||
|
cloudformation_json: Any,
|
||||||
|
account_id: str,
|
||||||
|
region_name: str,
|
||||||
|
**kwargs: Any,
|
||||||
|
) -> "NetworkInterface":
|
||||||
from ..models import ec2_backends
|
from ..models import ec2_backends
|
||||||
|
|
||||||
properties = cloudformation_json["Properties"]
|
properties = cloudformation_json["Properties"]
|
||||||
@ -186,14 +192,14 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
)
|
)
|
||||||
return network_interface
|
return network_interface
|
||||||
|
|
||||||
def stop(self):
|
def stop(self) -> None:
|
||||||
if self.public_ip_auto_assign:
|
if self.public_ip_auto_assign:
|
||||||
self.public_ip = None
|
self.public_ip = None
|
||||||
|
|
||||||
def start(self):
|
def start(self) -> None:
|
||||||
self.check_auto_public_ip()
|
self.check_auto_public_ip()
|
||||||
|
|
||||||
def check_auto_public_ip(self):
|
def check_auto_public_ip(self) -> None:
|
||||||
if (
|
if (
|
||||||
self.public_ip_auto_assign
|
self.public_ip_auto_assign
|
||||||
and str(self.public_ip_auto_assign).lower() == "true"
|
and str(self.public_ip_auto_assign).lower() == "true"
|
||||||
@ -201,17 +207,17 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
self.public_ip = random_public_ip()
|
self.public_ip = random_public_ip()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def group_set(self):
|
def group_set(self) -> Any: # type: ignore[misc]
|
||||||
if self.instance and self.instance.security_groups:
|
if self.instance and self.instance.security_groups:
|
||||||
return set(self._group_set) | set(self.instance.security_groups)
|
return set(self._group_set) | set(self.instance.security_groups)
|
||||||
else:
|
else:
|
||||||
return self._group_set
|
return self._group_set
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def has_cfn_attr(cls, attr):
|
def has_cfn_attr(cls, attr: str) -> bool:
|
||||||
return attr in ["PrimaryPrivateIpAddress", "SecondaryPrivateIpAddresses"]
|
return attr in ["PrimaryPrivateIpAddress", "SecondaryPrivateIpAddresses"]
|
||||||
|
|
||||||
def get_cfn_attribute(self, attribute_name):
|
def get_cfn_attribute(self, attribute_name: str) -> Any:
|
||||||
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
||||||
|
|
||||||
if attribute_name == "PrimaryPrivateIpAddress":
|
if attribute_name == "PrimaryPrivateIpAddress":
|
||||||
@ -223,10 +229,12 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
raise UnformattedGetAttTemplateException()
|
raise UnformattedGetAttTemplateException()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def physical_resource_id(self):
|
def physical_resource_id(self) -> str:
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
def get_filter_value(self, filter_name):
|
def get_filter_value(
|
||||||
|
self, filter_name: str, method_name: Optional[str] = None
|
||||||
|
) -> Any:
|
||||||
if filter_name == "network-interface-id":
|
if filter_name == "network-interface-id":
|
||||||
return self.id
|
return self.id
|
||||||
elif filter_name in ("addresses.private-ip-address", "private-ip-address"):
|
elif filter_name in ("addresses.private-ip-address", "private-ip-address"):
|
||||||
@ -242,7 +250,7 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
elif filter_name == "description":
|
elif filter_name == "description":
|
||||||
return self.description
|
return self.description
|
||||||
elif filter_name == "attachment.instance-id":
|
elif filter_name == "attachment.instance-id":
|
||||||
return self.instance.id if self.instance else None
|
return self.instance.id if self.instance else None # type: ignore[attr-defined]
|
||||||
elif filter_name == "attachment.instance-owner-id":
|
elif filter_name == "attachment.instance-owner-id":
|
||||||
return self.owner_id
|
return self.owner_id
|
||||||
else:
|
else:
|
||||||
@ -250,19 +258,19 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
|
|||||||
|
|
||||||
|
|
||||||
class NetworkInterfaceBackend:
|
class NetworkInterfaceBackend:
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
self.enis = {}
|
self.enis: Dict[str, NetworkInterface] = {}
|
||||||
|
|
||||||
def create_network_interface(
|
def create_network_interface(
|
||||||
self,
|
self,
|
||||||
subnet,
|
subnet: Any,
|
||||||
private_ip_address,
|
private_ip_address: Union[str, List[str]],
|
||||||
private_ip_addresses=None,
|
private_ip_addresses: Optional[List[Dict[str, Any]]] = None,
|
||||||
group_ids=None,
|
group_ids: Optional[List[str]] = None,
|
||||||
description=None,
|
description: Optional[str] = None,
|
||||||
tags=None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
**kwargs,
|
**kwargs: Any,
|
||||||
):
|
) -> NetworkInterface:
|
||||||
eni = NetworkInterface(
|
eni = NetworkInterface(
|
||||||
self,
|
self,
|
||||||
subnet,
|
subnet,
|
||||||
@ -276,23 +284,24 @@ class NetworkInterfaceBackend:
|
|||||||
self.enis[eni.id] = eni
|
self.enis[eni.id] = eni
|
||||||
return eni
|
return eni
|
||||||
|
|
||||||
def get_network_interface(self, eni_id):
|
def get_network_interface(self, eni_id: str) -> NetworkInterface:
|
||||||
for eni in self.enis.values():
|
for eni in self.enis.values():
|
||||||
if eni_id == eni.id:
|
if eni_id == eni.id:
|
||||||
return eni
|
return eni
|
||||||
raise InvalidNetworkInterfaceIdError(eni_id)
|
raise InvalidNetworkInterfaceIdError(eni_id)
|
||||||
|
|
||||||
def delete_network_interface(self, eni_id):
|
def delete_network_interface(self, eni_id: str) -> None:
|
||||||
deleted = self.enis.pop(eni_id, None)
|
deleted = self.enis.pop(eni_id, None)
|
||||||
if not deleted:
|
if not deleted:
|
||||||
raise InvalidNetworkInterfaceIdError(eni_id)
|
raise InvalidNetworkInterfaceIdError(eni_id)
|
||||||
return deleted
|
|
||||||
|
|
||||||
def describe_network_interfaces(self, filters=None):
|
def describe_network_interfaces(
|
||||||
|
self, filters: Any = None
|
||||||
|
) -> List[NetworkInterface]:
|
||||||
# Note: This is only used in EC2Backend#do_resources_exist
|
# Note: This is only used in EC2Backend#do_resources_exist
|
||||||
# Client-calls use #get_all_network_interfaces()
|
# Client-calls use #get_all_network_interfaces()
|
||||||
# We should probably merge these at some point..
|
# We should probably merge these at some point..
|
||||||
enis = self.enis.values()
|
enis = list(self.enis.values())
|
||||||
|
|
||||||
if filters:
|
if filters:
|
||||||
for (_filter, _filter_value) in filters.items():
|
for (_filter, _filter_value) in filters.items():
|
||||||
@ -302,33 +311,34 @@ class NetworkInterfaceBackend:
|
|||||||
eni for eni in enis if getattr(eni, _filter) in _filter_value
|
eni for eni in enis if getattr(eni, _filter) in _filter_value
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
self.raise_not_implemented_error(
|
self.raise_not_implemented_error( # type: ignore
|
||||||
f"The filter '{_filter}' for DescribeNetworkInterfaces"
|
f"The filter '{_filter}' for DescribeNetworkInterfaces"
|
||||||
)
|
)
|
||||||
return enis
|
return enis
|
||||||
|
|
||||||
def attach_network_interface(self, eni_id, instance_id, device_index):
|
def attach_network_interface(
|
||||||
|
self, eni_id: str, instance_id: str, device_index: int
|
||||||
|
) -> str:
|
||||||
eni = self.get_network_interface(eni_id)
|
eni = self.get_network_interface(eni_id)
|
||||||
instance = self.get_instance(instance_id)
|
instance = self.get_instance(instance_id) # type: ignore[attr-defined]
|
||||||
return instance.attach_eni(eni, device_index)
|
return instance.attach_eni(eni, device_index)
|
||||||
|
|
||||||
def detach_network_interface(self, attachment_id):
|
def detach_network_interface(self, attachment_id: str) -> None:
|
||||||
found_eni = None
|
|
||||||
|
|
||||||
for eni in self.enis.values():
|
for eni in self.enis.values():
|
||||||
if eni.attachment_id == attachment_id:
|
if eni.attachment_id == attachment_id:
|
||||||
found_eni = eni
|
eni.instance.detach_eni(eni) # type: ignore[attr-defined]
|
||||||
break
|
return
|
||||||
else:
|
raise InvalidNetworkAttachmentIdError(attachment_id)
|
||||||
raise InvalidNetworkAttachmentIdError(attachment_id)
|
|
||||||
|
|
||||||
found_eni.instance.detach_eni(found_eni)
|
|
||||||
|
|
||||||
def modify_network_interface_attribute(
|
def modify_network_interface_attribute(
|
||||||
self, eni_id, group_ids, source_dest_check=None, description=None
|
self,
|
||||||
):
|
eni_id: str,
|
||||||
|
group_ids: List[str],
|
||||||
|
source_dest_check: Optional[bool] = None,
|
||||||
|
description: Optional[str] = None,
|
||||||
|
) -> None:
|
||||||
eni = self.get_network_interface(eni_id)
|
eni = self.get_network_interface(eni_id)
|
||||||
groups = [self.get_security_group_from_id(group_id) for group_id in group_ids]
|
groups = [self.get_security_group_from_id(group_id) for group_id in group_ids] # type: ignore[attr-defined]
|
||||||
if groups:
|
if groups:
|
||||||
eni._group_set = groups
|
eni._group_set = groups
|
||||||
if source_dest_check in [True, False]:
|
if source_dest_check in [True, False]:
|
||||||
@ -337,8 +347,10 @@ class NetworkInterfaceBackend:
|
|||||||
if description:
|
if description:
|
||||||
eni.description = description
|
eni.description = description
|
||||||
|
|
||||||
def get_all_network_interfaces(self, eni_ids=None, filters=None):
|
def get_all_network_interfaces(
|
||||||
enis = self.enis.copy().values()
|
self, eni_ids: Optional[List[str]] = None, filters: Any = None
|
||||||
|
) -> List[NetworkInterface]:
|
||||||
|
enis = list(self.enis.values())
|
||||||
|
|
||||||
if eni_ids:
|
if eni_ids:
|
||||||
enis = [eni for eni in enis if eni.id in eni_ids]
|
enis = [eni for eni in enis if eni.id in eni_ids]
|
||||||
@ -350,7 +362,9 @@ class NetworkInterfaceBackend:
|
|||||||
|
|
||||||
return generic_filter(filters, enis)
|
return generic_filter(filters, enis)
|
||||||
|
|
||||||
def unassign_private_ip_addresses(self, eni_id=None, private_ip_address=None):
|
def unassign_private_ip_addresses(
|
||||||
|
self, eni_id: str, private_ip_address: Optional[List[str]] = None
|
||||||
|
) -> NetworkInterface:
|
||||||
eni = self.get_network_interface(eni_id)
|
eni = self.get_network_interface(eni_id)
|
||||||
if private_ip_address:
|
if private_ip_address:
|
||||||
for item in eni.private_ip_addresses.copy():
|
for item in eni.private_ip_addresses.copy():
|
||||||
@ -358,7 +372,9 @@ class NetworkInterfaceBackend:
|
|||||||
eni.private_ip_addresses.remove(item)
|
eni.private_ip_addresses.remove(item)
|
||||||
return eni
|
return eni
|
||||||
|
|
||||||
def assign_private_ip_addresses(self, eni_id=None, secondary_ips_count=None):
|
def assign_private_ip_addresses(
|
||||||
|
self, eni_id: str, secondary_ips_count: Optional[int] = None
|
||||||
|
) -> NetworkInterface:
|
||||||
eni = self.get_network_interface(eni_id)
|
eni = self.get_network_interface(eni_id)
|
||||||
eni_assigned_ips = [
|
eni_assigned_ips = [
|
||||||
item.get("PrivateIpAddress") for item in eni.private_ip_addresses
|
item.get("PrivateIpAddress") for item in eni.private_ip_addresses
|
||||||
@ -372,7 +388,12 @@ class NetworkInterfaceBackend:
|
|||||||
secondary_ips_count -= 1
|
secondary_ips_count -= 1
|
||||||
return eni
|
return eni
|
||||||
|
|
||||||
def assign_ipv6_addresses(self, eni_id=None, ipv6_addresses=None, ipv6_count=None):
|
def assign_ipv6_addresses(
|
||||||
|
self,
|
||||||
|
eni_id: str,
|
||||||
|
ipv6_addresses: Optional[List[str]] = None,
|
||||||
|
ipv6_count: Optional[int] = None,
|
||||||
|
) -> NetworkInterface:
|
||||||
eni = self.get_network_interface(eni_id)
|
eni = self.get_network_interface(eni_id)
|
||||||
if ipv6_addresses:
|
if ipv6_addresses:
|
||||||
eni.ipv6_addresses.extend(ipv6_addresses)
|
eni.ipv6_addresses.extend(ipv6_addresses)
|
||||||
@ -386,10 +407,12 @@ class NetworkInterfaceBackend:
|
|||||||
ipv6_count -= 1
|
ipv6_count -= 1
|
||||||
return eni
|
return eni
|
||||||
|
|
||||||
def unassign_ipv6_addresses(self, eni_id=None, ips=None):
|
def unassign_ipv6_addresses(
|
||||||
|
self, eni_id: str, ips: Optional[List[str]] = None
|
||||||
|
) -> NetworkInterface:
|
||||||
eni = self.get_network_interface(eni_id)
|
eni = self.get_network_interface(eni_id)
|
||||||
if ips:
|
if ips:
|
||||||
for ip in eni.ipv6_addresses.copy():
|
for ip in eni.ipv6_addresses.copy():
|
||||||
if ip in ips:
|
if ip in ips:
|
||||||
eni.ipv6_addresses.remove(ip)
|
eni.ipv6_addresses.remove(ip)
|
||||||
return eni, ips
|
return eni
|
||||||
|
@ -2,7 +2,7 @@ import copy
|
|||||||
import itertools
|
import itertools
|
||||||
import json
|
import json
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Optional
|
from typing import Any, Dict, Optional
|
||||||
from moto.core import CloudFormationModel
|
from moto.core import CloudFormationModel
|
||||||
from moto.core.utils import aws_api_matches
|
from moto.core.utils import aws_api_matches
|
||||||
from ..exceptions import (
|
from ..exceptions import (
|
||||||
@ -110,13 +110,13 @@ class SecurityRule(object):
|
|||||||
class SecurityGroup(TaggedEC2Resource, CloudFormationModel):
|
class SecurityGroup(TaggedEC2Resource, CloudFormationModel):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
ec2_backend,
|
ec2_backend: Any,
|
||||||
group_id,
|
group_id: str,
|
||||||
name,
|
name: str,
|
||||||
description,
|
description: str,
|
||||||
vpc_id=None,
|
vpc_id: Optional[str] = None,
|
||||||
tags=None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
is_default=None,
|
is_default: Optional[bool] = None,
|
||||||
):
|
):
|
||||||
self.ec2_backend = ec2_backend
|
self.ec2_backend = ec2_backend
|
||||||
self.id = group_id
|
self.id = group_id
|
||||||
|
@ -39,9 +39,9 @@ class DHCPOptions(EC2BaseResponse):
|
|||||||
|
|
||||||
def delete_dhcp_options(self):
|
def delete_dhcp_options(self):
|
||||||
dhcp_opt_id = self._get_param("DhcpOptionsId")
|
dhcp_opt_id = self._get_param("DhcpOptionsId")
|
||||||
delete_status = self.ec2_backend.delete_dhcp_options_set(dhcp_opt_id)
|
self.ec2_backend.delete_dhcp_options_set(dhcp_opt_id)
|
||||||
template = self.response_template(DELETE_DHCP_OPTIONS_RESPONSE)
|
template = self.response_template(DELETE_DHCP_OPTIONS_RESPONSE)
|
||||||
return template.render(delete_status=delete_status)
|
return template.render(delete_status="true")
|
||||||
|
|
||||||
def describe_dhcp_options(self):
|
def describe_dhcp_options(self):
|
||||||
dhcp_opt_ids = self._get_multi_param("DhcpOptionsId")
|
dhcp_opt_ids = self._get_multi_param("DhcpOptionsId")
|
||||||
|
@ -133,9 +133,9 @@ class ElasticNetworkInterfaces(EC2BaseResponse):
|
|||||||
def unassign_ipv6_addresses(self):
|
def unassign_ipv6_addresses(self):
|
||||||
eni_id = self._get_param("NetworkInterfaceId")
|
eni_id = self._get_param("NetworkInterfaceId")
|
||||||
ips = self._get_multi_param("Ipv6Addresses")
|
ips = self._get_multi_param("Ipv6Addresses")
|
||||||
eni, unassigned_ips = self.ec2_backend.unassign_ipv6_addresses(eni_id, ips)
|
eni = self.ec2_backend.unassign_ipv6_addresses(eni_id, ips)
|
||||||
template = self.response_template(UNASSIGN_IPV6_ADDRESSES)
|
template = self.response_template(UNASSIGN_IPV6_ADDRESSES)
|
||||||
return template.render(eni=eni, unassigned_ips=unassigned_ips)
|
return template.render(eni=eni, unassigned_ips=ips)
|
||||||
|
|
||||||
|
|
||||||
ASSIGN_PRIVATE_IP_ADDRESSES = """<AssignPrivateIpAddressesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
|
ASSIGN_PRIVATE_IP_ADDRESSES = """<AssignPrivateIpAddressesResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
|
||||||
|
@ -100,7 +100,7 @@ def random_flow_log_id():
|
|||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["flow-logs"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["flow-logs"])
|
||||||
|
|
||||||
|
|
||||||
def random_snapshot_id():
|
def random_snapshot_id() -> str:
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["snapshot"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["snapshot"])
|
||||||
|
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ def random_customer_gateway_id() -> str:
|
|||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["customer-gateway"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["customer-gateway"])
|
||||||
|
|
||||||
|
|
||||||
def random_volume_id():
|
def random_volume_id() -> str:
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["volume"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["volume"])
|
||||||
|
|
||||||
|
|
||||||
@ -170,7 +170,7 @@ def random_vpc_peering_connection_id():
|
|||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-peering-connection"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-peering-connection"])
|
||||||
|
|
||||||
|
|
||||||
def random_eip_association_id():
|
def random_eip_association_id() -> str:
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-elastic-ip-association"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-elastic-ip-association"])
|
||||||
|
|
||||||
|
|
||||||
@ -188,23 +188,23 @@ def random_route_table_id():
|
|||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["route-table"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["route-table"])
|
||||||
|
|
||||||
|
|
||||||
def random_eip_allocation_id():
|
def random_eip_allocation_id() -> str:
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-elastic-ip"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-elastic-ip"])
|
||||||
|
|
||||||
|
|
||||||
def random_dhcp_option_id():
|
def random_dhcp_option_id() -> str:
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["dhcp-options"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["dhcp-options"])
|
||||||
|
|
||||||
|
|
||||||
def random_eni_id():
|
def random_eni_id() -> str:
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["network-interface"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["network-interface"])
|
||||||
|
|
||||||
|
|
||||||
def random_eni_attach_id():
|
def random_eni_attach_id() -> str:
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["network-interface-attachment"])
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["network-interface-attachment"])
|
||||||
|
|
||||||
|
|
||||||
def random_nat_gateway_id():
|
def random_nat_gateway_id() -> str:
|
||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["nat-gateway"], size=17)
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["nat-gateway"], size=17)
|
||||||
|
|
||||||
|
|
||||||
@ -236,7 +236,7 @@ def random_carrier_gateway_id() -> str:
|
|||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["carrier-gateway"], size=17)
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["carrier-gateway"], size=17)
|
||||||
|
|
||||||
|
|
||||||
def random_public_ip():
|
def random_public_ip() -> str:
|
||||||
return f"54.214.{random.choice(range(255))}.{random.choice(range(255))}"
|
return f"54.214.{random.choice(range(255))}.{random.choice(range(255))}"
|
||||||
|
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ def random_dedicated_host_id():
|
|||||||
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["dedicated_host"], size=17)
|
return random_id(prefix=EC2_RESOURCE_TO_PREFIX["dedicated_host"], size=17)
|
||||||
|
|
||||||
|
|
||||||
def random_private_ip(cidr=None, ipv6=False):
|
def random_private_ip(cidr: str = None, ipv6: bool = False) -> str:
|
||||||
# prefix - ula.prefixlen : get number of remaing length for the IP.
|
# prefix - ula.prefixlen : get number of remaing length for the IP.
|
||||||
# prefix will be 32 for IPv4 and 128 for IPv6.
|
# prefix will be 32 for IPv4 and 128 for IPv6.
|
||||||
# random.getrandbits() will generate remaining bits for IPv6 or Ipv4 in decimal format
|
# random.getrandbits() will generate remaining bits for IPv6 or Ipv4 in decimal format
|
||||||
@ -259,16 +259,16 @@ def random_private_ip(cidr=None, ipv6=False):
|
|||||||
return f"10.{random.choice(range(255))}.{random.choice(range(255))}.{random.choice(range(255))}"
|
return f"10.{random.choice(range(255))}.{random.choice(range(255))}.{random.choice(range(255))}"
|
||||||
|
|
||||||
|
|
||||||
def random_ip():
|
def random_ip() -> str:
|
||||||
return f"127.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
|
return f"127.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
|
||||||
|
|
||||||
|
|
||||||
def generate_dns_from_ip(ip, dns_type="internal"):
|
def generate_dns_from_ip(ip: Any, dns_type: str = "internal") -> str:
|
||||||
splits = ip.split("/")[0].split(".") if "/" in ip else ip.split(".")
|
splits = ip.split("/")[0].split(".") if "/" in ip else ip.split(".")
|
||||||
return f"ip-{splits[0]}-{splits[1]}-{splits[2]}-{splits[3]}.ec2.{dns_type}"
|
return f"ip-{splits[0]}-{splits[1]}-{splits[2]}-{splits[3]}.ec2.{dns_type}"
|
||||||
|
|
||||||
|
|
||||||
def random_mac_address():
|
def random_mac_address() -> str:
|
||||||
return f"02:00:00:{random.randint(0, 255)}02x:{random.randint(0, 255)}02x:{random.randint(0, 255)}02x"
|
return f"02:00:00:{random.randint(0, 255)}02x:{random.randint(0, 255)}02x:{random.randint(0, 255)}02x"
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ class BlockDeviceType(object):
|
|||||||
status: Optional[str] = None,
|
status: Optional[str] = None,
|
||||||
attach_time: Optional[str] = None,
|
attach_time: Optional[str] = None,
|
||||||
delete_on_termination: bool = False,
|
delete_on_termination: bool = False,
|
||||||
size: Optional[str] = None,
|
size: Optional[int] = None,
|
||||||
volume_type: Optional[str] = None,
|
volume_type: Optional[str] = None,
|
||||||
iops: Optional[str] = None,
|
iops: Optional[str] = None,
|
||||||
encrypted: Optional[str] = None,
|
encrypted: Optional[str] = None,
|
||||||
|
@ -229,7 +229,7 @@ disable = W,C,R,E
|
|||||||
enable = anomalous-backslash-in-string, arguments-renamed, dangerous-default-value, deprecated-module, function-redefined, import-self, redefined-builtin, redefined-outer-name, reimported, pointless-statement, super-with-arguments, unused-argument, unused-import, unused-variable, useless-else-on-loop, wildcard-import
|
enable = anomalous-backslash-in-string, arguments-renamed, dangerous-default-value, deprecated-module, function-redefined, import-self, redefined-builtin, redefined-outer-name, reimported, pointless-statement, super-with-arguments, unused-argument, unused-import, unused-variable, useless-else-on-loop, wildcard-import
|
||||||
|
|
||||||
[mypy]
|
[mypy]
|
||||||
files= moto/a*,moto/b*,moto/c*,moto/d*,moto/ebs/,moto/ec2/models/a*,moto/ec2/models/c*,moto/moto_api
|
files= moto/a*,moto/b*,moto/c*,moto/d*,moto/ebs/,moto/ec2/models/a*,moto/ec2/models/c*,moto/ec2/models/d*,moto/ec2/models/e*,moto/moto_api
|
||||||
show_column_numbers=True
|
show_column_numbers=True
|
||||||
show_error_codes = True
|
show_error_codes = True
|
||||||
disable_error_code=abstract
|
disable_error_code=abstract
|
||||||
|
Loading…
Reference in New Issue
Block a user