Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow user to provide role for schedule #3363

Merged
merged 8 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class ScheduleEventProperties(BaseModel):
Schedule: Optional[PassThroughProp] = scheduleeventproperties("Schedule")
State: Optional[PassThroughProp] = scheduleeventproperties("State")
Target: Optional[ScheduleTarget] = scheduleeventproperties("Target")
Role: Optional[PassThroughProp] # TODO: add doc
RoleArn: Optional[PassThroughProp] # TODO: add doc


class ScheduleEvent(BaseModel):
Expand Down
54 changes: 38 additions & 16 deletions samtranslator/model/stepfunctions/events.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from abc import ABCMeta
from typing import Any, Dict, Optional, cast
from typing import Any, Dict, List, Optional, cast

from samtranslator.metrics.method_decorator import cw_timer
from samtranslator.model import Property, PropertyType, Resource, ResourceMacro
Expand All @@ -10,6 +10,7 @@
from samtranslator.model.exceptions import InvalidEventException
from samtranslator.model.iam import IAMRole, IAMRolePolicies
from samtranslator.model.intrinsics import fnSub
from samtranslator.model.stepfunctions.resources import StepFunctionsStateMachine
from samtranslator.model.types import IS_BOOL, IS_DICT, IS_STR, PassThrough
from samtranslator.swagger.swagger import SwaggerEditor
from samtranslator.translator import logical_id_generator
Expand Down Expand Up @@ -50,7 +51,13 @@ def _generate_logical_id(self, prefix, suffix, resource_type): # type: ignore[n
generator = logical_id_generator.LogicalIdGenerator(prefix + resource_type, suffix)
return generator.gen()

def _construct_role(self, resource, permissions_boundary=None, prefix=None, suffix=""): # type: ignore[no-untyped-def]
def _construct_role(
self,
resource: StepFunctionsStateMachine,
permissions_boundary: Optional[str],
prefix: Optional[str],
suffix: str = "",
sidhujus marked this conversation as resolved.
Show resolved Hide resolved
) -> IAMRole:
"""Constructs the IAM Role resource allowing the event service to invoke
the StartExecution API of the state machine resource it is associated with.

Expand Down Expand Up @@ -93,7 +100,7 @@ class Schedule(EventSource):
"DeadLetterConfig": PropertyType(False, IS_DICT),
"RetryPolicy": PropertyType(False, IS_DICT),
"Target": Property(False, IS_DICT),
"Role": Property(False, IS_STR),
"RoleArn": Property(False, IS_STR),
}

Schedule: PassThrough
Expand All @@ -105,7 +112,7 @@ class Schedule(EventSource):
DeadLetterConfig: Optional[Dict[str, Any]]
RetryPolicy: Optional[PassThrough]
Target: Optional[PassThrough]
Role: Optional[PassThrough]
RoleArn: Optional[PassThrough]

@cw_timer(prefix=SFN_EVETSOURCE_METRIC_PREFIX)
def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
Expand All @@ -115,7 +122,7 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
:returns: a list of vanilla CloudFormation Resources, to which this Schedule event expands
:rtype: list
"""
resources = []
resources: List[Any] = []

permissions_boundary = kwargs.get("permissions_boundary")

Expand All @@ -138,8 +145,8 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
events_rule.Description = self.Description

role = None
if self.Role is None:
role = self._construct_role(resource, permissions_boundary) # type: ignore[no-untyped-call]
if self.RoleArn is None:
role = self._construct_role(resource, permissions_boundary, prefix=None)
resources.append(role)

source_arn = events_rule.get_runtime_attr("arn")
Expand All @@ -150,13 +157,25 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
self, source_arn, passthrough_resource_attributes
)
resources.extend(dlq_resources)
events_rule.Targets = [self._construct_target(resource, role, self.Role, dlq_queue_arn)] # type: ignore[no-untyped-call]
events_rule.Targets = [self._construct_target(resource, role, self.RoleArn, dlq_queue_arn)]

return resources

def _construct_target(self, resource, role, provided_role=None, dead_letter_queue_arn=None): # type: ignore[no-untyped-def]
def _construct_target(
self,
resource: StepFunctionsStateMachine,
role: Optional[IAMRole],
provided_role: Optional[str],
dead_letter_queue_arn: Optional[str],
) -> Dict[str, Any]:
"""Constructs the Target property for the EventBridge Rule.

sidhujus marked this conversation as resolved.
Show resolved Hide resolved
parameters:
resource: The State Machine resource associated with the event
role: An IAMRole resource
provided_role: User provided role ARN to be used instead of a newly generated IAMRole resource
dead_letter_queue_arn: ARN of the dead letter queue

:returns: the Target property
:rtype: dict
"""
Expand All @@ -167,13 +186,12 @@ def _construct_target(self, resource, role, provided_role=None, dead_letter_queu
)

if provided_role:
role_arn = fnSub("arn:aws:iam::${AWS::AccountId}:role/${Role}", {"Role": provided_role})
target = {
"Arn": resource.get_runtime_attr("arn"),
"Id": target_id,
"RoleArn": role_arn,
"RoleArn": provided_role,
}
else:
elif role:
sidhujus marked this conversation as resolved.
Show resolved Hide resolved
target = {
"Arn": resource.get_runtime_attr("arn"),
"Id": target_id,
Expand Down Expand Up @@ -229,7 +247,7 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
:returns: a list of vanilla CloudFormation Resources, to which this CloudWatch Events/EventBridge event expands
:rtype: list
"""
resources = []
resources: List[Any] = []

permissions_boundary = kwargs.get("permissions_boundary")

Expand All @@ -244,7 +262,11 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]

resources.append(events_rule)

role = self._construct_role(resource, permissions_boundary) # type: ignore[no-untyped-call]
role = self._construct_role(
resource,
permissions_boundary,
prefix=None,
)
resources.append(role)

source_arn = events_rule.get_runtime_attr("arn")
Expand Down Expand Up @@ -344,7 +366,7 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
:returns: a list of vanilla CloudFormation Resources, to which this Api event expands
:rtype: list
"""
resources = []
resources: List[Any] = []

intrinsics_resolver = kwargs.get("intrinsics_resolver")
permissions_boundary = kwargs.get("permissions_boundary")
Expand All @@ -353,7 +375,7 @@ def to_cloudformation(self, resource, **kwargs): # type: ignore[no-untyped-def]
# Convert to lower case so that user can specify either GET or get
self.Method = self.Method.lower()

role = self._construct_role(resource, permissions_boundary) # type: ignore[no-untyped-call]
role = self._construct_role(resource, permissions_boundary, prefix=None)
resources.append(role)

explicit_api = kwargs["explicit_api"]
Expand Down
2 changes: 1 addition & 1 deletion samtranslator/schema/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -251046,7 +251046,7 @@
"markdownDescription": "A `RetryPolicy` object that includes information about the retry policy settings\\. For more information, see [Event retry policy and using dead\\-letter queues](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html) in the *Amazon EventBridge User Guide*\\. \n*Type*: [RetryPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-target.html#cfn-events-rule-target-retrypolicy) \n*Required*: No \n*AWS CloudFormation compatibility*: This property is passed directly to the [`RetryPolicy`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-target.html#cfn-events-rule-target-retrypolicy) property of the `AWS::Events::Rule` `Target` data type\\.",
"title": "RetryPolicy"
},
"Role": {
"RoleArn": {
"$ref": "#/definitions/PassThroughProp"
},
"Schedule": {
Expand Down
2 changes: 1 addition & 1 deletion schema_source/sam.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3013,7 +3013,7 @@
"markdownDescription": "A `RetryPolicy` object that includes information about the retry policy settings\\. For more information, see [Event retry policy and using dead\\-letter queues](https://docs.aws.amazon.com/eventbridge/latest/userguide/rule-dlq.html) in the *Amazon EventBridge User Guide*\\. \n*Type*: [RetryPolicy](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-target.html#cfn-events-rule-target-retrypolicy) \n*Required*: No \n*AWS CloudFormation compatibility*: This property is passed directly to the [`RetryPolicy`](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-events-rule-target.html#cfn-events-rule-target-retrypolicy) property of the `AWS::Events::Rule` `Target` data type\\.",
"title": "RetryPolicy"
},
"Role": {
"RoleArn": {
"$ref": "#/definitions/PassThroughProp"
},
"Schedule": {
Expand Down
9 changes: 3 additions & 6 deletions tests/model/stepfunctions/test_schedule_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

from parameterized import parameterized
from samtranslator.model.exceptions import InvalidEventException
from samtranslator.model.intrinsics import fnSub
from samtranslator.model.stepfunctions.events import Schedule


Expand Down Expand Up @@ -155,13 +154,11 @@ def test_to_cloudformation_with_dlq_generated_with_intrinsic_function_custom_log
self.schedule_event_source.to_cloudformation(resource=self.state_machine)

def test_to_cloudformation_with_role_arn_provided(self):
role = "ScheduleRole"
self.schedule_event_source.Role = role
role = "not a real arn"
self.schedule_event_source.RoleArn = role
resources = self.schedule_event_source.to_cloudformation(resource=self.state_machine)
event_rule = resources[0]
self.assertEqual(
event_rule.Targets[0]["RoleArn"], fnSub("arn:aws:iam::${AWS::AccountId}:role/${Role}", {"Role": role})
)
self.assertEqual(event_rule.Targets[0]["RoleArn"], "not a real arn")

@parameterized.expand(
[
Expand Down
23 changes: 3 additions & 20 deletions tests/translator/input/state_machine_with_schedule_role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,14 @@ Resources:

MyStateMachine:
Type: AWS::Serverless::StateMachine
Properties:
Definition:
Comment: A Hello World example of the Amazon States Language using Pass states
StartAt: Hello
States:
Hello:
Type: Pass
Result: Hello
Next: World
World:
Type: Pass
Result: World
End: true
Policies:
- Version: '2012-10-17'
Statement:
- Effect: Deny
Action: '*'
Resource: '*'

Properties:
DefinitionUri: s3://sam-demo-bucket/my_state_machine.asl.json
Events:
sidhujus marked this conversation as resolved.
Show resolved Hide resolved
CWSchedule:
Type: Schedule
Properties:
Schedule: rate(1 minute)
Description: test schedule
Enabled: false
Role: with-role-MyStateMachineCWScheduleRole-RI4SGRI1ORL9
RoleArn: FakeArn
sidhujus marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,9 @@
"Resources": {
"MyStateMachine": {
"Properties": {
"DefinitionString": {
"Fn::Join": [
"\n",
[
"{",
" \"Comment\": \"A Hello World example of the Amazon States Language using Pass states\",",
" \"StartAt\": \"Hello\",",
" \"States\": {",
" \"Hello\": {",
" \"Next\": \"World\",",
" \"Result\": \"Hello\",",
" \"Type\": \"Pass\"",
" },",
" \"World\": {",
" \"End\": true,",
" \"Result\": \"World\",",
" \"Type\": \"Pass\"",
" }",
" }",
"}"
]
]
"DefinitionS3Location": {
"Bucket": "sam-demo-bucket",
"Key": "my_state_machine.asl.json"
},
"RoleArn": {
"Fn::GetAtt": [
Expand Down Expand Up @@ -51,14 +32,7 @@
"Ref": "MyStateMachine"
},
"Id": "MyStateMachineCWScheduleStepFunctionsTarget",
"RoleArn": {
"Fn::Sub": [
"arn:aws:iam::${AWS::AccountId}:role/${Role}",
{
"Role": "with-role-MyStateMachineCWScheduleRole-RI4SGRI1ORL9"
}
]
}
"RoleArn": "FakeArn"
}
]
},
Expand All @@ -83,21 +57,6 @@
"Version": "2012-10-17"
},
"ManagedPolicyArns": [],
"Policies": [
{
"PolicyDocument": {
"Statement": [
{
"Action": "*",
"Effect": "Deny",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"PolicyName": "MyStateMachineRolePolicy0"
}
],
"Tags": [
{
"Key": "stateMachine:createdBy",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,9 @@
"Resources": {
"MyStateMachine": {
"Properties": {
"DefinitionString": {
"Fn::Join": [
"\n",
[
"{",
" \"Comment\": \"A Hello World example of the Amazon States Language using Pass states\",",
" \"StartAt\": \"Hello\",",
" \"States\": {",
" \"Hello\": {",
" \"Next\": \"World\",",
" \"Result\": \"Hello\",",
" \"Type\": \"Pass\"",
" },",
" \"World\": {",
" \"End\": true,",
" \"Result\": \"World\",",
" \"Type\": \"Pass\"",
" }",
" }",
"}"
]
]
"DefinitionS3Location": {
"Bucket": "sam-demo-bucket",
"Key": "my_state_machine.asl.json"
},
"RoleArn": {
"Fn::GetAtt": [
Expand Down Expand Up @@ -51,14 +32,7 @@
"Ref": "MyStateMachine"
},
"Id": "MyStateMachineCWScheduleStepFunctionsTarget",
"RoleArn": {
"Fn::Sub": [
"arn:aws:iam::${AWS::AccountId}:role/${Role}",
{
"Role": "with-role-MyStateMachineCWScheduleRole-RI4SGRI1ORL9"
}
]
}
"RoleArn": "FakeArn"
}
]
},
Expand All @@ -83,21 +57,6 @@
"Version": "2012-10-17"
},
"ManagedPolicyArns": [],
"Policies": [
{
"PolicyDocument": {
"Statement": [
{
"Action": "*",
"Effect": "Deny",
"Resource": "*"
}
],
"Version": "2012-10-17"
},
"PolicyName": "MyStateMachineRolePolicy0"
}
],
"Tags": [
{
"Key": "stateMachine:createdBy",
Expand Down
Loading