DashDeploy: move select function out of serverlessplugin

This commit is contained in:
Austen Collins 2015-12-13 18:49:00 -08:00
parent dd6479c382
commit c3f6937c8a
5 changed files with 319 additions and 320 deletions

View File

@ -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;
});
}
}

View File

@ -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);
});
});
});
}
}

View File

@ -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));
});
};

View File

@ -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));
});
};

View File

@ -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;
});
}
};