10
10
ApiGatewayApiKey ,
11
11
ApiGatewayAuthorizer ,
12
12
ApiGatewayBasePathMapping ,
13
+ ApiGatewayBasePathMappingV2 ,
13
14
ApiGatewayDeployment ,
14
15
ApiGatewayDomainName ,
16
+ ApiGatewayDomainNameV2 ,
15
17
ApiGatewayResponse ,
16
18
ApiGatewayRestApi ,
17
19
ApiGatewayStage ,
@@ -79,6 +81,13 @@ class ApiDomainResponse:
79
81
recordset_group : Any
80
82
81
83
84
+ @dataclass
85
+ class ApiDomainResponseV2 :
86
+ domain : Optional [ApiGatewayDomainNameV2 ]
87
+ apigw_basepath_mapping_list : Optional [List [ApiGatewayBasePathMappingV2 ]]
88
+ recordset_group : Any
89
+
90
+
82
91
class SharedApiUsagePlan :
83
92
"""
84
93
Collects API information from different API resources in the same template,
@@ -517,11 +526,7 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915
517
526
if mutual_tls_auth .get ("TruststoreVersion" , None ):
518
527
domain .MutualTlsAuthentication ["TruststoreVersion" ] = mutual_tls_auth ["TruststoreVersion" ]
519
528
520
- if self .domain .get ("SecurityPolicy" , None ):
521
- domain .SecurityPolicy = self .domain ["SecurityPolicy" ]
522
-
523
- if self .domain .get ("OwnershipVerificationCertificateArn" , None ):
524
- domain .OwnershipVerificationCertificateArn = self .domain ["OwnershipVerificationCertificateArn" ]
529
+ self ._set_optional_domain_properties (domain )
525
530
526
531
basepaths : Optional [List [str ]]
527
532
basepath_value = self .domain .get ("BasePath" )
@@ -539,12 +544,102 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915
539
544
basepath_resource_list : List [ApiGatewayBasePathMapping ] = []
540
545
541
546
if basepaths is None :
542
- basepath_mapping = ApiGatewayBasePathMapping (
543
- self .logical_id + "BasePathMapping" , attributes = self .passthrough_resource_attributes
547
+ basepath_mapping = self ._create_basepath_mapping (api_domain_name , rest_api , None , None )
548
+ basepath_resource_list .extend ([basepath_mapping ])
549
+ else :
550
+ sam_expect (basepaths , self .logical_id , "Domain.BasePath" ).to_be_a_list_of (ExpectedType .STRING )
551
+ for basepath in basepaths :
552
+ # Remove possible leading and trailing '/' because a base path may only
553
+ # contain letters, numbers, and one of "$-_.+!*'()"
554
+ path = "" .join (e for e in basepath if e .isalnum ())
555
+ mapping_basepath = path if normalize_basepath else basepath
556
+ logical_id = "{}{}{}" .format (self .logical_id , path , "BasePathMapping" )
557
+ basepath_mapping = self ._create_basepath_mapping (
558
+ api_domain_name , rest_api , logical_id , mapping_basepath
559
+ )
560
+ basepath_resource_list .extend ([basepath_mapping ])
561
+
562
+ # Create the Route53 RecordSetGroup resource
563
+ record_set_group = None
564
+ route53 = self .domain .get ("Route53" )
565
+ if route53 is not None :
566
+ sam_expect (route53 , self .logical_id , "Domain.Route53" ).to_be_a_map ()
567
+ if route53 .get ("HostedZoneId" ) is None and route53 .get ("HostedZoneName" ) is None :
568
+ raise InvalidResourceException (
569
+ self .logical_id ,
570
+ "HostedZoneId or HostedZoneName is required to enable Route53 support on Custom Domains." ,
571
+ )
572
+
573
+ logical_id_suffix = LogicalIdGenerator (
574
+ "" , route53 .get ("HostedZoneId" ) or route53 .get ("HostedZoneName" )
575
+ ).gen ()
576
+ logical_id = "RecordSetGroup" + logical_id_suffix
577
+
578
+ record_set_group = route53_record_set_groups .get (logical_id )
579
+
580
+ if route53 .get ("SeparateRecordSetGroup" ):
581
+ sam_expect (
582
+ route53 .get ("SeparateRecordSetGroup" ), self .logical_id , "Domain.Route53.SeparateRecordSetGroup"
583
+ ).to_be_a_bool ()
584
+ return ApiDomainResponse (
585
+ domain ,
586
+ basepath_resource_list ,
587
+ self ._construct_single_record_set_group (self .domain , api_domain_name , route53 ),
588
+ )
589
+
590
+ if not record_set_group :
591
+ record_set_group = self ._get_record_set_group (logical_id , route53 )
592
+ route53_record_set_groups [logical_id ] = record_set_group
593
+
594
+ record_set_group .RecordSets += self ._construct_record_sets_for_domain (self .domain , api_domain_name , route53 )
595
+
596
+ return ApiDomainResponse (domain , basepath_resource_list , record_set_group )
597
+
598
+ def _construct_api_domain_v2 (
599
+ self , rest_api : ApiGatewayRestApi , route53_record_set_groups : Any
600
+ ) -> ApiDomainResponseV2 :
601
+ """
602
+ Constructs and returns the ApiGateway Domain V2 and BasepathMapping V2
603
+ """
604
+ if self .domain is None :
605
+ return ApiDomainResponseV2 (None , None , None )
606
+
607
+ sam_expect (self .domain , self .logical_id , "Domain" ).to_be_a_map ()
608
+ domain_name : PassThrough = sam_expect (
609
+ self .domain .get ("DomainName" ), self .logical_id , "Domain.DomainName"
610
+ ).to_not_be_none ()
611
+ certificate_arn : PassThrough = sam_expect (
612
+ self .domain .get ("CertificateArn" ), self .logical_id , "Domain.CertificateArn"
613
+ ).to_not_be_none ()
614
+
615
+ api_domain_name = "{}{}" .format ("ApiGatewayDomainNameV2" , LogicalIdGenerator ("" , domain_name ).gen ())
616
+ domain_name_arn = ref (api_domain_name )
617
+ domain = ApiGatewayDomainNameV2 (api_domain_name , attributes = self .passthrough_resource_attributes )
618
+
619
+ domain .DomainName = domain_name
620
+ endpoint = self .domain .get ("EndpointConfiguration" )
621
+
622
+ if endpoint not in ["EDGE" , "REGIONAL" , "PRIVATE" ]:
623
+ raise InvalidResourceException (
624
+ self .logical_id ,
625
+ "EndpointConfiguration for Custom Domains must be"
626
+ " one of {}." .format (["EDGE" , "REGIONAL" , "PRIVATE" ]),
544
627
)
545
- basepath_mapping .DomainName = ref (api_domain_name )
546
- basepath_mapping .RestApiId = ref (rest_api .logical_id )
547
- basepath_mapping .Stage = ref (rest_api .logical_id + ".Stage" )
628
+
629
+ domain .CertificateArn = certificate_arn
630
+
631
+ domain .EndpointConfiguration = {"Types" : [endpoint ]}
632
+
633
+ self ._set_optional_domain_properties (domain )
634
+
635
+ basepaths : Optional [List [str ]] = self ._get_basepaths ()
636
+
637
+ # Boolean to allow/disallow symbols in BasePath property
638
+ normalize_basepath = self .domain .get ("NormalizeBasePath" , True )
639
+
640
+ basepath_resource_list : List [ApiGatewayBasePathMappingV2 ] = []
641
+ if basepaths is None :
642
+ basepath_mapping = self ._create_basepath_mapping_v2 (domain_name_arn , rest_api )
548
643
basepath_resource_list .extend ([basepath_mapping ])
549
644
else :
550
645
sam_expect (basepaths , self .logical_id , "Domain.BasePath" ).to_be_a_list_of (ExpectedType .STRING )
@@ -553,10 +648,10 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915
553
648
# contain letters, numbers, and one of "$-_.+!*'()"
554
649
path = "" .join (e for e in basepath if e .isalnum ())
555
650
logical_id = "{}{}{}" .format (self .logical_id , path , "BasePathMapping" )
556
- basepath_mapping = ApiGatewayBasePathMapping (
651
+ basepath_mapping = ApiGatewayBasePathMappingV2 (
557
652
logical_id , attributes = self .passthrough_resource_attributes
558
653
)
559
- basepath_mapping .DomainName = ref ( api_domain_name )
654
+ basepath_mapping .DomainNameArn = domain_name_arn
560
655
basepath_mapping .RestApiId = ref (rest_api .logical_id )
561
656
basepath_mapping .Stage = ref (rest_api .logical_id + ".Stage" )
562
657
basepath_mapping .BasePath = path if normalize_basepath else basepath
@@ -584,24 +679,48 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915
584
679
sam_expect (
585
680
route53 .get ("SeparateRecordSetGroup" ), self .logical_id , "Domain.Route53.SeparateRecordSetGroup"
586
681
).to_be_a_bool ()
587
- return ApiDomainResponse (
682
+ return ApiDomainResponseV2 (
588
683
domain ,
589
684
basepath_resource_list ,
590
- self ._construct_single_record_set_group (self .domain , api_domain_name , route53 ),
685
+ self ._construct_single_record_set_group (self .domain , domain_name , route53 ),
591
686
)
592
687
593
688
if not record_set_group :
594
- record_set_group = Route53RecordSetGroup (logical_id , attributes = self .passthrough_resource_attributes )
595
- if "HostedZoneId" in route53 :
596
- record_set_group .HostedZoneId = route53 .get ("HostedZoneId" )
597
- if "HostedZoneName" in route53 :
598
- record_set_group .HostedZoneName = route53 .get ("HostedZoneName" )
599
- record_set_group .RecordSets = []
689
+ record_set_group = self ._get_record_set_group (logical_id , route53 )
600
690
route53_record_set_groups [logical_id ] = record_set_group
601
691
602
- record_set_group .RecordSets += self ._construct_record_sets_for_domain (self .domain , api_domain_name , route53 )
692
+ record_set_group .RecordSets += self ._construct_record_sets_for_domain (self .domain , domain_name , route53 )
603
693
604
- return ApiDomainResponse (domain , basepath_resource_list , record_set_group )
694
+ return ApiDomainResponseV2 (domain , basepath_resource_list , record_set_group )
695
+
696
+ def _get_basepaths (self ) -> Optional [List [str ]]:
697
+ if self .domain is None :
698
+ return None
699
+ basepath_value = self .domain .get ("BasePath" )
700
+ if self .domain .get ("BasePath" ) and isinstance (basepath_value , str ):
701
+ return [basepath_value ]
702
+ if self .domain .get ("BasePath" ) and isinstance (basepath_value , list ):
703
+ return cast (Optional [List [Any ]], basepath_value )
704
+ return None
705
+
706
+ def _set_optional_domain_properties (self , domain : Union [ApiGatewayDomainName , ApiGatewayDomainNameV2 ]) -> None :
707
+ if self .domain is None :
708
+ return
709
+ if self .domain .get ("SecurityPolicy" , None ):
710
+ domain .SecurityPolicy = self .domain ["SecurityPolicy" ]
711
+ if self .domain .get ("Policy" , None ):
712
+ domain .Policy = self .domain ["Policy" ]
713
+ if self .domain .get ("OwnershipVerificationCertificateArn" , None ):
714
+ domain .OwnershipVerificationCertificateArn = self .domain ["OwnershipVerificationCertificateArn" ]
715
+
716
+ def _get_record_set_group (self , logical_id : str , route53 : Dict [str , Any ]) -> Route53RecordSetGroup :
717
+ record_set_group = Route53RecordSetGroup (logical_id , attributes = self .passthrough_resource_attributes )
718
+ if "HostedZoneId" in route53 :
719
+ record_set_group .HostedZoneId = route53 .get ("HostedZoneId" )
720
+ if "HostedZoneName" in route53 :
721
+ record_set_group .HostedZoneName = route53 .get ("HostedZoneName" )
722
+ record_set_group .RecordSets = []
723
+ return record_set_group
605
724
606
725
def _construct_single_record_set_group (
607
726
self , domain : Dict [str , Any ], api_domain_name : str , route53 : Any
@@ -667,6 +786,40 @@ def _construct_alias_target(self, domain: Dict[str, Any], api_domain_name: str,
667
786
alias_target ["DNSName" ] = route53 .get ("DistributionDomainName" )
668
787
return alias_target
669
788
789
+ def _create_basepath_mapping (
790
+ self ,
791
+ api_domain_name : PassThrough ,
792
+ rest_api : ApiGatewayRestApi ,
793
+ logical_id : Optional [str ],
794
+ basepath : Optional [str ],
795
+ ) -> ApiGatewayBasePathMapping :
796
+
797
+ basepath_mapping : ApiGatewayBasePathMapping
798
+ basepath_mapping = (
799
+ ApiGatewayBasePathMapping (logical_id , attributes = self .passthrough_resource_attributes )
800
+ if logical_id
801
+ else ApiGatewayBasePathMapping (
802
+ self .logical_id + "BasePathMapping" , attributes = self .passthrough_resource_attributes
803
+ )
804
+ )
805
+ basepath_mapping .DomainName = ref (api_domain_name )
806
+ basepath_mapping .RestApiId = ref (rest_api .logical_id )
807
+ basepath_mapping .Stage = ref (rest_api .logical_id + ".Stage" )
808
+ if basepath is not None :
809
+ basepath_mapping .BasePath = basepath
810
+ return basepath_mapping
811
+
812
+ def _create_basepath_mapping_v2 (
813
+ self , domain_name_arn : PassThrough , rest_api : ApiGatewayRestApi
814
+ ) -> ApiGatewayBasePathMappingV2 :
815
+ basepath_mapping = ApiGatewayBasePathMappingV2 (
816
+ self .logical_id + "BasePathMapping" , attributes = self .passthrough_resource_attributes
817
+ )
818
+ basepath_mapping .DomainNameArn = domain_name_arn
819
+ basepath_mapping .RestApiId = ref (rest_api .logical_id )
820
+ basepath_mapping .Stage = ref (rest_api .logical_id + ".Stage" )
821
+ return basepath_mapping
822
+
670
823
@cw_timer (prefix = "Generator" , name = "Api" )
671
824
def to_cloudformation (
672
825
self , redeploy_restapi_parameters : Optional [Any ], route53_record_set_groups : Dict [str , Route53RecordSetGroup ]
@@ -676,10 +829,19 @@ def to_cloudformation(
676
829
:returns: a tuple containing the RestApi, Deployment, and Stage for an empty Api.
677
830
:rtype: tuple
678
831
"""
832
+ api_domain_response : Union [ApiDomainResponseV2 , ApiDomainResponse ]
833
+ domain : Union [Resource , None ]
834
+ basepath_mapping : Union [List [ApiGatewayBasePathMapping ], List [ApiGatewayBasePathMappingV2 ], None ]
679
835
rest_api = self ._construct_rest_api ()
680
- api_domain_response = self ._construct_api_domain (rest_api , route53_record_set_groups )
836
+ api_domain_response = (
837
+ self ._construct_api_domain_v2 (rest_api , route53_record_set_groups )
838
+ if isinstance (self .domain , dict ) and self .domain .get ("EndpointConfiguration" ) == "PRIVATE"
839
+ else self ._construct_api_domain (rest_api , route53_record_set_groups )
840
+ )
841
+
681
842
domain = api_domain_response .domain
682
843
basepath_mapping = api_domain_response .apigw_basepath_mapping_list
844
+
683
845
route53_recordsetGroup = api_domain_response .recordset_group
684
846
685
847
deployment = self ._construct_deployment (rest_api )
@@ -703,6 +865,7 @@ def to_cloudformation(
703
865
Tuple [Resource ],
704
866
List [LambdaPermission ],
705
867
List [ApiGatewayBasePathMapping ],
868
+ List [ApiGatewayBasePathMappingV2 ],
706
869
],
707
870
] = []
708
871
0 commit comments