diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index ce68497a3..48607b770 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -398,6 +398,14 @@ class InvalidParameterValueErrorUnknownAttribute(EC2ClientError): ) +class InvalidGatewayIDError(EC2ClientError): + def __init__(self, gateway_id): + super(InvalidGatewayIDError, self).__init__( + "InvalidGatewayID.NotFound", + "The eigw ID '{0}' does not exist".format(gateway_id), + ) + + class InvalidInternetGatewayIdError(EC2ClientError): def __init__(self, internet_gateway_id): super(InvalidInternetGatewayIdError, self).__init__( diff --git a/moto/ec2/models.py b/moto/ec2/models.py index f68f3b575..f674d91c9 100644 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -111,6 +111,7 @@ from .exceptions import ( InvalidAssociationIDIamProfileAssociationError, InvalidVpcEndPointIdError, InvalidTaggableResourceType, + InvalidGatewayIDError, ) from .utils import ( EC2_RESOURCE_TO_PREFIX, @@ -123,6 +124,7 @@ from .utils import ( random_eni_id, random_instance_id, random_internet_gateway_id, + random_egress_only_internet_gateway_id, random_ip, random_ipv6_cidr, random_transit_gateway_attachment_id, @@ -4792,6 +4794,52 @@ class InternetGatewayBackend(object): return self.describe_internet_gateways(internet_gateway_ids=igw_ids)[0] +class EgressOnlyInternetGateway(TaggedEC2Resource): + def __init__(self, ec2_backend, vpc_id, tags=None): + self.id = random_egress_only_internet_gateway_id() + self.ec2_backend = ec2_backend + self.vpc_id = vpc_id + self.state = "attached" + self.add_tags(tags or {}) + + @property + def physical_resource_id(self): + return self.id + + +class EgressOnlyInternetGatewayBackend(object): + def __init__(self): + self.egress_only_internet_gateway_backend = {} + super(EgressOnlyInternetGatewayBackend, self).__init__() + + def create_egress_only_internet_gateway(self, vpc_id, tags=None): + vpc = self.get_vpc(vpc_id) + if not vpc: + raise InvalidVPCIdError(vpc_id) + egress_only_igw = EgressOnlyInternetGateway(self, vpc_id, tags) + self.egress_only_internet_gateway_backend[egress_only_igw.id] = egress_only_igw + return egress_only_igw + + def describe_egress_only_internet_gateways(self, ids=None, filters=None): + # TODO: support filtering based on tag + egress_only_igws = list(self.egress_only_internet_gateway_backend.values()) + + if ids: + egress_only_igws = [ + egress_only_igw + for egress_only_igw in egress_only_igws + if egress_only_igw.id in ids + ] + return egress_only_igws + + def delete_egress_only_internet_gateway(self, id): + egress_only_igw = self.egress_only_internet_gateway_backend.get(id) + if not egress_only_igw: + raise InvalidGatewayIDError(id) + if egress_only_igw: + self.egress_only_internet_gateway_backend.pop(id) + + class VPCGatewayAttachment(CloudFormationModel): def __init__(self, gateway_id, vpc_id): self.gateway_id = gateway_id @@ -7308,6 +7356,7 @@ class EC2Backend( RouteTableBackend, RouteBackend, InternetGatewayBackend, + EgressOnlyInternetGatewayBackend, VPCGatewayAttachmentBackend, SpotFleetBackend, SpotRequestBackend, diff --git a/moto/ec2/responses/__init__.py b/moto/ec2/responses/__init__.py index 0f8e6d8ff..5368062b8 100644 --- a/moto/ec2/responses/__init__.py +++ b/moto/ec2/responses/__init__.py @@ -12,6 +12,7 @@ from .elastic_network_interfaces import ElasticNetworkInterfaces from .general import General from .instances import InstanceResponse from .internet_gateways import InternetGateways +from .egress_only_internet_gateways import EgressOnlyInternetGateway from .ip_addresses import IPAddresses from .key_pairs import KeyPairs from .launch_templates import LaunchTemplates @@ -53,6 +54,7 @@ class EC2Response( General, InstanceResponse, InternetGateways, + EgressOnlyInternetGateway, IPAddresses, KeyPairs, LaunchTemplates, diff --git a/moto/ec2/responses/egress_only_internet_gateways.py b/moto/ec2/responses/egress_only_internet_gateways.py new file mode 100644 index 000000000..f0615f5e6 --- /dev/null +++ b/moto/ec2/responses/egress_only_internet_gateways.py @@ -0,0 +1,83 @@ +from moto.core.responses import BaseResponse +from moto.ec2.utils import filters_from_querystring, add_tag_specification + + +class EgressOnlyInternetGateway(BaseResponse): + def create_egress_only_internet_gateway(self): + vpc_id = self._get_param("VpcId") + tags = self._get_multi_param("TagSpecification") + tags = add_tag_specification(tags) + + egress_only_igw = self.ec2_backend.create_egress_only_internet_gateway( + vpc_id=vpc_id, tags=tags + ) + template = self.response_template(CREATE_EGRESS_ONLY_IGW_RESPONSE) + return template.render(egress_only_igw=egress_only_igw) + + def describe_egress_only_internet_gateways(self): + egress_only_igw_ids = self._get_multi_param("EgressOnlyInternetGatewayId") + filters = filters_from_querystring(self.querystring) + egress_only_igws = self.ec2_backend.describe_egress_only_internet_gateways( + egress_only_igw_ids, filters, + ) + template = self.response_template(DESCRIBE_EGRESS_ONLY_IGW_RESPONSE) + return template.render(egress_only_igws=egress_only_igws) + + def delete_egress_only_internet_gateway(self): + egress_only_igw_id = self._get_param("EgressOnlyInternetGatewayId") + self.ec2_backend.delete_egress_only_internet_gateway(id=egress_only_igw_id) + template = self.response_template(DELETE_EGRESS_ONLY_IGW_RESPONSE) + return template.render() + + +CREATE_EGRESS_ONLY_IGW_RESPONSE = """ + c617595f-6c29-4a00-a941-example + + + + {{ egress_only_igw.state }} + {{ egress_only_igw.vpc_id }} + + + {{ egress_only_igw.id }} + + {% for tag in egress_only_igw.get_tags() %} + + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + + +""" + +DESCRIBE_EGRESS_ONLY_IGW_RESPONSE = """ + ec441b4c-357f-4483-b4a7-example + + {% for egress_only_igw in egress_only_igws %} + + + + {{ egress_only_igw.state }} + {{ egress_only_igw.vpc_id }} + + + {{ egress_only_igw.id }} + + {% for tag in egress_only_igw.get_tags() %} + + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + + + {% endfor %} + +""" + +DELETE_EGRESS_ONLY_IGW_RESPONSE = """ + 59dbff89-35bd-4eac-99ed-be587EXAMPLE + true +""" diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index b7b5c5654..328b77029 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -23,6 +23,7 @@ EC2_RESOURCE_TO_PREFIX = { "image": "ami", "instance": "i", "internet-gateway": "igw", + "egress-only-internet-gateway": "eigw", "launch-template": "lt", "nat-gateway": "nat", "network-acl": "acl", @@ -153,6 +154,12 @@ def random_internet_gateway_id(): return random_id(prefix=EC2_RESOURCE_TO_PREFIX["internet-gateway"]) +def random_egress_only_internet_gateway_id(): + return random_id( + prefix=EC2_RESOURCE_TO_PREFIX["egress-only-internet-gateway"], size=17 + ) + + def random_route_table_id(): return random_id(prefix=EC2_RESOURCE_TO_PREFIX["route-table"]) diff --git a/tests/terraform-tests.success.txt b/tests/terraform-tests.success.txt index e1da881ee..ccbc00f2b 100644 --- a/tests/terraform-tests.success.txt +++ b/tests/terraform-tests.success.txt @@ -86,4 +86,5 @@ TestAccAWSRouteTable_IPv4_To_NatGateway TestAccAWSRouteTable_IPv4_To_TransitGateway TestAccAWSRouteTable_disappears TestAccAWSRouteTable_basic -TestAccAwsEc2ManagedPrefixList \ No newline at end of file +TestAccAwsEc2ManagedPrefixList +TestAccAWSEgressOnlyInternetGateway \ No newline at end of file