Skip to content

Adding support for S3 function code updates. Updated mocks and README #95

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
12 changes: 11 additions & 1 deletion test/unit/deploy_task_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ var deployTaskTest = {};

var awsSDKMock,
lambdaAPIMock,
s3APIMock,
defaultGruntConfig,
proxyAgentMock;

Expand Down Expand Up @@ -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(),
Expand All @@ -80,7 +89,8 @@ deployTaskTest.setUp = function(done) {
},
Lambda: function(params) {
return lambdaAPIMock;
}
},
S3: s3APIMock
};

proxyAgentMock = sinon.spy();
Expand Down
112 changes: 88 additions & 24 deletions utils/deploy_task.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {};

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

Expand Down Expand Up @@ -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.');
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;