diff --git a/samtranslator/model/api/api_generator.py b/samtranslator/model/api/api_generator.py index 3510e94a4..4d9326972 100644 --- a/samtranslator/model/api/api_generator.py +++ b/samtranslator/model/api/api_generator.py @@ -1326,8 +1326,19 @@ def _set_endpoint_configuration(self, rest_api: ApiGatewayRestApi, value: Union[ if isinstance(value, dict) and value.get("Type"): rest_api.Parameters = {"endpointConfigurationTypes": value.get("Type")} rest_api.EndpointConfiguration = {"Types": [value.get("Type")]} - if "VPCEndpointIds" in value: - rest_api.EndpointConfiguration["VpcEndpointIds"] = value.get("VPCEndpointIds") + + # SAM API `EndpointConfiguration` uses `VPCEndpointIds` but APIGateway RestApi uses `VpcEndpointIds`. + # Deny when both properties are defined at the same time. + if "VPCEndpointIds" in value and "VpcEndpointIds" in value: + raise InvalidResourceException( + rest_api.logical_id, + "'VPCEndpointIds' and 'VpcEndpointIds' cannot be used together in EndpointConfiguration.", + ) + # Accept when either `VPCEndpointIds` or `VpcEndpointIds` is defined by users + if "VPCEndpointIds" in value or "VpcEndpointIds" in value: + rest_api.EndpointConfiguration["VpcEndpointIds"] = value.get("VPCEndpointIds") or value.get( + "VpcEndpointIds" + ) else: rest_api.EndpointConfiguration = {"Types": [value]} rest_api.Parameters = {"endpointConfigurationTypes": value} diff --git a/tests/translator/input/api_endpoint_configuration_with_both_vpcendpoint.yaml b/tests/translator/input/api_endpoint_configuration_with_both_vpcendpoint.yaml new file mode 100644 index 000000000..431ecf043 --- /dev/null +++ b/tests/translator/input/api_endpoint_configuration_with_both_vpcendpoint.yaml @@ -0,0 +1,20 @@ +Resources: + ExplicitApiWithVpcEndpointIds: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionUri: s3://sam-demo-bucket/webpage_swagger.json + EndpointConfiguration: + Type: PRIVATE + VpcEndpointIds: + - vpc-1 + + ExplicitApiWithVPCEndpointIds: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionUri: s3://sam-demo-bucket/webpage_swagger.json + EndpointConfiguration: + Type: PRIVATE + VPCEndpointIds: + - vpc-2 diff --git a/tests/translator/input/error_api_invalid_endpoint_configuration_with_vpc.yaml b/tests/translator/input/error_api_invalid_endpoint_configuration_with_vpc.yaml new file mode 100644 index 000000000..a53e5727c --- /dev/null +++ b/tests/translator/input/error_api_invalid_endpoint_configuration_with_vpc.yaml @@ -0,0 +1,14 @@ +Resources: + ExplicitApi: + Type: AWS::Serverless::Api + Properties: + StageName: Prod + DefinitionUri: s3://sam-demo-bucket/webpage_swagger.json + EndpointConfiguration: + Type: PRIVATE + VPCEndpointIds: + - vpc-1 + - vpc-2 + VpcEndpointIds: + - vpc-3 + - vpc-4 diff --git a/tests/translator/output/api_endpoint_configuration_with_both_vpcendpoint.json b/tests/translator/output/api_endpoint_configuration_with_both_vpcendpoint.json new file mode 100644 index 000000000..f9376e42a --- /dev/null +++ b/tests/translator/output/api_endpoint_configuration_with_both_vpcendpoint.json @@ -0,0 +1,88 @@ +{ + "Resources": { + "ExplicitApiWithVPCEndpointIds": { + "Properties": { + "BodyS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "webpage_swagger.json" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ], + "VpcEndpointIds": [ + "vpc-2" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "ExplicitApiWithVPCEndpointIdsDeploymentf117c932f7": { + "Properties": { + "Description": "RestApi deployment id: f117c932f75cfa87d23dfed64e9430d0081ef289", + "RestApiId": { + "Ref": "ExplicitApiWithVPCEndpointIds" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "ExplicitApiWithVPCEndpointIdsProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiWithVPCEndpointIdsDeploymentf117c932f7" + }, + "RestApiId": { + "Ref": "ExplicitApiWithVPCEndpointIds" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "ExplicitApiWithVpcEndpointIds": { + "Properties": { + "BodyS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "webpage_swagger.json" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ], + "VpcEndpointIds": [ + "vpc-1" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "ExplicitApiWithVpcEndpointIdsDeploymentf117c932f7": { + "Properties": { + "Description": "RestApi deployment id: f117c932f75cfa87d23dfed64e9430d0081ef289", + "RestApiId": { + "Ref": "ExplicitApiWithVpcEndpointIds" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "ExplicitApiWithVpcEndpointIdsProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiWithVpcEndpointIdsDeploymentf117c932f7" + }, + "RestApiId": { + "Ref": "ExplicitApiWithVpcEndpointIds" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + } + } +} diff --git a/tests/translator/output/aws-cn/api_endpoint_configuration_with_both_vpcendpoint.json b/tests/translator/output/aws-cn/api_endpoint_configuration_with_both_vpcendpoint.json new file mode 100644 index 000000000..f9376e42a --- /dev/null +++ b/tests/translator/output/aws-cn/api_endpoint_configuration_with_both_vpcendpoint.json @@ -0,0 +1,88 @@ +{ + "Resources": { + "ExplicitApiWithVPCEndpointIds": { + "Properties": { + "BodyS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "webpage_swagger.json" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ], + "VpcEndpointIds": [ + "vpc-2" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "ExplicitApiWithVPCEndpointIdsDeploymentf117c932f7": { + "Properties": { + "Description": "RestApi deployment id: f117c932f75cfa87d23dfed64e9430d0081ef289", + "RestApiId": { + "Ref": "ExplicitApiWithVPCEndpointIds" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "ExplicitApiWithVPCEndpointIdsProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiWithVPCEndpointIdsDeploymentf117c932f7" + }, + "RestApiId": { + "Ref": "ExplicitApiWithVPCEndpointIds" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "ExplicitApiWithVpcEndpointIds": { + "Properties": { + "BodyS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "webpage_swagger.json" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ], + "VpcEndpointIds": [ + "vpc-1" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "ExplicitApiWithVpcEndpointIdsDeploymentf117c932f7": { + "Properties": { + "Description": "RestApi deployment id: f117c932f75cfa87d23dfed64e9430d0081ef289", + "RestApiId": { + "Ref": "ExplicitApiWithVpcEndpointIds" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "ExplicitApiWithVpcEndpointIdsProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiWithVpcEndpointIdsDeploymentf117c932f7" + }, + "RestApiId": { + "Ref": "ExplicitApiWithVpcEndpointIds" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + } + } +} diff --git a/tests/translator/output/aws-us-gov/api_endpoint_configuration_with_both_vpcendpoint.json b/tests/translator/output/aws-us-gov/api_endpoint_configuration_with_both_vpcendpoint.json new file mode 100644 index 000000000..f9376e42a --- /dev/null +++ b/tests/translator/output/aws-us-gov/api_endpoint_configuration_with_both_vpcendpoint.json @@ -0,0 +1,88 @@ +{ + "Resources": { + "ExplicitApiWithVPCEndpointIds": { + "Properties": { + "BodyS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "webpage_swagger.json" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ], + "VpcEndpointIds": [ + "vpc-2" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "ExplicitApiWithVPCEndpointIdsDeploymentf117c932f7": { + "Properties": { + "Description": "RestApi deployment id: f117c932f75cfa87d23dfed64e9430d0081ef289", + "RestApiId": { + "Ref": "ExplicitApiWithVPCEndpointIds" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "ExplicitApiWithVPCEndpointIdsProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiWithVPCEndpointIdsDeploymentf117c932f7" + }, + "RestApiId": { + "Ref": "ExplicitApiWithVPCEndpointIds" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + }, + "ExplicitApiWithVpcEndpointIds": { + "Properties": { + "BodyS3Location": { + "Bucket": "sam-demo-bucket", + "Key": "webpage_swagger.json" + }, + "EndpointConfiguration": { + "Types": [ + "PRIVATE" + ], + "VpcEndpointIds": [ + "vpc-1" + ] + }, + "Parameters": { + "endpointConfigurationTypes": "PRIVATE" + } + }, + "Type": "AWS::ApiGateway::RestApi" + }, + "ExplicitApiWithVpcEndpointIdsDeploymentf117c932f7": { + "Properties": { + "Description": "RestApi deployment id: f117c932f75cfa87d23dfed64e9430d0081ef289", + "RestApiId": { + "Ref": "ExplicitApiWithVpcEndpointIds" + }, + "StageName": "Stage" + }, + "Type": "AWS::ApiGateway::Deployment" + }, + "ExplicitApiWithVpcEndpointIdsProdStage": { + "Properties": { + "DeploymentId": { + "Ref": "ExplicitApiWithVpcEndpointIdsDeploymentf117c932f7" + }, + "RestApiId": { + "Ref": "ExplicitApiWithVpcEndpointIds" + }, + "StageName": "Prod" + }, + "Type": "AWS::ApiGateway::Stage" + } + } +} diff --git a/tests/translator/output/error_api_invalid_endpoint_configuration_with_vpc.json b/tests/translator/output/error_api_invalid_endpoint_configuration_with_vpc.json new file mode 100644 index 000000000..7df8487e8 --- /dev/null +++ b/tests/translator/output/error_api_invalid_endpoint_configuration_with_vpc.json @@ -0,0 +1,9 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 1. ", + "Resource with id [ExplicitApi] is invalid. ", + "'VPCEndpointIds' and 'VpcEndpointIds' cannot be used together in EndpointConfiguration." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [ExplicitApi] is invalid. 'VPCEndpointIds' and 'VpcEndpointIds' cannot be used together in EndpointConfiguration." +}