Skip to content

Commit

Permalink
Merge pull request serverless#6294 from serverless/interactive-server…
Browse files Browse the repository at this point in the history
…less

PLAT-1202 - Interactive `serverless` create
  • Loading branch information
medikoo authored Jul 10, 2019
2 parents 6156af4 + c14cece commit 4564c36
Show file tree
Hide file tree
Showing 19 changed files with 717 additions and 276 deletions.
18 changes: 9 additions & 9 deletions bin/serverless.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,15 @@ const invocationId = uuid.v4();
}
// requiring here so that if anything went wrong,
// during require, it will be caught.
const Serverless = require('../lib/Serverless'); // eslint-disable-line global-require
const Serverless = require('../lib/Serverless');

const serverless = new Serverless({
interactive: typeof process.env.CI === 'undefined',
});
const serverless = new Serverless();

serverless.invocationId = invocationId;

return serverless
.init()
.then(() => serverless.run())
.then(() => process.exit(0))
.catch(err => {
// If Enterprise Plugin, capture error
let enterpriseErrorHandler = null;
Expand All @@ -65,7 +62,10 @@ const invocationId = uuid.v4();
});
});
})
.catch(e => {
process.exitCode = 1;
logError(e);
}))();
.then(
() => process.exit(0),
e => {
process.exitCode = 1;
logError(e);
}
))();
1 change: 1 addition & 0 deletions lib/classes/PluginManager.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ describe('PluginManager', () => {
({ restoreEnv } = overrideEnv({ whitelist: ['APPDATA', 'PATH'] }));
serverless = new Serverless();
serverless.cli = new CLI();
serverless.processedInput = { commands: [], options: {} };
pluginManager = new PluginManager(serverless);
pluginManager.serverless.config.servicePath = 'foo';
});
Expand Down
4 changes: 2 additions & 2 deletions lib/classes/Service.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ class Service {
load(rawOptions) {
const that = this;
const options = rawOptions || {};
options.stage = options.stage || options.s;
options.region = options.region || options.r;
if (!options.stage && options.s) options.stage = options.s;
if (!options.region && options.r) options.region = options.r;
const servicePath = this.serverless.config.servicePath;

// skip if the service path is not found
Expand Down
1 change: 1 addition & 0 deletions lib/plugins/Plugins.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"plugins": [
"./interactiveCli",
"./config/config.js",
"./create/create.js",
"./install/install.js",
Expand Down
151 changes: 34 additions & 117 deletions lib/plugins/aws/configCredentials/awsConfigCredentials.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
'use strict';

const BbPromise = require('bluebird');
const constants = require('constants');
const path = require('path');
const fs = require('fs');
const fse = require('fs-extra');
const os = require('os');
const _ = require('lodash');
const credentials = require('../utils/credentials');

class AwsConfigCredentials {
constructor(serverless, options) {
Expand Down Expand Up @@ -52,128 +48,49 @@ class AwsConfigCredentials {
);
}

this.credentialsFilePath = path.join(os.homedir(), '.aws', 'credentials');
// Create the credentials file alongside the .aws directory if it's not yet present
fse.ensureFileSync(this.credentialsFilePath);

this.hooks = {
'config:credentials:config': () => BbPromise.bind(this).then(this.configureCredentials),
'config:credentials:config': () => this.configureCredentials(),
};
}

configureCredentials() {
// sanitize
this.options.provider = this.options.provider.toLowerCase();
this.options.profile = this.options.profile ? this.options.profile : 'default';

// resolve if provider option is not 'aws'
if (this.options.provider !== 'aws') {
return BbPromise.resolve();
}

// validate
if (!this.options.key || !this.options.secret) {
throw new this.serverless.classes.Error('Please include --key and --secret options for AWS.');
}

this.serverless.cli.log('Setting up AWS...');

this.credentials = this.getCredentials();

// Get the profile start line and end line numbers inside the credentials array
const profileBoundaries = this.getProfileBoundaries();

// Check if the profile exists
const isNewProfile = profileBoundaries.start === -1;
if (isNewProfile) {
this.addProfile();
} else {
// Only update the profile if the overwrite flag was set
if (!this.options.overwrite) {
const message = [
`Failed! ~/.aws/credentials already has a "${this.options.profile}" profile.`,
' Use the overwrite flag ("-o" or "--overwrite") to force the update',
].join('');
this.serverless.cli.log(message);
return BbPromise.resolve();
}

this.updateProfile(profileBoundaries);
}

return this.saveCredentialsFile();
}

getCredentials() {
// Get the credentials file lines
const credentialsFileContent = this.serverless.utils.readFileSync(this.credentialsFilePath);
return credentialsFileContent ? credentialsFileContent.split('\n') : [];
}

addProfile() {
this.credentials.push(
`[${this.options.profile}]`,
`aws_access_key_id = ${this.options.key}`,
`aws_secret_access_key = ${this.options.secret}`
);
}

updateProfile(profileBoundries) {
let currentLine = profileBoundries.start;
let endLine = profileBoundries.end;

// Remove existing 'aws_access_key_id' and 'aws_secret_access_key' properties
while (currentLine < endLine) {
const line = this.credentials[currentLine];
if (line.indexOf('aws_access_key_id') > -1 || line.indexOf('aws_secret_access_key') > -1) {
this.credentials.splice(currentLine, 1);
endLine--;
} else {
currentLine++;
return BbPromise.try(() => {
// sanitize
this.options.provider = this.options.provider.toLowerCase();
this.options.profile = this.options.profile ? this.options.profile : 'default';

// resolve if provider option is not 'aws'
if (this.options.provider !== 'aws') return null;

// validate
if (!this.options.key || !this.options.secret) {
throw new this.serverless.classes.Error(
'Please include --key and --secret options for AWS.'
);
}
}

// Add the key and the secret to the beginning of the section
const keyLine = `aws_access_key_id = ${this.options.key}`;
const secretLine = `aws_secret_access_key = ${this.options.secret}`;

this.credentials.splice(profileBoundries.start + 1, 0, secretLine);
this.credentials.splice(profileBoundries.start + 1, 0, keyLine);
}

saveCredentialsFile() {
// Generate the file content and add a line break at the end
const updatedCredsFileContent = `${this.credentials.join('\n')}\n`;

this.serverless.cli.log('Saving your AWS profile in "~/.aws/credentials"...');

return this.serverless.utils
.writeFile(this.credentialsFilePath, updatedCredsFileContent)
.then(() => {
// set file permissions to only readable/writable by owner (equivalent to 'chmod 600')
// NOTE: `chmod` doesn't behave as intended on Windows, so skip if we're on Windows.
if (os.platform() !== 'win32') {
fs.chmodSync(
this.credentialsFilePath,
(fs.constants || constants).S_IRUSR | (fs.constants || constants).S_IWUSR
);
this.serverless.cli.log('Setting up AWS...');

return credentials.resolveFileProfiles().then(profiles => {
if (profiles.has(this.options.profile)) {
// Only update the profile if the overwrite flag was set
if (!this.options.overwrite) {
const message = [
`Failed! ~/.aws/credentials already has a "${this.options.profile}" profile.`,
' Use the overwrite flag ("-o" or "--overwrite") to force the update',
].join('');
this.serverless.cli.log(message);
return null;
}
}
profiles.set(this.options.profile, {
accessKeyId: this.options.key,
secretAccessKey: this.options.secret,
});

this.serverless.cli.log(
`Success! Your AWS access keys were stored under the "${this.options.profile}" profile.`
);
return credentials.saveFileProfiles(profiles);
});
}

getProfileBoundaries() {
// Get the line number of the aws profile definition, defaults to -1
const start = this.credentials.indexOf(`[${this.options.profile}]`);

const nextProfile = _.findIndex(this.credentials, line => /\[[^\]]+\]/.test(line), start + 1);
// Get the line number of the next aws profile definition, defaults to the file lines number
const end = nextProfile + 1 || this.credentials.length;

return { start, end };
});
}
}

Expand Down
Loading

0 comments on commit 4564c36

Please sign in to comment.