diff --git a/samcli/lib/intrinsic_resolver/intrinsic_property_resolver.py b/samcli/lib/intrinsic_resolver/intrinsic_property_resolver.py index aecc2a85aad..f1649723a6c 100644 --- a/samcli/lib/intrinsic_resolver/intrinsic_property_resolver.py +++ b/samcli/lib/intrinsic_resolver/intrinsic_property_resolver.py @@ -615,7 +615,7 @@ def handle_fn_getatt(self, intrinsic_value, ignore_errors): verify_intrinsic_type_str(logical_id, IntrinsicResolver.FN_GET_ATT) verify_intrinsic_type_str(resource_type, IntrinsicResolver.FN_GET_ATT) - return self._symbol_resolver.resolve_symbols(logical_id, resource_type) + return self._symbol_resolver.resolve_symbols(logical_id, resource_type, ignore_errors) def handle_fn_ref(self, intrinsic_value, ignore_errors): """ @@ -716,24 +716,14 @@ def handle_fn_if(self, intrinsic_value, ignore_errors): ------- This will return value_if_true and value_if_false depending on how the condition is evaluated """ - arguments = self.intrinsic_property_resolver( - intrinsic_value, ignore_errors, parent_function=IntrinsicResolver.FN_IF - ) - verify_intrinsic_type_list(arguments, IntrinsicResolver.FN_IF) - verify_number_arguments(arguments, IntrinsicResolver.FN_IF, num=3) + verify_intrinsic_type_list(intrinsic_value, IntrinsicResolver.FN_IF) + verify_number_arguments(intrinsic_value, IntrinsicResolver.FN_IF, num=3) condition_name = self.intrinsic_property_resolver( - arguments[0], ignore_errors, parent_function=IntrinsicResolver.FN_IF + intrinsic_value[0], ignore_errors, parent_function=IntrinsicResolver.FN_IF ) verify_intrinsic_type_str(condition_name, IntrinsicResolver.FN_IF) - value_if_true = self.intrinsic_property_resolver( - arguments[1], ignore_errors, parent_function=IntrinsicResolver.FN_IF - ) - value_if_false = self.intrinsic_property_resolver( - arguments[2], ignore_errors, parent_function=IntrinsicResolver.FN_IF - ) - condition = self._conditions.get(condition_name) verify_intrinsic_type_dict( condition, @@ -750,7 +740,8 @@ def handle_fn_if(self, intrinsic_value, ignore_errors): message="The result of {} must evaluate to bool".format(IntrinsicResolver.FN_IF), ) - return value_if_true if condition_evaluated else value_if_false + selected_value = intrinsic_value[1] if condition_evaluated else intrinsic_value[2] + return self.intrinsic_property_resolver(selected_value, ignore_errors, parent_function=IntrinsicResolver.FN_IF) def handle_fn_equals(self, intrinsic_value, ignore_errors): """ diff --git a/tests/integration/local/invoke/test_integrations_cli.py b/tests/integration/local/invoke/test_integrations_cli.py index 95a0a95cea4..dfc011e8f7d 100644 --- a/tests/integration/local/invoke/test_integrations_cli.py +++ b/tests/integration/local/invoke/test_integrations_cli.py @@ -393,6 +393,27 @@ def test_invoke_with_env_using_parameters(self): self.assertEqual(environ["MyRuntimeVersion"], "v0") self.assertEqual(environ["EmptyDefaultParameter"], "") + @pytest.mark.flaky(reruns=3) + def test_invoke_with_env_using_fn_if_ignores_unresolvable_branch(self): + command_list = InvokeIntegBase.get_command_list( + "EchoEnvWithFnIf", + template_path=self.template_path, + event_path=self.event_path, + ) + + process = Popen(command_list, stdout=PIPE) + try: + stdout, _ = process.communicate(timeout=TIMEOUT) + except TimeoutExpired: + process.kill() + raise + + self.assertEqual(process.returncode, 0) + process_stdout = stdout.strip() + environ = json.loads(process_stdout.decode("utf-8")) + + self.assertEqual(environ["FunctionUrl"], "https://custom.example.com/") + @pytest.mark.flaky(reruns=3) def test_invoke_multi_tenant_function(self): command_list = InvokeIntegBase.get_command_list( diff --git a/tests/integration/testdata/invoke/template.yml b/tests/integration/testdata/invoke/template.yml index 61efc176eb5..2695fcf3587 100644 --- a/tests/integration/testdata/invoke/template.yml +++ b/tests/integration/testdata/invoke/template.yml @@ -26,6 +26,17 @@ Parameters: Type: String Default: "2" + UseCustomFunctionUrl: + Type: String + Default: "true" + + CustomFunctionUrl: + Type: String + Default: "https://custom.example.com/" + +Conditions: + ShouldUseCustomFunctionUrl: !Equals [!Ref UseCustomFunctionUrl, "true"] + Mappings: common: LambdaFunction: @@ -202,6 +213,30 @@ Resources: MyRuntimeVersion: !Ref MyRuntimeVersion EmptyDefaultParameter: !Ref EmptyDefaultParameter + FunctionWithUrlConfig: + Type: AWS::Serverless::Function + Properties: + Handler: main.handler + Runtime: python3.9 + CodeUri: . + Timeout: 600 + FunctionUrlConfig: + AuthType: NONE + + EchoEnvWithFnIf: + Type: AWS::Serverless::Function + Properties: + Handler: main.env_var_echo_hanler + Runtime: python3.9 + CodeUri: . + Timeout: 600 + Environment: + Variables: + FunctionUrl: !If + - ShouldUseCustomFunctionUrl + - !Ref CustomFunctionUrl + - !GetAtt FunctionWithUrlConfigUrl.FunctionUrl + TimeoutFunctionWithStringParameter: Type: AWS::Serverless::Function Properties: diff --git a/tests/unit/lib/intrinsic_resolver/test_intrinsic_resolver.py b/tests/unit/lib/intrinsic_resolver/test_intrinsic_resolver.py index 39d342764de..05ec9104c09 100644 --- a/tests/unit/lib/intrinsic_resolver/test_intrinsic_resolver.py +++ b/tests/unit/lib/intrinsic_resolver/test_intrinsic_resolver.py @@ -3,7 +3,7 @@ from copy import deepcopy from pathlib import Path from unittest import TestCase -from unittest.mock import patch +from unittest.mock import MagicMock, patch from parameterized import parameterized @@ -468,6 +468,13 @@ def test_fn_getatt_second_arguments_invalid(self, name, intrinsic): with self.assertRaises(InvalidIntrinsicException, msg=name): self.resolver.intrinsic_property_resolver({"Fn::GetAtt": ["some logical Id", intrinsic]}, True) + def test_fn_getatt_ignore_errors_forwarded_for_unsupported_attribute(self): + intrinsic = {"Fn::GetAtt": ["UnknownResource", "UnknownAttribute"]} + + result = self.resolver.intrinsic_property_resolver(intrinsic, True) + + self.assertEqual(result, "$UnknownResource.UnknownAttribute") + class TestIntrinsicFnSubResolver(TestCase): def setUp(self): @@ -906,6 +913,38 @@ def test_fn_if_condition_not_bool_fail(self): with self.assertRaises(InvalidIntrinsicException, msg="Invalid Condition"): self.resolver.intrinsic_property_resolver({"Fn::If": ["InvalidCondition", "test", "test"]}, True) + def test_fn_if_selects_resolvable_true_branch_ignoring_unresolvable_false_branch(self): + intrinsic = {"Fn::If": ["TestCondition", "resolved-value", {"Fn::GetAtt": ["Function2Url", "FunctionUrl"]}]} + + result = self.resolver.intrinsic_property_resolver(intrinsic, False) + self.assertEqual(result, "resolved-value") + + def test_fn_if_selects_resolvable_false_branch_ignoring_unresolvable_true_branch(self): + intrinsic = {"Fn::If": ["NotTestCondition", {"Fn::GetAtt": ["Function2Url", "FunctionUrl"]}, "resolved-value"]} + + result = self.resolver.intrinsic_property_resolver(intrinsic, False) + self.assertEqual(result, "resolved-value") + + def test_fn_if_does_not_evaluate_unselected_false_branch(self): + mock_handle_fn_getatt = MagicMock() + self.resolver.intrinsic_key_function_map[IntrinsicResolver.FN_GET_ATT] = mock_handle_fn_getatt + intrinsic = {"Fn::If": ["TestCondition", "resolved-value", {"Fn::GetAtt": ["Function2Url", "FunctionUrl"]}]} + + result = self.resolver.intrinsic_property_resolver(intrinsic, False) + + self.assertEqual(result, "resolved-value") + mock_handle_fn_getatt.assert_not_called() + + def test_fn_if_does_not_evaluate_unselected_true_branch(self): + mock_handle_fn_getatt = MagicMock() + self.resolver.intrinsic_key_function_map[IntrinsicResolver.FN_GET_ATT] = mock_handle_fn_getatt + intrinsic = {"Fn::If": ["NotTestCondition", {"Fn::GetAtt": ["Function2Url", "FunctionUrl"]}, "resolved-value"]} + + result = self.resolver.intrinsic_property_resolver(intrinsic, False) + + self.assertEqual(result, "resolved-value") + mock_handle_fn_getatt.assert_not_called() + class TestIntrinsicAttribteResolution(TestCase): def setUp(self):