express-lambda
Version:
Make AWS lambda behave like an express app
231 lines (215 loc) • 8.17 kB
JavaScript
"use strict";
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var aws = require("aws-sdk");
var async = require("async");
var awsResponse = require("./aws_response");
var integrationTemplate = require("./templates/integration");
var integrationResponses = require("./templates/integration_responses");
var selectionPatterns = require("./selection_patterns");
var log = require("./log");
var _ = require("underscore")._;
var statusCodesMap = {
"200": {
selectionPattern: null,
responseTemplates: {
"application/json": integrationResponses.standard
}
},
"201": {
selectionPattern: selectionPatterns.regex["201"],
responseTemplates: {
"application/json": ""
}
},
"301": {
selectionPattern: selectionPatterns.regex["301"],
responseTemplates: {
"application/json": ""
}
},
"302": {
selectionPattern: selectionPatterns.regex["302"],
responseTemplates: {
"application/json": ""
}
},
"404": {
selectionPattern: selectionPatterns.regex["404"],
responseTemplates: {
"application/json": integrationResponses.error
}
}
};
var methodResponsesParameters = {
"200": null,
"201": {
"method.response.header.Location": true
},
"301": {
"method.response.header.Location": true
},
"302": {
"method.response.header.Location": true
},
"404": null
};
var integrationResponsesParameters = {
"200": null,
"201": {
"method.response.header.Location": "integration.response.body.errorMessage"
},
"301": {
"method.response.header.Location": "integration.response.body.errorMessage"
},
"302": {
"method.response.header.Location": "integration.response.body.errorMessage"
},
"404": null
};
var ApiGatewayResource = function () {
function ApiGatewayResource(apiGw, parentResourceId, methods, path, fullpath) {
_classCallCheck(this, ApiGatewayResource);
this._apiGw = apiGw;
this._methods = methods;
this._path = path;
this._fullpath = fullpath;
this._region = process.env.AWS_REGION;
this._awsApiGw = new aws.APIGateway({ region: this._region });
this._awsLambda = new aws.Lambda({ region: this._region });
this.resourceId = null;
this.parentResourceId = parentResourceId;
}
_createClass(ApiGatewayResource, [{
key: "build",
value: function build(cb) {
var _this = this;
var tasks = [this._createResource.bind(this)];
if (this._methods.length) {
log.info("Building " + this._methods.join(", ") + " " + this._fullpath);
var methodTasks = this._methods.map(function (m) {
return [_this._createMethod.bind(_this, m), _this._createMethodResponses.bind(_this, m)];
});
tasks = tasks.concat(_.flatten(methodTasks));
} else {
tasks = tasks.concat(function (cb) {
awsResponse.log("Skipping method and method response creation");
cb();
});
}
async.series(tasks, cb);
}
}, {
key: "integrateToLambda",
value: function integrateToLambda(lambda, cb) {
var _this2 = this;
log.info("Integrating " + this._methods.join(", ") + " " + this._fullpath + " to lambda " + lambda.name);
var tasks = _.flatten(this._methods.map(function (m) {
return [_this2._createIntegration.bind(_this2, m, lambda), _this2._createIntegrationResponse.bind(_this2, m), _this2._authorizeLambdaInvocation.bind(_this2, m, lambda, process.env.AWS_ENVIRONMENT), _this2._authorizeLambdaInvocation.bind(_this2, m, lambda, "*")];
}));
async.series(tasks, cb);
}
}, {
key: "_createIntegration",
value: function _createIntegration(method, lambda, cb) {
// note: lambda functions must be called with a POST integrationHttpMethod
this._awsApiGw.putIntegration({
restApiId: this._apiGw.id,
resourceId: this.resourceId,
httpMethod: method,
integrationHttpMethod: "POST",
type: "AWS",
uri: this._lambdaIntegrationUri(lambda),
requestTemplates: {
"application/json": integrationTemplate
}
}, awsResponse.cb("putIntegration", cb));
}
}, {
key: "_authorizeLambdaInvocation",
value: function _authorizeLambdaInvocation(method, lambda, stage, cb) {
this._awsLambda.addPermission({
Action: "lambda:InvokeFunction",
FunctionName: lambda.name,
Principal: "apigateway.amazonaws.com",
StatementId: "apigateway-" + this._apiGw.name + "-" + +new Date(),
SourceArn: "arn:aws:execute-api:" + this._region + ":" + this._apiGw.accountNum + ":" + this._apiGw.id + "/" + stage + "/" + method + this._fullpath
}, awsResponse.cb("addPermission", cb));
}
}, {
key: "_createResource",
value: function _createResource(cb) {
var _this3 = this;
if (this._path) {
this._awsApiGw.createResource({
restApiId: this._apiGw.id,
parentId: this.parentResourceId,
pathPart: this._path
}, awsResponse.cb("createResource", cb, function (data) {
_this3.resourceId = data.id;
}));
} else {
// when no path is specified, we don't need to create a resource - using the top level
awsResponse.log("Skipping resource creation");
this.resourceId = this.parentResourceId;
cb();
}
}
}, {
key: "_createMethod",
value: function _createMethod(method, cb) {
this._awsApiGw.putMethod({
restApiId: this._apiGw.id,
resourceId: this.resourceId,
httpMethod: method,
authorizationType: "NONE"
}, awsResponse.cb("putMethod", cb));
}
}, {
key: "_createIntegrationResponse",
value: function _createIntegrationResponse(method, cb) {
var _this4 = this;
var requests = Object.keys(statusCodesMap).map(function (code) {
return function (cb) {
_this4._awsApiGw.putIntegrationResponse({
restApiId: _this4._apiGw.id,
resourceId: _this4.resourceId,
httpMethod: method,
statusCode: code,
responseTemplates: statusCodesMap[code].responseTemplates,
selectionPattern: statusCodesMap[code].selectionPattern,
responseParameters: integrationResponsesParameters[code]
}, awsResponse.cb("putIntegrationResponse-" + code, cb));
};
});
async.series(requests, cb);
}
}, {
key: "_createMethodResponses",
value: function _createMethodResponses(method, cb) {
var _this5 = this;
var requests = Object.keys(statusCodesMap).map(function (code) {
return function (cb) {
_this5._awsApiGw.putMethodResponse({
restApiId: _this5._apiGw.id,
resourceId: _this5.resourceId,
httpMethod: method,
statusCode: code,
responseModels: {
"application/json": "Empty"
},
responseParameters: methodResponsesParameters[code]
}, awsResponse.cb("putMethodResponse-" + code, cb));
};
});
async.series(requests, cb);
}
}, {
key: "_lambdaIntegrationUri",
value: function _lambdaIntegrationUri(lambda) {
return "arn:aws:apigateway:" + this._region + ":lambda:path/2015-03-31/functions/arn:aws:lambda:" + this._region + ":" + this._apiGw.accountNum + ":function:" + lambda.name + "/invocations";
}
}]);
return ApiGatewayResource;
}();
module.exports = ApiGatewayResource;