Repository: serverless/serverless (JavaScript) — Bug Fix

Issue:
Serverless does not create ~/.aws/credentials file when using config credentials CLI on OS X El Capitan 10.11.3
<!--
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 / Feature Proposal)
Bug Report
## Description
Same issue as #2857 but also happening on OS X. 
Serverless CLI fails to create `~/.aws/credentials` when calling `serverless config credentials --provider aws -k <key> -s <secret>` 
For bug reports:
* What went wrong?
Serverless did not create the `~/.aws/credentials` file.  After a success message there is a stack trace about failure. 
* What did you expect should have happened?
I expected serverless to create the `~/.aws/credentials` file. 
* What was the config you used?
No config; new project. 
* What stacktrace or error message from your provider did you see?
```
Serverless: Setting up AWS...
Serverless: Saving your AWS profile in "~/.aws/credentials"...
Serverless: Success! Your AWS access keys were stored under the "default" profile.

  Error --------------------------------------------------

     ENOENT: no such file or directory, open '/Users/alexbielen/.aws/credentials'

     For debugging logs, run again after setting the "SLS_DEBUG=*" environment variable.

  Stack Trace --------------------------------------------

Error: ENOENT: no such file or directory, open '/Users/alexbielen/.aws/credentials'
    at Error (native)
    at Object.fs.openSync (fs.js:640:18)
    at Object.fs.writeFileSync (fs.js:1333:33)
    at Object.fs.appendFileSync (fs.js:1392:6)
    at e (/usr/local/lib/node_modules/serverless/lib/classes/Utils.js:81:12)
    at Promise._execute (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/debuggability.js:299:9)
    at Promise._resolveFromExecutor (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:481:18)
    at new Promise (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:77:14)
    at Utils.appendFileSync (/usr/local/lib/node_modules/serverless/lib/classes/Utils.js:79:12)
    at AwsConfigCredentials.configureCredentials (/usr/local/lib/node_modules/serverless/lib/plugins/aws/configCredentials/awsConfigCredentials.js:94:27)
    at AwsConfigCredentials.tryCatcher (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/util.js:16:23)
    at Promise._settlePromiseFromHandler (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:510:31)
    at Promise._settlePromise (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:567:18)
    at Promise._settlePromiseCtx (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/promise.js:604:10)
    at Async._drainQueue (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:143:12)
    at Async._drainQueues (/usr/local/lib/node_modules/serverless/node_modules/bluebird/js/release/async.js:148:10)

  Get Support --------------------------------------------
     Docs:          docs.serverless.com
     Bugs:          github.com/serverless/serverless/issues

     Please report this error. We think it might be a bug.

  Your Environment Information -----------------------------
     OS:                 darwin
     Node Version:       6.7.0
     Serverless Version: 1.3.0
```

Similar or dependent issues:
* #2857 



Discussion:
@alexbielen what is your home directory?
@pmuens Looks like it's failing if the directory doesn't exist. https://github.com/serverless/serverless/blob/master/lib/plugins/aws/configCredentials/awsConfigCredentials.js#L94

We should probably use `outputFileSync ` https://github.com/jprichardson/node-fs-extra#outputfilefile-data-options-callback which created the directory if it doesn't exist yet
@DavidWells thanks for looking into that!

Good idea! I'll look into that!

Resolving patch:
diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.js
index cab236ccdd0..b2f9417cb62 100644
--- a/lib/plugins/aws/configCredentials/awsConfigCredentials.js
+++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.js
@@ -2,6 +2,7 @@
 
 const BbPromise = require('bluebird');
 const path = require('path');
+const fse = require('fs-extra');
 
 class AwsConfigCredentials {
   constructor(serverless, options) {
@@ -88,6 +89,9 @@ class AwsConfigCredentials {
           `Failed! ~/.aws/credentials exists and already has a "${this.options.profile}" profile.`);
         return BbPromise.resolve();
       }
+    } else {
+      // create the credentials file alongside the .aws directory if it's not yet present
+      fse.ensureFileSync(credsPath);
     }
 
     // write credentials file with 'default' profile


Test patch:
diff --git a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js
index ae118d48b6f..73dd379af0c 100644
--- a/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js
+++ b/lib/plugins/aws/configCredentials/awsConfigCredentials.test.js
@@ -71,11 +71,12 @@ describe('AwsConfigCredentials', () => {
 
   describe('#configureCredentials()', () => {
     let homeDir;
+    let tmpDirPath;
     let credentialsFilePath;
 
     beforeEach(() => {
       // create a new tmpDir for the homeDir path
-      const tmpDirPath = testUtils.getTmpDirPath();
+      tmpDirPath = testUtils.getTmpDirPath();
       fse.mkdirsSync(tmpDirPath);
 
       // create the .aws/credetials directory and file
@@ -128,7 +129,7 @@ describe('AwsConfigCredentials', () => {
       awsConfigCredentials.options.key = 'my-profile-key';
       awsConfigCredentials.options.secret = 'my-profile-secret';
 
-      awsConfigCredentials.configureCredentials().then(() => {
+      return awsConfigCredentials.configureCredentials().then(() => {
         const credentialsFileContent = fs.readFileSync(credentialsFilePath).toString();
         const lineByLineContent = credentialsFileContent.split('\n');
 
@@ -138,6 +139,18 @@ describe('AwsConfigCredentials', () => {
       });
     });
 
+    it('should create the .aws/credentials file if not yet present', () => {
+      // remove the .aws directory which was created in the before hook of the test
+      const awsDirectoryPath = path.join(tmpDirPath, '.aws');
+      fse.removeSync(awsDirectoryPath);
+
+      return awsConfigCredentials.configureCredentials().then(() => {
+        const isCredentialsFilePresent = fs.existsSync(path.join(awsDirectoryPath, 'credentials'));
+
+        expect(isCredentialsFilePresent).to.equal(true);
+      });
+    });
+
     afterEach(() => {
       // recover the homeDir
       process.env.HOME = homeDir;