@connected-home/serverless
Version:
The Serverless Application Framework - Powered By Amazon Web Services - http://www.serverless.com
545 lines (421 loc) • 17.3 kB
JavaScript
const SError = require('./Error'),
BbPromise = require('bluebird'),
fs = BbPromise.promisifyAll(require('fs')),
glob = require('glob'),
async = require('async'),
path = require('path'),
yaml = require('js-yaml'),
_ = require('lodash');
// TODO: Add Variables de/serialization methods
module.exports = function(S) {
class Serializer {
constructor() {
this._class = this.constructor.name;
}
serialize() {
return this['serialize' + this.constructor.name](this);
}
deserialize() {
return this['deserialize' + this.constructor.name](this);
}
/**
* Deserialize Project
*/
deserializeProject(project) {
let _this = this;
// Skip if project does not exist
if (!S.hasProject()) return BbPromise.resolve();
// Load Project
return S.utils.readFile(project.getFilePath())
.then((projectData) => project = _.merge(project, projectData))
// Load Templates
.then(function () {
return BbPromise.mapSeries([ 's-templates.json', 's-templates.yaml' ], (filename) => {
// Load Templates
let templatesFilePath = project.getRootPath(filename);
if (S.utils.fileExistsSync(templatesFilePath)) {
let template = new S.classes.Templates({}, templatesFilePath);
return template.load();
}
return null;
})
.filter((template) => { return template != null })
.then(function (templates) {
if (templates.length > 0) {
project.setTemplates(templates[0]);
}
});
})
.then(function () {
S.utils.sDebug(`deserializeProject: Load functions`);
// Load Functions
return globber(project.getRootPath(), 's-function.json')
.each(function (jsonPath) {
S.utils.sDebug(`deserializeProject: Load function: ${jsonPath}`);
let funcData = S.utils.readFileSync(jsonPath);
// Check function has a runtime property
if (!funcData.runtime) {
throw new SError(`Functions must have a runtime property as of Serverless v0.5`);
}
let func = new S.classes.Function(funcData, jsonPath);
return func.load()
.then(function (instance) {
// Check for function name uniqueness across project
if (project.getFunction(instance.name)) {
throw new SError(`Function name "${instance.name}" is already taken in the project. Function names must be unique across a project as of Serverless v0.5`);
}
project.setFunction(instance);
});
});
})
.then(function () {
// Load Project Variables & Stages
let variablesRootPath = project.getRootPath('_meta', 'variables');
// Skip if _meta/variables does not exist
if (!S.utils.dirExistsSync(variablesRootPath)) return;
let variableFiles = fs.readdirSync(variablesRootPath);
return BbPromise.resolve(variableFiles)
.each(function (variableFile) {
// Skip unrelated and hidden files
if (!variableFile || variableFile.charAt(0) === '.' || variableFile.indexOf('s-variables') == -1) return;
// Parse file name to get stage/region
let file = variableFile.replace('s-variables-', '').replace('.json', '');
if (file === 'common') {
// Load Variables
let variablesPath = path.join(variablesRootPath, 's-variables-common.json');
let variables = new S.classes.Variables({}, variablesPath);
variables.fromObject(S.utils.readFileSync(variablesPath));
project.setVariables(variables);
} else {
file = file.split('-');
if (file.length == 1) {
// Load Stage
let stage = new S.classes.Stage({name: file[0]});
return stage.load()
.then(function (instance) {
project.setStage(instance);
});
}
}
});
})
.then(function () {
// Load Resources
if (S.utils.fileExistsSync(project.getRootPath('s-resources-cf.json'))) {
let resources = new S.classes.Resources({}, project.getRootPath('s-resources-cf.json'));
return resources.load();
}
})
.then(function (resources) {
project.setResources(resources);
return project;
});
}
/**
* Serialize Project
*/
serializeProject(project, options) {
let _this = this,
serialize = [];
return BbPromise
// Save Functions
.map(project.getAllFunctions(), (f) => serialize.push(f.save(options)))
// Save Stages
.then(function () {
return BbPromise.map(project.getAllStages(), (stage) => {
return stage.save(options);
});
})
.then(function () {
// Save Project Variables "s-variables-common.json"
let variables = project.getVariables();
let variablesPath = project.getRootPath('_meta', 'variables', 's-variables-common.json');
S.utils.writeFileSync(variablesPath, variables.toObject());
})
.then(function () {
// Save Variables, Resources & Templates
serialize.push(project.getTemplates().save());
serialize.push(project.getAllResources().save());
// Clone and remove properties saved elsewhere
let clone = project.toObject();
if (clone.functions) delete clone.functions;
if (clone.resources) delete clone.resources;
if (clone.stages) delete clone.stages;
if (clone.variables) delete clone.variables;
if (clone.templates) delete clone.templates;
// Save s-project.json
serialize.push(S.utils.writeFile(_this.getRootPath('s-project.json'),
JSON.stringify(clone, null, 2)));
return BbPromise.all(serialize);
})
.then(function () {
return project;
});
}
/**
* Deserialize Function
*/
deserializeFunction(func) {
let _this = this;
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Function could not be loaded because no project path has been set on Serverless instance');
// Validate: Check function exists
let jsonPath = func.getRootPath('s-function.json');
if (!S.utils.fileExistsSync(jsonPath)) {
throw new SError(`Function "${func.getName()}" could not be loaded because it does not exist in your project`);
}
// Set Data
return func.fromObject(S.utils.readFileSync(jsonPath));
})
.then(function () {
return BbPromise.mapSeries([ 's-templates.json', 's-templates.yaml' ], (filename) => {
// Load Templates
let templatesFilePath = func.getRootPath(filename);
if (S.utils.fileExistsSync(templatesFilePath)) {
let template = new S.classes.Templates({}, templatesFilePath);
return template.load();
}
return null;
})
.filter(template => { return template != null })
.then(function (templates) {
if (templates.length > 0) {
func.setTemplate(templates[0]);
}
});
})
.then(function () {
return func;
});
}
/**
* Serialize Function
*/
serializeFunction(func) {
return BbPromise.try(function () {
// Validate: Check project path is set
if (!func.getProject()) throw new SError('Function could not be saved because no project path has been set on Serverless instance');
// Save Templates
return func.getTemplates().save();
})
.then(function () {
// Delete data saved elsewhere
let funcData = func.toObject({deep: true});
if (funcData.templates) delete funcData.templates;
// Save function
return S.utils.writeFile(func.getFilePath(), funcData);
})
.then(() => func);
}
/**
* Deserialize Resources
*/
deserializeResources(resources) {
let _this = this;
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Resources could not be loaded because no project path has been set on Serverless instance');
// Set Data
resources.fromObject(S.utils.readFileSync(resources.getFilePath()));
})
.then(function () {
return resources;
});
}
/**
* Serialize Resources
*/
serializeResources(resources) {
let _this = this;
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Resources could not be saved because no project path has been set on Serverless instance');
return S.utils.writeFile(resources.getProject().getRootPath('s-resources-cf.json'),
JSON.stringify(resources.toObject(), null, 2));
})
.then(function () {
return resources;
});
}
/**
* Deserialize Stage
*/
deserializeStage(stage) {
let _this = this;
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Stage could not be loaded because no project path has been set on Serverless instance');
// Load Stage's Variables
let variablesPath = stage.getProject().getRootPath('_meta', 'variables', 's-variables-' + stage.getName().toLowerCase() + '.json');
let variables = new S.classes.Variables({}, variablesPath);
variables.fromObject(S.utils.readFileSync(variablesPath));
stage.setVariables(variables);
// Load Stage's Regions
return fs.readdirSync(stage.getProject().getRootPath('_meta', 'variables'));
})
.each(function (variableFile) {
// Load region variables for this stage
if (variableFile.indexOf(`s-variables-${stage.name}-`) == -1) return;
const SRegion = S.classes.Region;
let regionName = SRegion.varsFilenameToRegionName(variableFile);
let region = new SRegion({ name: regionName }, stage);
return region.load()
.then((instance) => stage.setRegion(instance));
})
.then(function () {
return stage;
});
}
/**
* Serialize Stage
*/
serializeStage(stage) {
let _this = this;
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Stage could not be saved because no project path has been set on Serverless instance');
// Save Stage's Variables
let variablesPath = stage.getProject().getRootPath('_meta', 'variables', 's-variables-' + stage.getName().toLowerCase() + '.json');
S.utils.writeFileSync(variablesPath,
JSON.stringify(stage.getVariables().toObject(), null, 2));
return stage.getAllRegions();
})
.each(function (region) {
// Save Stage's Regions
return region.save();
})
.then(function () {
return stage;
});
}
/**
* Deserialize Region
*/
deserializeRegion(region) {
let _this = this;
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Region could not be loaded because no project path has been set on Serverless instance');
// Load region's Variables
let variablesPath = region.getProject().getRootPath('_meta', 'variables', 's-variables-' + region.getStage().getName().toLowerCase() + '-' + region.getName().toLowerCase().replace(/-/g, '') + '.json');
let variables = new S.classes.Variables({}, variablesPath);
variables.fromObject(S.utils.readFileSync(variablesPath));
region.setVariables(variables);
})
.then(function () {
return region;
});
}
/**
* Serialize Region
*/
serializeRegion(region) {
let _this = this;
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Region could not be saved because no project path has been set on Serverless instance');
// Save region's Variables
let variablesPath = region.getProject().getRootPath('_meta', 'variables', 's-variables-' + region.getStage().getName().toLowerCase() + '-' + region.getName().toLowerCase().replace(/-/g, '') + '.json');
S.utils.writeFileSync(variablesPath,
JSON.stringify(region.getVariables().toObject(), null, 2));
})
.then(function () {
return region;
});
}
/**
* Deserialize Templates
*/
deserializeTemplates(templates) {
let _this = this;
if (!templates.getFilePath()) return BbPromise.resolve();
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Templates could not be loaded because no project path has been set on Serverless instance');
// Set Data
let filePath = templates.getFilePath();
if (_.endsWith(filePath, '.yaml')) {
templates.fromObject(yaml.safeLoad(S.utils.readFileSync(filePath)));
} else {
templates.fromObject(S.utils.readFileSync(filePath));
}
})
.then(function () {
// Skip this, if we are in the project or component root,
if (S.utils.fileExistsSync(templates.getRootPath('s-project.json'))) {
return;
}
// People can store s-templates.json in infinite subfolders in their components. We have to find these...
// Loop through parent dirs and find parent templates until hitting component root
let parents = [],
parentDir = templates.getRootPath(),
notRoot = true;
while (notRoot) {
parentDir = path.dirname(parentDir);
notRoot = !S.utils.fileExistsSync(path.join(parentDir, 's-project.json'));
if (notRoot) {
if (S.utils.fileExistsSync(path.join(parentDir, 's-templates.json'))) {
parents.push(new S.classes.Templates(
S.utils.readFileSync(path.join(parentDir, 's-templates.json')),
path.join(parentDir, 's-templates.json')))
} else if (S.utils.fileExistsSync(path.join(parentDir, 's-templates.yaml'))) {
parents.push(new S.classes.Templates(
yaml.safeLoad(S.utils.readFileSync(path.join(parentDir, 's-templates.yaml'))),
path.join(parentDir, 's-templates.yaml')))
}
}
}
templates.setParents(parents);
})
.then(function () {
return templates;
});
}
/**
* Serialize Templates
* - Does not save template parents and may not need to.
*/
serializeTemplates(templates) {
let _this = this;
// Skip if template does not have filePath
if (!templates.getRootPath()) return BbPromise.resolve();
return BbPromise.try(function () {
// Validate: Check project path is set
if (!S.hasProject()) throw new SError('Templates could not be saved because no project path has been set on Serverless instance');
let templatesObj = templates.toObject()
if (_.isEmpty(templatesObj)) {
if (S.utils.fileExistsSync(templates.getFilePath())) {
return fs.unlinkAsync(templates.getFilePath());
}
} else {
if (_.endsWith(templates.getFilePath(), '.yaml')) {
return S.utils.writeFile(templates.getFilePath(), yaml.safeDump(templatesObj));
}
return S.utils.writeFile(templates.getFilePath(), templatesObj);
}
})
.then(function () {
return templates;
});
}
}
return Serializer;
};
/**
* Globber
* - Matches are sorted
*/
function globber(root, fileName) {
return new BbPromise(function(resolve, reject) {
const opts = { ignore: [
`${root}/_meta/**`,
`**/node_modules/**`
]};
return glob(`${root}/**/${fileName}`, opts, function (err, files) {
if (err) return reject(err);
resolve(files);
});
});
}
;