FunctionDeploy: continue work

This commit is contained in:
Austen Collins 2015-11-19 13:59:56 -08:00
parent e61c834749
commit 1c966c0f61
10 changed files with 413 additions and 240 deletions

View File

@ -188,6 +188,7 @@ class Jaws {
if (queue.indexOf(action) === -1) queue.push(action); // Prevent duplicate actions from being added
queue = queue.concat(_this.hooks[config.handler + 'Post']);
// Create promise chain
let chain = queue.reduce(function (previous, current) {
return previous.then(current);
}, BbPromise.resolve(evt));

View File

@ -0,0 +1,120 @@
'use strict';
/**
* Action: Code Compress: Lambda: Nodejs
* - Compress lambda files in distribution folder
* - Don't attach "evt" to context, it will be overwritten in concurrent operations
*/
const JawsPlugin = require('../../JawsPlugin'),
JawsError = require('../../jaws-error'),
JawsUtils = require('../../utils/index'),
BbPromise = require('bluebird'),
path = require('path'),
fs = require('fs'),
os = require('os'),
wrench = require('wrench'),
Zip = require('node-zip');
// Promisify fs module
BbPromise.promisifyAll(fs);
class CodeCompressLambdaNodejs extends JawsPlugin {
/**
* Constructor
*/
constructor(Jaws, config) {
super(Jaws, config);
}
/**
* Get Name
*/
static getName() {
return 'jaws.core.' + CodeCompressLambdaNodejs.name;
}
/**
* Register Plugin Actions
*/
registerActions() {
this.Jaws.addAction(this.codeCompressLambdaNodejs.bind(this), {
handler: 'codeCompressLambdaNodejs',
description: 'Deploys the code or endpoint of a function, or both'
});
return BbPromise.resolve();
}
/**
* Code Compress
*/
codeCompressLambdaNodejs(evt) {
let _this = this;
// Flow
return _this._validateAndPrepare(evt)
.bind(_this)
.then(_this._compress)
.then(function() {
return evt;
})
.catch(function(e) {console.log(e, e.stack)})
}
/**
* Validate And Prepare
*/
_validateAndPrepare(evt) {
let _this = this;
return BbPromise.resolve(evt);
}
/**
* Compress
*/
_compress(evt) {
let _this = this,
zip = new Zip();
evt.function.pathsPackaged.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 queued limit (' + zipBuffer.length + ' bytes)',
JawsError.errorCodes.ZIP_TOO_BIG)
);
}
// Set path of compressed package
evt.function.pathCompressed = path.join(evt.function.pathDist, 'package.zip');
// Create compressed package
fs.writeFileSync(
evt.function.pathCompressed,
zipBuffer);
JawsUtils.jawsDebug(`Compressed code written to ${evt.function.pathCompressed}`);
return BbPromise.resolve(evt);
}
}
module.exports = CodeCompressLambdaNodejs;

View File

@ -4,6 +4,7 @@
* Action: Code Package: Lambda: Nodejs
* - Accepts one function
* - Collects and optimizes the function's Lambda code in a temp folder
* - Don't attach "evt" to context, it will be overwritten in concurrent operations
*/
const JawsPlugin = require('../../JawsPlugin'),
@ -16,8 +17,7 @@ const JawsPlugin = require('../../JawsPlugin'),
babelify = require('babelify'),
browserify = require('browserify'),
UglifyJS = require('uglify-js'),
wrench = require('wrench'),
Zip = require('node-zip');
wrench = require('wrench');
// Promisify fs module
BbPromise.promisifyAll(fs);
@ -61,24 +61,22 @@ class CodePackageLambdaNodejs extends JawsPlugin {
codePackageLambdaNodejs(evt) {
let _this = this;
_this.evt = evt;
// Load AWS Service Instances
let awsConfig = {
region: _this.evt.region.region,
region: evt.region.region,
accessKeyId: _this.Jaws._awsAdminKeyId,
secretAccessKey: _this.Jaws._awsAdminSecretKey,
};
_this.S3 = require('../../utils/aws/S3')(awsConfig);
// Flow
return BbPromise.try(function() {})
return _this._validateAndPrepare(evt)
.bind(_this)
.then(_this._validateAndPrepare)
.then(_this._createDistFolder)
.then(_this._package)
.then(function() {
return _this.evt;
return evt;
})
.catch(function(e) {console.log(e, e.stack)})
}
@ -87,53 +85,54 @@ class CodePackageLambdaNodejs extends JawsPlugin {
* Validate And Prepare
*/
_validateAndPrepare() {
_validateAndPrepare(evt) {
let _this = this;
// Skip Function if it does not have a lambda
if (!_this.evt.function.cloudFormation ||
!_this.evt.function.cloudFormation.lambda ||
!_this.evt.function.cloudFormation.lambda.Function) {
return Promise.reject(new JawsError(_this.evt.function.name + 'does not have a lambda property'));
if (!evt.function.cloudFormation ||
!evt.function.cloudFormation.lambda ||
!evt.function.cloudFormation.lambda.Function) {
return BbPromise.reject(new JawsError(evt.function.name + 'does not have a lambda property'));
}
// Validate lambda attributes
let lambda = _this.evt.function.cloudFormation.lambda;
let lambda = evt.function.cloudFormation.lambda;
if (!lambda.Function.Type
|| !lambda.Function
|| !lambda.Function.Properties
|| !lambda.Function.Properties.Runtime
|| !lambda.Function.Properties.Handler) {
return Promise.reject(new JawsError('Missing required lambda attributes'));
return BbPromise.reject(new JawsError('Missing required lambda attributes'));
}
// Return
return BbPromise.resolve();
return BbPromise.resolve(evt);
}
/**
* Create Distribution Folder
*/
_createDistFolder() {
_createDistFolder(evt) {
let _this = this;
// Create dist folder
let d = new Date();
_this.evt.function.pathDist = path.join(os.tmpdir(), _this.evt.function.name + '@' + d.getTime());
let d = new Date();
evt.function.pathDist = path.join(os.tmpdir(), evt.function.name + '@' + d.getTime());
// Status
JawsUtils.jawsDebug('Packaging "' + _this.evt.function.name + '"...');
JawsUtils.jawsDebug('Saving in dist dir ' + _this.evt.function.pathDist);
JawsUtils.jawsDebug('Copying', _this.Jaws._projectRootPath, 'to', _this.evt.function.pathDist);
JawsUtils.jawsDebug('Packaging "' + evt.function.name + '"...');
JawsUtils.jawsDebug('Saving in dist dir ' + evt.function.pathDist);
JawsUtils.jawsDebug('Copying', _this.Jaws._projectRootPath, 'to', evt.function.pathDist);
// Copy entire test project to temp folder
let excludePatterns = _this.evt.function.package.excludePatterns || [];
let excludePatterns = evt.function.package.excludePatterns || [];
wrench.copyDirSyncRecursive(
_this.Jaws._projectRootPath,
_this.evt.function.pathDist,
evt.function.pathDist,
{
exclude: function(name, prefix) {
if (!excludePatterns.length) {
@ -141,7 +140,7 @@ class CodePackageLambdaNodejs extends JawsPlugin {
}
let relPath = path.join(
prefix.replace(_this.evt.function.pathDist, ''), name);
prefix.replace(evt.function.pathDist, ''), name);
return excludePatterns.some(sRegex => {
relPath = (relPath.charAt(0) == path.sep) ? relPath.substr(1) : relPath;
@ -160,18 +159,21 @@ class CodePackageLambdaNodejs extends JawsPlugin {
}
);
JawsUtils.jawsDebug('Packaging stage & region:', _this.evt.stage, _this.evt.region.region);
JawsUtils.jawsDebug('Packaging stage & region:', evt.stage, evt.region.region);
// Get ENV file from S3
return _this.S3.sGetEnvFile(
_this.evt.region.jawsBucket,
evt.region.jawsBucket,
_this.Jaws._projectJson.name,
_this.evt.stage
evt.stage
)
.then(function(s3ObjData) {
fs.writeFileSync(
path.join(_this.evt.function.pathDist,'.env'),
path.join(evt.function.pathDist,'.env'),
s3ObjData.Body);
return evt;
});
}
@ -179,69 +181,65 @@ class CodePackageLambdaNodejs extends JawsPlugin {
* Package
*/
_package() {
_package(evt) {
let _this = this,
lambda = _this.evt.function.cloudFormation.lambda,
lambda = evt.function.cloudFormation.lambda,
deferred = false,
targetZipPath = path.join(_this.evt.function.pathDist, 'package.zip'),
optimizeSettings = _this.evt.function.package.optimize;
optimizeSettings = evt.function.package.optimize;
if (optimizeSettings.builder) {
deferred = _this._optimize()
deferred = _this._optimize(evt)
.then(optimizedCodeBuffer => {
let envData = fs.readFileSync(path.join(_this.evt.function.pathDist, '.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 },
];
let envData = fs.readFileSync(path.join(evt.function.pathDist, '.env')),
handlerFileName = lambda.Function.Properties.Handler.split('.')[0];
compressPaths = compressPaths.concat(_this._generateIncludePaths());
return compressPaths;
// Create pathsPackaged for each file ready to compress
evt.function.pathsPackaged = [
// handlerFileName is the full path lambda file including dir rel to back
{ fileName: handlerFileName + '.js', data: optimizedCodeBuffer },
{ fileName: '.env', data: envData },
];
evt.function.pathsPackaged = evt.function.pathsPackaged.concat(_this._generateIncludePaths(evt));
return evt;
});
} else {
// User chose not to optimize, zip up whatever is in back
optimizeSettings.includePaths = ['.'];
let compressPaths = _this._generateIncludePaths();
deferred = Promise.resolve(compressPaths);
// Create pathsPackaged for each file ready to compress
evt.function.pathsPackaged = _this._generateIncludePaths(evt);
deferred = BbPromise.resolve(evt);
}
return deferred
.then(compressPaths => {
return _this._compress(compressPaths, targetZipPath);
})
.then(zipFilePath => {
_this.evt.function.pathCompressed = zipFilePath;
});
return deferred;
}
/**
* Optimize
*/
_optimize() {
_optimize(evt) {
let _this = this,
lambda = _this.evt.function.cloudFormation.lambda;
lambda = evt.function.cloudFormation.lambda;
if (!_this.evt.function.package.optimize
|| !_this.evt.function.package.optimize.builder) {
return Promise.reject(new JawsError('Cant optimize for nodejs. lambda jaws.json does not have optimize.builder set'));
if (!evt.function.package.optimize
|| !evt.function.package.optimize.builder) {
return BbPromise.reject(new JawsError('Cant optimize for nodejs. lambda jaws.json does not have optimize.builder set'));
}
if (_this.evt.function.package.optimize.builder.toLowerCase() == 'browserify') {
JawsUtils.jawsDebug('Optimizing via Browserify: ' + _this.evt.function.name + '"...');
return _this._browserifyBundle();
if (evt.function.package.optimize.builder.toLowerCase() == 'browserify') {
JawsUtils.jawsDebug('Optimizing via Browserify: ' + evt.function.name + '"...');
return _this._browserifyBundle(evt);
} else {
return Promise.reject(new JawsError(`Unsupported builder ${builder}`));
return BbPromise.reject(new JawsError(`Unsupported builder ${builder}`));
}
}
@ -249,7 +247,7 @@ class CodePackageLambdaNodejs extends JawsPlugin {
* Generate Include Paths
*/
_generateIncludePaths() {
_generateIncludePaths(evt) {
let _this = this,
compressPaths = [],
@ -257,10 +255,10 @@ class CodePackageLambdaNodejs extends JawsPlugin {
stats,
fullPath;
_this.evt.function.package.optimize.includePaths.forEach(p => {
evt.function.package.optimize.includePaths.forEach(p => {
try {
fullPath = path.resolve(path.join(_this.evt.function.pathDist, p));
fullPath = path.resolve(path.join(evt.function.pathDist, p));
stats = fs.lstatSync(fullPath);
} catch (e) {
console.error('Cant find includePath ', p, e);
@ -295,41 +293,12 @@ class CodePackageLambdaNodejs extends JawsPlugin {
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 queued limit (' + zipBuffer.length + ' bytes)',
JawsError.errorCodes.ZIP_TOO_BIG)
);
}
fs.writeFileSync(targetZipPath, zipBuffer);
JawsUtils.jawsDebug(`Compressed code written to ${targetZipPath}`);
return Promise.resolve(targetZipPath);
}
/**
* Browserify Bundle
* - Browserify the code and return buffer of bundled code
*/
_browserifyBundle() {
_browserifyBundle(evt) {
let _this = this;
let uglyOptions = {
@ -338,8 +307,8 @@ class CodePackageLambdaNodejs extends JawsPlugin {
};
let b = browserify({
basedir: _this.evt.function.pathDist,
entries: [_this.evt.function.cloudFormation.lambda.Function.Properties.Handler.split('.')[0] + '.js'],
basedir: evt.function.pathDist,
entries: [evt.function.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,
@ -354,32 +323,32 @@ class CodePackageLambdaNodejs extends JawsPlugin {
},
});
if (_this.evt.function.package.optimize.babel) {
if (evt.function.package.optimize.babel) {
b.transform(babelify);
}
if (_this.evt.function.package.optimize.transform) {
JawsUtils.jawsDebug('Adding transform', _this.evt.function.package.optimize.transform);
b.transform(_this.evt.function.package.optimize.transform);
if (evt.function.package.optimize.transform) {
JawsUtils.jawsDebug('Adding transform', evt.function.package.optimize.transform);
b.transform(evt.function.package.optimize.transform);
}
// optimize.exclude
_this.evt.function.package.optimize.exclude.forEach(file => {
evt.function.package.optimize.exclude.forEach(file => {
JawsUtils.jawsDebug('Excluding', file);
b.exclude(file);
});
// optimize.ignore
_this.evt.function.package.optimize.ignore.forEach(file => {
evt.function.package.optimize.ignore.forEach(file => {
JawsUtils.jawsDebug('Ignoring', file);
b.ignore(file);
});
// Perform Bundle
let bundledFilePath = path.join(_this.evt.function.pathDist, 'bundled.js'); // Save for auditing
let minifiedFilePath = path.join(_this.evt.function.pathDist, 'minified.js'); // Save for auditing
let bundledFilePath = path.join(evt.function.pathDist, 'bundled.js'); // Save for auditing
let minifiedFilePath = path.join(evt.function.pathDist, 'minified.js'); // Save for auditing
return new Promise(function(resolve, reject) {
return new BbPromise(function(resolve, reject) {
b.bundle(function(err, bundledBuf) {
if (err) {
console.error('Error running browserify bundle');
@ -389,7 +358,7 @@ class CodePackageLambdaNodejs extends JawsPlugin {
fs.writeFileSync(bundledFilePath, bundledBuf);
JawsUtils.jawsDebug(`Bundled file written to ${bundledFilePath}`);
if (_this.evt.function.package.optimize.minify) {
if (evt.function.package.optimize.minify) {
JawsUtils.jawsDebug('Minifying...');
let result = UglifyJS.minify(bundledFilePath, uglyOptions);

View File

@ -3,11 +3,13 @@
/**
* Action: Code Provision: Lambda: Nodejs
* - Collects and optimizes Lambda code in a temp folder
* - Don't attach "evt" to context, it will be overwritten in concurrent operations
*/
const JawsPlugin = require('../../JawsPlugin'),
JawsError = require('../../jaws-error'),
JawsUtils = require('../../utils/index'),
JawsCli = require('../../utils/cli'),
extend = require('util')._extend,
BbPromise = require('bluebird'),
path = require('path'),
@ -56,25 +58,23 @@ class CodeProvisionLambdaNodejs extends JawsPlugin {
codeProvisionLambdaNodejs(evt) {
let _this = this;
_this.evt = evt;
// Load AWS Service Instances
let awsConfig = {
region: _this.evt.region.region,
region: evt.region.region,
accessKeyId: _this.Jaws._awsAdminKeyId,
secretAccessKey: _this.Jaws._awsAdminSecretKey,
};
_this.Lambda = require('../../utils/aws/Lambda')(awsConfig);
_this.CloudFormation = require('../../utils/aws/Lambda')(awsConfig);
_this.S3 = require('../../utils/aws/S3')(awsConfig);
_this.CloudFormation = require('../../utils/aws/CloudFormation')(awsConfig);
_this.AwsMisc = require('../../utils/aws/Misc');
// Flow
return BbPromise.try(function() {})
return _this._validateAndPrepare(evt)
.bind(_this)
.then(_this._validateAndPrepare)
.then(_this._generateLambdaCf)
.then(_this._provision)
.then(function() {
return _this.evt;
return evt;
})
.catch(function(e) {
console.log(e.stack)
@ -83,11 +83,10 @@ class CodeProvisionLambdaNodejs extends JawsPlugin {
/**
* Validate And Prepare
* - If CLI, maps CLI input to event object
*/
_validateAndPrepare() {
BbPromise.resolve();
_validateAndPrepare(evt) {
return BbPromise.resolve(evt);
}
/**
@ -100,18 +99,19 @@ class CodeProvisionLambdaNodejs extends JawsPlugin {
* @private
*/
_generateLambdaCf() {
_generateLambdaCf(evt) {
let _this = this,
existingStack = true;
let _this = this;
evt.stackExists = true;
// Fetch Lambdas CF Stack
let params = {
StackName: _this.CloudFormation.sGetLambdasStackName(
_this.evt.stage,
evt.stage,
_this.Jaws._projectJson.name) /* required */
};
_this.CloudFormation.getTemplatePromised(params)
return _this.CloudFormation.getTemplatePromised(params)
.then(function(data) {
return data.TemplateBody;
})
@ -121,86 +121,164 @@ class CodeProvisionLambdaNodejs extends JawsPlugin {
if (e && ['ValidationError', 'ResourceNotFoundException'].indexOf(e.code) == -1) {
console.error(
'Error trying to fetch existing lambda cf stack for region',
_this.evt.region,
evt.region,
'stage',
_this.evt.stage,
evt.stage,
e
);
throw new JawsError(e.message);
}
JawsUtils.jawsDebug('no existing lambda stack');
existingStack = false;
evt.stackExists = false;
return false;
})
.then(cfTemplateBody => {
.then(deployedCfTemplate => {
// Recreate new CloudFormation Template
let templatesPath = path.join(__dirname, '..', '..', 'templates'),
lambdaCf = JawsUtils.readAndParseJsonSync(
projectCfTemplate = JawsUtils.readAndParseJsonSync(
path.join(templatesPath, 'lambdas-cf.json')
);
delete lambdaCf.Resources.lTemplate;
delete projectCfTemplate.Resources.lTemplate;
lambdaCf.Description = projName + ' lambdas';
lambdaCf.Parameters.aaLambdaRoleArn.Default = lambdaRoleArn;
projectCfTemplate.Description = _this.Jaws._projectJson.name + ' lambda resources';
projectCfTemplate.Parameters.aaLambdaRoleArn.Default = evt.region.iamRoleArnLambda;
// Always add lambdas tagged for deployment
awsmLambdasToDeploy.forEach(pkg => {
let lambdaAwsmJson = JawsUtils.readAndParseJsonSync(pkg.awsmPath),
lambdaName = pkg.lambdaName;
evt.functions.forEach(func => {
Object.keys(lambdaAwsmJson.cloudFormation.lambda).forEach(cfEleIdx => {
let cfEle = lambdaAwsmJson.cloudFormation.lambda[cfEleIdx];
Object.keys(func.cloudFormation.lambda).forEach(resourceKey => {
//If its not a lambda CF resource, prefix it with the lambda name to prevent collisions
let resourceIdx = (cfEle.Type == 'AWS::Lambda::Function') ? lambdaName : lambdaName + '-' + cfEleIdx;
// Get cloudformation.lambda resource (Functions, EventSourceMapping, AccessPolicyX)
let resourceJson = func.cloudFormation.lambda[resourceKey];
cfEle.Properties.Code = pkg.Code;
lambdaCf.Resources[resourceIdx] = cfEle;
// If its not a "Function" CF resource, prefix it with the function name to prevent collisions
resourceKey = (resourceJson.Type == 'AWS::Lambda::Function') ? func.name : func.name + '-' + resourceKey;
// Add to project CF template
projectCfTemplate.Resources[resourceKey] = resourceJson;
JawsUtils.jawsDebug(`Adding Resource ${resourceKey}`);
JawsUtils.jawsDebug(`adding Resource ${resourceIdx}`, cfEle);
});
});
// If existing lambdas CF template
if (cfTemplateBody) {
if (deployedCfTemplate) {
JawsUtils.jawsDebug('existing stack detected');
// Find all lambdas in project, and copy ones that are in existing lambda-cf
let existingTemplate = JSON.parse(cfTemplateBody);
deployedCfTemplate = JSON.parse(deployedCfTemplate);
return JawsUtils.getAllLambdaNames(_this._JAWS._projectRootPath)
.then(allLambdaNames => {
Object.keys(existingTemplate.Resources).forEach(resource => {
// Find all lambdas in project, and copy ones that are in deployed template
return JawsUtils.getFunctions(_this.Jaws._projectRootPath)
.then(allFunctions => {
if (!lambdaCf.Resources[resource] && allLambdaNames.indexOf(resource) != -1) {
JawsUtils.jawsDebug(`Adding exsiting lambda ${resource}`);
lambdaCf.Resources[resource] = existingTemplate.Resources[resource];
let allCfResources = [];
// Loop through all functions
for (let i = 0; i < allFunctions.length; i++) {
// Add Lambda CF Resources
if (allFunctions[i].cloudFormation && allFunctions[i].cloudFormation.lambda) {
let lambda = allFunctions[i].cloudFormation.lambda;
// Loop through each CF resource for this Lambda and add them to the allCfResources array
for (let resource in lambda) {
if (!resource.Type) continue;
// Prefix Function Name
if (['AWS::Lambda::EventSourceMapping',
'AWS::Lambda::Permission'].indexOf(lambda[resource].Type) !== -1) {
allCfResources.push(allFunctions[i].name + resource);
} else {
allCfResources.push(resource);
}
}
}
}
// Loop through deployed CF template resource keys
Object.keys(deployedCfTemplate.Resources).forEach(deployedResource => {
// If resource key does not exist in project Cf Template
if (!projectCfTemplate.Resources[deployedResource]
&& allCfResources.indexOf(deployedResource) != -1) {
JawsUtils.jawsDebug(`Adding existing lambda ${deployedResource}`);
projectCfTemplate.Resources[resource] = deployedCfTemplate.Resources[deployedResource];
}
});
return lambdaCf;
return projectCfTemplate;
});
} else {
return lambdaCf;
return projectCfTemplate;
}
})
.then(lambdaCfTemplate => {
.then(projectCfTemplate => {
let lambdasCfPath = path.join(
_this._JAWS._projectRootPath,
_this.Jaws._projectRootPath,
'cloudformation',
'lambdas-cf.json'
);
JawsUtils.jawsDebug(`Wrting to ${lambdasCfPath}`);
JawsUtils.jawsDebug(`Writing to ${lambdasCfPath}`);
return JawsUtils.writeFile(lambdasCfPath, JSON.stringify(lambdaCfTemplate, null, 2))
.then(() => existingStack);
evt.projectCfTemplate = projectCfTemplate;
return JawsUtils.writeFile(
lambdasCfPath,
JSON.stringify(evt.projectCfTemplate, null, 2)
)
.then(() => {
return evt;
});
});
}
_provision(evt) {
let _this = this,
createOrUpdate,
cfDeferred;
JawsUtils.jawsDebug(`Lambda stack exists (${evt.stackExists}), deploying with ARN: ${evt.region.iamRoleArnLambda}`);
if (evt.stackExists) {
cfDeferred = _this.CloudFormation.sUpdateLambdasStack(
_this.Jaws,
evt.stage,
evt.region.region);
createOrUpdate = 'update';
} else {
cfDeferred = _this.CloudFormation.sCreateLambdasStack(
_this.Jaws,
evt.stage,
evt.region.region);
createOrUpdate = 'create';
}
JawsCli.log('Running CloudFormation lambda deploy...');
let spinner = JawsCli.spinner();
spinner.start();
return cfDeferred
.then(function(cfData) {
return _this.CloudFormation.sMonitorCf(cfData, createOrUpdate);
})
.then(function() {
spinner.stop(true);
});
}
}
module.exports = CodeProvisionLambdaNodejs;

View File

@ -3,6 +3,7 @@
/**
* Action: Code Upload: Lambda: Nodejs
* - Uploads a single Lambda's code to their JAWS project bucket
* - Don't attach "evt" to context, it will be overwritten in concurrent operations
*/
const JawsPlugin = require('../../JawsPlugin'),
@ -55,11 +56,10 @@ class CodeUploadLambdaNodejs extends JawsPlugin {
codeUploadLambdaNodejs(evt) {
let _this = this;
_this.evt = evt;
// Load AWS Service Instances
let awsConfig = {
region: _this.evt.region.region,
region: evt.region.region,
accessKeyId: _this.Jaws._awsAdminKeyId,
secretAccessKey: _this.Jaws._awsAdminSecretKey,
};
@ -67,12 +67,11 @@ class CodeUploadLambdaNodejs extends JawsPlugin {
_this.AwsMisc = require('../../utils/aws/Misc');
// Flow
return BbPromise.try(function() {})
return _this._validateAndPrepare(evt)
.bind(_this)
.then(_this._validateAndPrepare)
.then(_this._upload)
.then(function() {
return _this.evt;
return evt;
})
.catch(function(e) {
console.log(e.stack)
@ -83,8 +82,8 @@ class CodeUploadLambdaNodejs extends JawsPlugin {
* Validate And Prepare
*/
_validateAndPrepare() {
Promise.resolve();
_validateAndPrepare(evt) {
return BbPromise.resolve(evt);
}
/**
@ -92,22 +91,32 @@ class CodeUploadLambdaNodejs extends JawsPlugin {
* - Upload zip file to S3
*/
_upload() {
_upload(evt) {
let _this = this;
JawsUtils.jawsDebug(`Uploading ${_this.evt.function.name} to ${_this.evt.region.jawsBucket}`);
JawsUtils.jawsDebug(`Uploading ${evt.function.name} to ${evt.region.jawsBucket}`);
return _this.S3.sPutLambdaZip(
_this.evt.region.jawsBucket,
evt.region.jawsBucket,
_this.Jaws._projectJson.name,
_this.evt.stage,
_this.evt.function.name,
fs.createReadStream(_this.evt.function.pathCompressed))
evt.stage,
evt.function.name,
fs.createReadStream(evt.function.pathCompressed))
.then(function(s3Key) {
_this.evt.function.s3Key = s3Key;
return BbPromise.resolve();
// Add S3Key and Bucket to Lambda.Function.Properties.Code
for (let r in evt.function.cloudFormation.lambda) {
if (evt.function.cloudFormation.lambda[r].Type === 'AWS::Lambda::Function') {
evt.function.cloudFormation.lambda[r].Properties.Code = {
S3Bucket: evt.region.jawsBucket,
S3Key: s3Key
}
}
}
return BbPromise.resolve(evt);
});
}
}

View File

@ -76,22 +76,23 @@ class FunctionDeploy extends JawsPlugin {
let _this = this;
_this.evt = {};
_this.evt.queued = {};
_this.evt.uploaded = {};
_this.evt.provisioned = {};
_this.evt.queued.type = evt.type ? evt.type : null;
_this.evt.queued.stage = evt.stage ? evt.stage : null;
_this.evt.queued.regions = evt.region ? [evt.region] : [];
_this.evt.queued.noExeCf = (evt.noExeCf == true || evt.noExeCf == 'true');
_this.evt.queued.paths = evt.paths ? evt.paths : [];
_this.evt.queued.functions = [];
_this.evt.provisioned.functions = [];
_this.evt.uploaded = {};
// Flow
return BbPromise.try(function() {})
.bind(_this)
.then(_this._validateAndPrepare)
.then(_this._promptStage)
.then(_this._deployRegions);
.then(_this._deployRegions)
.then(function() {
console.log("DONE: ", _this.evt);
});
}
/**
@ -99,6 +100,8 @@ class FunctionDeploy extends JawsPlugin {
* - If CLI, maps CLI input to event object
*/
// TODO: Loop through each function, check runtime and separate accordingly
_validateAndPrepare() {
let _this = this;
@ -205,6 +208,8 @@ class FunctionDeploy extends JawsPlugin {
* Deploy Regions
*/
// TODO: multi-language support?
_deployRegions() {
let _this = this;
@ -221,7 +226,7 @@ class FunctionDeploy extends JawsPlugin {
case "endpoint":
// TODO: Move to re-usable function ("all" needs this too)
// TODO: Create current, add function, add endpoints (even mutliple), process w/ error handling for API Gateway issues
// TODO: No concurrency, instead process w/ error handling for API Gateway throttling
return _this.Jaws.actions.endpointPackageApiGateway(evtClone)
.bind(_this)
.then(_this.Jaws.actions.endpointProvisionApiGateway);
@ -253,9 +258,10 @@ class FunctionDeploy extends JawsPlugin {
// Process sub-Actions
return _this.Jaws.actions.codePackageLambdaNodejs(newEvent)
.bind(_this)
.then(_this.Jaws.actions.codeCompressLambdaNodejs)
.then(_this.Jaws.actions.codeUploadLambdaNodejs)
.then(function(evt) {
// Add Function and Region
_this.evt.uploaded[region].push(evt.function);
return cb();
@ -277,28 +283,31 @@ class FunctionDeploy extends JawsPlugin {
})
.then(function() {
// If type is "endpoint", skip
if (_this.evt.queued.type === 'endpoint') return _this.evt;
return new BbPromise(function(resolve, reject) {
// If type is "code" or "all", do concurrent, multi-region, CF update
async.eachLimit(Object.keys(_this.evt.uploaded), 10, function(region, cb) {
// If type is "endpoint", skip
if (_this.evt.queued.type === 'endpoint') return _this.evt;
let newEvent = {
stage: _this.evt.queued.stage,
region: JawsUtils.getProjRegionConfigForStage(
_this.Jaws._projectJson,
_this.evt.queued.stage,
region),
functions: _this.evt.uploaded[region],
};
// If type is "code" or "all", do concurrent, multi-region, CF update
async.eachLimit(Object.keys(_this.evt.uploaded), 5, function(region, cb) {
return _this.Jaws.actions.codeProvisionLambdaNodejs(newEvent)
.then(function() {
return cb();
});
let newEvent = {
stage: _this.evt.queued.stage,
region: JawsUtils.getProjRegionConfigForStage(
_this.Jaws._projectJson,
_this.evt.queued.stage,
region),
functions: _this.evt.uploaded[region],
};
}, function() {
return _this.Jaws.actions.codeProvisionLambdaNodejs(newEvent)
.then(function() {
return cb();
});
}, function() {
return resolve(_this.evt);
});
});
});
}

View File

@ -20,6 +20,10 @@
"path": "./defaults/actions/CodePackageLambdaNodejs.js",
"config": {}
},
{
"path": "./defaults/actions/CodeCompressLambdaNodejs.js",
"config": {}
},
{
"path": "./defaults/actions/CodeUploadLambdaNodejs.js",
"config": {}

View File

@ -30,9 +30,9 @@ module.exports = function(config) {
* Get Lambdas Stack Name
*/
CloudFormation.sGetLambdasStackName = BbPromise.method(function(stage, projName) {
CloudFormation.sGetLambdasStackName = function(stage, projName) {
return [stage, projName, 'l'].join('-'); // stack names are alphanumeric + -, no _ :(
});
};
/**
* Get Resources Stack Name
@ -135,6 +135,7 @@ module.exports = function(config) {
*/
CloudFormation.sPutCfFile = function(projRootPath, bucketName, projName, projStage, type) {
if (['lambdas', 'resources'].indexOf(type) == -1) {
BbPromise.reject(new JawsError(`Type ${type} invalid. Must be lambdas or resources`, JawsError.errorCodes.UNKNOWN));
}
@ -165,17 +166,21 @@ module.exports = function(config) {
* Create Lambdas Stack
* @param JAWS
* @param stage
* @param lambdaRoleArn
* @param region
* @returns {*}
*/
CloudFormation.sCreateLambdasStack = function(JAWS, stage, lambdaRoleArn) {
CloudFormation.sCreateLambdasStack = function(JAWS, stage, region) {
let _this = this,
projRootPath = JAWS._projectRootPath,
bucketName = JAWS.getProjectBucket(config.region, stage),
projName = JAWS._projectJson.name;
projName = JAWS._projectJson.name,
regionJson = JawsUtils.getProjRegionConfigForStage(
JAWS._projectJson,
stage,
region);
let stackName = CloudFormation.sGetResourcesStackName(stage, projName);
let stackName = CloudFormation.sGetLambdasStackName(stage, projName);
let params = {
StackName: stackName,
@ -183,7 +188,7 @@ module.exports = function(config) {
OnFailure: 'ROLLBACK',
Parameters: [{
ParameterKey: 'aaLambdaRoleArn',
ParameterValue: lambdaRoleArn,
ParameterValue: regionJson.iamRoleArnLambda,
UsePreviousValue: false,
},],
Tags: [{
@ -192,7 +197,7 @@ module.exports = function(config) {
},],
};
return CloudFormation.sPutCfFile(projRootPath, bucketName, projName, stage, 'lambdas')
return CloudFormation.sPutCfFile(projRootPath, regionJson.jawsBucket, projName, stage, 'lambdas')
.then(function(templateUrl) {
params.TemplateURL = templateUrl;
return CloudFormation.createStackPromised(params);
@ -203,15 +208,16 @@ module.exports = function(config) {
* Update Lambdas Stack
* @param JAWS
* @param stage
* @param lambdaRoleArn
* @param region
* @returns {*}
*/
CloudFormation.sUpdateLambdasStack = function(JAWS, stage, lambdaRoleArn) {
CloudFormation.sUpdateLambdasStack = function(JAWS, stage, region) {
let _this = this,
projRootPath = JAWS._projectRootPath,
bucketName = JAWS.getProjectBucket(config.region, stage),
projName = JAWS._projectJson.name;
projName = JAWS._projectJson.name,
regionJson = JawsUtils.getProjRegionConfigForStage(JAWS._projectJson, stage, region);
let stackName = CloudFormation.sGetLambdasStackName(stage, projName);
@ -220,13 +226,13 @@ module.exports = function(config) {
Capabilities: [],
UsePreviousTemplate: false,
Parameters: [{
ParameterKey: 'aaLambdaRoleArn',
ParameterValue: lambdaRoleArn,
UsePreviousValue: false,
ParameterKey: 'aaLambdaRoleArn',
ParameterValue: regionJson.iamRoleArnLambda,
UsePreviousValue: false,
},],
};
return CloudFormation.sPutCfFile(projRootPath, bucketName, projName, stage, 'lambdas')
return CloudFormation.sPutCfFile(projRootPath, regionJson.jawsBucket, projName, stage, 'lambdas')
.then(function(templateUrl) {
params.TemplateURL = templateUrl;
return CloudFormation.updateStackPromised(params);

View File

@ -20,13 +20,13 @@ BbPromise.promisifyAll(fs);
module.exports = function(config) {
// Promisify and configure instance
const S3 = BbPromise.promisifyAll(new AWS.S3(config));
const S3 = BbPromise.promisifyAll(new AWS.S3(config), { suffix: "Promised" });
/**
* Create Bucket
*/
S3.sCreateBucket = function(bucketName) {
return S3.getBucketAclAsync({Bucket: bucketName})
return S3.getBucketAclPromised({Bucket: bucketName})
.then(function() {
//we are good, bucket already exists and we own it!
})
@ -38,7 +38,7 @@ module.exports = function(config) {
);
}
return S3.createBucketAsync({
return S3.createBucketPromised({
Bucket: bucketName,
ACL: 'private',
});
@ -57,7 +57,7 @@ module.exports = function(config) {
};
JawsUtils.jawsDebug(`env get s3 bucket: ${bucketName} key: ${key}`);
return S3.getObjectAsync(params)
return S3.getObjectPromised(params)
};
/**
@ -72,7 +72,7 @@ module.exports = function(config) {
Body: contents,
};
return S3.putObjectAsync(params);
return S3.putObjectPromised(params);
};
/**
@ -92,7 +92,7 @@ module.exports = function(config) {
JawsUtils.jawsDebug(`lambda zip s3 key: ${key}`);
return S3.uploadAsync(params)
return S3.uploadPromised(params)
.then(() => {
return key;
});

View File

@ -227,30 +227,6 @@ exports.generateJawsBucketName = function(stage, region, projectDomain) {
return `jaws.${stage}.${region}.${projectDomain}`;
};
/**
* Gets all lambda function name's
* @param projectRootPath
* @returns {Promise} list of functionName's
*/
exports.getAllLambdaNames = function(projectRootPath) {
let _this = this,
lambdaNames = [];
return this.getFunctions(projectRootPath, 'lambda')
.then(function(lambdaAwsmPaths) {
lambdaAwsmPaths.forEach(function(ljp) {
let awsm = _this.readAndParseJsonSync(ljp),
lambdaName = _this.getLambdaName(awsm);
lambdaNames.push(lambdaName);
});
return lambdaNames;
});
};
/**
* Given list of project stage objects, extract given region
* @param projectJawsJson
@ -260,6 +236,7 @@ exports.getAllLambdaNames = function(projectRootPath) {
*/
exports.getProjRegionConfigForStage = function(projectJawsJson, stage, regionName) {
let projectStageObj = projectJawsJson.stage[stage];
let region = projectStageObj.filter(regionObj => {