From 69c5e53472b4e7bed798c4cb142b2bae514e0736 Mon Sep 17 00:00:00 2001 From: doapp-ryanp Date: Wed, 21 Oct 2015 15:49:30 -0500 Subject: [PATCH] refactor --- lib/Jaws.js | 31 +- lib/JawsPlugin.js | 2 +- lib/commands/DeployEndpoint.js | 8 +- lib/commands/DeployResources.js | 2 +- lib/commands/JawsEnv.js | 6 +- lib/commands/ModuleCreate.js | 4 +- lib/commands/ModuleInstall.js | 2 +- lib/commands/ProjectCreate.js | 553 ------------------------ lib/commands/StageRegion.js | 218 +++++----- lib/commands/dash.js | 2 +- lib/commands/postinstall.js | 8 +- lib/commands/tag.js | 4 +- lib/defaults/actions/AliasLambda.js | 64 +-- lib/defaults/actions/DeployLambda.js | 560 ++++++++++++------------- lib/defaults/actions/ModuleCreate.js | 358 ++++++++++++++++ lib/defaults/actions/ProjectCreate.js | 581 ++++++++++++-------------- lib/defaults/actions/VersionLambda.js | 72 ++-- lib/templates/jaws.json | 3 +- lib/templates/nodejs/package.json | 2 +- lib/templates/resources-cf.json | 22 +- lib/utils/aws.js | 241 ++++++----- lib/utils/cli.js | 65 +-- lib/utils/index.js | 140 ++++--- 23 files changed, 1351 insertions(+), 1597 deletions(-) delete mode 100644 lib/commands/ProjectCreate.js create mode 100644 lib/defaults/actions/ModuleCreate.js diff --git a/lib/Jaws.js b/lib/Jaws.js index cc4b67f86..173098a12 100644 --- a/lib/Jaws.js +++ b/lib/Jaws.js @@ -40,8 +40,8 @@ class Jaws { silent: silent, path: path.join(this._projectRootPath, 'admin.env'), }); - this._profile = process.env.JAWS_ADMIN_AWS_PROFILE; - this._credentials = AWSUtils.profilesGet(this._profile)[this._profile]; + this._awsProfile = process.env.JAWS_ADMIN_AWS_PROFILE; + this._credentials = AWSUtils.profilesGet(this._awsProfile)[this._awsProfile]; } // TODO: Remove these eventually, keeping them for reference... @@ -148,14 +148,14 @@ class Jaws { */ _executeQueue(queue) { return Promise.try(() => { - return queue; - }) - .each(function(p) { - return p(); - }) - .catch(function(error) { - throw new JawsError(error); - }); + return queue; + }) + .each(function(p) { + return p(); + }) + .catch(function(error) { + throw new JawsError(error); + }); } @@ -192,6 +192,17 @@ class Jaws { * @returns {Promise} */ command(argv) { + JawsUtils.jawsDebug('command argv', argv); + + if (argv.version) { + console.log(this._version); + return Promise.resolve(); + } + + if (argv.help) { + //TODO: spit out cli help + return Promise.resolve(); + } // Check if command context and action are defined if (!this.commands[argv._[0]] || !this.commands[argv._[0]][argv._[1]]) { diff --git a/lib/JawsPlugin.js b/lib/JawsPlugin.js index 711b9b637..b1dbb0e89 100644 --- a/lib/JawsPlugin.js +++ b/lib/JawsPlugin.js @@ -75,7 +75,7 @@ class JawsPlugin { //TODO: implement return Promise.reject(new JawsError('Not implemented', JawsError.errorCodes.UNKNOWN)); } else { - return Promise.reject(new JawsError('You must specify all necessary options when in a non-interactive mode', JawsError.errorCodes.UNKNOWN)); + return Promise.resolve(); //in non interactive mode. All options must be set programatically } } diff --git a/lib/commands/DeployEndpoint.js b/lib/commands/DeployEndpoint.js index 4b1e7c669..e4c320839 100644 --- a/lib/commands/DeployEndpoint.js +++ b/lib/commands/DeployEndpoint.js @@ -114,7 +114,7 @@ class ApiDeployer { let _this = this; return AWSUtils.cfGetLambdaResourceSummaries( - _this._JAWS._profile, + _this._JAWS._awsProfile, _this._regionJson.region, AWSUtils.cfGetLambdasStackName(_this._stage, _this._JAWS._projectJson.name) ) @@ -701,7 +701,7 @@ class ApiDeployer { let _this = this; return AWSUtils.lambdaGetPolicy( - _this._JAWS._meta.profile, + _this._JAWS._awsProfile, _this._regionJson.region, endpoint.apiGateway.apig.lambda.PhysicalResourceId) .then(function(data) { @@ -737,7 +737,7 @@ class ApiDeployer { if (!statement) return Promise.resolve(endpoint); return AWSUtils.lambdaRemovePermission( - _this._JAWS._meta.profile, + _this._JAWS._awsProfile, _this._regionJson.region, endpoint.apiGateway.apig.lambda.PhysicalResourceId, 'jaws-apigateway-access') @@ -792,7 +792,7 @@ class ApiDeployer { + endpoint.apiGateway.cloudFormation.Path; return AWSUtils.lambdaAddPermission( - _this._JAWS._meta.profile, + _this._JAWS._awsProfile, _this._regionJson.region, statement) .then(function(data) { diff --git a/lib/commands/DeployResources.js b/lib/commands/DeployResources.js index 737ef480e..d2b9a3b9e 100644 --- a/lib/commands/DeployResources.js +++ b/lib/commands/DeployResources.js @@ -185,7 +185,7 @@ const ResourceDeployer = class ResourceDeployer extends ProjectCmd { return _this._updateStack() .bind(_this) .then(function(cfData) { - return AWSUtils.monitorCf(cfData, _this._JAWS._meta.profile, _this._regionJson.region, 'update'); + return AWSUtils.monitorCf(cfData, _this._JAWS._awsProfile, _this._regionJson.region, 'update'); }) .then(function(data) { spinner.stop(true); diff --git a/lib/commands/JawsEnv.js b/lib/commands/JawsEnv.js index 55e9b9090..d5db95c95 100644 --- a/lib/commands/JawsEnv.js +++ b/lib/commands/JawsEnv.js @@ -32,7 +32,7 @@ let CMD = class JawsEnv extends ProjectCmd { let deferred; if (_this._stage == 'local') { - deferred = Promise.resolve(fs.readFileSync(path.join(_this._JAWS._meta.projectRootPath, '.env'))); + deferred = Promise.resolve(fs.readFileSync(path.join(_this._JAWS._projectRootPath, '.env'))); } else { deferred = _this._JAWS.getEnvFile(_this._region, _this._stage) .then(function(s3ObjData) { @@ -94,7 +94,7 @@ let CMD = class JawsEnv extends ProjectCmd { */ listEnv(showWhereUsed) { let _this = this, - projRootDir = _this._JAWS._meta.projectRootPath, + projRootDir = _this._JAWS._projectRootPath, stage = _this._stage; return utils.findAllAwsmJsons(path.join(projRootDir, 'aws_modules')) @@ -208,7 +208,7 @@ let CMD = class JawsEnv extends ProjectCmd { }); if (_this._stage == 'local') { - putEnvQ.push(utils.writeFile(path.join(_this._JAWS._meta.projectRootPath, '.env'), contents)); + putEnvQ.push(utils.writeFile(path.join(_this._JAWS._projectRootPath, '.env'), contents)); } else { putEnvQ.push(_this._JAWS.putEnvFile(mapForRegion.regionName, _this._stage, contents)); } diff --git a/lib/commands/ModuleCreate.js b/lib/commands/ModuleCreate.js index 973874a26..3066c8db5 100644 --- a/lib/commands/ModuleCreate.js +++ b/lib/commands/ModuleCreate.js @@ -88,7 +88,7 @@ const CMD = class ModuleCreate extends ProjectCmd { if (_this._module.pkgMgr == 'npm') { let modulePath = path.join( - _this._JAWS._meta.projectRootPath, //TOOD: make this CWD if not in a JAWS project + _this._JAWS._projectRootPath, //TOOD: make this CWD if not in a JAWS project 'node_modules', _this._module.name); let templatesPath = path.join(__dirname, '..', 'templates'); @@ -207,7 +207,7 @@ const CMD = class ModuleCreate extends ProjectCmd { let templatesPath = path.join(__dirname, '..', 'templates'); let actionTemplateJson = utils.readAndParseJsonSync(path.join(templatesPath, 'action.awsm.json')); let modulePath = path.join( - _this._JAWS._meta.projectRootPath, //TOOD: make this CWD if not in a JAWS project + _this._JAWS._projectRootPath, //TOOD: make this CWD if not in a JAWS project 'aws_modules', _this._module.name); let actionPath = path.join(modulePath, _this._module.action); diff --git a/lib/commands/ModuleInstall.js b/lib/commands/ModuleInstall.js index 73e2b58ce..3dc273a8e 100644 --- a/lib/commands/ModuleInstall.js +++ b/lib/commands/ModuleInstall.js @@ -136,7 +136,7 @@ const CMD = class ModuleCreate extends ProjectCmd { _installFiles(tempDirPath) { let _this = this, srcAwsmJsonPath = path.join(tempDirPath, 'awsm.json'), - awsModsPath = path.join(_this._JAWS._meta.projectRootPath, 'aws_modules'); + awsModsPath = path.join(_this._JAWS._projectRootPath, 'aws_modules'); if (!utils.fileExistsSync(srcAwsmJsonPath)) { return Promise.reject(new JawsError('Module missing awsm.json file in root of project', JawsError.errorCodes.UNKNOWN)); diff --git a/lib/commands/ProjectCreate.js b/lib/commands/ProjectCreate.js deleted file mode 100644 index 51d4dd42c..000000000 --- a/lib/commands/ProjectCreate.js +++ /dev/null @@ -1,553 +0,0 @@ -'use strict'; - -/** - * JAWS Command: project create - * - Asks the user for information about their new JAWS project - * - Creates a new project in the current working directory - * - Creates IAM resources via CloudFormation - */ - -const GlobalCmd = require('./GlobalCmd'), - JawsError = require('../jaws-error'), - JawsCLI = require('../utils/cli'), - Promise = require('bluebird'), - path = require('path'), - os = require('os'), - AWSUtils = require('../utils/aws'), - utils = require('../utils'), - shortid = require('shortid'); - -let fs = require('fs'); -Promise.promisifyAll(fs); - -function generateShortId(maxLen) { - return shortid.generate().replace(/\W+/g, '').substring(0, maxLen).replace(/[_-]/g, ''); -} - -/** - * CMD Class - * @param name - * @param stage - * @param domain - * @param notificationEmail - * @param region - * @param profile - * @param noCf - * @param runtime - * @constructor - */ - -const CMD = class ProjectCreate extends GlobalCmd { - constructor(name, stage, region, domain, notificationEmail, profile, noCf, runtime) { - super(); - - // Defaults - this._name = name ? name : null; - this._domain = domain ? domain : null; - this._stage = stage ? stage.toLowerCase().replace(/\W+/g, '').substring(0, 15) : null; - this._notificationEmail = notificationEmail; - this._region = region; - this._profile = profile; - this._runtime = runtime ? runtime : 'nodejs'; - this._noCf = noCf; - this._prompts = { - properties: {}, - }; - this.Prompter = JawsCLI.prompt(); - this.Prompter.override = {}; - this._spinner = null; - - } - - /** - * Run - * @returns {*} - */ - - run() { - let _this = this; - - return Promise.try(function() { - - // ASCII Greeting - JawsCLI.ascii(); - - }) - .bind(_this) - .then(_this._prompt) - .then(_this._prepareProjectData) - .then(_this._createProjectDirectory) - .then(function() { - if (_this._noCf) { - let stackResourcesName = AWSUtils.cfGetResourcesStackName( - _this._stage, - _this._name - ); - - JawsCLI.log('Remember to run CloudFormation manually'); - JawsCLI.log(`MAKE SURE to create stack with name: ${stackResourcesName}`); - JawsCLI.log('After creating CF stack, remember to put the IAM role outputs and jawsBucket in your ' - + 'project jaws.json in the right stage/region.'); - return false; - } else { - return _this._createCfStack() - .bind(_this) - .then(function(cfData) { - if (_this._spinner) { - _this._spinner.stop(true); - } - _this._cfData = cfData; - }) - .then(_this._putEnvFile) - .then(_this._putCfFile); - } - }) - .then(_this._createProjectJson) - .then(_this._initRuntime) - .then(function() { - JawsCLI.log('Your project "' + _this._name - + '" has been successfully created in the current directory.'); - }); - } - - - /** - * Prompt - * @private - */ - - _prompt() { - - utils.jawsDebug('Prompting for new project information'); - let _this = this; - - let nameDescription = 'Enter a project name: '; - - // Prompt: name (project name) - _this.Prompter.override.name = _this._name; - _this._prompts.properties.name = { - description: nameDescription.yellow, - default: 'jaws-' + generateShortId(19), - message: 'Name must be only letters, numbers or dashes', - conform: function(name) { - let re = /^[a-zA-Z0-9-_]+$/; - return re.test(name); - }, - }; - - // Prompt: domain - for AWS hosted zone and more - _this.Prompter.override.domain = _this._domain; - - let domainDescription = 'Enter a project domain (You can change this at any time: '; - - _this._prompts.properties.domain = { - description: domainDescription.yellow, - default: 'myapp.com', - message: 'Domain must only contain lowercase letters, numbers, periods and dashes', - conform: function(bucket) { - let re = /^[a-z0-9-.]+$/; - return re.test(bucket); - }, - }; - - // Prompt: notification email - for AWS alerts - _this.Prompter.override.notificationEmail = _this._notificationEmail; - _this._prompts.properties.notificationEmail = { - description: 'Enter an email to use for AWS alarms: '.yellow, - required: true, - message: 'Please enter a valid email', - default: 'me@myapp.com', - conform: function(email) { - if (!email) return false; - return true; - }, - }; - - // Prompt: stage - _this.Prompter.override.stage = _this._stage; - - let stageDescription = 'Enter a stage for this project: '; - - _this._prompts.properties.stage = { - description: stageDescription.yellow, - default: 'dev', - message: 'Stage must be letters only', - conform: function(stage) { - let re = /^[a-zA-Z]+$/; - return re.test(stage); - }, - }; - - // Prompt: notification email - for AWS alerts - _this.Prompter.override.notificationEmail = _this._notificationEmail; - - let notificationEmailDescription = 'Enter an email to use for AWS alarms: '; - - _this._prompts.properties.notificationEmail = { - description: notificationEmailDescription.yellow, - required: true, - message: 'Please enter a valid email', - default: 'you@yourapp.com', - conform: function(email) { - if (!email) return false; - return true; - }, - }; - - // Prompt: API Keys - Create an AWS profile by entering API keys - if (!utils.fileExistsSync(path.join(AWSUtils.getConfigDir(), 'credentials'))) { - - _this.Prompter.override.awsAdminKeyId = _this._awsAdminKeyId; - - let apiKeyDescription = 'Enter the ACCESS KEY ID for your Admin AWS IAM User: '; - - _this._prompts.properties.awsAdminKeyId = { - description: apiKeyDescription.yellow, - required: true, - message: 'Please enter a valid access key ID', - conform: function(key) { - if (!key) return false; - return true; - }, - }; - _this.Prompter.override.awsAdminSecretKey = _this._awsAdminSecretKey; - - let apiSecretDescription = 'Enter the SECRET ACCESS KEY for your Admin AWS IAM User: '; - - _this._prompts.properties.awsAdminSecretKey = { - description: apiSecretDescription.yellow, - required: true, - message: 'Please enter a valid secret access key', - conform: function(key) { - if (!key) return false; - return true; - }, - }; - } - - // Show Prompts - return _this.Prompter.getAsync(_this._prompts) - .then(function(answers) { - _this._name = answers.name; - _this._domain = answers.domain; - _this._stage = answers.stage.toLowerCase(); - _this._notificationEmail = answers.notificationEmail; - _this._awsAdminKeyId = answers.awsAdminKeyId; - _this._awsAdminSecretKey = answers.awsAdminSecretKey; - - // If region exists, skip select prompt - if (_this._region) return; - - // Prompt: region select - let 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) { - _this._region = results[0].value; - }); - }) - .then(function() { - - // If profile exists, skip select prompt - if (_this._profile) return Promise.resolve(); - - // If aws credentials were passed, skip select prompt - if (_this._awsAdminKeyId && _this._awsAdminSecretKey) return Promise.resolve(); - - // Prompt: profile select - let profilesList = AWSUtils.profilesMap(), - profiles = Object.keys(profilesList), - choices = []; - - for (let i = 0; i < profiles.length; i++) { - choices.push({ - key: '', - value: profiles[i], - label: profiles[i], - }); - } - - return JawsCLI.select('Select an AWS profile for your project: ', choices, false) - .then(function(results) { - _this._profile = results[0].value; - }); - }); - } - - /** - * Prepare Project Data - * @private - */ - - _prepareProjectData() { - - let _this = this; - - // Validate: Ensure stage isn't "local" - if (_this._stage.toLowerCase() == 'local') { - throw new JawsError('Stage ' + _this._stage + ' is reserved'); - } - - // Validate: AWS only allows Alphanumeric and - in name - let nameOk = /^([a-zA-Z0-9-]+)$/.exec(_this._name); - if (!nameOk) { - throw new JawsError('Project names can only be alphanumeric and -'); - } - - // Append unique id if name is in use - if (utils.dirExistsSync(path.join(process.cwd(), _this._name))) { - _this._name = _this._name + '-' + generateShortId(19); - } - - // Append unique id if domain is default - if (_this._domain === 'myapp.com') { - _this._domain = 'myapp-' + generateShortId(8) + '.com'; - } - - // Set JAWS Bucket - _this._jawsBucket = utils.generateJawsBucketName(_this._stage, _this._region, _this._domain); - - // Validate: If no profile, ensure access keys, create profile - if (!_this._profile) { - - if (!_this._awsAdminKeyId) { - throw new JawsError( - 'An AWS Access Key ID is required', - JawsError.errorCodes.MISSING_AWS_CREDS); - } - - if (!_this._awsAdminSecretKey) { - throw new JawsError( - 'An AWS Secret Key is required', - JawsError.errorCodes.MISSING_AWS_CREDS); - } - - // Set profile - AWSUtils.profilesSet('default', _this._region, _this._awsAdminKeyId, _this._awsAdminSecretKey); - _this._profile = 'default'; - } - } - - - /** - * Create Project Directory - * @private - */ - - _createProjectDirectory() { - let _this = this; - - _this._projectRootPath = path.resolve(path.join(path.dirname('.'), _this._name)); - - // Prepare admin.env - let adminEnv = 'JAWS_ADMIN_AWS_PROFILE=' + _this._profile + os.EOL; - - // Prepare README.md - let readme = '#' + _this._name; - - // Create Project Scaffolding - return utils.writeFile( - path.join(_this._projectRootPath, '.env'), - 'JAWS_STAGE=' + _this._stage - + '\nJAWS_DATA_MODEL_STAGE=' + _this._stage - ) - .then(function() { - return Promise.all([ - fs.mkdirAsync(path.join(_this._projectRootPath, 'tests')), - fs.mkdirAsync(path.join(_this._projectRootPath, 'lib')), - fs.mkdirAsync(path.join(_this._projectRootPath, 'aws_modules')), - utils.writeFile(path.join(_this._projectRootPath, 'admin.env'), adminEnv), - utils.writeFile(path.join(_this._projectRootPath, 'README.md'), readme), - utils.generateResourcesCf( - _this._projectRootPath, - _this._name, - _this._domain, - _this._stage, - _this._region, - _this._notificationEmail, - _this._jawsBucket - ), - fs.writeFileAsync(path.join(_this._projectRootPath, '.gitignore'), fs.readFileSync(__dirname + '/../templates/gitignore')), - ]); - }); - } - - - /** - * Put ENV File - * - Creates ENV file in JAWS stage/region bucket - * @private - */ - - _putEnvFile() { - - let _this = this, - stage = this.stage; - - let envFileContents = `JAWS_STAGE=${stage} - JAWS_DATA_MODEL_STAGE=${stage}`; - - return AWSUtils.putEnvFile( - _this._profile, - _this._region, - _this._jawsBucket, - _this._name, - _this._stage, - envFileContents); - } - - - /** - * Put CF File - * @returns {*} - * @private - */ - - _putCfFile() { - - let _this = this; - - return AWSUtils.putCfFile( - _this._profile, - _this._projectRootPath, - _this._region, - _this._jawsBucket, - _this._name, - _this._stage, - 'resources'); - - } - - /** - * Create CloudFormation Stack - * @private - */ - - _createCfStack() { - - let _this = this; - - JawsCLI.log('Creating CloudFormation Stack for your new project (~5 mins)...'); - _this._spinner = JawsCLI.spinner(); - _this._spinner.start(); - - // Create CF stack - return AWSUtils.cfCreateResourcesStack( - _this._profile, - _this._region, - _this._projectRootPath, - _this._name, - _this._stage, - _this._domain, - _this._notificationEmail, - _this._jawsBucket - ) - .then(function(cfData) { - return AWSUtils.monitorCf(cfData, _this._profile, _this._region, 'create'); - }); - } - - /** - * Create Project JSON - * @private - */ - - _createProjectJson() { - - let _this = this, - iamRoleArnLambda, - iamRoleArnApiGateway; - - if (_this._cfData) { - for (let i = 0; i < _this._cfData.Outputs.length; i++) { - if (_this._cfData.Outputs[i].OutputKey === 'IamRoleArnLambda') { - iamRoleArnLambda = _this._cfData.Outputs[i].OutputValue; - } - - if (_this._cfData.Outputs[i].OutputKey === 'IamRoleArnApiGateway') { - iamRoleArnApiGateway = _this._cfData.Outputs[i].OutputValue; - } - } - } - - let templatesPath = path.join(__dirname, '..', 'templates'), - jawsJson = utils.readAndParseJsonSync(path.join(templatesPath, 'jaws.json')); - - jawsJson.stages[_this._stage] = [{ - region: _this._region, - iamRoleArnLambda: iamRoleArnLambda || '', - iamRoleArnApiGateway: iamRoleArnApiGateway || '', - jawsBucket: _this._jawsBucket, - }]; - - jawsJson.name = _this._name; - jawsJson.domain = _this._domain; - - fs.writeFileSync(path.join(_this._projectRootPath, 'jaws.json'), - JSON.stringify(jawsJson, null, 2)); - - return jawsJson; - } - - /** - * Init Runtime - * @private - */ - - _initRuntime() { - - let _this = this; - - JawsCLI.log('Preparing your runtime and installing jaws-core module...'); - - if (_this._runtime === 'nodejs') { - let packageJsonTemplate = utils.readAndParseJsonSync(path.join(__dirname, '..', 'templates', 'nodejs', 'package.json')); - packageJsonTemplate.name = _this._name; - return fs.writeFileAsync(path.join(_this._projectRootPath, 'package.json'), JSON.stringify(packageJsonTemplate, null, 2)) - .then(function() { - utils.jawsDebug('test_utils', 'Running NPM install...'); - utils.npmInstall(_this._projectRootPath); - }); - } else { - throw new JawsError('Unsupported runtime "' + _this.runtime + '"', JawsError.errorCodes.UNKNOWN); - } - } -}; - -/************************************** - * EXPORTS - **************************************/ - -/** - * Run - * @param name - * @param stage - * @param domain - * @param region - * @param notificationEmail - * @param profile - * @param noCf - * @param runtime defaults to 'nodejs' - * @returns {Promise} - */ - -exports.run = function(name, stage, region, domain, notificationEmail, profile, noCf, runtime) { - utils.jawsDebug('Running new project:', name); - let command = new CMD( - name, - stage, - region, - domain, - notificationEmail, - profile, - noCf, - runtime); - return command.run(); -}; diff --git a/lib/commands/StageRegion.js b/lib/commands/StageRegion.js index e624bf1bd..3bd7e5837 100644 --- a/lib/commands/StageRegion.js +++ b/lib/commands/StageRegion.js @@ -7,13 +7,13 @@ */ const ProjectCmd = require('./ProjectCmd.js'), - JawsError = require('../jaws-error'), - JawsCLI = require('../utils/cli'), - Promise = require('bluebird'), - os = require('os'), - path = require('path'), - AWSUtils = require('../utils/aws'), - utils = require('../utils'); + JawsError = require('../jaws-error'), + JawsCLI = require('../utils/cli'), + Promise = require('bluebird'), + os = require('os'), + path = require('path'), + AWSUtils = require('../utils/aws'), + utils = require('../utils'); let fs = require('fs'); @@ -22,10 +22,10 @@ Promise.promisifyAll(fs); const CMD = class StageRegion extends ProjectCmd { constructor(JAWS, type, stage, region, noCf) { super(JAWS); - this._type = type; - this._stage = stage; - this._region = region; - this._noCf = noCf; + this._type = type; + this._stage = stage; + this._region = region; + this._noCf = noCf; this._spinner = null; this._prompts = { properties: {}, @@ -41,53 +41,55 @@ const CMD = class StageRegion extends ProjectCmd { let _this = this; return _this._JAWS.validateProject() - .bind(_this) - .then(function() { - // Report Status - if (_this._type === 'stage') JawsCLI.log('Creating new stage...'); - if (_this._type === 'region') JawsCLI.log('Creating new region...'); - }) - .then(_this._promptStage) - .then(_this._promptRegion) - .then(_this._validate) - .then(function() { - return utils.generateResourcesCf( - _this._JAWS._meta.projectRootPath, - _this._JAWS._meta.projectJson.name, - _this._JAWS._meta.projectJson.domain, - _this._stage, - _this._region, - '', - _this._jawsBucket + .bind(_this) + .then(function() { + // Report Status + if (_this._type === 'stage') JawsCLI.log('Creating new stage...'); + if (_this._type === 'region') JawsCLI.log('Creating new region...'); + }) + .then(_this._promptStage) + .then(_this._promptRegion) + .then(_this._validate) + .then(function() { + return utils.generateResourcesCf( + _this._JAWS._projectRootPath, + _this._JAWS._meta.projectJson.name, + _this._JAWS._meta.projectJson.domain, + _this._stage, + _this._region, + '' + ); + }) + .then(() => { + return AWSUtils.createBucket(_this.Jaws._awsProfile, _this._region, _this.Jaws.getJawsBucket(_this._region, _this._stage)); + }) + .then(function() { + + if (_this._noCf) { + let stackName = AWSUtils.cfGetResourcesStackName( + _this._stage, + _this._JAWS._meta.projectJson.name ); - }) - .then(function() { - if (_this._noCf) { - let stackName = AWSUtils.cfGetResourcesStackName( - _this._stage, - _this._JAWS._meta.projectJson.name - ); - - JawsCLI.log('Remember to run CloudFormation manually'); - JawsCLI.log(`!!MAKE SURE!! to create stack with name: ${stackName}`); - JawsCLI.log('After creating CF stack, remember to put the IAM role outputs and jawsBucket in your ' - + 'project jaws.json in the right stage/region.'); - return false; - } else { - return _this._createCfStack() - .bind(_this) - .then(function(cfData) { - if (_this._spinner) { - _this._spinner.stop(true); - } - _this._cfData = cfData; - }) - .then(_this._putEnvFile) - .then(_this._putCfFile); - } - }) - .then(_this._updateProjectJson); + JawsCLI.log('Remember to run CloudFormation manually'); + JawsCLI.log(`!!MAKE SURE!! to create stack with name: ${stackName}`); + JawsCLI.log('After creating CF stack, remember to put the IAM role outputs and jawsBucket in your ' + + 'project jaws.json in the right stage/region.'); + return false; + } else { + return _this._createCfStack() + .bind(_this) + .then(function(cfData) { + if (_this._spinner) { + _this._spinner.stop(true); + } + _this._cfData = cfData; + }) + .then(_this._putEnvFile) + .then(_this._putCfFile); + } + }) + .then(_this._updateProjectJson); } _promptStage() { @@ -99,14 +101,14 @@ const CMD = class StageRegion extends ProjectCmd { if (_this._type === 'stage') { // User is creating a stage - _this.Prompter = JawsCLI.prompt(); + _this.Prompter = JawsCLI.prompt(); let stageDescription = `Enter the name of the stage to be created: \n`; _this._prompts.properties.stage = { description: stageDescription.yellow, - default: 'dev', - message: 'Stage must be letters only', - conform: function(stage) { + default: 'dev', + message: 'Stage must be letters only', + conform: function(stage) { let re = /^[a-zA-Z]+$/; return re.test(stage); }, @@ -114,9 +116,9 @@ const CMD = class StageRegion extends ProjectCmd { // Show Prompt return _this.Prompter.getAsync(_this._prompts) - .then(function(answers) { - _this._stage = answers.stage; - }); + .then(function(answers) { + _this._stage = answers.stage; + }); } else { @@ -138,22 +140,22 @@ const CMD = class StageRegion extends ProjectCmd { let choices = []; for (let i = 0; i < stages.length; i++) { choices.push({ - key: (i + 1) + ') ', + key: (i + 1) + ') ', value: stages[i], label: stages[i], }); } return JawsCLI.select('Which stage are you creating a region for: ', choices, false) - .then(function(results) { - _this._stage = results[0].value; - }); + .then(function(results) { + _this._stage = results[0].value; + }); } } _promptRegion() { - let _this = this, + let _this = this, validRegions = AWSUtils.validLambdaRegions; if (validRegions.indexOf(_this._region) != -1) { //they specified region and its valid @@ -164,7 +166,7 @@ const CMD = class StageRegion extends ProjectCmd { msg; validRegions.forEach(function(r) { choices.push({ - key: '', + key: '', value: r, label: r, }); @@ -178,9 +180,9 @@ const CMD = class StageRegion extends ProjectCmd { } return JawsCLI.select(msg, choices, false) - .then(function(results) { - _this._region = results[0].value; - }); + .then(function(results) { + _this._region = results[0].value; + }); } _validate() { @@ -227,8 +229,8 @@ const CMD = class StageRegion extends ProjectCmd { // Make sure region is not already defined if (_this._JAWS._meta.projectJson.stages[_this._stage].some(function(r) { - return r.region == _this._region; - })) { + return r.region == _this._region; + })) { return Promise.reject(new JawsError('Region "' + _this._region + '" is already defined in the stage "' + _this._stage + '"')); } } @@ -241,27 +243,27 @@ const CMD = class StageRegion extends ProjectCmd { let _this = this; JawsCLI.log('Creating CloudFormation stack for stage: "' - + _this._stage - + '" and region: "' - + _this._region - + '" (~5 mins)'); + + _this._stage + + '" and region: "' + + _this._region + + '" (~5 mins)'); _this._spinner = JawsCLI.spinner(); _this._spinner.start(); // Create CF stack return AWSUtils.cfCreateResourcesStack( - _this._JAWS._meta.profile, - _this._region, - _this._JAWS._meta.projectRootPath, - _this._JAWS._meta.projectJson.name, - _this._stage, - _this._domain, - '', // TODO: read email out of existing jaws-cf.json? - _this._jawsBucket - ) - .then(function(cfData) { - return AWSUtils.monitorCf(cfData, _this._JAWS._meta.profile, _this._region, 'create'); - }); + _this._JAWS._awsProfile, + _this._region, + _this._JAWS._projectRootPath, + _this._JAWS._meta.projectJson.name, + _this._stage, + _this._domain, + '', // TODO: read email out of existing jaws-cf.json? + _this._jawsBucket + ) + .then(function(cfData) { + return AWSUtils.monitorCf(cfData, _this._JAWS._awsProfile, _this._region, 'create'); + }); } @@ -279,12 +281,12 @@ const CMD = class StageRegion extends ProjectCmd { JAWS_DATA_MODEL_STAGE=${stage}`; return AWSUtils.putEnvFile( - _this._JAWS._meta.profile, - _this._region, - _this._jawsBucket, - _this._JAWS._meta.projectJson.name, - _this._stage, - envFileContents); + _this._JAWS._awsProfile, + _this._region, + _this._jawsBucket, + _this._JAWS._meta.projectJson.name, + _this._stage, + envFileContents); } /** @@ -297,13 +299,13 @@ JAWS_DATA_MODEL_STAGE=${stage}`; let _this = this; return AWSUtils.putCfFile( - _this._JAWS._meta.profile, - _this._JAWS._meta.projectRootPath, - _this._region, - _this._jawsBucket, - _this._JAWS._meta.projectJson.name, - _this._stage, - 'resources'); + _this._JAWS._awsProfile, + _this._JAWS._projectRootPath, + _this._region, + _this._jawsBucket, + _this._JAWS._meta.projectJson.name, + _this._stage, + 'resources'); } @@ -316,10 +318,10 @@ JAWS_DATA_MODEL_STAGE=${stage}`; let _this = this; let regionObj = { - region: _this._region, - iamRoleArnLambda: '', + region: _this._region, + iamRoleArnLambda: '', iamRoleArnApiGateway: '', - jawsBucket: _this._jawsBucket, + jawsBucket: _this._jawsBucket, }; if (_this._cfData) { @@ -341,8 +343,8 @@ JAWS_DATA_MODEL_STAGE=${stage}`; } return utils.writeFile( - path.join(_this._JAWS._meta.projectRootPath, 'jaws.json'), - JSON.stringify(_this._JAWS._meta.projectJson, null, 2)); + path.join(_this._JAWS._projectRootPath, 'jaws.json'), + JSON.stringify(_this._JAWS._meta.projectJson, null, 2)); } }; diff --git a/lib/commands/dash.js b/lib/commands/dash.js index f33e16f7c..cf92c6a31 100644 --- a/lib/commands/dash.js +++ b/lib/commands/dash.js @@ -144,7 +144,7 @@ CMD.prototype.run = Promise.method(function() { CMD.prototype._prepareResources = Promise.method(function() { let _this = this; - return JawsUtils.findAllAwsmJsons(path.join(_this._JAWS._meta.projectRootPath, 'aws_modules')) + return JawsUtils.findAllAwsmJsons(path.join(_this._JAWS._projectRootPath, 'aws_modules')) .then(function(jsonPaths) { let hybrids = []; diff --git a/lib/commands/postinstall.js b/lib/commands/postinstall.js index f1710b510..395fb0717 100644 --- a/lib/commands/postinstall.js +++ b/lib/commands/postinstall.js @@ -76,7 +76,7 @@ const CMD = class Postinstall extends ProjectCmd { return Promise.all(deferredDepInstalls); }) .then(function() { - return utils.findAllEnvletsForAwsm(_this._JAWS._meta.projectRootPath, _this._moduleName); + return utils.findAllEnvletsForAwsm(_this._JAWS._projectRootPath, _this._moduleName); }) .then(function(envlets) { JawsCLI.log('Successfully installed ' + _this._moduleName); @@ -104,9 +104,9 @@ const CMD = class Postinstall extends ProjectCmd { pkgMgrDir = 'node_modules'; } - let srcAwsmPath = path.join(_this._JAWS._meta.projectRootPath, pkgMgrDir, _this._moduleName, 'awsm'), - srcAwsmJsonPath = path.join(_this._JAWS._meta.projectRootPath, pkgMgrDir, _this._moduleName, 'awsm.json'), - awsModsPath = path.join(_this._JAWS._meta.projectRootPath, 'aws_modules'); + let srcAwsmPath = path.join(_this._JAWS._projectRootPath, pkgMgrDir, _this._moduleName, 'awsm'), + srcAwsmJsonPath = path.join(_this._JAWS._projectRootPath, pkgMgrDir, _this._moduleName, 'awsm.json'), + awsModsPath = path.join(_this._JAWS._projectRootPath, 'aws_modules'); if (!utils.fileExistsSync(srcAwsmJsonPath)) { return Promise.reject(new JawsError('Module missing awsm.json file in root of project', JawsError.errorCodes.UNKNOWN)); diff --git a/lib/commands/tag.js b/lib/commands/tag.js index 6df011131..5d167bccf 100644 --- a/lib/commands/tag.js +++ b/lib/commands/tag.js @@ -66,7 +66,7 @@ const CMD = class Tag extends ProjectCmd { return this._JAWS.validateProject() .then(function() { - return utils[findAllFunc](path.join(_this._JAWS._meta.projectRootPath, 'aws_modules')); + return utils[findAllFunc](path.join(_this._JAWS._projectRootPath, 'aws_modules')); }) .then(function(awsmJsonPaths) { let tagQueue = []; @@ -95,7 +95,7 @@ const CMD = class Tag extends ProjectCmd { return this._JAWS.validateProject() .then(() => { - return utils[findAllFunc](path.join(_this._JAWS._meta.projectRootPath, 'aws_modules')); + return utils[findAllFunc](path.join(_this._JAWS._projectRootPath, 'aws_modules')); }) .then(function(lAwsmJsonPaths) { diff --git a/lib/defaults/actions/AliasLambda.js b/lib/defaults/actions/AliasLambda.js index 783c9d0e0..b96503b20 100644 --- a/lib/defaults/actions/AliasLambda.js +++ b/lib/defaults/actions/AliasLambda.js @@ -89,28 +89,28 @@ usage: jaws lambda alias `, JawsUtils.jawsDebug(`Preparing to version ${version} to alias ${aliasName} for stage:`, _this._stage); return this._JAWS.validateProject() - .bind(_this) - .then(_this._promptStage) - .then(_this._setLambdaLogicalIds) - .then(_this._getRegions) - .each(region => { - //1) For each region, get all the lambdas for stack - let lStackName = AWSUtils.cfGetLambdasStackName(_this._stage, _this._JAWS._projectJson.name); - return AWSUtils.cfGetLambdaResourceSummaries(_this.Jaws._profile, region, lStackName) - .then(lambdaSummaries => { - //2) identify physical function names from logical - return AWSUtils.cfGetLambdaPhysicalsFromLogicals(_this._lambdaLogicalIdsToAlias, lambdaSummaries); - }) - .then(lambdaNamesToAlias => { - //3) create alias (should only be one lambda name, as we only support aliasing one lambda at a time - return AWSUtils.lambdaCreateAlias(_this.Jaws._profile, region, lambdaNamesToAlias[0], version, aliasName); - }); - }) - .then(aliasedLambda => { - JawsCLI.log('Lambda Alias: Successfully aliased lambda in requested regions:'); - JawsCLI.log(aliasedLambda); - return aliasedLambda; - }); + .bind(_this) + .then(_this._promptStage) + .then(_this._setLambdaLogicalIds) + .then(_this._getRegions) + .each(region => { + //1) For each region, get all the lambdas for stack + let lStackName = AWSUtils.cfGetLambdasStackName(_this._stage, _this.Jaws._projectJson.name); + return AWSUtils.cfGetLambdaResourceSummaries(_this.Jaws._awsProfile, region, lStackName) + .then(lambdaSummaries => { + //2) identify physical function names from logical + return AWSUtils.cfGetLambdaPhysicalsFromLogicals(_this._lambdaLogicalIdsToAlias, lambdaSummaries); + }) + .then(lambdaNamesToAlias => { + //3) create alias (should only be one lambda name, as we only support aliasing one lambda at a time + return AWSUtils.lambdaCreateAlias(_this.Jaws._awsProfile, region, lambdaNamesToAlias[0], version, aliasName); + }); + }) + .then(aliasedLambda => { + JawsCLI.log('Lambda Alias: Successfully aliased lambda in requested regions:'); + JawsCLI.log(aliasedLambda); + return aliasedLambda; + }); } /** @@ -124,7 +124,7 @@ usage: jaws lambda alias `, // If stage exists, skip if (!this._stage) { - stages = Object.keys(_this.JAWS._projectJson.stages); + stages = Object.keys(_this.Jaws._projectJson.stages); // If project only has 1 stage, skip prompt if (stages.length === 1) { @@ -147,9 +147,9 @@ usage: jaws lambda alias `, } return this.selectInput('AliasLambda: Choose a stage: ', choices, false) - .then(results => { - _this._stage = results[0].value; - }); + .then(results => { + _this._stage = results[0].value; + }); } /** @@ -166,7 +166,7 @@ usage: jaws lambda alias `, //Deploy to all regions in stage let stage = this._stage, - projJson = this.JAWS._projectJson; + projJson = this.Jaws._projectJson; let regionConfigs = projJson.stages[stage], regions = regionConfigs.map(rCfg => { @@ -185,12 +185,12 @@ usage: jaws lambda alias `, _setLambdaLogicalIds() { let _this = this; return JawsUtils.getFullLambdaPaths(process.cwd(), []) - .then(fullAwsmJsonPaths => { - _this._lambdaLogicalIdsToAlias = fullAwsmJsonPaths.map(alp => { - let awsmJson = JawsUtils.readAndParseJsonSync(alp); - return JawsUtils.getLambdaName(awsmJson); - }); + .then(fullAwsmJsonPaths => { + _this._lambdaLogicalIdsToAlias = fullAwsmJsonPaths.map(alp => { + let awsmJson = JawsUtils.readAndParseJsonSync(alp); + return JawsUtils.getLambdaName(awsmJson); }); + }); } } diff --git a/lib/defaults/actions/DeployLambda.js b/lib/defaults/actions/DeployLambda.js index be0a158ce..9c131515e 100644 --- a/lib/defaults/actions/DeployLambda.js +++ b/lib/defaults/actions/DeployLambda.js @@ -41,91 +41,91 @@ class Deployer { awsmLambdas = []; return BbPromise.try(function() { - }) - .bind(_this) - .then(function() { - return _this._lambdaAwsmPaths; - }) - .each(function(lambdaAwsmPath) { - let packager = new Packager( - _this._JAWS, - _this._stage, - _this._region, - lambdaAwsmPath - ); - - return BbPromise.try(function() { - }) - .bind(_this) - .then(function() { - return packager.run(); - }) - .then(function(packagedLambda) { - let jawsBucket = _this._JAWS.getJawsBucket(_this._region, _this._stage); - JawsCLI.log('Lambda Deployer: Uploading ' + packagedLambda.lambdaName + ` to ${jawsBucket}`); - - return AWSUtils.putLambdaZip( - _this._JAWS._profile, - _this._region, - jawsBucket, - projName, - _this._stage, - packagedLambda.lambdaName, - packagedLambda.zipBuffer - ) - .then(function(s3Key) { - awsmLambdas.push({ - awsmPath: lambdaAwsmPath, - Code: { - S3Bucket: jawsBucket, - S3Key: s3Key, - }, - lambdaName: packagedLambda.lambdaName, - }); - }); - }); - }) - .then(function() { - //At this point all packages have been created and uploaded to s3 - let lambdaRoleArn = JawsUtils - .getProjRegionConfigForStage(_this._JAWS._projectJson, _this._stage, _this._region).iamRoleArnLambda; - return [lambdaRoleArn, _this._generateLambdaCf(awsmLambdas, lambdaRoleArn)]; - }) - .spread(function(lambdaRoleArn, existingStack) { - if (_this._noExeCf) { - JawsCLI.log(`Lambda Deployer: not executing CloudFormation. Remember to set aaLambdaRoleArn parameter to ${lambdaRoleArn}`); - return false; - } else { - let createOrUpdate, - cfDeferred; - - JawsUtils.jawsDebug(`Deploying with lambda role arn ${lambdaRoleArn}`); - - if (existingStack) { - cfDeferred = AWSUtils.cfUpdateLambdasStack(_this._JAWS, _this._stage, _this._region, lambdaRoleArn); - createOrUpdate = 'update'; - } else { - cfDeferred = AWSUtils.cfCreateLambdasStack(_this._JAWS, _this._stage, _this._region, lambdaRoleArn); - createOrUpdate = 'create'; - } - - JawsCLI.log('Running CloudFormation lambda deploy...'); - let spinner = JawsCLI.spinner(); - spinner.start(); - - return cfDeferred - .then(function(cfData) { - return AWSUtils.monitorCf(cfData, _this._JAWS._profile, _this._region, createOrUpdate); - }) - .then(function() { - spinner.stop(true); - }); - } - }) - .then(function() { - JawsCLI.log('Lambda Deployer: Done deploying lambdas in ' + _this._region); - } + }) + .bind(_this) + .then(function() { + return _this._lambdaAwsmPaths; + }) + .each(function(lambdaAwsmPath) { + let packager = new Packager( + _this._JAWS, + _this._stage, + _this._region, + lambdaAwsmPath ); + + return BbPromise.try(function() { + }) + .bind(_this) + .then(function() { + return packager.run(); + }) + .then(function(packagedLambda) { + let jawsBucket = _this._JAWS.getJawsBucket(_this._region, _this._stage); + JawsCLI.log('Lambda Deployer: Uploading ' + packagedLambda.lambdaName + ` to ${jawsBucket}`); + + return AWSUtils.putLambdaZip( + _this._JAWS._awsProfile, + _this._region, + jawsBucket, + projName, + _this._stage, + packagedLambda.lambdaName, + packagedLambda.zipBuffer + ) + .then(function(s3Key) { + awsmLambdas.push({ + awsmPath: lambdaAwsmPath, + Code: { + S3Bucket: jawsBucket, + S3Key: s3Key, + }, + lambdaName: packagedLambda.lambdaName, + }); + }); + }); + }) + .then(function() { + //At this point all packages have been created and uploaded to s3 + let lambdaRoleArn = JawsUtils + .getProjRegionConfigForStage(_this._JAWS._projectJson, _this._stage, _this._region).iamRoleArnLambda; + return [lambdaRoleArn, _this._generateLambdaCf(awsmLambdas, lambdaRoleArn)]; + }) + .spread(function(lambdaRoleArn, existingStack) { + if (_this._noExeCf) { + JawsCLI.log(`Lambda Deployer: not executing CloudFormation. Remember to set aaLambdaRoleArn parameter to ${lambdaRoleArn}`); + return false; + } else { + let createOrUpdate, + cfDeferred; + + JawsUtils.jawsDebug(`Deploying with lambda role arn ${lambdaRoleArn}`); + + if (existingStack) { + cfDeferred = AWSUtils.cfUpdateLambdasStack(_this._JAWS, _this._stage, _this._region, lambdaRoleArn); + createOrUpdate = 'update'; + } else { + cfDeferred = AWSUtils.cfCreateLambdasStack(_this._JAWS, _this._stage, _this._region, lambdaRoleArn); + createOrUpdate = 'create'; + } + + JawsCLI.log('Running CloudFormation lambda deploy...'); + let spinner = JawsCLI.spinner(); + spinner.start(); + + return cfDeferred + .then(function(cfData) { + return AWSUtils.monitorCf(cfData, _this._JAWS._awsProfile, _this._region, createOrUpdate); + }) + .then(function() { + spinner.stop(true); + }); + } + }) + .then(function() { + JawsCLI.log('Lambda Deployer: Done deploying lambdas in ' + _this._region); + } + ); } /** @@ -150,84 +150,84 @@ class Deployer { existingStack = true, projName = this._JAWS._projectJson.name; - return AWSUtils.cfGetLambdasStackTemplate(_this._JAWS._profile, _this._region, _this._stage, projName) - .error(e => { - if (e && ['ValidationError', 'ResourceNotFoundException'].indexOf(e.code) == -1) { //ValidationError if DNE - console.error( - 'Error trying to fetch existing lambda cf stack for region', _this._region, 'stage', _this._stage, e - ); - throw new JawsError(e.message, JawsError.errorCodes.UNKNOWN); - } - - JawsUtils.jawsDebug('no exsting lambda stack'); - existingStack = false; - return false; - }) - .then(cfTemplateBody => { - let templatesPath = path.join(__dirname, '..', 'templates'), - lambdaCf = JawsUtils.readAndParseJsonSync(path.join(templatesPath, 'lambdas-cf.json')); - - delete lambdaCf.Resources.lTemplate; - lambdaCf.Description = projName + " lambdas"; - lambdaCf.Parameters.aaLambdaRoleArn.Default = lambdaRoleArn; - - //Always add lambdas tagged for deployment - taggedLambdaPkgs.forEach(function(pkg) { - let lResource = { - Type: "AWS::Lambda::Function", - Properties: {} - }, - awsm = JawsUtils.readAndParseJsonSync(pkg.awsmPath); - - lResource.Properties = awsm.lambda.cloudFormation; - lResource.Properties.Code = pkg.Code; - lResource.Properties.Role = { - Ref: "aaLambdaRoleArn" - }; - - JawsUtils.jawsDebug('adding Resource ' + pkg.lambdaName + ': '); - JawsUtils.jawsDebug(lResource); - - lambdaCf.Resources[pkg.lambdaName] = lResource; - }); - - // If existing lambdas CF template - if (cfTemplateBody) { - JawsUtils.jawsDebug('existing stack detected'); - - // Find all lambdas in project, and copy ones that are in existing lambda-cf - let existingTemplate = JSON.parse(cfTemplateBody); - - return JawsUtils.getAllLambdaNames(_this._JAWS._projectRootPath) - .then(allLambdaNames => { - Object.keys(existingTemplate.Resources).forEach(resource => { - - if (!lambdaCf.Resources[resource] && allLambdaNames.indexOf(resource) != -1) { - JawsUtils.jawsDebug(`Adding exsiting lambda ${resource}`); - lambdaCf.Resources[resource] = existingTemplate.Resources[resource]; - } - }); - - return lambdaCf; - }); - } else { - return lambdaCf; - } - }) - .then(lambdaCfTemplate => { - let lambdasCfPath = path.join( - _this._JAWS._projectRootPath, - 'cloudformation', - _this._stage, - _this._region, - 'lambdas-cf.json' + return AWSUtils.cfGetLambdasStackTemplate(_this._JAWS._awsProfile, _this._region, _this._stage, projName) + .error(e => { + if (e && ['ValidationError', 'ResourceNotFoundException'].indexOf(e.code) == -1) { //ValidationError if DNE + console.error( + 'Error trying to fetch existing lambda cf stack for region', _this._region, 'stage', _this._stage, e ); + throw new JawsError(e.message, JawsError.errorCodes.UNKNOWN); + } - JawsUtils.jawsDebug(`Wrting to ${lambdasCfPath}`); + JawsUtils.jawsDebug('no exsting lambda stack'); + existingStack = false; + return false; + }) + .then(cfTemplateBody => { + let templatesPath = path.join(__dirname, '..', 'templates'), + lambdaCf = JawsUtils.readAndParseJsonSync(path.join(templatesPath, 'lambdas-cf.json')); - return JawsUtils.writeFile(lambdasCfPath, JSON.stringify(lambdaCfTemplate, null, 2)) - .then(() => existingStack); + delete lambdaCf.Resources.lTemplate; + lambdaCf.Description = projName + " lambdas"; + lambdaCf.Parameters.aaLambdaRoleArn.Default = lambdaRoleArn; + + //Always add lambdas tagged for deployment + taggedLambdaPkgs.forEach(function(pkg) { + let lResource = { + Type: "AWS::Lambda::Function", + Properties: {} + }, + awsm = JawsUtils.readAndParseJsonSync(pkg.awsmPath); + + lResource.Properties = awsm.lambda.cloudFormation; + lResource.Properties.Code = pkg.Code; + lResource.Properties.Role = { + Ref: "aaLambdaRoleArn" + }; + + JawsUtils.jawsDebug('adding Resource ' + pkg.lambdaName + ': '); + JawsUtils.jawsDebug(lResource); + + lambdaCf.Resources[pkg.lambdaName] = lResource; }); + + // If existing lambdas CF template + if (cfTemplateBody) { + JawsUtils.jawsDebug('existing stack detected'); + + // Find all lambdas in project, and copy ones that are in existing lambda-cf + let existingTemplate = JSON.parse(cfTemplateBody); + + return JawsUtils.getAllLambdaNames(_this._JAWS._projectRootPath) + .then(allLambdaNames => { + Object.keys(existingTemplate.Resources).forEach(resource => { + + if (!lambdaCf.Resources[resource] && allLambdaNames.indexOf(resource) != -1) { + JawsUtils.jawsDebug(`Adding exsiting lambda ${resource}`); + lambdaCf.Resources[resource] = existingTemplate.Resources[resource]; + } + }); + + return lambdaCf; + }); + } else { + return lambdaCf; + } + }) + .then(lambdaCfTemplate => { + let lambdasCfPath = path.join( + _this._JAWS._projectRootPath, + 'cloudformation', + _this._stage, + _this._region, + 'lambdas-cf.json' + ); + + JawsUtils.jawsDebug(`Wrting to ${lambdasCfPath}`); + + return JawsUtils.writeFile(lambdasCfPath, JSON.stringify(lambdaCfTemplate, null, 2)) + .then(() => existingStack); + }); } } @@ -251,22 +251,22 @@ class Packager { this._lambdaName = this.createLambdaName(); return this._createDistFolder() - .then(function() { + .then(function() { - // Package by runtime - switch (_this._awsmJson.lambda.cloudFormation.Runtime) { - case 'nodejs': - return _this._packageNodeJs() - .then(function(packageData) { - packageData.lambdaName = _this._lambdaName; - return packageData; - }); - break; - default: - return Promise.reject(new JawsError('Unsupported lambda runtime ' + _this._awsmJson.lambda.cloudFormation.Runtime)); - break; - } - }); + // Package by runtime + switch (_this._awsmJson.cloudFormation.Lambda.Runtime) { + case 'nodejs': + return _this._packageNodeJs() + .then(function(packageData) { + packageData.lambdaName = _this._lambdaName; + return packageData; + }); + break; + default: + return Promise.reject(new JawsError('Unsupported lambda runtime ' + _this._awsmJson.cloudFormation.Lambda.Runtime)); + break; + } + }); } /** @@ -304,44 +304,44 @@ class Packager { JawsUtils.jawsDebug('copying', _this._JAWS._projectRootPath, 'to', _this._distDir); // Copy entire test project to temp folder - _this._excludePatterns = _this._awsmJson.lambda.package.excludePatterns || []; + _this._excludePatterns = _this._awsmJson.package.excludePatterns || []; wrench.copyDirSyncRecursive( - _this._JAWS._projectRootPath, - _this._distDir, - { - exclude: function(name, prefix) { - if (!_this._excludePatterns.length) { - return false; + _this._JAWS._projectRootPath, + _this._distDir, + { + exclude: function(name, prefix) { + if (!_this._excludePatterns.length) { + return false; + } + + let relPath = path.join( + prefix.replace(_this._distDir, ''), name); + + return _this._excludePatterns.some(sRegex => { + relPath = (relPath.charAt(0) == path.sep) ? relPath.substr(1) : relPath; + + let re = new RegExp(sRegex), + matches = re.exec(relPath); + + let willExclude = (matches && matches.length > 0); + + if (willExclude) { + JawsCLI.log(`Lambda Deployer: Excluding ${relPath}`); } - let relPath = path.join( - prefix.replace(_this._distDir, ''), name); - - return _this._excludePatterns.some(sRegex => { - relPath = (relPath.charAt(0) == path.sep) ? relPath.substr(1) : relPath; - - let re = new RegExp(sRegex), - matches = re.exec(relPath); - - let willExclude = (matches && matches.length > 0); - - if (willExclude) { - JawsCLI.log(`Lambda Deployer: Excluding ${relPath}`); - } - - return willExclude; - }); - }, - } + return willExclude; + }); + }, + } ); JawsUtils.jawsDebug('Packaging stage & region:', _this._stage, _this._region); // Get ENV file from S3 return _this._JAWS.getEnvFile(_this._region, _this._stage) - .then(function(s3ObjData) { - fs.writeFileSync(path.join(_this._distDir, '.env'), s3ObjData.Body); - }); + .then(function(s3ObjData) { + fs.writeFileSync(path.join(_this._distDir, '.env'), s3ObjData.Body); + }); } /** @@ -354,47 +354,47 @@ class Packager { let _this = this, deferred = null; - if (_this._awsmJson.lambda.package - && _this._awsmJson.lambda.package.optimize - && _this._awsmJson.lambda.package.optimize.builder) { + if (_this._awsmJson.package + && _this._awsmJson.package.optimize + && _this._awsmJson.package.optimize.builder) { deferred = _this._optimizeNodeJs() - .then(optimizedCodeBuffer => { + .then(optimizedCodeBuffer => { - // Lambda freaks out if code doesnt end in newline - let ocbWithNewline = optimizedCodeBuffer.concat(new Buffer('\n')); - let envData = fs.readFileSync(path.join(_this._distDir, '.env')); + // Lambda freaks out if code doesnt end in newline + let ocbWithNewline = optimizedCodeBuffer.concat(new Buffer('\n')); + let envData = fs.readFileSync(path.join(_this._distDir, '.env')); - let handlerFileName = _this._awsmJson.lambda.cloudFormation.Handler.split('.')[0], - compressPaths = [ + let handlerFileName = _this._awsmJson.cloudFormation.Lambda.Handler.split('.')[0], + compressPaths = [ - // handlerFileName is the full path lambda file including dir rel to back - {fileName: handlerFileName + '.js', data: ocbWithNewline}, - {fileName: '.env', data: envData}, - ]; + // handlerFileName is the full path lambda file including dir rel to back + {fileName: handlerFileName + '.js', data: ocbWithNewline}, + {fileName: '.env', data: envData}, + ]; - if (_this._awsmJson.lambda.package.optimize.includePaths.length) { - compressPaths = compressPaths.concat(_this._generateIncludePaths()); - } + if (_this._awsmJson.package.optimize.includePaths.length) { + compressPaths = compressPaths.concat(_this._generateIncludePaths()); + } - return _this._compressCode(compressPaths); - }); + return _this._compressCode(compressPaths); + }); } else { // User chose not to optimize, zip up whatever is in back - _this._awsmJson.lambda.package.optimize.includePaths = ['.']; - let compressPaths = _this._generateIncludePaths(); - deferred = _this._compressCode(compressPaths); + _this._awsmJson.package.optimize.includePaths = ['.']; + let compressPaths = _this._generateIncludePaths(); + deferred = _this._compressCode(compressPaths); } return deferred - .then(function(compressedCodeBuffer) { - let zippedFilePath = path.join(_this._distDir, 'package.zip'); // Save for auditing; - fs.writeFileSync(zippedFilePath, compressedCodeBuffer); + .then(function(compressedCodeBuffer) { + let zippedFilePath = path.join(_this._distDir, 'package.zip'); // Save for auditing; + fs.writeFileSync(zippedFilePath, compressedCodeBuffer); - JawsCLI.log(`Lambda Deployer: Compressed lambda written to ${zippedFilePath}`); + JawsCLI.log(`Lambda Deployer: Compressed lambda written to ${zippedFilePath}`); - return Promise.resolve({awsmFilePath: _this._lambdaPath, zipBuffer: compressedCodeBuffer}); - }); + return Promise.resolve({awsmFilePath: _this._lambdaPath, zipBuffer: compressedCodeBuffer}); + }); } /** @@ -406,12 +406,12 @@ class Packager { let _this = this; - if (!_this._awsmJson.lambda.package.optimize - || !_this._awsmJson.lambda.package.optimize.builder) { + if (!_this._awsmJson.package.optimize + || !_this._awsmJson.package.optimize.builder) { return Promise.reject(new JawsError('Cant optimize for nodejs. lambda jaws.json does not have optimize.builder set')); } - if (_this._awsmJson.lambda.package.optimize.builder.toLowerCase() == 'browserify') { + if (_this._awsmJson.package.optimize.builder.toLowerCase() == 'browserify') { return _this._browserifyBundle(); } else { return Promise.reject(new JawsError(`Unsupported builder ${builder}`)); @@ -432,7 +432,7 @@ class Packager { }; let b = browserify({ basedir: _this._distDir, - entries: [_this._awsmJson.lambda.cloudFormation.Handler.split('.')[0] + '.js'], + entries: [_this._awsmJson.cloudFormation.Lambda.Handler.split('.')[0] + '.js'], standalone: 'lambda', browserField: false, // Setup for node app (copy logic of --node in bin/args.js) builtins: false, @@ -447,21 +447,21 @@ class Packager { }, }); - if (_this._awsmJson.lambda.package.optimize.babel) { + if (_this._awsmJson.package.optimize.babel) { b.transform(babelify) } - if (_this._awsmJson.lambda.package.optimize.transform) { - b.transform(_this._awsmJson.lambda.package.optimize.transform); + if (_this._awsmJson.package.optimize.transform) { + b.transform(_this._awsmJson.package.optimize.transform); } // optimize.exclude - _this._awsmJson.lambda.package.optimize.exclude.forEach(file => { + _this._awsmJson.package.optimize.exclude.forEach(file => { b.exclude(file); }); // optimize.ignore - _this._awsmJson.lambda.package.optimize.ignore.forEach(file => { + _this._awsmJson.package.optimize.ignore.forEach(file => { b.ignore(file); }); @@ -478,7 +478,7 @@ class Packager { fs.writeFileSync(bundledFilePath, bundledBuf); JawsCLI.log(`Lambda Deployer: Bundled file written to ${bundledFilePath}`); - if (_this._awsmJson.lambda.package.optimize.exclude) { + if (_this._awsmJson.package.optimize.exclude) { let result = UglifyJS.minify(bundledFilePath, uglyOptions); if (!result || !result.code) { @@ -509,7 +509,7 @@ class Packager { stats, fullPath; - _this._awsmJson.lambda.package.optimize.includePaths.forEach(p => { + _this._awsmJson.package.optimize.includePaths.forEach(p => { try { fullPath = path.resolve(path.join(_this._distDir, p)); stats = fs.lstatSync(fullPath); @@ -524,20 +524,20 @@ class Packager { let dirname = path.basename(p); wrench - .readdirSyncRecursive(fullPath) - .forEach(file => { - // Ignore certain files - for (let i = 0; i < ignore.length; i++) { - if (file.toLowerCase().indexOf(ignore[i]) > -1) return; - } + .readdirSyncRecursive(fullPath) + .forEach(file => { + // Ignore certain files + for (let i = 0; i < ignore.length; i++) { + if (file.toLowerCase().indexOf(ignore[i]) > -1) return; + } - let filePath = [fullPath, file].join('/'); - if (fs.lstatSync(filePath).isFile()) { - let pathInZip = path.join(dirname, file); - JawsUtils.jawsDebug('Adding', pathInZip); - compressPaths.push({fileName: pathInZip, data: fs.readFileSync(filePath)}); - } - }); + let filePath = [fullPath, file].join('/'); + if (fs.lstatSync(filePath).isFile()) { + let pathInZip = path.join(dirname, file); + JawsUtils.jawsDebug('Adding', pathInZip); + compressPaths.push({fileName: pathInZip, data: fs.readFileSync(filePath)}); + } + }); } }); @@ -564,8 +564,8 @@ class Packager { if (zippedData.length > 52428800) { Promise.reject(new JawsError( - 'Zip file is > the 50MB Lambda deploy limit (' + zippedData.length + ' bytes)', - JawsError.errorCodes.ZIP_TOO_BIG) + 'Zip file is > the 50MB Lambda deploy limit (' + zippedData.length + ' bytes)', + JawsError.errorCodes.ZIP_TOO_BIG) ); } @@ -644,22 +644,22 @@ usage: jaws lambda deploy [rel or abs path to lambda dirs. default is cwd]`, this._noExeCf = (noExeCf == true || noExeCf == 'true'); return this._JAWS.validateProject() - .bind(_this) - .then(_this._promptStage) - .then(_this._validate) - .then(() => { - JawsUtils.jawsDebug('Deploying to stage:', _this._stage); - return _this._setLambdaAwsmPaths(lambdaPaths); - }) - .then(_this._getRegions) - .each(region => { - let d = new Deployer(_this.JAWS, _this._lambdaAwsmPathsToDeploy, _this._stage, region, _this._noExeCf); - return d.run(); - }) - .then(lambdaAwsmPkgs => { - JawsCLI.log('Lambda Deployer: Successfully deployed lambdas to the requested regions!'); - return lambdaAwsmPkgs; - }); + .bind(_this) + .then(_this._promptStage) + .then(_this._validate) + .then(() => { + JawsUtils.jawsDebug('Deploying to stage:', _this._stage); + return _this._setLambdaAwsmPaths(lambdaPaths); + }) + .then(_this._getRegions) + .each(region => { + let d = new Deployer(_this.Jaws, _this._lambdaAwsmPathsToDeploy, _this._stage, region, _this._noExeCf); + return d.run(); + }) + .then(lambdaAwsmPkgs => { + JawsCLI.log('Lambda Deployer: Successfully deployed lambdas to the requested regions!'); + return lambdaAwsmPkgs; + }); } /** @@ -673,7 +673,7 @@ usage: jaws lambda deploy [rel or abs path to lambda dirs. default is cwd]`, // If stage exists, skip if (!this._stage) { - stages = Object.keys(_this.JAWS._projectJson.stages); + stages = Object.keys(_this.Jaws._projectJson.stages); // If project only has 1 stage, skip prompt if (stages.length === 1) { @@ -696,9 +696,9 @@ usage: jaws lambda deploy [rel or abs path to lambda dirs. default is cwd]`, } return this.selectInput('Lambda Deployer: Choose a stage: ', choices, false) - .then(results => { - _this._stage = results[0].value; - }); + .then(results => { + _this._stage = results[0].value; + }); } /** @@ -715,7 +715,7 @@ usage: jaws lambda deploy [rel or abs path to lambda dirs. default is cwd]`, //Deploy to all regions in stage let stage = this._stage, - projJson = this.JAWS._projectJson; + projJson = this.Jaws._projectJson; let regionConfigs = projJson.stages[stage], regions = regionConfigs.map(rCfg => { @@ -734,7 +734,7 @@ usage: jaws lambda deploy [rel or abs path to lambda dirs. default is cwd]`, */ _validate() { // Validate: Check stage exists within project - if (!this.JAWS._projectJson.stages[_this._stage]) { + if (!this.Jaws._projectJson.stages[_this._stage]) { return Promise.reject(new JawsError(`Invalid stage ${stage}`, JawsError.errorCodes.UNKNOWN)); } @@ -750,9 +750,9 @@ usage: jaws lambda deploy [rel or abs path to lambda dirs. default is cwd]`, _setLambdaAwsmPaths(lambdaPaths) { let _this = this; return JawsUtils.getFullLambdaPaths(process.cwd(), lambdaPaths) - .then(fullAwsmJsonPaths => { - _this._lambdaAwsmPathsToDeploy = fullAwsmJsonPaths; - }); + .then(fullAwsmJsonPaths => { + _this._lambdaAwsmPathsToDeploy = fullAwsmJsonPaths; + }); } } diff --git a/lib/defaults/actions/ModuleCreate.js b/lib/defaults/actions/ModuleCreate.js new file mode 100644 index 000000000..4a1549b0a --- /dev/null +++ b/lib/defaults/actions/ModuleCreate.js @@ -0,0 +1,358 @@ +'use strict'; + +/** + * Action: ModuleCreate + */ + +const JawsPlugin = require('../../JawsPlugin'), + JawsError = require('../../jaws-error'), + JawsCLI = require('../../utils/cli'), + fs = require('fs'), + path = require('path'), + os = require('os'), + BbPromise = require('bluebird'), + AWSUtils = require('../../utils/aws'), + JawsUtils = require('../../utils'); + +let fs = require('fs'); +BbPromise.promisifyAll(fs); + +const supportedRuntimes = { + nodejs: { + defaultPkgMgr: 'npm', + validPkgMgrs: ['npm'] + } +}; + +/** + * ModuleCreate Class + */ + +class ModuleCreate extends JawsPlugin { + + /** + * @param Jaws class object + * @param config object + */ + + constructor(Jaws, config) { + super(Jaws, config); + this._templatesDir = path.join(__dirname, '..', '..', 'templates'); + this._resource = ""; + this._action = ""; + this._pkgMgr = false; + this._createLambda = false; + this._createEndpoint = false; + this._runtime = 'nodejs'; + } + + /** + * Define your plugins name + * + * @returns {string} + */ + static getName() { + return 'jaws.core.' + ModuleCreate.name; + } + + /** + * @returns {Promise} upon completion of all registrations + */ + + registerActions() { + this.Jaws.action(this.createModule.bind(this), { + handler: 'moduleCreate', + description: `Creates scaffolding for new aws module. +usage: jaws module create `, + context: 'module', + contextAction: 'create', + options: [ + { + option: 'runtime', + shortcut: 'r', + description: '' + }, + { + option: 'lambda', + shortcut: 'l', + description: '' + }, + { + option: 'endpoint', + shortcut: 'e', + description: '' + }, + { + option: 'package-manager', + shortcut: 'p', + description: '' + }, + + ], + }); + return Promise.resolve(); + } + + /** + * + * @param runtime + * @param createLambda + * @param createEndpoint + * @param pkgMgr + * @param resourceAction resource action + * @returns {Promise} + */ + createModule(runtime, createLambda, createEndpoint, pkgMgr) { + let _this = this, + resourceAction = Array.prototype.slice.call(arguments, 5); + + if (!resourceAction || resourceAction.length !== 2) { + return Promise.reject(new JawsError('Must specify a resource and action')); + } + + if (!createLambda && !createEndpoint) { //default is to create both + createEndpoint = true; + createLambda = true; + } + + this._resource = resourceAction[0]; + this._action = resourceAction[1]; + this._createEndpoint = createEndpoint; + this._createLambda = createLambda; + this._runtime = runtime || 'nodejs'; + + if (!supportedRuntimes[this._runtime]) { + throw new JawsError('Unsupported runtime ' + _this._runtime, JawsError.errorCodes.UNKNOWN); + } + + this._pkgMgr = pkgMgr || supportedRuntimes[this._runtime].defaultPkgMgr; + + if (supportedRuntimes[this._runtime].validPkgMgrs.indexOf(this._pkgMgr) == -1) { + throw new JawsError('Unsupported package manger "' + this._pkgMgr + '"', JawsError.errorCodes.UNKNOWN); + } + + return this._JAWS.validateProject() + .bind(_this) + .then(_this._sanitizeData) + .then(_this._createSkeleton) + .then(_this._createPackageMgrSkeleton) + .then(_this._initRuntime) + .then(function() { + JawsCLI.log('Successfully created ' + + _this._resourcee + + '/' + + _this._action); + }); + } + + _sanitizeData() { + this._action = this._action.toLowerCase().trim() + .replace(/\s/g, '-') + .replace(/[^a-zA-Z-\d:]/g, '') + .substring(0, 19); + + this._resource = this._resource.toLowerCase().trim() + .replace(/\s/g, '-') + .replace(/[^a-zA-Z-\d:]/g, '') + .substring(0, 19); + } + + _generateActionAwsmJson() { + let actionTemplateJson = utils.readAndParseJsonSync(path.join(_this._templatesDir, 'action.awsm.json')); + + //We prefix with an l to make sure the CloudFormation resource map index is unique + actionTemplateJson.name = 'l' + this._resource.charAt(0).toUpperCase() + this._resource.slice(1) + this.action.charAt(0).toUpperCase() + this.action.slice(1); + + if (this._createLambda) { + actionTemplateJson.cloudFormation.Lambda.Runtime = this._runtime; + + // Create files for lambda actions + switch (this._runtime) { + case 'nodejs': + actionTemplateJson.cloudFormation.Lambda.Handler = path.join('aws_modules', this._resource, this._action, 'handler.handler'); + break; + default: + throw new JawsError('This runtime is not supported "' + this._runtime + '"', JawsError.errorCodes.UNKNOWN); + break; + } + } else { + delete actionTemplateJson.lambda; + } + + if (this._createEndpoint) { + actionTemplateJson.cloudFormation.APIGatewayEndpoint.Path = this._resource + '/' + this._action; + } else { + delete actionTemplateJson.cloudFormation.APIGatewayEndpoint; + } + + //TODO: how do we support LambdaEventSourceMapping and LambdaAccessPolicyX + delete actionTemplateJson.cloudFormation.LambdaEventSourceMapping; + delete actionTemplateJson.cloudFormation.LambdaAccessPolicyX; + + return actionTemplateJson; + } + + _generateModuleAwsmJson() { + let moduleTemplateJson = utils.readAndParseJsonSync(path.join(this._templatesDir, 'module.awsm.json')); + moduleTemplateJson.name = this._resource; + return moduleTemplateJson; + }; + + /** + * + * @returns {Promise} + * @private + */ + _createPackageMgrSkeleton() { + let _this = this, + deferredWrites = []; + + switch (_this.runtime) { + case 'nodejs': + if (_this.pkgMgr == 'npm') { + + let modulePath = path.join(_this._JAWS._projectRootPath, 'node_modules', _this._resource); + + // Create node_module if DNE in node_modules + if (!utils.dirExistsSync(modulePath)) { + deferredWrites.push(fs.mkdirAsync(modulePath)); + } + + // Create module package.json if DNE in node_module + if (!utils.fileExistsSync(path.join(modulePath, 'package.json'))) { + let packageJsonTemplate = utils.readAndParseJsonSync(path.join(_this._templatesDir, 'nodejs', 'package.json')); + packageJsonTemplate.name = _this._resource; + packageJsonTemplate.description = 'An aws-module'; + packageJsonTemplate.dependencies = {}; + + deferredWrites.push( + fs.writeFileAsync( + path.join(modulePath, 'package.json'), + JSON.stringify(packageJsonTemplate, null, 2) + ) + ); + } + + // Create module awsm.json if DNE in node_module + if (!utils.fileExistsSync(path.join(modulePath, 'awsm.json'))) { + let moduleTemplateJson = _this._generateModuleAwsmJson(); + deferredWrites.push( + utils.writeFile( + path.join(modulePath, 'awsm.json'), + JSON.stringify(moduleTemplateJson, null, 2))); + } + + // Create root lib folder if DNE in node_module + let modLibPath = path.join(modulePath, 'lib'); + if (!utils.dirExistsSync(modLibPath)) { + deferredWrites.push(fs.mkdirAsync(modLibPath)); + } + + // Create awsm folder if DNE in node_module + if (!utils.dirExistsSync(path.join(modulePath, 'awsm'))) { + deferredWrites.push(fs.mkdirAsync(path.join(modulePath, 'awsm'))); + } + + // Create action if DNE in node_module + let actionPath = path.join(modulePath, 'awsm', _this.action); + if (!utils.dirExistsSync(actionPath)) { + + let actionTemplateJson = this._generateActionAwsmJson(), + handlerJs = fs.readFileSync(path.join(_this._templatesDir, 'nodejs', 'handler.js')), + indexJs = fs.readFileSync(path.join(_this._templatesDir, 'nodejs', 'index.js')); + + deferredWrites.push( + utils.writeFile( + path.join(actionPath, 'awsm.json'), + JSON.stringify(actionTemplateJson, null, 2) + ), + utils.writeFile(path.join(actionPath, 'handler.js'), handlerJs), + utils.writeFile(path.join(actionPath, 'index.js'), indexJs), + utils.writeFile(path.join(actionPath, 'event.json'), '{}') + ); + } + } + break; + default: + break; + } + + return Promise.all(deferredWrites); + } + + /** + * + * @returns {Promise} + * @private + */ + _createSkeleton() { + let _this = this, + writeFilesDeferred = []; + + let modulePath = path.join(_this._JAWS._projectRootPath, 'aws_modules', _this._resource), + actionPath = path.join(modulePath, _this.action); + + // If module/action already exists, throw error + if (utils.dirExistsSync(actionPath)) { + throw new JawsError( + actionPath + ' already exists', + JawsError.errorCodes.INVALID_PROJECT_JAWS + ); + } + + //module path will get created by util.writeFile if DNE + + // If module awsm.json doesn't exist, create it + if (!utils.fileExistsSync(path.join(modulePath, 'awsm.json'))) { + let moduleTemplateJson = _this._generateModuleAwsmJson(); + writeFilesDeferred.push( + utils.writeFile( + path.join(modulePath, 'awsm.json'), + JSON.stringify(moduleTemplateJson, null, 2) + ) + ); + } + + // Create action folder + writeFilesDeferred.push(actionPath); + + // Create action awsm.json + let actionTemplateJson = _this._generateActionAwsmJson(); + + + let handlerJs = fs.readFileSync(path.join(this._templatesDir, 'nodejs', 'handler.js')), + indexJs = fs.readFileSync(path.join(this._templatesDir, 'nodejs', 'index.js')); + + writeFilesDeferred.push( + utils.writeFile(path.join(actionPath, 'handler.js'), handlerJs), + utils.writeFile(path.join(actionPath, 'index.js'), indexJs), + utils.writeFile(path.join(actionPath, 'event.json'), '{}'), + utils.writeFile(path.join(actionPath, 'awsm.json'), JSON.stringify(actionTemplateJson, null, 2)) + ); + + return Promise.all(writeFilesDeferred); + } + + /** + * + * @returns {Promise} + * @private + */ + _initRuntime() { + let _this = this; + + JawsCLI.log('Preparing your runtime..'); + + if (_this._runtime === 'nodejs') { + let packageJsonTemplate = JawsUtils.readAndParseJsonSync(path.join(_this._templatesDir, 'nodejs', 'package.json')); + packageJsonTemplate.name = _this.Jaws._projectJson.name; + return fs.writeFileAsync(path.join(_this.Jaws, _projectRootPath, 'package.json'), JSON.stringify(packageJsonTemplate, null, 2)) + .then(function() { + JawsCLI.log('Installing jaws-core module...'); + JawsUtils.npmInstall(_this._projectRootPath); + }); + } + } +} + +module.exports = ModuleCreate; \ No newline at end of file diff --git a/lib/defaults/actions/ProjectCreate.js b/lib/defaults/actions/ProjectCreate.js index 40fe4e5da..9e7bd9b9b 100644 --- a/lib/defaults/actions/ProjectCreate.js +++ b/lib/defaults/actions/ProjectCreate.js @@ -4,16 +4,17 @@ * Action: ProjectCreate */ -const JawsPlugin = require('../../JawsPlugin'), - JawsError = require('../../jaws-error'), - JawsCLI = require('../../utils/cli'), - fs = require('fs'), - path = require('path'), - os = require('os'), - BbPromise = require('bluebird'), - AWSUtils = require('../../utils/aws'), - JawsUtils = require('../../utils'), - shortid = require('shortid'); +const JawsPlugin = require('../../JawsPlugin'), + JawsError = require('../../jaws-error'), + JawsCLI = require('../../utils/cli'), + path = require('path'), + os = require('os'), + BbPromise = require('bluebird'), + AWSUtils = require('../../utils/aws'), + JawsUtils = require('../../utils'); + +let fs = require('fs'); +BbPromise.promisifyAll(fs); /** * ProjectCreate Class @@ -28,6 +29,7 @@ class ProjectCreate extends JawsPlugin { constructor(Jaws, config) { super(Jaws, config); + this._templatesDir = path.join(__dirname, '..', '..', 'templates'); } /** @@ -44,7 +46,7 @@ class ProjectCreate extends JawsPlugin { */ registerActions() { - this.Jaws.action(this._action.bind(this), { + this.Jaws.action(this.createProject.bind(this), { handler: 'projectCreate', description: 'Creates scaffolding for a new JAWS project', context: 'project', @@ -85,269 +87,224 @@ class ProjectCreate extends JawsPlugin { } /** - * Action - * - Contains control flow + * + * @param name + * @param domain + * @param stage + * @param region + * @param notificationEmail + * @param runtime + * @param noCf * @returns {Promise} - * @private */ - - _action(name, domain, stage, region, notificationEmail, runtime, noCf) { + createProject(name, domain, stage, region, notificationEmail, runtime, noCf) { let _this = this; + /** + * Non-Interactive Validations + */ - return new Promise(resolve => { - - /** - * Non-Interactive Validations - */ - - if (!_this.Jaws._interactive) { - - // Check API Keys - if (!_this.Jaws._awsAdminKeyId || !_this.Jaws._awsAdminSecretKey) { - throw new JawsError('Missing AWS API Key and/or AWS Secret Key'); - } - - // Check Params - if (!name || !stage || !region || !domain || !notificationEmail) { - throw new JawsError('Missing required properties'); - } + if (!_this.Jaws._interactive && !this.Jaws._awsProfile) { + // Check API Keys + if (!_this.Jaws._awsAdminKeyId || !_this.Jaws._awsAdminSecretKey) { + throw new JawsError('Missing AWS API Key and/or AWS Secret Key'); } - /** - * Defaults - */ - - _this._name = name || null; - _this._domain = domain || null; - _this._stage = stage ? stage.toLowerCase().replace(/\W+/g, '').substring(0, 15) : null; - _this._notificationEmail = notificationEmail; - _this._region = region; - _this._runtime = runtime || 'nodejs'; - _this._noCf = noCf; - - // Interactive Defaults - if (_this.Jaws._interactive) { - _this._prompts = { - properties: {}, - }; - _this.Prompter = JawsCLI.prompt(); - _this.Prompter.override = {}; - _this._spinner = null; + // Check Params + if (!name || !stage || !region || !domain || !notificationEmail) { + throw new JawsError('Missing required properties'); } + } - /** - * Control Flow - */ + /** + * Defaults + */ - return BbPromise.try(function() { + _this._name = name || null; + _this._domain = domain || null; + _this._stage = stage ? stage.toLowerCase().replace(/\W+/g, '').substring(0, 15) : null; + _this._notificationEmail = notificationEmail; + _this._region = region; + _this._runtime = runtime || 'nodejs'; + _this._noCf = noCf; - // ASCII Greeting - if (_this.Jaws._interactive) JawsCLI.ascii(); + // Interactive Defaults + if (_this.Jaws._interactive) { + _this._prompts = { + properties: {}, + }; + _this.Prompter = JawsCLI.prompt(); + _this.Prompter.override = {}; + _this._spinner = null; + } + /** + * Control Flow + */ + + return BbPromise.try(function() { + if (_this.Jaws._interactive) { + JawsCLI.asciiGreeting(); + } }) - .bind(_this) - .then(_this._prompt) - .then(_this._prepareProjectData) - .then(_this._createProjectDirectory) - .then(_this._putEnvFile) - .then(_this._putCfFile) - .then(_this._createCfStack) - .then(_this._createProjectJson) - .then(_this._initRuntime); - - return resolve(); - - }); - } - - /** - * Generate ShortId - */ - - _generateShortId(maxLen) { - return shortid.generate().replace(/\W+/g, '').substring(0, maxLen).replace(/[_-]/g, ''); + .bind(_this) + .then(_this._prompt) + .then(_this._prepareProjectData) + .then(_this._createProjectDirectory) + .then(_this._createJawsBucket) + .then(_this._putEnvFile) + .then(_this._putCfFile) + .then(_this._createCfStack) + .then(_this._createProjectJson); } /** * Prompt - * @returns {*} + * @returns {Promise} * @private */ _prompt() { + let _this = this, + overrides = {}; - // Skip, if not interactive - if (!this.Jaws._interactive) return Promise.resolve(); + //Setup overrides based off of member var values + ['name', 'domain', 'notificationEmail', 'stage', 'awsAdminKeyId', 'awsAdminSecretKey'] + .forEach(memberVarKey => { + overrides[memberVarKey] = _this['_' + memberVarKey]; + }); - JawsUtils.jawsDebug('Prompting for new project information'); - - let _this = this; - - let nameDescription = 'Enter a project name: '; - - // Prompt: name (project name) - _this.Prompter.override.name = _this._name; - // Set default name - _this._name = 'jaws-' + _this._generateShortId(19); - _this._prompts.properties.name = { - description: nameDescription.yellow, - default: _this._name, - message: 'Name must be only letters, numbers or dashes', - conform: function (name) { - let re = /^[a-zA-Z0-9-_]+$/; - return re.test(name); - }, + let prompts = { + properties: { + name: { + description: 'Enter a project name: '.yellow, + default: 'jaws-' + JawsUtils.generateShortId(19), + message: 'Name must be only letters, numbers or dashes', + required: true, + conform: function(name) { + let re = /^[a-zA-Z0-9-_]+$/; + return re.test(name); + }, + }, + domain: { + description: 'Enter a project domain (used for jaws s3 bucket name): '.yellow, + default: 'myapp.com', + message: 'Domain must only contain lowercase letters, numbers, periods and dashes', + required: true, + conform: function(bucket) { + let re = /^[a-z0-9-.]+$/; + return re.test(bucket); + }, + }, + notificationEmail: { + description: 'Enter an email to use for AWS alarms: '.yellow, + required: true, + message: 'Please enter a valid email', + default: 'me@myapp.com', + conform: function(email) { + return (!email) ? false : true; + }, + }, + stage: { + description: 'Enter a stage name for this project: '.yellow, + required: true, + default: 'dev', + message: 'Stage must be letters only', + conform: function(stage) { + let re = /^[a-zA-Z]+$/; + return re.test(stage); + }, + }, + } }; - // Prompt: domain - for AWS hosted zone and more - _this.Prompter.override.domain = _this._domain; - - let domainDescription = 'Enter a project domain (You can change this at any time: '; - - _this._prompts.properties.domain = { - description: domainDescription.yellow, - default: 'myapp.com', - message: 'Domain must only contain lowercase letters, numbers, periods and dashes', - conform: function (bucket) { - let re = /^[a-z0-9-.]+$/; - return re.test(bucket); - }, - }; - - // Prompt: notification email - for AWS alerts - _this.Prompter.override.notificationEmail = _this._notificationEmail; - _this._prompts.properties.notificationEmail = { - description: 'Enter an email to use for AWS alarms: '.yellow, - required: true, - message: 'Please enter a valid email', - default: 'me@myapp.com', - conform: function (email) { - if (!email) return false; - return true; - }, - }; - - // Prompt: stage - _this.Prompter.override.stage = _this._stage; - - let stageDescription = 'Enter a stage for this project: '; - - _this._prompts.properties.stage = { - description: stageDescription.yellow, - default: 'dev', - message: 'Stage must be letters only', - conform: function (stage) { - let re = /^[a-zA-Z]+$/; - return re.test(stage); - }, - }; - - // Prompt: notification email - for AWS alerts - _this.Prompter.override.notificationEmail = _this._notificationEmail; - - let notificationEmailDescription = 'Enter an email to use for AWS alarms: '; - - _this._prompts.properties.notificationEmail = { - description: notificationEmailDescription.yellow, - required: true, - message: 'Please enter a valid email', - default: 'you@yourapp.com', - conform: function (email) { - if (!email) return false; - return true; - }, - }; - - // Prompt: API Keys - Create an AWS profile by entering API keys + //Create aws credentials if DNE if (!JawsUtils.fileExistsSync(path.join(AWSUtils.getConfigDir(), 'credentials'))) { - - _this.Prompter.override.awsAdminKeyId = _this._awsAdminKeyId; - - let apiKeyDescription = 'Enter the ACCESS KEY ID for your Admin AWS IAM User: '; - - _this._prompts.properties.awsAdminKeyId = { - description: apiKeyDescription.yellow, + prompts.properties.awsAdminKeyId = { + description: 'Enter the ACCESS KEY ID for your Admin AWS IAM User: '.yellow, required: true, message: 'Please enter a valid access key ID', - conform: function (key) { - if (!key) return false; - return true; + conform: function(key) { + return (key) ? false : true; }, }; - _this.Prompter.override.awsAdminSecretKey = _this._awsAdminSecretKey; - let apiSecretDescription = 'Enter the SECRET ACCESS KEY for your Admin AWS IAM User: '; - - _this._prompts.properties.awsAdminSecretKey = { - description: apiSecretDescription.yellow, + prompts.properties.awsAdminSecretKey = { + description: 'Enter the SECRET ACCESS KEY for your Admin AWS IAM User: '.yellow, required: true, message: 'Please enter a valid secret access key', - conform: function (key) { + conform: function(key) { if (!key) return false; return true; }, }; } - // Show Prompts - return _this.Prompter.getAsync(_this._prompts) - .then(function (answers) { - _this._name = answers.name; - _this._domain = answers.domain; - _this._stage = answers.stage.toLowerCase(); - _this._notificationEmail = answers.notificationEmail; - _this._awsAdminKeyId = answers.awsAdminKeyId; - _this._awsAdminSecretKey = answers.awsAdminSecretKey; + return this.promptInput(prompts, overrides) + .then(function(answers) { + _this._name = answers.name; + _this._domain = answers.domain; + _this._stage = answers.stage.toLowerCase(); + _this._notificationEmail = answers.notificationEmail; + _this._awsAdminKeyId = answers.awsAdminKeyId; + _this._awsAdminSecretKey = answers.awsAdminSecretKey; - // If region exists, skip select prompt - if (_this._region) return; + // If region exists, skip select prompt + if (_this._region) { + return; + } - // Prompt: region select - let choices = []; - AWSUtils.validLambdaRegions.forEach(function (r) { - choices.push({ - key: '', - value: r, - label: r, - }); + // Prompt: region select + let 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) { - _this._region = results[0].value; - }); - }) - .then(function () { - - // If profile exists, skip select prompt - if (_this._profile) return Promise.resolve(); - - // If aws credentials were passed, skip select prompt - if (_this._awsAdminKeyId && _this._awsAdminSecretKey) return Promise.resolve(); - - // Prompt: profile select - let profilesList = AWSUtils.profilesMap(), - profiles = Object.keys(profilesList), - choices = []; - - for (let i = 0; i < profiles.length; i++) { - choices.push({ - key: '', - value: profiles[i], - label: profiles[i], - }); - } - - return JawsCLI.select('Select an AWS profile for your project: ', choices, false) - .then(function (results) { - _this._profile = results[0].value; - }); }); + + return _this.selectInput('Select a region for your project: ', choices, false) + .then(results => { + _this._region = results[0].value; + }); + }) + .then(function() { + + // If profile exists, skip select prompt + if (_this._awsProfile) { + return; + } + + // If aws credentials were passed, skip select prompt + if (_this._awsAdminKeyId && _this._awsAdminSecretKey) { + return; + } + + // Prompt: profile select + let profilesList = AWSUtils.profilesMap(), + profiles = Object.keys(profilesList), + choices = []; + + for (let i = 0; i < profiles.length; i++) { + choices.push({ + key: '', + value: profiles[i], + label: profiles[i], + }); + } + + return _this.selectInput('Select an AWS profile for your project: ', choices, false) + .then(results => { + _this._awsProfile = results[0].value; + }); + }); } /** * Prepare Project Data + * @returns {Promise} + * @private */ _prepareProjectData() { @@ -367,21 +324,21 @@ class ProjectCreate extends JawsPlugin { // Append unique id if name is in use if (JawsUtils.dirExistsSync(path.join(process.cwd(), _this._name))) { - _this._name = _this._name + '-' + _this._generateShortId(19); + _this._name = _this._name + '-' + JawsUtils.generateShortId(19); } // Append unique id if domain is default if (_this._domain === 'myapp.com') { - _this._domain = 'myapp-' + _this._generateShortId(8) + '.com'; + _this._domain = 'myapp-' + JawsUtils.generateShortId(8) + '.com'; } // Set JAWS Bucket _this._jawsBucket = JawsUtils.generateJawsBucketName(_this._stage, _this._region, _this._domain); // If no profile, ensure access keys, create profile - if (!_this._profile) { + if (!_this._awsProfile) { AWSUtils.profilesSet('default', _this._region, _this.Jaws._awsAdminKeyId, _this.Jaws._awsAdminSecretKey); - _this._profile = 'default'; + _this._awsProfile = 'default'; } return Promise.resolve(); @@ -389,6 +346,7 @@ class ProjectCreate extends JawsPlugin { /** * Create Project Directory + * @returns {Private} * @private */ @@ -398,42 +356,52 @@ class ProjectCreate extends JawsPlugin { _this._projectRootPath = path.resolve(path.join(path.dirname('.'), _this._name)); // Prepare admin.env - let adminEnv = 'JAWS_ADMIN_AWS_PROFILE=' + _this._profile + os.EOL; + let adminEnv = 'JAWS_ADMIN_AWS_PROFILE=' + _this._awsProfile + os.EOL; // Prepare README.md let readme = '#' + _this._name; // Create Project Scaffolding return JawsUtils.writeFile( - path.join(_this._projectRootPath, '.env'), - 'JAWS_STAGE=' + _this._stage - + '\nJAWS_DATA_MODEL_STAGE=' + _this._stage - ) - .then(function () { - return Promise.all([ - fs.mkdirAsync(path.join(_this._projectRootPath, 'tests')), - fs.mkdirAsync(path.join(_this._projectRootPath, 'lib')), - fs.mkdirAsync(path.join(_this._projectRootPath, 'aws_modules')), - JawsUtils.writeFile(path.join(_this._projectRootPath, 'admin.env'), adminEnv), - JawsUtils.writeFile(path.join(_this._projectRootPath, 'README.md'), readme), - JawsUtils.generateResourcesCf( - _this._projectRootPath, - _this._name, - _this._domain, - _this._stage, - _this._region, - _this._notificationEmail, - _this._jawsBucket - ), - fs.writeFileAsync(path.join(_this._projectRootPath, '.gitignore'), fs.readFileSync(__dirname + '/../../templates/gitignore')), - ]); - }); + path.join(_this._projectRootPath, '.env'), + 'JAWS_STAGE=' + _this._stage + + '\nJAWS_DATA_MODEL_STAGE=' + _this._stage + ) + .then(function() { + return Promise.all([ + fs.mkdirAsync(path.join(_this._projectRootPath, 'tests')), + fs.mkdirAsync(path.join(_this._projectRootPath, 'lib')), + fs.mkdirAsync(path.join(_this._projectRootPath, 'aws_modules')), + JawsUtils.writeFile(path.join(_this._projectRootPath, 'admin.env'), adminEnv), + JawsUtils.writeFile(path.join(_this._projectRootPath, 'README.md'), readme), + JawsUtils.generateResourcesCf( + _this._projectRootPath, + _this._name, + _this._domain, + _this._stage, + _this._region, + _this._notificationEmail + ), + fs.writeFileAsync(path.join(_this._projectRootPath, '.gitignore'), fs.readFileSync(path.join(_this._templatesDir, 'gitignore'))), + ]); + }); } + /** + * Create jaws bucket if it does not exist + * + * @returns {Promise} + * @private + */ + _createJawsBucket() { + JawsUtils.jawsDebug('Creating jaws s3 bucket: ', this._jawsBucket); + return AWSUtils.createBucket(this._awsProfile, this._region, this._jawsBucket); + } /** * Put ENV File * - Creates ENV file in JAWS stage/region bucket + * @returns {Private} * @private */ @@ -446,18 +414,18 @@ class ProjectCreate extends JawsPlugin { JAWS_DATA_MODEL_STAGE=${stage}`; return AWSUtils.putEnvFile( - _this._profile, - _this._region, - _this._jawsBucket, - _this._name, - _this._stage, - envFileContents); + _this._awsProfile, + _this._region, + _this._jawsBucket, + _this._name, + _this._stage, + envFileContents); } /** * Put CF File - * @returns {*} + * @returns {Promise} * @private */ @@ -466,22 +434,26 @@ class ProjectCreate extends JawsPlugin { let _this = this; return AWSUtils.putCfFile( - _this._profile, - _this._projectRootPath, - _this._region, - _this._jawsBucket, - _this._name, - _this._stage, - 'resources'); + _this._awsProfile, + _this._projectRootPath, + _this._region, + _this._jawsBucket, + _this._name, + _this._stage, + 'resources'); } /** * Create CloudFormation Stack + * @returns {Promise} * @private */ - _createCfStack() { + if (this._noCf) { + JawsUtils.jawsDebug('No execute CF was specified, skipping'); + return Promise.resolve(); + } let _this = this; @@ -491,45 +463,42 @@ class ProjectCreate extends JawsPlugin { // Create CF stack return AWSUtils.cfCreateResourcesStack( - _this._profile, - _this._region, - _this._projectRootPath, - _this._name, - _this._stage, - _this._domain, - _this._notificationEmail, - _this._jawsBucket - ) - .then(function (cfData) { - return AWSUtils.monitorCf(cfData, _this._profile, _this._region, 'create'); - }); + _this._awsProfile, + _this._region, + _this._projectRootPath, + _this._name, + _this._stage, + _this._domain, + _this._notificationEmail + ) + .then(cfData => { + return AWSUtils.monitorCf(cfData, _this._awsProfile, _this._region, 'create'); + }); } /** * Create Project JSON * @private */ - - _createProjectJson() { + _createProjectJson(cfStackData) { let _this = this, iamRoleArnLambda, iamRoleArnApiGateway; - if (_this._cfData) { - for (let i = 0; i < _this._cfData.Outputs.length; i++) { - if (_this._cfData.Outputs[i].OutputKey === 'IamRoleArnLambda') { - iamRoleArnLambda = _this._cfData.Outputs[i].OutputValue; + if (cfStackData) { + for (let i = 0; i < cfStackData.Outputs.length; i++) { + if (cfStackData.Outputs[i].OutputKey === 'IamRoleArnLambda') { + iamRoleArnLambda = cfStackData.Outputs[i].OutputValue; } - if (_this._cfData.Outputs[i].OutputKey === 'IamRoleArnApiGateway') { - iamRoleArnApiGateway = _this._cfData.Outputs[i].OutputValue; + if (cfStackData.Outputs[i].OutputKey === 'IamRoleArnApiGateway') { + iamRoleArnApiGateway = cfStackData.Outputs[i].OutputValue; } } } - let templatesPath = path.join(__dirname, '..', '..', 'templates'), - jawsJson = JawsUtils.readAndParseJsonSync(path.join(templatesPath, 'jaws.json')); + let jawsJson = JawsUtils.readAndParseJsonSync(path.join(_this._templatesDir, 'jaws.json')); jawsJson.stages[_this._stage] = [{ region: _this._region, @@ -538,38 +507,14 @@ class ProjectCreate extends JawsPlugin { jawsBucket: _this._jawsBucket, }]; - jawsJson.name = _this._name; + jawsJson.name = _this._name; jawsJson.domain = _this._domain; fs.writeFileSync(path.join(_this._projectRootPath, 'jaws.json'), - JSON.stringify(jawsJson, null, 2)); + JSON.stringify(jawsJson, null, 2)); return jawsJson; } - - /** - * Init Runtime - * @private - */ - - _initRuntime() { - - let _this = this; - - JawsCLI.log('Preparing your runtime and installing jaws-core module...'); - - if (_this._runtime === 'nodejs') { - let packageJsonTemplate = JawsUtils.readAndParseJsonSync(path.join(__dirname, '..', 'templates', 'nodejs', 'package.json')); - packageJsonTemplate.name = _this._name; - return fs.writeFileAsync(path.join(_this._projectRootPath, 'package.json'), JSON.stringify(packageJsonTemplate, null, 2)) - .then(function () { - JawsUtils.jawsDebug('test_utils', 'Running NPM install...'); - JawsUtils.npmInstall(_this._projectRootPath); - }); - } else { - throw new JawsError('Unsupported runtime "' + _this.runtime + '"', JawsError.errorCodes.UNKNOWN); - } - } } module.exports = ProjectCreate; \ No newline at end of file diff --git a/lib/defaults/actions/VersionLambda.js b/lib/defaults/actions/VersionLambda.js index 26f3675cc..8ef2ee6a6 100644 --- a/lib/defaults/actions/VersionLambda.js +++ b/lib/defaults/actions/VersionLambda.js @@ -78,32 +78,32 @@ class VersionLambda extends JawsPlugin { this._stage = stage; this._region = region; //may not be set - return this._JAWS.validateProject() - .bind(_this) - .then(_this._promptStage) - .then(() => { - JawsUtils.jawsDebug('publishing version for stage:', _this._stage); - return _this._setLambdaLogicalIds(lambdaPaths); - }) - .then(_this._getRegions) - .each(region => { - //1) For each region, get all the lambdas for stack - let lStackName = AWSUtils.cfGetLambdasStackName(_this._stage, _this._JAWS._projectJson.name); - return AWSUtils.cfGetLambdaResourceSummaries(_this.Jaws._profile, region, lStackName) - .then(lambdaSummaries => { - //2) identify physical function names from logical - return AWSUtils.cfGetLambdaPhysicalsFromLogicals(_this._lambdaLogicalIdsToVersion, lambdaSummaries); - }) - .then(lambdaNamesToVersion => { - //3) publishVersions - return AWSUtils.lambdaPublishVersions(_this.Jaws._profile, region, lambdaNamesToVersion); - }); - }) - .then(versionedLambdas => { - JawsCLI.log('Lambda VersionLambda: Successfully published following lambda versions to the requested regions:'); - JawsCLI.log(versionedLambdas); - return versionedLambdas; - }); + return this.Jaws.validateProject() + .bind(_this) + .then(_this._promptStage) + .then(() => { + JawsUtils.jawsDebug('publishing version for stage:', _this._stage); + return _this._setLambdaLogicalIds(lambdaPaths); + }) + .then(_this._getRegions) + .each(region => { + //1) For each region, get all the lambdas for stack + let lStackName = AWSUtils.cfGetLambdasStackName(_this._stage, _this.Jaws._projectJson.name); + return AWSUtils.cfGetLambdaResourceSummaries(_this.Jaws._awsProfile, region, lStackName) + .then(lambdaSummaries => { + //2) identify physical function names from logical + return AWSUtils.cfGetLambdaPhysicalsFromLogicals(_this._lambdaLogicalIdsToVersion, lambdaSummaries); + }) + .then(lambdaNamesToVersion => { + //3) publishVersions + return AWSUtils.lambdaPublishVersions(_this.Jaws._awsProfile, region, lambdaNamesToVersion); + }); + }) + .then(versionedLambdas => { + JawsCLI.log('Lambda VersionLambda: Successfully published following lambda versions to the requested regions:'); + JawsCLI.log(versionedLambdas); + return versionedLambdas; + }); } /** @@ -117,7 +117,7 @@ class VersionLambda extends JawsPlugin { // If stage exists, skip if (!this._stage) { - stages = Object.keys(_this.JAWS._projectJson.stages); + stages = Object.keys(_this.Jaws._projectJson.stages); // If project only has 1 stage, skip prompt if (stages.length === 1) { @@ -140,9 +140,9 @@ class VersionLambda extends JawsPlugin { } return this.selectInput('VersionLambda: Choose a stage: ', choices, false) - .then(results => { - _this._stage = results[0].value; - }); + .then(results => { + _this._stage = results[0].value; + }); } /** @@ -159,7 +159,7 @@ class VersionLambda extends JawsPlugin { //Deploy to all regions in stage let stage = this._stage, - projJson = this.JAWS._projectJson; + projJson = this.Jaws._projectJson; let regionConfigs = projJson.stages[stage], regions = regionConfigs.map(rCfg => { @@ -179,12 +179,12 @@ class VersionLambda extends JawsPlugin { _setLambdaLogicalIds(lambdaPaths) { let _this = this; return JawsUtils.getFullLambdaPaths(process.cwd(), lambdaPaths) - .then(fullAwsmJsonPaths => { - _this._lambdaLogicalIdsToVersion = fullAwsmJsonPaths.map(alp => { - let awsmJson = JawsUtils.readAndParseJsonSync(alp); - return JawsUtils.getLambdaName(awsmJson); - }); + .then(fullAwsmJsonPaths => { + _this._lambdaLogicalIdsToVersion = fullAwsmJsonPaths.map(alp => { + let awsmJson = JawsUtils.readAndParseJsonSync(alp); + return JawsUtils.getLambdaName(awsmJson); }); + }); } } diff --git a/lib/templates/jaws.json b/lib/templates/jaws.json index d9fcfce9b..813ddcc1d 100644 --- a/lib/templates/jaws.json +++ b/lib/templates/jaws.json @@ -5,5 +5,6 @@ "author": "", "description": "", "domain": "", - "stages": {} + "stages": {}, + "plugins": [] } \ No newline at end of file diff --git a/lib/templates/nodejs/package.json b/lib/templates/nodejs/package.json index 79d9e242f..4d718295a 100644 --- a/lib/templates/nodejs/package.json +++ b/lib/templates/nodejs/package.json @@ -4,7 +4,7 @@ "description": "A JAWS application", "author": "me", "license": "MIT", - "private": false, + "private": true, "repository": { "type": "git", "url": "git://github.com/" diff --git a/lib/templates/resources-cf.json b/lib/templates/resources-cf.json index 8320fb8b0..b217a0d24 100644 --- a/lib/templates/resources-cf.json +++ b/lib/templates/resources-cf.json @@ -9,10 +9,6 @@ "Type": "String", "Default": "myapp.com" }, - "aaJawsBucket": { - "Type": "String", - "Default": "myapp.com" - }, "aaStage": { "Type": "String" }, @@ -139,7 +135,9 @@ ":", [ "arn:aws:logs", - { "Ref" : "AWS::Region" }, + { + "Ref": "AWS::Region" + }, "*:*" ] ] @@ -189,7 +187,9 @@ ":", [ "arn:aws:lambda", - { "Ref" : "AWS::Region" }, + { + "Ref": "AWS::Region" + }, "*:*" ] ] @@ -208,16 +208,6 @@ } ] } - }, - "JawsBucket" : { - "Type" : "AWS::S3::Bucket", - "Properties" : { - "AccessControl" : "Private", - "BucketName" : { - "Ref": "aaJawsBucket" - } - }, - "DeletionPolicy" : "Retain" } }, "Outputs": { diff --git a/lib/utils/aws.js b/lib/utils/aws.js index 7b4fc1221..bccdbf53a 100644 --- a/lib/utils/aws.js +++ b/lib/utils/aws.js @@ -53,8 +53,8 @@ module.exports.getConfigDir = function() { let env = process.env; let home = env.HOME || - env.USERPROFILE || - (env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null); + env.USERPROFILE || + (env.HOMEPATH ? ((env.HOMEDRIVE || 'C:/') + env.HOMEPATH) : null); if (!home) { throw new JawsError('Cant find homedir', JawsError.errorCodes.MISSING_HOMEDIR); @@ -94,16 +94,16 @@ module.exports.profilesSet = function(awsProfile, awsRegion, accessKeyId, secret } fs.appendFileSync( - credsPath, - `[${awsProfile}] + credsPath, + `[${awsProfile}] aws_access_key_id = ${accessKeyId.trim()} aws_secret_access_key = ${secretKey.trim()}\n`); let profileNameForConfig = (awsProfile == 'default') ? 'default' : 'profile ' + awsProfile; fs.appendFileSync( - configPath, - `[${profileNameForConfig}] + configPath, + `[${profileNameForConfig}] region = ${awsRegion}\n`); }; @@ -152,8 +152,8 @@ exports.iamGetRole = function(awsProfile, awsRegion, roleName) { if (error) { return reject(new JawsError( - error.message, - JawsError.errorCodes.UNKNOWN)); + error.message, + JawsError.errorCodes.UNKNOWN)); } else { return resolve(data); } @@ -190,8 +190,8 @@ exports.cfDescribeStacks = function(awsProfile, awsRegion, stackName) { if (error) { return reject(new JawsError( - error.message, - JawsError.errorCodes.UNKNOWN)); + error.message, + JawsError.errorCodes.UNKNOWN)); } else { return resolve(data); } @@ -231,8 +231,8 @@ exports.cfDescribeStackResource = function(awsProfile, awsRegion, stackId, cfRes if (error) { return reject(new JawsError( - error.message, - JawsError.errorCodes.UNKNOWN)); + error.message, + JawsError.errorCodes.UNKNOWN)); } else { return resolve(data); } @@ -296,40 +296,40 @@ exports.cfGetLambdaResourceSummaries = function(awsProfile, awsRegion, stackName return new Promise((resolve, reject)=> { async.whilst( - function() { - return moreResources === true; - }, - function(callback) { - _this.cfListStackResources(awsProfile, awsRegion, stackName, nextStackToken) - .then(function(lambdaCfResources) { - if (lambdaCfResources.StackResourceSummaries) { - lambdas = lambdas.concat(lambdaCfResources.StackResourceSummaries); - } + function() { + return moreResources === true; + }, + function(callback) { + _this.cfListStackResources(awsProfile, awsRegion, stackName, nextStackToken) + .then(function(lambdaCfResources) { + if (lambdaCfResources.StackResourceSummaries) { + lambdas = lambdas.concat(lambdaCfResources.StackResourceSummaries); + } - // Check if more resources are available - if (!lambdaCfResources.NextToken) { - moreResources = false; - } else { - nextStackToken = lambdaCfResources.NextToken; - } + // Check if more resources are available + if (!lambdaCfResources.NextToken) { + moreResources = false; + } else { + nextStackToken = lambdaCfResources.NextToken; + } - callback(); - }) - .catch(function(error) { - JawsCli.log('Warning: JAWS could not find a deployed Cloudformation ' - + 'template containing lambda functions.'); - console.log(error); - moreResources = false; - callback(error); - }); - }, - function(err) { - if (err) { - reject(err); - } else { - resolve(lambdas); - } + callback(); + }) + .catch(function(error) { + JawsCli.log('Warning: JAWS could not find a deployed Cloudformation ' + + 'template containing lambda functions.'); + console.log(error); + moreResources = false; + callback(error); + }); + }, + function(err) { + if (err) { + reject(err); + } else { + resolve(lambdas); } + } ); }); }; @@ -384,11 +384,11 @@ exports.lambdaPublishVersions = function(awsProfile, awsRegion, functionNames) { }); return Promise.all(deferreds) - .then(data => { - return data.map(d => { - return {FunctionName: d.FunctionName, Version: d.Version, FunctionArn: d.FunctionArn}; - }); + .then(data => { + return data.map(d => { + return {FunctionName: d.FunctionName, Version: d.Version, FunctionArn: d.FunctionArn}; }); + }); }; /** @@ -419,9 +419,9 @@ exports.lambdaCreateAlias = function(awsProfile, awsRegion, functionName, functi }; return L.createAliasAsync(params) - .then(d => { - return {AliasArn: d.AliasArn, FunctionVersion: d.FunctionVersion}; - }); + .then(d => { + return {AliasArn: d.AliasArn, FunctionVersion: d.FunctionVersion}; + }); }; /** @@ -466,11 +466,11 @@ exports.cfGetLambdasStackTemplate = function(awsProfile, awsRegion, stage, projN })); return CF.getTemplateAsync({ - StackName: _this.cfGetLambdasStackName(stage, projName) - }) - .then(function(data) { - return data.TemplateBody; - }); + StackName: _this.cfGetLambdasStackName(stage, projName) + }) + .then(function(data) { + return data.TemplateBody; + }); }; /** @@ -502,13 +502,13 @@ exports.putCfFile = function(awsProfile, projRootPath, awsRegion, bucketName, pr }; return this.putS3Object(awsProfile, awsRegion, params) - .then(function() { - //Really AWS - TemplateURL is an https:// URL. You force us to lookup endpt vs bucket/key attrs!?!? wtf not cool - let s3 = new AWS.S3(); + .then(function() { + //Really AWS - TemplateURL is an https:// URL. You force us to lookup endpt vs bucket/key attrs!?!? wtf not cool + let s3 = new AWS.S3(); - //Seriously, not cool... - return 'https://' + s3.endpoint.hostname + `/${bucketName}/${key}`; - }) + //Seriously, not cool... + return 'https://' + s3.endpoint.hostname + `/${bucketName}/${key}`; + }) }; /** @@ -550,10 +550,10 @@ exports.cfCreateLambdasStack = function(JAWS, stage, region, lambdaRoleArn) { }; return _this.putCfFile(awsProfile, projRootPath, region, bucketName, projName, stage, 'lambdas') - .then(function(templateUrl) { - params.TemplateURL = templateUrl; - return CF.createStackAsync(params); - }); + .then(function(templateUrl) { + params.TemplateURL = templateUrl; + return CF.createStackAsync(params); + }); }; /** @@ -591,10 +591,10 @@ exports.cfUpdateLambdasStack = function(JAWS, stage, region, lambdaRoleArn) { }; return _this.putCfFile(awsProfile, projRootPath, region, bucketName, projName, stage, 'lambdas') - .then(function(templateUrl) { - params.TemplateURL = templateUrl; - return CF.updateStackAsync(params); - }); + .then(function(templateUrl) { + params.TemplateURL = templateUrl; + return CF.updateStackAsync(params); + }); }; /** @@ -614,8 +614,7 @@ exports.cfCreateResourcesStack = function(awsProfile, projName, projStage, projDomain, - projNotificationEmail, - jawsBucket) { + projNotificationEmail) { let _this = this; @@ -643,10 +642,6 @@ exports.cfCreateResourcesStack = function(awsProfile, ParameterKey: 'aaStage', ParameterValue: projStage, UsePreviousValue: false, - }, { - ParameterKey: 'aaJawsBucket', - ParameterValue: jawsBucket, - UsePreviousValue: false, }, { ParameterKey: 'aaDataModelStage', ParameterValue: projStage, @@ -707,10 +702,10 @@ exports.cfUpdateResourcesStack = function(JAWS, stage, region) { }; return _this.putCfFile(awsProfile, projRootPath, region, bucketName, projName, stage, 'resources') - .then(function(templateUrl) { - params.TemplateURL = templateUrl; - return CF.updateStackAsync(params); - }); + .then(function(templateUrl) { + params.TemplateURL = templateUrl; + return CF.updateStackAsync(params); + }); }; /** @@ -746,31 +741,31 @@ exports.monitorCf = function(cfData, awsProfile, region, createOrUpdate, checkFr stackData = null; async.whilst( - function() { - return stackStatus !== stackStatusComplete; - }, + function() { + return stackStatus !== stackStatusComplete; + }, - function(callback) { - setTimeout(function() { - _this.cfDescribeStacks(awsProfile, region, cfData.StackId) - .then(function(data) { - stackData = data; - stackStatus = stackData.Stacks[0].StackStatus; + function(callback) { + setTimeout(function() { + _this.cfDescribeStacks(awsProfile, region, cfData.StackId) + .then(function(data) { + stackData = data; + stackStatus = stackData.Stacks[0].StackStatus; - if (!stackStatus || validStatuses.indexOf(stackStatus) === -1) { - console.log((data.Stacks && data.Stacks.length ? data.Stacks[0] : data)); - return reject(new JawsError( - `Something went wrong while ${createOrUpdate}ing your cloudformation`)); - } else { - return callback(); - } - }); - }, checkFreq); - }, + if (!stackStatus || validStatuses.indexOf(stackStatus) === -1) { + console.log((data.Stacks && data.Stacks.length ? data.Stacks[0] : data)); + return reject(new JawsError( + `Something went wrong while ${createOrUpdate}ing your cloudformation`)); + } else { + return callback(); + } + }); + }, checkFreq); + }, - function() { - return resolve(stackData.Stacks[0]); - } + function() { + return resolve(stackData.Stacks[0]); + } ); }); }; @@ -780,30 +775,30 @@ exports.monitorCf = function(cfData, awsProfile, region, createOrUpdate, checkFr * @param awsProfile * @param awsRegion * @param bucketName - * @returns {*} + * @returns {Promise} */ - exports.createBucket = function(awsProfile, awsRegion, bucketName) { this.configAWS(awsProfile, awsRegion); let s3 = Promise.promisifyAll(new AWS.S3()); return s3.getBucketAclAsync({Bucket: bucketName}) - .then(function() { - }) - .error(function(err) { - if (err.code == 'AccessDenied') { - throw new JawsError( - `Bucket ${bucketName} already exists and you do not have permissions to use it`, - JawsError.errorCodes.ACCESS_DENIED - ); - } + .then(function() { + //we are good, bucket already exists and we own it! + }) + .error(function(err) { + if (err.code == 'AccessDenied') { + throw new JawsError( + `Bucket ${bucketName} already exists and you do not have permissions to use it`, + JawsError.errorCodes.ACCESS_DENIED + ); + } - return s3.createBucketAsync({ - Bucket: bucketName, - ACL: 'private', - }); + return s3.createBucketAsync({ + Bucket: bucketName, + ACL: 'private', }); + }); }; /** @@ -900,9 +895,9 @@ exports.putLambdaZip = function(awsProfile, awsRegion, bucketName, projectName, utils.jawsDebug(`lambda zip s3 key: ${key}`); return this.putS3Object(awsProfile, awsRegion, params) - .then(function() { - return key; - }); + .then(function() { + return key; + }); }; /** @@ -931,8 +926,8 @@ exports.cwGetLogStreams = function(logGroupName, limit) { if (error) { return reject(new JawsError( - error.message, - JawsError.errorCodes.UNKNOWN)); + error.message, + JawsError.errorCodes.UNKNOWN)); } else { return resolve(data); } @@ -963,8 +958,8 @@ exports.cwGetStreamEvents = function(logGroupName, logStreamName) { cwLogs.getLogEvents(params, function(err, data) { if (error) { return reject(new JawsError( - error.message, - JawsError.errorCodes.UNKNOWN)); + error.message, + JawsError.errorCodes.UNKNOWN)); } else { return resolve(data); } diff --git a/lib/utils/cli.js b/lib/utils/cli.js index f5c59c64b..f48e1a2ad 100644 --- a/lib/utils/cli.js +++ b/lib/utils/cli.js @@ -4,16 +4,16 @@ * JAWS Services: CLI */ -let Promise = require('bluebird'), - prompt = require('prompt'), - path = require('path'), - os = require('os'), +let Promise = require('bluebird'), + prompt = require('prompt'), + path = require('path'), + os = require('os'), JawsError = require('../jaws-error/index'), - utils = require('../utils'), - fs = require('fs'), - chalk = require('chalk'), - Spinner = require('cli-spinner').Spinner, - keypress = require('keypress'); + utils = require('../utils'), + fs = require('fs'), + chalk = require('chalk'), + Spinner = require('cli-spinner').Spinner, + keypress = require('keypress'); Promise.promisifyAll(fs); Promise.promisifyAll(prompt); @@ -21,16 +21,17 @@ Promise.promisifyAll(prompt); /** * ASCII */ -exports.ascii = function() { +exports.asciiGreeting = function() { + let ver = require('../../package.json').version; let art = ''; - art = art + ' ____ _____ __ __ _________ ' + os.EOL; - art = art + ' | | / _ \\/ \\ / \\/ _____/ ' + os.EOL; - art = art + ' | |/ /_\\ \\ \\/\\/ /\\_____ \\ ' + os.EOL; - art = art + ' /\\__| / | \\ / / \\ ' + os.EOL; - art = art + ' \\________\\____|__ /\\__/\\__/ /_________/ v1 (BETA)' + os.EOL; - art = art + '' + os.EOL; - art = art + ' *** The Server-Less Framework *** ' + os.EOL; + art = art + ' ____ _____ __ __ _________ ' + os.EOL; + art = art + ' | | / _ \\/ \\ / \\/ _____/ ' + os.EOL; + art = art + ' | |/ /_\\ \\ \\/\\/ /\\_____ \\ ' + os.EOL; + art = art + ' /\\__| / | \\ / / \\ ' + os.EOL; + art = art + ' \\________\\____|__ /\\__/\\__/ /_________/ v' + ver + os.EOL; + art = art + '' + os.EOL; + art = art + ' *** The Server-Less Framework *** ' + os.EOL; console.log(chalk.yellow(art)); }; @@ -50,11 +51,11 @@ exports.spinner = function(message) { // Non-interactive spinner object spinner = { - start: function (message) { + start: function(message) { message = message || 'Loading... '; process.stdout.write(`JAWS: ${message}`); }, - stop: function (message) { + stop: function(message) { // Because of how spinner is used with normal library // we do a small hack and still allow for setting message @@ -85,7 +86,7 @@ exports.log = function(message) { exports.prompt = function() { prompt.start(); prompt.delimiter = ''; - prompt.message = 'JAWS: '; + prompt.message = 'JAWS: '; return prompt; }; @@ -138,7 +139,7 @@ Select._render = function() { for (let i = 0; i < _this.state.choices.length; i++) { let choice = _this.state.choices[i], - line = ''; + line = ''; // Increment line count _this.state.lines++; @@ -216,7 +217,7 @@ exports.select = function(message, choices, multi, doneLabel) { if (!_this.isInteractive()) { throw new JawsError('You must specify all necessary options when in a non-interactive mode.', - JawsError.errorCodes.UNKNOWN); + JawsError.errorCodes.UNKNOWN); } // Set keypress listener, if not set @@ -264,7 +265,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') { + && Select.state.choices[Select.state.index - 1].action.toLowerCase() === 'done') { process.stdin.removeListener('keypress', keypressHandler); return Select._close(); } else { @@ -293,21 +294,21 @@ exports.select = function(message, choices, multi, doneLabel) { // Update CheckList Select.state = { - choices: choices, - index: (choices[0] && choices[0].spacer) ? 2 : 1, - lines: 0, - multi: multi, + choices: choices, + index: (choices[0] && choices[0].spacer) ? 2 : 1, + lines: 0, + multi: multi, doneLabel: doneLabel ? doneLabel : 'Done', }; // Add Done and Cancel to choices if (Select.state.multi) { Select.state.choices.push( - {spacer: '- - - - -'}, - { - action: 'Done', - label: Select.state.doneLabel, - }); + {spacer: '- - - - -'}, + { + action: 'Done', + label: Select.state.doneLabel, + }); } // Log Message diff --git a/lib/utils/index.js b/lib/utils/index.js index 586cd8189..203a7d80a 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -11,7 +11,8 @@ let Promise = require('bluebird'), readdirp = require('readdirp'), JawsError = require('../jaws-error'), fs = require('fs'), - mkdirpAsync = require('mkdirp-then'); + mkdirpAsync = require('mkdirp-then'), + shortid = require('shortid'); Promise.promisifyAll(fs); @@ -37,26 +38,26 @@ exports.findAllAwsmPathsOfType = function(startPath, type) { } return _this.readRecursively(startPath, '*awsm.json') - .then(function(jsonPaths) { - return new Promise(function(resolve, reject) { + .then(function(jsonPaths) { + return new Promise(function(resolve, reject) { - let jawsPathsOfType = []; + let jawsPathsOfType = []; - // Check each file to ensure it is a lambda - async.eachLimit(jsonPaths, 10, function(jsonPath, cb) { - let lambdaJawsPath = path.join(startPath, jsonPath), - json = require(lambdaJawsPath); + // Check each file to ensure it is a lambda + async.eachLimit(jsonPaths, 10, function(jsonPath, cb) { + let lambdaJawsPath = path.join(startPath, jsonPath), + json = require(lambdaJawsPath); - if (typeof json[jawsJsonAttr] !== 'undefined') jawsPathsOfType.push(lambdaJawsPath); - return cb(); - }, + if (typeof json[jawsJsonAttr] !== 'undefined') jawsPathsOfType.push(lambdaJawsPath); + return cb(); + }, - function(error) { - if (error) reject(error); - resolve(jawsPathsOfType); - }); - }); + function(error) { + if (error) reject(error); + resolve(jawsPathsOfType); + }); }); + }); }; /** @@ -102,15 +103,15 @@ exports.findProjectRootPath = function(startDir) { exports.execute = function(promise) { promise - .catch(JawsError, function(e) { - console.error(e); - process.exit(e.messageId); - }) - .error(function(e) { - console.error(e); - process.exit(1); - }) - .done(); + .catch(JawsError, function(e) { + console.error(e); + process.exit(e.messageId); + }) + .error(function(e) { + console.error(e); + process.exit(1); + }) + .done(); }; /** @@ -128,15 +129,15 @@ exports.readRecursively = function(path, filter) { root: path, fileFilter: filter, }) - .on('data', function(entry) { - files.push(entry.path); - }) - .on('error', function(error) { - reject(error); - }) - .on('end', function() { - resolve(files); - }); + .on('data', function(entry) { + files.push(entry.path); + }) + .on('error', function(error) { + reject(error); + }) + .on('end', function() { + resolve(files); + }); }); }; @@ -171,24 +172,24 @@ exports.findAllEnvletsForAwsm = function(projectRootPath, modName) { let _this = this; return this.findAllAwsmPathsOfType(path.join(projectRootPath, 'aws_modules', modName), 'lambda') - .then(function(awsmJsonPaths) { - let envletKeys = []; + .then(function(awsmJsonPaths) { + let envletKeys = []; - awsmJsonPaths.forEach(function(awsmJsonPath) { - let awsmJson = _this.readAndParseJsonSync(awsmJsonPath); + awsmJsonPaths.forEach(function(awsmJsonPath) { + let awsmJson = _this.readAndParseJsonSync(awsmJsonPath); - //TODO: change to es6 set... - if (awsmJson.lambda && awsmJson.lambda.envlets) { - awsmJson.lambda.envlets.forEach(function(envlet) { - if (envletKeys.indexOf(envlet) == -1) { - envletKeys.push(envlet); - } - }); - } - }); - - return envletKeys; + //TODO: change to es6 set... + if (awsmJson.lambda && awsmJson.lambda.envlets) { + awsmJson.lambda.envlets.forEach(function(envlet) { + if (envletKeys.indexOf(envlet) == -1) { + envletKeys.push(envlet); + } + }); + } }); + + return envletKeys; + }); }; /** @@ -199,11 +200,11 @@ exports.findAllEnvletsForAwsm = function(projectRootPath, modName) { */ exports.findAllAwsmJsons = function(startPath) { return this.readRecursively(startPath, '*awsm.json') - .then(function(jsonPaths) { - return jsonPaths.map(function(jsonPath) { - return path.resolve(path.join(startPath, jsonPath)); - }); + .then(function(jsonPaths) { + return jsonPaths.map(function(jsonPath) { + return path.resolve(path.join(startPath, jsonPath)); }); + }); }; /** @@ -250,9 +251,13 @@ exports.writeFile = function(filePath, contents) { } return mkdirpAsync(path.dirname(filePath)) - .then(function() { - return fs.writeFileAsync(filePath, contents); - }); + .then(function() { + return fs.writeFileAsync(filePath, contents); + }); +}; + +exports.generateShortId = function(maxLen) { + return shortid.generate().replace(/\W+/g, '').substring(0, maxLen).replace(/[_-]/g, ''); }; /** @@ -294,16 +299,16 @@ exports.getAllLambdaNames = function(projectRootPath) { lambdaNames = []; return this.findAllLambdas(projectRootPath) - .then(function(lambdaAwsmPaths) { - lambdaAwsmPaths.forEach(function(ljp) { - let awsm = _this.readAndParseJsonSync(ljp), - lambdaName = _this.getLambdaName(awsm); + .then(function(lambdaAwsmPaths) { + lambdaAwsmPaths.forEach(function(ljp) { + let awsm = _this.readAndParseJsonSync(ljp), + lambdaName = _this.getLambdaName(awsm); - lambdaNames.push(lambdaName); - }); - - return lambdaNames; + lambdaNames.push(lambdaName); }); + + return lambdaNames; + }); }; /** @@ -371,7 +376,7 @@ exports.npmInstall = function(dir) { process.chdir(cwd); }; -exports.generateResourcesCf = function(projRootPath, projName, projDomain, stage, region, notificationEmail, jawsBucket) { +exports.generateResourcesCf = function(projRootPath, projName, projDomain, stage, region, notificationEmail) { let cfTemplate = require('../templates/resources-cf'); cfTemplate.Parameters.aaProjectName.Default = projName; @@ -379,13 +384,12 @@ exports.generateResourcesCf = function(projRootPath, projName, projDomain, stage cfTemplate.Parameters.aaProjectDomain.Default = projDomain; cfTemplate.Parameters.aaStage.Default = stage; cfTemplate.Parameters.aaDataModelStage.Default = stage; //to simplify bootstrap use same stage - cfTemplate.Parameters.aaJawsBucket.Default = jawsBucket; 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) + path.join(projRootPath, 'cloudformation', stage, region, 'resources-cf.json'), + JSON.stringify(cfTemplate, null, 2) ); };