diff --git a/appveyor-linux-binary.yml b/appveyor-linux-binary.yml index 5e4e849dce..7d574fce9e 100644 --- a/appveyor-linux-binary.yml +++ b/appveyor-linux-binary.yml @@ -19,6 +19,8 @@ configuration: - OtherTesting environment: + PYTHON_HOME: "$HOME/venv3.8" + PYTHON_VERSION: '3.8' AWS_DEFAULT_REGION: us-east-1 NODE_VERSION: "14.17.6" AWS_S3: 'AWS_S3_TESTING' @@ -29,9 +31,6 @@ environment: APPVEYOR_CONSOLE_DISABLE_PTY: false APPVEYOR_DETAILED_SHELL_LOGGING: true - matrix: - - PYTHON_HOME: "$HOME/venv3.11/bin" - PYTHON_VERSION: '3.11' install: # AppVeyor's apt-get cache might be outdated, and the package could potentially be 404. @@ -71,7 +70,7 @@ install: - sh: "PATH=$(echo $PWD'/aws_cli/bin'):$PATH" # Install pytest - - sh: "virtualenv pytest" + - sh: "python3.9 -m venv pytest" - sh: "./pytest/bin/python -m pip install -r requirements/pre-dev.txt" - sh: "./pytest/bin/python -m pip install -r requirements/dev.txt" - sh: "./pytest/bin/python -m pip install -r requirements/base.txt" diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 08ae51cff7..5c9bba5a26 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -1205,9 +1205,16 @@ def _link_gateway_resource_to_gateway_resource_call_back( return logical_id = referenced_gateway_resource_values[0] - gateway_resource_cfn_resource["Properties"]["ResourceId"] = ( - {"Ref": logical_id.value} if isinstance(logical_id, LogicalIdReference) else logical_id.value - ) + if isinstance(logical_id, LogicalIdReference): + if logical_id.resource_type == TF_AWS_API_GATEWAY_REST_API: + gateway_resource_cfn_resource["Properties"]["ResourceId"] = { + "Fn::GetAtt": [logical_id.value, "RootResourceId"] + } + else: + gateway_resource_cfn_resource["Properties"]["ResourceId"] = {"Ref": logical_id.value} + + else: + gateway_resource_cfn_resource["Properties"]["ResourceId"] = logical_id.value def _link_gateway_resource_to_parent_resource_call_back( @@ -1330,7 +1337,7 @@ def _link_gateway_stage_to_rest_api( def _link_gateway_method_to_gateway_resource( gateway_method_config_resources: Dict[str, TFResource], gateway_method_config_address_cfn_resources_map: Dict[str, List], - gateway_resources_terraform_resources: Dict[str, Dict], + gateway_resources_or_rest_apis_terraform_resources: Dict[str, Dict], ): """ Iterate through all the resources and link the corresponding @@ -1342,8 +1349,8 @@ def _link_gateway_method_to_gateway_resource( Dictionary of configuration Gateway Methods gateway_method_config_address_cfn_resources_map: Dict[str, List] Dictionary containing resolved configuration addresses matched up to the cfn Gateway Stage - gateway_resources_terraform_resources: Dict[str, Dict] - Dictionary of all actual terraform Rest API resources (not configuration resources). + gateway_resources_or_rest_apis_terraform_resources: Dict[str, Dict] + Dictionary of all actual terraform Rest API or gateway resource resources (not configuration resources). The dictionary's key is the calculated logical id for each resource. """ exceptions = ResourcePairExceptions( @@ -1353,12 +1360,16 @@ def _link_gateway_method_to_gateway_resource( resource_linking_pair = ResourceLinkingPair( source_resource_cfn_resource=gateway_method_config_address_cfn_resources_map, source_resource_tf_config=gateway_method_config_resources, - destination_resource_tf=gateway_resources_terraform_resources, + destination_resource_tf=gateway_resources_or_rest_apis_terraform_resources, expected_destinations=[ ResourcePairExceptedDestination( terraform_resource_type_prefix=API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX, terraform_attribute_name="id", ), + ResourcePairExceptedDestination( + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + terraform_attribute_name="root_resource_id", + ), ], terraform_link_field_name="resource_id", cfn_link_field_name="ResourceId", @@ -1412,7 +1423,7 @@ def _link_gateway_integrations_to_gateway_rest_apis( def _link_gateway_integrations_to_gateway_resource( gateway_integrations_config_resources: Dict[str, TFResource], gateway_integrations_config_address_cfn_resources_map: Dict[str, List], - gateway_resources_terraform_resources: Dict[str, Dict], + gateway_resources_or_rest_apis_terraform_resources: Dict[str, Dict], ): """ Iterate through all the resources and link the corresponding @@ -1424,9 +1435,9 @@ def _link_gateway_integrations_to_gateway_resource( Dictionary of configuration Gateway Integrations gateway_integrations_config_address_cfn_resources_map: Dict[str, List] Dictionary containing resolved configuration addresses matched up to the cfn Gateway Integration - gateway_resources_terraform_resources: Dict[str, Dict] - Dictionary of all actual terraform Rest API resources (not configuration resources). The dictionary's key is the - calculated logical id for each resource. + gateway_resources_or_rest_apis_terraform_resources: Dict[str, Dict] + Dictionary of all actual terraform Rest API or gateway resource resources (not configuration resources). The + dictionary's key is the calculated logical id for each resource. """ exceptions = ResourcePairExceptions( @@ -1436,12 +1447,16 @@ def _link_gateway_integrations_to_gateway_resource( resource_linking_pair = ResourceLinkingPair( source_resource_cfn_resource=gateway_integrations_config_address_cfn_resources_map, source_resource_tf_config=gateway_integrations_config_resources, - destination_resource_tf=gateway_resources_terraform_resources, + destination_resource_tf=gateway_resources_or_rest_apis_terraform_resources, expected_destinations=[ ResourcePairExceptedDestination( terraform_resource_type_prefix=API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX, terraform_attribute_name="id", ), + ResourcePairExceptedDestination( + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + terraform_attribute_name="root_resource_id", + ), ], terraform_link_field_name="resource_id", cfn_link_field_name="ResourceId", @@ -1579,10 +1594,11 @@ def _link_gateway_integration_responses_to_gateway_rest_apis( def _link_gateway_integration_responses_to_gateway_resource( gateway_integration_responses_config_resources: Dict[str, TFResource], gateway_integration_responses_config_address_cfn_resources_map: Dict[str, List], - gateway_resources_terraform_resources: Dict[str, Dict], + gateway_resources_or_rest_apis_terraform_resources: Dict[str, Dict], ): """ - Iterate through all the resources and link the corresponding Gateway Resource resource to each Gateway Integration + Iterate through all the resour + ces and link the corresponding Gateway Resource resource to each Gateway Integration Response resource. Parameters ---------- @@ -1591,9 +1607,9 @@ def _link_gateway_integration_responses_to_gateway_resource( gateway_integration_responses_config_address_cfn_resources_map: Dict[str, List] Dictionary containing resolved configuration addresses matched up to the internal mapped cfn Gateway Integration Response. - gateway_resources_terraform_resources: Dict[str, Dict] - Dictionary of all actual terraform Rest API resources (not configuration resources). The dictionary's key is the - calculated logical id for each resource. + gateway_resources_or_rest_apis_terraform_resources: Dict[str, Dict] + Dictionary of all actual terraform Rest API or gateway resource resources (not configuration resources). The + dictionary's key is the calculated logical id for each resource. """ exceptions = ResourcePairExceptions( @@ -1603,12 +1619,16 @@ def _link_gateway_integration_responses_to_gateway_resource( resource_linking_pair = ResourceLinkingPair( source_resource_cfn_resource=gateway_integration_responses_config_address_cfn_resources_map, source_resource_tf_config=gateway_integration_responses_config_resources, - destination_resource_tf=gateway_resources_terraform_resources, + destination_resource_tf=gateway_resources_or_rest_apis_terraform_resources, expected_destinations=[ ResourcePairExceptedDestination( terraform_resource_type_prefix=API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX, terraform_attribute_name="id", ), + ResourcePairExceptedDestination( + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + terraform_attribute_name="root_resource_id", + ), ], terraform_link_field_name="resource_id", cfn_link_field_name="ResourceId", diff --git a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py index dc5f3080f5..7d9d10f574 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py @@ -49,21 +49,11 @@ LinkingPairCaller( source=TF_AWS_API_GATEWAY_STAGE, dest=TF_AWS_API_GATEWAY_REST_API, linking_func=_link_gateway_stage_to_rest_api ), - LinkingPairCaller( - source=TF_AWS_API_GATEWAY_METHOD, - dest=TF_AWS_API_GATEWAY_RESOURCE, - linking_func=_link_gateway_method_to_gateway_resource, - ), LinkingPairCaller( source=TF_AWS_API_GATEWAY_INTEGRATION, dest=TF_AWS_API_GATEWAY_REST_API, linking_func=_link_gateway_integrations_to_gateway_rest_apis, ), - LinkingPairCaller( - source=TF_AWS_API_GATEWAY_INTEGRATION, - dest=TF_AWS_API_GATEWAY_RESOURCE, - linking_func=_link_gateway_integrations_to_gateway_resource, - ), LinkingPairCaller( source=TF_AWS_API_GATEWAY_INTEGRATION, dest=TF_AWS_LAMBDA_FUNCTION, @@ -74,11 +64,6 @@ dest=TF_AWS_API_GATEWAY_REST_API, linking_func=_link_gateway_integration_responses_to_gateway_rest_apis, ), - LinkingPairCaller( - source=TF_AWS_API_GATEWAY_INTEGRATION_RESPONSE, - dest=TF_AWS_API_GATEWAY_RESOURCE, - linking_func=_link_gateway_integration_responses_to_gateway_resource, - ), LinkingPairCaller( source=TF_AWS_API_GATEWAY_AUTHORIZER, dest=TF_AWS_LAMBDA_FUNCTION, @@ -102,4 +87,19 @@ destinations=[TF_AWS_API_GATEWAY_REST_API, TF_AWS_API_GATEWAY_RESOURCE], linking_func=_link_gateway_resources_to_parents, ), + LinkingMultipleDestinationsOptionsCaller( + source=TF_AWS_API_GATEWAY_METHOD, + destinations=[TF_AWS_API_GATEWAY_REST_API, TF_AWS_API_GATEWAY_RESOURCE], + linking_func=_link_gateway_method_to_gateway_resource, + ), + LinkingMultipleDestinationsOptionsCaller( + source=TF_AWS_API_GATEWAY_INTEGRATION, + destinations=[TF_AWS_API_GATEWAY_REST_API, TF_AWS_API_GATEWAY_RESOURCE], + linking_func=_link_gateway_integrations_to_gateway_resource, + ), + LinkingMultipleDestinationsOptionsCaller( + source=TF_AWS_API_GATEWAY_INTEGRATION_RESPONSE, + destinations=[TF_AWS_API_GATEWAY_REST_API, TF_AWS_API_GATEWAY_RESOURCE], + linking_func=_link_gateway_integration_responses_to_gateway_resource, + ), ] diff --git a/tests/integration/local/start_api/test_start_api_with_terraform_application.py b/tests/integration/local/start_api/test_start_api_with_terraform_application.py index 8953ec780f..69ebedf8ce 100644 --- a/tests/integration/local/start_api/test_start_api_with_terraform_application.py +++ b/tests/integration/local/start_api/test_start_api_with_terraform_application.py @@ -98,7 +98,7 @@ def tearDownClass(cls) -> None: [ { "terraform_application": "terraform-v1-nested-apis", - "testing_urls": ["parent/hello", "parent"], + "testing_urls": ["", "parent/hello", "parent"], }, { "terraform_application": "terraform-v1-api-simple", @@ -185,7 +185,7 @@ def test_unsupported_limitations(self): [ { "terraform_application": "terraform-v1-nested-apis", - "testing_urls": ["parent/hello", "parent"], + "testing_urls": ["", "parent/hello", "parent"], }, { "terraform_application": "terraform-v1-api-simple", diff --git a/tests/integration/testdata/start_api/terraform/terraform-v1-nested-apis/main.tf b/tests/integration/testdata/start_api/terraform/terraform-v1-nested-apis/main.tf index d0cec2da6a..3c2a904039 100644 --- a/tests/integration/testdata/start_api/terraform/terraform-v1-nested-apis/main.tf +++ b/tests/integration/testdata/start_api/terraform/terraform-v1-nested-apis/main.tf @@ -124,6 +124,13 @@ resource "aws_api_gateway_integration" "MyDemoIntegration" { depends_on = [aws_api_gateway_method.GetMethod] } +resource "aws_api_gateway_method_response" "MyDemoIntegration_response_200" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_resource.ChildResource.id + http_method = aws_api_gateway_method.GetMethod.http_method + status_code = "200" +} + resource "aws_api_gateway_method" "ParentResource_GetMethod" { rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id resource_id = aws_api_gateway_resource.ParentResource.id @@ -141,3 +148,35 @@ resource "aws_api_gateway_integration" "ParentResource_GetMethod_Integration" { uri = aws_lambda_function.HelloWorldFunction.invoke_arn depends_on = [aws_api_gateway_method.ParentResource_GetMethod] } + +resource "aws_api_gateway_method_response" "ParentResource_GetMethod_Integration_response_200" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_resource.ParentResource.id + http_method = aws_api_gateway_method.ParentResource_GetMethod.http_method + status_code = "200" +} + +resource "aws_api_gateway_method" "API_GetMethod" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_rest_api.MyDemoAPI.root_resource_id + http_method = "GET" + authorization = "NONE" +} + +resource "aws_api_gateway_integration" "API_GetMethod_Integration" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_rest_api.MyDemoAPI.root_resource_id + http_method = aws_api_gateway_method.ParentResource_GetMethod.http_method + integration_http_method = "POST" + type = "AWS_PROXY" + content_handling = "CONVERT_TO_TEXT" + uri = aws_lambda_function.HelloWorldFunction.invoke_arn + depends_on = [aws_api_gateway_method.ParentResource_GetMethod] +} + +resource "aws_api_gateway_method_response" "API_GetMethod_Integration_response_200" { + rest_api_id = aws_api_gateway_rest_api.MyDemoAPI.id + resource_id = aws_api_gateway_rest_api.MyDemoAPI.root_resource_id + http_method = aws_api_gateway_method.ParentResource_GetMethod.http_method + status_code = "200" +} diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py index 1bad1a205d..11fd230223 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_resource_linking.py @@ -1877,6 +1877,10 @@ def test_link_gateway_methods_to_gateway_resources( terraform_resource_type_prefix=API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX, terraform_attribute_name="id", ), + ResourcePairExceptedDestination( + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + terraform_attribute_name="root_resource_id", + ), ], terraform_link_field_name="resource_id", cfn_link_field_name="ResourceId", @@ -1957,6 +1961,10 @@ def test_link_gateway_integrations_to_gateway_resource( terraform_resource_type_prefix=API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX, terraform_attribute_name="id", ), + ResourcePairExceptedDestination( + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + terraform_attribute_name="root_resource_id", + ), ], terraform_link_field_name="resource_id", cfn_link_field_name="ResourceId", @@ -2072,6 +2080,14 @@ def test_link_gateway_methods_to_gateway_rest_apis_call_back( [LogicalIdReference(value="Resource1", resource_type=TF_AWS_API_GATEWAY_RESOURCE)], {"Ref": "Resource1"}, ), + ( + { + "Type": "AWS::ApiGateway::Method", + "Properties": {"HttpMethod": "post"}, + }, + [LogicalIdReference(value="api1", resource_type=TF_AWS_API_GATEWAY_REST_API)], + {"Fn::GetAtt": ["api1", "RootResourceId"]}, + ), ] ) def test_link_gateway_method_to_gateway_resource_call_back( @@ -2108,9 +2124,17 @@ def test_link_gateway_method_to_gateway_resource_call_back( [LogicalIdReference(value="RestApi", resource_type=TF_AWS_API_GATEWAY_REST_API)], {"Fn::GetAtt": ["RestApi", "RootResourceId"]}, ), + ( + { + "Type": "AWS::ApiGateway::Resource", + "Properties": {}, + }, + [LogicalIdReference(value="resource1", resource_type=TF_AWS_API_GATEWAY_RESOURCE)], + {"Ref": "resource1"}, + ), ] ) - def test_link_gateway_resource_to_gateway_rest_api_parent_id_call_back( + def test_link_gateway_resource_to_parent_resource_call_back( self, input_gateway_resource, logical_ids, expected_rest_api ): gateway_resource = deepcopy(input_gateway_resource) @@ -2280,6 +2304,10 @@ def test_link_gateway_integration_response_to_gateway_resource( terraform_resource_type_prefix=API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX, terraform_attribute_name="id", ), + ResourcePairExceptedDestination( + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + terraform_attribute_name="root_resource_id", + ), ], terraform_link_field_name="resource_id", cfn_link_field_name="ResourceId",