FunctionDeploy: Refactor, untested

This commit is contained in:
ac360 2016-01-01 17:40:03 -08:00
parent ec17ebd758
commit ed9fae6e77
4 changed files with 225 additions and 162 deletions

View File

@ -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 );
};
};

View File

@ -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);

View File

@ -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);
});
});
}

View File

@ -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 = {