diff --git a/README.md b/README.md index cec1a5b..7a6fa8e 100644 --- a/README.md +++ b/README.md @@ -327,6 +327,31 @@ grunt.initConfig({ You could then run `grunt lambda_package lambda_deploy` and it'll automatically create the package and deploy it without having to specify a package name. +##### options.deploy_mode +Type: `String` +Default value: `zip` + +Can be `zip` or `s3`. Determines how the code will be uploaded to lambda - via direct upload or via S3. For larger archives, +S3 may be more suitable. + +##### options.S3bucketName +Type: `String` +Default value: `null` + +Mandatory if `options.deploy_mode='s3'`. S3 bucket to upload code archive to. + +##### options.S3Prefix +Type: `String` +Default value: `` + +Used only for `options.deploy_mode='s3'`. S3 bucket prefix to be used when uploading code archive. + +##### options.S3MultiUploadPartSize +Type: `String` +Default value `5mb` + +For larger archives, determines chunk size for s3 multi-upload. Minimum value is '5mb'. + ##### options.profile Type: `String` Default value: `null` diff --git a/package.json b/package.json index aa9e517..a7f65f0 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,8 @@ "aws-sdk": "~2.2.32", "proxy-agent": "latest", "npm": "^2.10.0", - "q": "^1.4.1" + "q": "^1.4.1", + "bytes": "^2.4.0" }, "devDependencies": { "grunt-contrib-jshint": "^0.9.2", diff --git a/test/unit/deploy_task_test.js b/test/unit/deploy_task_test.js index d584f18..ffb16d8 100644 --- a/test/unit/deploy_task_test.js +++ b/test/unit/deploy_task_test.js @@ -40,6 +40,7 @@ var deployTaskTest = {}; var awsSDKMock, lambdaAPIMock, + s3APIMock, defaultGruntConfig, proxyAgentMock; @@ -70,6 +71,14 @@ deployTaskTest.setUp = function(done) { updateAlias: sinon.stub().callsArgWithAsync(1, null, {}) }; + s3APIMock = { + ManagedUpload : function(params){ + return { + send : sinon.stub(), + } + } + }; + awsSDKMock = { SharedIniFileCredentials: sinon.stub(), EC2MetadataCredentials: sinon.stub(), @@ -80,7 +89,8 @@ deployTaskTest.setUp = function(done) { }, Lambda: function(params) { return lambdaAPIMock; - } + }, + S3: s3APIMock }; proxyAgentMock = sinon.spy(); diff --git a/utils/deploy_task.js b/utils/deploy_task.js index fd6c675..143dff2 100644 --- a/utils/deploy_task.js +++ b/utils/deploy_task.js @@ -14,6 +14,7 @@ var AWS = require('aws-sdk'); var Q = require('q'); var arnParser = require('./arn_parser'); var dateFacade = require('./date_facade'); +var bytes = require('bytes'); var deployTask = {}; @@ -39,18 +40,22 @@ deployTask.getHandler = function (grunt) { aliases: null, enablePackageVersionAlias: false, subnetIds: null, - securityGroupIds: null + securityGroupIds: null, + deploy_mode: 'zip', + S3bucketName: null, + S3Prefix: '', + S3MultiUploadPartSize: '5mb' }); - + if (options.profile !== null) { var credentials = new AWS.SharedIniFileCredentials({profile: options.profile}); AWS.config.credentials = credentials; } //Adding proxy if exists - if(process.env.https_proxy !== undefined) { + if (process.env.https_proxy !== undefined) { AWS.config.update({ - httpOptions: { agent: proxy(process.env.https_proxy) } + httpOptions: {agent: proxy(process.env.https_proxy)} }); } @@ -83,6 +88,12 @@ deployTask.getHandler = function (grunt) { var package_version = grunt.config.get('lambda_deploy.' + this.target + '.version'); var package_name = grunt.config.get('lambda_deploy.' + this.target + '.package_name'); var archive_name = grunt.config.get('lambda_deploy.' + this.target + '.archive_name'); + var deploy_mode = grunt.config.get('lambda_deploy.' + this.target + '.deploy_mode'); + var s3_bucket_name = grunt.config.get('lambda_deploy.' + this.target + '.S3bucketName'); + var s3_prefix = grunt.config.get('lambda_deploy.' + this.target + '.S3Prefix') || ''; + var s3_part_size = grunt.config.get('lambda_deploy.' + this.target + '.S3MultiUploadPartSize') || '5mb'; + var is_s3_upload = deploy_mode == 's3'; + var s3_key = is_s3_upload ? s3_prefix + path.basename(deploy_package) : undefined; if (deploy_arn === null && deploy_function === null) { grunt.fail.warn('You must specify either an arn or a function name.'); @@ -150,10 +161,10 @@ deployTask.getHandler = function (grunt) { } if (options.subnetIds !== null && options.securityGroupIds !== null) { - configParams.VpcConfig = { - SubnetIds : options.subnetIds, - SecurityGroupIds : options.securityGroupIds - }; + configParams.VpcConfig = { + SubnetIds: options.subnetIds, + SecurityGroupIds: options.securityGroupIds + }; } var updateConfig = function (func_name, func_options) { @@ -248,38 +259,91 @@ deployTask.getHandler = function (grunt) { } }; - grunt.log.writeln('Uploading...'); - fs.readFile(deploy_package, function (err, data) { - if (err) { - grunt.fail.warn('Could not read package file (' + deploy_package + '), verify the lambda package ' + - 'location is correct, and that you have already created the package using lambda_package.'); + var uploadPackageToS3 = function (package_data) { + var upload_params = { + Bucket: s3_bucket_name, + Key: s3_key, + Body: package_data + }, + managed_upload = new AWS.S3.ManagedUpload({params: upload_params, partSize: bytes(s3_part_size)}), + deferred = Q.defer(); + + if (is_s3_upload) { + managed_upload.send(function (err) { + if (err) { + grunt.fail.warn('S3 Upload failed: ' + err); + deferred.reject(); + } + + grunt.log.writeln('S3 Upload success'); + deferred.resolve(); + }); + } else { + deferred.resolve(); } - var codeParams = { - FunctionName: deploy_function, - ZipFile: data - }; + return deferred.promise; + }; - lambda.updateFunctionCode(codeParams, function (err, data) { + var updateFunctionCode = function (code_params) { + var deferred = Q.defer(); + lambda.updateFunctionCode(code_params, function (err, data) { if (err) { grunt.fail.warn('Package upload failed, check you have lambda:UpdateFunctionCode permissions and that your package is not too big to upload.'); + deferred.reject(); } grunt.log.writeln('Package deployed.'); + deferred.resolve(); + }); + return deferred.promise; + }; + + grunt.log.writeln('Uploading...'); + fs.readFile(deploy_package, function (err, data) { + if (err) { + grunt.fail.warn('Could not read package file (' + deploy_package + '), verify the lambda package ' + + 'location is correct, and that you have already created the package using lambda_package.'); + } + var code_params; + + if (is_s3_upload) { + code_params = { + FunctionName: deploy_function, + S3Bucket: s3_bucket_name, + S3Key: s3_key + }; + } else { + code_params = { + FunctionName: deploy_function, + ZipFile: data + }; + } + + uploadPackageToS3(data).then(function () { + return updateFunctionCode(code_params); + }).then(function () { updateConfig(deploy_function, configParams) - .then(function () {return createVersion(deploy_function);}) - .then(function () {return setAliases(deploy_function);}) - .then(function () {return setPackageVersionAlias(deploy_function);}) + .then(function () { + return createVersion(deploy_function); + }) + .then(function () { + return setAliases(deploy_function); + }) + .then(function () { + return setPackageVersionAlias(deploy_function); + }) .then(function () { done(true); }).catch(function (err) { - grunt.fail.warn('Uncaught exception: ' + err.message); - }); + grunt.fail.warn('Uncaught exception: ' + err.message); + }); }); }); }); }; -}; +} + module.exports = deployTask;