forked from serverless/serverless
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
First pass at Python2 deployment for functions
- Loading branch information
Showing
1 changed file
with
341 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,341 @@ | ||
'use strict'; | ||
|
||
/** | ||
* Action: Code Deploy: Lambda: Python2 | ||
* - Uploads a single Lambda's code to their Serverless project bucket | ||
* - Don't attach "options" to context, it will be overwritten in concurrent operations | ||
* - WARNING: This Action runs concurrently. | ||
*/ | ||
|
||
module.exports = function(SPlugin, serverlessPath) { | ||
const path = require('path'), | ||
SError = require(path.join(serverlessPath, 'ServerlessError')), | ||
SUtils = require(path.join(serverlessPath, 'utils/index')), | ||
BbPromise = require('bluebird'), | ||
Zip = require('node-zip'), | ||
fs = require('fs'), | ||
os = require('os'); | ||
|
||
// Promisify fs module | ||
BbPromise.promisifyAll(fs); | ||
|
||
class CodeDeployLambdaPython2 extends SPlugin { | ||
|
||
constructor(S, config) { | ||
super(S, config); | ||
} | ||
|
||
static getName() { | ||
return 'serverless.core.' + CodeDeployLambdaPython2.name; | ||
} | ||
|
||
registerActions() { | ||
|
||
this.S.addAction(this.codeDeployLambdaPython2.bind(this), { | ||
handler: 'codeDeployLambdaPython2', | ||
description: 'Uploads a Lambda\'s code to S3' | ||
}); | ||
|
||
return BbPromise.resolve(); | ||
} | ||
|
||
/** | ||
* Deploy Code Lambda Node.Js | ||
*/ | ||
|
||
codeDeployLambdaPython2(evt) { | ||
let deployer = new Deployer(this.S); | ||
return deployer.deploy(evt); | ||
} | ||
} | ||
|
||
/** | ||
* Deployer Class | ||
* - Necessary for this action to run concurrently | ||
*/ | ||
|
||
class Deployer { | ||
|
||
constructor(S) { | ||
this.S = S; | ||
} | ||
|
||
deploy(evt) { | ||
|
||
let _this = this; | ||
_this.evt = evt; | ||
|
||
// Flow | ||
return _this._validateAndPrepare() | ||
.bind(_this) | ||
.then(_this._compress) | ||
.then(function() { | ||
return [_this._upload(), _this._provision()]; | ||
}) | ||
.map(function(func) { | ||
return func; | ||
}, { concurrency: 3 }) | ||
.then(_this._alias) | ||
.then(function() { | ||
|
||
/** | ||
* Return EVT | ||
*/ | ||
|
||
_this.evt.data.pathCompressed = _this.pathCompressed; | ||
_this.evt.data.s3Bucket = _this.s3Bucket; | ||
_this.evt.data.s3Key = _this.s3Key; | ||
_this.evt.data.lambdaVersion = _this.lambdaVersion; | ||
_this.evt.data.lambdaAlias = _this.lambdaAlias; | ||
_this.evt.data.lambdaAliasArn = _this.lambdaAliasArn; | ||
return _this.evt; | ||
|
||
}); | ||
} | ||
|
||
/** | ||
* Validate And Prepare | ||
*/ | ||
|
||
_validateAndPrepare() { | ||
|
||
let _this = this; | ||
|
||
// TODO: Validate Options | ||
|
||
// Load AWS Service Instances | ||
let awsConfig = { | ||
region: _this.evt.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.state.getMeta(); | ||
_this.project = _this.S.state.getProject(); | ||
_this.function = _this.S.state.getFunctions({ paths: [_this.evt.options.path] })[0]; | ||
|
||
return BbPromise.resolve(); | ||
} | ||
|
||
/** | ||
* Compress | ||
*/ | ||
|
||
_compress() { | ||
|
||
let zip = new Zip(); | ||
|
||
this.evt.options.pathsPackaged.forEach(nc => { | ||
zip.file(nc.name, fs.readFileSync(nc.path)); | ||
}); | ||
|
||
this.zipBuffer = zip.generate({ | ||
type: 'nodebuffer', | ||
compression: 'DEFLATE' | ||
}); | ||
|
||
if (this.zipBuffer.length > 52428800) { | ||
BbPromise.reject(new SError( | ||
'Zip file is > the 50MB Lambda queued limit (' + this.zipBuffer.length + ' bytes)', | ||
SError.errorCodes.ZIP_TOO_BIG) | ||
); | ||
} | ||
|
||
// Set path of compressed package | ||
this.pathCompressed = path.join(this.evt.options.pathDist, 'package.zip'); | ||
|
||
// Create compressed package | ||
fs.writeFileSync(this.pathCompressed, this.zipBuffer); | ||
|
||
SUtils.sDebug(`"${this.evt.options.stage} - ${this.evt.options.region} - ${this.function.name}": Compressed file created - ${this.pathCompressed}`); | ||
|
||
return BbPromise.resolve(); | ||
} | ||
|
||
/** | ||
* Upload | ||
* - Upload zip file to S3 | ||
*/ | ||
|
||
_upload() { | ||
|
||
let _this = this; | ||
|
||
SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.name}": Uploading to project bucket...`); | ||
|
||
return _this.S3.sPutLambdaZip( | ||
_this.meta.variables.projectBucket, | ||
_this.project.name, | ||
_this.evt.options.stage, | ||
_this.function.name, | ||
fs.createReadStream(_this.pathCompressed)) | ||
.then(function (s3Key) { | ||
|
||
// Store S3 Data | ||
_this.s3Bucket = _this.meta.variables.projectBucket; | ||
_this.s3Key = s3Key; | ||
|
||
}); | ||
} | ||
|
||
/** | ||
* Provision | ||
* - Deploy Lambda | ||
*/ | ||
|
||
_provision() { | ||
|
||
let _this = this; | ||
|
||
var params = { | ||
FunctionName: _this.Lambda.sGetLambdaName(_this.project.name, _this.function._config.component, _this.function._config.module, _this.function.name), | ||
Qualifier: '$LATEST' | ||
}; | ||
|
||
return _this.Lambda.getFunctionPromised(params) | ||
.catch(function (e) { | ||
_this.lambda = null; | ||
}) | ||
.then(function (data) { | ||
_this.lambda = data; | ||
}) | ||
.then(function () { | ||
|
||
// Create or Update Lambda | ||
if (!_this.lambda) { | ||
|
||
SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.name}": Creating Lambda function...`); | ||
|
||
// Create Lambda | ||
let params = { | ||
Code: { | ||
ZipFile: _this.zipBuffer | ||
}, | ||
FunctionName: _this.Lambda.sGetLambdaName(_this.project.name, _this.function._config.component, _this.function._config.module, _this.function.name), /* required */ | ||
Handler: _this.function.handler, /* required */ | ||
Role: _this.meta.stages[_this.evt.options.stage].regions[_this.evt.options.region].variables.iamRoleArnLambda, /* required */ | ||
Runtime: _this.function.getRuntime(), /* required */ | ||
Description: 'Serverless Lambda function for project: ' + _this.project.name, | ||
MemorySize: _this.function.memorySize, | ||
Publish: true, // Required by Serverless Framework & recommended best practice by AWS | ||
Timeout: _this.function.timeout | ||
}; | ||
|
||
return _this.Lambda.createFunctionPromised(params) | ||
.then(function (data) { | ||
|
||
// Save Version & Lambda | ||
_this.lambdaVersion = data.Version; | ||
_this.lambda = data; | ||
}) | ||
|
||
} else { | ||
|
||
SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.name}": Updating Lambda configuration...`); | ||
|
||
let params = { | ||
FunctionName: _this.lambda.Configuration.FunctionName, /* required */ | ||
Description: 'Serverless Lambda function for project: ' + _this.project.name, | ||
Handler: _this.function.handler, | ||
MemorySize: _this.function.memorySize, | ||
Role: _this.meta.stages[_this.evt.options.stage].regions[_this.evt.options.region].variables.iamRoleArnLambda, | ||
Timeout: _this.function.timeout | ||
}; | ||
|
||
return _this.Lambda.updateFunctionConfigurationPromised(params) | ||
.then(function () { | ||
SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.name}": Updating Lambda function...`); | ||
|
||
// Update Lambda Code | ||
let params = { | ||
FunctionName: _this.lambda.Configuration.FunctionName, /* required */ | ||
Publish: true, // Required by Serverless Framework & recommended by AWS | ||
ZipFile: _this.zipBuffer | ||
}; | ||
|
||
return _this.Lambda.updateFunctionCodePromised(params) | ||
.then(function (data) { | ||
|
||
// Save Version & Lambda | ||
_this.lambdaVersion = data.Version; | ||
_this.lambda = data; | ||
}); | ||
}); | ||
} | ||
}) | ||
} | ||
|
||
/** | ||
* Alias Lambda w/ Stage | ||
*/ | ||
|
||
_alias() { | ||
|
||
let _this = this; | ||
let aliasedLambda = false; | ||
_this.lambdaAlias = _this.evt.options.stage.toLowerCase(); | ||
|
||
var params = { | ||
FunctionName: _this.lambda.FunctionName, /* required */ | ||
Name: _this.lambdaAlias /* required */ | ||
}; | ||
|
||
return _this.Lambda.getAliasPromised(params) | ||
.then(function() { | ||
aliasedLambda = true; | ||
}, function(e) { | ||
aliasedLambda = false; | ||
}) | ||
.then(function() { | ||
|
||
if (aliasedLambda) { | ||
|
||
// Update Existing Alias | ||
|
||
SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.name}": Updating Lambda Alias for version - ${_this.lambdaVersion}`); | ||
|
||
let params = { | ||
FunctionName: _this.lambda.FunctionName, /* required */ | ||
FunctionVersion: _this.lambdaVersion, /* required */ | ||
Name: _this.lambdaAlias, /* required */ | ||
Description: 'Project: ' | ||
+ _this.project.name | ||
+ ' Stage: ' | ||
+ _this.evt.options.stage | ||
}; | ||
|
||
return _this.Lambda.updateAliasPromised(params); | ||
|
||
} else { | ||
|
||
// Create New Alias | ||
|
||
SUtils.sDebug(`"${_this.evt.options.stage} - ${_this.evt.options.region} - ${_this.function.name}": Creating New Lambda Alias for version - ${_this.lambdaVersion}`); | ||
|
||
let params = { | ||
FunctionName: _this.lambda.FunctionName, /* required */ | ||
FunctionVersion: _this.lambdaVersion, /* required */ | ||
Name: _this.lambdaAlias, /* required */ | ||
Description: 'Project: ' | ||
+ _this.project.name | ||
+ ' Stage: ' | ||
+ _this.evt.options.stage | ||
}; | ||
|
||
return _this.Lambda.createAliasPromised(params); | ||
} | ||
}) | ||
.then(function(data) { | ||
|
||
// Save Alias | ||
_this.lambdaAliasArn = data.AliasArn; | ||
}); | ||
} | ||
} | ||
|
||
return( CodeDeployLambdaPython2 ); | ||
}; |