From dea23a91a1d09b3e55732dcefa9abec52cbf8ee7 Mon Sep 17 00:00:00 2001 From: Vandita Patidar Date: Wed, 12 Feb 2025 23:31:18 -0800 Subject: [PATCH 1/7] Custom domain name support for private endpoints --- .../schema_source/aws_serverless_api.py | 18 +++ samtranslator/model/api/api_generator.py | 144 +++++++++++++++++- samtranslator/model/apigateway.py | 27 ++++ samtranslator/model/sam_resources.py | 2 + samtranslator/translator/verify_logical_id.py | 2 + .../api_with_custom_domains_private.yaml | 69 +++++++++ .../api_with_custom_domains_private.json | 142 +++++++++++++++++ .../api_with_custom_domains_private.json | 142 +++++++++++++++++ .../api_with_custom_domains_private.json | 142 +++++++++++++++++ tests/translator/test_translator.py | 1 + 10 files changed, 686 insertions(+), 3 deletions(-) create mode 100644 tests/translator/input/api_with_custom_domains_private.yaml create mode 100644 tests/translator/output/api_with_custom_domains_private.json create mode 100644 tests/translator/output/aws-cn/api_with_custom_domains_private.json create mode 100644 tests/translator/output/aws-us-gov/api_with_custom_domains_private.json diff --git a/samtranslator/internal/schema_source/aws_serverless_api.py b/samtranslator/internal/schema_source/aws_serverless_api.py index f4a7471c4..a3672edef 100644 --- a/samtranslator/internal/schema_source/aws_serverless_api.py +++ b/samtranslator/internal/schema_source/aws_serverless_api.py @@ -184,6 +184,24 @@ class Domain(BaseModel): ) +class DomainV2(BaseModel): + BasePath: Optional[PassThroughProp] = domain("BasePath") + NormalizeBasePath: Optional[bool] = domain("NormalizeBasePath") + CertificateArn: PassThroughProp = domain("CertificateArn") + DomainName: PassThroughProp = passthrough_prop( + DOMAIN_STEM, + "DomainName", + ["AWS::ApiGateway::DomainNameV2", "Properties", "DomainName"], + ) + EndpointConfiguration: Optional[SamIntrinsicable[Literal["PRIVATE"]]] = domain("EndpointConfiguration") + Route53: Optional[Route53] = domain("Route53") + SecurityPolicy: Optional[PassThroughProp] = passthrough_prop( + DOMAIN_STEM, + "SecurityPolicy", + ["AWS::ApiGateway::DomainNameV2", "Properties", "SecurityPolicy"], + ) + + class DefinitionUri(BaseModel): Bucket: PassThroughProp = passthrough_prop( DEFINITION_URI_STEM, diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 92a360c11..707f129ac 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -10,8 +10,10 @@ ApiGatewayApiKey, ApiGatewayAuthorizer, ApiGatewayBasePathMapping, + ApiGatewayBasePathMappingV2, ApiGatewayDeployment, ApiGatewayDomainName, + ApiGatewayDomainNameV2, ApiGatewayResponse, ApiGatewayRestApi, ApiGatewayStage, @@ -79,6 +81,13 @@ class ApiDomainResponse: recordset_group: Any +@dataclass +class ApiDomainResponseV2: + domain: Optional[ApiGatewayDomainNameV2] + apigw_basepath_mapping_list: Optional[List[ApiGatewayBasePathMappingV2]] + recordset_group: Any + + class SharedApiUsagePlan: """ Collects API information from different API resources in the same template, @@ -603,6 +612,129 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915 return ApiDomainResponse(domain, basepath_resource_list, record_set_group) + def _construct_api_domain_v2( + self, rest_api: ApiGatewayRestApi, route53_record_set_groups: Any + ) -> ApiDomainResponseV2: + """ + Constructs and returns the ApiGateway Domain and BasepathMapping + """ + if self.domain is None: + return ApiDomainResponseV2(None, None, None) + + sam_expect(self.domain, self.logical_id, "Domain").to_be_a_map() + domain_name: PassThrough = sam_expect( + self.domain.get("DomainName"), self.logical_id, "Domain.DomainName" + ).to_not_be_none() + domain_name_arn: PassThrough = sam_expect( + self.domain.get("DomainNameArn"), self.logical_id, "Domain.DomainNameArn" + ) + certificate_arn: PassThrough = sam_expect( + self.domain.get("CertificateArn"), self.logical_id, "Domain.CertificateArn" + ).to_not_be_none() + + api_domain_name = "{}{}".format("ApiGatewayDomainNameV2", LogicalIdGenerator("", domain_name).gen()) + self.domain["ApiDomainName"] = api_domain_name + domain = ApiGatewayDomainNameV2(api_domain_name, attributes=self.passthrough_resource_attributes) + + domain.DomainName = domain_name + endpoint = self.domain.get("EndpointConfiguration") + + if endpoint not in ["EDGE", "REGIONAL", "PRIVATE"]: + raise InvalidResourceException( + self.logical_id, + "EndpointConfiguration for Custom Domains must be" + " one of {}.".format(["EDGE", "REGIONAL", "PRIVATE"]), + ) + + domain.CertificateArn = certificate_arn + + domain.EndpointConfiguration = {"Types": [endpoint]} + + if self.domain.get("SecurityPolicy", None): + domain.SecurityPolicy = self.domain["SecurityPolicy"] + + if self.domain.get("Policy", None): + domain.Policy = self.domain["Policy"] + + basepaths: Optional[List[str]] + basepath_value = self.domain.get("BasePath") + # Create BasepathMappings + if self.domain.get("BasePath") and isinstance(basepath_value, str): + basepaths = [basepath_value] + elif self.domain.get("BasePath") and isinstance(basepath_value, list): + basepaths = cast(Optional[List[Any]], basepath_value) + else: + basepaths = None + + # Boolean to allow/disallow symbols in BasePath property + normalize_basepath = self.domain.get("NormalizeBasePath", True) + + basepath_resource_list: List[ApiGatewayBasePathMappingV2] = [] + if basepaths is None: + basepath_mapping = ApiGatewayBasePathMappingV2( + self.logical_id + "BasePathMapping", attributes=self.passthrough_resource_attributes + ) + basepath_mapping.DomainNameArn = ref(domain_name_arn) + basepath_mapping.RestApiId = ref(rest_api.logical_id) + basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") + basepath_resource_list.extend([basepath_mapping]) + else: + sam_expect(basepaths, self.logical_id, "Domain.BasePath").to_be_a_list_of(ExpectedType.STRING) + for basepath in basepaths: + # Remove possible leading and trailing '/' because a base path may only + # contain letters, numbers, and one of "$-_.+!*'()" + path = "".join(e for e in basepath if e.isalnum()) + logical_id = "{}{}{}".format(self.logical_id, path, "BasePathMapping") + basepath_mapping = ApiGatewayBasePathMappingV2( + logical_id, attributes=self.passthrough_resource_attributes + ) + basepath_mapping.DomainNameArn = ref(domain_name_arn) + basepath_mapping.RestApiId = ref(rest_api.logical_id) + basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") + basepath_mapping.BasePath = path if normalize_basepath else basepath + basepath_resource_list.extend([basepath_mapping]) + + # Create the Route53 RecordSetGroup resource + record_set_group = None + route53 = self.domain.get("Route53") + if route53 is not None: + sam_expect(route53, self.logical_id, "Domain.Route53").to_be_a_map() + if route53.get("HostedZoneId") is None and route53.get("HostedZoneName") is None: + raise InvalidResourceException( + self.logical_id, + "HostedZoneId or HostedZoneName is required to enable Route53 support on Custom Domains.", + ) + + logical_id_suffix = LogicalIdGenerator( + "", route53.get("HostedZoneId") or route53.get("HostedZoneName") + ).gen() + logical_id = "RecordSetGroup" + logical_id_suffix + + record_set_group = route53_record_set_groups.get(logical_id) + + if route53.get("SeparateRecordSetGroup"): + sam_expect( + route53.get("SeparateRecordSetGroup"), self.logical_id, "Domain.Route53.SeparateRecordSetGroup" + ).to_be_a_bool() + return ApiDomainResponseV2( + domain, + basepath_resource_list, + self._construct_single_record_set_group(self.domain, domain_name, route53), + ) + + if not record_set_group: + record_set_group = Route53RecordSetGroup(logical_id, attributes=self.passthrough_resource_attributes) + if "HostedZoneId" in route53: + record_set_group.HostedZoneId = route53.get("HostedZoneId") + if "HostedZoneName" in route53: + record_set_group.HostedZoneName = route53.get("HostedZoneName") + record_set_group.RecordSets = [] + route53_record_set_groups[logical_id] = record_set_group + + record_set_group.RecordSets += self._construct_record_sets_for_domain(self.domain, domain_name, route53) + + return ApiDomainResponseV2(domain, basepath_resource_list, record_set_group) + def _construct_single_record_set_group( self, domain: Dict[str, Any], api_domain_name: str, route53: Any ) -> Route53RecordSetGroup: @@ -677,9 +809,15 @@ def to_cloudformation( :rtype: tuple """ rest_api = self._construct_rest_api() - api_domain_response = self._construct_api_domain(rest_api, route53_record_set_groups) - domain = api_domain_response.domain - basepath_mapping = api_domain_response.apigw_basepath_mapping_list + if self.endpoint_configuration == "PRIVATE": + api_domain_response = self._construct_api_domain_v2(rest_api, route53_record_set_groups) + domain = api_domain_response.domain + basepath_mapping = api_domain_response.apigw_basepath_mapping_list + else: + api_domain_response = self._construct_api_domain(rest_api, route53_record_set_groups) + domain = api_domain_response.domain + basepath_mapping = api_domain_response.apigw_basepath_mapping_list + route53_recordsetGroup = api_domain_response.recordset_group deployment = self._construct_deployment(rest_api) diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 7d0ab6ffc..0603d78d5 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -230,6 +230,23 @@ class ApiGatewayDomainName(Resource): OwnershipVerificationCertificateArn: Optional[PassThrough] +class ApiGatewayDomainNameV2(Resource): + resource_type = "AWS::ApiGateway::DomainNameV2" + property_types = { + "DomainName": GeneratedProperty(), + "EndpointConfiguration": GeneratedProperty(), + "SecurityPolicy": GeneratedProperty(), + "CertificateArn": GeneratedProperty(), + "Tags": GeneratedProperty(), + } + + DomainName: PassThrough + EndpointConfiguration: Optional[PassThrough] + SecurityPolicy: Optional[PassThrough] + CertificateArn: Optional[PassThrough] + Tags: Optional[PassThrough] + + class ApiGatewayBasePathMapping(Resource): resource_type = "AWS::ApiGateway::BasePathMapping" property_types = { @@ -240,6 +257,16 @@ class ApiGatewayBasePathMapping(Resource): } +class ApiGatewayBasePathMappingV2(Resource): + resource_type = "AWS::ApiGateway::BasePathMappingV2" + property_types = { + "BasePath": GeneratedProperty(), + "DomainNameArn": GeneratedProperty(), + "RestApiId": GeneratedProperty(), + "Stage": GeneratedProperty(), + } + + class ApiGatewayUsagePlan(Resource): resource_type = "AWS::ApiGateway::UsagePlan" property_types = { diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 29d55f348..397b7666d 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -53,6 +53,7 @@ ApiGatewayApiKey, ApiGatewayDeployment, ApiGatewayDomainName, + ApiGatewayDomainNameV2, ApiGatewayStage, ApiGatewayUsagePlan, ApiGatewayUsagePlanKey, @@ -1310,6 +1311,7 @@ class SamApi(SamResourceMacro): "Stage": ApiGatewayStage.resource_type, "Deployment": ApiGatewayDeployment.resource_type, "DomainName": ApiGatewayDomainName.resource_type, + "DomainNameV2": ApiGatewayDomainNameV2.resource_type, "UsagePlan": ApiGatewayUsagePlan.resource_type, "UsagePlanKey": ApiGatewayUsagePlanKey.resource_type, "ApiKey": ApiGatewayApiKey.resource_type, diff --git a/samtranslator/translator/verify_logical_id.py b/samtranslator/translator/verify_logical_id.py index 4dd27882c..510995a83 100644 --- a/samtranslator/translator/verify_logical_id.py +++ b/samtranslator/translator/verify_logical_id.py @@ -15,6 +15,8 @@ "AWS::Cognito::UserPool": "AWS::Cognito::UserPool", "AWS::ApiGateway::DomainName": "AWS::ApiGateway::DomainName", "AWS::ApiGateway::BasePathMapping": "AWS::ApiGateway::BasePathMapping", + "AWS::ApiGateway::DomainNameV2": "AWS::ApiGateway::DomainNameV2", + "AWS::ApiGateway::BasePathMappingV2": "AWS::ApiGateway::BasePathMappingV2", "AWS::StepFunctions::StateMachine": "AWS::Serverless::StateMachine", "AWS::AppSync::GraphQLApi": "AWS::Serverless::GraphQLApi", } diff --git a/tests/translator/input/api_with_custom_domains_private.yaml b/tests/translator/input/api_with_custom_domains_private.yaml new file mode 100644 index 000000000..bbea1932e --- /dev/null +++ b/tests/translator/input/api_with_custom_domains_private.yaml @@ -0,0 +1,69 @@ +Parameters: + DomainName: + Type: String + Default: private.example.com + Description: Custom domain name for the API + + CertificateArn: + Type: String + Default: arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd + Description: ARN of the ACM certificate for the domain + + VpcEndpointId: + Type: String + Default: vpce-abcd1234efg + Description: VPC Endpoint ID for private API access + +Resources: + MyApi: + Type: AWS::Serverless::Api + Properties: + StageName: prod + EndpointConfiguration: + Type: PRIVATE + Auth: + ResourcePolicy: + CustomStatements: + - Effect: Allow + Principal: '*' + Action: execute-api:Invoke + Resource: execute-api:/* + - Effect: Deny + Principal: '*' + Action: execute-api:Invoke + Resource: execute-api:/* + Condition: + StringNotEquals: + aws:SourceVpce: !Ref VpcEndpointId + + ApiDomainName: + Type: AWS::ApiGateway::DomainNameV2 + Properties: + DomainName: !Ref DomainName + EndpointConfiguration: + Types: + - PRIVATE + SecurityPolicy: TLS_1_2 + RegionalCertificateArn: !Ref CertificateArn + + ApiMapping: + Type: AWS::ApiGateway::BasePathMappingV2 + Properties: + DomainName: !Ref ApiDomainName + RestApiId: !Ref MyApi + Stage: prod + + DomainNameVpcEndpointAssociation: + Type: AWS::ApiGateway::VpcEndpointAssociation + Properties: + DomainName: !Ref DomainName + VpcEndpointId: !Ref VpcEndpointId + +Outputs: + ApiDomainName: + Description: Custom Domain Name for the API + Value: !Ref DomainName + + ApiEndpoint: + Description: API Gateway endpoint URL + Value: !Sub https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/ diff --git a/tests/translator/output/api_with_custom_domains_private.json b/tests/translator/output/api_with_custom_domains_private.json new file mode 100644 index 000000000..55bd0ba5c --- /dev/null +++ b/tests/translator/output/api_with_custom_domains_private.json @@ -0,0 +1,142 @@ +{ + "Outputs": { + "ApiDomainName": { + "Description": "Custom Domain Name for the API", + "Value": { + "Ref": "DomainName" + } + }, + "ApiEndpoint": { + "Description": "API Gateway endpoint URL", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/" + } + } + }, + "Parameters": { + "CertificateArn": { + "Default": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "Description": "ARN of the ACM certificate for the domain", + "Type": "String" + }, + "DomainName": { + "Default": "private.example.com", + "Description": "Custom domain name for the API", + "Type": "String" + }, + "VpcEndpointId": { + "Default": "vpce-abcd1234efg", + "Description": "VPC Endpoint ID for private API access", + "Type": "String" + } + }, + "Resources": { + "ApiDomainName": { + "Properties": { + "DomainName": { + "Ref": "DomainName" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ] + }, + "RegionalCertificateArn": { + "Ref": "CertificateArn" + }, + "SecurityPolicy": "TLS_1_2" + }, + "Type": "AWS::ApiGateway::DomainNameV2" + }, + "ApiMapping": { + "Properties": { + "DomainName": { + "Ref": "ApiDomainName" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": "prod" + }, + "Type": "AWS::ApiGateway::BasePathMappingV2" + }, + "DomainNameVpcEndpointAssociation": { + "Properties": { + "DomainName": { + "Ref": "DomainName" + }, + "VpcEndpointId": { + "Ref": "VpcEndpointId" + } + }, + "Type": "AWS::ApiGateway::VpcEndpointAssociation" + }, + "MyApi": { + "Properties": { + "Body": { + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "paths": {}, + "swagger": "2.0", + "x-amazon-apigateway-policy": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Principal": "*", + "Resource": "execute-api:/*" + }, + { + "Action": "execute-api:Invoke", + "Condition": { + "StringNotEquals": { + "aws:SourceVpce": "vpce-abcd1234efg" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": "execute-api:/*" + } + ], + "Version": "2012-10-17" + } + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeployment2ced3f20ee": { + "Properties": { + "Description": "RestApi deployment id: 2ced3f20eef8a8f634f7677e1f11e4eb1a4b6b47", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApiprodStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment2ced3f20ee" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "prod" + }, + "Type": "AWS::ApiGateway::Stage" + } + } +} diff --git a/tests/translator/output/aws-cn/api_with_custom_domains_private.json b/tests/translator/output/aws-cn/api_with_custom_domains_private.json new file mode 100644 index 000000000..55bd0ba5c --- /dev/null +++ b/tests/translator/output/aws-cn/api_with_custom_domains_private.json @@ -0,0 +1,142 @@ +{ + "Outputs": { + "ApiDomainName": { + "Description": "Custom Domain Name for the API", + "Value": { + "Ref": "DomainName" + } + }, + "ApiEndpoint": { + "Description": "API Gateway endpoint URL", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/" + } + } + }, + "Parameters": { + "CertificateArn": { + "Default": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "Description": "ARN of the ACM certificate for the domain", + "Type": "String" + }, + "DomainName": { + "Default": "private.example.com", + "Description": "Custom domain name for the API", + "Type": "String" + }, + "VpcEndpointId": { + "Default": "vpce-abcd1234efg", + "Description": "VPC Endpoint ID for private API access", + "Type": "String" + } + }, + "Resources": { + "ApiDomainName": { + "Properties": { + "DomainName": { + "Ref": "DomainName" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ] + }, + "RegionalCertificateArn": { + "Ref": "CertificateArn" + }, + "SecurityPolicy": "TLS_1_2" + }, + "Type": "AWS::ApiGateway::DomainNameV2" + }, + "ApiMapping": { + "Properties": { + "DomainName": { + "Ref": "ApiDomainName" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": "prod" + }, + "Type": "AWS::ApiGateway::BasePathMappingV2" + }, + "DomainNameVpcEndpointAssociation": { + "Properties": { + "DomainName": { + "Ref": "DomainName" + }, + "VpcEndpointId": { + "Ref": "VpcEndpointId" + } + }, + "Type": "AWS::ApiGateway::VpcEndpointAssociation" + }, + "MyApi": { + "Properties": { + "Body": { + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "paths": {}, + "swagger": "2.0", + "x-amazon-apigateway-policy": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Principal": "*", + "Resource": "execute-api:/*" + }, + { + "Action": "execute-api:Invoke", + "Condition": { + "StringNotEquals": { + "aws:SourceVpce": "vpce-abcd1234efg" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": "execute-api:/*" + } + ], + "Version": "2012-10-17" + } + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeployment2ced3f20ee": { + "Properties": { + "Description": "RestApi deployment id: 2ced3f20eef8a8f634f7677e1f11e4eb1a4b6b47", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApiprodStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment2ced3f20ee" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "prod" + }, + "Type": "AWS::ApiGateway::Stage" + } + } +} diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json new file mode 100644 index 000000000..55bd0ba5c --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json @@ -0,0 +1,142 @@ +{ + "Outputs": { + "ApiDomainName": { + "Description": "Custom Domain Name for the API", + "Value": { + "Ref": "DomainName" + } + }, + "ApiEndpoint": { + "Description": "API Gateway endpoint URL", + "Value": { + "Fn::Sub": "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/" + } + } + }, + "Parameters": { + "CertificateArn": { + "Default": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "Description": "ARN of the ACM certificate for the domain", + "Type": "String" + }, + "DomainName": { + "Default": "private.example.com", + "Description": "Custom domain name for the API", + "Type": "String" + }, + "VpcEndpointId": { + "Default": "vpce-abcd1234efg", + "Description": "VPC Endpoint ID for private API access", + "Type": "String" + } + }, + "Resources": { + "ApiDomainName": { + "Properties": { + "DomainName": { + "Ref": "DomainName" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ] + }, + "RegionalCertificateArn": { + "Ref": "CertificateArn" + }, + "SecurityPolicy": "TLS_1_2" + }, + "Type": "AWS::ApiGateway::DomainNameV2" + }, + "ApiMapping": { + "Properties": { + "DomainName": { + "Ref": "ApiDomainName" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": "prod" + }, + "Type": "AWS::ApiGateway::BasePathMappingV2" + }, + "DomainNameVpcEndpointAssociation": { + "Properties": { + "DomainName": { + "Ref": "DomainName" + }, + "VpcEndpointId": { + "Ref": "VpcEndpointId" + } + }, + "Type": "AWS::ApiGateway::VpcEndpointAssociation" + }, + "MyApi": { + "Properties": { + "Body": { + "info": { + "title": { + "Ref": "AWS::StackName" + }, + "version": "1.0" + }, + "paths": {}, + "swagger": "2.0", + "x-amazon-apigateway-policy": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Principal": "*", + "Resource": "execute-api:/*" + }, + { + "Action": "execute-api:Invoke", + "Condition": { + "StringNotEquals": { + "aws:SourceVpce": "vpce-abcd1234efg" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": "execute-api:/*" + } + ], + "Version": "2012-10-17" + } + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiDeployment2ced3f20ee": { + "Properties": { + "Description": "RestApi deployment id: 2ced3f20eef8a8f634f7677e1f11e4eb1a4b6b47", + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "MyApiprodStage": { + "Properties": { + "DeploymentId": { + "Ref": "MyApiDeployment2ced3f20ee" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "StageName": "prod" + }, + "Type": "AWS::ApiGateway::Stage" + } + } +} diff --git a/tests/translator/test_translator.py b/tests/translator/test_translator.py index 802782666..89840cae9 100644 --- a/tests/translator/test_translator.py +++ b/tests/translator/test_translator.py @@ -331,6 +331,7 @@ def test_transform_feature_toggle(self, testcase, partition_with_region, mock_ge "api_with_custom_domain_route53_hosted_zone_name", "api_with_custom_domain_route53_multiple", "api_with_custom_domain_route53_multiple_intrinsic_hostedzoneid", + "api_with_custom_domains_private", "api_with_basic_custom_domain_http", "api_with_basic_custom_domain_intrinsics_http", "api_with_custom_domain_route53_http", From 54a512beedfe654497cb161b9b32737067d10f9f Mon Sep 17 00:00:00 2001 From: mbfreder Date: Thu, 6 Mar 2025 16:00:51 -0800 Subject: [PATCH 2/7] fix lint issues --- samtranslator/model/api/api_generator.py | 76 ++++++++++++------- .../api_with_custom_domains_private.yaml | 4 +- .../api_with_custom_domains_private.json | 8 +- .../api_with_custom_domains_private.json | 8 +- .../api_with_custom_domains_private.json | 8 +- 5 files changed, 63 insertions(+), 41 deletions(-) diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 707f129ac..786d4504f 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -650,33 +650,16 @@ def _construct_api_domain_v2( domain.EndpointConfiguration = {"Types": [endpoint]} - if self.domain.get("SecurityPolicy", None): - domain.SecurityPolicy = self.domain["SecurityPolicy"] - - if self.domain.get("Policy", None): - domain.Policy = self.domain["Policy"] + self._set_optional_domain_properties(domain) - basepaths: Optional[List[str]] - basepath_value = self.domain.get("BasePath") - # Create BasepathMappings - if self.domain.get("BasePath") and isinstance(basepath_value, str): - basepaths = [basepath_value] - elif self.domain.get("BasePath") and isinstance(basepath_value, list): - basepaths = cast(Optional[List[Any]], basepath_value) - else: - basepaths = None + basepaths: Optional[List[str]] = self._get_basepaths() # Boolean to allow/disallow symbols in BasePath property normalize_basepath = self.domain.get("NormalizeBasePath", True) basepath_resource_list: List[ApiGatewayBasePathMappingV2] = [] if basepaths is None: - basepath_mapping = ApiGatewayBasePathMappingV2( - self.logical_id + "BasePathMapping", attributes=self.passthrough_resource_attributes - ) - basepath_mapping.DomainNameArn = ref(domain_name_arn) - basepath_mapping.RestApiId = ref(rest_api.logical_id) - basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") + basepath_mapping = self._create_basepath_mapping(domain_name_arn, rest_api) basepath_resource_list.extend([basepath_mapping]) else: sam_expect(basepaths, self.logical_id, "Domain.BasePath").to_be_a_list_of(ExpectedType.STRING) @@ -723,18 +706,41 @@ def _construct_api_domain_v2( ) if not record_set_group: - record_set_group = Route53RecordSetGroup(logical_id, attributes=self.passthrough_resource_attributes) - if "HostedZoneId" in route53: - record_set_group.HostedZoneId = route53.get("HostedZoneId") - if "HostedZoneName" in route53: - record_set_group.HostedZoneName = route53.get("HostedZoneName") - record_set_group.RecordSets = [] + record_set_group = self._get_record_set_group(logical_id, route53) route53_record_set_groups[logical_id] = record_set_group record_set_group.RecordSets += self._construct_record_sets_for_domain(self.domain, domain_name, route53) return ApiDomainResponseV2(domain, basepath_resource_list, record_set_group) + def _get_basepaths(self) -> Optional[List[str]]: + if self.domain is None: + return None + basepath_value = self.domain.get("BasePath") + if self.domain.get("BasePath") and isinstance(basepath_value, str): + return [basepath_value] + if self.domain.get("BasePath") and isinstance(basepath_value, list): + return cast(Optional[List[Any]], basepath_value) + return None + + def _set_optional_domain_properties(self, domain: Union[ApiGatewayDomainName, ApiGatewayDomainNameV2]) -> None: + if self.domain is None: + return + if self.domain.get("SecurityPolicy", None): + domain.SecurityPolicy = self.domain["SecurityPolicy"] + + if self.domain.get("Policy", None): + domain.Policy = self.domain["Policy"] + + def _get_record_set_group(self, logical_id: str, route53: Dict[str, Any]) -> Route53RecordSetGroup: + record_set_group = Route53RecordSetGroup(logical_id, attributes=self.passthrough_resource_attributes) + if "HostedZoneId" in route53: + record_set_group.HostedZoneId = route53.get("HostedZoneId") + if "HostedZoneName" in route53: + record_set_group.HostedZoneName = route53.get("HostedZoneName") + record_set_group.RecordSets = [] + return record_set_group + def _construct_single_record_set_group( self, domain: Dict[str, Any], api_domain_name: str, route53: Any ) -> Route53RecordSetGroup: @@ -799,6 +805,17 @@ def _construct_alias_target(self, domain: Dict[str, Any], api_domain_name: str, alias_target["DNSName"] = route53.get("DistributionDomainName") return alias_target + def _create_basepath_mapping( + self, domain_name_arn: PassThrough, rest_api: ApiGatewayRestApi + ) -> ApiGatewayBasePathMappingV2: + basepath_mapping = ApiGatewayBasePathMappingV2( + self.logical_id + "BasePathMapping", attributes=self.passthrough_resource_attributes + ) + basepath_mapping.DomainNameArn = ref(domain_name_arn) + basepath_mapping.RestApiId = ref(rest_api.logical_id) + basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") + return basepath_mapping + @cw_timer(prefix="Generator", name="Api") def to_cloudformation( self, redeploy_restapi_parameters: Optional[Any], route53_record_set_groups: Dict[str, Route53RecordSetGroup] @@ -808,8 +825,11 @@ def to_cloudformation( :returns: a tuple containing the RestApi, Deployment, and Stage for an empty Api. :rtype: tuple """ + api_domain_response: Union[ApiDomainResponseV2, ApiDomainResponse] + domain: Union[Resource, None] + basepath_mapping: Union[List[ApiGatewayBasePathMapping], List[ApiGatewayBasePathMappingV2], None] rest_api = self._construct_rest_api() - if self.endpoint_configuration == "PRIVATE": + if self.endpoint_configuration and self.endpoint_configuration.get("Type") == "PRIVATE": api_domain_response = self._construct_api_domain_v2(rest_api, route53_record_set_groups) domain = api_domain_response.domain basepath_mapping = api_domain_response.apigw_basepath_mapping_list @@ -840,7 +860,9 @@ def to_cloudformation( List[Resource], Tuple[Resource], List[LambdaPermission], + List[Resource], List[ApiGatewayBasePathMapping], + List[ApiGatewayBasePathMappingV2], ], ] = [] diff --git a/tests/translator/input/api_with_custom_domains_private.yaml b/tests/translator/input/api_with_custom_domains_private.yaml index bbea1932e..8e88e24d6 100644 --- a/tests/translator/input/api_with_custom_domains_private.yaml +++ b/tests/translator/input/api_with_custom_domains_private.yaml @@ -44,12 +44,12 @@ Resources: Types: - PRIVATE SecurityPolicy: TLS_1_2 - RegionalCertificateArn: !Ref CertificateArn + CertificateArn: !Ref CertificateArn ApiMapping: Type: AWS::ApiGateway::BasePathMappingV2 Properties: - DomainName: !Ref ApiDomainName + DomainNameArn: !Ref ApiDomainName RestApiId: !Ref MyApi Stage: prod diff --git a/tests/translator/output/api_with_custom_domains_private.json b/tests/translator/output/api_with_custom_domains_private.json index 55bd0ba5c..c4e2365c1 100644 --- a/tests/translator/output/api_with_custom_domains_private.json +++ b/tests/translator/output/api_with_custom_domains_private.json @@ -33,6 +33,9 @@ "Resources": { "ApiDomainName": { "Properties": { + "CertificateArn": { + "Ref": "CertificateArn" + }, "DomainName": { "Ref": "DomainName" }, @@ -41,16 +44,13 @@ "PRIVATE" ] }, - "RegionalCertificateArn": { - "Ref": "CertificateArn" - }, "SecurityPolicy": "TLS_1_2" }, "Type": "AWS::ApiGateway::DomainNameV2" }, "ApiMapping": { "Properties": { - "DomainName": { + "DomainNameArn": { "Ref": "ApiDomainName" }, "RestApiId": { diff --git a/tests/translator/output/aws-cn/api_with_custom_domains_private.json b/tests/translator/output/aws-cn/api_with_custom_domains_private.json index 55bd0ba5c..c4e2365c1 100644 --- a/tests/translator/output/aws-cn/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-cn/api_with_custom_domains_private.json @@ -33,6 +33,9 @@ "Resources": { "ApiDomainName": { "Properties": { + "CertificateArn": { + "Ref": "CertificateArn" + }, "DomainName": { "Ref": "DomainName" }, @@ -41,16 +44,13 @@ "PRIVATE" ] }, - "RegionalCertificateArn": { - "Ref": "CertificateArn" - }, "SecurityPolicy": "TLS_1_2" }, "Type": "AWS::ApiGateway::DomainNameV2" }, "ApiMapping": { "Properties": { - "DomainName": { + "DomainNameArn": { "Ref": "ApiDomainName" }, "RestApiId": { diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json index 55bd0ba5c..c4e2365c1 100644 --- a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json @@ -33,6 +33,9 @@ "Resources": { "ApiDomainName": { "Properties": { + "CertificateArn": { + "Ref": "CertificateArn" + }, "DomainName": { "Ref": "DomainName" }, @@ -41,16 +44,13 @@ "PRIVATE" ] }, - "RegionalCertificateArn": { - "Ref": "CertificateArn" - }, "SecurityPolicy": "TLS_1_2" }, "Type": "AWS::ApiGateway::DomainNameV2" }, "ApiMapping": { "Properties": { - "DomainName": { + "DomainNameArn": { "Ref": "ApiDomainName" }, "RestApiId": { From 3057a36bd903b5ad20d56a20f2d69fae042024e3 Mon Sep 17 00:00:00 2001 From: mbfreder Date: Fri, 7 Mar 2025 14:40:24 -0800 Subject: [PATCH 3/7] more refactoring --- samtranslator/model/api/api_generator.py | 59 +++++++++++++----------- 1 file changed, 33 insertions(+), 26 deletions(-) diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 786d4504f..ef766d109 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -526,11 +526,7 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915 if mutual_tls_auth.get("TruststoreVersion", None): domain.MutualTlsAuthentication["TruststoreVersion"] = mutual_tls_auth["TruststoreVersion"] - if self.domain.get("SecurityPolicy", None): - domain.SecurityPolicy = self.domain["SecurityPolicy"] - - if self.domain.get("OwnershipVerificationCertificateArn", None): - domain.OwnershipVerificationCertificateArn = self.domain["OwnershipVerificationCertificateArn"] + self._set_optional_domain_properties(domain) basepaths: Optional[List[str]] basepath_value = self.domain.get("BasePath") @@ -548,12 +544,7 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915 basepath_resource_list: List[ApiGatewayBasePathMapping] = [] if basepaths is None: - basepath_mapping = ApiGatewayBasePathMapping( - self.logical_id + "BasePathMapping", attributes=self.passthrough_resource_attributes - ) - basepath_mapping.DomainName = ref(api_domain_name) - basepath_mapping.RestApiId = ref(rest_api.logical_id) - basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") + basepath_mapping = self._create_basepath_mapping(api_domain_name, rest_api, None, None) basepath_resource_list.extend([basepath_mapping]) else: sam_expect(basepaths, self.logical_id, "Domain.BasePath").to_be_a_list_of(ExpectedType.STRING) @@ -561,14 +552,11 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915 # Remove possible leading and trailing '/' because a base path may only # contain letters, numbers, and one of "$-_.+!*'()" path = "".join(e for e in basepath if e.isalnum()) + mapping_basepath = path if normalize_basepath else basepath logical_id = "{}{}{}".format(self.logical_id, path, "BasePathMapping") - basepath_mapping = ApiGatewayBasePathMapping( - logical_id, attributes=self.passthrough_resource_attributes + basepath_mapping = self._create_basepath_mapping( + api_domain_name, rest_api, logical_id, mapping_basepath ) - basepath_mapping.DomainName = ref(api_domain_name) - basepath_mapping.RestApiId = ref(rest_api.logical_id) - basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") - basepath_mapping.BasePath = path if normalize_basepath else basepath basepath_resource_list.extend([basepath_mapping]) # Create the Route53 RecordSetGroup resource @@ -600,12 +588,7 @@ def _construct_api_domain( # noqa: PLR0912, PLR0915 ) if not record_set_group: - record_set_group = Route53RecordSetGroup(logical_id, attributes=self.passthrough_resource_attributes) - if "HostedZoneId" in route53: - record_set_group.HostedZoneId = route53.get("HostedZoneId") - if "HostedZoneName" in route53: - record_set_group.HostedZoneName = route53.get("HostedZoneName") - record_set_group.RecordSets = [] + record_set_group = self._get_record_set_group(logical_id, route53) route53_record_set_groups[logical_id] = record_set_group record_set_group.RecordSets += self._construct_record_sets_for_domain(self.domain, api_domain_name, route53) @@ -616,7 +599,7 @@ def _construct_api_domain_v2( self, rest_api: ApiGatewayRestApi, route53_record_set_groups: Any ) -> ApiDomainResponseV2: """ - Constructs and returns the ApiGateway Domain and BasepathMapping + Constructs and returns the ApiGateway Domain V2 and BasepathMapping V2 """ if self.domain is None: return ApiDomainResponseV2(None, None, None) @@ -659,7 +642,7 @@ def _construct_api_domain_v2( basepath_resource_list: List[ApiGatewayBasePathMappingV2] = [] if basepaths is None: - basepath_mapping = self._create_basepath_mapping(domain_name_arn, rest_api) + basepath_mapping = self._create_basepath_mapping_v2(domain_name_arn, rest_api) basepath_resource_list.extend([basepath_mapping]) else: sam_expect(basepaths, self.logical_id, "Domain.BasePath").to_be_a_list_of(ExpectedType.STRING) @@ -728,9 +711,10 @@ def _set_optional_domain_properties(self, domain: Union[ApiGatewayDomainName, Ap return if self.domain.get("SecurityPolicy", None): domain.SecurityPolicy = self.domain["SecurityPolicy"] - if self.domain.get("Policy", None): domain.Policy = self.domain["Policy"] + if self.domain.get("OwnershipVerificationCertificateArn", None): + domain.OwnershipVerificationCertificateArn = self.domain["OwnershipVerificationCertificateArn"] def _get_record_set_group(self, logical_id: str, route53: Dict[str, Any]) -> Route53RecordSetGroup: record_set_group = Route53RecordSetGroup(logical_id, attributes=self.passthrough_resource_attributes) @@ -806,6 +790,29 @@ def _construct_alias_target(self, domain: Dict[str, Any], api_domain_name: str, return alias_target def _create_basepath_mapping( + self, + api_domain_name: PassThrough, + rest_api: ApiGatewayRestApi, + logical_id: Optional[str], + basepath: Optional[str], + ) -> ApiGatewayBasePathMapping: + + basepath_mapping: ApiGatewayBasePathMapping + basepath_mapping = ( + ApiGatewayBasePathMapping(logical_id, attributes=self.passthrough_resource_attributes) + if logical_id + else ApiGatewayBasePathMapping( + self.logical_id + "BasePathMapping", attributes=self.passthrough_resource_attributes + ) + ) + basepath_mapping.DomainName = ref(api_domain_name) + basepath_mapping.RestApiId = ref(rest_api.logical_id) + basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") + if basepath: + basepath_mapping.BasePath = basepath + return basepath_mapping + + def _create_basepath_mapping_v2( self, domain_name_arn: PassThrough, rest_api: ApiGatewayRestApi ) -> ApiGatewayBasePathMappingV2: basepath_mapping = ApiGatewayBasePathMappingV2( From f79bc2edb04d340554bf111f26c9c3872d244ee7 Mon Sep 17 00:00:00 2001 From: mbfreder Date: Mon, 17 Mar 2025 15:09:28 -0700 Subject: [PATCH 4/7] Generate correct V2 resources --- .cfnlintrc.yaml | 1 + .../schema_source/aws_serverless_api.py | 22 +------ samtranslator/model/api/api_generator.py | 11 ++-- samtranslator/schema/schema.json | 3 +- schema_source/sam.schema.json | 3 +- .../api_with_custom_domains_private.yaml | 29 ++------- .../api_with_custom_domains_private.json | 62 ++++++------------- .../api_with_custom_domains_private.json | 60 +++++++----------- .../api_with_custom_domains_private.json | 60 +++++++----------- 9 files changed, 82 insertions(+), 169 deletions(-) diff --git a/.cfnlintrc.yaml b/.cfnlintrc.yaml index ab3c4b409..552c6b1b6 100644 --- a/.cfnlintrc.yaml +++ b/.cfnlintrc.yaml @@ -139,6 +139,7 @@ ignore_templates: - tests/translator/output/**/managed_policies_everything.json # intentionally contains wrong arns - tests/translator/output/**/function_with_provisioned_poller_config.json - tests/translator/output/**/function_with_metrics_config.json + - tests/translator/output/**/api_with_custom_domains_private.json ignore_checks: - E2531 # Deprecated runtime; not relevant for transform tests diff --git a/samtranslator/internal/schema_source/aws_serverless_api.py b/samtranslator/internal/schema_source/aws_serverless_api.py index a3672edef..1379d0a57 100644 --- a/samtranslator/internal/schema_source/aws_serverless_api.py +++ b/samtranslator/internal/schema_source/aws_serverless_api.py @@ -165,7 +165,9 @@ class Domain(BaseModel): "DomainName", ["AWS::ApiGateway::DomainName", "Properties", "DomainName"], ) - EndpointConfiguration: Optional[SamIntrinsicable[Literal["REGIONAL", "EDGE"]]] = domain("EndpointConfiguration") + EndpointConfiguration: Optional[SamIntrinsicable[Literal["REGIONAL", "EDGE", "PRIVATE"]]] = domain( + "EndpointConfiguration" + ) MutualTlsAuthentication: Optional[PassThroughProp] = passthrough_prop( DOMAIN_STEM, "MutualTlsAuthentication", @@ -184,24 +186,6 @@ class Domain(BaseModel): ) -class DomainV2(BaseModel): - BasePath: Optional[PassThroughProp] = domain("BasePath") - NormalizeBasePath: Optional[bool] = domain("NormalizeBasePath") - CertificateArn: PassThroughProp = domain("CertificateArn") - DomainName: PassThroughProp = passthrough_prop( - DOMAIN_STEM, - "DomainName", - ["AWS::ApiGateway::DomainNameV2", "Properties", "DomainName"], - ) - EndpointConfiguration: Optional[SamIntrinsicable[Literal["PRIVATE"]]] = domain("EndpointConfiguration") - Route53: Optional[Route53] = domain("Route53") - SecurityPolicy: Optional[PassThroughProp] = passthrough_prop( - DOMAIN_STEM, - "SecurityPolicy", - ["AWS::ApiGateway::DomainNameV2", "Properties", "SecurityPolicy"], - ) - - class DefinitionUri(BaseModel): Bucket: PassThroughProp = passthrough_prop( DEFINITION_URI_STEM, diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index ef766d109..4c311d626 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -608,15 +608,12 @@ def _construct_api_domain_v2( domain_name: PassThrough = sam_expect( self.domain.get("DomainName"), self.logical_id, "Domain.DomainName" ).to_not_be_none() - domain_name_arn: PassThrough = sam_expect( - self.domain.get("DomainNameArn"), self.logical_id, "Domain.DomainNameArn" - ) certificate_arn: PassThrough = sam_expect( self.domain.get("CertificateArn"), self.logical_id, "Domain.CertificateArn" ).to_not_be_none() api_domain_name = "{}{}".format("ApiGatewayDomainNameV2", LogicalIdGenerator("", domain_name).gen()) - self.domain["ApiDomainName"] = api_domain_name + domain_name_arn = ref(api_domain_name) domain = ApiGatewayDomainNameV2(api_domain_name, attributes=self.passthrough_resource_attributes) domain.DomainName = domain_name @@ -654,7 +651,7 @@ def _construct_api_domain_v2( basepath_mapping = ApiGatewayBasePathMappingV2( logical_id, attributes=self.passthrough_resource_attributes ) - basepath_mapping.DomainNameArn = ref(domain_name_arn) + basepath_mapping.DomainNameArn = domain_name_arn basepath_mapping.RestApiId = ref(rest_api.logical_id) basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") basepath_mapping.BasePath = path if normalize_basepath else basepath @@ -818,7 +815,7 @@ def _create_basepath_mapping_v2( basepath_mapping = ApiGatewayBasePathMappingV2( self.logical_id + "BasePathMapping", attributes=self.passthrough_resource_attributes ) - basepath_mapping.DomainNameArn = ref(domain_name_arn) + basepath_mapping.DomainNameArn = domain_name_arn basepath_mapping.RestApiId = ref(rest_api.logical_id) basepath_mapping.Stage = ref(rest_api.logical_id + ".Stage") return basepath_mapping @@ -836,7 +833,7 @@ def to_cloudformation( domain: Union[Resource, None] basepath_mapping: Union[List[ApiGatewayBasePathMapping], List[ApiGatewayBasePathMappingV2], None] rest_api = self._construct_rest_api() - if self.endpoint_configuration and self.endpoint_configuration.get("Type") == "PRIVATE": + if isinstance(self.domain, dict) and self.domain.get("EndpointConfiguration") == "PRIVATE": api_domain_response = self._construct_api_domain_v2(rest_api, route53_record_set_groups) domain = api_domain_response.domain basepath_mapping = api_domain_response.apigw_basepath_mapping_list diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index f4a08880f..29e596b09 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -277250,7 +277250,8 @@ { "enum": [ "REGIONAL", - "EDGE" + "EDGE", + "PRIVATE" ], "type": "string" } diff --git a/schema_source/sam.schema.json b/schema_source/sam.schema.json index b74f1db9f..548b98c84 100644 --- a/schema_source/sam.schema.json +++ b/schema_source/sam.schema.json @@ -3618,7 +3618,8 @@ { "enum": [ "REGIONAL", - "EDGE" + "EDGE", + "PRIVATE" ], "type": "string" } diff --git a/tests/translator/input/api_with_custom_domains_private.yaml b/tests/translator/input/api_with_custom_domains_private.yaml index 8e88e24d6..2a9fb27b2 100644 --- a/tests/translator/input/api_with_custom_domains_private.yaml +++ b/tests/translator/input/api_with_custom_domains_private.yaml @@ -19,8 +19,10 @@ Resources: Type: AWS::Serverless::Api Properties: StageName: prod - EndpointConfiguration: - Type: PRIVATE + Domain: + DomainName: !Ref DomainName + CertificateArn: !Ref CertificateArn + EndpointConfiguration: PRIVATE Auth: ResourcePolicy: CustomStatements: @@ -36,29 +38,6 @@ Resources: StringNotEquals: aws:SourceVpce: !Ref VpcEndpointId - ApiDomainName: - Type: AWS::ApiGateway::DomainNameV2 - Properties: - DomainName: !Ref DomainName - EndpointConfiguration: - Types: - - PRIVATE - SecurityPolicy: TLS_1_2 - CertificateArn: !Ref CertificateArn - - ApiMapping: - Type: AWS::ApiGateway::BasePathMappingV2 - Properties: - DomainNameArn: !Ref ApiDomainName - RestApiId: !Ref MyApi - Stage: prod - - DomainNameVpcEndpointAssociation: - Type: AWS::ApiGateway::VpcEndpointAssociation - Properties: - DomainName: !Ref DomainName - VpcEndpointId: !Ref VpcEndpointId - Outputs: ApiDomainName: Description: Custom Domain Name for the API diff --git a/tests/translator/output/api_with_custom_domains_private.json b/tests/translator/output/api_with_custom_domains_private.json index c4e2365c1..d0733be50 100644 --- a/tests/translator/output/api_with_custom_domains_private.json +++ b/tests/translator/output/api_with_custom_domains_private.json @@ -31,45 +31,17 @@ } }, "Resources": { - "ApiDomainName": { + "ApiGatewayDomainNameV27c603ed871": { "Properties": { - "CertificateArn": { - "Ref": "CertificateArn" - }, - "DomainName": { - "Ref": "DomainName" - }, + "CertificateArn": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "DomainName": "private.example.com", "EndpointConfiguration": { "Types": [ "PRIVATE" ] - }, - "SecurityPolicy": "TLS_1_2" - }, - "Type": "AWS::ApiGateway::DomainNameV2" - }, - "ApiMapping": { - "Properties": { - "DomainNameArn": { - "Ref": "ApiDomainName" - }, - "RestApiId": { - "Ref": "MyApi" - }, - "Stage": "prod" - }, - "Type": "AWS::ApiGateway::BasePathMappingV2" - }, - "DomainNameVpcEndpointAssociation": { - "Properties": { - "DomainName": { - "Ref": "DomainName" - }, - "VpcEndpointId": { - "Ref": "VpcEndpointId" } }, - "Type": "AWS::ApiGateway::VpcEndpointAssociation" + "Type": "AWS::ApiGateway::DomainNameV2" }, "MyApi": { "Properties": { @@ -104,21 +76,27 @@ ], "Version": "2012-10-17" } + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "MyApiBasePathMapping": { + "Properties": { + "DomainNameArn": { + "Ref": "ApiGatewayDomainNameV27c603ed871" }, - "EndpointConfiguration": { - "Types": [ - "PRIVATE" - ] + "RestApiId": { + "Ref": "MyApi" }, - "Parameters": { - "endpointConfigurationTypes": "PRIVATE" + "Stage": { + "Ref": "MyApiprodStage" } }, - "Type": "AWS::ApiGateway::RestApi" + "Type": "AWS::ApiGateway::BasePathMappingV2" }, - "MyApiDeployment2ced3f20ee": { + "MyApiDeployment2a2c7964a1": { "Properties": { - "Description": "RestApi deployment id: 2ced3f20eef8a8f634f7677e1f11e4eb1a4b6b47", + "Description": "RestApi deployment id: 2a2c7964a1e48b03d060fa6af26d333717eb4f0f", "RestApiId": { "Ref": "MyApi" }, @@ -129,7 +107,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment2ced3f20ee" + "Ref": "MyApiDeployment2a2c7964a1" }, "RestApiId": { "Ref": "MyApi" diff --git a/tests/translator/output/aws-cn/api_with_custom_domains_private.json b/tests/translator/output/aws-cn/api_with_custom_domains_private.json index c4e2365c1..0619c4aca 100644 --- a/tests/translator/output/aws-cn/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-cn/api_with_custom_domains_private.json @@ -31,45 +31,17 @@ } }, "Resources": { - "ApiDomainName": { + "ApiGatewayDomainNameV27c603ed871": { "Properties": { - "CertificateArn": { - "Ref": "CertificateArn" - }, - "DomainName": { - "Ref": "DomainName" - }, + "CertificateArn": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "DomainName": "private.example.com", "EndpointConfiguration": { "Types": [ "PRIVATE" ] - }, - "SecurityPolicy": "TLS_1_2" - }, - "Type": "AWS::ApiGateway::DomainNameV2" - }, - "ApiMapping": { - "Properties": { - "DomainNameArn": { - "Ref": "ApiDomainName" - }, - "RestApiId": { - "Ref": "MyApi" - }, - "Stage": "prod" - }, - "Type": "AWS::ApiGateway::BasePathMappingV2" - }, - "DomainNameVpcEndpointAssociation": { - "Properties": { - "DomainName": { - "Ref": "DomainName" - }, - "VpcEndpointId": { - "Ref": "VpcEndpointId" } }, - "Type": "AWS::ApiGateway::VpcEndpointAssociation" + "Type": "AWS::ApiGateway::DomainNameV2" }, "MyApi": { "Properties": { @@ -107,18 +79,32 @@ }, "EndpointConfiguration": { "Types": [ - "PRIVATE" + "REGIONAL" ] }, "Parameters": { - "endpointConfigurationTypes": "PRIVATE" + "endpointConfigurationTypes": "REGIONAL" } }, "Type": "AWS::ApiGateway::RestApi" }, - "MyApiDeployment2ced3f20ee": { + "MyApiBasePathMapping": { + "Properties": { + "DomainNameArn": { + "Ref": "ApiGatewayDomainNameV27c603ed871" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": { + "Ref": "MyApiprodStage" + } + }, + "Type": "AWS::ApiGateway::BasePathMappingV2" + }, + "MyApiDeployment2a2c7964a1": { "Properties": { - "Description": "RestApi deployment id: 2ced3f20eef8a8f634f7677e1f11e4eb1a4b6b47", + "Description": "RestApi deployment id: 2a2c7964a1e48b03d060fa6af26d333717eb4f0f", "RestApiId": { "Ref": "MyApi" }, @@ -129,7 +115,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment2ced3f20ee" + "Ref": "MyApiDeployment2a2c7964a1" }, "RestApiId": { "Ref": "MyApi" diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json index c4e2365c1..0619c4aca 100644 --- a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json @@ -31,45 +31,17 @@ } }, "Resources": { - "ApiDomainName": { + "ApiGatewayDomainNameV27c603ed871": { "Properties": { - "CertificateArn": { - "Ref": "CertificateArn" - }, - "DomainName": { - "Ref": "DomainName" - }, + "CertificateArn": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "DomainName": "private.example.com", "EndpointConfiguration": { "Types": [ "PRIVATE" ] - }, - "SecurityPolicy": "TLS_1_2" - }, - "Type": "AWS::ApiGateway::DomainNameV2" - }, - "ApiMapping": { - "Properties": { - "DomainNameArn": { - "Ref": "ApiDomainName" - }, - "RestApiId": { - "Ref": "MyApi" - }, - "Stage": "prod" - }, - "Type": "AWS::ApiGateway::BasePathMappingV2" - }, - "DomainNameVpcEndpointAssociation": { - "Properties": { - "DomainName": { - "Ref": "DomainName" - }, - "VpcEndpointId": { - "Ref": "VpcEndpointId" } }, - "Type": "AWS::ApiGateway::VpcEndpointAssociation" + "Type": "AWS::ApiGateway::DomainNameV2" }, "MyApi": { "Properties": { @@ -107,18 +79,32 @@ }, "EndpointConfiguration": { "Types": [ - "PRIVATE" + "REGIONAL" ] }, "Parameters": { - "endpointConfigurationTypes": "PRIVATE" + "endpointConfigurationTypes": "REGIONAL" } }, "Type": "AWS::ApiGateway::RestApi" }, - "MyApiDeployment2ced3f20ee": { + "MyApiBasePathMapping": { + "Properties": { + "DomainNameArn": { + "Ref": "ApiGatewayDomainNameV27c603ed871" + }, + "RestApiId": { + "Ref": "MyApi" + }, + "Stage": { + "Ref": "MyApiprodStage" + } + }, + "Type": "AWS::ApiGateway::BasePathMappingV2" + }, + "MyApiDeployment2a2c7964a1": { "Properties": { - "Description": "RestApi deployment id: 2ced3f20eef8a8f634f7677e1f11e4eb1a4b6b47", + "Description": "RestApi deployment id: 2a2c7964a1e48b03d060fa6af26d333717eb4f0f", "RestApiId": { "Ref": "MyApi" }, @@ -129,7 +115,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment2ced3f20ee" + "Ref": "MyApiDeployment2a2c7964a1" }, "RestApiId": { "Ref": "MyApi" From 2e8184c5fd91982a505e11d7513bd293018803e4 Mon Sep 17 00:00:00 2001 From: mbfreder Date: Mon, 17 Mar 2025 17:07:36 -0700 Subject: [PATCH 5/7] Add Policy property to AWS::Serverless::Api.Domain --- .../schema_source/aws_serverless_api.py | 1 + samtranslator/model/apigateway.py | 2 ++ samtranslator/schema/schema.json | 3 ++ schema_source/sam.schema.json | 3 ++ .../api_with_custom_domains_private.yaml | 14 ++++++++++ .../api_with_custom_domains_private.json | 28 +++++++++++++++++-- .../api_with_custom_domains_private.json | 28 +++++++++++++++++-- .../api_with_custom_domains_private.json | 28 +++++++++++++++++-- 8 files changed, 98 insertions(+), 9 deletions(-) diff --git a/samtranslator/internal/schema_source/aws_serverless_api.py b/samtranslator/internal/schema_source/aws_serverless_api.py index 1379d0a57..bb315b2d6 100644 --- a/samtranslator/internal/schema_source/aws_serverless_api.py +++ b/samtranslator/internal/schema_source/aws_serverless_api.py @@ -159,6 +159,7 @@ class Route53(BaseModel): class Domain(BaseModel): BasePath: Optional[PassThroughProp] = domain("BasePath") NormalizeBasePath: Optional[bool] = domain("NormalizeBasePath") + Policy: Optional[PassThroughProp] CertificateArn: PassThroughProp = domain("CertificateArn") DomainName: PassThroughProp = passthrough_prop( DOMAIN_STEM, diff --git a/samtranslator/model/apigateway.py b/samtranslator/model/apigateway.py index 0603d78d5..d8aeddb94 100644 --- a/samtranslator/model/apigateway.py +++ b/samtranslator/model/apigateway.py @@ -238,6 +238,7 @@ class ApiGatewayDomainNameV2(Resource): "SecurityPolicy": GeneratedProperty(), "CertificateArn": GeneratedProperty(), "Tags": GeneratedProperty(), + "Policy": GeneratedProperty(), } DomainName: PassThrough @@ -245,6 +246,7 @@ class ApiGatewayDomainNameV2(Resource): SecurityPolicy: Optional[PassThrough] CertificateArn: Optional[PassThrough] Tags: Optional[PassThrough] + Policy: Optional[PassThrough] class ApiGatewayBasePathMapping(Resource): diff --git a/samtranslator/schema/schema.json b/samtranslator/schema/schema.json index 29e596b09..b8dc13fb1 100644 --- a/samtranslator/schema/schema.json +++ b/samtranslator/schema/schema.json @@ -277274,6 +277274,9 @@ "title": "OwnershipVerificationCertificateArn", "type": "string" }, + "Policy": { + "$ref": "#/definitions/PassThroughProp" + }, "Route53": { "allOf": [ { diff --git a/schema_source/sam.schema.json b/schema_source/sam.schema.json index 548b98c84..4a5dbf6e0 100644 --- a/schema_source/sam.schema.json +++ b/schema_source/sam.schema.json @@ -3670,6 +3670,9 @@ ], "title": "OwnershipVerificationCertificateArn" }, + "Policy": { + "$ref": "#/definitions/PassThroughProp" + }, "Route53": { "allOf": [ { diff --git a/tests/translator/input/api_with_custom_domains_private.yaml b/tests/translator/input/api_with_custom_domains_private.yaml index 2a9fb27b2..0eb302ae5 100644 --- a/tests/translator/input/api_with_custom_domains_private.yaml +++ b/tests/translator/input/api_with_custom_domains_private.yaml @@ -23,6 +23,20 @@ Resources: DomainName: !Ref DomainName CertificateArn: !Ref CertificateArn EndpointConfiguration: PRIVATE + Policy: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: '*' + Action: execute-api:Invoke + Resource: execute-api:/* + - Effect: Deny + Principal: '*' + Action: execute-api:Invoke + Resource: execute-api:/* + Condition: + StringNotEquals: + aws:SourceVpce: !Ref VpcEndpointId Auth: ResourcePolicy: CustomStatements: diff --git a/tests/translator/output/api_with_custom_domains_private.json b/tests/translator/output/api_with_custom_domains_private.json index d0733be50..c7c9d382a 100644 --- a/tests/translator/output/api_with_custom_domains_private.json +++ b/tests/translator/output/api_with_custom_domains_private.json @@ -39,6 +39,28 @@ "Types": [ "PRIVATE" ] + }, + "Policy": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Principal": "*", + "Resource": "execute-api:/*" + }, + { + "Action": "execute-api:Invoke", + "Condition": { + "StringNotEquals": { + "aws:SourceVpce": "vpce-abcd1234efg" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": "execute-api:/*" + } + ], + "Version": "2012-10-17" } }, "Type": "AWS::ApiGateway::DomainNameV2" @@ -94,9 +116,9 @@ }, "Type": "AWS::ApiGateway::BasePathMappingV2" }, - "MyApiDeployment2a2c7964a1": { + "MyApiDeployment305713bc0b": { "Properties": { - "Description": "RestApi deployment id: 2a2c7964a1e48b03d060fa6af26d333717eb4f0f", + "Description": "RestApi deployment id: 305713bc0b043df7d6fdb169ecf2f9d9844519da", "RestApiId": { "Ref": "MyApi" }, @@ -107,7 +129,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment2a2c7964a1" + "Ref": "MyApiDeployment305713bc0b" }, "RestApiId": { "Ref": "MyApi" diff --git a/tests/translator/output/aws-cn/api_with_custom_domains_private.json b/tests/translator/output/aws-cn/api_with_custom_domains_private.json index 0619c4aca..7d64f8b73 100644 --- a/tests/translator/output/aws-cn/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-cn/api_with_custom_domains_private.json @@ -39,6 +39,28 @@ "Types": [ "PRIVATE" ] + }, + "Policy": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Principal": "*", + "Resource": "execute-api:/*" + }, + { + "Action": "execute-api:Invoke", + "Condition": { + "StringNotEquals": { + "aws:SourceVpce": "vpce-abcd1234efg" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": "execute-api:/*" + } + ], + "Version": "2012-10-17" } }, "Type": "AWS::ApiGateway::DomainNameV2" @@ -102,9 +124,9 @@ }, "Type": "AWS::ApiGateway::BasePathMappingV2" }, - "MyApiDeployment2a2c7964a1": { + "MyApiDeployment305713bc0b": { "Properties": { - "Description": "RestApi deployment id: 2a2c7964a1e48b03d060fa6af26d333717eb4f0f", + "Description": "RestApi deployment id: 305713bc0b043df7d6fdb169ecf2f9d9844519da", "RestApiId": { "Ref": "MyApi" }, @@ -115,7 +137,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment2a2c7964a1" + "Ref": "MyApiDeployment305713bc0b" }, "RestApiId": { "Ref": "MyApi" diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json index 0619c4aca..7d64f8b73 100644 --- a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json @@ -39,6 +39,28 @@ "Types": [ "PRIVATE" ] + }, + "Policy": { + "Statement": [ + { + "Action": "execute-api:Invoke", + "Effect": "Allow", + "Principal": "*", + "Resource": "execute-api:/*" + }, + { + "Action": "execute-api:Invoke", + "Condition": { + "StringNotEquals": { + "aws:SourceVpce": "vpce-abcd1234efg" + } + }, + "Effect": "Deny", + "Principal": "*", + "Resource": "execute-api:/*" + } + ], + "Version": "2012-10-17" } }, "Type": "AWS::ApiGateway::DomainNameV2" @@ -102,9 +124,9 @@ }, "Type": "AWS::ApiGateway::BasePathMappingV2" }, - "MyApiDeployment2a2c7964a1": { + "MyApiDeployment305713bc0b": { "Properties": { - "Description": "RestApi deployment id: 2a2c7964a1e48b03d060fa6af26d333717eb4f0f", + "Description": "RestApi deployment id: 305713bc0b043df7d6fdb169ecf2f9d9844519da", "RestApiId": { "Ref": "MyApi" }, @@ -115,7 +137,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment2a2c7964a1" + "Ref": "MyApiDeployment305713bc0b" }, "RestApiId": { "Ref": "MyApi" From c365395f9f318c013d06d1aa95ea7b88fa565467 Mon Sep 17 00:00:00 2001 From: mbfreder Date: Tue, 18 Mar 2025 13:52:10 -0700 Subject: [PATCH 6/7] Address comments --- samtranslator/model/api/api_generator.py | 17 ++++++++--------- .../input/api_with_custom_domains_private.yaml | 2 +- .../output/api_with_custom_domains_private.json | 10 +++++----- .../aws-cn/api_with_custom_domains_private.json | 10 +++++----- .../api_with_custom_domains_private.json | 10 +++++----- 5 files changed, 24 insertions(+), 25 deletions(-) diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 4c311d626..1bad8d41c 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -833,14 +833,14 @@ def to_cloudformation( domain: Union[Resource, None] basepath_mapping: Union[List[ApiGatewayBasePathMapping], List[ApiGatewayBasePathMappingV2], None] rest_api = self._construct_rest_api() - if isinstance(self.domain, dict) and self.domain.get("EndpointConfiguration") == "PRIVATE": - api_domain_response = self._construct_api_domain_v2(rest_api, route53_record_set_groups) - domain = api_domain_response.domain - basepath_mapping = api_domain_response.apigw_basepath_mapping_list - else: - api_domain_response = self._construct_api_domain(rest_api, route53_record_set_groups) - domain = api_domain_response.domain - basepath_mapping = api_domain_response.apigw_basepath_mapping_list + api_domain_response = ( + self._construct_api_domain_v2(rest_api, route53_record_set_groups) + if isinstance(self.domain, dict) and self.domain.get("EndpointConfiguration") == "PRIVATE" + else self._construct_api_domain(rest_api, route53_record_set_groups) + ) + + domain = api_domain_response.domain + basepath_mapping = api_domain_response.apigw_basepath_mapping_list route53_recordsetGroup = api_domain_response.recordset_group @@ -864,7 +864,6 @@ def to_cloudformation( List[Resource], Tuple[Resource], List[LambdaPermission], - List[Resource], List[ApiGatewayBasePathMapping], List[ApiGatewayBasePathMappingV2], ], diff --git a/tests/translator/input/api_with_custom_domains_private.yaml b/tests/translator/input/api_with_custom_domains_private.yaml index 0eb302ae5..14003d61a 100644 --- a/tests/translator/input/api_with_custom_domains_private.yaml +++ b/tests/translator/input/api_with_custom_domains_private.yaml @@ -6,7 +6,7 @@ Parameters: CertificateArn: Type: String - Default: arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd + Default: another-api-arn Description: ARN of the ACM certificate for the domain VpcEndpointId: diff --git a/tests/translator/output/api_with_custom_domains_private.json b/tests/translator/output/api_with_custom_domains_private.json index c7c9d382a..e753d1f0a 100644 --- a/tests/translator/output/api_with_custom_domains_private.json +++ b/tests/translator/output/api_with_custom_domains_private.json @@ -15,7 +15,7 @@ }, "Parameters": { "CertificateArn": { - "Default": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "Default": "another-api-arn", "Description": "ARN of the ACM certificate for the domain", "Type": "String" }, @@ -33,7 +33,7 @@ "Resources": { "ApiGatewayDomainNameV27c603ed871": { "Properties": { - "CertificateArn": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "CertificateArn": "another-api-arn", "DomainName": "private.example.com", "EndpointConfiguration": { "Types": [ @@ -116,9 +116,9 @@ }, "Type": "AWS::ApiGateway::BasePathMappingV2" }, - "MyApiDeployment305713bc0b": { + "MyApiDeployment7c3b13a843": { "Properties": { - "Description": "RestApi deployment id: 305713bc0b043df7d6fdb169ecf2f9d9844519da", + "Description": "RestApi deployment id: 7c3b13a843cdd653d1310c6fd7881e8fe8e49da8", "RestApiId": { "Ref": "MyApi" }, @@ -129,7 +129,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment305713bc0b" + "Ref": "MyApiDeployment7c3b13a843" }, "RestApiId": { "Ref": "MyApi" diff --git a/tests/translator/output/aws-cn/api_with_custom_domains_private.json b/tests/translator/output/aws-cn/api_with_custom_domains_private.json index 7d64f8b73..eadba3c58 100644 --- a/tests/translator/output/aws-cn/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-cn/api_with_custom_domains_private.json @@ -15,7 +15,7 @@ }, "Parameters": { "CertificateArn": { - "Default": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "Default": "another-api-arn", "Description": "ARN of the ACM certificate for the domain", "Type": "String" }, @@ -33,7 +33,7 @@ "Resources": { "ApiGatewayDomainNameV27c603ed871": { "Properties": { - "CertificateArn": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "CertificateArn": "another-api-arn", "DomainName": "private.example.com", "EndpointConfiguration": { "Types": [ @@ -124,9 +124,9 @@ }, "Type": "AWS::ApiGateway::BasePathMappingV2" }, - "MyApiDeployment305713bc0b": { + "MyApiDeployment7c3b13a843": { "Properties": { - "Description": "RestApi deployment id: 305713bc0b043df7d6fdb169ecf2f9d9844519da", + "Description": "RestApi deployment id: 7c3b13a843cdd653d1310c6fd7881e8fe8e49da8", "RestApiId": { "Ref": "MyApi" }, @@ -137,7 +137,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment305713bc0b" + "Ref": "MyApiDeployment7c3b13a843" }, "RestApiId": { "Ref": "MyApi" diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json index 7d64f8b73..eadba3c58 100644 --- a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json @@ -15,7 +15,7 @@ }, "Parameters": { "CertificateArn": { - "Default": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "Default": "another-api-arn", "Description": "ARN of the ACM certificate for the domain", "Type": "String" }, @@ -33,7 +33,7 @@ "Resources": { "ApiGatewayDomainNameV27c603ed871": { "Properties": { - "CertificateArn": "arn:aws:acm:us-west-2:123456789:certificate/abcd-000-1234-0000-000000abcd", + "CertificateArn": "another-api-arn", "DomainName": "private.example.com", "EndpointConfiguration": { "Types": [ @@ -124,9 +124,9 @@ }, "Type": "AWS::ApiGateway::BasePathMappingV2" }, - "MyApiDeployment305713bc0b": { + "MyApiDeployment7c3b13a843": { "Properties": { - "Description": "RestApi deployment id: 305713bc0b043df7d6fdb169ecf2f9d9844519da", + "Description": "RestApi deployment id: 7c3b13a843cdd653d1310c6fd7881e8fe8e49da8", "RestApiId": { "Ref": "MyApi" }, @@ -137,7 +137,7 @@ "MyApiprodStage": { "Properties": { "DeploymentId": { - "Ref": "MyApiDeployment305713bc0b" + "Ref": "MyApiDeployment7c3b13a843" }, "RestApiId": { "Ref": "MyApi" From c3b8b4ef7c3ea8e086b6b74ba249c5d9346887d0 Mon Sep 17 00:00:00 2001 From: mbfreder Date: Tue, 18 Mar 2025 18:23:08 -0700 Subject: [PATCH 7/7] update tests --- tests/translator/input/api_with_custom_domains_private.yaml | 2 +- tests/translator/output/api_with_custom_domains_private.json | 2 +- .../output/aws-cn/api_with_custom_domains_private.json | 2 +- .../output/aws-us-gov/api_with_custom_domains_private.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/translator/input/api_with_custom_domains_private.yaml b/tests/translator/input/api_with_custom_domains_private.yaml index 14003d61a..5f6cb4709 100644 --- a/tests/translator/input/api_with_custom_domains_private.yaml +++ b/tests/translator/input/api_with_custom_domains_private.yaml @@ -55,7 +55,7 @@ Resources: Outputs: ApiDomainName: Description: Custom Domain Name for the API - Value: !Ref DomainName + Value: !Ref MyApi.DomainNameV2 ApiEndpoint: Description: API Gateway endpoint URL diff --git a/tests/translator/output/api_with_custom_domains_private.json b/tests/translator/output/api_with_custom_domains_private.json index e753d1f0a..2e1e715fb 100644 --- a/tests/translator/output/api_with_custom_domains_private.json +++ b/tests/translator/output/api_with_custom_domains_private.json @@ -3,7 +3,7 @@ "ApiDomainName": { "Description": "Custom Domain Name for the API", "Value": { - "Ref": "DomainName" + "Ref": "ApiGatewayDomainNameV27c603ed871" } }, "ApiEndpoint": { diff --git a/tests/translator/output/aws-cn/api_with_custom_domains_private.json b/tests/translator/output/aws-cn/api_with_custom_domains_private.json index eadba3c58..9aab6c8b9 100644 --- a/tests/translator/output/aws-cn/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-cn/api_with_custom_domains_private.json @@ -3,7 +3,7 @@ "ApiDomainName": { "Description": "Custom Domain Name for the API", "Value": { - "Ref": "DomainName" + "Ref": "ApiGatewayDomainNameV27c603ed871" } }, "ApiEndpoint": { diff --git a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json index eadba3c58..9aab6c8b9 100644 --- a/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json +++ b/tests/translator/output/aws-us-gov/api_with_custom_domains_private.json @@ -3,7 +3,7 @@ "ApiDomainName": { "Description": "Custom Domain Name for the API", "Value": { - "Ref": "DomainName" + "Ref": "ApiGatewayDomainNameV27c603ed871" } }, "ApiEndpoint": {