1
+ import json
1
2
import re
2
3
from abc import ABC
3
4
from datetime import datetime , timezone
4
5
5
6
from botocore .parsers import ResponseParserError
6
7
from moto .core .utils import camelcase_to_underscores , underscores_to_camelcase
7
8
from moto .ec2 .exceptions import InvalidVpcEndPointIdError
8
- from moto .ec2 .models import SubnetBackend , TransitGatewayAttachmentBackend
9
+ from moto .ec2 .models import (
10
+ EC2Backend ,
11
+ SubnetBackend ,
12
+ TransitGatewayAttachmentBackend ,
13
+ VPCBackend ,
14
+ ec2_backends ,
15
+ )
9
16
from moto .ec2 .models .launch_templates import LaunchTemplate as MotoLaunchTemplate
10
17
from moto .ec2 .models .subnets import Subnet
18
+ from moto .ec2 .models .vpcs import VPCEndPoint
11
19
12
20
from localstack .aws .api import RequestContext , handler
13
21
from localstack .aws .api .ec2 import (
30
38
DescribeSubnetsResult ,
31
39
DescribeTransitGatewaysRequest ,
32
40
DescribeTransitGatewaysResult ,
41
+ DescribeVpcEndpointServicesRequest ,
42
+ DescribeVpcEndpointServicesResult ,
43
+ DescribeVpcEndpointsRequest ,
44
+ DescribeVpcEndpointsResult ,
45
+ DnsOptions ,
33
46
DnsOptionsSpecification ,
47
+ DnsRecordIpType ,
34
48
Ec2Api ,
35
49
InstanceType ,
36
50
IpAddressType ,
68
82
from localstack .services .moto import call_moto
69
83
from localstack .utils .aws import aws_stack
70
84
from localstack .utils .patch import patch
71
- from localstack .utils .strings import first_char_to_upper , long_uid
85
+ from localstack .utils .strings import first_char_to_upper , long_uid , short_uid
72
86
73
87
# additional subnet attributes not yet supported upstream
74
88
ADDITIONAL_SUBNET_ATTRS = ("private_dns_name_options_on_launch" , "enable_dns64" )
@@ -381,6 +395,66 @@ def modify_launch_template(
381
395
382
396
return result
383
397
398
+ @handler ("DescribeVpcEndpointServices" , expand = False )
399
+ def describe_vpc_endpoint_services (
400
+ self ,
401
+ context : RequestContext ,
402
+ request : DescribeVpcEndpointServicesRequest ,
403
+ ) -> DescribeVpcEndpointServicesResult :
404
+ ep_services = VPCBackend ._collect_default_endpoint_services (
405
+ account_id = context .account_id , region = context .region
406
+ )
407
+
408
+ moto_backend = get_moto_backend (context )
409
+ service_names = [s ["ServiceName" ] for s in ep_services ]
410
+ execute_api_name = f"com.amazonaws.{ context .region } .execute-api"
411
+
412
+ if execute_api_name not in service_names :
413
+ # ensure that the service entry for execute-api exists
414
+ zones = moto_backend .describe_availability_zones ()
415
+ zones = [zone .name for zone in zones ]
416
+ private_dns_name = f"*.execute-api.{ context .region } .amazonaws.com"
417
+ service = {
418
+ "ServiceName" : execute_api_name ,
419
+ "ServiceId" : f"vpce-svc-{ short_uid ()} " ,
420
+ "ServiceType" : [{"ServiceType" : "Interface" }],
421
+ "AvailabilityZones" : zones ,
422
+ "Owner" : "amazon" ,
423
+ "BaseEndpointDnsNames" : [f"execute-api.{ context .region } .vpce.amazonaws.com" ],
424
+ "PrivateDnsName" : private_dns_name ,
425
+ "PrivateDnsNames" : [{"PrivateDnsName" : private_dns_name }],
426
+ "VpcEndpointPolicySupported" : True ,
427
+ "AcceptanceRequired" : False ,
428
+ "ManagesVpcEndpoints" : False ,
429
+ "PrivateDnsNameVerificationState" : "verified" ,
430
+ "SupportedIpAddressTypes" : ["ipv4" ],
431
+ }
432
+ ep_services .append (service )
433
+
434
+ return call_moto (context )
435
+
436
+ @handler ("DescribeVpcEndpoints" , expand = False )
437
+ def describe_vpc_endpoints (
438
+ self ,
439
+ context : RequestContext ,
440
+ request : DescribeVpcEndpointsRequest ,
441
+ ) -> DescribeVpcEndpointsResult :
442
+ result : DescribeVpcEndpointsResult = call_moto (context )
443
+
444
+ for endpoint in result .get ("VpcEndpoints" ):
445
+ endpoint .setdefault ("DnsOptions" , DnsOptions (DnsRecordIpType = DnsRecordIpType .ipv4 ))
446
+ endpoint .setdefault ("IpAddressType" , IpAddressType .ipv4 )
447
+ endpoint .setdefault ("RequesterManaged" , False )
448
+ endpoint .setdefault ("RouteTableIds" , [])
449
+ # AWS parity: Version should not be contained in the policy response
450
+ policy = endpoint .get ("PolicyDocument" )
451
+ if policy and '"Version":' in policy :
452
+ policy = json .loads (policy )
453
+ policy .pop ("Version" , None )
454
+ endpoint ["PolicyDocument" ] = json .dumps (policy )
455
+
456
+ return result
457
+
384
458
385
459
@patch (SubnetBackend .modify_subnet_attribute )
386
460
def modify_subnet_attribute (fn , self , subnet_id , attr_name , attr_value ):
@@ -399,6 +473,11 @@ def modify_subnet_attribute(fn, self, subnet_id, attr_name, attr_value):
399
473
return fn (self , subnet_id , attr_name , attr_value )
400
474
401
475
476
+ def get_moto_backend (context : RequestContext ) -> EC2Backend :
477
+ """Get the moto EC2 backend for the given request context"""
478
+ return ec2_backends [context .account_id ][context .region ]
479
+
480
+
402
481
@patch (Subnet .get_filter_value )
403
482
def get_filter_value (fn , self , filter_name ):
404
483
if filter_name in (
@@ -414,3 +493,8 @@ def delete_transit_gateway_vpc_attachment(fn, self, transit_gateway_attachment_i
414
493
transit_gateway_attachment = self .transit_gateway_attachments .get (transit_gateway_attachment_id )
415
494
transit_gateway_attachment .state = "deleted"
416
495
return transit_gateway_attachment
496
+
497
+
498
+ # fix a bug in upstream moto where a space is encoded in the "Statement" key - TODO remove once fixed upstream
499
+ if "Statement " in VPCEndPoint .DEFAULT_POLICY :
500
+ VPCEndPoint .DEFAULT_POLICY ["Statement" ] = VPCEndPoint .DEFAULT_POLICY .pop ("Statement " )
0 commit comments