diff --git a/lib/ServerlessPlugin.js b/lib/ServerlessPlugin.js index a884c6db2..badca34c9 100644 --- a/lib/ServerlessPlugin.js +++ b/lib/ServerlessPlugin.js @@ -1,10 +1,10 @@ 'use strict'; const SError = require('./ServerlessError'), - SUtils = require('./utils/index'), - SCli = require('./utils/cli'), - awsMisc = require('./utils/aws/Misc'), - BbPromise = require('bluebird'); + SUtils = require('./utils/index'), + SCli = require('./utils/cli'), + awsMisc = require('./utils/aws/Misc'), + BbPromise = require('bluebird'); /** * This is the base class that all Serverless Plugins should extend. @@ -16,9 +16,8 @@ class ServerlessPlugin { * Constructor */ - constructor(S, config) { - this.S = S; - this.config = config; + constructor(S) { + this.S = S; } /** @@ -46,7 +45,7 @@ class ServerlessPlugin { } /** - * CLI Prompt Input + * CLI: Prompt Input * - Handy CLI Prompt Input function for Plugins * @param promptSchema @see https://github.com/flatiron/prompt#prompting-with-validation-default-values-and-more-complex-properties * @param overrides map {key: 'overrideValue'} @@ -64,7 +63,7 @@ class ServerlessPlugin { } /** - * CLI Prompt Select + * CLI: Prompt Select * - Handy CLI Select Input function for Plugins * @param message string * @param choices [{key:"",value:"",label:""}] @@ -85,7 +84,7 @@ class ServerlessPlugin { } /** - * CLI Prompt Select Functions + * CLI: Prompt Select Functions * - Prompt the user to select a function in the current working directory */ @@ -151,7 +150,7 @@ class ServerlessPlugin { } /** - * CLI Prompt Select Endpoints + * CLI: Prompt Select Endpoints * - Prompt the user to select an endpoint in the current working directory */ @@ -222,114 +221,12 @@ class ServerlessPlugin { }); } - /** - * CLI Prompt Select Functions & Endpoints - * - Prompt the user to select both functions & endpoints to deploy + * CLI: Prompt Select Stage */ - cliPromptSelectFunctionsAndEndpoints(cwd, message, multi, skipSingles) { - - let _this = this; - - // If not interactive, throw error - if (!this.S._interactive) { - return BbPromise.reject(new SError('Sorry, this is only available in interactive mode')); - } - - // Get Functions - return SUtils.getFunctions(cwd, null) - .then(function(functions) { - - return SUtils.getEndpoints(cwd, null) - .then(function (endpoints) { - - return [functions, endpoints]; - }); - }) - .spread(function(functions, endpoints) { - // If no functions & endpoints found, return - if (!functions.length && !endpoints.length) { - return []; - } - - let functionsAndEndpoints = []; - - functions.forEach(function(func){ - let obj = { - obj: func, - type: "function", - moduleFunction: func.module.name + ' - ' + func.name - }; - - functionsAndEndpoints.push(obj); - }); - - endpoints.forEach(function(endpoint){ - let obj = { - obj: endpoint, - type: "endpoint", - moduleFunction: endpoint.module.name + ' - ' + endpoint.function.name - }; - - functionsAndEndpoints.push(obj); - }); - - // Prepare endpoints choices - let choices = []; - for (let i = 0; i < functionsAndEndpoints.length; i++) { - - let label = 'function'; - // if endpoint, change label - if (functionsAndEndpoints[i].type === "endpoint") { - label = 'endpoint - ' + functionsAndEndpoints[i].obj.method + ' - ' + functionsAndEndpoints[i].obj.path; - } - - choices.push({ - key: ' ', - value: functionsAndEndpoints[i], - label: label - }); - } - - choices.sort(function(a,b) { - if (a.value.moduleFunction < b.value.moduleFunction) return -1; - if (a.value.moduleFunction > b.value.moduleFunction) return 1; - return 0; - }); - - // Add spacers between modules - let last; - for (let i = 0; i < choices.length; i++) { - let functionName; - - if (choices[i].value.type === "function") functionName = choices[i].value.obj.name; - if (choices[i].value.type === "endpoint") functionName = choices[i].value.obj.function.name; - - let current = choices[i].value.moduleFunction; - - if (current !== last) { - last = current; - choices.splice(i, 0, { spacer: current }); - } - } - - // Show select input - return _this.cliPromptSelect(message, choices, multi, 'Deploy') - .then(function(selected) { - - let selectedItems = []; - for (let i = 0; i < selected.length; i++) { - if (selected[i].toggled) selectedItems.push(selected[i].value); - } - - return selectedItems; - }); - }); - } - - cliPromptSelectStage(message, stage, addLocalStage) { + let _this = this, stages = Object.keys(_this.S._projectJson.stages); @@ -357,12 +254,17 @@ class ServerlessPlugin { } return SCli.select(message, choices, false) - .then(function(results) { - return results[0].value; - }); + .then(function(results) { + return results[0].value; + }); } + /** + * CLI: Prompt Select Region + */ + cliPromptSelectRegion(message, addAllRegions, region, stage) { + let _this = this; // Resolve region if provided @@ -403,27 +305,27 @@ class ServerlessPlugin { } let choices = regionChoices.map(r => { - return { - key: '', - value: r, - label: r, - }; + return { + key: '', + value: r, + label: r, + }; }); if (addAllRegions) { choices.push( - { - key: '', - value: 'all', - label: 'all', - } + { + key: '', + value: 'all', + label: 'all', + } ); } return _this.cliPromptSelect(message, choices, false) - .then(results => { - return results[0].value; - }); + .then(results => { + return results[0].value; + }); } } diff --git a/lib/actions/DashDeploy.js b/lib/actions/DashDeploy.js index d72157e52..22baaf212 100644 --- a/lib/actions/DashDeploy.js +++ b/lib/actions/DashDeploy.js @@ -19,14 +19,14 @@ */ const SPlugin = require('../ServerlessPlugin'), - SError = require('../ServerlessError'), - SUtils = require('../utils/index'), - SCli = require('../utils/cli'), - BbPromise = require('bluebird'), - async = require('async'), - path = require('path'), - fs = require('fs'), - os = require('os'); + SError = require('../ServerlessError'), + SUtils = require('../utils/index'), + SCli = require('../utils/cli'), + BbPromise = require('bluebird'), + async = require('async'), + path = require('path'), + fs = require('fs'), + os = require('os'); // Promisify fs module BbPromise.promisifyAll(fs); @@ -71,8 +71,16 @@ class Dash extends SPlugin { description: 'Optional - Target one region to deploy to' }, { option: 'aliasFunction', // TODO: Implement - shortcut: 'af', + shortcut: 'f', description: 'Optional - Provide a custom Alias to your Functions' + }, { + option: 'aliasEndpoint', // TODO: Implement + shortcut: 'e', + description: 'Optional - Provide a custom Alias to your Endpoints' + }, { + option: 'aliasRestApi', // TODO: Implement + shortcut: 'r', + description: 'Optional - Provide a custom Api Gateway Stage Variable for your REST API' }, { option: 'all', shortcut: 'a', @@ -90,9 +98,8 @@ class Dash extends SPlugin { dash(event) { - let _this = this, - evt = {}; + evt = {}; // If CLI - parse options if (_this.S.cli) { @@ -130,27 +137,27 @@ class Dash extends SPlugin { // Flow return BbPromise.try(function() { - if (_this.S._interactive) { - SCli.asciiGreeting(); - } - }) - .bind(_this) - .then(_this._validateAndPrepare) - .then(_this._prepareFunctions) - .then(_this._prepareEndpoints) - .then(_this._prompt) - .then(function(evt) { - return _this.cliPromptSelectStage('Choose a stage: ', evt.stage, false) - .then(stage => { - evt.stage = stage; - return evt; - }) - }) - .then(_this._prepareRegions) - .then(_this._processDeployment) - .then(function(evt) { - return evt; - }); + if (_this.S._interactive) { + SCli.asciiGreeting(); + } + }) + .bind(_this) + .then(_this._validateAndPrepare) + .then(_this._prepareFunctions) + .then(_this._prepareEndpoints) + .then(_this._prompt) + .then(function(evt) { + return _this.cliPromptSelectStage('Choose a stage: ', evt.stage, false) + .then(stage => { + evt.stage = stage; + return evt; + }) + }) + .then(_this._prepareRegions) + .then(_this._processDeployment) + .then(function(evt) { + return evt; + }); } @@ -191,31 +198,31 @@ class Dash extends SPlugin { if (!_this.S.cli) { return SUtils.getFunctions( - _this.S._projectRootPath, - evt.all ? null : evt.paths) - .then(function (functions) { + _this.S._projectRootPath, + evt.all ? null : evt.paths) + .then(function (functions) { - evt.functions = functions; - // Delete Paths - if (evt.paths) delete evt.paths; - return evt; - }); + evt.functions = functions; + // Delete Paths + if (evt.paths) delete evt.paths; + return evt; + }); } // IF CLI + ALL/paths, get functions if (_this.S.cli && (evt.all || evt.paths.length)) { return SUtils.getFunctions( - _this.S._projectRootPath, - evt.all ? null : evt.paths) - .then(function (functions) { + _this.S._projectRootPath, + evt.all ? null : evt.paths) + .then(function (functions) { - if (!functions.length) throw new SError(`No functions found`); - evt.functions = functions; - // Delete Paths - if (evt.paths) delete evt.paths; - return evt; - }); + if (!functions.length) throw new SError(`No functions found`); + evt.functions = functions; + // Delete Paths + if (evt.paths) delete evt.paths; + return evt; + }); } return evt; @@ -235,32 +242,32 @@ class Dash extends SPlugin { if (!_this.S.cli) { return SUtils.getEndpoints( - _this.S._projectRootPath, - evt.all ? null : evt.paths) - .then(function (endpoints) { + _this.S._projectRootPath, + evt.all ? null : evt.paths) + .then(function (endpoints) { - evt.endpoints = endpoints; - // Delete Paths - if (evt.paths) delete evt.paths; - return evt; - }); + evt.endpoints = endpoints; + // Delete Paths + if (evt.paths) delete evt.paths; + return evt; + }); } // IF CLI + ALL/paths, get endpoints if (_this.S.cli && (evt.all || evt.paths.length)) { return SUtils.getEndpoints( - _this.S._projectRootPath, - evt.all ? null : evt.paths) - .then(function (endpoints) { + _this.S._projectRootPath, + evt.all ? null : evt.paths) + .then(function (endpoints) { - if (!endpoints.length) throw new SError(`No endpoints found`); - evt.endpoints = endpoints; + if (!endpoints.length) throw new SError(`No endpoints found`); + evt.endpoints = endpoints; - // Delete Paths - if (evt.paths) delete evt.paths; - return evt; - }); + // Delete Paths + if (evt.paths) delete evt.paths; + return evt; + }); } return evt; @@ -277,19 +284,106 @@ class Dash extends SPlugin { if (_this.S.cli && !evt.all && !evt.paths.length) { - return _this.cliPromptSelectFunctionsAndEndpoints( - process.cwd(), - 'Select the functions/endpoints you wish to deploy:', - true, - true) - .then(function (selected) { - selected.forEach(function(item){ - if(item.type === "function") evt.functions.push(item.obj); - if(item.type === "endpoint") evt.endpoints.push(item.obj); - }); + // If not interactive, throw error + if (!this.S._interactive) { + return BbPromise.reject(new SError('Sorry, this is only available in interactive mode')); + } - return evt; - }); + // Get Functions + return SUtils.getFunctions(process.cwd(), null) + .then(function(functions) { + + return SUtils.getEndpoints(process.cwd(), null) + .then(function (endpoints) { + return [functions, endpoints]; + }); + }) + .spread(function(functions, endpoints) { + + // If no functions & endpoints found, return + if (!functions.length && !endpoints.length) return []; + + let functionsAndEndpoints = []; + + functions.forEach(function(func){ + let obj = { + obj: func, + type: "function", + moduleFunction: func.module.name + ' - ' + func.name + }; + + functionsAndEndpoints.push(obj); + }); + + endpoints.forEach(function(endpoint){ + let obj = { + obj: endpoint, + type: "endpoint", + moduleFunction: endpoint.module.name + ' - ' + endpoint.function.name + }; + + functionsAndEndpoints.push(obj); + }); + + // Prepare endpoints choices + let choices = []; + for (let i = 0; i < functionsAndEndpoints.length; i++) { + + let label = 'function'; + // if endpoint, change label + if (functionsAndEndpoints[i].type === "endpoint") { + label = 'endpoint - ' + functionsAndEndpoints[i].obj.path + ' - ' + functionsAndEndpoints[i].obj.method; + } + + choices.push({ + key: ' ', + value: functionsAndEndpoints[i], + label: label + }); + } + + choices.sort(function(a,b) { + if (a.value.moduleFunction < b.value.moduleFunction) return -1; + if (a.value.moduleFunction > b.value.moduleFunction) return 1; + return 0; + }); + + // Add spacers between modules + let last; + for (let i = 0; i < choices.length; i++) { + let functionName; + + if (choices[i].value.type === "function") functionName = choices[i].value.obj.name; + if (choices[i].value.type === "endpoint") functionName = choices[i].value.obj.function.name; + + let current = choices[i].value.moduleFunction; + + if (current !== last) { + last = current; + choices.splice(i, 0, { spacer: current }); + } + } + + // Show select input + return _this.cliPromptSelect('Select the functions and endpoints you wish to deploy', choices, true, 'Deploy') + .then(function(selected) { + + let selectedItems = []; + for (let i = 0; i < selected.length; i++) { + if (selected[i].toggled) selectedItems.push(selected[i].value); + } + + return selectedItems; + }); + }) + .then(function (selected) { + selected.forEach(function(item){ + if(item.type === "function") evt.functions.push(item.obj); + if(item.type === "endpoint") evt.endpoints.push(item.obj); + }); + + return evt; + }); } return evt; } @@ -303,8 +397,8 @@ class Dash extends SPlugin { // If no region specified, deploy to all regions in stage if (!evt.regions.length) { evt.regions = this.S._projectJson.stages[evt.stage].map(rCfg => { - return rCfg.region; - }); + return rCfg.region; + }); } return evt; @@ -324,36 +418,36 @@ class Dash extends SPlugin { _this._spinner.start(); return BbPromise.try(function() { - return evt.regions; - }) - .bind(_this) - .each(function(region) { + return evt.regions; + }) + .bind(_this) + .each(function(region) { - // Add Deployed Region - evt.deployedFunctions[region] = []; - evt.deployedEndpoints[region] = []; + // Add Deployed Region + evt.deployedFunctions[region] = []; + evt.deployedEndpoints[region] = []; - // Deploy Function Code in each region - return _this._deployCodeByRegion(evt, region) - .then(function(){ - return _this._deployEndpointsByRegion(evt, region) - }); - }) - .then(function() { + // Deploy Function Code in each region + return _this._deployCodeByRegion(evt, region) + .then(function(){ + return _this._deployEndpointsByRegion(evt, region) + }); + }) + .then(function() { - // Status - _this._spinner.stop(true); - SCli.log('Successfully deployed functions/endpoints in "' + evt.stage + '" to the following regions: ' + evt.regions); - // Display Methods & URLS - for (let i = 0; i < Object.keys(evt.deployedEndpoints).length; i++) { - let region = evt.deployedEndpoints[Object.keys(evt.deployedEndpoints)[i]]; - SCli.log(Object.keys(evt.deployedEndpoints)[i] + ' ------------------------'); - for (let j = 0; j < region.length; j++) { - SCli.log(' ' + region[j].method + ' - ' + region[j].url); + // Status + _this._spinner.stop(true); + SCli.log('Successfully deployed functions/endpoints in "' + evt.stage + '" to the following regions: ' + evt.regions); + // Display Methods & URLS + for (let i = 0; i < Object.keys(evt.deployedEndpoints).length; i++) { + let region = evt.deployedEndpoints[Object.keys(evt.deployedEndpoints)[i]]; + SCli.log(Object.keys(evt.deployedEndpoints)[i] + ' ------------------------'); + for (let j = 0; j < region.length; j++) { + SCli.log(' ' + region[j].method + ' - ' + region[j].url); + } } - } - return evt; - }); + return evt; + }); } /** @@ -377,35 +471,35 @@ class Dash extends SPlugin { let evtClone = { stage: evt.stage, region: SUtils.getRegionConfig( - _this.S._projectJson, - evt.stage, - region), + _this.S._projectJson, + evt.stage, + region), function: func, }; // Process sub-Actions return _this.S.actions.codePackageLambdaNodejs(evtClone) - .bind(_this) - .then(_this.S.actions.codeDeployLambdaNodejs) - .then(function(evtCloneProcessed) { + .bind(_this) + .then(_this.S.actions.codeDeployLambdaNodejs) + .then(function(evtCloneProcessed) { - // Add Function and Region - evt.deployedFunctions[region].push(evtCloneProcessed.function); - return cb(); - }) - .catch(function(e) { + // Add Function and Region + evt.deployedFunctions[region].push(evtCloneProcessed.function); + return cb(); + }) + .catch(function(e) { - // Stash Failed Function Code - if (!evt.failed) evt.failed = {}; - if (!evt.failed[region]) evt.failed[region] = []; - evt.failed[region].push({ - message: e.message, - stack: e.stack, - function: func.path, - }); + // Stash Failed Function Code + if (!evt.failed) evt.failed = {}; + if (!evt.failed[region]) evt.failed[region] = []; + evt.failed[region].push({ + message: e.message, + stack: e.stack, + function: func.path, + }); - return cb(); - }) + return cb(); + }) }, function() { return resolve(evt, region); @@ -422,10 +516,10 @@ class Dash extends SPlugin { _deployEndpointsByRegion(evt, region) { let _this = this, - regionConfig = SUtils.getRegionConfig( - _this.S._projectJson, - evt.stage, - region); + regionConfig = SUtils.getRegionConfig( + _this.S._projectJson, + evt.stage, + region); // Load AWS Service Instance for APIGateway let awsConfig = { @@ -437,66 +531,66 @@ class Dash extends SPlugin { // Get or Create REST API for Region return ApiGateway.sFindOrCreateRestApi( - _this.S, - evt.stage, - region) - .then(function(restApi) { + _this.S, + evt.stage, + region) + .then(function(restApi) { - return new BbPromise(function(resolve, reject) { + return new BbPromise(function(resolve, reject) { - // A function can have multiple endpoints. Process all endpoints for this Function - async.eachSeries(evt.endpoints, function(endpoint, eCb) { + // A function can have multiple endpoints. Process all endpoints for this Function + async.eachSeries(evt.endpoints, function(endpoint, eCb) { - // Create new event object - let evtClone = { - stage: evt.stage, - region: regionConfig, - endpoint: endpoint, - aliasEndpoint: evt.aliasEndpoint, - aliasRestApi: evt.aliasRestApi, - }; + // Create new event object + let evtClone = { + stage: evt.stage, + region: regionConfig, + endpoint: endpoint, + aliasEndpoint: evt.aliasEndpoint, + aliasRestApi: evt.aliasRestApi, + }; - return _this.S.actions.endpointBuildApiGateway(evtClone) - .then(function (evtProcessed) { - // Add provisioned endpoint urls - evt.deployedEndpoints[region].push({ - function: evtProcessed.endpoint.function.name, - method: evtProcessed.endpoint.method, - url: evtProcessed.endpoint.url - }); + return _this.S.actions.endpointBuildApiGateway(evtClone) + .then(function (evtProcessed) { + // Add provisioned endpoint urls + evt.deployedEndpoints[region].push({ + function: evtProcessed.endpoint.function.name, + method: evtProcessed.endpoint.method, + url: evtProcessed.endpoint.url + }); - return eCb(); - }) - .catch(function(e) { + return eCb(); + }) + .catch(function(e) { - // Stash Failed Endpoint - if (!evt.failed) evt.failed = {}; - if (!evt.failed[region]) evt.failed[region] = []; - evt.failed[region].push({ - message: e.message, - stack: e.stack, - endpoint: endpoint - }); + // Stash Failed Endpoint + if (!evt.failed) evt.failed = {}; + if (!evt.failed[region]) evt.failed[region] = []; + evt.failed[region].push({ + message: e.message, + stack: e.stack, + endpoint: endpoint + }); - return eCb(); + return eCb(); + }); + + }, function() { + return resolve(evt); + }); + }) + .then(function(evt) { + + // Deploy API Gateway Deployment in region + + let newEvent = { + stage: evt.stage, + region: regionConfig + }; + + return _this.S.actions.endpointDeployApiGateway(newEvent); }); - - }, function() { - return resolve(evt); - }); - }) - .then(function(evt) { - - // Deploy API Gateway Deployment in region - - let newEvent = { - stage: evt.stage, - region: regionConfig - }; - - return _this.S.actions.endpointDeployApiGateway(newEvent); - }); - }); + }); } } diff --git a/lib/utils/aws/CloudWatch.js b/lib/utils/aws/CloudWatch.js index d466e525a..87dfcc215 100644 --- a/lib/utils/aws/CloudWatch.js +++ b/lib/utils/aws/CloudWatch.js @@ -34,7 +34,7 @@ module.exports = function(config) { return CloudWatch.describeLogStreamsAsync(params) .error(function(error) { - return BbPromise.reject(new SError(error.message, SError.errorCodes.UNKNOWN); + return BbPromise.reject(new SError(error.message, SError.errorCodes.UNKNOWN)); }); }; @@ -47,7 +47,7 @@ module.exports = function(config) { return CloudWatch.getLogEventsAsync(params) .error(function(error) { - return BbPromise.reject(new SError(error.message, SError.errorCodes.UNKNOWN); + return BbPromise.reject(new SError(error.message, SError.errorCodes.UNKNOWN)); }); }; diff --git a/lib/utils/aws/IAM.js b/lib/utils/aws/IAM.js index b1543cfbb..22fc82e78 100644 --- a/lib/utils/aws/IAM.js +++ b/lib/utils/aws/IAM.js @@ -32,7 +32,7 @@ module.exports = function(config) { }; return IAM.getRoleAsync(params) .error(function(error) { - return BbPromise.reject(new SError(error.message, SError.errorCodes.UNKNOWN); + return BbPromise.reject(new SError(error.message, SError.errorCodes.UNKNOWN)); }); }; diff --git a/lib/utils/cli.js b/lib/utils/cli.js index 121454e93..fd826fcdf 100644 --- a/lib/utils/cli.js +++ b/lib/utils/cli.js @@ -19,7 +19,7 @@ Promise.promisifyAll(fs); Promise.promisifyAll(prompt); /** - * ASCII + * ASCII Greeting */ exports.asciiGreeting = function() { @@ -382,6 +382,9 @@ exports.generateActionHelp = function(cmdConfig) { return Promise.resolve(); }; +/** + * AWS Prompt Input ENV Key + */ exports.awsPromptInputEnvKey = function(message, SPluginObj) { @@ -405,7 +408,7 @@ exports.awsPromptInputEnvKey = function(message, SPluginObj) { .then(function(answers) { return answers.key; }); -} +}; exports.awsPromptInputEnvValue = function(message, SPluginObj) { @@ -429,4 +432,4 @@ exports.awsPromptInputEnvValue = function(message, SPluginObj) { .then(function(answers) { return answers.value; }); -} \ No newline at end of file +}; \ No newline at end of file