node-lambda-postinstall
Version:
Command line tool for locally running and remotely deploying your node.js applications to Amazon Lambda.
310 lines (246 loc) • 8.76 kB
JavaScript
;
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 dotenv = require('dotenv');
var Lambda = function () {
this.version = packageJson.version;
return this;
};
Lambda.prototype._createSampleFile = function (file) {
var exampleFile = process.cwd() + '/' + file;
var boilerplateFile = __dirname + '/' + file + '.example';
if (!fs.existsSync(exampleFile)) {
fs.writeFileSync(exampleFile, fs.readFileSync(boilerplateFile));
console.log(exampleFile + ' file successfully created');
}
};
Lambda.prototype.setup = function () {
console.log('Running setup.');
this._createSampleFile('.env');
this._createSampleFile('event.json');
this._createSampleFile('deploy.env');
console.log('Setup done. Edit the .env, deploy.env, and event.json files as needed.');
};
Lambda.prototype.run = function (program) {
this._createSampleFile('event.json');
var splitHandler = program.handler.split('.');
var filename = splitHandler[0] + '.js';
var handlername = splitHandler[1];
var handler = require(process.cwd() + '/' + filename)[handlername];
var event = require(process.cwd() + '/' + program.eventFile);
this._runHandler(handler, event);
};
Lambda.prototype._runHandler = function (handler, event) {
var context = {
succeed: function (result) {
console.log('succeed: ' + JSON.stringify(result));
process.exit(0);
},
fail: function (error) {
console.log('fail: ' + error);
process.exit(-1);
},
done: function () {
process.exit(0);
}
};
handler(event, context);
};
Lambda.prototype._params = function (program, buffer) {
var params = {
FunctionName: program.functionName + (program.environment ? '-' + program.environment : ''),
FunctionZip: buffer,
Handler: program.handler,
Mode: program.mode,
Role: program.role,
Runtime: program.runtime,
Description: program.description,
MemorySize: program.memorySize,
Timeout: program.timeout
};
if (program.version) {
params.FunctionName += ('-' + program.version);
}
return params;
};
/**
* @deprecated
*/
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) {
if (err) {
throw err;
}
return callback(null, true);
});
};
Lambda.prototype._npmInstall = function (program, codeDirectory, callback) {
exec('npm install --production --prefix ' + codeDirectory, function (err) {
if (err) {
throw err;
}
return callback(null, true);
});
};
Lambda.prototype._zip = function (program, codeDirectory, callback) {
var options = {
type: 'nodebuffer',
compression: 'DEFLATE'
};
console.log('=> Zipping repo. This might take up to 30 seconds');
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._nativeZip = function (program, codeDirectory, callback) {
var zipfile = this._zipfileTmpPath(program),
cmd = 'zip -r ' + zipfile + ' .';
exec(cmd, {
cwd: codeDirectory,
maxBuffer: 50 * 1024 * 1024
}, function (err) {
if (err !== null) {
return callback(err, null);
}
var data = fs.readFileSync(zipfile);
callback(null, data);
});
};
Lambda.prototype._postInstallScript = function (codeDirectory, callback) {
var script_filename='post_install.sh';
var cmd = './'+script_filename;
var filePath = [codeDirectory, script_filename].join('/');
fs.exists(filePath, function(exists) {
if (exists) {
console.log('=> Running post install script '+script_filename);
exec(cmd, { cwd: codeDirectory,maxBuffer: 50 * 1024 * 1024 }, function(error, stdout, stderr){
if (error) callback(error +" stdout: " + stdout + "stderr"+stderr);
else {
console.log("\t\t"+stdout);
callback(null);
}
});
} else {
callback(null);
}
});
};
Lambda.prototype._codeDirectory = function (program) {
var epoch_time = +new Date();
return os.tmpDir() + '/' + program.functionName + '-' + epoch_time;
};
Lambda.prototype._setEnvironmentVars = function (program, codeDirectory) {
console.log('=> Setting "environment variables" for Lambda from %s', program.configFile);
// Which file is the handler?
var handlerFileName = codeDirectory + '/' + program.handler.split('.').shift() + '.js';
var contents = fs.readFileSync(handlerFileName);
var configValues = fs.readFileSync(program.configFile);
var prefix = '////////////////////////////////////\n// "Environment Variables"\n';
var config = dotenv.parse(configValues);
for (var k in config) {
if (!config.hasOwnProperty(k)) {
continue;
}
// Use JSON.stringify to ensure that it's valid code.
prefix += 'process.env["' + k + '"]=' + JSON.stringify(config[k]) + ';\n';
}
prefix += '////////////////////////////////////\n\n';
fs.writeFileSync(handlerFileName, prefix + contents.toString());
};
Lambda.prototype.deploy = function (program) {
this._createSampleFile('.env');
// Warn if not building on 64-bit linux
var arch = process.platform + '.' + process.arch;
if (arch !== 'linux.x64') {
console.warn('Warning!!! You are building on a platform that is not 64-bit Linux (%s). ' +
'If any of your Node dependencies include C-extensions, they may not work as expected in the ' +
'Lambda environment.\n\n', arch);
}
var _this = this;
var regions = program.region.split(',');
var codeDirectory = _this._codeDirectory(program);
console.log('=> Moving files to temporary directory');
// Move all files to tmp folder (except .git, .log, event.json and node_modules)
_this._rsync(program, codeDirectory, function (err) {
if (err) {
console.error(err);
return;
}
console.log('=> Running npm install --production');
_this._npmInstall(program, codeDirectory, function (err) {
if (err) {
console.error(err);
return;
}
_this._postInstallScript(codeDirectory, function (err) {
if (err) {
console.error(err);
return;
}
// Add custom environment variables if program.configFile is defined
if (program.configFile) {
_this._setEnvironmentVars(program, codeDirectory);
}
console.log('=> Zipping deployment package');
var archive = process.platform !== 'win32' ? _this._nativeZip : _this._zip;
archive = archive.bind(_this);
archive(program, codeDirectory, function (err, buffer) {
if (err) {
console.error(err);
return;
}
console.log('=> Reading zip file to memory');
var params = _this._params(program, buffer);
async.map(regions, function (region, cb) {
console.log('=> Uploading zip file to AWS Lambda ' + region + ' with parameters:');
console.log(params);
var aws_security = {
accessKeyId: program.accessKey,
secretAccessKey: program.secretKey,
region: region
};
if (program.sessionToken){
aws_security.sessionToken = program.sessionToken;
};
aws.config.update(aws_security);
var lambda = new aws.Lambda({
apiVersion: '2014-11-11'
});
lambda.uploadFunction(params, function (err, data) {
cb(err, data);
});
}, function (err, results) {
if (err) {
console.error(err);
} else {
console.log('=> Zip file(s) done uploading. Results follow: ');
console.log(results);
}
});
});
});
});
});
};
module.exports = new Lambda();