From 00dbf8a41e60411bc1dbdc859cba441c2f1ac885 Mon Sep 17 00:00:00 2001 From: Austen Collins Date: Mon, 9 Nov 2015 21:12:17 -0800 Subject: [PATCH] utils: refactor common utils for simplicity --- lib/defaults/actions/EndpointDeploy.js | 157 ++++++++++--- lib/defaults/actions/LambdaAlias.js | 2 +- lib/defaults/actions/LambdaDeploy.js | 2 +- lib/defaults/actions/LambdaVersion.js | 2 +- lib/utils/aws/ApiGateway.js | 29 +++ lib/utils/index.js | 312 +++++++++++-------------- tests/tests/actions/EndpointDeploy.js | 9 +- 7 files changed, 302 insertions(+), 211 deletions(-) diff --git a/lib/defaults/actions/EndpointDeploy.js b/lib/defaults/actions/EndpointDeploy.js index 52124f973..992eb0cc3 100644 --- a/lib/defaults/actions/EndpointDeploy.js +++ b/lib/defaults/actions/EndpointDeploy.js @@ -68,13 +68,30 @@ class EndpointDeploy extends JawsPlugin { * @param region * @returns {Promise.} */ + endpointDeploy(stage, region, noExeCf) { - let _this = this; - this._stage = stage; - this._region = region; - this._noExeCf = (noExeCf == true || noExeCf == 'true'); - this._endpointAwsmPaths = Array.prototype.slice.call(arguments, 3); + let _this = this; + this._stage = stage; + this._region = region; + this._noExeCf = (noExeCf == true || noExeCf == 'true'); + this._servicePaths = Array.prototype.slice.call(arguments, 3); + + // Resolve full paths for services + if (this._servicePaths) { + this._servicePaths = JawsUtils.getServices( + _this.Jaws._projectRootPath, + 'endpoint', + this._servicePaths + ); + } + + return console.log(this._servicePaths); + + // TODO: If no servicePaths, check if interactive, check if in a service, deploy that, or throw error + + + return BbPromise.try(function() {}) .bind(_this) @@ -153,6 +170,7 @@ class EndpointDeploy extends JawsPlugin { } _deployRegions() { + let _this = this; return BbPromise.try(function() { @@ -162,10 +180,12 @@ class EndpointDeploy extends JawsPlugin { // Deploy endpoints to each region _this._regionJson = JawsUtils.getProjRegionConfigForStage(_this.Jaws._projectJson, _this._stage, region); + return _this._fetchDeployedLambdas() .bind(_this) - .then(_this._findOrCreateApi); - + .then(_this._findOrCreateApi) + .then(_this._getApiResources) + .then(_this._buildEndpoints); }); } @@ -198,11 +218,6 @@ class EndpointDeploy extends JawsPlugin { let _this = this; - // If missing restApiId, check if regional REST API exists in another stage - if (!_this._regionJson.restApiId) { - _this._regionJson.restApiId = JawsUtils.findRegionalApi(_this.Jaws._projectJson, _this._region); - } - // Check Project's jaws.json for restApiId, otherwise create an api if (_this._regionJson.restApiId) { @@ -220,25 +235,115 @@ class EndpointDeploy extends JawsPlugin { }); } else { - // Create regional REST API + // List all REST APIs + return AwsApiGateway.getRestApis() + .then(function(response) { - let apiName = _this.Jaws._projectJson.name; - apiName = apiName.substr(0, 1023); // keep the name length below the limits + // Find REST API w/ same name as project + for (let i = 0; i < response.items.length;i++) { + if (response.items[i].name === _this.Jaws._projectJson.name) { + _this._restApiId = response.items[i].id; - return AwsApiGateway.createRestApi(apiName, - _this.Jaws._projectJson.description ? _this.Jaws._projectJson.description : 'A REST API for a JAWS project.' - ).then(function(response) { + // Save to jaws.json + JawsUtils.saveRegionalApi(_this.Jaws._projectJson, _this._region, _this._restApiId, _this.Jaws._projectRootPath); + break; + } + } - _this._restApiId = response.id; - JawsCLI.log( - 'Endpoint Deployer: "' - + _this._stage + ' - ' - + _this._regionJson.region - + '": created a new REST API on AWS API Gateway with ID: ' - + response.id); - }); + // If no REST API found, create one + if (!_this._restApiId) { + + let apiName = _this.Jaws._projectJson.name; + apiName = apiName.substr(0, 1023); // keep the name length below the limits + + return AwsApiGateway.createRestApi( + apiName, + _this.Jaws._projectJson.description ? _this.Jaws._projectJson.description : 'A REST API for a JAWS project.' + ).then(function (response) { + + _this._restApiId = response.id; + + // Save to jaws.json + JawsUtils.saveRegionalApi(_this.Jaws._projectJson, _this._region, _this._restApiId, _this.Jaws._projectRootPath); + + JawsCLI.log( + 'Endpoint Deployer: "' + + _this._stage + ' - ' + + _this._regionJson.region + + '": created a new REST API on AWS API Gateway with ID: ' + + response.id); + }); + } + }); } } + + /** + * Get API Resources + * @returns {Promise} + * @private + */ + + _getApiResources() { + + let _this = this; + + // List all Resources for this REST API + return AwsApiGateway.getResources(_this._restApiId) + .then(function(response) { + + _this._resources = response.items; + + // Get Parent Resource ID + for (let i = 0; i < _this._resources.length; i++) { + if (_this._resources[i].path === '/') { + _this._parentResourceId = _this._resources[i].id; + } + } + + JawsCLI.log( + 'Endpoint Deployer: "' + + _this._stage + ' - ' + + _this._regionJson.region + + '": found ' + + _this._resources.length + + ' existing resources on API Gateway'); + }); + } + + /** + * Build Endpoints + * @returns {Promise} + * @private + */ + + _buildEndpoints() { + + let _this = this; + + return BbPromise.try(function() { + return _this._servicePaths; + }).each(function(servicePath) { + + + + return console.log(service); + + return _this._createEndpointResources(endpoint) + .bind(_this) + .then(_this._createEndpointMethod) + .then(_this._createEndpointIntegration) + .then(_this._manageLambdaAccessPolicy) + .then(_this._createEndpointMethodResponses) + .then(_this._createEndpointMethodIntegResponses) + .then(function() { + + // Clean-up hack + // TODO figure out how "apig" temp property is being written to the awsm's json and remove that + if (endpoint.apiGateway.apig) delete endpoint.apiGateway.apig; + }); + }); + } } module.exports = EndpointDeploy; \ No newline at end of file diff --git a/lib/defaults/actions/LambdaAlias.js b/lib/defaults/actions/LambdaAlias.js index faf8a875b..cd9621194 100644 --- a/lib/defaults/actions/LambdaAlias.js +++ b/lib/defaults/actions/LambdaAlias.js @@ -192,7 +192,7 @@ usage: jaws lambda alias `, */ _setLambdaLogicalIds() { let _this = this; - return JawsUtils.getFullLambdaPaths(process.cwd(), []) //leaving here for to be created GREATEST and 'ALL' option + return JawsUtils.getFullServicePaths(process.cwd(), []) //leaving here for to be created GREATEST and 'ALL' option .then(fullAwsmJsonPaths => { _this._lambdaLogicalIdsToAlias = fullAwsmJsonPaths.map(alp => { let awsmJson = JawsUtils.readAndParseJsonSync(alp); diff --git a/lib/defaults/actions/LambdaDeploy.js b/lib/defaults/actions/LambdaDeploy.js index 99fb22e46..0caa7147a 100644 --- a/lib/defaults/actions/LambdaDeploy.js +++ b/lib/defaults/actions/LambdaDeploy.js @@ -750,7 +750,7 @@ ex: */ _setLambdaAwsmPaths(lambdaPaths) { let _this = this; - return JawsUtils.resolveLambdaPaths(process.cwd(), this.Jaws._projectRootPath, lambdaPaths) + return JawsUtils.resolveServicePaths(process.cwd(), this.Jaws._projectRootPath, lambdaPaths) .then(fullAwsmJsonPaths => { _this._lambdaAwsmPathsToDeploy = fullAwsmJsonPaths; }); diff --git a/lib/defaults/actions/LambdaVersion.js b/lib/defaults/actions/LambdaVersion.js index 7e2cb3eab..8d983ba1d 100644 --- a/lib/defaults/actions/LambdaVersion.js +++ b/lib/defaults/actions/LambdaVersion.js @@ -182,7 +182,7 @@ ex: */ _setLambdaLogicalIds(lambdaPaths) { let _this = this; - return JawsUtils.resolveLambdaPaths(process.cwd(), this.Jaws._projectRootPath, lambdaPaths) + return JawsUtils.resolveServicePaths(process.cwd(), this.Jaws._projectRootPath, lambdaPaths) .then(fullAwsmJsonPaths => { _this._lambdaLogicalIdsToVersion = fullAwsmJsonPaths.map(alp => { let awsmJson = JawsUtils.readAndParseJsonSync(alp); diff --git a/lib/utils/aws/ApiGateway.js b/lib/utils/aws/ApiGateway.js index fdb9cc0d8..a4534a9e4 100644 --- a/lib/utils/aws/ApiGateway.js +++ b/lib/utils/aws/ApiGateway.js @@ -31,3 +31,32 @@ module.exports.createRestApi = function(name, description) { return ApiGateway.createRestApiAsync(params); }; +/** + * Get REST APIs + */ + +module.exports.getRestApis = function() { + + var params = { + limit: 500, + //position: + }; + + return ApiGateway.getRestApisAsync(params); +}; + +/** + * Get Resources + */ + +module.exports.getResources = function(restApiId) { + + var params = { + restApiId: restApiId, + limit: 500, + //position: + }; + + return ApiGateway.getResourcesAsync(params); +}; + diff --git a/lib/utils/index.js b/lib/utils/index.js index ce01b77b2..b0e75a24e 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -18,81 +18,13 @@ let Promise = require('bluebird'), Promise.promisifyAll(fs); -/** - * Find all lambda.awsm.json paths of given type - * - * @param startPath - * @param type type lambda|endpoint - * @returns {Promise.} list of full paths tho lambda.awsm.json's - */ -exports.findAllAwsmPathsOfType = function(startPath, type) { - let _this = this, - jawsJsonAttr; - switch (type) { - case 'lambda': - jawsJsonAttr = 'lambda'; - break; - case 'endpoint': - jawsJsonAttr = 'apiGateway'; - break; - default: - return Promise.reject(new JawsError(`Invalid type ${type}`, JawsError.errorCodes.UNKNOWN)); - break; - } - - return _this.readRecursively(startPath, '*lambda.awsm.json') - .then(function(jsonPaths) { - _this.jawsDebug('lambda.awsm.json paths found: ', jsonPaths); - - return new Promise(function(resolve, reject) { - - 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 = _this.readAndParseJsonSync(lambdaJawsPath); - - if (typeof json.cloudFormation[jawsJsonAttr] !== 'undefined') jawsPathsOfType.push(lambdaJawsPath); - return cb(); - }, - - function(error) { - if (error) reject(error); - - _this.jawsDebug('lambda.awsm.json FULL paths found: ', jawsPathsOfType); - let s = new Set(jawsPathsOfType); - resolve(Array.from(s)); - }); - }); - }); -}; - -/** - * Given a list of paths to lambda dirs, will resolve to abs path (ex: ~,./). - * If lambdaPaths[0] == 'all' will find all lambda paths - * If lambdaPaths is empty or not set, will use lambda at cwd - * - * @param cwd - * @param projectRootPath - * @param lambdaPaths Array - * @returns {Promise.} list of full paths to lambda.awsm.json files that are type lambda - */ -exports.resolveLambdaPaths = function(cwd, projectRootPath, lambdaPaths) { - if (lambdaPaths[0] && lambdaPaths[0].toLowerCase() == 'all') { - this.jawsDebug('all lambda paths specified, searching..'); - return this.findAllLambdas(projectRootPath); - } else { - return this.getFullLambdaPaths(cwd, lambdaPaths); - } -}; - /** * Find project root path * * @param startDir * @returns {*} */ + exports.findProjectRootPath = function(startDir) { let _this = this; @@ -131,16 +63,16 @@ exports.findProjectRootPath = function(startDir) { exports.execute = function(promise) { promise - .catch(JawsError, function(e) { - //console.error(e); - throw e; - process.exit(e.messageId); - }) - .error(function(e) { - console.error(e); - process.exit(1); - }) - .done(); + .catch(JawsError, function(e) { + //console.error(e); + throw e; + process.exit(e.messageId); + }) + .error(function(e) { + console.error(e); + process.exit(1); + }) + .done(); }; /** @@ -158,36 +90,95 @@ 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); + }); }); }; /** - * Find all dirs that are lambdas + * Get Services * - * @param projectRootPath - * @returns {Promise.} list of full paths to lambda.awsm.json files that are type lambda + * @param baseDir + * @param type + * @param servicePaths + * @returns {*} */ -exports.findAllLambdas = function(projectRootPath) { - return this.findAllAwsmPathsOfType(projectRootPath, 'lambda'); -}; -/** - * Find all dirs that are endpoints - * - * @param projectRootPath - * @returns {Promise.} list of full paths to awsm.json files that are type endpoint - */ -exports.findAllEndpoints = function(projectRootPath) { - return this.findAllAwsmPathsOfType(projectRootPath, 'endpoint'); +exports.getServices = function(baseDir, type, servicePaths) { + + let _this = this, + resolvedPaths = []; + + // Get services by service paths + if (servicePaths) { + + for (let servicePath of servicePaths) { + let tempPath = ''; + + servicePath = expandHomeDir(servicePath); + + if (servicePath.indexOf('/') == 0) { + tempPath = path.join(baseDir, servicePath, 'lambda.awsm.json'); + } else { + tempPath = path.resolve(baseDir, servicePath, './lambda.awsm.json'); + } + + resolvedPaths.push(tempPath); + } + + resolvedPaths.forEach(p => { + if (!_this.fileExistsSync(p)) { + throw new JawsError(`Invalid service path ${p}`, JawsError.errorCodes.INVALID_RESOURCE_NAME); + } + }); + + return Promise.resolve(resolvedPaths); + } + + // Get services by base directory + if (!servicePaths) { + + return _this.readRecursively(baseDir, '*lambda.awsm.json') + .then(function (servicePaths) { + + _this.jawsDebug('lambda.awsm.json paths found: ', servicePaths); + + return new Promise(function (resolve, reject) { + + let resolvedPaths = []; + + // Check each file to ensure it is a lambda + async.eachLimit(servicePaths, 10, function (servicePath, cb) { + + servicePath = path.join(baseDir, servicePath); + let serviceJson = _this.readAndParseJsonSync(servicePath); + + if (type) { + if (typeof serviceJson.cloudFormation[type] !== 'undefined') resolvedPaths.push(servicePath); + } else { + resolvedPaths.push(servicePath); + } + + return cb(); + }, function (error) { + + if (error) reject(error); + + _this.jawsDebug('lambda.awsm.json FULL paths found: ', resolvedPaths); + + let s = new Set(resolvedPaths); + resolve(Array.from(s)); + }); + }); + }); + } }; /** @@ -200,25 +191,25 @@ exports.findAllEndpoints = function(projectRootPath) { exports.findAllEnvletsForAwsm = function(projectRootPath, modName) { let _this = this; - return this.findAllAwsmPathsOfType(path.join(projectRootPath, 'aws_modules', modName), 'lambda') - .then(function(awsmJsonPaths) { - let envletKeys = []; + return this.getServices(path.join(projectRootPath, 'aws_modules', modName), 'lambda') + .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); - } - }); - } + //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; }); - - return envletKeys; - }); }; /** @@ -229,53 +220,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)); + }); }); - }); -}; - -/** - * - * @param baseDir typically CWD - * @param lambdaPaths optional abs or rel (to baseDir) paths to lambda dirs. If ommitted returns lambda at baseDir - * @returns {Promise.} - */ -exports.getFullLambdaPaths = function(baseDir, lambdaPaths) { - let _this = this, - fullPaths = []; - - if (!lambdaPaths || lambdaPaths.length == 0) { //getting lambda at CWD - let awsmPath = path.join(process.cwd(), 'lambda.awsm.json'); - fullPaths.push(awsmPath); - } - - for (let lambdaPath of lambdaPaths) { - let awsmPath = ''; - - lambdaPath = expandHomeDir(lambdaPath); - - if (lambdaPath.indexOf('/') == 0) { - awsmPath = path.join(lambdaPath, 'lambda.awsm.json'); - } else { - awsmPath = path.resolve(baseDir, lambdaPath, './lambda.awsm.json'); - } - - fullPaths.push(awsmPath); - } - - fullPaths.forEach(p => { - if (!_this.fileExistsSync(p)) { - throw new JawsError(`Invalid lambda path ${p}`, JawsError.errorCodes.INVALID_RESOURCE_NAME); - } - }); - - this.jawsDebug('got full lambda paths:', fullPaths); - - let s = new Set(fullPaths); - - return Promise.resolve(Array.from(s)); }; /** @@ -293,9 +242,9 @@ 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) { @@ -340,17 +289,17 @@ exports.getAllLambdaNames = function(projectRootPath) { let _this = this, lambdaNames = []; - return this.findAllLambdas(projectRootPath) - .then(function(lambdaAwsmPaths) { - lambdaAwsmPaths.forEach(function(ljp) { - let awsm = _this.readAndParseJsonSync(ljp), - lambdaName = _this.getLambdaName(awsm); + return this.getServices(projectRootPath, 'lambda') + .then(function(lambdaAwsmPaths) { + lambdaAwsmPaths.forEach(function(ljp) { + let awsm = _this.readAndParseJsonSync(ljp), + lambdaName = _this.getLambdaName(awsm); - lambdaNames.push(lambdaName); + lambdaNames.push(lambdaName); + }); + + return lambdaNames; }); - - return lambdaNames; - }); }; /** @@ -432,8 +381,8 @@ exports.generateResourcesCf = function(projRootPath, projName, projDomain, stage cfTemplate.Description = projName + ' resources'; return this.writeFile( - path.join(projRootPath, 'cloudformation', 'resources-cf.json'), - JSON.stringify(cfTemplate, null, 2) + path.join(projRootPath, 'cloudformation', 'resources-cf.json'), + JSON.stringify(cfTemplate, null, 2) ); }; @@ -452,8 +401,8 @@ exports.addStageToResourcesCf = function(projRootPath, stage) { cfTemplate.Parameters.aaDataModelStage.AllowedValues.push(stage); return this.writeFile( - projResoucesCfPath, - JSON.stringify(cfTemplate, null, 2) + projResoucesCfPath, + JSON.stringify(cfTemplate, null, 2) ); }; @@ -518,7 +467,8 @@ exports.findRegionalApi = function(projectJawsJson, regionName) { */ exports.saveRegionalApi = function(projectJawsJson, regionName, restApiId, rootPath) { - for (stages of Object.keys(projectJawsJson.stages)) { + + for (let stages of Object.keys(projectJawsJson.stages)) { for (let i = 0; i < stages.length; i++) { if (stages[i].region === regionName) { stages[i].restApiId = restApiId; diff --git a/tests/tests/actions/EndpointDeploy.js b/tests/tests/actions/EndpointDeploy.js index b9a745274..5f80e4548 100644 --- a/tests/tests/actions/EndpointDeploy.js +++ b/tests/tests/actions/EndpointDeploy.js @@ -20,9 +20,11 @@ describe('Test action: Endpoint Deploy', function() { testUtils.createTestProject(config) .then(projPath => { process.chdir(projPath); + Jaws = new JAWS({ interactive: false, }); + done(); }); }); @@ -36,7 +38,12 @@ describe('Test action: Endpoint Deploy', function() { it('Endpoint Deploy', function(done) { this.timeout(0); - Jaws.actions.endpointDeploy(config.stage, config.region, config.noExecuteCf) + Jaws.actions.endpointDeploy( + config.stage, + config.region, + config.noExecuteCf, + 'aws_modules/users/create' + ) .then(function() { done(); })