UNPKG

express-lambda

Version:

Make AWS lambda behave like an express app

231 lines (215 loc) 8.17 kB
"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;