From 1ca9c1161233e8c5f217a91c48cd506952c8d4dc Mon Sep 17 00:00:00 2001 From: doapp-ryanp Date: Mon, 21 Sep 2015 10:28:14 -0500 Subject: [PATCH 1/4] remove JAWS_REGION as lambda already defines this in AWS_REGION env var --- lib/commands/new_project.js | 6 ++---- lib/commands/new_stage_region.js | 21 ++++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/commands/new_project.js b/lib/commands/new_project.js index d1c8d90db..cbd82b51c 100644 --- a/lib/commands/new_project.js +++ b/lib/commands/new_project.js @@ -123,7 +123,7 @@ CMD.prototype._prompt = Promise.method(function() { _this.Prompter.override.name = _this._name; _this._prompts.properties.name = { description: 'Enter a project name: '.yellow, - default: 'jaws-' + shortid.generate().replace(/\W+/g, '').substring(0, 19).replace('_',''), + default: 'jaws-' + shortid.generate().replace(/\W+/g, '').substring(0, 19).replace('_', ''), message: 'Name must be only letters, numbers, underscores or dashes', conform: function(name) { var re = /^[a-zA-Z0-9-_]+$/; @@ -331,7 +331,6 @@ CMD.prototype._createProjectDirectory = Promise.method(function() { path.join(_this._projectRootPath, 'back', '.env'), 'JAWS_STAGE=' + _this._stage + '\nJAWS_DATA_MODEL_PREFIX=' + _this._stage - + '\nJAWS_REGION=' + _this._region ) .then(function() { return Promise.all([ @@ -364,8 +363,7 @@ CMD.prototype._createS3JawsStructure = Promise.method(function() { .then(function() { var envFileContents = 'JAWS_STAGE=' + _this._stage - + '\nJAWS_DATA_MODEL_PREFIX=' + _this._stage - + '\nJAWS_REGION=' + _this._region; + + '\nJAWS_DATA_MODEL_PREFIX=' + _this._stage; return AWSUtils.putEnvFile( _this._profile, diff --git a/lib/commands/new_stage_region.js b/lib/commands/new_stage_region.js index 3307eaa8c..c55ecb0f0 100644 --- a/lib/commands/new_stage_region.js +++ b/lib/commands/new_stage_region.js @@ -103,7 +103,7 @@ CMD.prototype._validate = Promise.method(function() { var _this = this; // Check project config is valid - if (!_this._JAWS._meta.projectJson.project || !_this._JAWS._meta.projectJson.stages) { + if (!_this._JAWS._meta.projectJson.stages) { throw new JawsError('Project\'s jaws.json is malformed or has no existing stages object defined'); } @@ -155,8 +155,7 @@ CMD.prototype._createEnvFile = Promise.method(function() { if (_this._type !== 'stage') return; var envFileContents = 'JAWS_STAGE=' + _this._stage - + '\nJAWS_DATA_MODEL_PREFIX=' + _this._stage - + '\nJAWS_REGION=' + _this._region; + + '\nJAWS_DATA_MODEL_PREFIX=' + _this._stage; return AWSUtils.putEnvFile( _this._JAWS._meta.profile, @@ -185,14 +184,14 @@ CMD.prototype._createCfStack = Promise.method(function() { spinner.start(); return AWSUtils.cfCreateResourcesStack( - _this._JAWS._meta.profile, - _this._region, - _this._JAWS._meta.projectRootPath, - _this._JAWS._meta.projectJson.name, - _this._stage, - _this._JAWS._meta.projectJson.jawsBuckets[_this._region], - '' // TODO: read email out of existing jaws-cf.json? - ) + _this._JAWS._meta.profile, + _this._region, + _this._JAWS._meta.projectRootPath, + _this._JAWS._meta.projectJson.name, + _this._stage, + _this._JAWS._meta.projectJson.jawsBuckets[_this._region], + '' // TODO: read email out of existing jaws-cf.json? + ) .then(function(cfData) { return AWSUtils.monitorCf(cfData, _this._JAWS._meta.profile, _this._region, 'create') .then(function(cfData) { From b62375bc55b16d8a0683fb6279a8825d46e9ee79 Mon Sep 17 00:00:00 2001 From: doapp-ryanp Date: Mon, 21 Sep 2015 13:45:12 -0500 Subject: [PATCH 2/4] upgrade new stage/region to support v1 json formats. make CLI for new command have better docs and more consistent --- bin/jaws | 98 ++++++++++------- lib/commands/new_project.js | 14 +-- lib/commands/new_stage_region.js | 175 ++++++++++++++++++++++--------- lib/utils/index.js | 55 ++++++---- package.json | 1 - tests/cli/new_stage_region.js | 2 +- 6 files changed, 228 insertions(+), 117 deletions(-) diff --git a/bin/jaws b/bin/jaws index 11562c4d0..51d555f16 100755 --- a/bin/jaws +++ b/bin/jaws @@ -7,7 +7,6 @@ var JawsError = require('../lib/jaws-error'), program = require('commander'), utils = require('../lib/utils'), Promise = require('bluebird'), - minimist = require('minimist'), execute = utils.execute; Promise.onPossiblyUnhandledRejection(function(error) { @@ -22,50 +21,69 @@ program /** * New - * - Create a new project|stage|region|action + * - Create a new project|stage|region */ - program - .command('new ') - .allowUnknownOption() - .description('Make a new "project", "stage", "region", or "module "') - .action(function() { + .command('new [params]') + .description('new project, stage and region commands\n\nValid \'s:' + + '\n\nproject: create new JAWS project in CWD.' + + '\n\t Ex: jaws new project' + + '\n\nstage: create new stage in existing region' + + '\n\t Ex: jaws new stage dev' + + '\n\nregion: create new region for given stage' + + '\n\t Ex: jaws new region dev' + ) + .option('-d, --dont-exe-cf', 'Don\'t execute CloudFormation, just generate it.') + .option('-r, --region ', 'name of aws region to use') + .option('-b, --s3-bucket ', 'project & region only: JAWS S3 bucket name. Will be created if DNE in "project" cmd. Must exist for "region" cmd.') + .option('-n, --proj-name ', 'project only: name for new project') + .option('-s, --stage ', 'project only: same of stage for new project') + .option('-e, --email ', 'project only: notification email to use in CloudFormation') + .option('-p, --aws-profile ', 'project only: Admin AWS profile as defined in ~/.aws/credentials to use') + .action(function(cmd, params, options) { - // Parse Args - var args = minimist(process.argv.slice(3)); - var type = args._[0] ? args._[0].toLowerCase() : null; - - if (type == 'project') { - // New Project + if (cmd == 'project') { var CmdNewProject = require('../lib/commands/new_project'); execute(CmdNewProject.run( - args.name, - args.stage ? args.stage.toLowerCase() : null, - args.s3Bucket, - args.region, - args.email, - args.profile, - args.noExeCf + options.projName, + options.stage ? options.stage.toLowerCase() : null, + options.s3Bucket, + options.region, + options.email, + options.awsProfile, + options.dontExeCf )); - } else if (type == 'region' || type == 'stage') { + } else if (cmd == 'region' || cmd == 'stage') { + var theParams = process.argv.slice(3); + + if (!theParams) { + throw new JawsError('Missing params', JawsError.errorCodes.UNKNOWN); + } + + if (theParams[0][0] == '-') { //TODO: how do we get around this commander shortcoming? + throw new JawsError('Specify options after cmd', JawsError.errorCodes.UNKNOWN); + } + + //TODO: iterate thru cmds to see how many are before - + if (theParams.length < 2) { + throw new JawsError('must specify stage name', JawsError.errorCodes.UNKNOWN); + } + + var CmdNewStageRegion = require('../lib/commands/new_stage_region'), + stageName = theParams[1]; - // New Region/Stage - var CmdNewStageRegion = require('../lib/commands/new_stage_region'); execute(CmdNewStageRegion.run( JAWS, - type, - args.stage, - args.region, - args.noExeCf + cmd, + stageName, + options.region, + options.s3Bucket, + options.dontExeCf )); - } else { - - // Unknown Type - console.error('Unsupported type ' + type + '. Must be project|region|stage|module'); + console.error('Unsupported cmd ' + cmd + '. Must be project|stage|region'); process.exit(1); - } }); @@ -77,10 +95,10 @@ program program .command('module [params]') - .description('aws-module commands\n\nValid ' + - '\'s:\n\ninstall: install aws-module.' + + .description('aws-module commands\n\nValid \'s:' + + '\n\ninstall: install aws-module.' + '\n\t Ex: jaws module install ' + - '\n\tupdate: update aws-module' + + '\n\nupdate: update aws-module' + '\n\t Ex: jaws module update ' + '\n\ncreate: create aws-module action. Module will be created if DNE. create ' + '\n\t Ex: jaws module create users list' @@ -89,15 +107,19 @@ program .option('-d, --dont-install-dependencies', 'Install&Update Only: DONT install awsm\'s depdencies automatically') .option('-l, --lambda', 'Create Only: create lambda. Default is create lambda and endpoint.') .option('-e, --endpoint', 'Create Only: create API Gateway endpoint. Default is create lambda and endpoint.') - .option('-r, --runtime', 'Create Only: lambda runtime. Default nodejs') + .option('-r, --runtime ', 'Create Only: lambda runtime. Default nodejs') .action(function(cmd, params, options) { var theParams = process.argv.slice(3); if (!theParams) { throw new JawsError('Missing params', JawsError.errorCodes.UNKNOWN); } + if (theParams[0][0] == '-') { //TODO: how do we get around this commander shortcoming? + throw new JawsError('Specify options after cmd', JawsError.errorCodes.UNKNOWN); + } if (cmd == 'install' || cmd == 'update') { - if (theParams.length !== 2) { + //TODO: iterate thru cmds to see how many are before - + if (theParams.length < 2) { throw new JawsError('Please specify awsm url', JawsError.errorCodes.UNKNOWN); } var url = theParams[1], @@ -105,7 +127,7 @@ program execute(theCmd[cmd](JAWS, url, options.save, options.dontInstallDependencies)); } else if (cmd == 'create') { - if (theParams.length !== 3) { + if (theParams.length < 3) { throw new JawsError('Please specify awsm resource and action name'); } diff --git a/lib/commands/new_project.js b/lib/commands/new_project.js index cbd82b51c..b5b404f0c 100644 --- a/lib/commands/new_project.js +++ b/lib/commands/new_project.js @@ -71,6 +71,7 @@ function CMD(name, stage, s3Bucket, notificationEmail, region, profile, noCf) { }; this.Prompter = JawsCLI.prompt(); this.Prompter.override = {}; + this._spinner = null; } /** @@ -213,7 +214,7 @@ CMD.prototype._prompt = Promise.method(function() { _this._awsAdminSecretKey = answers.awsAdminSecretKey; // If region exists, skip select prompt - if (_this._region) return Promise.resolve(); + if (_this._region) return; // Prompt: region select var choices = [ @@ -226,7 +227,6 @@ CMD.prototype._prompt = Promise.method(function() { return JawsCLI.select('Select a region for your project: ', choices, false) .then(function(results) { _this._region = results[0].value; - return Promise.resolve(); }); }) .then(function() { @@ -250,7 +250,6 @@ CMD.prototype._prompt = Promise.method(function() { return JawsCLI.select('Select a profile for your project: ', choices, false) .then(function(results) { _this._profile = results[0].value; - return Promise.resolve(); }); }); }); @@ -338,10 +337,7 @@ CMD.prototype._createProjectDirectory = Promise.method(function() { fs.mkdirAsync(path.join(_this._projectRootPath, 'tests')), fs.mkdirAsync(path.join(_this._projectRootPath, 'back', 'aws_modules')), utils.writeFile(path.join(_this._projectRootPath, 'admin.env'), adminEnv), - utils.writeFile(path.join( - _this._projectRootPath, 'cloudformation', _this._stage, _this._region, 'resources-cf.json'), - JSON.stringify(cfTemplate, null, 2) - ), + utils.generateResourcesCf(_this._projectRootPath, _this._name, _this._stage, _this._region, _this._notificationEmail), ]); }); }); @@ -383,9 +379,7 @@ CMD.prototype._createCfStack = Promise.method(function() { var _this = this; - // Start loading icon - JawsCLI.log('Creating CloudFormation Stack for your new project (~5 mins)...'); - _this._spinner = JawsCLI.spinner(''); + _this._spinner = JawsCLI.spinner('Creating CloudFormation Stack for your new project (~5 mins)...'); _this._spinner.start(); // Create CF stack diff --git a/lib/commands/new_stage_region.js b/lib/commands/new_stage_region.js index c55ecb0f0..76894cd8e 100644 --- a/lib/commands/new_stage_region.js +++ b/lib/commands/new_stage_region.js @@ -17,11 +17,17 @@ var JawsError = require('../jaws-error'), Promise.promisifyAll(fs); /** - * Run + * + * @param JAWS + * @param type 'stage'|'region' + * @param stage + * @param region Optional. Will be prompted for if omitted + * @param s3Bucket Optional. only valid for type of region + * @param noCf Optional + * @returns {*} */ - -module.exports.run = function(JAWS, type, stage, region, noCf) { - var command = new CMD(JAWS, type, stage, region, noCf); +module.exports.run = function(JAWS, type, stage, region, s3Bucket, noCf) { + var command = new CMD(JAWS, type, stage, region, s3Bucket, noCf); return command.run(); }; @@ -29,13 +35,19 @@ module.exports.run = function(JAWS, type, stage, region, noCf) { * Command Class * @constructor */ - -function CMD(JAWS, type, stage, region, noCf) { +function CMD(JAWS, type, stage, region, s3Bucket, noCf) { this._JAWS = JAWS; this._type = type; this._stage = stage; this._region = region; + this._s3Bucket = s3Bucket; this._noCf = noCf; + this._prompts = { + properties: {}, + }; + this.Prompter = JawsCli.prompt(); + this.Prompter.override = {}; + this._spinner = null; } /** @@ -53,9 +65,43 @@ CMD.prototype.run = Promise.method(function() { return _this._JAWS.validateProject() .bind(_this) .then(_this._promptRegion) + .then(function() { + if (_this._type == 'stage') { //if stage we now have region, so we know bucket + _this._s3Bucket = _this._JAWS._meta.projectJson.jawsBuckets[_this._region]; + } else { + return _this._promptJawsS3Bucket(); + } + }) .then(_this._validate) .then(_this._createEnvFile) - .then(_this._createCfStack) + .then(function() { + return utils.generateResourcesCf( + _this._JAWS._meta.projectRootPath, + _this._JAWS._meta.projectJson.name, + _this._stage, + _this._region, + '' + ); + }) + .then(function() { + if (_this._noCf) { + JawsCli.log('ENV var file created in s3. CloudFormation file can be run manually'); + JawsCli.log('!!MAKE SURE!! to create stack with name: ' + AWSUtils.cfGetResourcesStackName( + _this._stage, + _this._JAWS._meta.projectJson.name + )); + JawsCli.log('After creating CF stack, remember to put the IAM role outputs in your project jaws.json'); + return false; + } else { + return _this._createCfStack() + .then(function(cfData) { + if (_this._spinner) { + _this._spinner.stop(true); + } + return cfData; + }); + } + }) .then(_this._updateProjectJson); }); @@ -64,36 +110,76 @@ CMD.prototype.run = Promise.method(function() { */ CMD.prototype._promptRegion = Promise.method(function() { + var _this = this, + msg = 'Choose a region lambda supports: ', + validRegions = [ + 'us-east-1', + 'us-west-1', + 'us-west-2', + 'eu-west-1', + 'ap-northeast-1', + ]; - var _this = this; + if (_this._type == 'stage') { //must use existing region + validRegions = Object.keys(_this._JAWS._meta.projectJson.jawsBuckets); + if (validRegions.length == 1) { + _this._region = validRegions[0]; + return Promise.resolve(); + } + msg = 'Choose an existing project region: '; + } - // If region exists, skip - if (_this._region) return; - - var regions = [ - 'us-east-1', - 'us-west-1', - 'us-west-2', - 'eu-west-1', - 'ap-northeast-1', - ]; + if (validRegions.indexOf(_this._region) != -1) { //they specified region and its valid + return Promise.resolve(); + } // Create Choices var choices = []; - for (var i = 0; i < (regions.length + 1); i++) { + for (var i = 0; i < (validRegions.length + 1); i++) { choices.push({ key: (i + 1) + ') ', - value: regions[i], - label: regions[i], + value: validRegions[i], + label: validRegions[i], }); } - return JawsCli.select('Choose a region within this stage: ', choices, false) + return JawsCli.select(msg, choices, false) .then(function(results) { _this._region = [results[0].value]; }); }); +CMD.prototype._promptJawsS3Bucket = Promise.method(function() { + var _this = this; + if (_this._s3Bucket) return; + + //Don't ever auto-create s3 bucket as its against best practice, and this is not the quick start path + + // Prompt: s3 bucket - holds env vars for this project + _this.Prompter.override.s3Bucket = _this._s3Bucket; + _this._prompts.properties.s3Bucket = { + description: 'Enter EXISTING s3 bucket for this region. MUST be in ' + _this._region + ': '.yellow, + default: 'jaws.yourapp.com', + message: 'Bucket name must only contain lowercase letters, numbers, periods and dashes', + conform: function(bucket) { + var re = /^[a-z0-9-.]+$/; + return re.test(bucket); + }, + }; + + return new Promise(function(resolve, reject) { + _this.Prompter.get(_this._prompts, function(err, answers) { + if (err) { + reject(new JawsError(err)); + } + resolve(answers); + }); + }) + .then(function(answers) { + _this._s3Bucket = answers.s3Bucket; + }); +}); + /** * CMD: Validate */ @@ -122,7 +208,7 @@ CMD.prototype._validate = Promise.method(function() { // Make sure stage is not already defined in s3 env var - don't want to overwrite it var envCmd = require('./env'); - return envCmd.getEnvFileAsMap(_this._JAWS, _this._stage) + return envCmd.getEnvFileAsMap(_this._JAWS, _this._stage, _this._region) .then(function(envMap) { if (Object.keys(envMap).length > 0) { throw new JawsError('Stage "' + _this._stage + '" can not be created as an env var file already exists'); @@ -151,16 +237,13 @@ CMD.prototype._createEnvFile = Promise.method(function() { var _this = this; - // If type is not stage, skip this - if (_this._type !== 'stage') return; - var envFileContents = 'JAWS_STAGE=' + _this._stage + '\nJAWS_DATA_MODEL_PREFIX=' + _this._stage; return AWSUtils.putEnvFile( _this._JAWS._meta.profile, - _this._JAWS._meta.projectJson.envVarBucket.region, - _this._JAWS._meta.projectJson.envVarBucket.name, + _this._region, + _this._s3Bucket, _this._JAWS._meta.projectJson.name, _this._stage, envFileContents); @@ -174,14 +257,13 @@ CMD.prototype._createCfStack = Promise.method(function() { var _this = this; - // Start loading icon - var spinner = JawsCli.spinner( - 'Creating CloudFormation stack "' + _this._spinner = JawsCli.spinner( + 'Creating CloudFormation stack for stage: "' + _this._stage - + '" - "' + + '" and region: "' + _this._region - + '"'); - spinner.start(); + + '" (~5 mins)'); + _this._spinner.start(); return AWSUtils.cfCreateResourcesStack( _this._JAWS._meta.profile, @@ -193,12 +275,7 @@ CMD.prototype._createCfStack = Promise.method(function() { '' // TODO: read email out of existing jaws-cf.json? ) .then(function(cfData) { - return AWSUtils.monitorCf(cfData, _this._JAWS._meta.profile, _this._region, 'create') - .then(function(cfData) { - _this._cfData = cfData; - spinner.stop(true); - JawsCli.log('CloudFormation Stack "' + cfData.StackName + '" successfully created.'); - }); + return AWSUtils.monitorCf(cfData, _this._JAWS._meta.profile, _this._region, 'create'); }); }); @@ -206,21 +283,25 @@ CMD.prototype._createCfStack = Promise.method(function() { * CMD: Update Project JSON */ -CMD.prototype._updateProjectJson = Promise.method(function() { +CMD.prototype._updateProjectJson = Promise.method(function(cfData) { var _this = this; var regionObj = { region: _this._region, + iamRoleArnLambda: '', + iamRoleArnApiGateway: '', }; - for (var i = 0; i < _this._cfData.Outputs.length; i++) { - if (_this._cfData.Outputs[i].OutputKey === 'IamRoleArnLambda') { - regionObj.IamRoleArnLambda = _this._cfData.Outputs[i].OutputValue; - } + if (cfData) { + for (var i = 0; i < cfData.Outputs.length; i++) { + if (cfData.Outputs[i].OutputKey === 'IamRoleArnLambda') { + regionObj.IamRoleArnLambda = cfData.Outputs[i].OutputValue; + } - if (_this._cfData.Outputs[i].OutputKey === 'IamRoleArnApiGateway') { - regionObj.iamRoleArnApiGateway = _this._cfData.Outputs[i].OutputValue; + if (cfData.Outputs[i].OutputKey === 'IamRoleArnApiGateway') { + regionObj.iamRoleArnApiGateway = cfData.Outputs[i].OutputValue; + } } } diff --git a/lib/utils/index.js b/lib/utils/index.js index f52cdd8af..ac2fa88e1 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -20,7 +20,7 @@ Promise.promisifyAll(fs); * @param projectRootPath * @param type lambda|endpoint */ -module.exports.findAllAwsmPathsOfType = function(projectRootPath, type) { +exports.findAllAwsmPathsOfType = function(projectRootPath, type) { var _this = this, jawsJsonAttr; switch (type) { @@ -64,7 +64,7 @@ module.exports.findAllAwsmPathsOfType = function(projectRootPath, type) { * @param startDir * @returns {*} */ -module.exports.findProjectRootPath = function(startDir) { +exports.findProjectRootPath = function(startDir) { var _this = this; // Check if startDir is root @@ -99,7 +99,7 @@ module.exports.findProjectRootPath = function(startDir) { * @param promise */ -module.exports.execute = function(promise) { +exports.execute = function(promise) { promise .catch(JawsError, function(e) { console.error(e); @@ -118,7 +118,7 @@ module.exports.execute = function(promise) { * @param filter * @returns {Promise} */ -module.exports.readRecursively = function(path, filter) { +exports.readRecursively = function(path, filter) { return new Promise(function(resolve, reject) { var files = []; @@ -145,7 +145,7 @@ module.exports.readRecursively = function(path, filter) { * @param projectRootPath * @returns {Promise} list of full paths to awsm.json files that are type lambda */ -module.exports.findAllLambdas = function(projectRootPath) { +exports.findAllLambdas = function(projectRootPath) { return this.findAllAwsmPathsOfType(projectRootPath, 'lambda'); }; @@ -155,7 +155,7 @@ module.exports.findAllLambdas = function(projectRootPath) { * @param projectRootPath * @returns {Promise} list of full paths to awsm.json files that are type endpoint */ -module.exports.findAllEndpoints = function(projectRootPath) { +exports.findAllEndpoints = function(projectRootPath) { return this.findAllAwsmPathsOfType(projectRootPath, 'endpoint'); }; @@ -166,7 +166,7 @@ module.exports.findAllEndpoints = function(projectRootPath) { * @returns {Promise} list of full paths to awsm.json files */ -module.exports.findAllAwsmJsons = function(startPath) { +exports.findAllAwsmJsons = function(startPath) { return this.readRecursively(startPath, '*awsm.json') .then(function(jsonPaths) { return jsonPaths.map(function(jsonPath) { @@ -182,7 +182,7 @@ module.exports.findAllAwsmJsons = function(startPath) { * * @param str */ -module.exports.logIfVerbose = function(str) { +exports.logIfVerbose = function(str) { if (process.env.JAWS_VERBOSE) { console.log(str); } @@ -195,7 +195,7 @@ module.exports.logIfVerbose = function(str) { * @param contents node Buffer * @returns {Promise} */ -module.exports.writeFile = function(filePath, contents) { +exports.writeFile = function(filePath, contents) { return mkdirpAsync(path.dirname(filePath)) .then(function() { return fs.writeFileAsync(filePath, contents); @@ -208,7 +208,7 @@ module.exports.writeFile = function(filePath, contents) { * @returns {string} */ -module.exports.generateLambdaName = function(awsmJson) { +exports.generateLambdaName = function(awsmJson) { var handlerName = awsmJson.lambda.cloudFormation.Handler.replace('aws_modules', ''), resourceAction = handlerName.substr(0, handlerName.lastIndexOf('/')); @@ -226,7 +226,7 @@ module.exports.generateLambdaName = function(awsmJson) { * @param projectRootPath * @returns {Promise} list of functionName's */ -module.exports.getAllLambdaNames = function(projectRootPath) { +exports.getAllLambdaNames = function(projectRootPath) { var _this = this, lambdaNames = []; @@ -248,28 +248,28 @@ module.exports.getAllLambdaNames = function(projectRootPath) { * * @param projectJawsJson * @param stage - * @param region + * @param regionName * @returns {*} region object for stage */ -module.exports.getProjRegionConfigForStage = function(projectJawsJson, stage, region) { +exports.getProjRegionConfigForStage = function(projectJawsJson, stage, regionName) { var projectStageObj = projectJawsJson.stages[stage]; var region = projectStageObj.filter(function(regionObj) { - return regionObj.region == region; + return regionObj.region == regionName; }); if (!region || region.length == 0) { - throw new JawsError('Could not find region ' + region, JawsError.errorCodes.UNKNOWN); + throw new JawsError('Could not find region ' + regionName, JawsError.errorCodes.UNKNOWN); } if (region.length > 1) { - throw new JawsError('Multiple regions named ' + region, JawsError.errorCodes.UNKNOWN); + throw new JawsError('Multiple regions named ' + regionName, JawsError.errorCodes.UNKNOWN); } return region[0]; }; -module.exports.dirExistsSync = function(path) { +exports.dirExistsSync = function(path) { try { var stats = fs.lstatSync(path); return stats.isDirectory(); @@ -279,7 +279,7 @@ module.exports.dirExistsSync = function(path) { } }; -module.exports.fileExistsSync = function(path) { +exports.fileExistsSync = function(path) { try { var stats = fs.lstatSync(path); return stats.isFile(); @@ -289,7 +289,7 @@ module.exports.fileExistsSync = function(path) { } }; -module.exports.readAndParseJsonSync = function(path) { +exports.readAndParseJsonSync = function(path) { return JSON.parse(fs.readFileSync(path)); }; @@ -306,4 +306,19 @@ exports.npmInstall = function(dir) { } process.chdir(cwd); -}; \ No newline at end of file +}; + +exports.generateResourcesCf = function(projRootPath, projName, stage, region, notificationEmail) { + var cfTemplate = require('../templates/resources-cf'); + cfTemplate.Parameters.aaProjectName.Default = projName; + cfTemplate.Parameters.aaProjectName.AllowedValues = [projName]; + cfTemplate.Parameters.aaStage.Default = stage; + cfTemplate.Parameters.aaDataModelPrefix.Default = stage; //to simplify bootstrap use same stage + cfTemplate.Parameters.aaNotficationEmail.Default = notificationEmail; + cfTemplate.Description = projName + " resources"; + + return this.writeFile( + path.join(projRootPath, 'cloudformation', stage, region, 'resources-cf.json'), + JSON.stringify(cfTemplate, null, 2) + ); +}; diff --git a/package.json b/package.json index 9ddc20d16..2ecd5892b 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,6 @@ "insert-module-globals": "^6.5.2", "jaws-api-gateway-client": "0.11.0", "keypress": "^0.2.1", - "minimist": "^1.2.0", "mkdirp-then": "^1.1.0", "moment": "^2.10.6", "node-uuid": "^1.4.2", diff --git a/tests/cli/new_stage_region.js b/tests/cli/new_stage_region.js index f83ec7ea6..ab677abdf 100644 --- a/tests/cli/new_stage_region.js +++ b/tests/cli/new_stage_region.js @@ -73,7 +73,7 @@ describe('Test "new stage/region" command', function() { it('Create New region', function(done) { this.timeout(0); - CmdNewStageRegion.run(JAWS, 'region', tempStage, tempRegion2, false) + CmdNewStageRegion.run(JAWS, 'region', tempStage, tempRegion2, config.regionBucket, false) .then(function() { var jawsJson = require(path.join(process.cwd(), '../jaws.json')); var region = false; From c0c3ba5236d2ac77c7a64a06fa3981e685ce7bc0 Mon Sep 17 00:00:00 2001 From: doapp-ryanp Date: Mon, 21 Sep 2015 20:59:31 -0500 Subject: [PATCH 3/4] commonize lambda region values --- lib/commands/new_project.js | 25 ++++------ lib/commands/new_stage_region.js | 82 ++++++++++++++------------------ lib/utils/aws.js | 2 + lib/utils/cli.js | 11 +++-- 4 files changed, 52 insertions(+), 68 deletions(-) diff --git a/lib/commands/new_project.js b/lib/commands/new_project.js index b5b404f0c..edbff4719 100644 --- a/lib/commands/new_project.js +++ b/lib/commands/new_project.js @@ -195,17 +195,8 @@ CMD.prototype._prompt = Promise.method(function() { } // Show Prompts - return new Promise(function(resolve, reject) { - _this.Prompter.get(_this._prompts, function(err, answers) { - if (err) { - reject(new JawsError(err)); - } - resolve(answers); - }); - }) + return _this.Prompter.getAsync(_this._prompts) .then(function(answers) { - - // Set Answers _this._name = answers.name; _this._stage = answers.stage.toLowerCase(); _this._s3Bucket = answers.s3Bucket; @@ -217,12 +208,14 @@ CMD.prototype._prompt = Promise.method(function() { if (_this._region) return; // Prompt: region select - var choices = [ - {key: '', label: 'us-east-1', value: 'us-east-1'}, - {key: '', label: 'us-west-2', value: 'us-west-2'}, - {key: '', label: 'eu-west-1', value: 'eu-west-1'}, - {key: '', label: 'ap-northeast-1', value: 'ap-northeast-1'}, - ]; + var choices = []; + AWSUtils.validLambdaRegions.forEach(function(r) { + choices.push({ + key: '', + value: r, + label: r, + }); + }); return JawsCLI.select('Select a region for your project: ', choices, false) .then(function(results) { diff --git a/lib/commands/new_stage_region.js b/lib/commands/new_stage_region.js index 76894cd8e..ca0fe6b31 100644 --- a/lib/commands/new_stage_region.js +++ b/lib/commands/new_stage_region.js @@ -7,7 +7,7 @@ */ var JawsError = require('../jaws-error'), - JawsCli = require('../utils/cli'), + JawsCLI = require('../utils/cli'), Promise = require('bluebird'), fs = require('fs'), path = require('path'), @@ -42,11 +42,6 @@ function CMD(JAWS, type, stage, region, s3Bucket, noCf) { this._region = region; this._s3Bucket = s3Bucket; this._noCf = noCf; - this._prompts = { - properties: {}, - }; - this.Prompter = JawsCli.prompt(); - this.Prompter.override = {}; this._spinner = null; } @@ -59,8 +54,8 @@ CMD.prototype.run = Promise.method(function() { var _this = this; // Status - if (_this._type === 'stage') JawsCli.log('Creating new stage "' + _this._stage + '"...'); - if (_this._type === 'region') JawsCli.log('Creating new region within stage "' + _this._stage + '"...'); + if (_this._type === 'stage') JawsCLI.log('Creating new stage "' + _this._stage + '"...'); + if (_this._type === 'region') JawsCLI.log('Creating new region within stage "' + _this._stage + '"...'); return _this._JAWS.validateProject() .bind(_this) @@ -85,12 +80,12 @@ CMD.prototype.run = Promise.method(function() { }) .then(function() { if (_this._noCf) { - JawsCli.log('ENV var file created in s3. CloudFormation file can be run manually'); - JawsCli.log('!!MAKE SURE!! to create stack with name: ' + AWSUtils.cfGetResourcesStackName( + JawsCLI.log('ENV var file created in s3. CloudFormation file can be run manually'); + JawsCLI.log('!!MAKE SURE!! to create stack with name: ' + AWSUtils.cfGetResourcesStackName( _this._stage, _this._JAWS._meta.projectJson.name )); - JawsCli.log('After creating CF stack, remember to put the IAM role outputs in your project jaws.json'); + JawsCLI.log('After creating CF stack, remember to put the IAM role outputs in your project jaws.json'); return false; } else { return _this._createCfStack() @@ -112,13 +107,7 @@ CMD.prototype.run = Promise.method(function() { CMD.prototype._promptRegion = Promise.method(function() { var _this = this, msg = 'Choose a region lambda supports: ', - validRegions = [ - 'us-east-1', - 'us-west-1', - 'us-west-2', - 'eu-west-1', - 'ap-northeast-1', - ]; + validRegions = AWSUtils.validLambdaRegions; if (_this._type == 'stage') { //must use existing region validRegions = Object.keys(_this._JAWS._meta.projectJson.jawsBuckets); @@ -133,52 +122,51 @@ CMD.prototype._promptRegion = Promise.method(function() { return Promise.resolve(); } - // Create Choices var choices = []; - for (var i = 0; i < (validRegions.length + 1); i++) { + validRegions.forEach(function(r) { choices.push({ - key: (i + 1) + ') ', - value: validRegions[i], - label: validRegions[i], + key: '', + value: r, + label: r, }); - } + }); - return JawsCli.select(msg, choices, false) + return JawsCLI.select(msg, choices, false) .then(function(results) { _this._region = [results[0].value]; }); }); -CMD.prototype._promptJawsS3Bucket = Promise.method(function() { +CMD.prototype._promptJawsS3Bucket = function() { var _this = this; - if (_this._s3Bucket) return; //Don't ever auto-create s3 bucket as its against best practice, and this is not the quick start path + var Prompter = JawsCLI.prompt(), + prompts = { + properties: { + s3Bucket: { + description: 'Enter EXISTING s3 bucket for this region. Must be in THIS region: '.yellow, + required: true, + default: 'jaws-' + _this._region + '.yourapp.com', + message: 'Bucket name must only contain lowercase letters, numbers, periods and dashes', + conform: function(bucket) { + var re = /^[a-z0-9-.]+$/; + return re.test(bucket); + } + } + } + }; - // Prompt: s3 bucket - holds env vars for this project - _this.Prompter.override.s3Bucket = _this._s3Bucket; - _this._prompts.properties.s3Bucket = { - description: 'Enter EXISTING s3 bucket for this region. MUST be in ' + _this._region + ': '.yellow, - default: 'jaws.yourapp.com', - message: 'Bucket name must only contain lowercase letters, numbers, periods and dashes', - conform: function(bucket) { - var re = /^[a-z0-9-.]+$/; - return re.test(bucket); - }, + Prompter.override = { + s3Bucket: _this._s3Bucket, }; - return new Promise(function(resolve, reject) { - _this.Prompter.get(_this._prompts, function(err, answers) { - if (err) { - reject(new JawsError(err)); - } - resolve(answers); - }); - }) + return Prompter.getAsync(prompts) .then(function(answers) { _this._s3Bucket = answers.s3Bucket; + return _this._s3Bucket; }); -}); +}; /** * CMD: Validate @@ -257,7 +245,7 @@ CMD.prototype._createCfStack = Promise.method(function() { var _this = this; - _this._spinner = JawsCli.spinner( + _this._spinner = JawsCLI.spinner( 'Creating CloudFormation stack for stage: "' + _this._stage + '" and region: "' diff --git a/lib/utils/aws.js b/lib/utils/aws.js index c56761fa5..709cf5a95 100644 --- a/lib/utils/aws.js +++ b/lib/utils/aws.js @@ -15,6 +15,8 @@ var Promise = require('bluebird'), Promise.promisifyAll(fs); +exports.validLambdaRegions = ['us-east-1', 'us-west-2', 'eu-west-1', 'ap-northeast-1']; + /** * Set AWS SDK Creds and region from a given profile * diff --git a/lib/utils/cli.js b/lib/utils/cli.js index 0b95771c1..d3133afa7 100644 --- a/lib/utils/cli.js +++ b/lib/utils/cli.js @@ -16,11 +16,12 @@ var Promise = require('bluebird'), keypress = require('keypress'); Promise.promisifyAll(fs); +Promise.promisifyAll(prompt); /** * ASCII */ -module.exports.ascii = function() { +exports.ascii = function() { var art = ''; art = art + ' ____ _____ __ __ _________ ' + os.EOL; @@ -37,7 +38,7 @@ module.exports.ascii = function() { /** * Spinner */ -module.exports.spinner = function(message) { +exports.spinner = function(message) { var spinner = new Spinner('JAWS: ' + chalk.yellow('%s ' + message)); spinner.setSpinnerString('|/-\\'); return spinner; @@ -46,14 +47,14 @@ module.exports.spinner = function(message) { /** * Log */ -module.exports.log = function(message) { +exports.log = function(message) { console.log('JAWS: ' + chalk.yellow(message + ' ')); }; /** * Prompt */ -module.exports.prompt = function() { +exports.prompt = function() { prompt.start(); prompt.delimiter = ''; prompt.message = 'JAWS: '; @@ -159,7 +160,7 @@ Select._close = function(cb) { * @param doneLabel * @returns {Promise} */ -module.exports.select = function(message, choices, multi, doneLabel) { +exports.select = function(message, choices, multi, doneLabel) { // Set keypress listener, if not set if (!Select.state) { From d53e2abb72dac8e4a80f9b3c47bb11d0340ddf83 Mon Sep 17 00:00:00 2001 From: doapp-ryanp Date: Mon, 21 Sep 2015 21:30:08 -0500 Subject: [PATCH 4/4] unbind keyup when select is done --- lib/commands/new_stage_region.js | 3 ++- lib/utils/cli.js | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/lib/commands/new_stage_region.js b/lib/commands/new_stage_region.js index ca0fe6b31..d2b6b1b40 100644 --- a/lib/commands/new_stage_region.js +++ b/lib/commands/new_stage_region.js @@ -133,7 +133,8 @@ CMD.prototype._promptRegion = Promise.method(function() { return JawsCLI.select(msg, choices, false) .then(function(results) { - _this._region = [results[0].value]; + console.log('results', results[0].value); + _this._region = results[0].value; }); }); diff --git a/lib/utils/cli.js b/lib/utils/cli.js index d3133afa7..8f56c04ae 100644 --- a/lib/utils/cli.js +++ b/lib/utils/cli.js @@ -137,7 +137,7 @@ Select._clear = function() { // Private: Close Select._close = function(cb) { - + utils.logIfVerbose('Closing select listener'); var _this = this; process.stdin.pause(); @@ -167,8 +167,7 @@ exports.select = function(message, choices, multi, doneLabel) { keypress(process.stdin); - process.stdin.on('keypress', function(ch, key) { - + var keypressHandler = function(ch, key) { if (key && key.ctrl && key.name == 'c') { process.stdin.pause(); @@ -209,6 +208,7 @@ exports.select = function(message, choices, multi, doneLabel) { // Check if "done" option if (Select.state.choices[Select.state.index - 1].action && Select.state.choices[Select.state.index - 1].action.toLowerCase() === 'done') { + process.stdin.removeListener('keypress', keypressHandler); return Select._close(); } else { @@ -216,14 +216,16 @@ exports.select = function(message, choices, multi, doneLabel) { Select.state.choices[Select.state.index - 1].toggled = Select.state.choices[Select.state.index - 1].toggled ? false : true; if (!Select.state.multi) { + process.stdin.removeListener('keypress', keypressHandler); Select._close(); } else { return Select._render(); } } } - }); + }; + process.stdin.on('keypress', keypressHandler); process.stdin.setRawMode(true); }