diff --git a/lib/actions/CodeDeployLambdaNodeJs.js b/lib/actions/CodeDeployLambdaNodeJs.js index 6f11bad36..878efb5bd 100644 --- a/lib/actions/CodeDeployLambdaNodeJs.js +++ b/lib/actions/CodeDeployLambdaNodeJs.js @@ -62,23 +62,9 @@ module.exports = function(SPlugin, serverlessPath) { deploy(options) { - let _this = this; + let _this = this; _this.options = options; - // Load AWS Service Instances - let awsConfig = { - region: this.options.region, - accessKeyId: _this.S.config.awsAdminKeyId, - secretAccessKey: _this.S.config.awsAdminSecretKey - }; - _this.S3 = require('../utils/aws/S3')(awsConfig); - _this.Lambda = require('../utils/aws/Lambda')(awsConfig); - _this.AwsMisc = require('../utils/aws/Misc'); - - // Instantiate Classes - _this.project = _this.S.classes.Project(_this.S); - _this.meta = _this.S.classes.Meta(_this.S); - // Flow return _this._validateAndPrepare() .bind(_this) @@ -86,7 +72,21 @@ module.exports = function(SPlugin, serverlessPath) { .then(_this._upload) .then(_this._deploy) .then(function() { - return options; + + /** + * Return Action Data + * - WARNING: Adjusting these will break Plugins + */ + + return { + options: _this.options, + pathCompressed: _this.pathCompressed, + s3Bucket: _this.s3Bucket, + s3Key: _this.S3Key, + lambdaVersion: _this.lambdaVersion, + lambdaAlias: _this.lambdaAlias, + lambdaAliasArn: _this.lambdaAliasArn + }; }); } @@ -95,6 +95,29 @@ module.exports = function(SPlugin, serverlessPath) { */ _validateAndPrepare() { + + let _this = this; + + // TODO: Validate Options + + // Load AWS Service Instances + let awsConfig = { + region: _this.options.region, + accessKeyId: _this.S.config.awsAdminKeyId, + secretAccessKey: _this.S.config.awsAdminSecretKey + }; + _this.S3 = require('../utils/aws/S3')(awsConfig); + _this.Lambda = require('../utils/aws/Lambda')(awsConfig); + _this.AwsMisc = require('../utils/aws/Misc'); + + // Instantiate Classes + _this.meta = _this.S.classes.Meta(_this.S); + _this.project = _this.S.classes.Project(_this.S); + _this.function = _this.S.classes.Function(_this.S, { + module: _this.options.module, + function: _this.options.function + }); + return BbPromise.resolve(); } @@ -106,7 +129,7 @@ module.exports = function(SPlugin, serverlessPath) { let zip = new Zip(); - this.options.function.pathsPackaged.forEach(nc => { + this.options.pathsPackaged.forEach(nc => { zip.file(nc.fileName, nc.data); }); @@ -116,7 +139,7 @@ module.exports = function(SPlugin, serverlessPath) { }); if (zipBuffer.length > 52428800) { - Promise.reject(new SError( + BbPromise.reject(new SError( 'Zip file is > the 50MB Lambda queued limit (' + zipBuffer.length + ' bytes)', SError.errorCodes.ZIP_TOO_BIG) ); @@ -126,11 +149,9 @@ module.exports = function(SPlugin, serverlessPath) { this.pathCompressed = path.join(this.options.pathDist, 'package.zip'); // Create compressed package - fs.writeFileSync( - this.pathCompressed, - zipBuffer); + fs.writeFileSync(this.pathCompressed, zipBuffer); - SUtils.sDebug(`"${this.options.stage} - ${this.options.region.region} - ${this.function.data.name}": Compressed file created - ${this.pathCompressed}`); + SUtils.sDebug(`"${this.options.stage} - ${this.options.region} - ${this.function.data.name}": Compressed file created - ${this.pathCompressed}`); return BbPromise.resolve(); } @@ -140,23 +161,23 @@ module.exports = function(SPlugin, serverlessPath) { * - Upload zip file to S3 */ - _upload(evt) { + _upload() { let _this = this; SUtils.sDebug(`"${this.options.stage} - ${this.options.region} - ${this.function.data.name}": Uploading to project bucket...`); return _this.S3.sPutLambdaZip( - this.projectBucket, + this.meta.data.private.variables.projectBucket, _this.S.data.project.get('name'), this.options.stage, - this.function.name, + this.function.data.name, fs.createReadStream(this.pathCompressed)) .then(function(s3Key) { // Store S3 Data - this.function.s3Bucket = this.region.regionBucket; - this.function.s3Key = s3Key; + _this.s3Bucket = this.meta.data.private.variables.projectBucket; + _this.s3Key = s3Key; return BbPromise.resolve(); }); @@ -170,11 +191,10 @@ module.exports = function(SPlugin, serverlessPath) { _deploy() { let _this = this, - lambda, - lambdaVersion; + lambda; var params = { - FunctionName: _this.Lambda.sGetLambdaName(_this.S.data.project.get('name'), evt.function.name), + FunctionName: _this.Lambda.sGetLambdaName(_this.project.data.name, _this.function.data.name), Qualifier: '$LATEST' }; @@ -191,28 +211,29 @@ module.exports = function(SPlugin, serverlessPath) { if (!lambda) { - SUtils.sDebug(`"${evt.stage} - ${evt.region.region} - ${evt.function.name}": Creating Lambda function...`); + SUtils.sDebug(`"${_this.options.stage} - ${_this.options.region} - ${_this.function.data.name}": Creating Lambda function...`); // Create Lambda let params = { Code: { - S3Bucket: evt.function.s3Bucket, - S3Key: evt.function.s3Key + S3Bucket: _this.s3Bucket, + S3Key: _this.s3Key }, - FunctionName: _this.Lambda.sGetLambdaName(_this.S.data.project.get('name'), evt.function.name), /* required */ - Handler: evt.function.handler, /* required */ - Role: evt.region.iamRoleArnLambda, /* required */ - Runtime: evt.function.module.runtime, /* required */ - Description: 'Serverless Lambda function for project: ' + _this.S.data.project.get('name'), - MemorySize: evt.function.memorySize, - Publish: true, // Required by Serverless Framework & recommended by AWS - Timeout: evt.function.timeout + FunctionName: _this.Lambda.sGetLambdaName(_this.project.data.name, _this.data.function.name), /* required */ + Handler: _this.function.data.handler, /* required */ + Role: _this.meta.data.private.stages[_this.options.stage].regions[_this.options.region].variables.iamRoleArnLambda, /* required */ + Runtime: _this.function.module.runtime, /* required */ + Description: 'Serverless Lambda function for project: ' + _this.project.data.name, + MemorySize: _this.function.data.memorySize, + Publish: true, // Required by Serverless Framework & recommended best practice by AWS + Timeout: _this.function.data.timeout }; + return _this.Lambda.createFunctionPromised(params) .then(function(data) { // Save Version - lambdaVersion = data.Version; + _this.lambdaVersion = data.Version; return data; }) @@ -222,33 +243,33 @@ module.exports = function(SPlugin, serverlessPath) { } else { - SUtils.sDebug(`"${evt.stage} - ${evt.region.region} - ${evt.function.name}": Updating Lambda configuration...`); + SUtils.sDebug(`"${_this.options.stage} - ${_this.options.region} - ${_this.function.data.name}": Updating Lambda configuration...`); let params = { FunctionName: lambda.Configuration.FunctionName, /* required */ - Description: 'Serverless Lambda function for project: ' + _this.S._project.name, - Handler: evt.function.handler, - MemorySize: evt.function.memorySize, - Role: evt.region.iamRoleArnLambda, - Timeout: evt.function.timeout + Description: 'Serverless Lambda function for project: ' + _this.project.data.name, + Handler: _this.function.data.handler, + MemorySize: _this.function.data.memorySize, + Role: _this.meta.data.private.stages[_this.options.stage].regions[_this.options.region].variables.iamRoleArnLambda, + Timeout: _this.function.data.timeout }; return _this.Lambda.updateFunctionConfigurationPromised(params) .then(function(){ - SUtils.sDebug(`"${evt.stage} - ${evt.region.region} - ${evt.function.name}": Updating Lambda function...`); + SUtils.sDebug(`"${_this.options.stage} - ${_this.options.region} - ${_this.function.data.name}": Updating Lambda function...`); // Update Lambda Code let params = { FunctionName: lambda.Configuration.FunctionName, /* required */ Publish: true, // Required by Serverless Framework & recommended by AWS - S3Bucket: evt.region.regionBucket, - S3Key: evt.function.s3Key, + S3Bucket: _this.meta.data.private.variables.projectBucket, + S3Key: _this.s3Key }; return _this.Lambda.updateFunctionCodePromised(params) .then(function(data) { // Save Version - lambdaVersion = data.Version; + _this.lambdaVersion = data.Version; return( data ); }); @@ -260,10 +281,11 @@ module.exports = function(SPlugin, serverlessPath) { // Alias Lambda w/ Stage let aliasedLambda = false; + _this.lambdaAlias = _this.options.stage.toLowerCase(); var params = { FunctionName: data.FunctionName, /* required */ - Name: evt.stage.toLowerCase() /* required */ + Name: _this.lambdaAlias /* required */ }; return _this.Lambda.getAliasPromised(params) @@ -279,13 +301,13 @@ module.exports = function(SPlugin, serverlessPath) { // Update Existing Alias - SUtils.sDebug(`"${evt.stage} - ${evt.region.region} - ${evt.function.name}": Updating Lambda Alias for version - ${lambdaVersion}`); + SUtils.sDebug(`"${_this.options.stage} - ${_this.options.region} - ${_this.function.data.name}": Updating Lambda Alias for version - ${_this.lambdaVersion}`); let params = { - FunctionName: data.FunctionName, /* required */ - FunctionVersion: lambdaVersion, /* required */ - Name: evt.stage, /* required */ - Description: 'Project: ' + _this.S._project.name + ' Stage: ' + evt.stage + FunctionName: data.FunctionName, /* required */ + FunctionVersion: _this.lambdaVersion, /* required */ + Name: _this.lambdaAlias, /* required */ + Description: 'Project: ' + _this.project.data.name + ' Stage: ' + _this.options.stage }; return _this.Lambda.updateAliasPromised(params); @@ -294,28 +316,25 @@ module.exports = function(SPlugin, serverlessPath) { // Create New Alias - SUtils.sDebug(`"${evt.stage} - ${evt.region.region} - ${evt.function.name}": Creating New Lambda Alias for version - ${lambdaVersion}`); + SUtils.sDebug(`"${_this.options.stage} - ${_this.options.region} - ${_this.function.data.name}": Creating New Lambda Alias for version - ${_this.lambdaVersion}`); let params = { - FunctionName: data.FunctionName, /* required */ - FunctionVersion: lambdaVersion, /* required */ - Name: evt.stage, /* required */ - Description: 'Project: ' + _this.S._project.name + ' Stage: ' + evt.stage + FunctionName: data.FunctionName, /* required */ + FunctionVersion: _this.lambdaVersion, /* required */ + Name: _this.lambdaAlias, /* required */ + Description: 'Project: ' + _this.project.data.name + ' Stage: ' + _this.options.stage }; return _this.Lambda.createAliasPromised(params); } }) .then(function(data) { - // Add Version & Alias information to evt - evt.function.deployedVersion = data.FunctionVersion; - evt.function.deployedAlias = evt.stage; - evt.function.deployedAliasArn = data.AliasArn; - return evt + // Save Alias + _this.lambdaAliasArn = data.AliasArn; }); }) } } return( CodeDeployLambdaNodejs ); -}; +}; \ No newline at end of file diff --git a/lib/actions/CodePackageLambdaNodeJs.js b/lib/actions/CodePackageLambdaNodeJs.js index fbd1b1592..b80970405 100644 --- a/lib/actions/CodePackageLambdaNodeJs.js +++ b/lib/actions/CodePackageLambdaNodeJs.js @@ -10,7 +10,7 @@ module.exports = function(SPlugin, serverlessPath) { - const path = require('path'), + const path = require('path'), SError = require(path.join(serverlessPath, 'ServerlessError')), SUtils = require(path.join(serverlessPath, 'utils/index')), BbPromise = require('bluebird'), @@ -64,9 +64,56 @@ module.exports = function(SPlugin, serverlessPath) { package(options) { - let _this = this; + let _this = this; _this.options = options; + // Flow + return _this._validateAndPrepare() + .bind(_this) + .then(_this._createDistFolder) + .then(_this._package) + .then(function() { + + /** + * Return Action Data + * - WARNING: Adjusting these will break Plugins + */ + + return { + options: _this.options, + pathsPackaged: _this.pathsPackaged, + pathDist: _this.pathDist + }; + }); + } + + /** + * Validate And Prepare + */ + + _validateAndPrepare() { + + let _this = this; + + //TODO: Use Function.validate() + + // Validate + if (!_this.function.data.name) { + throw new SError('Function does not have a name property'); + } + if (!_this.function.data.handler) { + throw new SError('Function does not have a handler property'); + } + if (!_this.function.data.timeout) { + throw new SError('Function does not have a timeout property'); + } + if (!_this.function.data.memorySize) { + throw new SError('Function does not have a memorySize property'); + } + if (!_this.function.module.data.runtime) { + throw new SError('Function\'s parent module is missing a runtime property'); + } + // Load AWS Service Instances let awsConfig = { region: _this.options.region, @@ -76,44 +123,12 @@ module.exports = function(SPlugin, serverlessPath) { _this.S3 = require('../utils/aws/S3')(awsConfig); // Instantiate classes - _this.function = _this.S.classes.Function(_this.S); + _this.function = _this.S.classes.Function(_this.S, { + module: _this.options.module, + function: _this.options.function + }); _this.meta = _this.S.classes.Meta(_this.S); - // Flow - return _this._validateAndPrepare() - .bind(_this) - .then(_this._createDistFolder) - .then(_this._package) - .then(function() { - return options; - }); - } - - /** - * Validate And Prepare - */ - - _validateAndPrepare() { - - //TODO: Use Function.validate() - - // Validate - if (!this.function.data.name) { - throw new SError('Function does not have a name property'); - } - if (!this.function.data.handler) { - throw new SError('Function does not have a handler property'); - } - if (!this.function.data.timeout) { - throw new SError('Function does not have a timeout property'); - } - if (!this.function.data.memorySize) { - throw new SError('Function does not have a memorySize property'); - } - if (!this.function.module.data.runtime) { - throw new SError('Function\'s parent module is missing a runtime property'); - } - return BbPromise.resolve(); } @@ -126,13 +141,13 @@ module.exports = function(SPlugin, serverlessPath) { let _this = this; // Create dist folder - let d = new Date(); - this.pathDist = path.join(os.tmpdir(), _this.function.data.name + '@' + d.getTime()); + let d = new Date(); + _this.pathDist = path.join(os.tmpdir(), _this.function.data.name + '@' + d.getTime()); // Status SUtils.sDebug(`"${_this.options.stage} - ${_this.options.region} - ${_this.function.data.name}": Copying in dist dir ${_this.pathDist}`); - // Copy entire test project to temp folder + // Copy entire test project to temp folder, don't include anything in excludePatterns let excludePatterns = this.function.data.custom.excludePatterns || []; wrench.copyDirSyncRecursive( @@ -167,14 +182,14 @@ module.exports = function(SPlugin, serverlessPath) { // Get ENV file from S3 return _this.S3.sGetEnvFile( - this.regionBucket, + _this.meta.data.private.variables.projectBucket, _this.project.data.name, - this.options.stage, - this.options.region) + _this.options.stage, + _this.options.region) .then(function(s3ObjData) { fs.writeFileSync( - path.join(this.pathDist,'.env'), + path.join(_this.pathDist,'.env'), s3ObjData.Body); }); @@ -184,13 +199,13 @@ module.exports = function(SPlugin, serverlessPath) { * Package */ - _package(evt) { + _package() { // Zip up whatever is in back this.function.data.custom.includePaths = ['.']; // Create pathsPackaged for each file ready to compress - this.pathsPackaged = _this._generateIncludePaths(); + this.pathsPackaged = this._generateIncludePaths(); return BbPromise.resolve(); } @@ -201,15 +216,16 @@ module.exports = function(SPlugin, serverlessPath) { _generateIncludePaths() { - let compressPaths = [], + let _this = this, + compressPaths = [], ignore = ['.DS_Store'], stats, fullPath; - this.function.data.custom.includePaths.forEach(p => { + _this.function.data.custom.includePaths.forEach(p => { try { - fullPath = path.resolve(path.join(this.pathDist, p)); + fullPath = path.resolve(path.join(_this.pathDist, p)); stats = fs.lstatSync(fullPath); } catch (e) { console.error('Cant find includePath ', p, e); diff --git a/lib/actions/FunctionDeploy.js b/lib/actions/FunctionDeploy.js index a80a173f6..74975ea94 100644 --- a/lib/actions/FunctionDeploy.js +++ b/lib/actions/FunctionDeploy.js @@ -98,6 +98,39 @@ module.exports = function(SPlugin, serverlessPath) { if (this.S.cli.options.nonInteractive) this.S.config.interactive = false; } + // Flow + return _this._validateAndPrepare() + .bind(_this) + .then(function() { + return _this.cliPromptSelectStage('Function Deployer - Choose a stage: ', this.options.stage, false) + .then(stage => { + this.options.stage = stage; + }) + }) + .then(_this._processDeployment) + .then(function() { + + /** + * Return Action Data + * - WARNING: Adjusting these will break Plugins + */ + + return { + options: _this.options + } + + }); + } + + /** + * Validate And Prepare + * - If CLI, maps CLI input to event object + */ + + _validateAndPrepare() { + + let _this = this; + // Set Defaults this.options.stage = this.options.stage ? this.options.stage : null; this.options.paths = this.options.paths ? this.options.paths : []; @@ -112,30 +145,6 @@ module.exports = function(SPlugin, serverlessPath) { this.project = new this.S.classes.Project(this.S); this.meta = new this.S.classes.Meta(this.S); - // Flow - return _this._validateAndPrepare() - .bind(_this) - .then(function() { - return _this.cliPromptSelectStage('Function Deployer - Choose a stage: ', this.options.stage, false) - .then(stage => { - this.options.stage = stage; - }) - }) - .then(_this._processDeployment) - .then(function() { - return _this.options; - }); - } - - /** - * Validate And Prepare - * - If CLI, maps CLI input to event object - */ - - _validateAndPrepare() { - - let _this = this; - // Non-CLI Validations if (!_this.S.cli) { @@ -165,7 +174,7 @@ module.exports = function(SPlugin, serverlessPath) { let _this = this; // Status - SCli.log('Deploying functions in "' + _this.options.stage + '" to the following regions: ' + this.regions.join(', ')); + SCli.log('Deploying functions in "' + _this.options.stage + '" to the following regions: ' + _this.regions.join(', ')); _this._spinner = SCli.spinner(); _this._spinner.start(); @@ -214,7 +223,7 @@ module.exports = function(SPlugin, serverlessPath) { let _this = this; - return new BbPromise(function(resolve, reject) { + return new BbPromise(function(resolve) { /** * Package, Upload, Deploy, Alias functions' code concurrently @@ -223,23 +232,42 @@ module.exports = function(SPlugin, serverlessPath) { async.eachLimit(this.functions, 5, function(func, cb) { - // Create new this.options object for concurrent operations - let options = { - stage: _this.options.stage, - region: _this.options.region, - module: func.module.data.name, - function: func.data.name - }; + return new BbPromise(function(resolve) { + + // Nodejs + if (func.module.data.runtime = 'nodejs') { + + // Package Code + return _this.S.actions.codePackageLambdaNodejs({ + stage: _this.options.stage, + region: _this.options.region, + module: func.module.data.name, + function: func.data.name + }) + .bind(_this) + .then(function(result) { + + // Deploy Code + return _this.S.actions.codeDeployLambdaNodejs({ + stage: result.options.stage, + region: result.options.region, + module: result.options.module, + function: result.options.function, + pathDist: result.pathDist, + pathsPackaged: result.pathsPackaged + }) + }) + .then(function(result) { + return resolve(result); + }); + } + }) + .then(function(result) { - // Process sub-Actions - return _this.S.actions.codePackageLambdaNodejs(options) - .bind(_this) - .then(_this.S.actions.codeDeployLambdaNodejs) - .then(_this.S.actions.codeEventDeployLambda) - .then(function(returnedOptions) { // Add Function and Region - this.options.deployed[region].push(returnedOptions.function); + this.options.deployed[region].push(result.function); return cb(); + }) .catch(function(e) { @@ -253,10 +281,10 @@ module.exports = function(SPlugin, serverlessPath) { }); return cb(); - }) + }); }, function() { - return resolve(this.options, region); + return resolve(region); }); }); } diff --git a/lib/actions/FunctionRun.js b/lib/actions/FunctionRun.js index b95e7237c..4e29d76bd 100644 --- a/lib/actions/FunctionRun.js +++ b/lib/actions/FunctionRun.js @@ -71,7 +71,7 @@ module.exports = function(SPlugin, serverlessPath) { return BbPromise.reject(new SError(`Could not find a function with the path: ${options.path}`)); } - + // Runtime: nodejs if (Project.data.modules[options.module].runtime === 'nodejs') { let newOptions = {