Repository: serverless/serverless (JavaScript)
Date: 2019-02-28 12:12:47+00:00
Task type: Bug Fix

Issue:
WebSockets custom function roles crash
<!--
1. If you have a question and not a bug report please ask first at http://forum.serverless.com
2. Please check if an issue already exists. This bug may have already been documented
3. Check out and follow our Guidelines: https://github.com/serverless/serverless/blob/master/CONTRIBUTING.md
4. Fill out the whole template so we have a good overview on the issue
5. Do not remove any section of the template. If something is not applicable leave it empty but leave it in the Issue
6. Please follow the template, otherwise we'll have to ask you to update it
-->

# This is a Bug Report

## Description

* What went wrong?

When using custom function roles and wesockets, Serverless crashes. 

Example serverless.yml:
```yaml
functions:
  myWebSocket:
    handler: myWebsocket.handler
    role: { "Fn::GetAtt": [ "MyWebSocketLambdaFunctionRole", "Arn" ] }
    events:
      - websocket:
          route: $connect
      - websocket:
          route: $disconnect
      - websocket:
          route: $default

resources:
  Resources:
    MyWebSocketLambdaFunctionRole: 
      Type: "AWS::IAM::Role,
      Properties:
        #custom role policies
```

Stack trace:


* What did you expect should have happened?
 No crashes and allow my custom roles

* What was the config you used?
* What stacktrace or error message from your provider did you see?
```bash
  Type Error ---------------------------------------------
 
  Cannot read property 'Properties' of undefined
 
     For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.
 
  Stack Trace --------------------------------------------
 
TypeError: Cannot read property 'Properties' of undefined
    at AwsCompileWebsockets.compileApi (/Users/...project/node_modules/serverless/lib/plugins/aws/package/compile/events/websockets/lib/api.js:39:8)
From previous event:
    at Object.package:compileEvents [as hook] (/Users/...project/node_modules/serverless/lib/plugins/aws/package/compile/events/websockets/index.js:39:12)
    at BbPromise.reduce (/Users/...project/node_modules/serverless/lib/classes/PluginManager.js:407:55)
From previous event:
    at PluginManager.invoke (/Users/...project/node_modules/serverless/lib/classes/PluginManager.js:407:22)
    at PluginManager.spawn (/Users/...project/node_modules/serverless/lib/classes/PluginManager.js:425:17)
    at Deploy.BbPromise.bind.then (/Users/...project/node_modules/serverless/lib/plugins/deploy/deploy.js:117:50)
From previous event:
    at Object.before:deploy:deploy [as hook] (/Users/...project/node_modules/serverless/lib/plugins/deploy/deploy.js:107:10)
    at BbPromise.reduce (/Users/...project/node_modules/serverless/lib/classes/PluginManager.js:407:55)
From previous event:
    at PluginManager.invoke (/Users/...project/node_modules/serverless/lib/classes/PluginManager.js:407:22)
    at PluginManager.run (/Users/...project/node_modules/serverless/lib/classes/PluginManager.js:438:17)
    at variables.populateService.then.then (/Users/...project/node_modules/serverless/lib/Serverless.js:114:33)
    at processImmediate (timers.js:637:19)
    at process.topLevelDomainCallback (domain.js:120:23)
From previous event:
    at Serverless.run (/Users/...project/node_modules/serverless/lib/Serverless.js:101:6)
    at serverless.init.then (/Users/...project/node_modules/serverless/bin/serverless:43:28)
    at /Users/...project/node_modules/graceful-fs/graceful-fs.js:111:16
    at /Users/...project/node_modules/graceful-fs/graceful-fs.js:45:10
    at FSReqCallback.args [as oncomplete] (fs.js:146:20)
From previous event:
    at initializeErrorReporter.then (/Users/...project/node_modules/serverless/bin/serverless:43:6)
    at processImmediate (timers.js:637:19)
    at process.topLevelDomainCallback (domain.js:120:23)
From previous event:
    at /Users/...project/node_modules/serverless/bin/serverless:28:46
    at Object.<anonymous> (/Users/...project/node_modules/serverless/bin/serverless:67:4)
    at Module._compile (internal/modules/cjs/loader.js:734:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:745:10)
    at Module.load (internal/modules/cjs/loader.js:626:32)
    at tryModuleLoad (internal/modules/cjs/loader.js:566:12)
    at Function.Module._load (internal/modules/cjs/loader.js:558:3)
    at Function.Module.runMain (internal/modules/cjs/loader.js:797:12)
    at executeUserCode (internal/bootstrap/node.js:526:15)
    at startMainThreadExecution (internal/bootstrap/node.js:439:3)
```

Similar or dependent issues:
* #5824 

## Additional Data

* ***Serverless Framework Version you're using***: 1.38.0
* ***Operating System***: macOS 10.14
* ***Stack Trace***:
* ***Provider Error messages***:



Issue discussion:
Thanks a lot for reporting this @chris-feist . I think I know what's going on, I'll push a fix very soon.

Solution patch:
diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/api.js b/lib/plugins/aws/package/compile/events/websockets/lib/api.js
index 0dbd2804585..f52067a6a67 100644
--- a/lib/plugins/aws/package/compile/events/websockets/lib/api.js
+++ b/lib/plugins/aws/package/compile/events/websockets/lib/api.js
@@ -23,20 +23,25 @@ module.exports = {
       },
     });
 
-    // insert policy that allows functions to postToConnection
-    const websocketsPolicy = {
-      Effect: 'Allow',
-      Action: ['execute-api:ManageConnections'],
-      Resource: ['arn:aws:execute-api:*:*:*/@connections/*'],
-    };
-
-    this.serverless.service.provider.compiledCloudFormationTemplate
-      .Resources[this.provider.naming.getRoleLogicalId()]
-      .Properties
-      .Policies[0]
-      .PolicyDocument
-      .Statement
-      .push(websocketsPolicy);
+    const defaultRoleResource = this.serverless.service.provider.compiledCloudFormationTemplate
+      .Resources[this.provider.naming.getRoleLogicalId()];
+
+    if (defaultRoleResource) {
+      // insert policy that allows functions to postToConnection
+      const websocketsPolicy = {
+        Effect: 'Allow',
+        Action: ['execute-api:ManageConnections'],
+        Resource: ['arn:aws:execute-api:*:*:*/@connections/*'],
+      };
+
+      this.serverless.service.provider.compiledCloudFormationTemplate
+        .Resources[this.provider.naming.getRoleLogicalId()]
+        .Properties
+        .Policies[0]
+        .PolicyDocument
+        .Statement
+        .push(websocketsPolicy);
+    }
 
     return BbPromise.resolve();
   },


Test patch:
diff --git a/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js b/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js
index bf31fbcbb9d..a3ed46155c2 100644
--- a/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js
+++ b/lib/plugins/aws/package/compile/events/websockets/lib/api.test.js
@@ -77,4 +77,17 @@ describe('#compileApi()', () => {
         },
       });
     }));
+
+  it('should NOT add the websockets policy if role resource does not exist', () => {
+    awsCompileWebsocketsEvents.serverless.service.provider.compiledCloudFormationTemplate
+      .Resources = {};
+
+    return awsCompileWebsocketsEvents
+      .compileApi().then(() => {
+        const resources = awsCompileWebsocketsEvents.serverless.service.provider
+          .compiledCloudFormationTemplate.Resources;
+
+        expect(resources[roleLogicalId]).to.deep.equal(undefined);
+      });
+  });
 });