Repository: serverless/serverless (JavaScript) — Bug Fix

Issue:
Can't deploy with master branch: "Cannot read property 'endsWith' of null"
<!--
1. If you have a question and not a bug/feature request please ask it at http://forum.serverless.com
2. Please check if an issue already exists so there are no duplicates
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

Installed from master branch (needed to pull in https://github.com/serverless/serverless/pull/2014), and `serverless deploy` no longer works.  I get the error "Cannot read property 'endsWith' of null".

This happens with full Admin AWS credentials on the hello-world project created by `serverless create --template aws-nodejs`

For bug reports:
- What went wrong?
- What did you expect should have happened?
- What was the config you used?
- What stacktrace or error message from your provider did you see?
## Additional Data
- **_Serverless Framework Version you're using**_: 1.0.2
- **_Operating System**_: Ubuntu 14
- **_Stack Trace**_: below
- **_Provider Error messages**_: none

``` bash
$ SLS_DEBUG=true serverless deploy -v

Serverless: Packaging service...
Serverless: Removing old service versions...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading service .zip file to S3...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...

  Serverless Error ---------------------------------------

     Cannot read property 'endsWith' of null

  Stack Trace --------------------------------------------

ServerlessError: Cannot read property 'endsWith' of null
    at sdk.request.then.catch (/home/ubuntu/.npm-global/lib/node_modules/serverless/lib/plugins/aws/lib/monitorStack.js:97:26)
    at tryCatcher (/home/ubuntu/.npm-global/lib/node_modules/serverless/node_modules/bluebird/js/release/util.js:16:23)
    at Promise._settlePromiseFromHandler (/home/ubuntu/.npm-global/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:510:31)
    at Promise._settlePromise (/home/ubuntu/.npm-global/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:567:18)
    at Promise._settlePromise0 (/home/ubuntu/.npm-global/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:612:10)
    at Promise._settlePromises (/home/ubuntu/.npm-global/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:687:18)
    at Async._drainQueue (/home/ubuntu/.npm-global/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:138:16)
    at Async._drainQueues (/home/ubuntu/.npm-global/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:148:10)
    at Immediate.Async.drainQueues (/home/ubuntu/.npm-global/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:17:14)
    at runCallback (timers.js:574:20)
    at tryOnImmediate (timers.js:554:5)
    at processImmediate [as _immediateCallback] (timers.js:533:5)

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues

  Your Environment Infomation -----------------------------
     OS:                 linux
     Node Version:       6.7.0
     Serverless Version: 1.0.2
```



Discussion:
@bobby-brennan thanks for reporting!

It think I know what introduced the bug (a recent update to the `monitorStack` method). Will work on a fix!

BTW. Stack deployment should still work if you omit the `--verbose`/ `-v` flag.

@bobby-brennan could you provide your `serverless.yml` and a step-by-step list on how to reproduce this? Unfortunately I'm not able to reporduce this on my machine right now.

THanks for your help!

FWIW I wasn't able to reproduce on OSX, just on Ubuntu 14.04.

```
npm install -g serverless/serverless
serverless create --template aws-nodejs
serverless deploy -v
```

Thanks! Hmm. That's really strange. I'll try to make it fail with the steps provided.

Awesome, thanks for the quick response!

FYI, removing the `-v` option didn't seem to help.


Resolving patch:
diff --git a/lib/plugins/aws/lib/monitorStack.js b/lib/plugins/aws/lib/monitorStack.js
index 3c3e49e8c53..5abe556460f 100644
--- a/lib/plugins/aws/lib/monitorStack.js
+++ b/lib/plugins/aws/lib/monitorStack.js
@@ -45,23 +45,24 @@ module.exports = {
                 data.StackEvents.reverse().forEach((event) => {
                   const eventInRange = (monitoredSince < event.Timestamp);
                   const eventNotLogged = (loggedEvents.indexOf(event.EventId) === -1);
-                  let eventStatus = event.ResourceStatus;
+                  let eventStatus = event.ResourceStatus || null;
                   if (eventInRange && eventNotLogged) {
                     // Keep track of stack status
                     if (event.ResourceType === 'AWS::CloudFormation::Stack') {
                       stackStatus = eventStatus;
                     }
                     // Keep track of first failed event
-                    if (eventStatus.endsWith('FAILED') && stackLatestError === null) {
+                    if (eventStatus
+                      && eventStatus.endsWith('FAILED') && stackLatestError === null) {
                       stackLatestError = event;
                     }
                     // Log stack events
                     if (this.options.verbose) {
-                      if (eventStatus.endsWith('FAILED')) {
+                      if (eventStatus && eventStatus.endsWith('FAILED')) {
                         eventStatus = chalk.red(eventStatus);
-                      } else if (eventStatus.endsWith('PROGRESS')) {
+                      } else if (eventStatus && eventStatus.endsWith('PROGRESS')) {
                         eventStatus = chalk.yellow(eventStatus);
-                      } else if (eventStatus.endsWith('COMPLETE')) {
+                      } else if (eventStatus && eventStatus.endsWith('COMPLETE')) {
                         eventStatus = chalk.green(eventStatus);
                       }
                       let eventLog = `CloudFormation - ${eventStatus} - `;
@@ -77,7 +78,9 @@ module.exports = {
                 });
                 // Handle stack create/update/delete failures
                 if ((stackLatestError && !this.options.verbose)
-                || (stackStatus.endsWith('ROLLBACK_COMPLETE') && this.options.verbose)) {
+                || (stackStatus
+                    && stackStatus.endsWith('ROLLBACK_COMPLETE')
+                    && this.options.verbose)) {
                   this.serverless.cli.log('Deployment failed!');
                   let errorMessage = 'An error occurred while provisioning your stack: ';
                   errorMessage += `${stackLatestError.LogicalResourceId} - `;


Test patch:
diff --git a/lib/plugins/aws/tests/monitorStack.js b/lib/plugins/aws/tests/monitorStack.js
index c820f27a9b9..4e1ce49cd67 100644
--- a/lib/plugins/aws/tests/monitorStack.js
+++ b/lib/plugins/aws/tests/monitorStack.js
@@ -302,6 +302,63 @@ describe('monitorStack', () => {
       });
     });
 
+    it('should keep monitoring when 1st ResourceType is not "AWS::CloudFormation::Stack"', () => {
+      const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request');
+      const cfDataMock = {
+        StackId: 'new-service-dev',
+      };
+      const firstNoStackResourceTypeEvent = {
+        StackEvents: [
+          {
+            EventId: '1a2b3c4d',
+            LogicalResourceId: 'somebucket',
+            ResourceType: 'AWS::S3::Bucket',
+            Timestamp: new Date(),
+          },
+        ],
+      };
+      const updateStartEvent = {
+        StackEvents: [
+          {
+            EventId: '1a2b3c4d',
+            LogicalResourceId: 'mocha',
+            ResourceType: 'AWS::CloudFormation::Stack',
+            Timestamp: new Date(),
+            ResourceStatus: 'UPDATE_IN_PROGRESS',
+          },
+        ],
+      };
+      const updateComplete = {
+        StackEvents: [
+          {
+            EventId: '1m2n3o4p',
+            LogicalResourceId: 'mocha',
+            ResourceType: 'AWS::CloudFormation::Stack',
+            Timestamp: new Date(),
+            ResourceStatus: 'UPDATE_COMPLETE',
+          },
+        ],
+      };
+      describeStackEventsStub.onCall(0).returns(BbPromise.resolve(firstNoStackResourceTypeEvent));
+      describeStackEventsStub.onCall(1).returns(BbPromise.resolve(updateStartEvent));
+      describeStackEventsStub.onCall(2).returns(BbPromise.resolve(updateComplete));
+
+      return awsPlugin.monitorStack('update', cfDataMock, 10).then(() => {
+        expect(describeStackEventsStub.callCount).to.be.equal(3);
+        expect(describeStackEventsStub.calledWithExactly(
+          'CloudFormation',
+          'describeStackEvents',
+          {
+            StackName: cfDataMock.StackId,
+          },
+          awsPlugin.options.stage,
+          awsPlugin.options.region
+        )).to.be.equal(true);
+        awsPlugin.provider.request.restore();
+      });
+    });
+
+
     it('should catch describeStackEvents error if stack was not in deleting state', () => {
       const describeStackEventsStub = sinon.stub(awsPlugin.provider, 'request');
       const cfDataMock = {