From ef9cd2206c4c9e306168eed0fc53adb279460589 Mon Sep 17 00:00:00 2001 From: Sid Madipalli Date: Mon, 10 Nov 2025 14:08:20 -0800 Subject: [PATCH 1/7] Adding AWS::NoValue support to Serverless Function IAM role --- samtranslator/model/sam_resources.py | 53 ++++++++++--- tests/model/test_sam_resources.py | 112 +++++++++++++++++++++++++++ 2 files changed, 155 insertions(+), 10 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 70e7ef6750..b246e4afd4 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -347,16 +347,13 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P managed_policy_map = kwargs.get("managed_policy_map", {}) get_managed_policy_map = kwargs.get("get_managed_policy_map") - execution_role = None - if lambda_function.Role is None: - execution_role = self._construct_role( - managed_policy_map, - event_invoke_policies, - intrinsics_resolver, - get_managed_policy_map, - ) - lambda_function.Role = execution_role.get_runtime_attr("arn") - resources.append(execution_role) + execution_role = self._construct_role( + managed_policy_map, + event_invoke_policies, + intrinsics_resolver, + get_managed_policy_map, + ) + self._make_lambda_role(lambda_function, intrinsics_resolver, execution_role, resources) try: resources += self._generate_event_resources( @@ -374,6 +371,42 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P return resources + def _make_lambda_role( + self, + lambda_function: LambdaFunction, + intrinsics_resolver: IntrinsicsResolver, + execution_role: IAMRole, + resources: List[Any], + ) -> None: + lambda_role = lambda_function.Role + + if lambda_role is None: + resources.append(execution_role) + lambda_function.Role = execution_role.get_runtime_attr("arn") + + if is_intrinsic_if(lambda_role): + resources.append(execution_role) + + # We need to create and if else condition here + role_resolved_value = intrinsics_resolver.resolve_parameter_refs(self.Role) + role_list = role_resolved_value.get("Fn::If") + + # both are none values then we need to create a role + if is_intrinsic_no_value(role_list[1]) and is_intrinsic_no_value(role_list[2]): + lambda_function.Role = execution_role.get_runtime_attr("arn") + + # first value is none so we should create condition ? create : [2] + elif is_intrinsic_no_value(role_list[1]): + lambda_function.Role = make_conditional( + role_list[0], execution_role.get_runtime_attr("arn"), role_list[2] + ) + + # second value is none so we should create condition ? [1] : create + elif is_intrinsic_no_value(role_list[2]): + lambda_function.Role = make_conditional( + role_list[0], role_list[1], execution_role.get_runtime_attr("arn") + ) + def _construct_event_invoke_config( # noqa: PLR0913 self, function_name: str, diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index b53649f107..5db3868449 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -738,3 +738,115 @@ def test_function_datasource_set_with_none(): api = SamGraphQLApi("MyApi") none_datasource = api._construct_none_datasource("foo") assert none_datasource + + +class TestSamFunctionRoleResolver(TestCase): + """ + Tests for resolving IAM role property values in SamFunction + """ + + def setUp(self): + self.function = SamFunction("foo") + self.function.CodeUri = "s3://foobar/foo.zip" + self.function.Runtime = "foo" + self.function.Handler = "bar" + self.template = {"Conditions": {}} + + self.kwargs = { + "intrinsics_resolver": IntrinsicsResolver({}), + "event_resources": [], + "managed_policy_map": {}, + "resource_resolver": ResourceResolver({}), + "conditions": self.template.get("Conditions", {}), + } + + def test_role_none_creates_execution_role(self): + self.function.Role = None + cfn_resources = self.function.to_cloudformation(**self.kwargs) + generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] + + self.assertEqual(len(generated_roles), 1) # Should create execution role + + def test_role_explicit_arn_no_execution_role(self): + test_role = "arn:aws:iam::123456789012:role/existing-role" + self.function.Role = test_role + + cfn_resources = self.function.to_cloudformation(**self.kwargs) + generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] + lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function") + + self.assertEqual(len(generated_roles), 0) # Should not create execution role + self.assertEqual(lambda_function.Role, test_role) + + def test_role_fn_if_no_aws_no_value_keeps_original(self): + role_conditional = { + "Fn::If": ["Condition", "arn:aws:iam::123456789012:role/existing-role", {"Ref": "iamRoleArn"}] + } + self.function.Role = role_conditional + + template = {"Conditions": {"Condition": True}} + kwargs = dict(self.kwargs) + kwargs["conditions"] = template.get("Conditions", {}) + + cfn_resources = self.function.to_cloudformation(**self.kwargs) + generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] + lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function") + + self.assertEqual(len(generated_roles), 1) + self.assertEqual(lambda_function.Role, role_conditional) + + def test_role_fn_if_both_no_value_creates_execution_role(self): + role_conditional = {"Fn::If": ["Condition", {"Ref": "AWS::NoValue"}, {"Ref": "AWS::NoValue"}]} + self.function.Role = role_conditional + + template = {"Conditions": {"Condition": True}} + kwargs = dict(self.kwargs) + kwargs["conditions"] = template.get("Conditions", {}) + + cfn_resources = self.function.to_cloudformation(**self.kwargs) + generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] + + self.assertEqual(len(generated_roles), 1) + + def test_role_fn_if_first_no_value_creates_conditional_role(self): + role_conditional = {"Fn::If": ["Condition", {"Ref": "AWS::NoValue"}, {"Ref": "iamRoleArn"}]} + self.function.Role = role_conditional + + template = {"Conditions": {"Condition": True}} + kwargs = dict(self.kwargs) + kwargs["conditions"] = template.get("Conditions", {}) + + cfn_resources = self.function.to_cloudformation(**self.kwargs) + generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] + lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function") + + self.assertEqual(len(generated_roles), 1) + self.assertEqual( + lambda_function.Role, {"Fn::If": ["Condition", {"Fn::GetAtt": ["fooRole", "Arn"]}, {"Ref": "iamRoleArn"}]} + ) + + def test_role_fn_if_second_no_value_creates_conditional_role(self): + role_conditional = {"Fn::If": ["Condition", {"Ref": "iamRoleArn"}, {"Ref": "AWS::NoValue"}]} + self.function.Role = role_conditional + + template = {"Conditions": {"Condition": True}} + kwargs = dict(self.kwargs) + kwargs["conditions"] = template.get("Conditions", {}) + + cfn_resources = self.function.to_cloudformation(**self.kwargs) + generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] + lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function") + + self.assertEqual(len(generated_roles), 1) + self.assertEqual( + lambda_function.Role, {"Fn::If": ["Condition", {"Ref": "iamRoleArn"}, {"Fn::GetAtt": ["fooRole", "Arn"]}]} + ) + + def test_role_get_att_no_execution_role(self): + role_get_att = {"Fn::GetAtt": ["MyCustomRole", "Arn"]} + self.function.Role = role_get_att + + cfn_resources = self.function.to_cloudformation(**self.kwargs) + lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function") + + self.assertEqual(lambda_function.Role, role_get_att) From 79e868011d0e442a32c697366cfea0bc45a2ffd5 Mon Sep 17 00:00:00 2001 From: Sid Madipalli Date: Mon, 8 Dec 2025 14:14:26 -0800 Subject: [PATCH 2/7] Added translator transform templated tests --- tests/model/test_sam_resources.py | 2 + .../error_function_invalid_iam_role_type.yaml | 8 ++ .../input/function_with_iam_role.yaml | 20 +++++ .../error_function_invalid_iam_role_type.json | 14 +++ .../output/aws-cn/function_with_iam_role.json | 85 +++++++++++++++++++ .../error_function_invalid_iam_role_type.json | 14 +++ .../aws-us-gov/function_with_iam_role.json | 85 +++++++++++++++++++ .../error_function_invalid_iam_role_type.json | 14 +++ .../output/function_with_iam_role.json | 85 +++++++++++++++++++ 9 files changed, 327 insertions(+) create mode 100644 tests/translator/input/error_function_invalid_iam_role_type.yaml create mode 100644 tests/translator/input/function_with_iam_role.yaml create mode 100644 tests/translator/output/aws-cn/error_function_invalid_iam_role_type.json create mode 100644 tests/translator/output/aws-cn/function_with_iam_role.json create mode 100644 tests/translator/output/aws-us-gov/error_function_invalid_iam_role_type.json create mode 100644 tests/translator/output/aws-us-gov/function_with_iam_role.json create mode 100644 tests/translator/output/error_function_invalid_iam_role_type.json create mode 100644 tests/translator/output/function_with_iam_role.json diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index 4c66ec4211..edc54004ae 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -851,6 +851,8 @@ def test_role_get_att_no_execution_role(self): lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function") self.assertEqual(lambda_function.Role, role_get_att) + + class TestSamCapacityProvider(TestCase): """Tests for SamCapacityProvider""" diff --git a/tests/translator/input/error_function_invalid_iam_role_type.yaml b/tests/translator/input/error_function_invalid_iam_role_type.yaml new file mode 100644 index 0000000000..abcac61df9 --- /dev/null +++ b/tests/translator/input/error_function_invalid_iam_role_type.yaml @@ -0,0 +1,8 @@ +Resources: + MinimalFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.10 + Role: 2 diff --git a/tests/translator/input/function_with_iam_role.yaml b/tests/translator/input/function_with_iam_role.yaml new file mode 100644 index 0000000000..96729196a3 --- /dev/null +++ b/tests/translator/input/function_with_iam_role.yaml @@ -0,0 +1,20 @@ +Parameters: + iamRoleArn: + Type: String + Description: The ARN of an IAM role to use as this function's execution role. + If a role isn't specified, one is created for you with a logical ID of Role. + +Conditions: + RoleExists: !Not [!Equals ['', !Ref iamRoleArn]] + +Resources: + MinimalFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.10 + Role: !If + - RoleExists + - !Ref "iamRoleArn" + - !Ref "AWS::NoValue" diff --git a/tests/translator/output/aws-cn/error_function_invalid_iam_role_type.json b/tests/translator/output/aws-cn/error_function_invalid_iam_role_type.json new file mode 100644 index 0000000000..22f3b61ecf --- /dev/null +++ b/tests/translator/output/aws-cn/error_function_invalid_iam_role_type.json @@ -0,0 +1,14 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 1. ", + "Resource with id [MinimalFunction] is invalid. ", + "Property 'Role' should be a string." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MinimalFunction] is invalid. Property 'Role' should be a string.", + "errors": [ + { + "errorMessage": "Resource with id [MinimalFunction] is invalid. Property 'Role' should be a string." + } + ] +} diff --git a/tests/translator/output/aws-cn/function_with_iam_role.json b/tests/translator/output/aws-cn/function_with_iam_role.json new file mode 100644 index 0000000000..d6f9ed00a3 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_iam_role.json @@ -0,0 +1,85 @@ +{ + "Conditions": { + "RoleExists": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Ref": "iamRoleArn" + } + ] + } + ] + } + }, + "Parameters": { + "iamRoleArn": { + "Description": "The ARN of an IAM role to use as this function's execution role. If a role isn't specified, one is created for you with a logical ID of Role.", + "Type": "String" + } + }, + "Resources": { + "MinimalFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::If": [ + "RoleExists", + { + "Ref": "iamRoleArn" + }, + { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + } + ] + }, + "Runtime": "python3.10", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MinimalFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-us-gov/error_function_invalid_iam_role_type.json b/tests/translator/output/aws-us-gov/error_function_invalid_iam_role_type.json new file mode 100644 index 0000000000..22f3b61ecf --- /dev/null +++ b/tests/translator/output/aws-us-gov/error_function_invalid_iam_role_type.json @@ -0,0 +1,14 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 1. ", + "Resource with id [MinimalFunction] is invalid. ", + "Property 'Role' should be a string." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MinimalFunction] is invalid. Property 'Role' should be a string.", + "errors": [ + { + "errorMessage": "Resource with id [MinimalFunction] is invalid. Property 'Role' should be a string." + } + ] +} diff --git a/tests/translator/output/aws-us-gov/function_with_iam_role.json b/tests/translator/output/aws-us-gov/function_with_iam_role.json new file mode 100644 index 0000000000..0674fa36b2 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_iam_role.json @@ -0,0 +1,85 @@ +{ + "Conditions": { + "RoleExists": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Ref": "iamRoleArn" + } + ] + } + ] + } + }, + "Parameters": { + "iamRoleArn": { + "Description": "The ARN of an IAM role to use as this function's execution role. If a role isn't specified, one is created for you with a logical ID of Role.", + "Type": "String" + } + }, + "Resources": { + "MinimalFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::If": [ + "RoleExists", + { + "Ref": "iamRoleArn" + }, + { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + } + ] + }, + "Runtime": "python3.10", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MinimalFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/error_function_invalid_iam_role_type.json b/tests/translator/output/error_function_invalid_iam_role_type.json new file mode 100644 index 0000000000..22f3b61ecf --- /dev/null +++ b/tests/translator/output/error_function_invalid_iam_role_type.json @@ -0,0 +1,14 @@ +{ + "_autoGeneratedBreakdownErrorMessage": [ + "Invalid Serverless Application Specification document. ", + "Number of errors found: 1. ", + "Resource with id [MinimalFunction] is invalid. ", + "Property 'Role' should be a string." + ], + "errorMessage": "Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [MinimalFunction] is invalid. Property 'Role' should be a string.", + "errors": [ + { + "errorMessage": "Resource with id [MinimalFunction] is invalid. Property 'Role' should be a string." + } + ] +} diff --git a/tests/translator/output/function_with_iam_role.json b/tests/translator/output/function_with_iam_role.json new file mode 100644 index 0000000000..3abc1c5ccc --- /dev/null +++ b/tests/translator/output/function_with_iam_role.json @@ -0,0 +1,85 @@ +{ + "Conditions": { + "RoleExists": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Ref": "iamRoleArn" + } + ] + } + ] + } + }, + "Parameters": { + "iamRoleArn": { + "Description": "The ARN of an IAM role to use as this function's execution role. If a role isn't specified, one is created for you with a logical ID of Role.", + "Type": "String" + } + }, + "Resources": { + "MinimalFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::If": [ + "RoleExists", + { + "Ref": "iamRoleArn" + }, + { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + } + ] + }, + "Runtime": "python3.10", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MinimalFunctionRole": { + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} From cf96ad9463b1d4c0724c49d3516710718435f124 Mon Sep 17 00:00:00 2001 From: Sid Madipalli Date: Thu, 11 Dec 2025 14:59:50 -0800 Subject: [PATCH 3/7] Adding condition for IAM resource --- samtranslator/model/sam_resources.py | 16 +++++++++++++--- .../output/aws-cn/function_with_iam_role.json | 8 ++++++++ .../aws-us-gov/function_with_iam_role.json | 8 ++++++++ .../output/function_with_iam_role.json | 8 ++++++++ 4 files changed, 37 insertions(+), 3 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 52372b168c..3cf41d0914 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -394,7 +394,7 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P intrinsics_resolver, get_managed_policy_map, ) - self._make_lambda_role(lambda_function, intrinsics_resolver, execution_role, resources) + self._make_lambda_role(lambda_function, intrinsics_resolver, execution_role, resources, conditions) try: resources += self._generate_event_resources( @@ -418,6 +418,7 @@ def _make_lambda_role( intrinsics_resolver: IntrinsicsResolver, execution_role: IAMRole, resources: List[Any], + conditions: Dict[str, Any], ) -> None: lambda_role = lambda_function.Role @@ -432,8 +433,17 @@ def _make_lambda_role( role_resolved_value = intrinsics_resolver.resolve_parameter_refs(self.Role) role_list = role_resolved_value.get("Fn::If") - # both are none values then we need to create a role - if is_intrinsic_no_value(role_list[1]) and is_intrinsic_no_value(role_list[2]): + is_both_intrinsic_no_values = is_intrinsic_no_value(role_list[1]) and is_intrinsic_no_value(role_list[2]) + + # When either one of the condition is a non no value we need to conditionally + # create IAM role, This requires generating a condition that negates the condition check + # passed for IAM role creation and use that for the new role being created + if not is_both_intrinsic_no_values: + execution_role.set_resource_attribute("Condition", f"NOT{role_list[0]}") + conditions[f"NOT{role_list[0]}"] = make_not_conditional(role_list[0]) + + # both are none values, we need to create a role + if is_both_intrinsic_no_values: lambda_function.Role = execution_role.get_runtime_attr("arn") # first value is none so we should create condition ? create : [2] diff --git a/tests/translator/output/aws-cn/function_with_iam_role.json b/tests/translator/output/aws-cn/function_with_iam_role.json index d6f9ed00a3..2ba77f0e45 100644 --- a/tests/translator/output/aws-cn/function_with_iam_role.json +++ b/tests/translator/output/aws-cn/function_with_iam_role.json @@ -1,5 +1,12 @@ { "Conditions": { + "NOTRoleExists": { + "Fn::Not": [ + { + "Condition": "RoleExists" + } + ] + }, "RoleExists": { "Fn::Not": [ { @@ -52,6 +59,7 @@ "Type": "AWS::Lambda::Function" }, "MinimalFunctionRole": { + "Condition": "NOTRoleExists", "Properties": { "AssumeRolePolicyDocument": { "Statement": [ diff --git a/tests/translator/output/aws-us-gov/function_with_iam_role.json b/tests/translator/output/aws-us-gov/function_with_iam_role.json index 0674fa36b2..2364b2bdee 100644 --- a/tests/translator/output/aws-us-gov/function_with_iam_role.json +++ b/tests/translator/output/aws-us-gov/function_with_iam_role.json @@ -1,5 +1,12 @@ { "Conditions": { + "NOTRoleExists": { + "Fn::Not": [ + { + "Condition": "RoleExists" + } + ] + }, "RoleExists": { "Fn::Not": [ { @@ -52,6 +59,7 @@ "Type": "AWS::Lambda::Function" }, "MinimalFunctionRole": { + "Condition": "NOTRoleExists", "Properties": { "AssumeRolePolicyDocument": { "Statement": [ diff --git a/tests/translator/output/function_with_iam_role.json b/tests/translator/output/function_with_iam_role.json index 3abc1c5ccc..4a427ef0f3 100644 --- a/tests/translator/output/function_with_iam_role.json +++ b/tests/translator/output/function_with_iam_role.json @@ -1,5 +1,12 @@ { "Conditions": { + "NOTRoleExists": { + "Fn::Not": [ + { + "Condition": "RoleExists" + } + ] + }, "RoleExists": { "Fn::Not": [ { @@ -52,6 +59,7 @@ "Type": "AWS::Lambda::Function" }, "MinimalFunctionRole": { + "Condition": "NOTRoleExists", "Properties": { "AssumeRolePolicyDocument": { "Statement": [ From 83e1f032e97514534305ee4857862415d28bb673 Mon Sep 17 00:00:00 2001 From: Sid Madipalli Date: Thu, 11 Dec 2025 15:56:51 -0800 Subject: [PATCH 4/7] Adding condition for IAM role with if and else case --- samtranslator/model/sam_resources.py | 13 ++- .../input/function_with_iam_role_create.yaml | 20 +++++ .../aws-cn/function_with_iam_role_create.json | 86 +++++++++++++++++++ .../function_with_iam_role_create.json | 86 +++++++++++++++++++ .../output/function_with_iam_role_create.json | 86 +++++++++++++++++++ 5 files changed, 284 insertions(+), 7 deletions(-) create mode 100644 tests/translator/input/function_with_iam_role_create.yaml create mode 100644 tests/translator/output/aws-cn/function_with_iam_role_create.json create mode 100644 tests/translator/output/aws-us-gov/function_with_iam_role_create.json create mode 100644 tests/translator/output/function_with_iam_role_create.json diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 3cf41d0914..07ccb1ba14 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -435,28 +435,27 @@ def _make_lambda_role( is_both_intrinsic_no_values = is_intrinsic_no_value(role_list[1]) and is_intrinsic_no_value(role_list[2]) - # When either one of the condition is a non no value we need to conditionally - # create IAM role, This requires generating a condition that negates the condition check - # passed for IAM role creation and use that for the new role being created - if not is_both_intrinsic_no_values: - execution_role.set_resource_attribute("Condition", f"NOT{role_list[0]}") - conditions[f"NOT{role_list[0]}"] = make_not_conditional(role_list[0]) - # both are none values, we need to create a role if is_both_intrinsic_no_values: lambda_function.Role = execution_role.get_runtime_attr("arn") # first value is none so we should create condition ? create : [2] + # create a condition for IAM role to only create on if case elif is_intrinsic_no_value(role_list[1]): lambda_function.Role = make_conditional( role_list[0], execution_role.get_runtime_attr("arn"), role_list[2] ) + execution_role.set_resource_attribute("Condition", f"{role_list[0]}") # second value is none so we should create condition ? [1] : create + # create a condition for IAM role to only create on else case + # with top level condition that negates the condition passed elif is_intrinsic_no_value(role_list[2]): lambda_function.Role = make_conditional( role_list[0], role_list[1], execution_role.get_runtime_attr("arn") ) + execution_role.set_resource_attribute("Condition", f"NOT{role_list[0]}") + conditions[f"NOT{role_list[0]}"] = make_not_conditional(role_list[0]) def _construct_event_invoke_config( # noqa: PLR0913 self, diff --git a/tests/translator/input/function_with_iam_role_create.yaml b/tests/translator/input/function_with_iam_role_create.yaml new file mode 100644 index 0000000000..e35677248b --- /dev/null +++ b/tests/translator/input/function_with_iam_role_create.yaml @@ -0,0 +1,20 @@ +Parameters: + iamRoleArn: + Type: String + Description: The ARN of an IAM role to use as this function's execution role. + If a role isn't specified, one is created for you with a logical ID of Role. + +Conditions: + CreateRole: !Not [!Equals ['', !Ref iamRoleArn]] + +Resources: + MinimalFunction: + Type: AWS::Serverless::Function + Properties: + CodeUri: s3://sam-demo-bucket/hello.zip + Handler: hello.handler + Runtime: python3.10 + Role: !If + - CreateRole + - !Ref "AWS::NoValue" + - !Ref "iamRoleArn" diff --git a/tests/translator/output/aws-cn/function_with_iam_role_create.json b/tests/translator/output/aws-cn/function_with_iam_role_create.json new file mode 100644 index 0000000000..3658806450 --- /dev/null +++ b/tests/translator/output/aws-cn/function_with_iam_role_create.json @@ -0,0 +1,86 @@ +{ + "Conditions": { + "CreateRole": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Ref": "iamRoleArn" + } + ] + } + ] + } + }, + "Parameters": { + "iamRoleArn": { + "Description": "The ARN of an IAM role to use as this function's execution role. If a role isn't specified, one is created for you with a logical ID of Role.", + "Type": "String" + } + }, + "Resources": { + "MinimalFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::If": [ + "CreateRole", + { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + }, + { + "Ref": "iamRoleArn" + } + ] + }, + "Runtime": "python3.10", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MinimalFunctionRole": { + "Condition": "CreateRole", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-cn:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/aws-us-gov/function_with_iam_role_create.json b/tests/translator/output/aws-us-gov/function_with_iam_role_create.json new file mode 100644 index 0000000000..66fbd7e044 --- /dev/null +++ b/tests/translator/output/aws-us-gov/function_with_iam_role_create.json @@ -0,0 +1,86 @@ +{ + "Conditions": { + "CreateRole": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Ref": "iamRoleArn" + } + ] + } + ] + } + }, + "Parameters": { + "iamRoleArn": { + "Description": "The ARN of an IAM role to use as this function's execution role. If a role isn't specified, one is created for you with a logical ID of Role.", + "Type": "String" + } + }, + "Resources": { + "MinimalFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::If": [ + "CreateRole", + { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + }, + { + "Ref": "iamRoleArn" + } + ] + }, + "Runtime": "python3.10", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MinimalFunctionRole": { + "Condition": "CreateRole", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws-us-gov:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} diff --git a/tests/translator/output/function_with_iam_role_create.json b/tests/translator/output/function_with_iam_role_create.json new file mode 100644 index 0000000000..cbc366d924 --- /dev/null +++ b/tests/translator/output/function_with_iam_role_create.json @@ -0,0 +1,86 @@ +{ + "Conditions": { + "CreateRole": { + "Fn::Not": [ + { + "Fn::Equals": [ + "", + { + "Ref": "iamRoleArn" + } + ] + } + ] + } + }, + "Parameters": { + "iamRoleArn": { + "Description": "The ARN of an IAM role to use as this function's execution role. If a role isn't specified, one is created for you with a logical ID of Role.", + "Type": "String" + } + }, + "Resources": { + "MinimalFunction": { + "Properties": { + "Code": { + "S3Bucket": "sam-demo-bucket", + "S3Key": "hello.zip" + }, + "Handler": "hello.handler", + "Role": { + "Fn::If": [ + "CreateRole", + { + "Fn::GetAtt": [ + "MinimalFunctionRole", + "Arn" + ] + }, + { + "Ref": "iamRoleArn" + } + ] + }, + "Runtime": "python3.10", + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::Lambda::Function" + }, + "MinimalFunctionRole": { + "Condition": "CreateRole", + "Properties": { + "AssumeRolePolicyDocument": { + "Statement": [ + { + "Action": [ + "sts:AssumeRole" + ], + "Effect": "Allow", + "Principal": { + "Service": [ + "lambda.amazonaws.com" + ] + } + } + ], + "Version": "2012-10-17" + }, + "ManagedPolicyArns": [ + "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole" + ], + "Tags": [ + { + "Key": "lambda:createdBy", + "Value": "SAM" + } + ] + }, + "Type": "AWS::IAM::Role" + } + } +} From 615f202ec2b851292b7b12f58286dced3371de28 Mon Sep 17 00:00:00 2001 From: Sid Madipalli Date: Thu, 11 Dec 2025 16:55:49 -0800 Subject: [PATCH 5/7] Addressing comments --- samtranslator/model/sam_resources.py | 31 ++++++++++--------- tests/model/test_sam_resources.py | 3 +- ...ction_with_conditional_iam_role_else.yaml} | 0 ...unction_with_conditional_iam_role_if.yaml} | 0 ...ction_with_conditional_iam_role_else.json} | 0 ...unction_with_conditional_iam_role_if.json} | 0 ...ction_with_conditional_iam_role_else.json} | 0 ...unction_with_conditional_iam_role_if.json} | 0 ...ction_with_conditional_iam_role_else.json} | 0 ...unction_with_conditional_iam_role_if.json} | 0 10 files changed, 18 insertions(+), 16 deletions(-) rename tests/translator/input/{function_with_iam_role_create.yaml => function_with_conditional_iam_role_else.yaml} (100%) rename tests/translator/input/{function_with_iam_role.yaml => function_with_conditional_iam_role_if.yaml} (100%) rename tests/translator/output/aws-cn/{function_with_iam_role_create.json => function_with_conditional_iam_role_else.json} (100%) rename tests/translator/output/aws-cn/{function_with_iam_role.json => function_with_conditional_iam_role_if.json} (100%) rename tests/translator/output/aws-us-gov/{function_with_iam_role_create.json => function_with_conditional_iam_role_else.json} (100%) rename tests/translator/output/aws-us-gov/{function_with_iam_role.json => function_with_conditional_iam_role_if.json} (100%) rename tests/translator/output/{function_with_iam_role_create.json => function_with_conditional_iam_role_else.json} (100%) rename tests/translator/output/{function_with_iam_role.json => function_with_conditional_iam_role_if.json} (100%) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 07ccb1ba14..080ee154e8 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -426,36 +426,37 @@ def _make_lambda_role( resources.append(execution_role) lambda_function.Role = execution_role.get_runtime_attr("arn") - if is_intrinsic_if(lambda_role): - resources.append(execution_role) + elif is_intrinsic_if(lambda_role): # We need to create and if else condition here - role_resolved_value = intrinsics_resolver.resolve_parameter_refs(self.Role) - role_list = role_resolved_value.get("Fn::If") + role_resolved_value = intrinsics_resolver.resolve_parameter_refs(lambda_role) + role_condition, role_if, role_else = role_resolved_value.get("Fn::If") + + is_both_intrinsic_no_values = is_intrinsic_no_value(role_if) and is_intrinsic_no_value(role_else) - is_both_intrinsic_no_values = is_intrinsic_no_value(role_list[1]) and is_intrinsic_no_value(role_list[2]) + # Create a role only when no value is in either of the conditions + if is_intrinsic_no_value(role_if) or is_intrinsic_no_value(role_else): + resources.append(execution_role) - # both are none values, we need to create a role + # both are none values, we always need to create a role if is_both_intrinsic_no_values: lambda_function.Role = execution_role.get_runtime_attr("arn") # first value is none so we should create condition ? create : [2] # create a condition for IAM role to only create on if case - elif is_intrinsic_no_value(role_list[1]): + elif is_intrinsic_no_value(role_if): lambda_function.Role = make_conditional( - role_list[0], execution_role.get_runtime_attr("arn"), role_list[2] + role_condition, execution_role.get_runtime_attr("arn"), role_else ) - execution_role.set_resource_attribute("Condition", f"{role_list[0]}") + execution_role.set_resource_attribute("Condition", f"{role_condition}") # second value is none so we should create condition ? [1] : create # create a condition for IAM role to only create on else case # with top level condition that negates the condition passed - elif is_intrinsic_no_value(role_list[2]): - lambda_function.Role = make_conditional( - role_list[0], role_list[1], execution_role.get_runtime_attr("arn") - ) - execution_role.set_resource_attribute("Condition", f"NOT{role_list[0]}") - conditions[f"NOT{role_list[0]}"] = make_not_conditional(role_list[0]) + elif is_intrinsic_no_value(role_else): + lambda_function.Role = make_conditional(role_condition, role_if, execution_role.get_runtime_attr("arn")) + execution_role.set_resource_attribute("Condition", f"NOT{role_condition}") + conditions[f"NOT{role_condition}"] = make_not_conditional(role_condition) def _construct_event_invoke_config( # noqa: PLR0913 self, diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index edc54004ae..0f0ab5fe95 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -793,7 +793,8 @@ def test_role_fn_if_no_aws_no_value_keeps_original(self): generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function") - self.assertEqual(len(generated_roles), 1) + # Should not create a role if a role is passed in for both cases + self.assertEqual(len(generated_roles), 0) self.assertEqual(lambda_function.Role, role_conditional) def test_role_fn_if_both_no_value_creates_execution_role(self): diff --git a/tests/translator/input/function_with_iam_role_create.yaml b/tests/translator/input/function_with_conditional_iam_role_else.yaml similarity index 100% rename from tests/translator/input/function_with_iam_role_create.yaml rename to tests/translator/input/function_with_conditional_iam_role_else.yaml diff --git a/tests/translator/input/function_with_iam_role.yaml b/tests/translator/input/function_with_conditional_iam_role_if.yaml similarity index 100% rename from tests/translator/input/function_with_iam_role.yaml rename to tests/translator/input/function_with_conditional_iam_role_if.yaml diff --git a/tests/translator/output/aws-cn/function_with_iam_role_create.json b/tests/translator/output/aws-cn/function_with_conditional_iam_role_else.json similarity index 100% rename from tests/translator/output/aws-cn/function_with_iam_role_create.json rename to tests/translator/output/aws-cn/function_with_conditional_iam_role_else.json diff --git a/tests/translator/output/aws-cn/function_with_iam_role.json b/tests/translator/output/aws-cn/function_with_conditional_iam_role_if.json similarity index 100% rename from tests/translator/output/aws-cn/function_with_iam_role.json rename to tests/translator/output/aws-cn/function_with_conditional_iam_role_if.json diff --git a/tests/translator/output/aws-us-gov/function_with_iam_role_create.json b/tests/translator/output/aws-us-gov/function_with_conditional_iam_role_else.json similarity index 100% rename from tests/translator/output/aws-us-gov/function_with_iam_role_create.json rename to tests/translator/output/aws-us-gov/function_with_conditional_iam_role_else.json diff --git a/tests/translator/output/aws-us-gov/function_with_iam_role.json b/tests/translator/output/aws-us-gov/function_with_conditional_iam_role_if.json similarity index 100% rename from tests/translator/output/aws-us-gov/function_with_iam_role.json rename to tests/translator/output/aws-us-gov/function_with_conditional_iam_role_if.json diff --git a/tests/translator/output/function_with_iam_role_create.json b/tests/translator/output/function_with_conditional_iam_role_else.json similarity index 100% rename from tests/translator/output/function_with_iam_role_create.json rename to tests/translator/output/function_with_conditional_iam_role_else.json diff --git a/tests/translator/output/function_with_iam_role.json b/tests/translator/output/function_with_conditional_iam_role_if.json similarity index 100% rename from tests/translator/output/function_with_iam_role.json rename to tests/translator/output/function_with_conditional_iam_role_if.json From 026679c015854ee259b2ec5c7730a16c095ea36c Mon Sep 17 00:00:00 2001 From: Sid Madipalli Date: Fri, 12 Dec 2025 15:47:40 -0800 Subject: [PATCH 6/7] Reformatted to make value changes in to_cloudformation --- samtranslator/model/sam_resources.py | 95 +++++++++++++++++----------- tests/model/test_sam_resources.py | 16 ++--- 2 files changed, 62 insertions(+), 49 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 080ee154e8..41e515d3ab 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -394,7 +394,24 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P intrinsics_resolver, get_managed_policy_map, ) - self._make_lambda_role(lambda_function, intrinsics_resolver, execution_role, resources, conditions) + + if lambda_function.Role is None: + lambda_function.Role = execution_role.get_runtime_attr("arn") + resources.append(execution_role) + elif is_intrinsic_if(lambda_function.Role): + role_changes = self._make_lambda_role(lambda_function, intrinsics_resolver, execution_role) + + if role_changes["should_append_role"]: + resources.append(execution_role) + + if role_changes["lambda_role_value"] is not None: + lambda_function.Role = role_changes["lambda_role_value"] + + if role_changes["execution_role_condition"] is not None: + execution_role.set_resource_attribute("Condition", role_changes["execution_role_condition"]) + + if role_changes["new_condition"] is not None: + conditions.update(role_changes["new_condition"]) try: resources += self._generate_event_resources( @@ -417,46 +434,48 @@ def _make_lambda_role( lambda_function: LambdaFunction, intrinsics_resolver: IntrinsicsResolver, execution_role: IAMRole, - resources: List[Any], - conditions: Dict[str, Any], - ) -> None: - lambda_role = lambda_function.Role - - if lambda_role is None: - resources.append(execution_role) - lambda_function.Role = execution_role.get_runtime_attr("arn") - - elif is_intrinsic_if(lambda_role): - - # We need to create and if else condition here - role_resolved_value = intrinsics_resolver.resolve_parameter_refs(lambda_role) - role_condition, role_if, role_else = role_resolved_value.get("Fn::If") - - is_both_intrinsic_no_values = is_intrinsic_no_value(role_if) and is_intrinsic_no_value(role_else) + ) -> Dict[str, Any]: + """ + Analyzes lambda role requirements and returns the changes needed. - # Create a role only when no value is in either of the conditions - if is_intrinsic_no_value(role_if) or is_intrinsic_no_value(role_else): - resources.append(execution_role) + Returns: + Dict containing: + - 'should_append_role': bool - whether to append execution_role to resources + - 'lambda_role_value': Any - value to set for lambda_function.Role + - 'execution_role_condition': str|None - condition to set on execution_role + - 'new_condition': Dict|None - new condition to add to conditions dict + """ + lambda_role = lambda_function.Role + execution_role_arn = execution_role.get_runtime_attr("arn") - # both are none values, we always need to create a role - if is_both_intrinsic_no_values: - lambda_function.Role = execution_role.get_runtime_attr("arn") + result = { + "should_append_role": False, + "lambda_role_value": None, + "execution_role_condition": None, + "new_condition": None, + } - # first value is none so we should create condition ? create : [2] - # create a condition for IAM role to only create on if case - elif is_intrinsic_no_value(role_if): - lambda_function.Role = make_conditional( - role_condition, execution_role.get_runtime_attr("arn"), role_else - ) - execution_role.set_resource_attribute("Condition", f"{role_condition}") - - # second value is none so we should create condition ? [1] : create - # create a condition for IAM role to only create on else case - # with top level condition that negates the condition passed - elif is_intrinsic_no_value(role_else): - lambda_function.Role = make_conditional(role_condition, role_if, execution_role.get_runtime_attr("arn")) - execution_role.set_resource_attribute("Condition", f"NOT{role_condition}") - conditions[f"NOT{role_condition}"] = make_not_conditional(role_condition) + # We need to create and if else condition here + role_resolved_value = intrinsics_resolver.resolve_parameter_refs(lambda_role) + role_condition, role_if, role_else = role_resolved_value.get("Fn::If") + + # first value is none so we should create condition ? create : [2] + # create a condition for IAM role to only create on if case + if is_intrinsic_no_value(role_if): + result["lambda_role_value"] = make_conditional(role_condition, execution_role_arn, role_else) + result["execution_role_condition"] = f"{role_condition}" + result["should_append_role"] = True + + # second value is none so we should create condition ? [1] : create + # create a condition for IAM role to only create on else case + # with top level condition that negates the condition passed + elif is_intrinsic_no_value(role_else): + result["lambda_role_value"] = make_conditional(role_condition, role_if, execution_role_arn) + result["execution_role_condition"] = f"NOT{role_condition}" + result["new_condition"] = {f"NOT{role_condition}": make_not_conditional(role_condition)} + result["should_append_role"] = True + + return result def _construct_event_invoke_config( # noqa: PLR0913 self, diff --git a/tests/model/test_sam_resources.py b/tests/model/test_sam_resources.py index 0f0ab5fe95..cbc8412dab 100644 --- a/tests/model/test_sam_resources.py +++ b/tests/model/test_sam_resources.py @@ -751,14 +751,12 @@ def setUp(self): self.function.CodeUri = "s3://foobar/foo.zip" self.function.Runtime = "foo" self.function.Handler = "bar" - self.template = {"Conditions": {}} - self.kwargs = { "intrinsics_resolver": IntrinsicsResolver({}), "event_resources": [], "managed_policy_map": {}, "resource_resolver": ResourceResolver({}), - "conditions": self.template.get("Conditions", {}), + "conditions": {"Conditions": {}}, } def test_role_none_creates_execution_role(self): @@ -785,9 +783,8 @@ def test_role_fn_if_no_aws_no_value_keeps_original(self): } self.function.Role = role_conditional - template = {"Conditions": {"Condition": True}} kwargs = dict(self.kwargs) - kwargs["conditions"] = template.get("Conditions", {}) + kwargs["conditions"] = {"Condition": True} cfn_resources = self.function.to_cloudformation(**self.kwargs) generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] @@ -801,9 +798,8 @@ def test_role_fn_if_both_no_value_creates_execution_role(self): role_conditional = {"Fn::If": ["Condition", {"Ref": "AWS::NoValue"}, {"Ref": "AWS::NoValue"}]} self.function.Role = role_conditional - template = {"Conditions": {"Condition": True}} kwargs = dict(self.kwargs) - kwargs["conditions"] = template.get("Conditions", {}) + kwargs["conditions"] = {"Condition": True} cfn_resources = self.function.to_cloudformation(**self.kwargs) generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] @@ -814,9 +810,8 @@ def test_role_fn_if_first_no_value_creates_conditional_role(self): role_conditional = {"Fn::If": ["Condition", {"Ref": "AWS::NoValue"}, {"Ref": "iamRoleArn"}]} self.function.Role = role_conditional - template = {"Conditions": {"Condition": True}} kwargs = dict(self.kwargs) - kwargs["conditions"] = template.get("Conditions", {}) + kwargs["conditions"] = {"Condition": True} cfn_resources = self.function.to_cloudformation(**self.kwargs) generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] @@ -831,9 +826,8 @@ def test_role_fn_if_second_no_value_creates_conditional_role(self): role_conditional = {"Fn::If": ["Condition", {"Ref": "iamRoleArn"}, {"Ref": "AWS::NoValue"}]} self.function.Role = role_conditional - template = {"Conditions": {"Condition": True}} kwargs = dict(self.kwargs) - kwargs["conditions"] = template.get("Conditions", {}) + kwargs["conditions"] = {"Condition": True} cfn_resources = self.function.to_cloudformation(**self.kwargs) generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)] From 31cec029dd0d16ce1fb96c4ad512e0ed2673a3ef Mon Sep 17 00:00:00 2001 From: Sid Madipalli Date: Fri, 12 Dec 2025 16:44:01 -0800 Subject: [PATCH 7/7] Ignore PLR0912 too many branches --- samtranslator/model/sam_resources.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/samtranslator/model/sam_resources.py b/samtranslator/model/sam_resources.py index 41e515d3ab..23eac72748 100644 --- a/samtranslator/model/sam_resources.py +++ b/samtranslator/model/sam_resources.py @@ -293,7 +293,7 @@ def resources_to_link(self, resources: Dict[str, Any]) -> Dict[str, Any]: raise InvalidResourceException(self.logical_id, e.message) from e @cw_timer - def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: PLR0915 + def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: PLR0915, PLR0912 """Returns the Lambda function, role, and event resources to which this SAM Function corresponds. :param dict kwargs: already-converted resources that may need to be modified when converting this \ @@ -448,7 +448,7 @@ def _make_lambda_role( lambda_role = lambda_function.Role execution_role_arn = execution_role.get_runtime_attr("arn") - result = { + result: Dict[str, Any] = { "should_append_role": False, "lambda_role_value": None, "execution_role_condition": None, @@ -459,9 +459,13 @@ def _make_lambda_role( role_resolved_value = intrinsics_resolver.resolve_parameter_refs(lambda_role) role_condition, role_if, role_else = role_resolved_value.get("Fn::If") + if is_intrinsic_no_value(role_if) and is_intrinsic_no_value(role_else): + result["lambda_role_value"] = execution_role_arn + result["should_append_role"] = True + # first value is none so we should create condition ? create : [2] # create a condition for IAM role to only create on if case - if is_intrinsic_no_value(role_if): + elif is_intrinsic_no_value(role_if): result["lambda_role_value"] = make_conditional(role_condition, execution_role_arn, role_else) result["execution_role_condition"] = f"{role_condition}" result["should_append_role"] = True