Skip to content

Commit ef9cd22

Browse files
author
Sid Madipalli
committed
Adding AWS::NoValue support to Serverless Function IAM role
1 parent e5afb37 commit ef9cd22

File tree

2 files changed

+155
-10
lines changed

2 files changed

+155
-10
lines changed

samtranslator/model/sam_resources.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -347,16 +347,13 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P
347347
managed_policy_map = kwargs.get("managed_policy_map", {})
348348
get_managed_policy_map = kwargs.get("get_managed_policy_map")
349349

350-
execution_role = None
351-
if lambda_function.Role is None:
352-
execution_role = self._construct_role(
353-
managed_policy_map,
354-
event_invoke_policies,
355-
intrinsics_resolver,
356-
get_managed_policy_map,
357-
)
358-
lambda_function.Role = execution_role.get_runtime_attr("arn")
359-
resources.append(execution_role)
350+
execution_role = self._construct_role(
351+
managed_policy_map,
352+
event_invoke_policies,
353+
intrinsics_resolver,
354+
get_managed_policy_map,
355+
)
356+
self._make_lambda_role(lambda_function, intrinsics_resolver, execution_role, resources)
360357

361358
try:
362359
resources += self._generate_event_resources(
@@ -374,6 +371,42 @@ def to_cloudformation(self, **kwargs): # type: ignore[no-untyped-def] # noqa: P
374371

375372
return resources
376373

374+
def _make_lambda_role(
375+
self,
376+
lambda_function: LambdaFunction,
377+
intrinsics_resolver: IntrinsicsResolver,
378+
execution_role: IAMRole,
379+
resources: List[Any],
380+
) -> None:
381+
lambda_role = lambda_function.Role
382+
383+
if lambda_role is None:
384+
resources.append(execution_role)
385+
lambda_function.Role = execution_role.get_runtime_attr("arn")
386+
387+
if is_intrinsic_if(lambda_role):
388+
resources.append(execution_role)
389+
390+
# We need to create and if else condition here
391+
role_resolved_value = intrinsics_resolver.resolve_parameter_refs(self.Role)
392+
role_list = role_resolved_value.get("Fn::If")
393+
394+
# both are none values then we need to create a role
395+
if is_intrinsic_no_value(role_list[1]) and is_intrinsic_no_value(role_list[2]):
396+
lambda_function.Role = execution_role.get_runtime_attr("arn")
397+
398+
# first value is none so we should create condition ? create : [2]
399+
elif is_intrinsic_no_value(role_list[1]):
400+
lambda_function.Role = make_conditional(
401+
role_list[0], execution_role.get_runtime_attr("arn"), role_list[2]
402+
)
403+
404+
# second value is none so we should create condition ? [1] : create
405+
elif is_intrinsic_no_value(role_list[2]):
406+
lambda_function.Role = make_conditional(
407+
role_list[0], role_list[1], execution_role.get_runtime_attr("arn")
408+
)
409+
377410
def _construct_event_invoke_config( # noqa: PLR0913
378411
self,
379412
function_name: str,

tests/model/test_sam_resources.py

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,3 +738,115 @@ def test_function_datasource_set_with_none():
738738
api = SamGraphQLApi("MyApi")
739739
none_datasource = api._construct_none_datasource("foo")
740740
assert none_datasource
741+
742+
743+
class TestSamFunctionRoleResolver(TestCase):
744+
"""
745+
Tests for resolving IAM role property values in SamFunction
746+
"""
747+
748+
def setUp(self):
749+
self.function = SamFunction("foo")
750+
self.function.CodeUri = "s3://foobar/foo.zip"
751+
self.function.Runtime = "foo"
752+
self.function.Handler = "bar"
753+
self.template = {"Conditions": {}}
754+
755+
self.kwargs = {
756+
"intrinsics_resolver": IntrinsicsResolver({}),
757+
"event_resources": [],
758+
"managed_policy_map": {},
759+
"resource_resolver": ResourceResolver({}),
760+
"conditions": self.template.get("Conditions", {}),
761+
}
762+
763+
def test_role_none_creates_execution_role(self):
764+
self.function.Role = None
765+
cfn_resources = self.function.to_cloudformation(**self.kwargs)
766+
generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)]
767+
768+
self.assertEqual(len(generated_roles), 1) # Should create execution role
769+
770+
def test_role_explicit_arn_no_execution_role(self):
771+
test_role = "arn:aws:iam::123456789012:role/existing-role"
772+
self.function.Role = test_role
773+
774+
cfn_resources = self.function.to_cloudformation(**self.kwargs)
775+
generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)]
776+
lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function")
777+
778+
self.assertEqual(len(generated_roles), 0) # Should not create execution role
779+
self.assertEqual(lambda_function.Role, test_role)
780+
781+
def test_role_fn_if_no_aws_no_value_keeps_original(self):
782+
role_conditional = {
783+
"Fn::If": ["Condition", "arn:aws:iam::123456789012:role/existing-role", {"Ref": "iamRoleArn"}]
784+
}
785+
self.function.Role = role_conditional
786+
787+
template = {"Conditions": {"Condition": True}}
788+
kwargs = dict(self.kwargs)
789+
kwargs["conditions"] = template.get("Conditions", {})
790+
791+
cfn_resources = self.function.to_cloudformation(**self.kwargs)
792+
generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)]
793+
lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function")
794+
795+
self.assertEqual(len(generated_roles), 1)
796+
self.assertEqual(lambda_function.Role, role_conditional)
797+
798+
def test_role_fn_if_both_no_value_creates_execution_role(self):
799+
role_conditional = {"Fn::If": ["Condition", {"Ref": "AWS::NoValue"}, {"Ref": "AWS::NoValue"}]}
800+
self.function.Role = role_conditional
801+
802+
template = {"Conditions": {"Condition": True}}
803+
kwargs = dict(self.kwargs)
804+
kwargs["conditions"] = template.get("Conditions", {})
805+
806+
cfn_resources = self.function.to_cloudformation(**self.kwargs)
807+
generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)]
808+
809+
self.assertEqual(len(generated_roles), 1)
810+
811+
def test_role_fn_if_first_no_value_creates_conditional_role(self):
812+
role_conditional = {"Fn::If": ["Condition", {"Ref": "AWS::NoValue"}, {"Ref": "iamRoleArn"}]}
813+
self.function.Role = role_conditional
814+
815+
template = {"Conditions": {"Condition": True}}
816+
kwargs = dict(self.kwargs)
817+
kwargs["conditions"] = template.get("Conditions", {})
818+
819+
cfn_resources = self.function.to_cloudformation(**self.kwargs)
820+
generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)]
821+
lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function")
822+
823+
self.assertEqual(len(generated_roles), 1)
824+
self.assertEqual(
825+
lambda_function.Role, {"Fn::If": ["Condition", {"Fn::GetAtt": ["fooRole", "Arn"]}, {"Ref": "iamRoleArn"}]}
826+
)
827+
828+
def test_role_fn_if_second_no_value_creates_conditional_role(self):
829+
role_conditional = {"Fn::If": ["Condition", {"Ref": "iamRoleArn"}, {"Ref": "AWS::NoValue"}]}
830+
self.function.Role = role_conditional
831+
832+
template = {"Conditions": {"Condition": True}}
833+
kwargs = dict(self.kwargs)
834+
kwargs["conditions"] = template.get("Conditions", {})
835+
836+
cfn_resources = self.function.to_cloudformation(**self.kwargs)
837+
generated_roles = [x for x in cfn_resources if isinstance(x, IAMRole)]
838+
lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function")
839+
840+
self.assertEqual(len(generated_roles), 1)
841+
self.assertEqual(
842+
lambda_function.Role, {"Fn::If": ["Condition", {"Ref": "iamRoleArn"}, {"Fn::GetAtt": ["fooRole", "Arn"]}]}
843+
)
844+
845+
def test_role_get_att_no_execution_role(self):
846+
role_get_att = {"Fn::GetAtt": ["MyCustomRole", "Arn"]}
847+
self.function.Role = role_get_att
848+
849+
cfn_resources = self.function.to_cloudformation(**self.kwargs)
850+
lambda_function = next(r for r in cfn_resources if r.resource_type == "AWS::Lambda::Function")
851+
852+
self.assertEqual(lambda_function.Role, role_get_att)

0 commit comments

Comments
 (0)