servant-cli
Version:
Command line tool for building Commands with Servant (http://www.getservant.com)
306 lines (214 loc) • 9.84 kB
JavaScript
;
/**
* Servant Command Line Interface
*/
var aws = require('aws-sdk');
var exec = require('child_process').exec;
var fs = require('fs');
var os = require('os');
var packageJson = require('./../package.json');
var path = require('path');
var async = require('async');
var zip = new require('node-zip')();
var wrench = require('wrench');
var jsonfile = require('jsonfile');
var del = require('del');
var Lambda = function() {
this.version = packageJson.version;
return this;
};
/**
* Lambda: Init
*/
Lambda.prototype.init = function(program) {
console.log('****** Setting up a new Servant Extension...');
// Create .env File
fs.writeFileSync(process.cwd() + '/.env', fs.readFileSync(__dirname + '/.env.template'));
// Create package.json
fs.writeFileSync(process.cwd() + '/package.json', fs.readFileSync(__dirname + '/package.json'));
// Create Extension Root File: index.js
fs.writeFileSync(process.cwd() + '/index.js', fs.readFileSync(__dirname + '/index.js'));
// Create Extension Core File: extension.js
fs.writeFileSync(process.cwd() + '/extension.js', fs.readFileSync(__dirname + '/extension.js'));
// Create Command Router File: extension_router.json
jsonfile.writeFileSync(process.cwd() + '/extension_router.json', {
Commands: {}
});
// Create Commands Folder
var dir = './commands';
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
console.log('****** Success: Created boilerplate files for a new Servant Extension');
console.log('****** What\'s Next: If your AWS account is set up, open the .env file and add your AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ROLE_ARN, AWS_FUNCTION_NAME. If your AWS Account is not set up, follow the instructions in the documentation here: https://github.com/servant-app/servant-cli');
};
/**
* Command
* - Creates a new Servant Command
*/
Lambda.prototype.command = function(program) {
// Validate
// Check Command Router JSON File
if (!fs.existsSync('./extension_router.json')) return console.log('****** Servant Error: You don\'t have a extension_router.json file. Make one before creating a command.');
// Check Root Extension Folder
if (!fs.existsSync('./commands')) return console.log('****** Servant Error: You don\'t have a Commands folder. Are you in the root directory of your Extension?');
if (fs.existsSync('./commands/' + program.name)) return console.log('****** Servant Error: You already have a command with this name in your Commands folder.');
// Check Name is included
if (!program.name || program.name === 'none') return console.log('****** Servant Error: You must include a Name. Like: -n test_command');
// Check Command ID is included
if (!program.command_id || program.command_id === 'none') return console.log('****** Servant Error: You must include a Command ID. Like: -i com_jkaI19lL');
// Sanitize Command Name
program.name = program.name.toLowerCase().replace(/[^a-zA-Z0-9]/g, '').replace(/ /g, "_");
// Create Command Subfolder
fs.mkdirSync('./commands/' + program.name);
// Create Command Root File
fs.writeFileSync(process.cwd() + '/commands/' + program.name + '/command.js', fs.readFileSync(__dirname + '/command_simple.js'));
// Create Command Event File
jsonfile.writeFileSync(process.cwd() + '/commands/' + program.name + '/event.json', {
command_id: program.command_id,
data: {}
});
// Add Command To Command Router
var extension_router_file = process.cwd() + '/extension_router.json';
var router_object = jsonfile.readFileSync(extension_router_file);
router_object.Commands[program.command_id] = program.name;
jsonfile.writeFileSync(extension_router_file, router_object);
// Conclude
console.log('****** Success: Created boilerplate files for a new Servant Command called: ' + program.name);
};
/**
* Lambda: Run Locally
*/
Lambda.prototype.run = function(program) {
// Check if command name is given
if (!program.name || program.name === 'none') return console.log('****** Servant Error: Please specify a command to run with the -c flag. Like this: servant run -c example_command');
// Check Command Exists
if (!fs.existsSync(process.cwd() + '/commands/' + program.name)) return console.log('****** Servant Error: Command not found. Are you sure you typed its name correctly?');
// Check if in extension's root directory
if (!fs.existsSync(process.cwd() + '/index.js') || !fs.existsSync(process.cwd() + '/commands/' + program.name + '/event.json')) return console.log('****** Servant Error: You must be in the root directory of your extension to test running a command.');
var extension = require(process.cwd() + '/index.js');
var event = require(process.cwd() + '/commands/' + program.name + '/event.json');
// Run Handler
this._runHandler(extension.handler, event);
};
Lambda.prototype._runHandler = function(handler, event) {
var context = {
done: function(error, result) {
if (error) {
console.log('****** Command Returned An Error: ');
console.log(error);
}
if (result) {
console.log('****** Command Finished Successfully: ');
console.log(result);
}
return process.exit(0);
}
};
handler(event, context);
};
Lambda.prototype._params = function(program, buffer) {
var params = {
FunctionName: program.functionName,
FunctionZip: buffer,
Handler: program.handler,
Mode: program.mode,
Role: program.role,
Runtime: program.runtime,
Description: program.description,
MemorySize: program.memorySize,
Timeout: program.timeout
};
return params;
};
Lambda.prototype._zipfileTmpPath = function(program) {
var ms_since_epoch = +new Date;
var filename = program.functionName + '-' + ms_since_epoch + '.zip';
var zipfile = path.join(os.tmpDir(), filename);
return zipfile;
};
Lambda.prototype._rsync = function(program, codeDirectory, callback) {
exec('rsync -r --exclude=.git --exclude=*.log --exclude=node_modules . ' + codeDirectory, function(err, stdout, stderr) {
if (err) {
throw err;
}
return callback(null, true);
});
};
Lambda.prototype._npmInstall = function(program, codeDirectory, callback) {
exec('npm install --production --prefix ' + codeDirectory, function(err, stdout, stderr) {
if (err) {
throw err;
}
return callback(null, true);
});
};
Lambda.prototype._zip = function(program, codeDirectory, callback) {
var zipfile = this._zipfileTmpPath(program);
var options = {
type: 'nodebuffer',
compression: 'DEFLATE'
}
var files = wrench.readdirSyncRecursive(codeDirectory);
files.forEach(function(file) {
var filePath = [codeDirectory, file].join('/');
var isFile = fs.lstatSync(filePath).isFile();
if (isFile) {
var content = fs.readFileSync(filePath);
zip.file(file, content);
}
});
var data = zip.generate(options);
return callback(null, data);
};
Lambda.prototype._codeDirectory = function(program) {
var epoch_time = +new Date;
return os.tmpDir() + '/' + program.functionName + '-' + epoch_time;
};
/**
* Deploy
* - Zip dependencies and upload to AWS Lambda
*/
Lambda.prototype.deploy = function(program) {
console.log('****** Deploying your Servant Extension to AWS Lambda. This might take a few minutes...');
// Defaults
var _this = this;
var regions = program.region.split(',');
var codeDirectory = _this._codeDirectory(program);
var fs = require('fs');
var dir = './stmp';
// Create Temp Folder
if (fs.existsSync(dir)) del.sync(['./stmp']);
fs.mkdirSync(dir);
// Move all files to tmp folder (except .git, .log, event.json and node_modules)
_this._rsync(program, codeDirectory, function(err, result) {
_this._npmInstall(program, codeDirectory, function(err, result) {
_this._zip(program, codeDirectory, function(err, buffer) {
console.log('****** Zipping up your Extension\'s files...');
//var buffer = fs.readFileSync(zipfile);
var params = _this._params(program, buffer);
async.map(regions, function(region, cb) {
console.log('****** Uploading your Extension to AWS Lambda with these parameters: ');
console.log(params);
aws.config.update({
accessKeyId: program.accessKey,
secretAccessKey: program.secretKey,
region: region
});
var lambda = new aws.Lambda({
apiVersion: '2014-11-11'
});
lambda.uploadFunction(params, function(err, data) {
cb(err, data);
});
}, function(err, results) {
if (err) return console.log(err);
// Remove Temp Directory
del(['./stmp'], function(err, paths) {
if (err) return console.log(err);
return console.log('****** Success: Your Servant Extension has been successfully deployed to AWS Lambda. If you haven\'t already, register your Extension @ http://www.getservant.com as an AWS Lambda App with this AWS Lambda ARN: ' + results[0].FunctionARN);
});
});
});
});
});
};
module.exports = new Lambda();