From a2dce9c8be94f39f6f71d2fbaa22d0c512ff056a Mon Sep 17 00:00:00 2001 From: Austen Collins Date: Thu, 24 Sep 2015 12:03:21 -0700 Subject: [PATCH] deploy resources: complete command --- lib/commands/deploy_lambda.js | 2 +- lib/commands/deploy_resources.js | 130 ++++++++++++++++++++++--------- lib/commands/new_project.js | 13 ++++ lib/commands/new_stage_region.js | 2 +- lib/utils/aws.js | 45 ++++++++++- tests/all.js | 1 + tests/cli/deploy_resources.js | 57 ++++++++++++++ 7 files changed, 209 insertions(+), 41 deletions(-) create mode 100644 tests/cli/deploy_resources.js diff --git a/lib/commands/deploy_lambda.js b/lib/commands/deploy_lambda.js index 8461eb057..73c7f3361 100644 --- a/lib/commands/deploy_lambda.js +++ b/lib/commands/deploy_lambda.js @@ -288,7 +288,7 @@ Deployer.prototype.deploy = Promise.method(function() { }) .then(function() { spinner.stop(true); - }) + }); } }) .then(function() { diff --git a/lib/commands/deploy_resources.js b/lib/commands/deploy_resources.js index 295913f38..49f5f14e5 100644 --- a/lib/commands/deploy_resources.js +++ b/lib/commands/deploy_resources.js @@ -2,10 +2,9 @@ /** * JAWS Command: deploy resources - * - Deploys project's API Gateway REST API to the specified stage and one or all regions + * - Deploys project's resources-cf.json */ - var JawsError = require('../jaws-error'), JawsCli = require('../utils/cli'), Promise = require('bluebird'), @@ -27,8 +26,8 @@ Promise.promisifyAll(fs); * @returns {*} */ -module.exports.run = function(JAWS, stage, region, allTagged) { - var command = new CMD(JAWS, stage, region, allTagged); +module.exports.run = function(JAWS, stage, region) { + var command = new CMD(JAWS, stage, region); return command.run(); }; @@ -37,18 +36,13 @@ module.exports.run = function(JAWS, stage, region, allTagged) { * @param JAWS * @param stage * @param region - * @param allTagged * @constructor */ -function CMD(JAWS, stage, region, allTagged) { +function CMD(JAWS, stage, region) { var _this = this; _this._stage = stage; - _this._allTagged = allTagged; _this._JAWS = JAWS; - _this._prjJson = JAWS._meta.projectJson; - _this._prjRootPath = JAWS._meta.projectRootPath; - _this._prjCreds = JAWS._meta.credentials; if (region && stage) { _this._regions = _this._JAWS._meta.projectJson.stages[_this._stage].filter(function(r) { @@ -70,43 +64,34 @@ CMD.prototype.run = Promise.method(function() { // Flow return _this._JAWS.validateProject() .bind(_this) - .then(function() { - // If !allTagged, tag current directory - if (!_this._allTagged) { - return CMDtag.tag('endpoint', null, false); + .then(_this._promptStage) + .then(function(answer) { + if (answer) _this._stage = answer[0].value; + }) + .then(_this._promptRegions) + .then(function(answer) { + if (answer) { + _this._regions = [utils.getProjRegionConfigForStage(_this._JAWS, _this._stage, answer[0].value)]; } }) - .then(_this._promptStage) - .then(_this._promptRegions) .then(function() { return _this._regions; }) .each(function(regionJson) { - JawsCli.log('Endpoint Deployer: Deploying endpoint(s) to region "' + regionJson.region + '"...'); + JawsCli.log('Resources Deployer "' + + _this._stage + + '": Deploying resources to region "' + + regionJson.region + + '"...'); - var deployer = new ApiDeployer( + var deployer = new ResourceDeployer( _this._JAWS, _this._stage, - regionJson, - _this._prjRootPath, - _this._prjJson, - _this._prjCreds + regionJson ); - return deployer.deploy() - .then(function(url) { - JawsCli.log('Endpoint Deployer: Endpoints for stage "' - + _this._stage - + '" successfully deployed to API Gateway in the region "' - + regionJson.region - + '". Access them @ ' - + url); - }); - }) - .then(function() { - // Untag All tagged endpoints - return _this._allTagged ? CMDtag.tagAll(_this._JAWS, 'endpoint', true) : CMDtag.tag('endpoint', null, true); + return deployer.deploy(); }); }); @@ -121,7 +106,7 @@ CMD.prototype._promptStage = Promise.method(function() { // If stage, skip if (_this._stage) return; - var stages = Object.keys(_this._prjJson.stages); + var stages = Object.keys(_this._JAWS._meta.projectJson.stages); if (!stages.length) { throw new JawsError('You have no stages in this project'); } @@ -175,3 +160,76 @@ CMD.prototype._promptRegions = Promise.method(function() { return JawsCli.select('Select a region in this stage to deploy to: ', choices, false); }); +/** + * Resource Deployer + * @param JAWS + * @param stage + * @param region + * @constructor + */ + +function ResourceDeployer(JAWS, stage, region) { + + var _this = this; + _this._JAWS = JAWS; + _this._stage = stage; + _this._regionJson = region; + +} + +/** + * Resource Deployer: Deploy + */ + +ResourceDeployer.prototype.deploy = Promise.method(function() { + + var _this = this; + + JawsCli.log('Resources Deployer "' + + _this._stage + + ' - ' + + _this._regionJson.region + + '": Performing Cloudformation stack update. ' + + 'This could take a while depending on how many resources you are updating...'); + + var spinner = JawsCli.spinner(); + spinner.start(); + + return _this._updateStack() + .bind(_this) + .then(function(cfData) { + return AWSUtils.monitorCf(cfData, _this._JAWS._meta.profile, _this._regionJson.region, 'update'); + }) + .then(function(data) { + spinner.stop(true); + JawsCli.log('Resources Deployer "' + + _this._stage + + ' - ' + + _this._regionJson.region + + '": Cloudformation stack update completed successfully!'); + }) + .catch(function(error) { + spinner.stop(true); + JawsCli.log('Resources Deployer "' + + _this._stage + + ' - ' + + _this._regionJson.region + + '": Cloudformation stack update failed because of the following error...'); + console.log(error); + }); +}); + +/** + * Resource Deployer: Update Stack + */ + +ResourceDeployer.prototype._updateStack = Promise.method(function() { + + var _this = this; + + // Fetch Cloudformation template + return AWSUtils.cfUpdateResourcesStack( + _this._JAWS, + _this._stage, + _this._regionJson.region); +}); diff --git a/lib/commands/new_project.js b/lib/commands/new_project.js index 3f35600a7..478ab97a4 100644 --- a/lib/commands/new_project.js +++ b/lib/commands/new_project.js @@ -144,6 +144,19 @@ CMD.prototype._prompt = Promise.method(function() { }, }; + // 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: 'you@yourapp.com', + conform: function(email) { + if (!email) return false; + return true; + }, + }; + // Prompt: s3 bucket - holds env vars for this project _this.Prompter.override.s3Bucket = _this._s3Bucket; _this._prompts.properties.s3Bucket = { diff --git a/lib/commands/new_stage_region.js b/lib/commands/new_stage_region.js index 914840c3c..90df023b9 100644 --- a/lib/commands/new_stage_region.js +++ b/lib/commands/new_stage_region.js @@ -215,7 +215,7 @@ CMD.prototype._validate = Promise.method(function() { // 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; })) { throw new JawsError('Region "' + _this._region + '" is already defined in the stage "' + _this._stage + '"'); } diff --git a/lib/utils/aws.js b/lib/utils/aws.js index 416d4a137..96cbb10ea 100644 --- a/lib/utils/aws.js +++ b/lib/utils/aws.js @@ -19,7 +19,7 @@ exports.validLambdaRegions = [ 'us-east-1', 'us-west-2', //oregon 'eu-west-1', //Ireland - 'ap-northeast-1' //Tokyo + 'ap-northeast-1', //Tokyo ]; /** @@ -456,7 +456,7 @@ exports.cfUpdateLambdasStack = function(JAWS, stage, region, lambdaRoleArn) { }; /** - * CloudFormation: Create Stack + * CloudFormation: Create Resources Stack * @param awsProfile * @param awsRegion * @param projRootPath @@ -520,6 +520,45 @@ exports.cfCreateResourcesStack = function(awsProfile, awsRegion, projRootPath, p }); }; +/** + * CloudFormation: Update Resources Stack + * @param JAWS + * @param stage + * @param region + * @returns {*} + */ + +exports.cfUpdateResourcesStack = function(JAWS, stage, region) { + + var _this = this, + awsProfile = JAWS._meta.profile, + projRootPath = JAWS._meta.projectRootPath, + bucketName = JAWS._meta.projectJson.jawsBuckets[region], + projName = JAWS._meta.projectJson.name; + + _this.configAWS(awsProfile, region); + + var CF = Promise.promisifyAll(new AWS.CloudFormation({ + apiVersion: '2010-05-15', + })), + stackName = _this.cfGetResourcesStackName(stage, projName); + + var params = { + StackName: stackName, + Capabilities: [ + 'CAPABILITY_IAM', + ], + UsePreviousTemplate: false, + Parameters: [] + }; + + return _this.putCfFile(awsProfile, projRootPath, region, bucketName, projName, stage, 'resources') + .then(function(templateUrl) { + params.TemplateURL = templateUrl; + return CF.updateStackAsync(params); + }); +}; + /** * CloudFormation: Monitor CF Stack Status (Create/Update) * @param cfData @@ -878,7 +917,7 @@ exports.lambdaRemovePermission = function(awsProfile, awsRegion, functionName, s var params = { FunctionName: functionName, /* required */ - StatementId: statementId /* required */ + StatementId: statementId, /* required */ }; return new Promise(function(resolve, reject) { diff --git a/tests/all.js b/tests/all.js index 64cb5e208..fe550eba8 100644 --- a/tests/all.js +++ b/tests/all.js @@ -26,6 +26,7 @@ describe('AllTests', function() { */ //require('./cli/dash'); //require('./cli/deploy_lambda'); + //require('./cli/deploy_resources'); //require('./cli/deploy_endpoint'); //require('./cli/new_stage_region'); //require('./cli/new_project'); diff --git a/tests/cli/deploy_resources.js b/tests/cli/deploy_resources.js new file mode 100644 index 000000000..f44556ae7 --- /dev/null +++ b/tests/cli/deploy_resources.js @@ -0,0 +1,57 @@ +'use strict'; + +/** + * JAWS Test: Deploy Resources + */ + +var Jaws = require('../../lib/index.js'), + CmdDeployResources = require('../../lib/commands/deploy_resources'), + CmdTag = require('../../lib/commands/tag'), + JawsError = require('../../lib/jaws-error'), + testUtils = require('../test_utils'), + Promise = require('bluebird'), + path = require('path'), + assert = require('chai').assert, + config = require('../config'), + lambdaPaths = {}, + projPath, + JAWS; + +describe('Test deploy resources command', function() { + + before(function(done) { + testUtils.createTestProject( + config.name, + config.region, + config.stage, + config.iamRoleArnLambda, + config.iamRoleArnApiGateway, + config.usEast1Bucket) + .then(function(pp) { + projPath = pp; + process.chdir(path.join(projPath)); + JAWS = new Jaws(); + }) + .then(done); + }); + + describe('Positive tests', function() { + + it('Deploy Resources', function(done) { + + this.timeout(0); + + CmdDeployResources.run(JAWS, config.stage, config.region) + .then(function() { + done(); + }) + .catch(JawsError, function(e) { + done(e); + }) + .error(function(e) { + console.log(e); + done(e); + }); + }); + }); +}); \ No newline at end of file