ModuleCreate and FunctionCreate: refactor

This commit is contained in:
Eslam A. Hefnawy 2016-01-01 18:46:31 +02:00
parent 1fbff06925
commit aa9560586d
5 changed files with 120 additions and 182 deletions

View File

@ -22,7 +22,7 @@ class ServerlessFunction {
constructor(Serverless, options) {
this.S = Serverless;
this.options = options || {};
this.load(options);
this.load();
}
/**
@ -30,13 +30,11 @@ class ServerlessFunction {
* - Load from source (i.e., file system);
*/
load(module, func) {
load() {
let _this = this;
if (module) {
_this.options.module = module;
_this.options.function = func;
if (_this.options.module && _this.options.function) {
_this.options.functionPath = path.join(
_this.S.config.projectPath,
'back',
@ -47,14 +45,53 @@ class ServerlessFunction {
's-function.json');
}
// TODO: Validate function exists
// Defaults
_this.data = {};
_this.data.endpoints = [];
_this.data.name = _this.options.function || 'function' + SUtils.generateShortId(6);
_this.data.custom = {
"excludePatterns": [],
"envVars": []
};
_this.data.handler = "";
_this.data.timeout = 6;
_this.data.memorySize = 1024;
_this.data.events = [
{
"name": "default",
"eventSourceArn": "",
"startingPosition": "TRIM_HORIZON",
"batchSize": 100,
"enabled": true
}
];
_this.data.endpoints = [
{
"path": "",
"method": "GET",
"authorizationType": "none",
"apiKeyRequired": false,
"requestParameters": {},
"requestTemplates": {
"application/json": ""
},
"responses": {
"default": {
"statusCode": "200",
"responseParameters": {},
"responseModels": {},
"responseTemplates": {
"application/json": ""
}
},
"400": {
"statusCode": "400"
}
}
}
];
// If no function path exists, return
if (!_this.options.functionPath) return;
if (!_this.options.functionPath || !SUtils.fileExistsSync(path.join(_this.options.functionPath, 's-function.json'))) return;
let functionJson = SUtils.readAndParseJsonSync(path.join(_this.options.functionPath, 's-function.json'));

View File

@ -22,7 +22,7 @@ class ServerlessModule {
constructor(Serverless, options) {
this.S = Serverless;
this.options = options || {};
this.load(options);
this.load();
}
/**
@ -30,25 +30,23 @@ class ServerlessModule {
* - Load from source (i.e., file system);
*/
load(module) {
load() {
let _this = this;
if (moduleJson) {
_this.options.module = moduleJson;
_this.options.modulePath = path.join(_this.S.config.projectPath, 'back', 'modules', _this.options.module, 's-module.json')
if (_this.options.module) {
_this.options.modulePath = path.join(_this.S.config.projectPath, 'back', 'modules', _this.options.module)
}
// TODO: Validate module exists in project
// Defaults
_this.data = {};
_this.data.name = 'module' + SUtils.generateShortId(6);
_this.data.name = _this.options.module || 'module' + SUtils.generateShortId(6);
_this.data.version = '0.0.1';
_this.data.profile = 'aws-v' + require('../package.json').version;
_this.data.location = 'https://github.com/...';
_this.data.author = '';
_this.data.description = 'A Serverless Module';
_this.data.runtime = _this.options.runtime || 'nodejs';
_this.data.custom = {};
_this.data.functions = {};
_this.data.cloudFormation = {
@ -57,9 +55,9 @@ class ServerlessModule {
};
// If no project path exists, return
if (!_this.S.config.projectPath || _this.options.module) return;
if (!_this.S.config.projectPath || !_this.options.module || !SUtils.fileExistsSync(path.join(_this.options.modulePath, 's-module.json'))) return;
let moduleJson = SUtils.readAndParseJsonSync(path.join(_this.S.config.projectPath, 'back', 'modules', _this.options.module, 's-module.json'));
let moduleJson = SUtils.readAndParseJsonSync(path.join(_this.options.modulePath, 's-module.json'));
// Add Functions
moduleJson.functions = {};

View File

@ -32,7 +32,7 @@ module.exports = function(SPlugin, serverlessPath) {
constructor(S, config) {
super(S, config);
this._templatesDir = path.join(__dirname, '..', 'templates');
this.evt = {};
this.options = {};
}
static getName() {
@ -83,19 +83,15 @@ usage: serverless function create <function>`,
* Action
*/
functionCreate(evt) {
functionCreate(options) {
let _this = this;
this.options = options || {};
if (evt) {
_this.evt = evt;
_this.S.config.interactive = false;
}
// If CLI and not subaction, parse options
if (_this.S.cli && (!evt || !evt._subaction)) {
_this.evt = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them
if (_this.S.cli.options.nonInteractive) _this.S.config.interactive = false;
// If CLI, parse arguments
if (this.S.cli && (!options || !options.subaction)) {
this.options = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them
if (this.S.cli.options.nonInteractive) this.S.config.interactive = false;
}
return _this._promptModuleFunction()
@ -103,8 +99,12 @@ usage: serverless function create <function>`,
.then(_this._validateAndPrepare)
.then(_this._createFunctionSkeleton)
.then(function() {
SCli.log('Successfully created function: "' + _this.functionName + '"');
return _this.evt;
SCli.log('Successfully created function: "' + _this.options.function + '"');
// Return
return {
options: _this.options
}
});
}
@ -120,7 +120,7 @@ usage: serverless function create <function>`,
if (!_this.S.config.interactive) return BbPromise.resolve();
['module', 'function'].forEach(memberVarKey => {
overrides[memberVarKey] = _this['evt'][memberVarKey];
overrides[memberVarKey] = _this['options'][memberVarKey];
});
let prompts = {
@ -140,8 +140,8 @@ usage: serverless function create <function>`,
return _this.cliPromptInput(prompts, overrides)
.then(function(answers) {
_this.evt.module = answers.module;
_this.evt.function = answers.function;
_this.options.module = answers.module;
_this.options.function = answers.function;
});
};
@ -153,53 +153,34 @@ usage: serverless function create <function>`,
_validateAndPrepare() {
// Non interactive validation
if (!this.S.config.interactive) {
if (!this.evt.module || !this.evt.function) {
if (!this.options.module || !this.options.function) {
return BbPromise.reject(new SError('Missing module or/and function names'));
}
}
// Add default runtime
if (!this.evt.runtime) {
this.evt.runtime = 'nodejs';
}
// Check if runtime is supported
if (!SUtils.supportedRuntimes[this.evt.runtime]) {
return BbPromise.reject(new SError('Unsupported runtime ' + this.evt.runtime, SError.errorCodes.UNKNOWN));
}
// Set default function template
if (!this.evt.template) this.evt.template = 's-function.json';
// Sanitize function folder name
this.evt.function = this.evt.function.toLowerCase().trim()
this.options.function = this.options.function.toLowerCase().trim()
.replace(/\s/g, '-')
.replace(/[^a-zA-Z-\d:]/g, '')
.substring(0, 19);
// If module does NOT exists, throw error
this.evt.module = this.evt.module.trim();
this.options.module = this.options.module.trim();
let moduleFullPath = path.join(this.S.config.projectPath, 'back', 'modules', this.evt.module);
let moduleFullPath = path.join(this.S.config.projectPath, 'back', 'modules', this.options.module);
if (!SUtils.dirExistsSync(moduleFullPath)) {
return BbPromise.reject(new SError('module ' + this.evt.module + ' does NOT exist',
return BbPromise.reject(new SError('module ' + this.options.module + ' does NOT exist',
SError.errorCodes.INVALID_PROJECT_SERVERLESS));
}
// If function already exists in module, throw error
let functionPath = path.join(this.S.config.projectPath, 'back', 'modules', this.evt.module, this.evt.function);
let functionPath = path.join(this.S.config.projectPath, 'back', 'modules', this.options.module, 'functions', this.options.function);
if (SUtils.dirExistsSync(functionPath)) {
return BbPromise.reject(new SError('function ' + this.evt.function + ' already exists in module ' + this.evt.module,
return BbPromise.reject(new SError('function ' + this.options.function + ' already exists in module ' + this.options.module,
SError.errorCodes.INVALID_PROJECT_SERVERLESS
));
}
// Fetch Module Json
let moduleName = this.evt.module;
this.evt.module = SUtils.readAndParseJsonSync(path.join(moduleFullPath, 's-module.json'));
this.evt.module.pathModule = path.join('back', 'modules', moduleName);
return BbPromise.resolve();
};
@ -212,62 +193,23 @@ usage: serverless function create <function>`,
let _this = this,
writeDeferred = [],
functionInstance = new _this.S.classes.Function(_this.S, { module: _this.options.module, function: _this.options.function}),
functionPath = path.join('back', 'modules', _this.options.module, 'functions', _this.options.function),
eventJson = {},
handlerJs = fs.readFileSync(path.join(this._templatesDir, 'nodejs', 'handler.js'));
// Generate Function JSON
let functionShortName = _this.evt.function;
let functionJson = _this._generateFunctionJson();
// Change EVT function to Object
_this.evt.function = functionJson.functions[Object.keys(functionJson.functions)[0]];
_this.pathFunction = path.join('back', 'modules', _this.evt.module.name, functionShortName);
// Set function on event
eventJson[_this.functionName] = {};
eventJson[_this.options.function] = {};
// Write function files: directory, handler, event.json, s-function.json
writeDeferred.push(
fs.mkdirSync(path.join(_this.S.config.projectPath, _this.pathFunction)),
SUtils.writeFile(path.join(path.join(_this.S.config.projectPath, _this.pathFunction), 'handler.js'), handlerJs),
SUtils.writeFile(path.join(_this.S.config.projectPath, _this.pathFunction, 'event.json'), JSON.stringify(eventJson, null, 2)),
SUtils.writeFile(path.join(_this.S.config.projectPath, _this.pathFunction, 's-function.json'), JSON.stringify(functionJson, null, 2))
fs.mkdirSync(path.join(_this.S.config.projectPath, functionPath)),
SUtils.writeFile(path.join(path.join(_this.S.config.projectPath, functionPath), 'handler.js'), handlerJs),
SUtils.writeFile(path.join(_this.S.config.projectPath, functionPath, 'event.json'), JSON.stringify(eventJson, null, 2)),
functionInstance.save()
);
// Add path function to evt function
_this.evt.function.pathFunction = _this.pathFunction;
_this.evt.function.name = _this.functionName;
return BbPromise.all(writeDeferred);
};
/*
* Generate s-function.json template (private)
*/
_generateFunctionJson() {
let _this = this;
// Load s-function.json template
let functionJsonTemplate = SUtils.readAndParseJsonSync(path.join(_this._templatesDir, _this.evt.template));
// Add Full Function Name (Include Module Name)
_this.functionName = _this.evt.function;
functionJsonTemplate.functions[_this.functionName] = functionJsonTemplate.functions.functionTemplate;
// Remove Template
delete functionJsonTemplate.functions.functionTemplate;
// Add Function Handler
functionJsonTemplate.functions[_this.functionName].handler = path.join('modules', _this.evt.module.name, _this.evt.function, 'handler.handler');
// Add endpoint path
functionJsonTemplate.functions[_this.functionName].endpoints[0].path = _this.evt.module.name + '/' + _this.evt.function;
// Return
return functionJsonTemplate;
};
}
return( FunctionCreate );

View File

@ -31,7 +31,7 @@ module.exports = function(SPlugin, serverlessPath) {
constructor(S, config) {
super(S, config);
this._templatesDir = path.join(__dirname, '..', 'templates');
this.evt = {};
this.options = {};
}
static getName() {
@ -81,22 +81,15 @@ usage: serverless module create`,
* Action
*/
moduleCreate(evt) {
moduleCreate(options) {
let _this = this;
this.options = options || {};
if (evt) {
_this.evt = evt;
_this.S.config.interactive = false;
}
// If CLI, parse options
if (_this.S.cli) {
_this.evt = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them
if (_this.S.cli.options.nonInteractive) {
_this.S.config.interactive = false;
}
// If CLI, parse arguments
if (this.S.cli && (!options || !options.subaction)) {
this.options = JSON.parse(JSON.stringify(this.S.cli.options)); // Important: Clone objects, don't refer to them
if (this.S.cli.options.nonInteractive) this.S.config.interactive = false;
}
return _this._promptModuleFunction()
@ -106,23 +99,23 @@ usage: serverless module create`,
.then(function() {
let _this = this,
evtClone = {
options = {
_subaction: true,
module: _this.evt.module.name,
function: _this.evt.function
module: _this.options.module,
function: _this.options.function
};
return _this.S.actions.functionCreate(evtClone)
.then(function(evt) {
_this.evt.function = evt.function;
});
return _this.S.actions.functionCreate(options)
})
.then(_this._installModuleDependencies)
.then(function() {
SCli.log('Successfully created new serverless module "' + _this.evt.module.name + '" with its first function "' + _this.evt.function.name + '"');
// Return Event
return _this.evt;
// Return
return {
options: _this.options
}
});
};
@ -138,7 +131,7 @@ usage: serverless module create`,
if (!_this.S.config.interactive) return BbPromise.resolve();
['module', 'function'].forEach(memberVarKey => {
overrides[memberVarKey] = _this['evt'][memberVarKey];
overrides[memberVarKey] = _this['options'][memberVarKey];
});
let prompts = {
@ -158,8 +151,8 @@ usage: serverless module create`,
return _this.cliPromptInput(prompts, overrides)
.then(function(answers) {
_this.evt.module = answers.module;
_this.evt.function = answers.function;
_this.options.module = answers.module;
_this.options.function = answers.function;
});
};
@ -171,40 +164,37 @@ usage: serverless module create`,
// non interactive validation
if (!this.S.config.interactive) {
if (!this.evt.module || !this.evt.function) {
if (!this.options.module || !this.options.function) {
return BbPromise.reject(new SError('Missing module or/and function names'));
}
}
// Add default runtime
if (!this.evt.runtime) {
this.evt.runtime = 'nodejs';
if (!this.options.runtime) {
this.options.runtime = 'nodejs';
}
if (!SUtils.supportedRuntimes[this.evt.runtime]) {
return BbPromise.reject(new SError('Unsupported runtime ' + this.evt.runtime, SError.errorCodes.UNKNOWN));
if (!SUtils.supportedRuntimes[this.options.runtime]) {
return BbPromise.reject(new SError('Unsupported runtime ' + this.options.runtime, SError.errorCodes.UNKNOWN));
}
// Set default function template
if (!this.evt.template) this.evt.template = 's-function.json';
// Sanitize module
this.evt.module = this.evt.module.toLowerCase().trim()
this.options.module = this.options.module.toLowerCase().trim()
.replace(/\s/g, '-')
.replace(/[^a-zA-Z-\d:]/g, '')
.substring(0, 19);
// Sanitize function
this.evt.function = this.evt.function.toLowerCase().trim()
this.options.function = this.options.function.toLowerCase().trim()
.replace(/\s/g, '-')
.replace(/[^a-zA-Z-\d:]/g, '')
.substring(0, 19);
// If module already exists, throw error
let pathModule = path.join(this.S.config.projectPath, 'back', 'modules', this.evt.module);
let pathModule = path.join(this.S.config.projectPath, 'back', 'modules', this.options.module);
if (SUtils.dirExistsSync(pathModule)) {
return BbPromise.reject(new SError(
'Module ' + this.evt.module + ' already exists',
'Module ' + this.options.module + ' already exists',
SError.errorCodes.INVALID_PROJECT_SERVERLESS
));
}
@ -220,59 +210,30 @@ usage: serverless module create`,
let _this = this,
writeDeferred = [],
moduleJsonTemplate = _this._generateModuleJson(),
module = new _this.S.classes.Module(_this.S, { module: _this.options.module, runtime: _this.options.runtime }),
pathModule = path.join('back', 'modules', _this.options.module),
packageJsonTemplate = SUtils.readAndParseJsonSync(path.join(_this._templatesDir, 'nodejs', 'package.json')),
libJs = fs.readFileSync(path.join(this._templatesDir, 'nodejs', 'index.js'));
// Save Path
let pathModule = path.join('back', 'modules', _this.evt.module);
// Prep package.json
packageJsonTemplate.name = _this.evt.module;
packageJsonTemplate.name = _this.options.module;
packageJsonTemplate.description = 'Dependencies for a Serverless Module written in Node.js';
// Write base module structure
writeDeferred.push(
fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule)),
fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule, 'lib')),
fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule, 'package')),
fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule, '_module')),
fs.mkdirSync(path.join(_this.S.config.projectPath, pathModule, 'functions')),
SUtils.writeFile(path.join(_this.S.config.projectPath, pathModule, 'lib', 'index.js'), libJs),
SUtils.writeFile(path.join(_this.S.config.projectPath, pathModule, 'package.json'), JSON.stringify(packageJsonTemplate, null, 2)),
SUtils.writeFile(path.join(_this.S.config.projectPath, pathModule, 's-module.json'), JSON.stringify(moduleJsonTemplate, null, 2))
);
module.save()
// Set evt properties
let moduleName = _this.evt.module;
_this.evt.module = moduleJsonTemplate;
_this.evt.module.name = moduleName;
_this.evt.module.pathModule = pathModule;
);
return BbPromise.all(writeDeferred);
};
/*
* Generate s-module.json template (private)
*/
_generateModuleJson() {
let _this = this;
let moduleJsonTemplate = SUtils.readAndParseJsonSync(path.join(this._templatesDir, 's-module.json'));
moduleJsonTemplate.name = _this.evt.module;
// Add runtime
switch (_this.evt.runtime) {
case 'nodejs':
moduleJsonTemplate.runtime = 'nodejs';
break;
default:
return BbPromise.reject(new SError('Unsupported runtime ' + this.evt.runtime, SError.errorCodes.UNKNOWN));
break;
}
// Return
return moduleJsonTemplate;
};
_installModuleDependencies() {
SCli.log('Installing "serverless-helpers" for this module via NPM...');

View File

@ -208,7 +208,7 @@ usage: serverless stage create`,
.then(function(result) {
// Overwrite this meta w/ RegionCreate's meta
this.meta = result.meta;
_this.meta = result.meta;
});
}
}