EC2 - VpcEndpointServiceConfiguration (#4815)

This commit is contained in:
Bert Blommers 2022-01-30 22:00:26 -01:00 committed by GitHub
parent f554ad3997
commit 2fd6f34060
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 907 additions and 76 deletions

View File

@ -268,6 +268,8 @@ jobs:
cd ..
- name: "Create report"
run: |
ls -la
cp server_output.log moto-terraform-tests/build/server_output.log
cd moto-terraform-tests
bin/create-report
bin/create-report-cli

View File

@ -1280,7 +1280,7 @@
## ec2
<details>
<summary>34% implemented</summary>
<summary>35% implemented</summary>
- [ ] accept_reserved_instances_exchange_quote
- [ ] accept_transit_gateway_multicast_domain_associations
@ -1394,7 +1394,7 @@
- [X] create_vpc
- [X] create_vpc_endpoint
- [ ] create_vpc_endpoint_connection_notification
- [ ] create_vpc_endpoint_service_configuration
- [X] create_vpc_endpoint_service_configuration
- [X] create_vpc_peering_connection
- [X] create_vpn_connection
- [ ] create_vpn_connection_route
@ -1455,7 +1455,7 @@
- [X] delete_volume
- [X] delete_vpc
- [ ] delete_vpc_endpoint_connection_notifications
- [ ] delete_vpc_endpoint_service_configurations
- [X] delete_vpc_endpoint_service_configurations
- [X] delete_vpc_endpoints
- [X] delete_vpc_peering_connection
- [X] delete_vpn_connection
@ -1593,8 +1593,8 @@
- [ ] describe_vpc_classic_link_dns_support
- [ ] describe_vpc_endpoint_connection_notifications
- [ ] describe_vpc_endpoint_connections
- [ ] describe_vpc_endpoint_service_configurations
- [ ] describe_vpc_endpoint_service_permissions
- [X] describe_vpc_endpoint_service_configurations
- [X] describe_vpc_endpoint_service_permissions
- [X] describe_vpc_endpoint_services
- [X] describe_vpc_endpoints
- [X] describe_vpc_peering_connections
@ -1728,9 +1728,9 @@
- [X] modify_vpc_attribute
- [ ] modify_vpc_endpoint
- [ ] modify_vpc_endpoint_connection_notification
- [ ] modify_vpc_endpoint_service_configuration
- [X] modify_vpc_endpoint_service_configuration
- [ ] modify_vpc_endpoint_service_payer_responsibility
- [ ] modify_vpc_endpoint_service_permissions
- [X] modify_vpc_endpoint_service_permissions
- [X] modify_vpc_peering_connection_options
- [X] modify_vpc_tenancy
- [ ] modify_vpn_connection

View File

@ -1,6 +1,7 @@
include README.md LICENSE AUTHORS.md
include requirements.txt requirements-dev.txt tox.ini
include moto/config/resources/aws_managed_rules.json
include moto/ec2/_models/*.py
include moto/ec2/resources/instance_types.json
include moto/ec2/resources/instance_type_offerings/*/*.json
include moto/ec2/resources/amis.json

View File

@ -141,7 +141,7 @@ ec2
- [X] create_vpc
- [X] create_vpc_endpoint
- [ ] create_vpc_endpoint_connection_notification
- [ ] create_vpc_endpoint_service_configuration
- [X] create_vpc_endpoint_service_configuration
- [X] create_vpc_peering_connection
- [X] create_vpn_connection
- [ ] create_vpn_connection_route
@ -202,7 +202,7 @@ ec2
- [X] delete_volume
- [X] delete_vpc
- [ ] delete_vpc_endpoint_connection_notifications
- [ ] delete_vpc_endpoint_service_configurations
- [X] delete_vpc_endpoint_service_configurations
- [X] delete_vpc_endpoints
- [X] delete_vpc_peering_connection
- [X] delete_vpn_connection
@ -344,8 +344,16 @@ ec2
- [ ] describe_vpc_classic_link_dns_support
- [ ] describe_vpc_endpoint_connection_notifications
- [ ] describe_vpc_endpoint_connections
- [ ] describe_vpc_endpoint_service_configurations
- [ ] describe_vpc_endpoint_service_permissions
- [X] describe_vpc_endpoint_service_configurations
The Filters, MaxResults, NextToken parameters are not yet implemented
- [X] describe_vpc_endpoint_service_permissions
The Filters, MaxResults, NextToken parameters are not yet implemented
- [X] describe_vpc_endpoint_services
Return info on services to which you can create a VPC endpoint.
@ -490,9 +498,13 @@ ec2
- [X] modify_vpc_attribute
- [ ] modify_vpc_endpoint
- [ ] modify_vpc_endpoint_connection_notification
- [ ] modify_vpc_endpoint_service_configuration
- [X] modify_vpc_endpoint_service_configuration
The following parameters are not yet implemented: RemovePrivateDnsName, AddNetworkLoadBalancerArns, RemoveNetworkLoadBalancerArns, AddGatewayLoadBalancerArns, RemoveGatewayLoadBalancerArns
- [ ] modify_vpc_endpoint_service_payer_responsibility
- [ ] modify_vpc_endpoint_service_permissions
- [X] modify_vpc_endpoint_service_permissions
- [X] modify_vpc_peering_connection_options
- [X] modify_vpc_tenancy
- [ ] modify_vpn_connection

39
moto/ec2/_models/core.py Normal file
View File

@ -0,0 +1,39 @@
from moto.core.models import BaseModel
from ..exceptions import FilterNotImplementedError
class TaggedEC2Resource(BaseModel):
def get_tags(self, *args, **kwargs):
tags = []
if self.id:
tags = self.ec2_backend.describe_tags(filters={"resource-id": [self.id]})
return tags
def add_tag(self, key, value):
self.ec2_backend.create_tags([self.id], {key: value})
def add_tags(self, tag_map):
for key, value in tag_map.items():
self.ec2_backend.create_tags([self.id], {key: value})
def get_filter_value(self, filter_name, method_name=None):
tags = self.get_tags()
if filter_name.startswith("tag:"):
tagname = filter_name.replace("tag:", "", 1)
for tag in tags:
if tag["key"] == tagname:
return tag["value"]
return None
elif filter_name == "tag-key":
return [tag["key"] for tag in tags]
elif filter_name == "tag-value":
return [tag["value"] for tag in tags]
value = getattr(self, filter_name.lower().replace("-", "_"), None)
if value is not None:
return value
raise FilterNotImplementedError(filter_name, method_name)

View File

@ -0,0 +1,132 @@
from moto.core.models import CloudFormationModel
from moto.core.utils import get_random_hex
from .core import TaggedEC2Resource
from ..exceptions import UnknownVpcEndpointService
class VPCServiceConfiguration(TaggedEC2Resource, CloudFormationModel):
def __init__(
self, load_balancers, region, acceptance_required, private_dns_name, ec2_backend
):
self.id = f"vpce-svc-{get_random_hex(length=8)}"
self.service_name = f"com.amazonaws.vpce.{region}.{self.id}"
self.service_state = "Available"
self.availability_zones = []
for lb in load_balancers:
for subnet in lb.subnets:
self.availability_zones.append(subnet.availability_zone)
self.gateway_load_balancer_arns = []
self.network_load_balancer_arns = []
for lb in load_balancers:
if lb.loadbalancer_type == "network":
self.service_type = "Interface"
self.network_load_balancer_arns.append(lb.arn)
else:
self.service_type = "Gateway"
self.gateway_load_balancer_arns.append(lb.arn)
self.acceptance_required = acceptance_required
self.manages_vpc_endpoints = False
self.private_dns_name = private_dns_name
self.endpoint_dns_name = f"{self.id}.{region}.vpce.amazonaws.com"
self.principals = []
self.ec2_backend = ec2_backend
class VPCServiceConfigurationBackend(object):
def __init__(self):
self.configurations = {}
super().__init__()
@property
def elbv2_backend(self):
from moto.elbv2.models import elbv2_backends
return elbv2_backends[self.region_name]
def get_vpc_endpoint_service(self, resource_id):
return self.configurations.get(resource_id)
def create_vpc_endpoint_service_configuration(
self, lb_arns, acceptance_required, private_dns_name, tags
):
lbs = self.elbv2_backend.describe_load_balancers(arns=lb_arns, names=None)
config = VPCServiceConfiguration(
load_balancers=lbs,
region=self.region_name,
acceptance_required=acceptance_required,
private_dns_name=private_dns_name,
ec2_backend=self,
)
for tag in tags or []:
tag_key = tag.get("Key")
tag_value = tag.get("Value")
config.add_tag(tag_key, tag_value)
self.configurations[config.id] = config
return config
def describe_vpc_endpoint_service_configurations(self, service_ids):
"""
The Filters, MaxResults, NextToken parameters are not yet implemented
"""
if service_ids:
found_configs = []
for service_id in service_ids:
if service_id in self.configurations:
found_configs.append(self.configurations[service_id])
else:
raise UnknownVpcEndpointService(service_id)
return found_configs
return self.configurations.values()
def delete_vpc_endpoint_service_configurations(self, service_ids):
missing = [s for s in service_ids if s not in self.configurations]
for s in service_ids:
self.configurations.pop(s, None)
return missing
def describe_vpc_endpoint_service_permissions(self, service_id):
"""
The Filters, MaxResults, NextToken parameters are not yet implemented
"""
config = self.describe_vpc_endpoint_service_configurations([service_id])[0]
return config.principals
def modify_vpc_endpoint_service_permissions(
self, service_id, add_principals, remove_principals
):
config = self.describe_vpc_endpoint_service_configurations([service_id])[0]
config.principals += add_principals
config.principals = [p for p in config.principals if p not in remove_principals]
config.principals = list(set(config.principals))
def modify_vpc_endpoint_service_configuration(
self,
service_id,
acceptance_required,
private_dns_name,
add_network_lbs,
remove_network_lbs,
add_gateway_lbs,
remove_gateway_lbs,
):
"""
The following parameters are not yet implemented: RemovePrivateDnsName
"""
config = self.describe_vpc_endpoint_service_configurations([service_id])[0]
if private_dns_name is not None:
config.private_dns_name = private_dns_name
if acceptance_required is not None:
config.acceptance_required = str(acceptance_required).lower() == "true"
for lb in add_network_lbs:
config.network_load_balancer_arns.append(lb)
for lb in remove_network_lbs:
config.network_load_balancer_arns.remove(lb)
for lb in add_gateway_lbs:
config.gateway_load_balancer_arns.append(lb)
for lb in remove_gateway_lbs:
config.gateway_load_balancer_arns.remove(lb)

View File

@ -734,3 +734,19 @@ class InvalidCarrierGatewayID(EC2ClientError):
"InvalidCarrierGatewayID.NotFound",
"The CarrierGateway ID '{0}' does not exist".format(carrier_gateway_id),
)
class NoLoadBalancersProvided(EC2ClientError):
def __init__(self):
super().__init__(
"InvalidParameter",
"exactly one of network_load_balancer_arn or gateway_load_balancer_arn is a required member",
)
class UnknownVpcEndpointService(EC2ClientError):
def __init__(self, service_id):
super().__init__(
"InvalidVpcEndpointServiceId.NotFound",
f"The VpcEndpointService Id '{service_id}' does not exist",
)

View File

@ -17,7 +17,7 @@ from boto3 import Session
from moto.core import ACCOUNT_ID
from moto.core import BaseBackend
from moto.core.models import Model, BaseModel, CloudFormationModel
from moto.core.models import Model, CloudFormationModel
from moto.core.utils import (
iso_8601_datetime_with_milliseconds,
camelcase_to_underscores,
@ -121,6 +121,8 @@ from .exceptions import (
InvalidGatewayIDError,
InvalidCarrierGatewayID,
)
from ._models.core import TaggedEC2Resource
from ._models.vpc_service_configuration import VPCServiceConfigurationBackend
from .utils import (
EC2_RESOURCE_TO_PREFIX,
EC2_PREFIX_TO_RESOURCE,
@ -239,42 +241,6 @@ class StateReason(object):
self.code = code
class TaggedEC2Resource(BaseModel):
def get_tags(self, *args, **kwargs):
tags = []
if self.id:
tags = self.ec2_backend.describe_tags(filters={"resource-id": [self.id]})
return tags
def add_tag(self, key, value):
self.ec2_backend.create_tags([self.id], {key: value})
def add_tags(self, tag_map):
for key, value in tag_map.items():
self.ec2_backend.create_tags([self.id], {key: value})
def get_filter_value(self, filter_name, method_name=None):
tags = self.get_tags()
if filter_name.startswith("tag:"):
tagname = filter_name.replace("tag:", "", 1)
for tag in tags:
if tag["key"] == tagname:
return tag["value"]
return None
elif filter_name == "tag-key":
return [tag["key"] for tag in tags]
elif filter_name == "tag-value":
return [tag["value"] for tag in tags]
value = getattr(self, filter_name.lower().replace("-", "_"), None)
if value is not None:
return value
raise FilterNotImplementedError(filter_name, method_name)
class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
def __init__(
self,
@ -503,6 +469,8 @@ class NetworkInterface(TaggedEC2Resource, CloudFormationModel):
return self.description
elif filter_name == "attachment.instance-id":
return self.instance.id if self.instance else None
elif filter_name == "attachment.instance-owner-id":
return self.owner_id
else:
return super().get_filter_value(filter_name, "DescribeNetworkInterfaces")
@ -1509,27 +1477,6 @@ class SettingsBackend(object):
class TagBackend(object):
VALID_TAG_FILTERS = ["key", "resource-id", "resource-type", "value"]
VALID_TAG_RESOURCE_FILTER_TYPES = [
"customer-gateway",
"dhcp-options",
"image",
"instance",
"internet-gateway",
"network-acl",
"network-interface",
"reserved-instances",
"route-table",
"security-group",
"snapshot",
"spot-instances-request",
"subnet",
"volume",
"vpc",
"vpc-flow-log",
"vpc-peering-connection" "vpn-connection",
"vpn-gateway",
]
def __init__(self):
self.tags = defaultdict(dict)
super().__init__()
@ -8667,6 +8614,7 @@ class EC2Backend(
FlowLogsBackend,
NetworkInterfaceBackend,
VPNConnectionBackend,
VPCServiceConfigurationBackend,
VPCPeeringConnectionBackend,
RouteTableBackend,
RouteBackend,
@ -8781,6 +8729,8 @@ class EC2Backend(
self.get_volume(volume_id=resource_id)
elif resource_prefix == EC2_RESOURCE_TO_PREFIX["vpc"]:
self.get_vpc(vpc_id=resource_id)
elif resource_prefix == EC2_RESOURCE_TO_PREFIX["vpc-endpoint-service"]:
self.get_vpc_endpoint_service(resource_id)
elif resource_prefix == EC2_RESOURCE_TO_PREFIX["vpc-peering-connection"]:
self.get_vpc_peering_connection(vpc_pcx_id=resource_id)
elif resource_prefix == EC2_RESOURCE_TO_PREFIX["vpn-connection"]:

View File

@ -30,6 +30,7 @@ from .virtual_private_gateways import VirtualPrivateGateways
from .vm_export import VMExport
from .vm_import import VMImport
from .vpcs import VPCs
from .vpc_service_configuration import VPCEndpointServiceConfiguration
from .vpc_peering_connections import VPCPeeringConnections
from .vpn_connections import VPNConnections
from .windows import Windows
@ -74,6 +75,7 @@ class EC2Response(
VMExport,
VMImport,
VPCs,
VPCEndpointServiceConfiguration,
VPCPeeringConnections,
VPNConnections,
Windows,

View File

@ -0,0 +1,223 @@
from moto.core.responses import BaseResponse
from ..exceptions import NoLoadBalancersProvided
class VPCEndpointServiceConfiguration(BaseResponse):
def create_vpc_endpoint_service_configuration(self):
gateway_lbs = self._get_multi_param("GatewayLoadBalancerArn")
network_lbs = self._get_multi_param("NetworkLoadBalancerArn")
if not gateway_lbs and not network_lbs:
raise NoLoadBalancersProvided
tags = self._get_multi_param("TagSpecification")
if tags:
tags = tags[0].get("Tag")
acceptance_required = (
str(self._get_param("AcceptanceRequired", "true")).lower() == "true"
)
private_dns_name = self._get_param("PrivateDnsName")
config = self.ec2_backend.create_vpc_endpoint_service_configuration(
gateway_lbs or network_lbs,
acceptance_required=acceptance_required,
private_dns_name=private_dns_name,
tags=tags,
)
template = self.response_template(CREATE_VPC_ENDPOINT_SERVICE_CONFIGURATION)
return template.render(config=config)
def describe_vpc_endpoint_service_configurations(self):
service_ids = self._get_multi_param("ServiceId")
configs = self.ec2_backend.describe_vpc_endpoint_service_configurations(
service_ids
)
template = self.response_template(DESCRIBE_VPC_ENDPOINT_SERVICE_CONFIGURATION)
return template.render(configs=configs)
def delete_vpc_endpoint_service_configurations(self):
service_ids = self._get_multi_param("ServiceId")
missing_configs = self.ec2_backend.delete_vpc_endpoint_service_configurations(
service_ids
)
template = self.response_template(DELETE_VPC_ENDPOINT_SERVICE_CONFIGURATION)
return template.render(missing=missing_configs)
def describe_vpc_endpoint_service_permissions(self):
service_id = self._get_param("ServiceId")
principals = self.ec2_backend.describe_vpc_endpoint_service_permissions(
service_id
)
template = self.response_template(DESCRIBE_VPC_ENDPOINT_SERVICE_PERMISSIONS)
return template.render(principals=principals)
def modify_vpc_endpoint_service_configuration(self):
service_id = self._get_param("ServiceId")
private_dns_name = self._get_param("PrivateDnsName")
acceptance_required = self._get_param("AcceptanceRequired")
add_network_lbs = self._get_multi_param("AddNetworkLoadBalancerArn")
remove_network_lbs = self._get_multi_param("RemoveNetworkLoadBalancerArn")
add_gateway_lbs = self._get_multi_param("AddGatewayLoadBalancerArn")
remove_gateway_lbs = self._get_multi_param("RemoveGatewayLoadBalancerArn")
self.ec2_backend.modify_vpc_endpoint_service_configuration(
service_id,
acceptance_required=acceptance_required,
private_dns_name=private_dns_name,
add_network_lbs=add_network_lbs,
remove_network_lbs=remove_network_lbs,
add_gateway_lbs=add_gateway_lbs,
remove_gateway_lbs=remove_gateway_lbs,
)
return MODIFY_VPC_ENDPOINT_SERVICE_CONFIGURATION
def modify_vpc_endpoint_service_permissions(self):
service_id = self._get_param("ServiceId")
add_principals = self._get_multi_param("AddAllowedPrincipals")
remove_principals = self._get_multi_param("RemoveAllowedPrincipals")
self.ec2_backend.modify_vpc_endpoint_service_permissions(
service_id, add_principals, remove_principals
)
return MODIFY_VPC_ENDPOINT_SERVICE_PERMISSIONS
CREATE_VPC_ENDPOINT_SERVICE_CONFIGURATION = """
<CreateVpcEndpointServiceConfigurationResult xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<serviceConfiguration>
<serviceType>
<item><serviceType>{{ config.service_type }}</serviceType></item>
</serviceType>
<serviceId>{{ config.id }}</serviceId>
<serviceName>{{ config.service_name }}</serviceName>
<serviceState>{{ config.service_state }}</serviceState>
<availabilityZoneSet>
{% for zone in config.availability_zones %}<item>{{ zone }}</item>{% endfor %}
</availabilityZoneSet>
<acceptanceRequired>{{ 'true' if config.acceptance_required else 'false' }}</acceptanceRequired>
<managesVpcEndpoints>{{ 'true' if config.manages_vpc_endpoints else 'false' }}</managesVpcEndpoints>
{%- if config.network_load_balancer_arns %}
<networkLoadBalancerArnSet>
{% for lb in config.network_load_balancer_arns %}<item>{{ lb }}</item>{% endfor %}
</networkLoadBalancerArnSet>
{% endif -%}
{%- if config.gateway_load_balancer_arns %}
<gatewayLoadBalancerArnSet>
{% for lb in config.gateway_load_balancer_arns %}<item>{{ lb }}</item>{% endfor %}
</gatewayLoadBalancerArnSet>
{% endif -%}
<baseEndpointDnsNameSet><item>{{ config.endpoint_dns_name }}</item></baseEndpointDnsNameSet>
<privateDnsName>{{ config.private_dns_name }}</privateDnsName>
<privateDnsNameConfiguration>
{% if config.private_dns_name %}
<state>verified</state>
<type>TXT</type>
<value>val</value>
<name>n</name>
{% endif %}
</privateDnsNameConfiguration>
</serviceConfiguration>
</CreateVpcEndpointServiceConfigurationResult>
"""
DESCRIBE_VPC_ENDPOINT_SERVICE_CONFIGURATION = """
<DescribeVpcEndpointServiceConfigurationsResult>
<serviceConfigurationSet>
{% for config in configs %}
<item>
<serviceType>
<item><serviceType>{{ config.service_type }}</serviceType></item>
</serviceType>
<serviceId>{{ config.id }}</serviceId>
<serviceName>{{ config.service_name }}</serviceName>
<serviceState>{{ config.service_state }}</serviceState>
<availabilityZoneSet>
{% for zone in config.availability_zones %}<item>{{ zone }}</item>{% endfor %}
</availabilityZoneSet>
<acceptanceRequired>{{ 'true' if config.acceptance_required else 'false' }}</acceptanceRequired>
<managesVpcEndpoints>{{ 'true' if config.manages_vpc_endpoints else 'false' }}</managesVpcEndpoints>
{%- if config.network_load_balancer_arns %}
<networkLoadBalancerArnSet>
{% for lb in config.network_load_balancer_arns %}<item>{{ lb }}</item>{% endfor %}
</networkLoadBalancerArnSet>
{% endif -%}
{%- if config.gateway_load_balancer_arns %}
<gatewayLoadBalancerArnSet>
{% for lb in config.gateway_load_balancer_arns %}<item>{{ lb }}</item>{% endfor %}
</gatewayLoadBalancerArnSet>
{% endif -%}
<baseEndpointDnsNameSet><item>{{ config.endpoint_dns_name }}</item></baseEndpointDnsNameSet>
<privateDnsName>{{ config.private_dns_name }}</privateDnsName>
<privateDnsNameConfiguration>
{% if config.private_dns_name %}
<state>verified</state>
<type>TXT</type>
<value>val</value>
<name>n</name>
{% endif %}
</privateDnsNameConfiguration>
<tagSet>
{% for tag in config.get_tags() %}
<item>
<key>{{ tag.key }}</key>
<value>{{ tag.value }}</value>
</item>
{% endfor %}
</tagSet>
</item>
{% endfor %}
</serviceConfigurationSet>
</DescribeVpcEndpointServiceConfigurationsResult>
"""
DELETE_VPC_ENDPOINT_SERVICE_CONFIGURATION = """
<DeleteVpcEndpointServiceConfigurationsResult>
<unsuccessful>
{% for m in missing %}
<item>
<error>
<code>InvalidVpcEndpointService.NotFound</code>
<message>The VpcEndpointService Id '{{ m }}' does not exist</message>
</error>
<resourceId>{{ m }}</resourceId>
</item>
{% endfor %}
</unsuccessful>
</DeleteVpcEndpointServiceConfigurationsResult>
"""
DESCRIBE_VPC_ENDPOINT_SERVICE_PERMISSIONS = """
<DescribeVpcEndpointServicePermissionsResult>
<allowedPrincipals>
{% for principal in principals %}
<item>
<principal>{{ principal }}</principal>
</item>
{% endfor %}
</allowedPrincipals>
</DescribeVpcEndpointServicePermissionsResult>
"""
MODIFY_VPC_ENDPOINT_SERVICE_PERMISSIONS = """
<ModifyVpcEndpointServicePermissionsResult>
<return>true</return>
</ModifyVpcEndpointServicePermissionsResult>
"""
MODIFY_VPC_ENDPOINT_SERVICE_CONFIGURATION = """
<ModifyVpcEndpointServiceConfigurationResult>
<return>true</return>
</ModifyVpcEndpointServiceConfigurationResult>
"""

View File

@ -43,6 +43,7 @@ EC2_RESOURCE_TO_PREFIX = {
"volume": "vol",
"vpc": "vpc",
"vpc-endpoint": "vpce",
"vpc-endpoint-service": "vpce-svc",
"managed-prefix-list": "pl",
"vpc-cidr-association-id": "vpc-cidr-assoc",
"vpc-elastic-ip": "eipalloc",
@ -626,6 +627,8 @@ def get_prefix(resource_id):
if resource_id_prefix == EC2_RESOURCE_TO_PREFIX["network-interface"]:
if after.startswith("attach"):
resource_id_prefix = EC2_RESOURCE_TO_PREFIX["network-interface-attachment"]
if resource_id.startswith(EC2_RESOURCE_TO_PREFIX["vpc-endpoint-service"]):
resource_id_prefix = EC2_RESOURCE_TO_PREFIX["vpc-endpoint-service"]
if resource_id_prefix not in EC2_RESOURCE_TO_PREFIX.values():
uuid4hex = re.compile(r"[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z", re.I)
if uuid4hex.match(resource_id) is not None:

View File

@ -4,7 +4,7 @@ from jinja2 import Template
from botocore.exceptions import ParamValidationError
from collections import OrderedDict
from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel, CloudFormationModel
from moto.core import ACCOUNT_ID, BaseBackend, BaseModel, CloudFormationModel
from moto.core.utils import (
iso_8601_datetime_with_milliseconds,
get_random_hex,
@ -428,6 +428,7 @@ class FakeLoadBalancer(CloudFormationModel):
"access_logs.s3.prefix",
"deletion_protection.enabled",
"idle_timeout.timeout_seconds",
"load_balancing.cross_zone.enabled",
"routing.http2.enabled",
"routing.http.drop_invalid_header_fields.enabled",
}
@ -618,7 +619,7 @@ class ELBv2Backend(BaseBackend):
vpc_id = subnets[0].vpc_id
arn = make_arn_for_load_balancer(
account_id=1, name=name, region_name=self.region_name
account_id=ACCOUNT_ID, name=name, region_name=self.region_name
)
dns_name = "%s-1.%s.elb.amazonaws.com" % (name, self.region_name)
@ -944,7 +945,7 @@ Member must satisfy regular expression pattern: {}".format(
)
arn = make_arn_for_target_group(
account_id=1, name=name, region_name=self.region_name
account_id=ACCOUNT_ID, name=name, region_name=self.region_name
)
target_group = FakeTargetGroup(name, arn, **kwargs)
self.target_groups[target_group.arn] = target_group

View File

@ -133,3 +133,4 @@ TestAccAWSDataSourceIAMRole
TestAccAWSDataSourceIAMUser
TestAccAWSIAMAccountAlias
TestAccAWSIAMOpenIDConnectProvider
TestAccAWSVpcEndpointService

View File

@ -6,6 +6,7 @@ from botocore.exceptions import ClientError
import sure # noqa # pylint: disable=unused-import
from moto import mock_ec2, settings
from moto.core import ACCOUNT_ID
from moto.ec2.utils import random_private_ip
from tests import EXAMPLE_AMI_ID
from uuid import uuid4
@ -488,6 +489,38 @@ def test_elastic_network_interfaces_get_by_attachment_instance_id():
enis.get("NetworkInterfaces").should.have.length_of(0)
@mock_ec2
def test_elastic_network_interfaces_get_by_attachment_instance_owner_id():
ec2_resource = boto3.resource("ec2", region_name="us-west-2")
ec2_client = boto3.client("ec2", region_name="us-west-2")
vpc = ec2_resource.create_vpc(CidrBlock="10.0.0.0/16")
subnet = ec2_resource.create_subnet(
VpcId=vpc.id, CidrBlock="10.0.0.0/24", AvailabilityZone="us-west-2a"
)
security_group1 = ec2_resource.create_security_group(
GroupName=str(uuid4()), Description="desc"
)
create_instances_result = ec2_resource.create_instances(
ImageId=EXAMPLE_AMI_ID, MinCount=1, MaxCount=1
)
instance = create_instances_result[0]
eni1 = ec2_resource.create_network_interface(
SubnetId=subnet.id, Groups=[security_group1.id]
)
ec2_client.attach_network_interface(
NetworkInterfaceId=eni1.id, InstanceId=instance.id, DeviceIndex=1
)
filters = [{"Name": "attachment.instance-owner-id", "Values": [ACCOUNT_ID]}]
enis = ec2_client.describe_network_interfaces(Filters=filters)["NetworkInterfaces"]
eni_ids = [eni["NetworkInterfaceId"] for eni in enis]
eni_ids.should.contain(eni1.id)
@mock_ec2
def test_elastic_network_interfaces_describe_network_interfaces_with_filter():
ec2 = boto3.resource("ec2", region_name="us-west-2")

View File

@ -0,0 +1,397 @@
import boto3
import pytest
import sure # noqa # pylint: disable=unused-import
from botocore.exceptions import ClientError
from moto import mock_ec2, mock_elbv2
from moto.core.utils import get_random_hex
# See our Development Tips on writing tests for hints on how to write good tests:
# http://docs.getmoto.org/en/latest/docs/contributing/development_tips/tests.html
@mock_ec2
def test_create_vpc_endpoint_service_configuration_without_params():
client = boto3.client("ec2", region_name="us-west-2")
with pytest.raises(ClientError) as exc:
client.create_vpc_endpoint_service_configuration()
err = exc.value.response["Error"]
err["Code"].should.equal("InvalidParameter")
err["Message"].should.equal(
"exactly one of network_load_balancer_arn or gateway_load_balancer_arn is a required member"
)
@mock_ec2
@mock_elbv2
def test_create_vpc_endpoint_service_configuration_with_network_load_balancer():
region_name = "eu-west-3"
client = boto3.client("ec2", region_name=region_name)
lb_arn = create_load_balancer(
region_name=region_name, lb_type="network", zone="eu-west-3b"
)
resp = client.create_vpc_endpoint_service_configuration(
NetworkLoadBalancerArns=[lb_arn]
)
resp.should.have.key("ServiceConfiguration")
config = resp["ServiceConfiguration"]
config.should.have.key("ServiceType").equals([{"ServiceType": "Interface"}])
config.should.have.key("ServiceId").match("^vpce-svc-")
config.should.have.key("ServiceName").equals(
f"com.amazonaws.vpce.eu-west-3.{config['ServiceId']}"
)
config.should.have.key("ServiceState").equals("Available")
config.should.have.key("AvailabilityZones").equals(["eu-west-3b"])
config.should.have.key("AcceptanceRequired").equals(True)
config.should.have.key("ManagesVpcEndpoints").equals(False)
config.should.have.key("NetworkLoadBalancerArns").equals([lb_arn])
config.should.have.key("BaseEndpointDnsNames").equals(
[f"{config['ServiceId']}.eu-west-3.vpce.amazonaws.com"]
)
config.should.have.key("PrivateDnsNameConfiguration").equals({})
config.shouldnt.have.key("GatewayLoadBalancerArns")
@mock_ec2
@mock_elbv2
def test_create_vpc_endpoint_service_configuration_with_gateway_load_balancer():
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
resp = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)
resp.should.have.key("ServiceConfiguration")
config = resp["ServiceConfiguration"]
config.should.have.key("ServiceType").equals([{"ServiceType": "Gateway"}])
config.should.have.key("ServiceId").match("^vpce-svc-")
config.should.have.key("ServiceName").equals(
f"com.amazonaws.vpce.us-east-2.{config['ServiceId']}"
)
config.should.have.key("ServiceState").equals("Available")
config.should.have.key("AvailabilityZones").equals(["us-east-1c"])
config.should.have.key("AcceptanceRequired").equals(True)
config.should.have.key("ManagesVpcEndpoints").equals(False)
config.should.have.key("GatewayLoadBalancerArns").equals([lb_arn])
config.should.have.key("BaseEndpointDnsNames").equals(
[f"{config['ServiceId']}.us-east-2.vpce.amazonaws.com"]
)
config.should.have.key("PrivateDnsNameConfiguration").equals({})
config.shouldnt.have.key("NetworkLoadBalancerArns")
@mock_ec2
@mock_elbv2
def test_create_vpc_endpoint_service_configuration_with_options():
client = boto3.client("ec2", region_name="us-east-2")
lb_arn = create_load_balancer(
region_name="us-east-2", lb_type="gateway", zone="us-east-1c"
)
resp = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn],
AcceptanceRequired=False,
PrivateDnsName="example.com",
)
resp.should.have.key("ServiceConfiguration")
config = resp["ServiceConfiguration"]
config.should.have.key("AcceptanceRequired").equals(False)
config.should.have.key("PrivateDnsName").equals("example.com")
config.should.have.key("PrivateDnsNameConfiguration").equals(
{"Name": "n", "State": "verified", "Type": "TXT", "Value": "val"}
)
@mock_ec2
@mock_elbv2
def test_describe_vpc_endpoint_service_configurations():
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
config1 = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)["ServiceConfiguration"]["ServiceId"]
config2 = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)["ServiceConfiguration"]["ServiceId"]
resp = client.describe_vpc_endpoint_service_configurations()
resp.should.have.key("ServiceConfigurations")
service_ids = [s["ServiceId"] for s in resp["ServiceConfigurations"]]
service_ids.should.contain(config1)
service_ids.should.contain(config2)
resp = client.describe_vpc_endpoint_service_configurations(ServiceIds=[config2])
resp.should.have.key("ServiceConfigurations").length_of(1)
result = resp["ServiceConfigurations"][0]
result.should.have.key("ServiceId").equals(config2)
result.should.have.key("ServiceName")
result.should.have.key("ServiceState")
result.should.have.key("GatewayLoadBalancerArns").equals([lb_arn])
@mock_ec2
@mock_elbv2
@pytest.mark.parametrize(
"tags",
[
[{"Key": "k1", "Value": "v1"}],
[{"Key": "k1", "Value": "v1"}, {"Key": "k2", "Value": "v2"}],
],
)
def test_describe_vpc_endpoint_service_configurations_with_tags(tags):
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
service_id = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn],
TagSpecifications=[{"ResourceType": "vpc-endpoint-service", "Tags": tags}],
)["ServiceConfiguration"]["ServiceId"]
resp = client.describe_vpc_endpoint_service_configurations(ServiceIds=[service_id])
resp.should.have.key("ServiceConfigurations").length_of(1)
result = resp["ServiceConfigurations"][0]
result.should.have.key("Tags").length_of(len(tags))
for tag in tags:
result["Tags"].should.contain(tag)
@mock_ec2
@mock_elbv2
def test_describe_vpc_endpoint_service_configurations_and_add_tags():
tags = [{"Key": "k1", "Value": "v1"}]
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
service_id = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)["ServiceConfiguration"]["ServiceId"]
client.create_tags(Resources=[service_id], Tags=tags)
resp = client.describe_vpc_endpoint_service_configurations(ServiceIds=[service_id])
resp.should.have.key("ServiceConfigurations").length_of(1)
result = resp["ServiceConfigurations"][0]
result.should.have.key("Tags").length_of(len(tags))
for tag in tags:
result["Tags"].should.contain(tag)
@mock_ec2
def test_describe_vpc_endpoint_service_configurations_unknown():
client = boto3.client("ec2", region_name="eu-west-3")
with pytest.raises(ClientError) as exc:
# Will always fail if at least one ServiceId is unknown
client.describe_vpc_endpoint_service_configurations(
ServiceIds=["vpce-svc-unknown"]
)
err = exc.value.response["Error"]
err["Code"].should.equal("InvalidVpcEndpointServiceId.NotFound")
err["Message"].should.equal(
"The VpcEndpointService Id 'vpce-svc-unknown' does not exist"
)
@mock_ec2
@mock_elbv2
def test_delete_vpc_endpoint_service_configurations():
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
service_id = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)["ServiceConfiguration"]["ServiceId"]
resp = client.delete_vpc_endpoint_service_configurations(ServiceIds=[service_id])
resp.should.have.key("Unsuccessful").equals([])
@mock_ec2
def test_delete_vpc_endpoint_service_configurations_already_deleted():
client = boto3.client("ec2", region_name="eu-west-3")
resp = client.delete_vpc_endpoint_service_configurations(
ServiceIds=["vpce-svc-03cf101d15c3bff53"]
)
resp.should.have.key("Unsuccessful").length_of(1)
u = resp["Unsuccessful"][0]
u.should.have.key("ResourceId").equals("vpce-svc-03cf101d15c3bff53")
u.should.have.key("Error")
u["Error"].should.have.key("Code").equals("InvalidVpcEndpointService.NotFound")
u["Error"].should.have.key("Message").equals(
"The VpcEndpointService Id 'vpce-svc-03cf101d15c3bff53' does not exist"
)
@mock_ec2
@mock_elbv2
def test_describe_vpc_endpoint_service_permissions():
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
service_id = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)["ServiceConfiguration"]["ServiceId"]
resp = client.describe_vpc_endpoint_service_permissions(ServiceId=service_id)
resp.should.have.key("AllowedPrincipals").equals([])
@mock_ec2
@mock_elbv2
def test_modify_vpc_endpoint_service_permissions():
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
service_id = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)["ServiceConfiguration"]["ServiceId"]
client.modify_vpc_endpoint_service_permissions(
ServiceId=service_id, AddAllowedPrincipals=["prin1", "cipal2"]
)
resp = client.describe_vpc_endpoint_service_permissions(ServiceId=service_id)
resp.should.have.key("AllowedPrincipals").length_of(2)
resp["AllowedPrincipals"].should.contain({"Principal": "prin1"})
resp["AllowedPrincipals"].should.contain({"Principal": "cipal2"})
client.modify_vpc_endpoint_service_permissions(
ServiceId=service_id, RemoveAllowedPrincipals=["prin1"]
)
resp = client.describe_vpc_endpoint_service_permissions(ServiceId=service_id)
resp.should.have.key("AllowedPrincipals").length_of(1)
resp["AllowedPrincipals"].should.contain({"Principal": "cipal2"})
@mock_ec2
@mock_elbv2
def test_modify_vpc_endpoint_service_configuration():
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
service_id = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)["ServiceConfiguration"]["ServiceId"]
client.modify_vpc_endpoint_service_configuration(
ServiceId=service_id, PrivateDnsName="dnsname", AcceptanceRequired=False
)
config = client.describe_vpc_endpoint_service_configurations(
ServiceIds=[service_id]
)["ServiceConfigurations"][0]
config.should.have.key("AcceptanceRequired").equals(False)
config.should.have.key("PrivateDnsName").equals("dnsname")
@mock_ec2
@mock_elbv2
def test_modify_vpc_endpoint_service_configuration_with_new_loadbalancers():
region = "us-east-2"
client = boto3.client("ec2", region_name=region)
lb_arn = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
lb_arn2 = create_load_balancer(
region_name=region, lb_type="gateway", zone="us-east-1c"
)
lb_arn3 = create_load_balancer(
region_name=region, lb_type="network", zone="us-east-1c"
)
service_id = client.create_vpc_endpoint_service_configuration(
GatewayLoadBalancerArns=[lb_arn]
)["ServiceConfiguration"]["ServiceId"]
client.modify_vpc_endpoint_service_configuration(
ServiceId=service_id,
AddNetworkLoadBalancerArns=[lb_arn3],
AddGatewayLoadBalancerArns=[lb_arn2],
)
config = client.describe_vpc_endpoint_service_configurations(
ServiceIds=[service_id]
)["ServiceConfigurations"][0]
config["GatewayLoadBalancerArns"].should.equal([lb_arn, lb_arn2])
config["NetworkLoadBalancerArns"].should.equal([lb_arn3])
client.modify_vpc_endpoint_service_configuration(
ServiceId=service_id,
RemoveNetworkLoadBalancerArns=[lb_arn3],
RemoveGatewayLoadBalancerArns=[lb_arn],
)
config = client.describe_vpc_endpoint_service_configurations(
ServiceIds=[service_id]
)["ServiceConfigurations"][0]
config["GatewayLoadBalancerArns"].should.equal([lb_arn2])
config.shouldnt.have.key("NetworkLoadBalancerArns")
def create_load_balancer(region_name, zone, lb_type):
ec2 = boto3.resource("ec2", region_name=region_name)
elbv2 = boto3.client("elbv2", region_name=region_name)
vpc = ec2.create_vpc(CidrBlock="172.28.7.0/24", InstanceTenancy="default")
subnet = ec2.create_subnet(
VpcId=vpc.id, CidrBlock="172.28.7.192/26", AvailabilityZone=zone
)
lb_name = f"lb_vpce-{get_random_hex(length=10)}"
response = elbv2.create_load_balancer(
Name=lb_name, Subnets=[subnet.id], Scheme="internal", Type=lb_type
)
return response["LoadBalancers"][0]["LoadBalancerArn"]

View File

@ -19,7 +19,7 @@ def test_create_load_balancer():
lb = response.get("LoadBalancers")[0]
lb.get("DNSName").should.equal("my-lb-1.us-east-1.elb.amazonaws.com")
lb.get("LoadBalancerArn").should.equal(
"arn:aws:elasticloadbalancing:us-east-1:1:loadbalancer/my-lb/50dc6c495c0c9188"
f"arn:aws:elasticloadbalancing:us-east-1:{ACCOUNT_ID}:loadbalancer/my-lb/50dc6c495c0c9188"
)
lb.get("SecurityGroups").should.equal([security_group.id])
lb.get("AvailabilityZones").should.equal(
@ -1224,6 +1224,25 @@ def test_modify_load_balancer_attributes_routing_http2_enabled():
routing_http2_enabled["Value"].should.equal("false")
@mock_elbv2
@mock_ec2
def test_modify_load_balancer_attributes_crosszone_enabled():
response, _, _, _, _, client = create_load_balancer()
arn = response["LoadBalancers"][0]["LoadBalancerArn"]
client.modify_load_balancer_attributes(
LoadBalancerArn=arn,
Attributes=[
{"Key": "load_balancing.cross_zone.enabled", "Value": "false"},
{"Key": "deletion_protection.enabled", "Value": "false"},
],
)
attrs = client.describe_load_balancer_attributes(LoadBalancerArn=arn)["Attributes"]
attrs.should.contain({"Key": "deletion_protection.enabled", "Value": "false"})
attrs.should.contain({"Key": "load_balancing.cross_zone.enabled", "Value": "false"})
@mock_elbv2
@mock_ec2
def test_modify_load_balancer_attributes_routing_http_drop_invalid_header_fields_enabled():

View File

@ -153,7 +153,7 @@ def test_create_target_group_and_listeners():
e.value.operation_name.should.equal("DeleteTargetGroup")
e.value.args.should.equal(
(
"An error occurred (ResourceInUse) when calling the DeleteTargetGroup operation: The target group 'arn:aws:elasticloadbalancing:us-east-1:1:targetgroup/a-target/50dc6c495c0c9188' is currently in use by a listener or a rule",
f"An error occurred (ResourceInUse) when calling the DeleteTargetGroup operation: The target group 'arn:aws:elasticloadbalancing:us-east-1:{ACCOUNT_ID}:targetgroup/a-target/50dc6c495c0c9188' is currently in use by a listener or a rule",
)
) # NOQA