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 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" 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/samcli/hook_packages/terraform/hooks/prepare/exceptions.py b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py index 7168037ae9..ca8cdf55f6 100644 --- a/samcli/hook_packages/terraform/hooks/prepare/exceptions.py +++ b/samcli/hook_packages/terraform/hooks/prepare/exceptions.py @@ -207,6 +207,32 @@ 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 + """ + + +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..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,8 @@ from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( FunctionLayerLocalVariablesLinkingLimitationException, GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, + GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayIntegrationResponseLocalVariablesLinkingLimitationException, GatewayResourceToApiGatewayMethodLocalVariablesLinkingLimitationException, @@ -18,6 +20,8 @@ LambdaFunctionToApiGatewayIntegrationLocalVariablesLinkingLimitationException, LocalVariablesLinkingLimitationException, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayAuthorizerToRestApiLinkingLimitationException, + OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationLinkingLimitationException, OneGatewayResourceToApiGatewayIntegrationResponseLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, @@ -50,6 +54,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." @@ -231,9 +236,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 @@ -284,7 +292,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( @@ -1535,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 @@ -1546,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( @@ -1557,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", @@ -1566,3 +1579,105 @@ def _link_gateway_authorizer_to_lambda_function( linking_exceptions=exceptions, ) 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: + """ + 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..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,11 +13,13 @@ ) 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, _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 +80,14 @@ 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, + linking_func=_link_gateway_method_to_gateway_authorizer, + ), ] 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/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" } 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/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: 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/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), 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..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,12 +7,16 @@ from parameterized import parameterized from samcli.hook_packages.terraform.hooks.prepare.exceptions import ( GatewayAuthorizerToLambdaFunctionLocalVariablesLinkingLimitationException, + GatewayAuthorizerToRestApiLocalVariablesLinkingLimitationException, + GatewayMethodToGatewayAuthorizerLocalVariablesLinkingLimitationException, InvalidResourceLinkingException, LocalVariablesLinkingLimitationException, ONE_LAMBDA_LAYER_LINKING_ISSUE_LINK, LOCAL_VARIABLES_SUPPORT_ISSUE_LINK, APPLY_WORK_AROUND_MESSAGE, OneGatewayAuthorizerToLambdaFunctionLinkingLimitationException, + OneGatewayAuthorizerToRestApiLinkingLimitationException, + OneGatewayMethodToGatewayAuthorizerLinkingLimitationException, OneLambdaLayerLinkingLimitationException, FunctionLayerLocalVariablesLinkingLimitationException, OneGatewayResourceToApiGatewayMethodLinkingLimitationException, @@ -36,9 +40,13 @@ ) 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_authorizer_to_rest_api, + _link_gateway_method_to_gateway_authorizer, + _link_gateway_method_to_gateway_authorizer_call_back, _resolve_module_output, _resolve_module_variable, _build_module, @@ -1124,6 +1132,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", @@ -2081,6 +2104,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 +2121,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 +2273,105 @@ 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_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" + ) + @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()) 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()