Utils: refactor getResources, populate, Tests: fix all, Serverless: add methods to work with sPaths

This commit is contained in:
ac360 2016-01-08 01:17:42 -08:00
parent 6508c11fbe
commit fde3eee4fd
24 changed files with 305 additions and 247 deletions

View File

@ -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');
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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

View File

@ -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();
});
}
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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) {

View File

@ -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);
});
}

View File

@ -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
}

View File

@ -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();

View File

@ -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];

View File

@ -123,6 +123,7 @@ module.exports = function(SPlugin, serverlessPath) {
* Return EVT
*/
_this.evt.projectPath = _this.S.config.projectPath;
return _this.evt;
});
}

View File

@ -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;
};
/**

View File

@ -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;
};

View File

@ -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');
});

View File

@ -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 = '*';

View File

@ -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) {

View File

@ -8,7 +8,6 @@
"description": "",
"cloudFormation": {
"lambdaIamPolicyDocumentStatements": [],
"apiGatewayIamPolicyDocumentStatements": [],
"resources": {}
}
}

View File

@ -1,4 +1,4 @@
{
name: "nodejscomponent",
runtime: "nodejs"
"name": "nodejscomponent",
"runtime": "nodejs"
}

View File

@ -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'
]
};

View File

@ -51,7 +51,7 @@ describe('Test action: Function Create', function() {
this.timeout(0);
let evt = {
options: {
component: 'nodejscomponent'
component: 'nodejscomponent',
module: 'module1',
function: 'new'
}

View File

@ -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();

View File

@ -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);
});
});
});