From fde3eee4fd9d3896bf99f72773a44e247ff41ace Mon Sep 17 00:00:00 2001 From: ac360 Date: Fri, 8 Jan 2016 01:17:42 -0800 Subject: [PATCH] Utils: refactor getResources, populate, Tests: fix all, Serverless: add methods to work with sPaths --- lib/Serverless.js | 1 + lib/ServerlessComponent.js | 18 ++- lib/ServerlessFunction.js | 21 +-- lib/ServerlessModule.js | 34 +++-- lib/ServerlessProject.js | 127 +++++++++++------- lib/actions/CodeDeployLambdaNodeJs.js | 47 +++---- lib/actions/CodeEventDeployLambda.js | 2 +- lib/actions/CodePackageLambdaNodeJs.js | 7 +- lib/actions/EndpointBuildApiGateway.js | 7 +- lib/actions/EndpointDeploy.js | 2 +- lib/actions/FunctionDeploy.js | 36 ++--- lib/actions/FunctionRunLambdaNodeJs.js | 7 +- lib/actions/ProjectCreate.js | 1 + lib/utils/aws/Lambda.js | 4 +- lib/utils/index.js | 105 ++++++++------- tests/all.js | 4 +- tests/config.js | 2 +- .../module1/function1/handler.js | 4 +- .../nodejscomponent/module1/s-module.json | 1 - .../test-prj/nodejscomponent/s-component.json | 4 +- tests/tests/actions/EndpointDeploy.js | 11 +- tests/tests/actions/FunctionCreate.js | 2 +- tests/tests/actions/FunctionDeploy.js | 48 ++++--- tests/tests/actions/FunctionRun.js | 57 ++++---- 24 files changed, 305 insertions(+), 247 deletions(-) diff --git a/lib/Serverless.js b/lib/Serverless.js index 43cd5514f..05846187e 100644 --- a/lib/Serverless.js +++ b/lib/Serverless.js @@ -360,6 +360,7 @@ class Serverless { throw new SError('Invalid path'); } } else if (type.indexOf('endpoint') > -1) { + let pathArray = path.split('/'); if (!pathArray[0] || !pathArray[1] || !pathArray[2] || path.indexOf('@') == -1 || path.indexOf('~') == -1) { throw new SError('Invalid path'); } diff --git a/lib/ServerlessComponent.js b/lib/ServerlessComponent.js index 1108ac5d5..cac7abf3a 100644 --- a/lib/ServerlessComponent.js +++ b/lib/ServerlessComponent.js @@ -31,15 +31,18 @@ class ServerlessComponent { /** * Update Config - * - Takes config.sPath or config.component + * - Takes config.component */ updateConfig(config) { if (config) { // Set sPath - if (config.component) this.config.sPath = this.S.buildPath({ - component: config.component - }); + if (config.component) { + this.config.component = config.component; + this.config.sPath = this.S.buildPath({ + component: config.component + }); + } // Make full path if (this.S.config.projectPath && this.config.sPath) { let parse = this.S.parsePath(this.config.sPath); @@ -72,12 +75,15 @@ class ServerlessComponent { // Add Modules & Functions component.modules = {}; - let componentContents = fs.readdirSync(path.join(_this._fullPath, 'modules')); + let componentContents = fs.readdirSync(_this._fullPath); // Check folders to see which is a module for (let i = 0; i < componentContents.length; i++) { if (SUtils.fileExistsSync(path.join(_this._fullPath, componentContents[i], 's-module.json'))) { - let module = new this.S.classes.Module(_this.S, { module: componentContents[i] }); + let module = new this.S.classes.Module(_this.S, { + component: component.name, + module: componentContents[i] + }); module = module.get(); component.modules[module.name] = module; } diff --git a/lib/ServerlessFunction.js b/lib/ServerlessFunction.js index af6cf4896..3cf8a59d9 100644 --- a/lib/ServerlessFunction.js +++ b/lib/ServerlessFunction.js @@ -32,17 +32,22 @@ class ServerlessFunction { /** * Update Config - * - Takes config.sPath and parses it to the scope's config object + * - Takes config.component, config.module, config.function */ updateConfig(config) { if (config) { // Set sPath - if (config.component || config.module || config.function) this.config.sPath = this.S.buildPath({ - component: config.component, - module: config.module, - function: config.function - }); + if (config.component || config.module || config.function) { + this.config.component = config.component; + this.config.module = config.module; + this.config.function = config.function; + this.config.sPath = this.S.buildPath({ + component: config.component, + module: config.module, + function: config.function + }); + } // Make full path if (this.S.config.projectPath && this.config.sPath) { let parse = this.S.parsePath(this.config.sPath); @@ -63,7 +68,7 @@ class ServerlessFunction { // Defaults _this.data = {}; _this.data.name = _this.config.function || 'function' + SUtils.generateShortId(6); - _this.data.handler = (_this.config.function && _this.config.module) ? path.posix.join('modules', _this.config.module, 'functions', _this.config.function, 'handler.handler') : ''; + _this.data.handler = path.posix.join(_this.config.component, _this.config.module, _this.config.function, 'handler.handler'); _this.data.runtime = _this.config.runtime || 'nodejs'; _this.data.timeout = 6; _this.data.memorySize = 1024; @@ -74,7 +79,7 @@ class ServerlessFunction { _this.data.events = []; _this.data.endpoints = [ { - "path": (_this.config.function && _this.config.module) ? path.posix.join(_this.config.module, _this.config.function) : '', + "path": _this.config.component + '/' + _this.config.module + '/' + _this.config.function, "method": "GET", "authorizationType": "none", "apiKeyRequired": false, diff --git a/lib/ServerlessModule.js b/lib/ServerlessModule.js index a7790bfde..c1ab5c933 100644 --- a/lib/ServerlessModule.js +++ b/lib/ServerlessModule.js @@ -33,16 +33,20 @@ class ServerlessModule { /** * Update Config - * - Takes config.sPath and parses it to the scope's config object + * - Takes config.component and config.module */ updateConfig(config) { if (config) { // Set sPath - if (config.component || config.module) this.config.sPath = this.S.buildPath({ - component: config.component, - module: config.module - }); + if (config.component || config.module) { + this.config.component = config.component; + this.config.module = config.module; + this.config.sPath = this.S.buildPath({ + component: config.component, + module: config.module + }); + } // Make full path if (this.S.config.projectPath && this.config.sPath) { let parse = this.S.parsePath(this.config.sPath); @@ -86,16 +90,18 @@ class ServerlessModule { // Add Functions moduleJson.functions = {}; - let functionList = fs.readdirSync(path.join(_this._fullPath, 'functions')); + let moduleContents = fs.readdirSync(_this._fullPath); - for (let i = 0; i < functionList.length; i++) { - - let func = new ServerlessFunction(_this.S, { - module: _this.data.module, - function: functionList[i] - }); - func = func.get(); - moduleJson.functions[func.name] = func; + for (let i = 0; i < moduleContents.length; i++) { + if (SUtils.fileExistsSync(path.join(_this._fullPath, moduleContents[i], 's-function.json'))) { + let func = new ServerlessFunction(_this.S, { + component: _this.config.component, + module: _this.config.module, + function: moduleContents[i] + }); + func = func.get(); + moduleJson.functions[func.name] = func; + } } // Get templates diff --git a/lib/ServerlessProject.js b/lib/ServerlessProject.js index a12baed9f..767a0f252 100644 --- a/lib/ServerlessProject.js +++ b/lib/ServerlessProject.js @@ -20,10 +20,21 @@ class ServerlessProject { constructor(Serverless, options) { this.S = Serverless; - this.options = options || {}; + this.config = {}; + this.updateConfig(config); this.load(); } + /** + * Update Config + */ + + updateConfig(config) { + if (config) { + this.config = config; + } + } + /** * Load * - Load from source (i.e., file system); @@ -114,13 +125,14 @@ class ServerlessProject { // Get Project JSON let project = SUtils.readAndParseJsonSync(path.join(_this.S.config.projectPath, 's-project.json')); + project.components = project.components ? project.components : {}; let projectContents = fs.readdirSync(path.join(_this.S.config.projectPath)); for (let i = 0; i < projectContents.length; i++) { - if (SUtils.fileExistsSync(path.join(_this.S.config.projectPath, componentContents[i], 's-component.json'))) { - let component = new this.S.classes.Component(_this.S, { component: componentContents[i] }); + if (SUtils.fileExistsSync(path.join(_this.S.config.projectPath, projectContents[i], 's-component.json'))) { + let component = new this.S.classes.Component(_this.S, { component: projectContents[i] }); component = component.get(); - project.data.components[component.name] = component; + project.components[component.name] = component; } } @@ -207,7 +219,7 @@ class ServerlessProject { // If paths, and this component is not included, skip if (options.paths && options.paths.length && - !pathsObj[componentName]) continue; + typeof pathsObj[componentName] === 'undefined') continue; let component = new _this.S.classes.Component(_this.S, { component: componentName }); component.push(component); @@ -256,7 +268,7 @@ class ServerlessProject { // If paths, and this component is not included, skip if (options.paths && options.paths.length && - !pathsObj[component.name]) continue; + typeof pathsObj[component.name] === 'undefined') continue; for (let j = 0; j < component.modules.length; j++) { @@ -265,7 +277,7 @@ class ServerlessProject { // If paths, and this component is not included, skip if (options.paths && options.paths.length && - !pathsObj[component.name][moduleName]) continue; + typeof pathsObj[component.name][moduleName] === 'undefined') continue; let module = new _this.S.classes.Component(_this.S, { component: component.name, @@ -294,56 +306,57 @@ class ServerlessProject { functions = [], pathsObj = {}; - options = options || {}; + options = options || {}; // If paths, create temp obj for easy referencing if (options.paths && options.paths.length) { options.paths.forEach(function (path) { - let component = path.split('/')[0]; - let module = path.split('/')[1]; - let func = path.split('/')[2].split('@')[0]; // Allows using this in getEndpoints + var parsed = _this.S.parsePath(path); - if (!pathsObj[component]) pathsObj[component] = {}; - if (!pathsObj[component][module]) [component][module] = {}; - pathsObj[component][module][func] = true; + if (!pathsObj[parsed.component]) pathsObj[parsed.component] = {}; + if (!pathsObj[parsed.component][parsed.module]) pathsObj[parsed.component][parsed.module] = {}; + pathsObj[parsed.component][parsed.module][parsed.function] = true; }); } for (let i = 0; i < Object.keys(_this.data.components).length; i++) { - let component = Object.keys(_this.data.components)[i]; + let component = _this.data.components[Object.keys(_this.data.components)[i]]; // If paths, and this component is not included, skip if (options.paths && options.paths.length && - !pathsObj[component.name]) continue; + typeof pathsObj[component.name] === 'undefined') continue; - for (let j = 0; j < component.modules.length; j++) { + if (!component.modules) continue; - let module = Object.keys(component.modules)[j]; + for (let j = 0; j < Object.keys(component.modules).length; j++) { + + let module = component.modules[Object.keys(component.modules)[j]]; // If paths, and this component is not included, skip if (options.paths && options.paths.length && - !pathsObj[component.name][module.name]) continue; + typeof pathsObj[component.name][module.name] === 'undefined') continue; - for (let k = 0; k < module.functions.length; k++) { + if (!module.functions) continue; - let funcName = Object.keys(module.functions)[k]; + for (let k = 0; k < Object.keys(module.functions).length; k++) { + + let func = module.functions[Object.keys(module.functions)[k]]; // If paths, and this component is not included, skip if (options.paths && options.paths.length && - !pathsObj[component.name][module.name] && - !pathsObj[component.name][module.name][funcName]) continue; + typeof pathsObj[component.name][module.name][func.name] === 'undefined') continue; - let func = new _this.S.classes.Function(_this.S, { + let funcInstance = new _this.S.classes.Function(_this.S, { component: component.name, module: module.name, - function: funcName + function: func.name }); - functions.push(func); + functions.push(funcInstance); } } } @@ -374,20 +387,15 @@ class ServerlessProject { if (options.paths && options.paths.length) { options.paths.forEach(function (path) { - if (path.indexOf('@') == -1 || path.indexOf('~') == -1) { - throw new SError('Invalid endpoint path provided: ' + path); - } + _this.S.validatePath(path, 'endpoint'); - let component = path.split('/')[0]; - let module = path.split('/')[1]; - let func = path.split('/')[2].split('@')[0]; - let urlPath = path.split('@')[1].split('~')[0]; - let method = path.split('~')[1]; + let parsed = _this.S.parsePath(path); - if (!pathsObj[module]) pathsObj[module] = {}; - if (!pathsObj[module][func]) pathsObj[module][func] = {}; - if (!pathsObj[module][func][urlPath]) pathsObj[module][func][urlPath] = {}; - if (!pathsObj[module][func][urlPath][method]) pathsObj[module][func][urlPath][method] = true; + if (!pathsObj[parsed.component]) pathsObj[parsed.component] = {}; + if (!pathsObj[parsed.component][parsed.module]) pathsObj[parsed.component][parsed.module] = {}; + if (!pathsObj[parsed.component][parsed.module][parsed.function]) pathsObj[parsed.component][parsed.module][parsed.function] = {}; + if (!pathsObj[parsed.component][parsed.module][parsed.function][parsed.urlPath]) pathsObj[parsed.component][parsed.module][parsed.function][parsed.urlPath] = {}; + if (!pathsObj[parsed.component][parsed.module][parsed.function][parsed.urlPath][parsed.urlMethod]) pathsObj[parsed.component][parsed.module][parsed.function][parsed.urlPath][parsed.urlMethod] = true; }); } @@ -396,15 +404,24 @@ class ServerlessProject { for (let i = 0; i < functions.length; i++) { - let func = functions[i].data; + let func = functions[i]; - for (let j = 0; j < func.endpoints.length; j++) { + for (let j = 0; j < func.data.endpoints.length; j++) { - let endpoint = func.endpoints[j]; + let endpoint = func.data.endpoints[j]; if (options.paths && options.paths.length && - !pathsObj[func.component][func.module][func.name][endpoint.path][endpoint.method]) continue; + typeof pathsObj[func.config.component][func.config.module][func.data.name][endpoint.path][endpoint.method] === 'undefined') continue; + + // TODO: Make a real class for ServerlessEndpoint + endpoint = {}; + endpoint.data = func.data.endpoints[j]; + endpoint.config = {}; + endpoint.config.sPath = func.config.sPath + '@' + endpoint.data.path + '~' + endpoint.data.method; + endpoint.config.component = func.config.component; + endpoint.config.module = func.config.module; + endpoint.config.function = func.config.function; endpoints.push(endpoint); } @@ -422,22 +439,30 @@ class ServerlessProject { * - Saves data to file system */ - save() { + save(options) { let _this = this; - // Loop over components and save - Object.keys(_this.data.components).forEach(function(componentName) { - - let component = new _this.S.classes.Module(_this.S); - component.data = Object.create(_this.data.components[componentName]); - component.save(); - }); + // Validate paths + if (!_this.S.config.projectPath) throw new SError('Missing project path'); // Save JSON file - fs.writeFileSync(path.join(_this.S.config.projectPath, 's-project.json'), + fs.writeFileSync(path.join( + _this.S.config.projectPath, + 's-project.json'), JSON.stringify(this.data, null, 2)); + // Save all nested data + if (options && options.deep) { + + // Loop over components and save + Object.keys(_this.data.components).forEach(function(componentName) { + + let component = new _this.S.classes.Module(_this.S); + component.data = Object.create(_this.data.components[componentName]); + component.save(); + }); + } } } diff --git a/lib/actions/CodeDeployLambdaNodeJs.js b/lib/actions/CodeDeployLambdaNodeJs.js index 69b39b068..e4e1ff83c 100644 --- a/lib/actions/CodeDeployLambdaNodeJs.js +++ b/lib/actions/CodeDeployLambdaNodeJs.js @@ -117,8 +117,9 @@ module.exports = function(SPlugin, serverlessPath) { _this.meta = new _this.S.classes.Meta(_this.S); _this.project = new _this.S.classes.Project(_this.S); _this.function = new _this.S.classes.Function(_this.S, { - module: _this.evt.options.module, - function: _this.evt.options.function + component: _this.evt.options.component, + module: _this.evt.options.module, + function: _this.evt.options.function }); return BbPromise.resolve(); @@ -165,7 +166,7 @@ module.exports = function(SPlugin, serverlessPath) { */ _upload() { - console.log("uplaod to S3 started..."); + let _this = this; SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.data.name}": Uploading to project bucket...`); @@ -177,7 +178,7 @@ module.exports = function(SPlugin, serverlessPath) { _this.function.data.name, fs.createReadStream(_this.pathCompressed)) .then(function (s3Key) { - console.log("uplaod to S3 completed."); + // Store S3 Data _this.s3Bucket = _this.meta.data.private.variables.projectBucket; _this.s3Key = s3Key; @@ -195,7 +196,7 @@ module.exports = function(SPlugin, serverlessPath) { let _this = this; var params = { - FunctionName: _this.Lambda.sGetLambdaName(_this.project.data.name, _this.function.module, _this.function.data.name), + FunctionName: _this.Lambda.sGetLambdaName(_this.project.data.name, _this.evt.options.component, _this.evt.options.module, _this.function.data.name), Qualifier: '$LATEST' }; @@ -210,7 +211,7 @@ module.exports = function(SPlugin, serverlessPath) { // Create or Update Lambda if (!_this.lambda) { - console.log("uploading/creating lambda started..."); + SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.data.name}": Creating Lambda function...`); // Create Lambda @@ -218,19 +219,19 @@ module.exports = function(SPlugin, serverlessPath) { Code: { ZipFile: _this.zipBuffer }, - FunctionName: _this.Lambda.sGetLambdaName(_this.project.data.name, _this.function.module, _this.function.data.name), /* required */ - Handler: _this.function.data.handler, /* required */ - Role: _this.meta.data.private.stages[_this.evt.options.stage].regions[_this.evt.options.region].variables.iamRoleArnLambda, /* required */ - Runtime: _this.function.data.runtime, /* required */ - Description: 'Serverless Lambda function for project: ' + _this.project.data.name, - MemorySize: _this.function.data.memorySize, - Publish: true, // Required by Serverless Framework & recommended best practice by AWS - Timeout: _this.function.data.timeout + FunctionName: _this.Lambda.sGetLambdaName(_this.project.data.name, _this.evt.options.component, _this.evt.options.module, _this.function.data.name), /* required */ + Handler: _this.function.data.handler, /* required */ + Role: _this.meta.data.private.stages[_this.evt.options.stage].regions[_this.evt.options.region].variables.iamRoleArnLambda, /* required */ + Runtime: _this.function.data.runtime, /* required */ + Description: 'Serverless Lambda function for project: ' + _this.project.data.name, + MemorySize: _this.function.data.memorySize, + Publish: true, // Required by Serverless Framework & recommended best practice by AWS + Timeout: _this.function.data.timeout }; return _this.Lambda.createFunctionPromised(params) .then(function (data) { - console.log("uploading/creating lambda completed."); + // Save Version & Lambda _this.lambdaVersion = data.Version; _this.lambda = data; @@ -239,14 +240,14 @@ module.exports = function(SPlugin, serverlessPath) { } else { SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.data.name}": Updating Lambda configuration...`); - console.log("uploading/updating lambda started..."); + let params = { FunctionName: _this.lambda.Configuration.FunctionName, /* required */ Description: 'Serverless Lambda function for project: ' + _this.project.data.name, - Handler: _this.function.data.handler, - MemorySize: _this.function.data.memorySize, - Role: _this.meta.data.private.stages[_this.evt.options.stage].regions[_this.evt.options.region].variables.iamRoleArnLambda, - Timeout: _this.function.data.timeout + Handler: _this.function.data.handler, + MemorySize: _this.function.data.memorySize, + Role: _this.meta.data.private.stages[_this.evt.options.stage].regions[_this.evt.options.region].variables.iamRoleArnLambda, + Timeout: _this.function.data.timeout }; return _this.Lambda.updateFunctionConfigurationPromised(params) @@ -256,13 +257,13 @@ module.exports = function(SPlugin, serverlessPath) { // Update Lambda Code let params = { FunctionName: _this.lambda.Configuration.FunctionName, /* required */ - Publish: true, // Required by Serverless Framework & recommended by AWS - ZipFile: _this.zipBuffer + Publish: true, // Required by Serverless Framework & recommended by AWS + ZipFile: _this.zipBuffer }; return _this.Lambda.updateFunctionCodePromised(params) .then(function (data) { - console.log("uploading/updating lambda completed."); + // Save Version & Lambda _this.lambdaVersion = data.Version; _this.lambda = data; diff --git a/lib/actions/CodeEventDeployLambda.js b/lib/actions/CodeEventDeployLambda.js index fe802be93..1c1ede137 100644 --- a/lib/actions/CodeEventDeployLambda.js +++ b/lib/actions/CodeEventDeployLambda.js @@ -57,7 +57,7 @@ module.exports = function(SPlugin, serverlessPath) { async.eachLimit(evt.function.events, 5, function (event, cb) { let params = { - FunctionName: _this.Lambda.sGetLambdaName(_this.S.data.project.get('name'), evt.function.module, evt.function.name), + FunctionName: _this.Lambda.sGetLambdaName(evt.function.data.name, evt.function.config.component, evt.function.config.module, evt.function.name), EventSourceArn: event.eventSourceArn, StartingPosition: event.startingPosition, BatchSize: event.batchSize, diff --git a/lib/actions/CodePackageLambdaNodeJs.js b/lib/actions/CodePackageLambdaNodeJs.js index 4da416a04..46a26d6da 100644 --- a/lib/actions/CodePackageLambdaNodeJs.js +++ b/lib/actions/CodePackageLambdaNodeJs.js @@ -97,8 +97,9 @@ module.exports = function(SPlugin, serverlessPath) { _this.meta = new _this.S.classes.Meta(_this.S); _this.project = new _this.S.classes.Project(_this.S); _this.function = new _this.S.classes.Function(_this.S, { - module: _this.evt.options.module, - function: _this.evt.options.function + component: _this.evt.options.component, + module: _this.evt.options.module, + function: _this.evt.options.function }); //TODO: Use Function.validate() @@ -150,7 +151,7 @@ module.exports = function(SPlugin, serverlessPath) { let excludePatterns = this.function.data.custom.excludePatterns || []; wrench.copyDirSyncRecursive( - path.join(_this.S.config.projectPath, 'back'), + path.join(_this.S.config.projectPath, this.evt.options.component), _this.pathDist, { exclude: function(name, prefix) { diff --git a/lib/actions/EndpointBuildApiGateway.js b/lib/actions/EndpointBuildApiGateway.js index 067d9790f..a8f3c99f8 100644 --- a/lib/actions/EndpointBuildApiGateway.js +++ b/lib/actions/EndpointBuildApiGateway.js @@ -128,7 +128,7 @@ module.exports = function(SPlugin, serverlessPath) { _this.meta = new _this.S.classes.Meta(_this.S); // Define useful variables - _this.endpoint = _this.project.getEndpoints({ + _this.endpoint = _this.project.getEndpoints({ paths: [_this.evt.options.path], populate: true, stage: _this.evt.options.stage, @@ -216,7 +216,7 @@ module.exports = function(SPlugin, serverlessPath) { let _this = this; let params = { - FunctionName: _this.Lambda.sGetLambdaName(_this.project.data.name, _this.endpoint.module, _this.endpoint.function), /* required */ + FunctionName: _this.Lambda.sGetLambdaName(_this.project.data.name, _this.endpoint.config.component, _this.endpoint.config.module, _this.endpoint.config.function), /* required */ Qualifier: _this.evt.options.stage }; @@ -237,9 +237,6 @@ module.exports = function(SPlugin, serverlessPath) { + _this.endpoint.data.path + '": found the target lambda with function name: ' + _this.deployedLambda.FunctionName); - }) - .catch(function(e) { - console.log(e); }); } diff --git a/lib/actions/EndpointDeploy.js b/lib/actions/EndpointDeploy.js index 0de29e813..b7898ce93 100644 --- a/lib/actions/EndpointDeploy.js +++ b/lib/actions/EndpointDeploy.js @@ -290,7 +290,7 @@ module.exports = function(SPlugin, serverlessPath) { options: { stage: _this.evt.options.stage, region: region, - path: endpoint.sPath, + path: endpoint.config.sPath, aliasEndpoint: _this.evt.options.aliasEndpoint, aliasRestApi: _this.evt.options.aliasRestApi } diff --git a/lib/actions/FunctionDeploy.js b/lib/actions/FunctionDeploy.js index 7eb710b15..332039396 100644 --- a/lib/actions/FunctionDeploy.js +++ b/lib/actions/FunctionDeploy.js @@ -117,7 +117,7 @@ module.exports = function(SPlugin, serverlessPath) { let region = _this.failed[Object.keys(_this.failed)[i]]; SCli.log(Object.keys(_this.failed)[i] + ' ------------------------'); for (let j = 0; j < region.length; j++) { - SCli.log(' ' + region[j].module + '/' + region[j].function + ': ' + region[j].message ); + SCli.log(' ' + region[j].component + '/' + region[j].module + '/' + region[j].function + ': ' + region[j].message ); SUtils.sDebug(region[j].stack); } } @@ -136,7 +136,7 @@ module.exports = function(SPlugin, serverlessPath) { let region = _this.deployed[Object.keys(_this.deployed)[i]]; SCli.log(Object.keys(_this.deployed)[i] + ' ------------------------'); for (let j = 0; j < region.length; j++) { - SCli.log(' ' + region[j].module + '/' + region[j].function + ': ' + region[j].Arn ); + SCli.log(' ' + region[j].component + '/' + region[j].module + '/' + region[j].function + ': ' + region[j].Arn ); } } } @@ -174,6 +174,7 @@ module.exports = function(SPlugin, serverlessPath) { _this.regions = _this.evt.options.region ? [_this.evt.options.region] : Object.keys(_this.meta.data.private.stages[_this.evt.options.stage].regions); if (!_this.evt.options.paths.length) { + let CWD = process.cwd(), isModule = SUtils.fileExistsSync(path.join(CWD, 's-module.json')) || SUtils.fileExistsSync(path.join(CWD, '..', 's-module.json')), isFunction = SUtils.fileExistsSync(path.join(CWD, 's-function.json')); @@ -230,10 +231,9 @@ module.exports = function(SPlugin, serverlessPath) { .each(function(region) { // Prepare functions - let getFunctionsOptions = { + _this.functions = _this.project.getFunctions({ paths: _this.evt.options.paths - }; - _this.functions = _this.project.getFunctions(getFunctionsOptions); + }); // Deploy Function Code in each region return _this._deployCodeByRegion(region); @@ -269,10 +269,11 @@ module.exports = function(SPlugin, serverlessPath) { let newEvt = { options: { - stage: _this.evt.options.stage, - region: region, - module: func.module, - function: func.data.name + stage: _this.evt.options.stage, + region: region, + component: func.config.component, + module: func.config.module, + function: func.data.name } }; @@ -285,6 +286,7 @@ module.exports = function(SPlugin, serverlessPath) { options: { stage: result.options.stage, region: result.options.region, + component: result.options.component, module: result.options.module, function: result.options.function, pathDist: result.data.pathDist, @@ -303,9 +305,10 @@ module.exports = function(SPlugin, serverlessPath) { if (!_this.deployed) _this.deployed = {}; if (!_this.deployed[region]) _this.deployed[region] = []; _this.deployed[region].push({ - module: func.module, - function: func.data.name, - Arn: result.data.lambdaAliasArn + component: func.config.component, + module: func.config.module, + function: func.data.name, + Arn: result.data.lambdaAliasArn }); return cb(); @@ -317,10 +320,11 @@ module.exports = function(SPlugin, serverlessPath) { if (!_this.failed) _this.failed = {}; if (!_this.failed[region]) _this.failed[region] = []; _this.failed[region].push({ - module: func.module, - function: func.data.name, - message: e.message, - stack: e.stack + component: func.config.component, + module: func.config.module, + function: func.data.name, + message: e.message, + stack: e.stack }); return cb(); diff --git a/lib/actions/FunctionRunLambdaNodeJs.js b/lib/actions/FunctionRunLambdaNodeJs.js index 73022107d..244da544d 100644 --- a/lib/actions/FunctionRunLambdaNodeJs.js +++ b/lib/actions/FunctionRunLambdaNodeJs.js @@ -5,6 +5,7 @@ */ module.exports = function(SPlugin, serverlessPath) { + const path = require('path'), SError = require(path.join(serverlessPath, 'ServerlessError')), SUtils = require(path.join(serverlessPath, 'utils')), @@ -62,8 +63,8 @@ module.exports = function(SPlugin, serverlessPath) { // Instantiate Classes _this.function = new _this.S.classes.Function(_this.S, { component: _this.evt.options.path.split('/')[0], - module: _this.evt.options.path.split('/')[1], - function: _this.evt.options.path.split('/')[2] + module: _this.evt.options.path.split('/')[1], + function: _this.evt.options.path.split('/')[2] }); // Prepare result object @@ -79,7 +80,7 @@ module.exports = function(SPlugin, serverlessPath) { // Load function file & handler let functionFile = _this.function.data.handler.split('/').pop().split('.')[0]; let functionHandler = _this.function.data.handler.split('/').pop().split('.')[1]; - let functionPath = path.join(_this.S.config.projectPath, _this.function.component, _this.function.module, _this.function.data.name); + let functionPath = path.join(_this.S.config.projectPath, _this.component, _this.module, _this.function.data.name); functionFile = path.join(functionPath, (functionFile + '.js')); functionHandler = require(functionFile)[functionHandler]; diff --git a/lib/actions/ProjectCreate.js b/lib/actions/ProjectCreate.js index 8741f7b97..733c3a8ad 100644 --- a/lib/actions/ProjectCreate.js +++ b/lib/actions/ProjectCreate.js @@ -123,6 +123,7 @@ module.exports = function(SPlugin, serverlessPath) { * Return EVT */ + _this.evt.projectPath = _this.S.config.projectPath; return _this.evt; }); } diff --git a/lib/utils/aws/Lambda.js b/lib/utils/aws/Lambda.js index fd644a2df..fb4189aa2 100644 --- a/lib/utils/aws/Lambda.js +++ b/lib/utils/aws/Lambda.js @@ -26,8 +26,8 @@ module.exports = function(config) { * Get Lambda Name */ - Lambda.sGetLambdaName = function(projectName, moduleName, functionName) { - return projectName + '-' + moduleName + '-' + functionName; + Lambda.sGetLambdaName = function(projectName, componentName, moduleName, functionName) { + return projectName + '-' + componentName + '-' + moduleName + '-' + functionName; }; /** diff --git a/lib/utils/index.js b/lib/utils/index.js index 4ca621c32..e70ef9c61 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -78,34 +78,33 @@ exports.getResources = function(populatedData) { let cfTemplate = JSON.parse(JSON.stringify(populatedData.cloudFormation)); - // Loop through modules and aggregate resources - for (let i = 0; i < Object.keys(populatedData.modules).length; i++) { - - let moduleObj = populatedData.modules[Object.keys(populatedData.modules)[i]]; + // Helper function to aggregate resources + function aggregate(cfData) { // If no cloudFormation in module, skip... - if (!moduleObj.cloudFormation) continue; + if (!cfData.cloudFormation) return; // Merge Lambda Policy Statements - if (moduleObj.cloudFormation.lambdaIamPolicyDocumentStatements && - moduleObj.cloudFormation.lambdaIamPolicyDocumentStatements.length > 0) { - SCli.log('Merging in Lambda IAM Policy statements from module: ' + moduleObj.name); + if (cfData.cloudFormation.lambdaIamPolicyDocumentStatements && + cfData.cloudFormation.lambdaIamPolicyDocumentStatements.length > 0) { + SCli.log('Merging in Lambda IAM Policy statements from: ' + cfData.name); - moduleObj.cloudFormation.lambdaIamPolicyDocumentStatements.forEach(function(policyStmt) { + cfData.cloudFormation.lambdaIamPolicyDocumentStatements.forEach(function (policyStmt) { cfTemplate.Resources.IamPolicyLambda.Properties.PolicyDocument.Statement.push(policyStmt); }); } - // Merge resources - if (moduleObj.cloudFormation.resources) { + // Merge Resources + if (cfData.cloudFormation.resources) { - let cfResourceKeys = Object.keys(moduleObj.cloudFormation.resources); + let cfResourceKeys = Object.keys(cfData.cloudFormation.resources); if (cfResourceKeys.length > 0) { - SCli.log('Merging in CF Resources from module: ' + moduleObj.name); + SCli.log('Merging in CF Resources from module: ' + cfData.name); } cfResourceKeys.forEach(function (resourceKey) { + if (cfTemplate.Resources[resourceKey]) { SCli.log( chalk.bgYellow.white(' WARN ') + @@ -113,11 +112,24 @@ exports.getResources = function(populatedData) { ); } - cfTemplate.Resources[resourceKey] = moduleObj.cloudFormation.resources[resourceKey]; + cfTemplate.Resources[resourceKey] = cfData.cloudFormation.resources[resourceKey]; }); } } + // Aggregate Components CF + for (let i = 0; i < Object.keys(populatedData.components).length; i++) { + let component = populatedData.components[Object.keys(populatedData.components)[i]]; + aggregate(component); + + // Aggregate Modules CF + if (component.modules) { + for (let j = 0; j < component.modules.length; j++) { + aggregate(component.modules[j]); + } + } + } + return cfTemplate; }; @@ -545,12 +557,12 @@ exports.doesFunctionExist = function(functionName, moduleName, componentName, pr * Populate */ -exports.populate = function(S, populatedData, stage, region) { +exports.populate = function(S, unPopulatedData, stage, region) { let _this = this; // Validate required params - if (!S || !populatedData || !stage || !region) throw new SError(`Missing required params: Serverless, populatedData, stage, region`); + if (!S || !unPopulatedData || !stage || !region) throw new SError(`Missing required params: Serverless, populatedData, stage, region`); // Get Meta & Project (to populate templates from) let meta = new S.classes.Meta(S); @@ -562,37 +574,40 @@ exports.populate = function(S, populatedData, stage, region) { // Validate region exists in stage if (typeof region != 'undefined' && !meta.data.private.stages[stage].regions[region]) throw new SError(`Region doesn't exist in provided stage`); - // Combine all templates found in project - let templates = {}; - for (let i = 0; i < Object.keys(project.data.modules).length; i++) { - let module = Object.keys(project.data.modules)[i]; - templates[module] = project.data.modules[module].templates; + // If project has modules w/ templates, combine all templates found in project and populate them + if (project.data.components && project.data.components.modules) { + + let templates = {}; + for (let i = 0; i < Object.keys(project.data.components.modules).length; i++) { + let module = Object.keys(project.data.components.modules)[i]; + templates[module] = project.data.components.modules[module].templates; + } + + // Populate templates + traverse(unPopulatedData).forEach(function(val) { + + let t = this; + + // check if the current string is a template $${...} + if (typeof val === 'string' && val.match(/\$\${([^{}]*)}/g) != null) { + + let template = val.replace('$${', '').replace('}', ''); + let templateModule = template.slice(0, template.indexOf(".")); // assumes template path is valid format + let templateName = template.slice(template.indexOf(".") + 1, template.length); // assumes template path is valid format + + // Check module and template key exist + if (!templates[templateModule])throw new SError(`This module does not exist: ${templateModule}`); + if (!templates[templateModule][templateName] && templates[templateModule][templateName] !== "")throw new SError(`Missing template in module: ${templateName} in ${templateModule}`); + + // Replace + t.update(templates[templateModule][templateName]); + + } + }); } - // Populate templates - traverse(populatedData).forEach(function(val) { - - let t = this; - - // check if the current string is a template $${...} - if (typeof val === 'string' && val.match(/\$\${([^{}]*)}/g) != null) { - - let template = val.replace('$${', '').replace('}', ''); - let templateModule = template.slice(0, template.indexOf(".")); // assumes template path is valid format - let templateName = template.slice(template.indexOf(".") + 1, template.length); // assumes template path is valid format - - // Check module and template key exist - if (!templates[templateModule])throw new SError(`This module does not exist: ${templateModule}`); - if (!templates[templateModule][templateName] && templates[templateModule][templateName] !== "")throw new SError(`Missing template in module: ${templateName} in ${templateModule}`); - - // Replace - t.update(templates[templateModule][templateName]); - - } - }); - // Populate variables - traverse(populatedData).forEach(function(val) { + traverse(unPopulatedData).forEach(function(val) { let t = this; @@ -622,7 +637,7 @@ exports.populate = function(S, populatedData, stage, region) { } }); - return populatedData; + return unPopulatedData; }; diff --git a/tests/all.js b/tests/all.js index f31b41311..84b1963a0 100644 --- a/tests/all.js +++ b/tests/all.js @@ -14,7 +14,7 @@ describe('All Tests', function() { //require('./tests/actions/TestPluginCustom'); //require('./tests/actions/TestDefaultActionHook'); //require('./tests/actions/ProjectCreate'); - require('./tests/actions/ComponentCreate'); + //require('./tests/actions/ComponentCreate'); //require('./tests/actions/StageCreate'); //require('./tests/actions/RegionCreate'); //require('./tests/actions/ModuleInstall'); @@ -26,5 +26,5 @@ describe('All Tests', function() { //require('./tests/actions/ResourcesDeploy'); //require('./tests/actions/FunctionRun'); //require('./tests/actions/FunctionDeploy'); - //require('./tests/actions/EndpointDeploy'); + require('./tests/actions/EndpointDeploy'); }); diff --git a/tests/config.js b/tests/config.js index 74ff96692..806dd4dd3 100644 --- a/tests/config.js +++ b/tests/config.js @@ -3,7 +3,7 @@ const path = require('path'); // Require ENV lets, can also set ENV lets in your IDE -require('dotenv').config({path: path.join(__dirname, '.env'), silent: true}); +require('dotenv').config({ path: path.join(__dirname, '.env'), silent: true }); process.env.DEBUG = '*'; diff --git a/tests/test-prj/nodejscomponent/module1/function1/handler.js b/tests/test-prj/nodejscomponent/module1/function1/handler.js index 9202a5072..f0afe4ef3 100644 --- a/tests/test-prj/nodejscomponent/module1/function1/handler.js +++ b/tests/test-prj/nodejscomponent/module1/function1/handler.js @@ -1,8 +1,8 @@ 'use strict'; // Load ENV -var ServerlessHelpers = require('serverless-helpers-js'); -ServerlessHelpers.loadEnv(); +//var ServerlessHelpers = require('serverless-helpers-js'); +//ServerlessHelpers.loadEnv(); // Lambda Handler module.exports.handler = function(event, context) { diff --git a/tests/test-prj/nodejscomponent/module1/s-module.json b/tests/test-prj/nodejscomponent/module1/s-module.json index 63f416676..71f160d2e 100644 --- a/tests/test-prj/nodejscomponent/module1/s-module.json +++ b/tests/test-prj/nodejscomponent/module1/s-module.json @@ -8,7 +8,6 @@ "description": "", "cloudFormation": { "lambdaIamPolicyDocumentStatements": [], - "apiGatewayIamPolicyDocumentStatements": [], "resources": {} } } \ No newline at end of file diff --git a/tests/test-prj/nodejscomponent/s-component.json b/tests/test-prj/nodejscomponent/s-component.json index 9ee4c64aa..127f95e10 100644 --- a/tests/test-prj/nodejscomponent/s-component.json +++ b/tests/test-prj/nodejscomponent/s-component.json @@ -1,4 +1,4 @@ { - name: "nodejscomponent", - runtime: "nodejs" + "name": "nodejscomponent", + "runtime": "nodejs" } \ No newline at end of file diff --git a/tests/tests/actions/EndpointDeploy.js b/tests/tests/actions/EndpointDeploy.js index 73be50dfc..bfc6b55d8 100644 --- a/tests/tests/actions/EndpointDeploy.js +++ b/tests/tests/actions/EndpointDeploy.js @@ -19,11 +19,10 @@ let serverless; */ let validateEvent = function(evt) { - assert.equal(true, typeof evt.options.stage != 'undefined'); - assert.equal(true, typeof evt.options.region != 'undefined'); - assert.equal(true, typeof evt.options.all != 'undefined'); - assert.equal(true, typeof evt.options.paths != 'undefined'); - assert.equal(true, typeof evt.data.deployed != 'undefined'); + assert.equal(true, typeof evt.options.stage != 'undefined'); + assert.equal(true, typeof evt.options.region != 'undefined'); + assert.equal(true, typeof evt.options.paths != 'undefined'); + assert.equal(true, typeof evt.data.deployed != 'undefined'); if (evt.data.failed) { for (let i = 0; i < Object.keys(evt.data.failed).length; i++) { @@ -76,7 +75,7 @@ describe('Test Action: Endpoint Deploy', function() { stage: config.stage, region: config.region, paths: [ - 'moduleone/one@moduleone/one~GET' + 'nodejscomponent/module1/function1@nodejscomponent/module1/function1~GET' ] }; diff --git a/tests/tests/actions/FunctionCreate.js b/tests/tests/actions/FunctionCreate.js index bb6001175..347db165b 100644 --- a/tests/tests/actions/FunctionCreate.js +++ b/tests/tests/actions/FunctionCreate.js @@ -51,7 +51,7 @@ describe('Test action: Function Create', function() { this.timeout(0); let evt = { options: { - component: 'nodejscomponent' + component: 'nodejscomponent', module: 'module1', function: 'new' } diff --git a/tests/tests/actions/FunctionDeploy.js b/tests/tests/actions/FunctionDeploy.js index 727ea239e..8de44731d 100644 --- a/tests/tests/actions/FunctionDeploy.js +++ b/tests/tests/actions/FunctionDeploy.js @@ -5,12 +5,12 @@ */ let Serverless = require('../../../lib/Serverless.js'), - path = require('path'), - utils = require('../../../lib/utils/index'), - assert = require('chai').assert, - testUtils = require('../../test_utils'), - AWS = require('aws-sdk'), - config = require('../../config'); + path = require('path'), + utils = require('../../../lib/utils/index'), + assert = require('chai').assert, + testUtils = require('../../test_utils'), + AWS = require('aws-sdk'), + config = require('../../config'); let serverless; @@ -73,19 +73,19 @@ describe('Test Action: Function Deploy', function() { this.timeout(0); testUtils.createTestProject(config, ['nodejscomponent']) - .then(projPath => { + .then(projPath => { - process.chdir(projPath); + process.chdir(projPath); - serverless = new Serverless({ - interactive: false, - awsAdminKeyId: config.awsAdminKeyId, - awsAdminSecretKey: config.awsAdminSecretKey, - projectPath: projPath - }); - - done(); + serverless = new Serverless({ + interactive: false, + awsAdminKeyId: config.awsAdminKeyId, + awsAdminSecretKey: config.awsAdminSecretKey, + projectPath: projPath }); + + done(); + }); }); after(function(done) { @@ -101,17 +101,15 @@ describe('Test Action: Function Deploy', function() { this.timeout(0); - let evt = { - options: { - stage: config.stage, - region: config.region, - paths: [ - 'nodejscomponent/module1/function1' - ] - } + let options = { + stage: config.stage, + region: config.region, + paths: [ + 'nodejscomponent/module1/function1' + ] }; - serverless.actions.functionDeploy(evt) + serverless.actions.functionDeploy(options) .then(function(evt) { validateEvent(evt); done(); diff --git a/tests/tests/actions/FunctionRun.js b/tests/tests/actions/FunctionRun.js index 97b754cd4..301b22c85 100644 --- a/tests/tests/actions/FunctionRun.js +++ b/tests/tests/actions/FunctionRun.js @@ -5,11 +5,11 @@ */ let Serverless = require('../../../lib/Serverless.js'), - path = require('path'), - utils = require('../../../lib/utils/index'), - assert = require('chai').assert, - testUtils = require('../../test_utils'), - config = require('../../config'); + path = require('path'), + utils = require('../../../lib/utils/index'), + assert = require('chai').assert, + testUtils = require('../../test_utils'), + config = require('../../config'); let serverless; @@ -29,22 +29,22 @@ describe('Test Action: Function Run', function() { before(function(done) { this.timeout(0); - testUtils.createTestProject(config, ['moduleone/functions/one']) - .then(projPath => { + testUtils.createTestProject(config, ['nodejscomponent']) + .then(projPath => { - this.timeout(0); + this.timeout(0); - process.chdir(projPath); + process.chdir(projPath); - serverless = new Serverless({ - interactive: true, - awsAdminKeyId: config.awsAdminKeyId, - awsAdminSecretKey: config.awsAdminSecretKey, - projectPath: projPath - }); - - done(); + serverless = new Serverless({ + interactive: true, + awsAdminKeyId: config.awsAdminKeyId, + awsAdminSecretKey: config.awsAdminSecretKey, + projectPath: projPath }); + + done(); + }); }); after(function(done) { @@ -55,20 +55,19 @@ describe('Test Action: Function Run', function() { it('should run the function with no errors', function(done) { this.timeout(0); - let evt = { - options: { - path: 'nodejscomponent/module1/function1' - } + let options = { + path: 'nodejscomponent/module1/function1' }; - serverless.actions.functionRun(evt) - .then(function(evt) { - validateEvent(evt); - done(); - }) - .catch(e => { - done(e); - }); + serverless.actions.functionRun(options) + .then(function(evt) { + validateEvent(evt); + + done(); + }) + .catch(e => { + done(e); + }); }); });