From 6f137dc90c02dad3dc87bb8d49533304df0b33a2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 29 May 2023 18:29:37 +0000 Subject: [PATCH 01/14] feat: update SAM CLI with latest App Templates commit hash (#5211) * feat: updating app templates repo hash with (a34f563f067e13df3eb350d36461b99397b6cda6) * dummy change to trigger checks * revert dummy commit --------- Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- samcli/runtime_config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/runtime_config.json b/samcli/runtime_config.json index fb814034f3..40b2a8b551 100644 --- a/samcli/runtime_config.json +++ b/samcli/runtime_config.json @@ -1,3 +1,3 @@ { - "app_template_repo_commit": "824220f550c2d651dbbf5c64020c453dfcd39c4f" + "app_template_repo_commit": "a34f563f067e13df3eb350d36461b99397b6cda6" } From 34d5874220b1a7ad7e51bc37ce1c21a1f0af7e88 Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Mon, 29 May 2023 12:29:16 -0700 Subject: [PATCH 02/14] Enable hook-name flag for sam local start-api --- samcli/commands/local/start_api/cli.py | 20 ++++++++++++++++++- .../commands/local/start_api/core/options.py | 4 ++++ .../local/start_api/core/test_command.py | 2 ++ .../unit/commands/local/start_api/test_cli.py | 3 +++ .../unit/commands/samconfig/test_samconfig.py | 1 + 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index 145b0a58a2..b32edd030a 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -9,8 +9,10 @@ from samcli.cli.cli_config_file import TomlProvider, configuration_option from samcli.cli.main import aws_creds_options, pass_context, print_cmdline_args from samcli.cli.main import common_options as cli_framework_options +from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled from samcli.commands._utils.option_value_processor import process_image_options -from samcli.commands._utils.options import generate_next_command_recommendation +from samcli.commands._utils.options import generate_next_command_recommendation, hook_name_click_option, \ + skip_prepare_infra_option from samcli.commands.local.cli_common.options import ( invoke_common_options, local_common_options, @@ -54,6 +56,10 @@ context_settings={"max_content_width": 120}, ) @configuration_option(provider=TomlProvider(section="parameters")) +@hook_name_click_option( + force_prepare=False, invalid_coexist_options=["t", "template-file", "template", "parameter-overrides"] +) +@skip_prepare_infra_option @service_common_options(3000) @click.option( "--static-dir", @@ -98,6 +104,8 @@ def cli( container_host, container_host_interface, invoke_image, + hook_name, + skip_prepare_infra, ): """ `sam local start-api` command entry point @@ -128,6 +136,7 @@ def cli( container_host, container_host_interface, invoke_image, + hook_name, ) # pragma: no cover @@ -155,6 +164,7 @@ def do_cli( # pylint: disable=R0914 container_host, container_host_interface, invoke_image, + hook_name, ): """ Implementation of the ``cli`` method, just separated out for unit testing purposes @@ -170,6 +180,14 @@ def do_cli( # pylint: disable=R0914 LOG.debug("local start-api command is called") + if ( + hook_name + and ExperimentalFlag.IaCsSupport.get(hook_name) is not None + and not is_experimental_enabled(ExperimentalFlag.IaCsSupport.get(hook_name)) + ): + LOG.info("Terraform Support beta feature is not enabled.") + return + processed_invoke_images = process_image_options(invoke_image) # Pass all inputs to setup necessary context to invoke function locally. diff --git a/samcli/commands/local/start_api/core/options.py b/samcli/commands/local/start_api/core/options.py index d9b89145e0..21bb1bf822 100644 --- a/samcli/commands/local/start_api/core/options.py +++ b/samcli/commands/local/start_api/core/options.py @@ -17,6 +17,8 @@ "parameter_overrides", ] +EXTENSION_OPTIONS: List[str] = ["hook_name", "skip_prepare_infra"] + CONTAINER_OPTION_NAMES: List[str] = [ "host", "port", @@ -53,6 +55,7 @@ + ARTIFACT_LOCATION_OPTIONS + CONFIGURATION_OPTION_NAMES + ALL_COMMON_OPTIONS + + EXTENSION_OPTIONS ) OPTIONS_INFO: Dict[str, Dict] = { @@ -65,6 +68,7 @@ "Artifact Location Options": { "option_names": {opt: {"rank": idx} for idx, opt in enumerate(ARTIFACT_LOCATION_OPTIONS)} }, + "Extension Options": {"option_names": {opt: {"rank": idx} for idx, opt in enumerate(EXTENSION_OPTIONS)}}, "Configuration Options": { "option_names": {opt: {"rank": idx} for idx, opt in enumerate(CONFIGURATION_OPTION_NAMES)}, "extras": [ diff --git a/tests/unit/commands/local/start_api/core/test_command.py b/tests/unit/commands/local/start_api/core/test_command.py index 30c07ae5d4..67eab70400 100644 --- a/tests/unit/commands/local/start_api/core/test_command.py +++ b/tests/unit/commands/local/start_api/core/test_command.py @@ -28,6 +28,7 @@ def test_get_options_local_start_api_command(self, mock_get_params): MockParams(rv=("--parameter-overrides", ""), name="parameter_overrides"), MockParams(rv=("--host", ""), name="host"), MockParams(rv=("--config-file", ""), name="config_file"), + MockParams(rv=("--hook_name", ""), name="hook_name"), MockParams(rv=("--beta-features", ""), name="beta_features"), MockParams(rv=("--log-file", ""), name="log_file"), MockParams(rv=("--debug", ""), name="debug"), @@ -41,6 +42,7 @@ def test_get_options_local_start_api_command(self, mock_get_params): "Container Options": [("", ""), ("--host", ""), ("", "")], "Description": [(cmd.description + cmd.description_addendum, "")], "Examples": [("", ""), ("$sam local start-api\x1b[0m", "")], + "Extension Options": [("", ""), ("--hook_name", ""), ("", "")], "Other Options": [("", ""), ("--debug", ""), ("", "")], "Beta Options": [("", ""), ("--beta-features", ""), ("", "")], "Required Options": [("", ""), ("--template-file", ""), ("", "")], diff --git a/tests/unit/commands/local/start_api/test_cli.py b/tests/unit/commands/local/start_api/test_cli.py index 4f96394742..f8aae412c7 100644 --- a/tests/unit/commands/local/start_api/test_cli.py +++ b/tests/unit/commands/local/start_api/test_cli.py @@ -39,6 +39,8 @@ def setUp(self): self.warm_containers = None self.debug_function = None + self.hook_name = None + self.ctx_mock = Mock() self.ctx_mock.region = self.region_name self.ctx_mock.profile = self.profile @@ -196,4 +198,5 @@ def call_cli(self): container_host=self.container_host, container_host_interface=self.container_host_interface, invoke_image=self.invoke_image, + hook_name=self.hook_name, ) diff --git a/tests/unit/commands/samconfig/test_samconfig.py b/tests/unit/commands/samconfig/test_samconfig.py index 1ff1217138..19b4a7672d 100644 --- a/tests/unit/commands/samconfig/test_samconfig.py +++ b/tests/unit/commands/samconfig/test_samconfig.py @@ -457,6 +457,7 @@ def test_local_start_api(self, do_cli_mock): "localhost", "127.0.0.1", ("image",), + None, ) @patch("samcli.commands.local.start_lambda.cli.do_cli") From 5338ec450308d28738f40968ac669e81266dcacc Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Mon, 29 May 2023 13:34:44 -0700 Subject: [PATCH 03/14] Format files --- samcli/commands/local/start_api/cli.py | 7 +++++-- samcli/commands/remote_invoke/remote_invoke_context.py | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/samcli/commands/local/start_api/cli.py b/samcli/commands/local/start_api/cli.py index b32edd030a..be657c7661 100644 --- a/samcli/commands/local/start_api/cli.py +++ b/samcli/commands/local/start_api/cli.py @@ -11,8 +11,11 @@ from samcli.cli.main import common_options as cli_framework_options from samcli.commands._utils.experimental import ExperimentalFlag, is_experimental_enabled from samcli.commands._utils.option_value_processor import process_image_options -from samcli.commands._utils.options import generate_next_command_recommendation, hook_name_click_option, \ - skip_prepare_infra_option +from samcli.commands._utils.options import ( + generate_next_command_recommendation, + hook_name_click_option, + skip_prepare_infra_option, +) from samcli.commands.local.cli_common.options import ( invoke_common_options, local_common_options, diff --git a/samcli/commands/remote_invoke/remote_invoke_context.py b/samcli/commands/remote_invoke/remote_invoke_context.py index 7795476b89..1e176f01a3 100644 --- a/samcli/commands/remote_invoke/remote_invoke_context.py +++ b/samcli/commands/remote_invoke/remote_invoke_context.py @@ -30,7 +30,6 @@ class RemoteInvokeContext: - _boto_client_provider: BotoProviderType _boto_resource_provider: BotoProviderType _stack_name: Optional[str] From dc5562a7a324621d478761ddeb7fbcbb4ed82352 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Mon, 29 May 2023 17:06:02 -0700 Subject: [PATCH 04/14] fix: fix failing Terraform integration test cases (#5218) * fix: fix the failing terraform integration test cases * fix: fix the resource address while accessing the module config resources * fix: fix checking the experimental log integration test cases --- .../hook_packages/terraform/hooks/prepare/translate.py | 10 ++++++++-- .../local/invoke/test_invoke_terraform_applications.py | 2 +- .../image_based_lambda_functions_local_backend/main.tf | 1 - .../image_based_lambda_functions_s3_backend/main.tf | 1 - .../terraform/hooks/prepare/test_translate.py | 8 ++++++-- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/samcli/hook_packages/terraform/hooks/prepare/translate.py b/samcli/hook_packages/terraform/hooks/prepare/translate.py index ca02488757..62f8d4da12 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/translate.py +++ b/samcli/hook_packages/terraform/hooks/prepare/translate.py @@ -111,15 +111,21 @@ def _check_unresolvable_values(root_module: dict, root_tf_module: TFModule) -> N # iterate over resources for current module for resource in curr_module.get("resources", []): resource_type = resource.get("type") + resource_name = resource.get("name") + resource_mode = resource.get("mode") resource_mapper = RESOURCE_TRANSLATOR_MAPPING.get(resource_type) if not resource_mapper: continue resource_values = resource.get("values") - resource_full_address = resource.get("address") + resource_address = ( + f"data.{resource_type}.{resource_name}" + if resource_mode == "data" + else f"{resource_type}.{resource_name}" + ) - config_resource_address = get_configuration_address(resource_full_address) + config_resource_address = get_configuration_address(resource_address) config_resource = curr_tf_module.resources[config_resource_address] for prop_builder in resource_mapper.property_builder_mapping.values(): diff --git a/tests/integration/local/invoke/test_invoke_terraform_applications.py b/tests/integration/local/invoke/test_invoke_terraform_applications.py index 3c8053b86e..1421e0fa9b 100644 --- a/tests/integration/local/invoke/test_invoke_terraform_applications.py +++ b/tests/integration/local/invoke/test_invoke_terraform_applications.py @@ -159,7 +159,7 @@ def test_invoke_function_get_experimental_prompted(self): "You can also enable this beta feature with 'sam local invoke --beta-features'." ) self.assertRegex(stdout.decode("utf-8"), terraform_beta_feature_prompted_text) - self.assertTrue(stderr.decode("utf-8").startswith(Colored().yellow(EXPERIMENTAL_WARNING))) + self.assertRegex(stderr.decode("utf-8"), EXPERIMENTAL_WARNING) response = json.loads(stdout.decode("utf-8").split("\n")[2][85:].strip()) expected_response = json.loads('{"statusCode":200,"body":"{\\"message\\": \\"hello world\\"}"}') diff --git a/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_local_backend/main.tf b/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_local_backend/main.tf index 5f7cb18578..821af9b7f6 100644 --- a/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_local_backend/main.tf +++ b/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_local_backend/main.tf @@ -1,6 +1,5 @@ provider "aws" { # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true diff --git a/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_s3_backend/main.tf b/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_s3_backend/main.tf index 99e39ab8e9..df805b56eb 100644 --- a/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_s3_backend/main.tf +++ b/tests/integration/testdata/buildcmd/terraform/image_based_lambda_functions_s3_backend/main.tf @@ -4,7 +4,6 @@ terraform { provider "aws" { # Make it faster by skipping something - skip_get_ec2_platforms = true skip_metadata_api_check = true skip_region_validation = true skip_credentials_validation = true diff --git a/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py b/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py index 164e3c7df6..e8309379db 100644 --- a/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py +++ b/tests/unit/hook_packages/terraform/hooks/prepare/test_translate.py @@ -1116,8 +1116,12 @@ class TestUnresolvableAttributeCheck: @patch("samcli.hook_packages.terraform.hooks.prepare.translate.RESOURCE_TRANSLATOR_MAPPING") @patch("samcli.hook_packages.terraform.hooks.prepare.translate.LOG") def test_module_contains_unresolvables(self, log_mock, mapping_mock): - config_addr = "addr" - module = {"resources": [{"address": config_addr, "values": Mock()}]} + config_addr = "function.func1" + module = { + "resources": [ + {"address": config_addr, "values": Mock(), "type": "function", "mode": "resource", "name": "func1"} + ] + } tf_module = Mock() tf_module_attr = Mock() From 888cb4558817dcd3a6ddd1c1b56a7e4185189256 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 30 May 2023 14:39:03 -0700 Subject: [PATCH 05/14] chore: bump version to 1.85.0 (#5226) --- samcli/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samcli/__init__.py b/samcli/__init__.py index c1a3f89daf..87cd027bf6 100644 --- a/samcli/__init__.py +++ b/samcli/__init__.py @@ -2,4 +2,4 @@ SAM CLI version """ -__version__ = "1.84.0" +__version__ = "1.85.0" From acc0acd10261e038f23bf704cb5779177334f771 Mon Sep 17 00:00:00 2001 From: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> Date: Tue, 30 May 2023 16:28:09 -0700 Subject: [PATCH 06/14] chore: use the SAR Application created in testing accounts (#5221) --- .../aws-serverless-application-with-application-id-map.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml b/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml index e50d00fbd3..d1f83aaf40 100644 --- a/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml +++ b/tests/integration/testdata/buildcmd/aws-serverless-application-with-application-id-map.yaml @@ -4,7 +4,7 @@ Transform: AWS::Serverless-2016-10-31 Mappings: MappingExample: us-east-2: - ApplicationId: arn:aws:serverlessrepo:us-east-1:077246666028:applications/hello-world + ApplicationId: !Sub arn:aws:serverlessrepo:us-east-1:${AWS::AccountId}:applications/sam-cli-integration-test-sar-app Resources: MyApplication: From f23dc230928f035989c08b3cb7c62c119bca6b2a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 30 May 2023 23:39:51 +0000 Subject: [PATCH 07/14] chore: update aws_lambda_builders to 1.32.0 (#5215) Co-authored-by: GitHub Action Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- requirements/reproducible-mac.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements/reproducible-mac.txt b/requirements/reproducible-mac.txt index bf6fb04e9e..14dfaf2c06 100644 --- a/requirements/reproducible-mac.txt +++ b/requirements/reproducible-mac.txt @@ -275,6 +275,7 @@ importlib-metadata==6.1.0 \ --hash=sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20 \ --hash=sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09 # via + # attrs # click # flask # jsonpickle From 2e79b5035d42699c1757378a589f36d24b772f6d Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Wed, 31 May 2023 10:08:08 -0700 Subject: [PATCH 08/14] feat: Added linking Gateway Method to Lambda Authorizer (#5228) * Added linking method to authorizer * Fixed docstring spelling mistake --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- .../terraform/hooks/prepare/exceptions.py | 14 ++++ .../hooks/prepare/resource_linking.py | 69 +++++++++++++++++ .../hooks/prepare/resources/resource_links.py | 6 ++ .../hooks/prepare/test_resource_linking.py | 74 +++++++++++++++++++ 4 files changed, 163 insertions(+) diff --git a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py index 7168037ae9..ab42fe70b8 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py +++ b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py @@ -207,6 +207,20 @@ class GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException( """ +class OneGatewayMethodToGatewayAuthorizerLinkingLimitationException(OneResourceLinkingLimitationException): + """ + Exception specific for Gateway Method linking to more than one Gateway Authorizer + """ + + +class GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException( + LocalVariablesLinkingLimitationException +): + """ + Exception specific for Gateway Method linking to Gateway Authorizer using locals. + """ + + class InvalidSamMetadataPropertiesException(UserException): pass diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 377d7c4321..a68d286276 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -10,6 +10,7 @@ from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( FunctionLayerLocalVariablesLinkingLimitationException, GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayMethodLocalVariablesLinkingLimitationException, @@ -18,6 +19,7 @@ LambdaFunctionToApiGatewayIntegrationLocalVariablesLinkingLimitationException, LocalVariablesLinkingLimitationException, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, @@ -50,6 +52,7 @@ LAMBDA_LAYER_RESOURCE_ADDRESS_PREFIX = "aws_lambda_layer_version." API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX = "aws_api_gateway_rest_api." API_GATEWAY_RESOURCE_RESOURCE_ADDRESS_PREFIX = "aws_api_gateway_resource." +API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX = "aws_api_gateway_authorizer." TERRAFORM_LOCAL_VARIABLES_ADDRESS_PREFIX = "local." DATA_RESOURCE_ADDRESS_PREFIX = "data." @@ -1566,3 +1569,69 @@ def _link_gateway_authorizer_to_lambda_function( linking_exceptions=exceptions, ) ResourceLinker(resource_linking_pair).link_resources() + + +def _link_gateway_method_to_gateway_authorizer_call_back( + gateway_method_cfn_resource: Dict, authorizer_resources: List[ReferenceType] +) -> None: + """ + Callback function that is used by the linking algorithm to update a CFN Method Resource with + a reference to the Lambda Authorizers's Id + + Parameters + ---------- + gateway_method_cfn_resource: Dict + API Gateway Method CFN resource + authorizer_resources: List[ReferenceType] + List of referenced Authorizers either as the logical id of Authorizer resources + defined in the customer project, or ARN values for actual Authorizers defined + in customer's account. This list should always contain one element only. + """ + if len(authorizer_resources) > 1: + raise InvalidResourceLinkingException("Could not link multiple Lambda Authorizers to one Gateway Method") + + if not authorizer_resources: + LOG.info("Unable to find any references to Authorizers, skip linking Gateway Method to Lambda Authorizer") + return + + logical_id = authorizer_resources[0] + gateway_method_cfn_resource["Properties"]["AuthorizerId"] = ( + {"Ref": logical_id.value} if isinstance(logical_id, LogicalIdReference) else logical_id.value + ) + + +def _link_gateway_method_to_gateway_authorizer( + gateway_method_config_resources: Dict[str, TFResource], + gateway_method_config_address_cfn_resources_map: Dict[str, List], + authorizer_resources: Dict[str, Dict], +) -> None: + """ + Iterate through all the resources and link the corresponding + Gateway Method resources to each Gateway Authorizer + + Parameters + ---------- + gateway_method_config_resources: Dict[str, TFResource] + 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 + authorizer_resources: Dict[str, Dict] + Dictionary of all Terraform Authorizer resources (not configuration resources). + The dictionary's key is the calculated logical id for each resource. + """ + exceptions = ResourcePairExceptions( + multiple_resource_linking_exception=OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, + local_variable_linking_exception=GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, + ) + 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=authorizer_resources, + tf_destination_attribute_name="id", + terraform_link_field_name="authorizer_id", + cfn_link_field_name="AuthorizerId", + terraform_resource_type_prefix=API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=_link_gateway_method_to_gateway_authorizer_call_back, + linking_exceptions=exceptions, + ) + ResourceLinker(resource_linking_pair).link_resources() 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 45d7a3ed59..cd882f5815 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py @@ -18,6 +18,7 @@ _link_gateway_integrations_to_function_resource, _link_gateway_integrations_to_gateway_resource, _link_gateway_integrations_to_gateway_rest_apis, + _link_gateway_method_to_gateway_authorizer, _link_gateway_method_to_gateway_resource, _link_gateway_methods_to_gateway_rest_apis, _link_gateway_resources_to_gateway_rest_apis, @@ -78,4 +79,9 @@ dest=TF_AWS_LAMBDA_FUNCTION, linking_func=_link_gateway_authorizer_to_lambda_function, ), + LinkingPairCaller( + source=TF_AWS_API_GATEWAY_METHOD, + dest=TF_AWS_API_GATEWAY_AUTHORIZER, + linking_func=_link_gateway_method_to_gateway_authorizer, + ), ] 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 2e492a2cb1..b4afa74564 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 @@ -7,12 +7,14 @@ from parameterized import parameterized from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, InvalidResourceLinkingException, LocalVariablesLinkingLimitationException, ONE_LAMBDA_LAYER_LINKING_ISSUE_LINK, LOCAL_VARIABLES_SUPPORT_ISSUE_LINK, APPLY_WORK_AROUND_MESSAGE, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneLambdaLayerLinkingLimitationException, FunctionLayerLocalVariablesLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, @@ -36,9 +38,12 @@ ) from samcli.hook_packages.terraform.hooks.prepare.resource_linking import ( + API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX, _clean_references_list, _link_gateway_authorizer_to_lambda_function, _link_gateway_authorizer_to_lambda_function_call_back, + _link_gateway_method_to_gateway_authorizer, + _link_gateway_method_to_gateway_authorizer_call_back, _resolve_module_output, _resolve_module_variable, _build_module, @@ -2081,6 +2086,10 @@ def test_link_gateway_integration_to_function_call_back( _link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back, "Could not link multiple Rest APIs to one Gateway resource", ), + ( + _link_gateway_method_to_gateway_authorizer_call_back, + "Could not link multiple Lambda Authorizers to one Gateway Method", + ), ] ) def test_linking_callbacks_raises_multiple_reference_exception(self, linking_call_back_method, expected_message): @@ -2094,6 +2103,7 @@ def test_linking_callbacks_raises_multiple_reference_exception(self, linking_cal (_link_gateway_resource_to_gateway_rest_apis_parent_id_call_back,), (_link_gateway_resource_to_gateway_resource_call_back,), (_link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back,), + (_link_gateway_method_to_gateway_authorizer_call_back,), ] ) def test_linking_callbacks_skips_empty_references(self, linking_call_back_method): @@ -2245,3 +2255,67 @@ def test_link_gateway_authorizer_to_lambda_function( ) mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) + + @parameterized.expand( + [ + ( + [LogicalIdReference("Authorizer")], + {"Ref": "Authorizer"}, + ), + ( + [ExistingResourceReference("Existing123")], + "Existing123", + ), + ] + ) + def test_link_gateway_method_to_gateway_authorizer_call_back(self, logical_ids, expected_reference): + original_method = { + "Type": "AWS::ApiGateway::Method", + "Properties": {"AuthorizerId": "id here"}, + } + new_method = deepcopy(original_method) + + _link_gateway_method_to_gateway_authorizer_call_back(new_method, logical_ids) + + original_method["Properties"]["AuthorizerId"] = expected_reference + self.assertEqual(original_method, new_method) + + @patch( + "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_method_to_gateway_authorizer_call_back" + ) + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinker") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinkingPair") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourcePairExceptions") + def test_link_gateway_method_to_gateway_authorizer( + self, + mock_resource_linking_exceptions, + mock_resource_linking_pair, + mock_resource_linker, + mock_link_gateway_method_to_gateway_authorizer_call_back, + ): + method_cfn_resources = Mock() + method_config_resources = Mock() + authorizer_tf_resources = Mock() + + _link_gateway_method_to_gateway_authorizer( + method_config_resources, method_cfn_resources, authorizer_tf_resources + ) + + mock_resource_linking_exceptions.assert_called_once_with( + multiple_resource_linking_exception=OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, + local_variable_linking_exception=GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, + ) + + mock_resource_linking_pair.assert_called_once_with( + source_resource_cfn_resource=method_cfn_resources, + source_resource_tf_config=method_config_resources, + destination_resource_tf=authorizer_tf_resources, + tf_destination_attribute_name="id", + terraform_link_field_name="authorizer_id", + cfn_link_field_name="AuthorizerId", + terraform_resource_type_prefix=API_GATEWAY_AUTHORIZER_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=mock_link_gateway_method_to_gateway_authorizer_call_back, + linking_exceptions=mock_resource_linking_exceptions(), + ) + + mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) From ec942e962954d96b44aaf7d3b434acf5c4b9d5e7 Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Wed, 31 May 2023 11:56:42 -0700 Subject: [PATCH 09/14] feat: Return early during linking if no destination resources are found (#5220) * Returns during linking if no destination resources are found * Updated comment to correctly reflect state * Cleaned extra word --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- .../terraform/hooks/prepare/resource_linking.py | 12 ++++++++++-- .../hooks/prepare/test_resource_linking.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index a68d286276..84d67e0808 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -234,9 +234,12 @@ def _link_using_terraform_config(self, source_tf_resource: TFResource, cfn_resou ) if not dest_resources: LOG.debug( - "There are destination resources defined for for the source resource %s", + "There are no destination resources defined for the source resource %s, skipping linking.", source_tf_resource.full_address, ) + + return + for cfn_resource in cfn_resources: self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore @@ -287,7 +290,12 @@ def _link_using_linking_fields(self, cfn_resource: Dict) -> None: else ExistingResourceReference(value) for value in values ] - LOG.debug("The value of the source resource linking field after mapping $s", dest_resources) + + if not dest_resources: + LOG.debug("Skipping linking call back, no destination resources discovered.") + return + + LOG.debug("The value of the source resource linking field after mapping %s", dest_resources) self._resource_pair.cfn_resource_update_call_back_function(cfn_resource, dest_resources) # type: ignore def _process_resolved_resources( 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 b4afa74564..b363b21fbb 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 @@ -1129,6 +1129,21 @@ def setUp(self) -> None: linking_exceptions=self.linker_exceptions, ) + def test_applied_empty_destination_skip_call_back(self): + resource_linker = ResourceLinker(self.sample_resource_linking_pair) + resource_linker._link_using_linking_fields({"Properties": {"Layers": []}}) + + self.sample_resource_linking_pair.cfn_resource_update_call_back_function.assert_not_called() + + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking._resolve_resource_attribute") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinker._process_resolved_resources") + def test_config_empty_destination_skip_call_back(self, proccess_resolved_res_mock, resolve_resource_attr_mock): + resource_linker = ResourceLinker(self.sample_resource_linking_pair) + proccess_resolved_res_mock.return_value = [] + resource_linker._link_using_terraform_config(Mock(), Mock()) + + self.sample_resource_linking_pair.cfn_resource_update_call_back_function.assert_not_called() + def test_handle_linking_mix_of_applied_and_non_applied_resources(self): cfn_resource_depend_on_applied_resources = { "Type": "AWS::Lambda::Function", From 81b3a71b8bc6c7d405b4d4f8b674271d39ccf4e1 Mon Sep 17 00:00:00 2001 From: Jacob Fuss <32497805+jfuss@users.noreply.github.com> Date: Wed, 31 May 2023 14:03:58 -0500 Subject: [PATCH 10/14] chore: Strengthen wording on "no Auth" during deploy (#5231) Co-authored-by: Jacob Fuss Co-authored-by: Sriram Madapusi Vasudevan <3770774+sriram-mv@users.noreply.github.com> --- samcli/commands/deploy/deploy_context.py | 2 +- samcli/commands/deploy/guided_context.py | 2 +- .../commands/deploy/test_guided_context.py | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/samcli/commands/deploy/deploy_context.py b/samcli/commands/deploy/deploy_context.py index 07b883f054..c088518337 100644 --- a/samcli/commands/deploy/deploy_context.py +++ b/samcli/commands/deploy/deploy_context.py @@ -242,7 +242,7 @@ def deploy( for resource, authorization_required in auth_required_per_resource: if not authorization_required: - click.secho(f"{resource} may not have authorization defined.", fg="yellow") + click.secho(f"{resource} has no authentication.", fg="yellow") if use_changeset: try: diff --git a/samcli/commands/deploy/guided_context.py b/samcli/commands/deploy/guided_context.py index 4b65526d84..d5b27b745f 100644 --- a/samcli/commands/deploy/guided_context.py +++ b/samcli/commands/deploy/guided_context.py @@ -224,7 +224,7 @@ def prompt_authorization(self, stacks: List[Stack]): for resource, authorization_required in auth_required_per_resource: if not authorization_required: auth_confirm = confirm( - f"\t{self.start_bold}{resource} may not have authorization defined, Is this okay?{self.end_bold}", + f"\t{self.start_bold}{resource} has no authentication. Is this okay?{self.end_bold}", default=False, ) if not auth_confirm: diff --git a/tests/unit/commands/deploy/test_guided_context.py b/tests/unit/commands/deploy/test_guided_context.py index ce24541a46..a5137a781b 100644 --- a/tests/unit/commands/deploy/test_guided_context.py +++ b/tests/unit/commands/deploy/test_guided_context.py @@ -136,7 +136,7 @@ def test_guided_prompts_check_defaults_public_resources_zips( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -203,7 +203,7 @@ def test_guided_prompts_check_defaults_public_resources_images( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -282,7 +282,7 @@ def test_guided_prompts_check_defaults_public_resources_images_ecr_url( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -406,7 +406,7 @@ def test_guided_prompts_images_missing_repo( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -485,7 +485,7 @@ def test_guided_prompts_images_no_repo( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -702,7 +702,7 @@ def test_guided_prompts_check_configuration_file_prompt_calls( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -766,7 +766,7 @@ def test_guided_prompts_check_parameter_from_template( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -825,7 +825,7 @@ def test_guided_prompts_check_parameter_from_cmd_or_config( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), @@ -970,7 +970,7 @@ def test_guided_prompts_check_default_config_region( call(f"\t{self.gc.start_bold}Allow SAM CLI IAM role creation{self.gc.end_bold}", default=True), call(f"\t{self.gc.start_bold}Disable rollback{self.gc.end_bold}", default=False), call( - f"\t{self.gc.start_bold}HelloWorldFunction may not have authorization defined, Is this okay?{self.gc.end_bold}", + f"\t{self.gc.start_bold}HelloWorldFunction has no authentication. Is this okay?{self.gc.end_bold}", default=False, ), call(f"\t{self.gc.start_bold}Save arguments to configuration file{self.gc.end_bold}", default=True), From d517ece302a9e5fdfaa1ab10e231115cad2034fe Mon Sep 17 00:00:00 2001 From: Lucas <12496191+lucashuy@users.noreply.github.com> Date: Wed, 31 May 2023 14:16:59 -0700 Subject: [PATCH 11/14] feat: Link Lambda Authorizer to Rest API (#5219) * Link RestApiId property for Lambda Authorizers * Updated docstring * Format files --------- Co-authored-by: Mohamed Elasmar <71043312+moelasmar@users.noreply.github.com> --- .../terraform/hooks/prepare/exceptions.py | 12 +++++ .../hooks/prepare/resource_linking.py | 46 +++++++++++++++++-- .../hooks/prepare/resources/resource_links.py | 6 +++ .../hooks/prepare/test_resource_linking.py | 41 +++++++++++++++++ 4 files changed, 101 insertions(+), 4 deletions(-) diff --git a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py index ab42fe70b8..ca8cdf55f6 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py +++ b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py @@ -207,6 +207,18 @@ class GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException( """ +class OneGatewayAuthorizerToRestApiLinkingLimitationException(OneResourceLinkingLimitationException): + """ + Exception specific for Gateway Authorizer linking to more than one Rest API + """ + + +class GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException(LocalVariablesLinkingLimitationException): + """ + Exception specific for Gateway Authorizer linking to Rest APIs using locals. + """ + + class OneGatewayMethodToGatewayAuthorizerLinkingLimitationException(OneResourceLinkingLimitationException): """ Exception specific for Gateway Method linking to more than one Gateway Authorizer diff --git a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py index 84d67e0808..1cfeab8d5f 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resource_linking.py @@ -10,6 +10,7 @@ from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( FunctionLayerLocalVariablesLinkingLimitationException, GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException, @@ -19,6 +20,7 @@ LambdaFunctionToApiGatewayIntegrationLocalVariablesLinkingLimitationException, LocalVariablesLinkingLimitationException, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayAuthorizerToRestApiLinkingLimitationException, OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException, @@ -1546,7 +1548,7 @@ def _link_gateway_authorizer_to_lambda_function_call_back( def _link_gateway_authorizer_to_lambda_function( authorizer_config_resources: Dict[str, TFResource], authorizer_cfn_resources: Dict[str, List], - authorizer_tf_resources: Dict[str, Dict], + lamda_function_resources: Dict[str, Dict], ) -> None: """ Iterate through all the resources and link the corresponding Authorizer to each Lambda Function @@ -1557,8 +1559,8 @@ def _link_gateway_authorizer_to_lambda_function( Dictionary of configuration Authorizer resources authorizer_cfn_resources: Dict[str, List] Dictionary containing resolved configuration address of CFN Authorizer resources - lambda_layers_terraform_resources: Dict[str, Dict] - Dictionary of all actual terraform layers resources (not configuration resources). The dictionary's key is the + lamda_function_resources: Dict[str, Dict] + Dictionary of Terraform Lambda Function resources (not configuration resources). The dictionary's key is the calculated logical id for each resource """ exceptions = ResourcePairExceptions( @@ -1568,7 +1570,7 @@ def _link_gateway_authorizer_to_lambda_function( resource_linking_pair = ResourceLinkingPair( source_resource_cfn_resource=authorizer_cfn_resources, source_resource_tf_config=authorizer_config_resources, - destination_resource_tf=authorizer_tf_resources, + destination_resource_tf=lamda_function_resources, tf_destination_attribute_name="invoke_arn", terraform_link_field_name="authorizer_uri", cfn_link_field_name="AuthorizerUri", @@ -1579,6 +1581,42 @@ def _link_gateway_authorizer_to_lambda_function( ResourceLinker(resource_linking_pair).link_resources() +def _link_gateway_authorizer_to_rest_api( + authorizer_config_resources: Dict[str, TFResource], + authorizer_cfn_resources: Dict[str, List], + rest_api_resource: Dict[str, Dict], +) -> None: + """ + Iterate through all the resources and link the corresponding Authorizer to each Rest Api + + Parameters + ---------- + authorizer_config_resources: Dict[str, TFResource] + Dictionary of configuration Authorizer resources + authorizer_cfn_resources: Dict[str, List] + Dictionary containing resolved configuration address of CFN Authorizer resources + rest_api_resource: Dict[str, Dict] + Dictionary of Terraform Rest Api resources (not configuration resources). The dictionary's key is the + calculated logical id for each resource + """ + exceptions = ResourcePairExceptions( + multiple_resource_linking_exception=OneGatewayAuthorizerToRestApiLinkingLimitationException, + local_variable_linking_exception=GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, + ) + resource_linking_pair = ResourceLinkingPair( + source_resource_cfn_resource=authorizer_cfn_resources, + source_resource_tf_config=authorizer_config_resources, + destination_resource_tf=rest_api_resource, + tf_destination_attribute_name="id", + terraform_link_field_name="rest_api_id", + cfn_link_field_name="RestApiId", + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=_link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back, + linking_exceptions=exceptions, + ) + ResourceLinker(resource_linking_pair).link_resources() + + def _link_gateway_method_to_gateway_authorizer_call_back( gateway_method_cfn_resource: Dict, authorizer_resources: List[ReferenceType] ) -> None: 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 cd882f5815..b91478580a 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py +++ b/samcli/hook_packages/terraform/hooks/prepare/resources/resource_links.py @@ -13,6 +13,7 @@ ) from samcli.hook_packages.terraform.hooks.prepare.resource_linking import ( _link_gateway_authorizer_to_lambda_function, + _link_gateway_authorizer_to_rest_api, _link_gateway_integration_responses_to_gateway_resource, _link_gateway_integration_responses_to_gateway_rest_apis, _link_gateway_integrations_to_function_resource, @@ -79,6 +80,11 @@ dest=TF_AWS_LAMBDA_FUNCTION, linking_func=_link_gateway_authorizer_to_lambda_function, ), + LinkingPairCaller( + source=TF_AWS_API_GATEWAY_AUTHORIZER, + dest=TF_AWS_API_GATEWAY_REST_API, + linking_func=_link_gateway_authorizer_to_rest_api, + ), LinkingPairCaller( source=TF_AWS_API_GATEWAY_METHOD, dest=TF_AWS_API_GATEWAY_AUTHORIZER, 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 b363b21fbb..cad10e35b3 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 @@ -7,6 +7,7 @@ from parameterized import parameterized from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, InvalidResourceLinkingException, LocalVariablesLinkingLimitationException, @@ -14,6 +15,7 @@ LOCAL_VARIABLES_SUPPORT_ISSUE_LINK, APPLY_WORK_AROUND_MESSAGE, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayAuthorizerToRestApiLinkingLimitationException, OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneLambdaLayerLinkingLimitationException, FunctionLayerLocalVariablesLinkingLimitationException, @@ -42,6 +44,7 @@ _clean_references_list, _link_gateway_authorizer_to_lambda_function, _link_gateway_authorizer_to_lambda_function_call_back, + _link_gateway_authorizer_to_rest_api, _link_gateway_method_to_gateway_authorizer, _link_gateway_method_to_gateway_authorizer_call_back, _resolve_module_output, @@ -2295,6 +2298,44 @@ def test_link_gateway_method_to_gateway_authorizer_call_back(self, logical_ids, original_method["Properties"]["AuthorizerId"] = expected_reference self.assertEqual(original_method, new_method) + @patch( + "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_resource_to_gateway_rest_apis_rest_api_id_call_back" + ) + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinker") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourceLinkingPair") + @patch("samcli.hook_packages.terraform.hooks.prepare.resource_linking.ResourcePairExceptions") + def test_link_gateway_authorizer_to_rest_api( + self, + mock_resource_linking_exceptions, + mock_resource_linking_pair, + mock_resource_linker, + mock_link_resource_to_rest_api_call_back, + ): + authorizer_cfn_resources = Mock() + authorizer_config_resources = Mock() + rest_api_resources = Mock() + + _link_gateway_authorizer_to_rest_api(authorizer_config_resources, authorizer_cfn_resources, rest_api_resources) + + mock_resource_linking_exceptions.assert_called_once_with( + multiple_resource_linking_exception=OneGatewayAuthorizerToRestApiLinkingLimitationException, + local_variable_linking_exception=GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, + ) + + mock_resource_linking_pair.assert_called_once_with( + source_resource_cfn_resource=authorizer_cfn_resources, + source_resource_tf_config=authorizer_config_resources, + destination_resource_tf=rest_api_resources, + tf_destination_attribute_name="id", + terraform_link_field_name="rest_api_id", + cfn_link_field_name="RestApiId", + terraform_resource_type_prefix=API_GATEWAY_REST_API_RESOURCE_ADDRESS_PREFIX, + cfn_resource_update_call_back_function=mock_link_resource_to_rest_api_call_back, + linking_exceptions=mock_resource_linking_exceptions(), + ) + + mock_resource_linker.assert_called_once_with(mock_resource_linking_pair()) + @patch( "samcli.hook_packages.terraform.hooks.prepare.resource_linking._link_gateway_method_to_gateway_authorizer_call_back" ) From 81c9c41ffd04d180f06c134ef13bf07e539d67ee Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Thu, 1 Jun 2023 08:35:59 -0700 Subject: [PATCH 12/14] Terraform start-api integration tests --- .../local/start_api/start_api_integ_base.py | 12 +++- ...st_start_api_with_terraform_application.py | 56 +++++++++++++++++++ 2 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 tests/integration/local/start_api/test_start_api_with_terraform_application.py diff --git a/tests/integration/local/start_api/start_api_integ_base.py b/tests/integration/local/start_api/start_api_integ_base.py index b1f9a6a785..77f755aec8 100644 --- a/tests/integration/local/start_api/start_api_integ_base.py +++ b/tests/integration/local/start_api/start_api_integ_base.py @@ -33,6 +33,9 @@ class StartApiIntegBaseClass(TestCase): do_collect_cmd_init_output: bool = False + command_list = None + project_directory = None + @classmethod def setUpClass(cls): # This is the directory for tests/integration which will be used to file the testdata @@ -84,7 +87,8 @@ def start_api_with_retry(cls, retries=3): def start_api(cls): command = get_sam_command() - command_list = [command, "local", "start-api", "-t", cls.template, "-p", cls.port] + command_list = cls.command_list or [command, "local", "start-api", "-t", cls.template] + command_list.extend(["-p", cls.port]) if cls.container_mode: command_list += ["--warm-containers", cls.container_mode] @@ -99,7 +103,11 @@ def start_api(cls): for image in cls.invoke_image: command_list += ["--invoke-image", image] - cls.start_api_process = Popen(command_list, stderr=PIPE, stdout=PIPE) + cls.start_api_process = ( + Popen(command_list, stderr=PIPE, stdout=PIPE) + if not cls.project_directory + else Popen(command_list, stderr=PIPE, stdout=PIPE, cwd=cls.project_directory) + ) cls.start_api_process_output = wait_for_local_process( cls.start_api_process, cls.port, collect_output=cls.do_collect_cmd_init_output ) 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 new file mode 100644 index 0000000000..628083753a --- /dev/null +++ b/tests/integration/local/start_api/test_start_api_with_terraform_application.py @@ -0,0 +1,56 @@ +import shutil +import os +from pathlib import Path +from typing import Optional +from unittest import skipIf +from http.client import HTTPConnection + +import pytest +import requests + +from tests.integration.local.start_api.start_api_integ_base import StartApiIntegBaseClass +from tests.testing_utils import get_sam_command, CI_OVERRIDE + + +class TerraformStartApiIntegrationBase(StartApiIntegBaseClass): + terraform_application: Optional[str] = None + + @classmethod + def setUpClass(cls): + command = get_sam_command() + cls.template_path = "" + cls.build_before_invoke = False + cls.command_list = [command, "local", "start-api", "--hook-name", "terraform", "--beta-features"] + cls.test_data_path = Path(cls.get_integ_dir()) / "testdata" / "start_api" + cls.project_directory = cls.test_data_path / "terraform" / cls.terraform_application + super(TerraformStartApiIntegrationBase, cls).setUpClass() + + @staticmethod + def get_integ_dir(): + return Path(__file__).resolve().parents[2] + + def tearDown(self) -> None: + shutil.rmtree(str(Path(self.project_directory / ".aws-sam-iacs")), ignore_errors=True) # type: ignore + shutil.rmtree(str(Path(self.project_directory / ".terraform")), ignore_errors=True) # type: ignore + try: + os.remove(str(Path(self.project_directory / ".terraform.lock.hcl"))) # type: ignore + except (FileNotFoundError, PermissionError): + pass + + +# @skipIf( +# not CI_OVERRIDE, +# "Skip Terraform test cases unless running in CI", +# ) +@pytest.mark.flaky(reruns=3) +class TestStartApiTerraformApplication(TerraformStartApiIntegrationBase): + terraform_application = "terraform-v1-api-simple" + + def setUp(self): + self.url = "http://127.0.0.1:{}".format(self.port) + + def test_successful_request(self): + response = requests.get(self.url + "/hello", timeout=300) + + self.assertEqual(response.status_code, 200) + self.assertEqual(response.json(), {"message": "hello world"}) From 400d4e6c37127645a505c092c2f5316dbfc175bf Mon Sep 17 00:00:00 2001 From: Daniel Mil Date: Thu, 1 Jun 2023 08:56:08 -0700 Subject: [PATCH 13/14] Add test files --- .../HelloWorldFunction.zip | Bin 0 -> 1079 bytes .../terraform/terraform-v1-api-simple/main.tf | 112 ++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 tests/integration/testdata/start_api/terraform/terraform-v1-api-simple/HelloWorldFunction.zip create mode 100644 tests/integration/testdata/start_api/terraform/terraform-v1-api-simple/main.tf diff --git a/tests/integration/testdata/start_api/terraform/terraform-v1-api-simple/HelloWorldFunction.zip b/tests/integration/testdata/start_api/terraform/terraform-v1-api-simple/HelloWorldFunction.zip new file mode 100644 index 0000000000000000000000000000000000000000..4daa6de02dd8d472c93e7be277639d6220ea20b3 GIT binary patch literal 1079 zcmWIWW@Zs#-~hsiz!xD5NPwF`fgwIVGcU6wK3=b&GBkvjfn6f?L=p)9PQR8^TEWf0 z$nt}cfdOa$0|PTdfHyk_NIw(QkN~Vk*RwD%umO!uEGQtt)V;w6yKWl@?ES6H_H>d% zj=rr_mB#73$cT&^d&Rnvd~@F3nr0UsKKwd(1On*(k|vmU$lC-Lv;C$Aq$o-7pq&E{M-q~^XxoMvJEv_l+m@7jTa9;hPw$6Vy`wtz-{!cZE_a|D; zeVDjm-otwV`j)dFmQUoFvQj%^=GxV#jB;WW`yak~>TfKv^`&=a>12=7dUs@}Yn^z} zFu?8)zcdJTqJ=t z?SE6fiLK;bUk3&I4etb<+SUg09kOvSntbo~f?C7+ga0qrO)`tl_p0VQ<7}sSdh&*g z$w~E2-%Miq^?$!E*jEv^Y;xM}6Fw>bvpuG-H<`PiX<4zU?Xji0HFNg=UgQ|py+YGt zRrH$pt#d_Oa|5T`@Htzt>Z0C$pN~`I-W@BlkX^#O$1aogyad~GpBYa?Oit|OG%Q@r zJne+QhMg=wGo;F|=4x6*KUUklYT^|>#^BDNRr{aKXiYwO@2*tqq9%TG^V?6a-djyoq`xwJ&A=1r>d(6HZAx$U$5MgY z6aJ*VtGvDB-}X1=;G`9O{2n_nZGjRH_M`<$cLKnqRg_v-npu>Zo0?ZrtXEP|LQc|U zWD;RU%@?p_4ayfVu%r>hA|aOqc%vDN Date: Thu, 1 Jun 2023 09:01:02 -0700 Subject: [PATCH 14/14] Uncomment skip --- .../test_start_api_with_terraform_application.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 628083753a..59b088b54b 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 @@ -38,10 +38,10 @@ def tearDown(self) -> None: pass -# @skipIf( -# not CI_OVERRIDE, -# "Skip Terraform test cases unless running in CI", -# ) +@skipIf( + not CI_OVERRIDE, + "Skip Terraform test cases unless running in CI", +) @pytest.mark.flaky(reruns=3) class TestStartApiTerraformApplication(TerraformStartApiIntegrationBase): terraform_application = "terraform-v1-api-simple"