Repository: serverless/serverless (JavaScript) — Bug Fix

Issue:
AWS::Scheduler::Schedule events do not work with custom IAM Roles
### Are you certain it's a bug?

- [X] Yes, it looks like a bug

### Is the issue caused by a plugin?

- [X] It is not a plugin issue

### Are you using the latest v3 release?

- [X] Yes, I'm using the latest v3 release

### Is there an existing issue for this?

- [X] I have searched existing issues, it hasn't been reported yet

### Issue description

When using the [AWS::Scheduler](https://www.serverless.com/framework/docs/providers/aws/events/schedule#use-awsschedulerschedule-instead-of-awseventrule) events, and the function has a custom IAM Role, the Cloudformation template is invalid.

### Service configuration (serverless.yml) content

```yaml
service: brians-sls
frameworkVersion: '3'

provider:
  name: aws
  runtime: nodejs18.x

functions:
  rateHandler:
    role: MyRole
    handler: index.run
    events:
      - schedule:
          method: scheduler
          rate: rate(1 minute)
          timezone: America/New_York

resources:
  Resources:
    MyRole:
      Type: AWS::IAM::Role
      Properties:
        AssumeRolePolicyDocument:
          Version: '2012-10-17'
          Statement:
            - Effect: Allow
              Principal:
                Service: lambda.amazonaws.com
              Action:
                - sts:AssumeRole
        ManagedPolicyArns:
          - 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
```


### Command name and used flags

sls deploy

### Command output

```shell
✖ Stack brians-sls-dev failed to deploy (2s)
Environment: darwin, node 18.16.0, framework 3.32.2, plugin 6.2.3, SDK 4.3.2
Credentials: Local, environment variables
Docs:        docs.serverless.com
Support:     forum.serverless.com
Bugs:        github.com/serverless/serverless/issues

Error:
The CloudFormation template is invalid: Template error: instance of Fn::GetAtt references undefined resource IamRoleLambdaExecution
```


### Environment information

```shell
Framework Core: 3.32.2
Plugin: 6.2.3
SDK: 4.3.2
```



Discussion:
It appears it doesn't handle well custom role defined at function level (cc @tie624)

PR with a fix is welcome!
I can work on a fix.
There isn't an easy way to fix this. We had decided to use the default lambda execution role as the role to be used by the scheduler, but didn't take into account the event that the default role is not created because all the lambdas had a custom role defined. 

I see 3 possible ways to fix this:

1. Create the default execution role if there are any scheduler schedules, even if there are no lambdas that will be using it. 
2. Set the role for the scheduler to be the same as the role (possibly custom) used by the lambda. -- The issue here is that the role for the lambda has not yet been compiled yet when compiling the role for the scheduler.
3. Create a new role just for the scheduler.

@medikoo thoughts?



@tie624, wouldn't the proper fix be to follow the same path as we do when we detect that no default role is created (we then simply list an info reminder that permissions need to be ensured on the execution role)?

I think the fix should also be to fall back to this notice if we detect the custom role on lambda on which the event is assigned.
@medikoo sounds like you are suggesting option 2. I'll get a PR ready for it. 
We are currently outputting a message using `log.info` if the default execution role is not being created. 

Resolving patch:
diff --git a/lib/plugins/aws/package/compile/events/schedule.js b/lib/plugins/aws/package/compile/events/schedule.js
index 2cdbb799d06..2a34cf0d029 100644
--- a/lib/plugins/aws/package/compile/events/schedule.js
+++ b/lib/plugins/aws/package/compile/events/schedule.js
@@ -136,9 +136,11 @@ class AwsCompileScheduledEvents {
               Name = event.schedule.name;
               timezone = event.schedule.timezone;
               Description = event.schedule.description;
-              roleArn = {
-                'Fn::GetAtt': ['IamRoleLambdaExecution', 'Arn'],
-              };
+
+              const functionLogicalId = this.provider.naming.getLambdaLogicalId(functionName);
+              const functionResource = resources[functionLogicalId];
+
+              roleArn = functionResource.Properties.Role;
 
               method = event.schedule.method || METHOD_EVENT_BUS;
 


Test patch:
diff --git a/test/unit/lib/plugins/aws/package/compile/events/schedule.test.js b/test/unit/lib/plugins/aws/package/compile/events/schedule.test.js
index 11eb0d3ce33..66cb1505135 100644
--- a/test/unit/lib/plugins/aws/package/compile/events/schedule.test.js
+++ b/test/unit/lib/plugins/aws/package/compile/events/schedule.test.js
@@ -10,7 +10,7 @@ const METHOD_EVENT_BUS = 'eventBus';
 
 chaiUse(chaiAsPromised);
 
-async function run(events) {
+async function run(events, options = {}) {
   const params = {
     fixture: 'function',
     command: 'package',
@@ -19,6 +19,7 @@ async function run(events) {
         test: {
           handler: 'index.handler',
           events,
+          role: options.functionRole,
         },
       },
     },
@@ -414,4 +415,24 @@ describe('test/unit/lib/plugins/aws/package/compile/events/schedule.test.js', ()
   it('should not create schedule resources when no scheduled event is given', async () => {
     expect((await run([])).scheduleCfResources).to.be.empty;
   });
+
+  it('should pass the custom roleArn to method:schedule resources', async () => {
+    const events = [
+      {
+        schedule: {
+          rate: 'rate(15 minutes)',
+          method: METHOD_SCHEDULER,
+          name: 'scheduler-scheduled-event',
+          description: 'Scheduler Scheduled Event',
+          input: '{"key":"array"}',
+        },
+      },
+    ];
+
+    const { scheduleCfResources } = await run(events, { functionRole: 'customRole' });
+
+    expect(scheduleCfResources[0].Properties.Target.RoleArn).to.deep.equal({
+      'Fn::GetAtt': ['customRole', 'Arn'],
+    });
+  });
 });