EndpointDeploy: Finish refactor

This commit is contained in:
ac360 2016-01-03 21:13:39 -08:00
parent 016f0812ad
commit 8eb2e0afad
7 changed files with 142 additions and 134 deletions

View File

@ -35,7 +35,6 @@ class ServerlessFunction {
let _this = this;
// Defaults
_this.moduleName = null;
_this.data = {};
_this.data.name = _this.options.function || 'function' + SUtils.generateShortId(6);
_this.data.handler = (_this.options.function && _this.options.module) ? path.join('modules', _this.options.module, 'functions', _this.options.function, 'handler.handler') : "";

View File

@ -201,7 +201,7 @@ class ServerlessProject {
/**
* getFunctions
* - returns an array of module instances
* - returns an array of function instances
* - paths is an array with this format: ['moduleOne/functionOne', 'moduleTwo/functionOne']
*/
@ -238,7 +238,6 @@ class ServerlessProject {
module: module.name,
function: func.name
});
funcInstance.data = func;
functions.push(funcInstance);
}
}
@ -296,20 +295,22 @@ class ServerlessProject {
// Loop - Endpoints
for (let k = 0; k < func.endpoints.length; k++) {
let endpoint = func.endpoints[k];
let endpoint = {
data: func.endpoints[k]
};
// If paths, and this endpoint is not included, skip
if (options.paths &&
options.paths.length &&
(!pathsObj[module.name] ||
!pathsObj[module.name][func.name] ||
!pathsObj[module.name][func.name][endpoint.path] ||
!pathsObj[module.name][func.name][endpoint.path][endpoint.method])
!pathsObj[module.name][func.name][endpoint.data.path] ||
!pathsObj[module.name][func.name][endpoint.data.path][endpoint.data.method])
) continue;
endpoint._module = module.name;
endpoint._function = func.name;
endpoint._sPath = module.name + '/' + func.name + '@' + endpoint.path + '~' + endpoint.method;
endpoint.module = module.name;
endpoint.function = func.name;
endpoint.sPath = module.name + '/' + func.name + '@' + endpoint.data.path + '~' + endpoint.data.method;
endpoints.push(endpoint);
}
}

View File

@ -119,7 +119,7 @@ module.exports = function(SPlugin, serverlessPath) {
throw new SError('Function does not have a memorySize property');
}
if (!_this.function.data.runtime) {
throw new SError('Function\'s parent module is missing a runtime property');
throw new SError('Function does not have a runtime property');
}
// Load AWS Service Instances

View File

@ -93,7 +93,7 @@ module.exports = function(SPlugin, serverlessPath) {
+ _this.options.region
+ '.amazonaws.com/'
+ _this.options.stage
+ _this.endpoint.path;
+ _this.endpoint.data.path;
SUtils.sDebug(
'"'
@ -101,9 +101,9 @@ module.exports = function(SPlugin, serverlessPath) {
+ '" successfully built endpoint on API Gateway in the region "'
+ _this.options.region
+ '". Access it via '
+ _this.endpoint.method
+ _this.endpoint.data.method
+ ' @ '
+ _this.endpoint.url);
+ _this.url);
/**
* Return Action Data
@ -159,34 +159,34 @@ module.exports = function(SPlugin, serverlessPath) {
_this.Lambda = require('../utils/aws/Lambda')(awsConfig);
// Validate and sanitize endpoint attributes
if (!_this.endpoint.path) {
if (!_this.endpoint.data.path) {
throw new SError('Endpoint does not have a "path" property');
}
if (!_this.endpoint.method) {
if (!_this.endpoint.data.method) {
throw new SError('Endpoint does not have a "method" property');
}
if (!_this.endpoint.authorizationType) {
if (!_this.endpoint.data.authorizationType) {
throw new SError('Endpoint does not have a "authorizationType" property');
}
if (typeof _this.endpoint.apiKeyRequired === 'undefined') {
if (typeof _this.endpoint.data.apiKeyRequired === 'undefined') {
throw new SError('Endpoint does not have a "apiKeyRequired" property');
}
if (!_this.endpoint.requestTemplates) {
if (!_this.endpoint.data.requestTemplates) {
throw new SError('Endpoint does not have a "requestTemplates" property');
}
if (!_this.endpoint.requestParameters) {
if (!_this.endpoint.data.requestParameters) {
throw new SError('Endpoint does not have a "requestParameters" property');
}
if (!_this.endpoint.responses) {
if (!_this.endpoint.data.responses) {
throw new SError('Endpoint does not have a "responses" property');
}
// Sanitize path - Remove excessive forward slashes
if (_this.endpoint.path.charAt(0) !== '/') _this.endpoint.path = '/' + _this.endpoint.path;
if (_this.endpoint.path.charAt(_this.endpoint.path.length) === '/') _this.endpoint.path = _this.endpoint.path.slice(0, -1);
if (_this.endpoint.data.path.charAt(0) !== '/') _this.endpoint.data.path = '/' + _this.endpoint.data.path;
if (_this.endpoint.data.path.charAt(_this.endpoint.data.path.length) === '/') _this.endpoint.data.path = _this.endpoint.data.path.slice(0, -1);
// Sanitize method
_this.endpoint.method = _this.endpoint.method.toUpperCase();
_this.endpoint.data.method = _this.endpoint.data.method.toUpperCase();
return BbPromise.resolve();
}
@ -220,7 +220,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.module, _this.endpoint.function), /* required */
Qualifier: _this.options.stage
};
@ -230,7 +230,7 @@ module.exports = function(SPlugin, serverlessPath) {
_this.deployedLambda = data.Configuration;
// Prepare StatementId
_this.lambdaPolicyStatementId = ('s_apig' + _this.endpoint.path + '_' + _this.endpoint.method).replace(/[\/{}]/g, '_');
_this.lambdaPolicyStatementId = ('s_apig' + _this.endpoint.data.path + '_' + _this.endpoint.data.method).replace(/[\/{}]/g, '_');
SUtils.sDebug(
'"'
@ -238,7 +238,7 @@ module.exports = function(SPlugin, serverlessPath) {
+ ' - '
+ _this.options.region
+ ' - '
+ _this.endpoint.path
+ _this.endpoint.data.path
+ '": found the target lambda with function name: '
+ _this.deployedLambda.FunctionName);
})
@ -274,7 +274,7 @@ module.exports = function(SPlugin, serverlessPath) {
+ ' - '
+ _this.options.region
+ ' - '
+ _this.endpoint.path
+ _this.endpoint.data.path
+ '": found '
+ _this.apiResources.length
+ ' existing Resources on API Gateway');
@ -315,7 +315,7 @@ module.exports = function(SPlugin, serverlessPath) {
// Check paths to see if resources need building
for (let i = 0; i < _this.apiResources.length; i++) {
if (_this.apiResources[i].path === _this.endpoint.path) {
if (_this.apiResources[i].path === _this.endpoint.data.path) {
_this.resource = _this.apiResources[i];
break;
}
@ -331,14 +331,14 @@ module.exports = function(SPlugin, serverlessPath) {
+ ' - '
+ _this.options.region
+ ' - '
+ _this.endpoint.path
+ _this.endpoint.data.path
+ '": '
+ '": no resources need to be created for this endpoint');
return BbPromise.resolve();
}
let eResources = _this.endpoint.path.split('/');
let eResources = _this.endpoint.data.path.split('/');
eResources[0] = '/'; // Our split removes the initial '/' and leaves an empty string, replace it
return new BbPromise(function(resolve, reject) {
@ -408,7 +408,7 @@ module.exports = function(SPlugin, serverlessPath) {
'"'
+ _this.options.stage + ' - '
+ _this.options.region
+ ' - ' + _this.endpoint.path + '": '
+ ' - ' + _this.endpoint.data.path + '": '
+ 'created resource: '
+ response.pathPart);
@ -431,17 +431,17 @@ module.exports = function(SPlugin, serverlessPath) {
requestParameters = {};
// If Request Params, add them
if (_this.endpoint.requestParameters) {
if (_this.endpoint.data.requestParameters) {
// Format them per APIG API's Expectations
for (let prop in _this.endpoint.requestParameters) {
let requestParam = _this.endpoint.requestParameters[prop];
for (let prop in _this.endpoint.data.requestParameters) {
let requestParam = _this.endpoint.data.requestParameters[prop];
requestParameters[requestParam] = true;
}
}
let params = {
httpMethod: _this.endpoint.method, /* required */
httpMethod: _this.endpoint.data.method, /* required */
resourceId: _this.resource.id, /* required */
restApiId: _this.restApi.id /* required */
};
@ -457,7 +457,7 @@ module.exports = function(SPlugin, serverlessPath) {
}
let params = {
httpMethod: _this.endpoint.method, /* required */
httpMethod: _this.endpoint.data.method, /* required */
resourceId: _this.resource.id, /* required */
restApiId: _this.restApi.id /* required */
};
@ -466,12 +466,12 @@ module.exports = function(SPlugin, serverlessPath) {
.then(function(response) {
let params = {
authorizationType: _this.endpoint.authorizationType, /* required */
httpMethod: _this.endpoint.method, /* required */
authorizationType: _this.endpoint.data.authorizationType, /* required */
httpMethod: _this.endpoint.data.method, /* required */
resourceId: _this.resource.id, /* required */
restApiId: _this.restApi.id, /* required */
apiKeyRequired: _this.endpoint.apiKeyRequired,
requestModels: _this.endpoint.requestModels,
apiKeyRequired: _this.endpoint.data.apiKeyRequired,
requestModels: _this.endpoint.data.requestModels,
requestParameters: requestParameters
};
return SUtils.persistantRequest( function(){ return _this.ApiGateway.putMethodPromised(params); } )
@ -482,12 +482,12 @@ module.exports = function(SPlugin, serverlessPath) {
// Method does not exist. Create it.
let params = {
authorizationType: _this.endpoint.authorizationType, /* required */
httpMethod: _this.endpoint.method, /* required */
authorizationType: _this.endpoint.data.authorizationType, /* required */
httpMethod: _this.endpoint.data.method, /* required */
resourceId: _this.resource.id, /* required */
restApiId: _this.restApi.id, /* required */
apiKeyRequired: _this.endpoint.apiKeyRequired,
requestModels: _this.endpoint.requestModels,
apiKeyRequired: _this.endpoint.data.apiKeyRequired,
requestModels: _this.endpoint.data.requestModels,
requestParameters: requestParameters
};
@ -499,18 +499,19 @@ module.exports = function(SPlugin, serverlessPath) {
'"'
+ _this.options.stage + ' - '
+ _this.options.region
+ ' - ' + _this.endpoint.path + '": '
+ ' - ' + _this.endpoint.data.path + '": '
+ 'created method: '
+ _this.endpoint.method);
+ _this.endpoint.data.method);
});
}
/*
Coerce the evnt.endpoint.requestTemplates[prop] values. Previously this was only validly a string. Often that
Coerce the _this.endpoint.data.requestTemplates[prop] values. Previously this was only validly a string. Often that
string contained a stringified JSON object. For those cases, dealing with and modifying the string was painful. As
such, this method enables the string to validly be of a different type. In this expansion, an object.
*/
prepareRequestTemplates(requestTemplates) {
_prepareRequestTemplates(requestTemplates) {
let ret = {};
for (let property in requestTemplates) {
if (requestTemplates.hasOwnProperty(property)) {
@ -537,12 +538,12 @@ module.exports = function(SPlugin, serverlessPath) {
else alias = '${stageVariables.functionAlias}';
let params = {
httpMethod: _this.endpoint.method, /* required */
httpMethod: _this.endpoint.data.method, /* required */
resourceId: _this.resource.id, /* required */
restApiId: _this.restApi.id, /* required */
type: 'AWS', /* required */
cacheKeyParameters: _this.endpoint.cacheKeyParameters || [],
cacheNamespace: _this.endpoint.cacheNamespace || null,
cacheKeyParameters: _this.endpoint.data.cacheKeyParameters || [],
cacheNamespace: _this.endpoint.data.cacheNamespace || null,
// Due to a bug in API Gateway reported here: https://github.com/awslabs/aws-apigateway-swagger-importer/issues/41
// Specifying credentials within API Gateway causes extra latency (~500ms)
// Until API Gateway is fixed, we need to make a separate call to Lambda to add credentials to API Gateway
@ -550,8 +551,8 @@ module.exports = function(SPlugin, serverlessPath) {
// _this._regionJson.iamRoleArnApiGateway
credentials: null,
integrationHttpMethod: 'POST',
requestParameters: _this.endpoint.requestParameters || {},
requestTemplates: _this.prepareRequestTemplates(_this.endpoint.requestTemplates),
requestParameters: _this.endpoint.data.requestParameters || {},
requestTemplates: _this._prepareRequestTemplates(_this.endpoint.requestTemplates),
uri: 'arn:aws:apigateway:' // Make ARN for apigateway - lambda
+ _this.options.region
+ ':lambda:path/2015-03-31/functions/arn:aws:lambda:'
@ -576,7 +577,7 @@ module.exports = function(SPlugin, serverlessPath) {
'"'
+ _this.options.stage + ' - '
+ _this.options.region
+ ' - ' + _this.endpoint.path + '": '
+ ' - ' + _this.endpoint.data.path + '": '
+ 'created integration with the type: AWS');
})
.catch(function(error) {
@ -597,7 +598,7 @@ module.exports = function(SPlugin, serverlessPath) {
return BbPromise.try(function() {
// Collect Response Keys
if (_this.endpoint.responses) return Object.keys(_this.endpoint.responses);
if (_this.endpoint.data.responses) return Object.keys(_this.endpoint.data.responses);
else return [];
})
@ -605,7 +606,7 @@ module.exports = function(SPlugin, serverlessPath) {
// Iterate through each response to be created
let thisResponse = _this.endpoint.responses[responseKey];
let thisResponse = _this.endpoint.data.responses[responseKey];
let responseParameters = {};
let responseModels = {};
@ -627,7 +628,7 @@ module.exports = function(SPlugin, serverlessPath) {
}
let params = {
httpMethod: _this.endpoint.method, /* required */
httpMethod: _this.endpoint.data.method, /* required */
resourceId: _this.resource.id, /* required */
restApiId: _this.restApi.id, /* required */
statusCode: thisResponse.statusCode, /* required */
@ -643,7 +644,7 @@ module.exports = function(SPlugin, serverlessPath) {
'"'
+ _this.options.stage + ' - '
+ _this.options.region
+ ' - ' + _this.endpoint.path + '": '
+ ' - ' + _this.endpoint.data.path + '": '
+ 'created method response');
})
@ -664,12 +665,12 @@ module.exports = function(SPlugin, serverlessPath) {
return BbPromise.try(function() {
// Collect Response Keys
if (_this.endpoint.responses) return Object.keys(_this.endpoint.responses);
if (_this.endpoint.data.responses) return Object.keys(_this.endpoint.data.responses);
else return [];
})
.each(function(responseKey) {
let thisResponse = _this.endpoint.responses[responseKey];
let thisResponse = _this.endpoint.data.responses[responseKey];
// Add Response Parameters
let responseParameters = thisResponse.responseParameters || {};
@ -681,7 +682,7 @@ module.exports = function(SPlugin, serverlessPath) {
let selectionPattern = thisResponse.selectionPattern || (responseKey === 'default' ? null : responseKey);
let params = {
httpMethod: _this.endpoint.method, /* required */
httpMethod: _this.endpoint.data.method, /* required */
resourceId: _this.resource.id, /* required */
restApiId: _this.restApi.id, /* required */
statusCode: thisResponse.statusCode, /* required */
@ -698,7 +699,7 @@ module.exports = function(SPlugin, serverlessPath) {
'"'
+ _this.options.stage + ' - '
+ _this.options.region
+ ' - ' + _this.endpoint.path + '": '
+ ' - ' + _this.endpoint.data.path + '": '
+ 'created method integration response');
}).catch(function(error) {
@ -781,7 +782,7 @@ module.exports = function(SPlugin, serverlessPath) {
'"'
+ _this.options.stage + ' - '
+ _this.options.region
+ ' - ' + _this.endpoint.path + '": '
+ ' - ' + _this.endpoint.data.path + '": '
+ 'removed existing lambda access policy statement');
})
.catch(function(error) {});
@ -796,8 +797,8 @@ module.exports = function(SPlugin, serverlessPath) {
let _this = this;
// Sanitize Path - Remove first and last slashes, if any
_this.endpoint.path = _this.endpoint.path.split('/');
_this.endpoint.path = _this.endpoint.path.join('/');
_this.endpoint.data.path = _this.endpoint.data.path.split('/');
_this.endpoint.data.path = _this.endpoint.data.path.join('/');
// Create new access policy statement
let params = {};
@ -812,8 +813,8 @@ module.exports = function(SPlugin, serverlessPath) {
+ ':'
+ _this.restApi.id
+ '/*/'
+ _this.endpoint.method
+ _this.endpoint.path;
+ _this.endpoint.data.method
+ _this.endpoint.data.path;
return _this.Lambda.addPermissionPromised(params)
.then(function() {
@ -824,7 +825,7 @@ module.exports = function(SPlugin, serverlessPath) {
+ ' - '
+ _this.options.region
+ ' - '
+ _this.endpoint.path
+ _this.endpoint.data.path
+ '": '
+ 'added permission to Lambda');
})

View File

@ -271,7 +271,7 @@ module.exports = function(SPlugin, serverlessPath) {
let options = {
stage: _this.options.stage,
region: region,
path: endpoint._sPath,
path: endpoint.sPath,
aliasEndpoint: _this.options.aliasEndpoint,
aliasRestApi: _this.options.aliasRestApi
};
@ -283,10 +283,11 @@ module.exports = function(SPlugin, serverlessPath) {
if (!_this.deployed) _this.deployed = {};
if (!_this.deployed[region]) _this.deployed[region] = [];
_this.deployed[region].push({
endpointModule: endpoint._module,
endpointFunction: endpoint._function,
endpointPath: endpoint.path,
endpointMethod: endpoint.method,
module: endpoint.module,
function: endpoint.function,
endpointSPath: endpoint.sPath,
endpointPath: endpoint.data.path,
endpointMethod: endpoint.data.method,
endpointUrl: result.data.url
});
@ -298,10 +299,11 @@ module.exports = function(SPlugin, serverlessPath) {
if (!_this.failed) _this.failed = {};
if (!_this.failed[region]) _this.failed[region] = [];
_this.failed[region].push({
endpointModule: endpoint._module,
endpointFunction: endpoint._function,
endpointPath: endpoint.path,
endpointMethod: endpoint.method,
module: endpoint.module,
function: endpoint.function,
endpointSPath: endpoint.sPath,
endpointPath: endpoint.data.path,
endpointMethod: endpoint.data.method,
message: e.message,
stack: e.stack
});

View File

@ -77,10 +77,6 @@ module.exports = function(SPlugin, serverlessPath) {
*/
_validateAndPrepare() {
let _this = this;
return BbPromise.resolve();
}
@ -139,4 +135,4 @@ module.exports = function(SPlugin, serverlessPath) {
}
return( EndpointDeployApiGateway );
};
};

View File

@ -115,15 +115,52 @@ module.exports = function(SPlugin, serverlessPath) {
.then(_this._processDeployment)
.then(function() {
// Display Failed Function Deployments
if (_this.failed) {
SCli.log('Failed to deploy the following functions in "'
+ _this.options.stage
+ '" to the following regions:');
// Display Errors
for (let i = 0; i < Object.keys(_this.failed).length; i++) {
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 );
SUtils.sDebug(region[j].stack);
}
}
}
// Display Successful Function Deployments
if (_this.deployed) {
// Status
SCli.log('Successfully deployed functions in "'
+ _this.options.stage
+ '" to the following regions: ');
// Display Functions & ARNs
for (let i = 0; i < Object.keys(_this.deployed).length; i++) {
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 );
}
}
}
/**
* Return Action Data
* - WARNING: Adjusting these will break Plugins
*/
return {
options: _this.options
options: _this.options,
data: {
deployed: _this.deployed,
failed: _this.failed
}
}
});
}
@ -137,18 +174,17 @@ module.exports = function(SPlugin, serverlessPath) {
let _this = this;
// Set Defaults
this.options.stage = this.options.stage ? this.options.stage : null;
this.options.paths = this.options.paths ? this.options.paths : [];
this.options.all = this.options.all ? true : false;
this.options.aliasFunction = this.options.aliasFunction ? this.options.aliasFunction : null;
_this.options.stage = _this.options.stage ? _this.options.stage : null;
_this.options.paths = _this.options.paths ? _this.options.paths : [];
_this.options.all = _this.options.all ? true : false;
_this.options.aliasFunction = _this.options.aliasFunction ? _this.options.aliasFunction : null;
// Instantiate Classes
this.project = new this.S.classes.Project(this.S);
this.meta = new this.S.classes.Meta(this.S);
_this.project = new _this.S.classes.Project(_this.S);
_this.meta = new _this.S.classes.Meta(_this.S);
// Set Deploy Regions
this.regions = this.options.region ? [this.options.region] : Object.keys(this.meta.data.private.stages[this.options.stage].regions);
this.deployed = {};
_this.regions = _this.options.region ? [_this.options.region] : Object.keys(_this.meta.data.private.stages[_this.options.stage].regions);
// Validate Paths
if (!_this.options.paths.length && !_this.options.all) {
@ -176,6 +212,7 @@ module.exports = function(SPlugin, serverlessPath) {
+ _this.options.stage
+ '" to the following regions: '
+ _this.regions.join(', '));
SCli.log('------------------------');
_this._spinner = SCli.spinner();
_this._spinner.start();
@ -195,39 +232,8 @@ module.exports = function(SPlugin, serverlessPath) {
})
.then(function() {
// Status
// Stop Spinner
_this._spinner.stop(true);
if (_this.failed) {
// Status
SCli.log('Failed to deploy the following functions in "' + _this.options.stage + '" to the following regions:');
// Display Methods & URLS
for (let i = 0; i < Object.keys(_this.failed).length; i++) {
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].function + ': ' + region[j].message );
SUtils.sDebug(region[j].stack);
}
}
} else {
// Status
SCli.log('Successfully deployed functions in "'
+ _this.options.stage
+ '" to the following regions: ');
// Display Functions & ARNs
for (let i = 0; i < Object.keys(_this.deployed).length; i++) {
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]);
}
}
}
});
}
@ -248,7 +254,7 @@ module.exports = function(SPlugin, serverlessPath) {
async.eachLimit(_this.functions, 5, function(func, cb) {
return new BbPromise(function(resolve) {
return BbPromise.try(function() {
// Nodejs
if (func.data.runtime = 'nodejs') {
@ -271,19 +277,21 @@ module.exports = function(SPlugin, serverlessPath) {
function: result.options.function,
pathDist: result.pathDist,
pathsPackaged: result.pathsPackaged
})
})
.then(function(result) {
return resolve(result);
});
});
}
})
.then(function(result) {
// Add Function and Region
if (!_this.deployed) _this.deployed = {};
if (!_this.deployed[region]) _this.deployed[region] = [];
let deployed = result.options.module + '-' + result.options.function + ': ' + result.lambdaAliasArn;
_this.deployed[region].push(deployed);
_this.deployed[region].push({
module: func.module,
function: func.data.name,
ARN: result.lambdaAliasArn
});
return cb();
@ -291,12 +299,13 @@ module.exports = function(SPlugin, serverlessPath) {
.catch(function(e) {
// Stash Failed Function Code
if (!_this.failed) _this.failed = {};
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,
function: func.module + '-' + func.data.name,
stack: e.stack
});
return cb();