aws-lager
Version:
AWS Lambda / API Gateway / Endpoint Router
207 lines (180 loc) • 6.46 kB
JavaScript
;
const fs = require('fs');
const path = require('path');
const Promise = require('bluebird');
const _ = require('lodash');
const AWS = require('aws-sdk');
const archiver = require('archiver');
const IntegrationDataInjector = require('./integration-data-injector');
const lager = require('lager/lib/lager');
/**
* Constructor function
* @param {Object} spec - base API specification
* @constructor
*/
let Lambda = function Lambda(config) {
this.config = config;
this.config.params = this.config.params || {};
this.config.params = _.assign({
FunctionName: config.identifier,
Handler: 'lambda.handler',
Role: 'PLEASE-CONFIGURE-AN-EXECUTION-ROLE-FOR-' + config.identifier,
Runtime: 'nodejs4.3',
Timeout: 15,
Publish: false
}, this.config.params);
this.config.includeLibs = this.config.includeLibs || [];
_.forEach(this.config.includeLibs, (libDir, index) => {
this.config.includeLibs[index] = path.join(process.cwd(), 'libs', libDir);
});
if (this.config.includeEndpoints) {
this.config.includeLibs.push(path.join(process.cwd(), 'endpoints'));
}
};
/**
* Returns a string representation of a Lambda instance
* @return {string}
*/
Lambda.prototype.toString = function toString() {
return 'Lambda ' + this.config.identifier;
};
/**
* Deploys the lambda in AWS
* @return {Promise<LambdaIntegrationDataInjector>} - the IntegrationDataInjector of the lambda
*/
Lambda.prototype.deploy = function deploy(region, stage, environment) {
console.log(' * Deploy ' + this.config.identifier);
const awsLambda = new AWS.Lambda({ region });
return this.isDeployed(awsLambda)
.then((isDeployed) => {
if (isDeployed) {
// If the function already exists
console.log(' * The lambda ' + this.config.identifier + ' already exists');
return this.update(awsLambda, environment);
} else {
// If error occured because the function does not exists, we create it
console.log(' * The lambda ' + this.config.identifier + ' does not exists');
return this.create(awsLambda, environment);
}
})
.then((data) => {
// Publish a new version
console.log(' * Lambda ' + this.config.identifier + ' deployed');
return this.publishVersion(awsLambda, stage);
})
.then((data) => {
console.log(' * Lambda ' + this.config.identifier + ' published: \x1b[0;36mversion ' + data.Version + '\x1b[0m');
console.log(' * Function ARN: \x1b[0;36m' + data.FunctionArn + '\x1b[0m\n');
return new IntegrationDataInjector(data);
});
};
/**
* Create a zip package for a lambda and provide it's content in a buffer
* @param {String} lambdaPath - path to the function
* @param {Array} dependenciesPaths - directories to include in the the function
* @param {Function} cb
*/
function getPackageBufferCb(lambdaPath, dependenciesPaths, cb) {
dependenciesPaths = dependenciesPaths || [];
var archivePath = '/tmp/' + (new Buffer(lambdaPath).toString('base64')) + '.zip';
var outputStream = fs.createWriteStream(archivePath);
var archive = archiver.create('zip', {});
outputStream.on('close', function() {
fs.readFile(archivePath, cb);
});
archive.on('error', function(e) {
cb(e);
});
archive.pipe(outputStream);
// Add the Lamba code to the archive
archive.directory(lambdaPath, '');
// Add the library code (aka code in common for all lambdas) to the archive
dependenciesPaths.forEach(function(dependencyPath) {
archive.directory(dependencyPath, path.basename(dependencyPath));
});
// Add the application configuration of the environment to the archive
var envConfig = process.env;
envConfig.LAMBDA = true;
archive.append(JSON.stringify(envConfig), { name: 'env_config.json' });
archive.finalize();
}
// @TODO improve this to avoid using Promise.promisify()
/**
* Creates a buffer containing the lambda package
* @return {Promise<Buffer>}
*/
Lambda.prototype.buildPackage = function buildPackage() {
return Promise.promisify(getPackageBufferCb)(this.config.handlerPath, this.config.includeLibs);
};
/* istanbul ignore next */
/**
* Check if the Lambda already exists in AWS
* @return {Promise<Boolean>}
*/
Lambda.prototype.isDeployed = function isDeployed(awsLambda) {
let params = { FunctionName: this.config.params.FunctionName };
return Promise.promisify(awsLambda.getFunction.bind(awsLambda))(params)
.then(() => {
return Promise.resolve(true);
})
.catch((e) => {
if (e.code !== 'ResourceNotFoundException') {
throw(e);
}
return Promise.resolve(false);
});
};
/* istanbul ignore next */
/**
* Create the lambda in AWS
* @return {Promise<Object>} - AWS description of the lambda
*/
Lambda.prototype.create = function create(awsLambda, environment) {
let params = _.cloneDeep(this.config.params);
return Promise.all([this.buildPackage(), lager.retrieveRoleArn(params.Role, environment)])
.spread((buffer, roleArn) => {
params.Code = { ZipFile: buffer };
params.Role = roleArn;
return Promise.promisify(awsLambda.createFunction.bind(awsLambda))(params);
});
};
/* istanbul ignore next */
/**
* Update the lambda in AWS
* @return {Promise<Object>} - AWS description of the lambda
*/
Lambda.prototype.update = function update(awsLambda, environment) {
return this.buildPackage()
.then((buffer) => {
// First, update the code
let codeParams = {
FunctionName: this.config.params.FunctionName,
Publish: this.config.params.Publish,
ZipFile: buffer
};
return [
Promise.promisify(awsLambda.updateFunctionCode.bind(awsLambda))(codeParams),
lager.retrieveRoleArn(this.config.params.Role, environment)
];
})
.spread((codeUpdateResponse, roleArn) => {
// Then, update the configuration
var configParams = _.cloneDeep(this.config.params);
delete configParams.Publish;
configParams.Role = roleArn;
return Promise.promisify(awsLambda.updateFunctionConfiguration.bind(awsLambda))(configParams);
});
};
/* istanbul ignore next */
/**
* Create a new version of the lambda
* @return {Promise<Object>} - AWS description of the lambda
*/
Lambda.prototype.publishVersion = function publishVersion(awsLambda, alias) {
// @TODO set alias and delete previous version
let params = {
FunctionName: this.config.params.FunctionName
};
return Promise.promisify(awsLambda.publishVersion.bind(awsLambda))(params);
};
module.exports = Lambda;