mirror of
https://github.com/serverless/serverless.git
synced 2026-01-18 14:58:43 +00:00
CodePackageLambdaNodejs: start work on this
This commit is contained in:
parent
9c81468260
commit
53fb4e6b3a
@ -2,17 +2,23 @@
|
||||
|
||||
/**
|
||||
* Action: Code Package: Lambda: Nodejs
|
||||
* - Collects and optimizes Lambda code in a temp folder
|
||||
* - Accepts one function
|
||||
* - Collects and optimizes the function's Lambda code in a temp folder
|
||||
*/
|
||||
|
||||
const JawsPlugin = require('../../JawsPlugin'),
|
||||
JawsError = require('../../jaws-error'),
|
||||
JawsUtils = require('../../utils/index'),
|
||||
extend = require('util')._extend,
|
||||
JawsCli = require('../../utils/cli'),
|
||||
BbPromise = require('bluebird'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
os = require('os');
|
||||
os = require('os'),
|
||||
babelify = require('babelify'),
|
||||
browserify = require('browserify'),
|
||||
UglifyJS = require('uglify-js'),
|
||||
wrench = require('wrench'),
|
||||
Zip = require('node-zip');
|
||||
|
||||
// Promisify fs module
|
||||
BbPromise.promisifyAll(fs);
|
||||
@ -41,7 +47,7 @@ class CodePackageLambdaNodejs extends JawsPlugin {
|
||||
|
||||
registerActions() {
|
||||
|
||||
this.Jaws.addAction(this.CodePackageLambdaNodejs.bind(this), {
|
||||
this.Jaws.addAction(this.codePackageLambdaNodejs.bind(this), {
|
||||
handler: 'codePackageLambdaNodejs',
|
||||
description: 'Deploys the code or endpoint of a function, or both'
|
||||
});
|
||||
@ -58,89 +64,355 @@ class CodePackageLambdaNodejs extends JawsPlugin {
|
||||
let _this = this;
|
||||
_this.evt = evt;
|
||||
|
||||
// Load AWS Service Instances
|
||||
let awsConfig = {
|
||||
region: _this.evt.deployRegion.region,
|
||||
accessKeyId: _this.Jaws._awsAdminKeyId,
|
||||
secretAccessKey: _this.Jaws._awsAdminSecretKey,
|
||||
};
|
||||
_this.S3 = require('../../utils/aws/S3')(awsConfig);
|
||||
|
||||
// Flow
|
||||
return BbPromise.try(function() {})
|
||||
.bind(_this)
|
||||
.then(_this._validateAndPrepare)
|
||||
.then(_this._promptStage)
|
||||
.then(_this._prepareRegions)
|
||||
.then(_this._deployRegions);
|
||||
.then(_this._createDistFolder)
|
||||
.then(_this._package)
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate And Prepare
|
||||
* - If CLI, maps CLI input to event object
|
||||
*/
|
||||
|
||||
_validateAndPrepare() {
|
||||
|
||||
let _this = this,
|
||||
lambda;
|
||||
|
||||
// Require function config
|
||||
let functionJson = require(_this.evt.currentFunction);
|
||||
|
||||
// Skip Function if it does not have a lambda
|
||||
try {
|
||||
lambda = functionJson.cloudFormation.lambda;
|
||||
} catch(error) {
|
||||
return Promise.reject(new JawsError(_this.evt.currentFunction + 'does not have a lambda property'));
|
||||
}
|
||||
|
||||
// Validate lambda attributes
|
||||
if (!lambda.Type
|
||||
|| !lambda.Properties
|
||||
|| !lambda.Properties.Runtime
|
||||
|| !lambda.Properties.Handler) {
|
||||
return Promise.reject(new JawsError('Missing one of many required lambda attributes'));
|
||||
}
|
||||
|
||||
// Add function path to functionJson
|
||||
functionJson.path = _this.evt.currentFunction;
|
||||
|
||||
// Change function path to object
|
||||
_this.evt.currentFunction = functionJson;
|
||||
|
||||
return BbPromise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Distribution Folder
|
||||
*/
|
||||
|
||||
_createDistFolder() {
|
||||
|
||||
let _this = this;
|
||||
|
||||
// If cli, process command line input
|
||||
if (_this.Jaws.cli) {
|
||||
// Create dist folder
|
||||
let d = new Date();
|
||||
_this.evt.distDir = path.join(os.tmpdir(), _this.evt.currentFunction.name + '@' + d.getTime());
|
||||
|
||||
// Add options to evt
|
||||
_this.evt = _this.Jaws.cli.options;
|
||||
// Status
|
||||
JawsCli.log('Lambda Deployer: Packaging "' + _this.evt.currentFunction.name + '"...');
|
||||
JawsCli.log('Lambda Deployer: Saving in dist dir ' + _this.evt.distDir);
|
||||
|
||||
// Add type. Should be first in array
|
||||
_this.evt.type = _this.Jaws.cli.params[0];
|
||||
JawsUtils.jawsDebug('copying', _this.Jaws._projectRootPath, 'to', _this.evt.distDir);
|
||||
|
||||
// Add function paths. Should be all other array items
|
||||
_this.Jaws.cli.params.splice(0,1);
|
||||
_this.evt.functions = _this.Jaws.cli.params;
|
||||
}
|
||||
|
||||
// Validate type
|
||||
if (!_this.evt.type ||
|
||||
(_this.evt.type !== 'code' &&
|
||||
_this.evt.type !== 'endpoint' &&
|
||||
_this.evt.type !== 'all')
|
||||
) {
|
||||
throw new JawsError(`Invalid type. Must be "code", "endpoint", or "all" `);
|
||||
}
|
||||
|
||||
// Validate stage
|
||||
if (!this.evt.stage) throw new JawsError(`Stage is required`);
|
||||
|
||||
// If region specified, add it to regions array for deployment
|
||||
if (this.evt.region) {
|
||||
this.evt.regions = [this.evt.region];
|
||||
delete this.evt.region; // Remove original "region" property for cleanliness
|
||||
}
|
||||
|
||||
// Process noExeCf
|
||||
this.evt.noExeCf = (this.evt.noExeCf == true || this.evt.noExeCf == 'true');
|
||||
|
||||
// Get full function paths relative to project root
|
||||
if (!_this.evt.functions.length) {
|
||||
|
||||
// If no functions, check cwd and resolve that path
|
||||
return JawsUtils.getFunctions(
|
||||
_this.Jaws._projectRootPath,
|
||||
_this.evt.type
|
||||
)
|
||||
.then(function(functions) {
|
||||
// If no functions, throw error
|
||||
if (!_this.evt.functions.length) {
|
||||
throw new JawsError(`No function found. Make sure your current working directory is a function.`);
|
||||
// Copy entire test project to temp folder
|
||||
let excludePatterns = _this.evt.currentFunction.package.excludePatterns || [];
|
||||
wrench.copyDirSyncRecursive(
|
||||
_this.Jaws._projectRootPath,
|
||||
_this.evt.distDir,
|
||||
{
|
||||
exclude: function(name, prefix) {
|
||||
if (!excludePatterns.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_this.evt.functions = functions;
|
||||
let relPath = path.join(
|
||||
prefix.replace(_this.evt.distDir, ''), name);
|
||||
|
||||
return excludePatterns.some(sRegex => {
|
||||
relPath = (relPath.charAt(0) == path.sep) ? relPath.substr(1) : relPath;
|
||||
|
||||
let re = new RegExp(sRegex),
|
||||
matches = re.exec(relPath),
|
||||
willExclude = (matches && matches.length > 0);
|
||||
|
||||
if (willExclude) {
|
||||
JawsCLI.log(`Lambda Deployer: Excluding ${relPath}`);
|
||||
}
|
||||
|
||||
return willExclude;
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
JawsUtils.jawsDebug('Packaging stage & region:', _this.evt.stage, _this.evt.deployRegion);
|
||||
|
||||
// Get ENV file from S3
|
||||
return _this.S3.sGetEnvFile(
|
||||
_this.evt.deployRegion.jawsBucket,
|
||||
_this.Jaws._projectJson.name,
|
||||
_this.evt.stage
|
||||
)
|
||||
.then(function(s3ObjData) {
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(_this.evt.distDir,'.env'),
|
||||
s3ObjData.Body);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Package
|
||||
*/
|
||||
|
||||
_package() {
|
||||
|
||||
let _this = this,
|
||||
lambda = _this.evt.currentFunction.cloudFormation.lambda,
|
||||
deferred = false,
|
||||
targetZipPath = path.join(_this.evt.distDir, 'package.zip'),
|
||||
optimizeSettings = _this.evt.currentFunction.package.optimize;
|
||||
|
||||
if (optimizeSettings.builder) {
|
||||
|
||||
deferred = _this._optimizeNodeJs()
|
||||
.then(optimizedCodeBuffer => {
|
||||
let envData = fs.readFileSync(path.join(_this.evt.distDir, '.env')),
|
||||
handlerFileName = lambda.Function.Properties.Handler.split('.')[0],
|
||||
compressPaths = [
|
||||
// handlerFileName is the full path lambda file including dir rel to back
|
||||
{fileName: handlerFileName + '.js', data: optimizedCodeBuffer},
|
||||
{fileName: '.env', data: envData},
|
||||
];
|
||||
|
||||
compressPaths = compressPaths.concat(_this._generateIncludePaths());
|
||||
return compressPaths;
|
||||
});
|
||||
|
||||
} else {
|
||||
|
||||
// If functions, resolve their paths
|
||||
return JawsUtils.getFunctions(
|
||||
_this.Jaws._projectRootPath,
|
||||
_this.evt.type,
|
||||
_this.evt.functions
|
||||
)
|
||||
.then(function(functions) {
|
||||
_this.evt.functions = functions;
|
||||
});
|
||||
// User chose not to optimize, zip up whatever is in back
|
||||
optimizeSettings.includePaths = ['.'];
|
||||
let compressPaths = _this._generateIncludePaths();
|
||||
|
||||
deferred = Promise.resolve(compressPaths);
|
||||
|
||||
}
|
||||
|
||||
return deferred
|
||||
.then(compressPaths => {
|
||||
return _this._compress(compressPaths, targetZipPath);
|
||||
})
|
||||
.then(zipFilePath => {
|
||||
return Promise.resolve({awsmFilePath: _this._lambdaAwsmPath, zipFilePath: zipFilePath});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimize
|
||||
*/
|
||||
|
||||
_optimize() {
|
||||
|
||||
let _this = this,
|
||||
lambda = _this.evt.currentFunction.cloudFormation.lambda;
|
||||
|
||||
if (!_this.evt.currentFunction.package.optimize
|
||||
|| !_this.evt.currentFunction.package.optimize.builder) {
|
||||
return Promise.reject(new JawsError('Cant optimize for nodejs. lambda jaws.json does not have optimize.builder set'));
|
||||
}
|
||||
|
||||
if (_this.evt.currentFunction.package.optimize.builder.toLowerCase() == 'browserify') {
|
||||
return _this._browserifyBundle();
|
||||
} else {
|
||||
return Promise.reject(new JawsError(`Unsupported builder ${builder}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Include Paths
|
||||
*/
|
||||
|
||||
_generateIncludePaths() {
|
||||
|
||||
let _this = this,
|
||||
compressPaths = [],
|
||||
ignore = ['.DS_Store'],
|
||||
stats,
|
||||
fullPath;
|
||||
|
||||
_this._awsmJson.package.optimize.includePaths.forEach(p => {
|
||||
try {
|
||||
fullPath = path.resolve(path.join(_this._distDir, p));
|
||||
stats = fs.lstatSync(fullPath);
|
||||
} catch (e) {
|
||||
console.error('Cant find includePath ', p, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (stats.isFile()) {
|
||||
JawsUtils.jawsDebug('INCLUDING', fullPath);
|
||||
compressPaths.push({fileName: p, data: fs.readFileSync(fullPath)});
|
||||
} else if (stats.isDirectory()) {
|
||||
let dirname = path.basename(p);
|
||||
|
||||
wrench
|
||||
.readdirSyncRecursive(fullPath)
|
||||
.forEach(file => {
|
||||
// Ignore certain files
|
||||
for (let i = 0; i < ignore.length; i++) {
|
||||
if (file.toLowerCase().indexOf(ignore[i]) > -1) return;
|
||||
}
|
||||
|
||||
let filePath = [fullPath, file].join('/');
|
||||
if (fs.lstatSync(filePath).isFile()) {
|
||||
let pathInZip = path.join(dirname, file);
|
||||
JawsUtils.jawsDebug('INCLUDING', pathInZip);
|
||||
compressPaths.push({fileName: pathInZip, data: fs.readFileSync(filePath)});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return compressPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compress
|
||||
*/
|
||||
|
||||
_compress(compressPaths, targetZipPath) {
|
||||
let zip = new Zip();
|
||||
|
||||
compressPaths.forEach(nc => {
|
||||
zip.file(nc.fileName, nc.data);
|
||||
});
|
||||
|
||||
let zipBuffer = zip.generate({
|
||||
type: 'nodebuffer',
|
||||
compression: 'DEFLATE',
|
||||
});
|
||||
|
||||
if (zipBuffer.length > 52428800) {
|
||||
Promise.reject(new JawsError(
|
||||
'Zip file is > the 50MB Lambda deploy limit (' + zipBuffer.length + ' bytes)',
|
||||
JawsError.errorCodes.ZIP_TOO_BIG)
|
||||
);
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetZipPath, zipBuffer);
|
||||
JawsCLI.log(`Lambda Deployer: Compressed code written to ${targetZipPath}`);
|
||||
|
||||
return Promise.resolve(targetZipPath);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Browserify the code and return buffer of bundled code
|
||||
*
|
||||
* @returns {Promise.Buffer}
|
||||
* @private
|
||||
*/
|
||||
|
||||
_browserifyBundle() {
|
||||
|
||||
let _this = this;
|
||||
let uglyOptions = {
|
||||
mangle: true, // @see http://lisperator.net/uglifyjs/compress
|
||||
compress: {},
|
||||
};
|
||||
let b = browserify({
|
||||
basedir: _this._distDir,
|
||||
entries: [_this._awsmJson.cloudFormation.lambda.Function.Properties.Handler.split('.')[0] + '.js'],
|
||||
standalone: 'lambda',
|
||||
browserField: false, // Setup for node app (copy logic of --node in bin/args.js)
|
||||
builtins: false,
|
||||
commondir: false,
|
||||
ignoreMissing: true, // Do not fail on missing optional dependencies
|
||||
detectGlobals: true, // Default for bare in cli is true, but we don't care if its slower
|
||||
insertGlobalVars: { // Handle process https://github.com/substack/node-browserify/issues/1277
|
||||
//__filename: insertGlobals.lets.__filename,
|
||||
//__dirname: insertGlobals.lets.__dirname,
|
||||
process: function() {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (_this._awsmJson.package.optimize.babel) {
|
||||
b.transform(babelify);
|
||||
}
|
||||
|
||||
if (_this._awsmJson.package.optimize.transform) {
|
||||
JawsUtils.jawsDebug('Adding transform', _this._awsmJson.package.optimize.transform);
|
||||
b.transform(_this._awsmJson.package.optimize.transform);
|
||||
}
|
||||
|
||||
// optimize.exclude
|
||||
_this._awsmJson.package.optimize.exclude.forEach(file => {
|
||||
JawsUtils.jawsDebug('EXCLUDING', file);
|
||||
b.exclude(file);
|
||||
});
|
||||
|
||||
// optimize.ignore
|
||||
_this._awsmJson.package.optimize.ignore.forEach(file => {
|
||||
JawsUtils.jawsDebug('IGNORING', file);
|
||||
b.ignore(file);
|
||||
});
|
||||
|
||||
// Perform Bundle
|
||||
let bundledFilePath = path.join(_this._distDir, 'bundled.js'); // Save for auditing
|
||||
let minifiedFilePath = path.join(_this._distDir, 'minified.js'); // Save for auditing
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
b.bundle(function(err, bundledBuf) {
|
||||
if (err) {
|
||||
console.error('Error running browserify bundle');
|
||||
reject(err);
|
||||
} else {
|
||||
fs.writeFileSync(bundledFilePath, bundledBuf);
|
||||
JawsCLI.log(`Lambda Deployer: Bundled file written to ${bundledFilePath}`);
|
||||
|
||||
if (_this._awsmJson.package.optimize.minify) {
|
||||
JawsUtils.jawsDebug('Minifying...');
|
||||
let result = UglifyJS.minify(bundledFilePath, uglyOptions);
|
||||
|
||||
if (!result || !result.code) {
|
||||
reject(new JawsError('Problem uglifying code'));
|
||||
}
|
||||
|
||||
fs.writeFileSync(minifiedFilePath, result.code);
|
||||
|
||||
JawsCLI.log(`Lambda Deployer: Minified file written to ${minifiedFilePath}`);
|
||||
resolve(result.code);
|
||||
} else {
|
||||
resolve(bundledBuf);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = CodePackageLambdaNodejs;
|
||||
@ -41,7 +41,7 @@ class CodeProvisionLambdaNodejs extends JawsPlugin {
|
||||
|
||||
registerActions() {
|
||||
|
||||
this.Jaws.addAction(this.CodeProvisionLambdaNodejs.bind(this), {
|
||||
this.Jaws.addAction(this.codeProvisionLambdaNodejs.bind(this), {
|
||||
handler: 'codeProvisionLambdaNodejs',
|
||||
description: 'Deploys the code or endpoint of a function, or both'
|
||||
});
|
||||
|
||||
@ -11,6 +11,7 @@ const JawsPlugin = require('../../JawsPlugin'),
|
||||
JawsUtils = require('../../utils/index'),
|
||||
extend = require('util')._extend,
|
||||
BbPromise = require('bluebird'),
|
||||
async = require('async'),
|
||||
path = require('path'),
|
||||
fs = require('fs'),
|
||||
os = require('os');
|
||||
@ -138,6 +139,7 @@ class FunctionDeploy extends JawsPlugin {
|
||||
_this.evt.type
|
||||
)
|
||||
.then(function(functions) {
|
||||
|
||||
// If no functions, throw error
|
||||
if (!_this.evt.functions.length) {
|
||||
throw new JawsError(`No function found. Make sure your current working directory is a function.`);
|
||||
@ -236,14 +238,8 @@ class FunctionDeploy extends JawsPlugin {
|
||||
})
|
||||
.each(function(region) {
|
||||
|
||||
// Clone evt object keeps us safe when we start doing concurrent operations
|
||||
let evtClone = extend({}, _this.evt);
|
||||
|
||||
// Add Region JSON for the deploy region
|
||||
evtClone.deployRegion = JawsUtils.getProjRegionConfigForStage(_this.Jaws._projectJson, _this.evt.stage, region);
|
||||
|
||||
// Deploy Type
|
||||
switch(evtClone.type) {
|
||||
switch(_this.evt.type) {
|
||||
|
||||
// Deploy Endpoint only
|
||||
case "endpoint":
|
||||
@ -254,6 +250,40 @@ class FunctionDeploy extends JawsPlugin {
|
||||
|
||||
// Deploy Code only (lambda)
|
||||
case "code":
|
||||
|
||||
return new BbPromise(function(resolve, reject) {
|
||||
|
||||
// Process Functions concurrently
|
||||
async.eachLimit(_this.evt.functions, 5, function(func, cb) {
|
||||
|
||||
// Clone evt object for concurrent operations
|
||||
let evtClone = extend({}, _this.evt);
|
||||
|
||||
// Add Region JSON for the deploy region
|
||||
evtClone.deployRegion = JawsUtils.getProjRegionConfigForStage(
|
||||
_this.Jaws._projectJson,
|
||||
_this.evt.stage,
|
||||
region
|
||||
);
|
||||
|
||||
// Add Function to event clone
|
||||
evtClone.currentFunction = func;
|
||||
|
||||
return _this.Jaws.actions.codePackageLambdaNodejs(evtClone)
|
||||
.bind(_this)
|
||||
.then(_this.Jaws.actions.codeProvisionLambdaNodejs)
|
||||
.then(function() {
|
||||
return cb();
|
||||
})
|
||||
.catch(function(e) {
|
||||
return reject(new JawsError(func + ' - ' + e.message));
|
||||
});
|
||||
|
||||
}, function() {
|
||||
return resolve(_this.evt);
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
|
||||
// Deploy Code then Endpoint
|
||||
|
||||
@ -226,327 +226,7 @@ class Deployer {
|
||||
}
|
||||
}
|
||||
|
||||
class Packager {
|
||||
constructor(JAWS, stage, region, lambdaAwsmPath) {
|
||||
this._JAWS = JAWS;
|
||||
this._lambdaAwsmPath = lambdaAwsmPath;
|
||||
this._stage = stage;
|
||||
this._region = region;
|
||||
this._awsmJson = JawsUtils.readAndParseJsonSync(this._lambdaAwsmPath);
|
||||
this._distDir = '';
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise} {awsmFilePath: "", zipFilePath: "/tmp/path/to/blah.zip"}
|
||||
*/
|
||||
run() {
|
||||
let _this = this;
|
||||
|
||||
return this._createDistFolder()
|
||||
.then(function() {
|
||||
let runtime = _this._awsmJson.cloudFormation.lambda.Function.Properties.Runtime;
|
||||
|
||||
switch (runtime) {
|
||||
case 'nodejs':
|
||||
return _this._packageNodeJs(runtime);
|
||||
break;
|
||||
default:
|
||||
return Promise.reject(new JawsError(`Unsupported lambda runtime ${runtime}`, JawsError.errorCodes.UNKNOWN));
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Dist Folder (for an individual lambda)
|
||||
*
|
||||
* @returns {Promise}
|
||||
* @private
|
||||
*/
|
||||
_createDistFolder() {
|
||||
|
||||
let _this = this;
|
||||
|
||||
// Create dist folder
|
||||
let d = new Date();
|
||||
_this._distDir = path.join(os.tmpdir(), this._awsmJson.name + '@' + d.getTime());
|
||||
|
||||
// Status
|
||||
JawsCLI.log('Lambda Deployer: Packaging "' + this._awsmJson.name + '"...');
|
||||
JawsCLI.log('Lambda Deployer: Saving in dist dir ' + _this._distDir);
|
||||
|
||||
JawsUtils.jawsDebug('copying', _this._JAWS._projectRootPath, 'to', _this._distDir);
|
||||
|
||||
// Copy entire test project to temp folder
|
||||
_this._excludePatterns = _this._awsmJson.package.excludePatterns || [];
|
||||
wrench.copyDirSyncRecursive(
|
||||
_this._JAWS._projectRootPath,
|
||||
_this._distDir,
|
||||
{
|
||||
exclude: function(name, prefix) {
|
||||
if (!_this._excludePatterns.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let relPath = path.join(
|
||||
prefix.replace(_this._distDir, ''), name);
|
||||
|
||||
return _this._excludePatterns.some(sRegex => {
|
||||
relPath = (relPath.charAt(0) == path.sep) ? relPath.substr(1) : relPath;
|
||||
|
||||
let re = new RegExp(sRegex),
|
||||
matches = re.exec(relPath),
|
||||
willExclude = (matches && matches.length > 0);
|
||||
|
||||
if (willExclude) {
|
||||
JawsCLI.log(`Lambda Deployer: Excluding ${relPath}`);
|
||||
}
|
||||
|
||||
return willExclude;
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
JawsUtils.jawsDebug('Packaging stage & region:', _this._stage, _this._region);
|
||||
|
||||
// Get ENV file from S3
|
||||
return _this._JAWS.getEnvFile(_this._region, _this._stage)
|
||||
.then(function(s3ObjData) {
|
||||
fs.writeFileSync(path.join(_this._distDir, '.env'), s3ObjData.Body);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Promise} {awsmFilePath: "", zipFilePath: "/tmp/blah.zip"}
|
||||
* @private
|
||||
*/
|
||||
_packageNodeJs() {
|
||||
let _this = this,
|
||||
deferred = false,
|
||||
targetZipPath = path.join(this._distDir, 'package.zip'),
|
||||
optimizeSettings = _this._awsmJson.package.optimize;
|
||||
|
||||
if (optimizeSettings.builder) {
|
||||
deferred = _this._optimizeNodeJs()
|
||||
.then(optimizedCodeBuffer => {
|
||||
let envData = fs.readFileSync(path.join(_this._distDir, '.env')),
|
||||
handlerFileName = _this._awsmJson.cloudFormation.lambda.Function.Properties.Handler.split('.')[0],
|
||||
compressPaths = [
|
||||
|
||||
// handlerFileName is the full path lambda file including dir rel to back
|
||||
{fileName: handlerFileName + '.js', data: optimizedCodeBuffer},
|
||||
{fileName: '.env', data: envData},
|
||||
];
|
||||
|
||||
compressPaths = compressPaths.concat(_this._generateIncludePaths());
|
||||
|
||||
return compressPaths;
|
||||
});
|
||||
} else {
|
||||
// User chose not to optimize, zip up whatever is in back
|
||||
optimizeSettings.includePaths = ['.'];
|
||||
let compressPaths = _this._generateIncludePaths();
|
||||
|
||||
deferred = Promise.resolve(compressPaths);
|
||||
}
|
||||
|
||||
return deferred
|
||||
.then(compressPaths => {
|
||||
return _this._compressCode(compressPaths, targetZipPath);
|
||||
})
|
||||
.then(zipFilePath => {
|
||||
return Promise.resolve({awsmFilePath: _this._lambdaAwsmPath, zipFilePath: zipFilePath});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {*}
|
||||
* @private
|
||||
*/
|
||||
_optimizeNodeJs() {
|
||||
|
||||
let _this = this;
|
||||
|
||||
if (!_this._awsmJson.package.optimize
|
||||
|| !_this._awsmJson.package.optimize.builder) {
|
||||
return Promise.reject(new JawsError('Cant optimize for nodejs. lambda jaws.json does not have optimize.builder set'));
|
||||
}
|
||||
|
||||
if (_this._awsmJson.package.optimize.builder.toLowerCase() == 'browserify') {
|
||||
return _this._browserifyBundle();
|
||||
} else {
|
||||
return Promise.reject(new JawsError(`Unsupported builder ${builder}`));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Browserify the code and return buffer of bundled code
|
||||
*
|
||||
* @returns {Promise.Buffer}
|
||||
* @private
|
||||
*/
|
||||
_browserifyBundle() {
|
||||
|
||||
let _this = this;
|
||||
let uglyOptions = {
|
||||
mangle: true, // @see http://lisperator.net/uglifyjs/compress
|
||||
compress: {},
|
||||
};
|
||||
let b = browserify({
|
||||
basedir: _this._distDir,
|
||||
entries: [_this._awsmJson.cloudFormation.lambda.Function.Properties.Handler.split('.')[0] + '.js'],
|
||||
standalone: 'lambda',
|
||||
browserField: false, // Setup for node app (copy logic of --node in bin/args.js)
|
||||
builtins: false,
|
||||
commondir: false,
|
||||
ignoreMissing: true, // Do not fail on missing optional dependencies
|
||||
detectGlobals: true, // Default for bare in cli is true, but we don't care if its slower
|
||||
insertGlobalVars: { // Handle process https://github.com/substack/node-browserify/issues/1277
|
||||
//__filename: insertGlobals.lets.__filename,
|
||||
//__dirname: insertGlobals.lets.__dirname,
|
||||
process: function() {
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (_this._awsmJson.package.optimize.babel) {
|
||||
b.transform(babelify);
|
||||
}
|
||||
|
||||
if (_this._awsmJson.package.optimize.transform) {
|
||||
JawsUtils.jawsDebug('Adding transform', _this._awsmJson.package.optimize.transform);
|
||||
b.transform(_this._awsmJson.package.optimize.transform);
|
||||
}
|
||||
|
||||
// optimize.exclude
|
||||
_this._awsmJson.package.optimize.exclude.forEach(file => {
|
||||
JawsUtils.jawsDebug('EXCLUDING', file);
|
||||
b.exclude(file);
|
||||
});
|
||||
|
||||
// optimize.ignore
|
||||
_this._awsmJson.package.optimize.ignore.forEach(file => {
|
||||
JawsUtils.jawsDebug('IGNORING', file);
|
||||
b.ignore(file);
|
||||
});
|
||||
|
||||
// Perform Bundle
|
||||
let bundledFilePath = path.join(_this._distDir, 'bundled.js'); // Save for auditing
|
||||
let minifiedFilePath = path.join(_this._distDir, 'minified.js'); // Save for auditing
|
||||
|
||||
return new Promise(function(resolve, reject) {
|
||||
b.bundle(function(err, bundledBuf) {
|
||||
if (err) {
|
||||
console.error('Error running browserify bundle');
|
||||
reject(err);
|
||||
} else {
|
||||
fs.writeFileSync(bundledFilePath, bundledBuf);
|
||||
JawsCLI.log(`Lambda Deployer: Bundled file written to ${bundledFilePath}`);
|
||||
|
||||
if (_this._awsmJson.package.optimize.minify) {
|
||||
JawsUtils.jawsDebug('Minifying...');
|
||||
let result = UglifyJS.minify(bundledFilePath, uglyOptions);
|
||||
|
||||
if (!result || !result.code) {
|
||||
reject(new JawsError('Problem uglifying code'));
|
||||
}
|
||||
|
||||
fs.writeFileSync(minifiedFilePath, result.code);
|
||||
|
||||
JawsCLI.log(`Lambda Deployer: Minified file written to ${minifiedFilePath}`);
|
||||
resolve(result.code);
|
||||
} else {
|
||||
resolve(bundledBuf);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns {Array}
|
||||
* @private
|
||||
*/
|
||||
_generateIncludePaths() {
|
||||
let _this = this,
|
||||
compressPaths = [],
|
||||
ignore = ['.DS_Store'],
|
||||
stats,
|
||||
fullPath;
|
||||
|
||||
_this._awsmJson.package.optimize.includePaths.forEach(p => {
|
||||
try {
|
||||
fullPath = path.resolve(path.join(_this._distDir, p));
|
||||
stats = fs.lstatSync(fullPath);
|
||||
} catch (e) {
|
||||
console.error('Cant find includePath ', p, e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (stats.isFile()) {
|
||||
JawsUtils.jawsDebug('INCLUDING', fullPath);
|
||||
compressPaths.push({fileName: p, data: fs.readFileSync(fullPath)});
|
||||
} else if (stats.isDirectory()) {
|
||||
let dirname = path.basename(p);
|
||||
|
||||
wrench
|
||||
.readdirSyncRecursive(fullPath)
|
||||
.forEach(file => {
|
||||
// Ignore certain files
|
||||
for (let i = 0; i < ignore.length; i++) {
|
||||
if (file.toLowerCase().indexOf(ignore[i]) > -1) return;
|
||||
}
|
||||
|
||||
let filePath = [fullPath, file].join('/');
|
||||
if (fs.lstatSync(filePath).isFile()) {
|
||||
let pathInZip = path.join(dirname, file);
|
||||
JawsUtils.jawsDebug('INCLUDING', pathInZip);
|
||||
compressPaths.push({fileName: pathInZip, data: fs.readFileSync(filePath)});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return compressPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param compressPaths
|
||||
* @param targetZipPath string
|
||||
* @returns {Promise.string} targetZipPath
|
||||
* @private
|
||||
*/
|
||||
_compressCode(compressPaths, targetZipPath) {
|
||||
let zip = new Zip();
|
||||
|
||||
compressPaths.forEach(nc => {
|
||||
zip.file(nc.fileName, nc.data);
|
||||
});
|
||||
|
||||
let zipBuffer = zip.generate({
|
||||
type: 'nodebuffer',
|
||||
compression: 'DEFLATE',
|
||||
});
|
||||
|
||||
if (zipBuffer.length > 52428800) {
|
||||
Promise.reject(new JawsError(
|
||||
'Zip file is > the 50MB Lambda deploy limit (' + zipBuffer.length + ' bytes)',
|
||||
JawsError.errorCodes.ZIP_TOO_BIG)
|
||||
);
|
||||
}
|
||||
|
||||
fs.writeFileSync(targetZipPath, zipBuffer);
|
||||
JawsCLI.log(`Lambda Deployer: Compressed code written to ${targetZipPath}`);
|
||||
|
||||
return Promise.resolve(targetZipPath);
|
||||
}
|
||||
}
|
||||
|
||||
class DeployLambda extends JawsPlugin {
|
||||
|
||||
@ -570,6 +250,7 @@ class DeployLambda extends JawsPlugin {
|
||||
*
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
static getName() {
|
||||
return 'jaws.core.' + DeployLambda.name;
|
||||
}
|
||||
|
||||
@ -17,8 +17,8 @@ let BbPromise = require('bluebird'),
|
||||
// Promisify fs module. This adds "Async" to the end of every method
|
||||
BbPromise.promisifyAll(fs);
|
||||
|
||||
|
||||
module.exports = function(config) {
|
||||
|
||||
// Promisify and configure instance
|
||||
const S3 = BbPromise.promisifyAll(new AWS.S3(config));
|
||||
|
||||
@ -48,6 +48,7 @@ module.exports = function(config) {
|
||||
/**
|
||||
* Get the env file for a given stage
|
||||
*/
|
||||
|
||||
S3.sGetEnvFile = function(bucketName, projectName, stage) {
|
||||
let key = ['JAWS', projectName, stage, 'envVars', '.env'].join('/'),
|
||||
params = {
|
||||
@ -56,7 +57,7 @@ module.exports = function(config) {
|
||||
};
|
||||
|
||||
JawsUtils.jawsDebug(`env get s3 bucket: ${bucketName} key: ${key}`);
|
||||
return S3.getObjectAsync(params);
|
||||
return S3.getObjectAsync(params)
|
||||
};
|
||||
|
||||
/**
|
||||
@ -94,6 +95,7 @@ module.exports = function(config) {
|
||||
return key;
|
||||
});
|
||||
};
|
||||
|
||||
// Return configured, customized instance
|
||||
return S3;
|
||||
|
||||
|
||||
@ -47,14 +47,12 @@
|
||||
"browserify": "^12.0.1",
|
||||
"chalk": "^1.1.0",
|
||||
"cli-spinner": "^0.2.1",
|
||||
"commander": "^2.5.0",
|
||||
"commop": "^1.2.0",
|
||||
"debug": "^2.2.0",
|
||||
"dotenv": "^1.2.0",
|
||||
"download": "^4.2.0",
|
||||
"expand-home-dir": "0.0.2",
|
||||
"insert-module-globals": "^6.5.2",
|
||||
"jaws-api-gateway-client": "0.11.0",
|
||||
"keypress": "^0.2.1",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp-then": "^1.1.0",
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
"lambda": {
|
||||
"Type": "AWS::Lambda::Function",
|
||||
"Properties": {
|
||||
"Handler": "",
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/bundle/browserify/handler.handler"
|
||||
"Role": {
|
||||
"Ref": "aaLambdaRoleArn"
|
||||
},
|
||||
@ -26,12 +27,9 @@
|
||||
"S3Bucket": "",
|
||||
"S3Key": ""
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Timeout": 6,
|
||||
"MemorySize": 1024
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/bundle/browserify/handler.handler"
|
||||
}
|
||||
},
|
||||
"apiGatewayEndpoint": {
|
||||
"Type": "AWS::ApiGateway::Endpoint",
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
"lambda": {
|
||||
"Type": "AWS::Lambda::Function",
|
||||
"Properties": {
|
||||
"Handler": "",
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/bundle/nonoptimized/handler.handler",
|
||||
"Role": {
|
||||
"Ref": "aaLambdaRoleArn"
|
||||
},
|
||||
@ -26,12 +27,9 @@
|
||||
"S3Bucket": "",
|
||||
"S3Key": ""
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Timeout": 6,
|
||||
"MemorySize": 1024
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/bundle/nonoptimized/handler.handler"
|
||||
}
|
||||
},
|
||||
"apiGatewayEndpoint": {
|
||||
"Type": "AWS::ApiGateway::Endpoint",
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
"lambda": {
|
||||
"Type": "AWS::Lambda::Function",
|
||||
"Properties": {
|
||||
"Handler": "",
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/sessions/create/index.handler",
|
||||
"Role": {
|
||||
"Ref": "aaLambdaRoleArn"
|
||||
},
|
||||
@ -26,12 +27,9 @@
|
||||
"S3Bucket": "",
|
||||
"S3Key": ""
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Timeout": 6,
|
||||
"MemorySize": 1024
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/sessions/create/index.handler"
|
||||
},
|
||||
"apiGatewayEndpoint": {
|
||||
"Type": "AWS::ApiGateway::Endpoint",
|
||||
|
||||
@ -18,7 +18,8 @@
|
||||
"lambda": {
|
||||
"Type": "AWS::Lambda::Function",
|
||||
"Properties": {
|
||||
"Handler": "",
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/sessions/show/index.handler",
|
||||
"Role": {
|
||||
"Ref": "aaLambdaRoleArn"
|
||||
},
|
||||
@ -26,12 +27,9 @@
|
||||
"S3Bucket": "",
|
||||
"S3Key": ""
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Timeout": 6,
|
||||
"MemorySize": 1024
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/sessions/show/index.handler"
|
||||
}
|
||||
},
|
||||
"apiGateway": {
|
||||
"Endpoint": {
|
||||
|
||||
@ -19,7 +19,8 @@
|
||||
"lambda": {
|
||||
"Type": "AWS::Lambda::Function",
|
||||
"Properties": {
|
||||
"Handler": "",
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/users/create/index.handler",
|
||||
"Role": {
|
||||
"Ref": "aaLambdaRoleArn"
|
||||
},
|
||||
@ -27,12 +28,9 @@
|
||||
"S3Bucket": "",
|
||||
"S3Key": ""
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Timeout": 6,
|
||||
"MemorySize": 1024
|
||||
},
|
||||
"Runtime": "nodejs",
|
||||
"Handler": "aws_modules/users/create/index.handler"
|
||||
}
|
||||
},
|
||||
|
||||
"apiGateway": {
|
||||
|
||||
@ -13,16 +13,19 @@ let JAWS = require('../../../lib/Jaws.js'),
|
||||
|
||||
let Jaws;
|
||||
|
||||
describe('Test action: Function Deploy', function() {
|
||||
describe('Test Action: Function Deploy', function() {
|
||||
|
||||
before(function(done) {
|
||||
this.timeout(0);
|
||||
|
||||
testUtils.createTestProject(config)
|
||||
.then(projPath => {
|
||||
process.chdir(projPath);
|
||||
|
||||
Jaws = new JAWS({
|
||||
interactive: false,
|
||||
awsAdminKeyId: config.awsAdminKeyId,
|
||||
awsAdminSecretKey: config.awsAdminSecretKey
|
||||
});
|
||||
|
||||
done();
|
||||
@ -33,16 +36,16 @@ describe('Test action: Function Deploy', function() {
|
||||
done();
|
||||
});
|
||||
|
||||
describe('Function Deploy Endpoint positive tests', function() {
|
||||
describe('Function Deploy Code Lambda Nodejs', function() {
|
||||
it('Function Deploy Code Lambda Nodejs', function(done) {
|
||||
|
||||
it('Function Deploy Endpoint', function(done) {
|
||||
this.timeout(0);
|
||||
|
||||
let event = {
|
||||
stage: config.stage,
|
||||
region: config.region,
|
||||
noExeCf: config.noExecuteCf,
|
||||
type: 'endpoint',
|
||||
type: 'code',
|
||||
functions: ['aws_modules/users/create'],
|
||||
};
|
||||
|
||||
@ -55,4 +58,27 @@ describe('Test action: Function Deploy', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
//describe('Function Deploy: Endpoint: ApiGateway', function() {
|
||||
//
|
||||
// it('Function Deploy Endpoint', function(done) {
|
||||
// this.timeout(0);
|
||||
//
|
||||
// let event = {
|
||||
// stage: config.stage,
|
||||
// region: config.region,
|
||||
// noExeCf: config.noExecuteCf,
|
||||
// type: 'endpoint',
|
||||
// functions: ['aws_modules/users/create'],
|
||||
// };
|
||||
//
|
||||
// Jaws.actions.functionDeploy(event)
|
||||
// .then(function() {
|
||||
// done();
|
||||
// })
|
||||
// .catch(e => {
|
||||
// done(e);
|
||||
// });
|
||||
// });
|
||||
//});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user