UNPKG

@pulumi/aws-serverless

Version:

Pulumi Amazon Web Services (AWS) Serverless Components.

203 lines 8.99 kB
"use strict"; // Copyright 2016-2018, Pulumi Corporation. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. Object.defineProperty(exports, "__esModule", { value: true }); const aws = require("@pulumi/aws"); const pulumi = require("@pulumi/pulumi"); const function_1 = require("./function"); const utils_1 = require("./utils"); class API extends pulumi.ComponentResource { constructor(name, args, opts) { super("aws-serverless:apigateway:API", name, {}, opts); let swaggerString; let swaggerSpec; let lambdas; if (args.swaggerSpec) { swaggerString = pulumi.output(args.swaggerSpec); lambdas = {}; } else if (args.routes) { const [spec, routeLambdas] = swaggerSpecFromRoutes(name, args.routes); swaggerSpec = spec; swaggerString = createSwaggerString(spec); lambdas = routeLambdas; } else { throw new Error("RestAPI must specify either `routes` or `swaggerSpec` options to configure the RestAPI."); } const stageName = args.stageName || "stage"; // Create the API Gateway Rest API, using a swagger spec. this.restAPI = new aws.apigateway.RestApi(name, { body: swaggerString, }, { parent: this }); // Create a deployment of the Rest API. this.deployment = new aws.apigateway.Deployment(name, { restApi: this.restAPI, // Note: Set to empty to avoid creating an implicit stage, we'll create it explicitly below instead. stageName: "", // Note: We set `variables` here because it forces recreation of the Deployment object // whenever the body hash changes. Because we use a blank stage name above, there will // not actually be any stage created in AWS, and thus these variables will not actually // end up anywhere. But this will still cause the right replacement of the Deployment // when needed. The Stage allocated below will be the stable stage that always points // to the latest deployment of the API. variables: { version: swaggerString.apply(utils_1.sha1hash), }, }, { parent: this }); // Expose the URL that the API is served at. this.url = this.deployment.invokeUrl.apply(url => url + stageName + "/"); // Ensure that the permissions allow the API Gateway to invoke the lambdas. const permissions = []; if (swaggerSpec) { for (const path of Object.keys(swaggerSpec.paths)) { for (let method of Object.keys(swaggerSpec.paths[path])) { const methodAndPath = method + ":" + path; const lambda = lambdas[methodAndPath]; if (lambda) { if (method === "x-amazon-apigateway-any-method") { method = "*"; } else { method = method.toUpperCase(); } const permissionName = name + "-" + utils_1.sha1hash(methodAndPath); const invokePermission = new aws.lambda.Permission(permissionName, { action: "lambda:invokeFunction", function: lambda, principal: "apigateway.amazonaws.com", // We give permission for this function to be invoked by any stage at the given method and // path on the API. We allow any stage instead of encoding the one known stage that will be // deployed by Pulumi because the API Gateway console "Test" feature invokes the route // handler with the fake stage `test-invoke-stage`. sourceArn: this.deployment.executionArn.apply(arn => arn + "*/" + method + path), }, { parent: this }); permissions.push(invokePermission); } } } } // Create a stage, which is an addressable instance of the Rest API. Set it to point at the latest deployment. this.stage = new aws.apigateway.Stage(name, { restApi: this.restAPI, deployment: this.deployment, stageName: stageName, }, { parent: this, dependsOn: permissions }); this.registerOutputs({ url: this.url, }); } } exports.API = API; function swaggerSpecFromRoutes(name, routes) { const swagger = createBaseSpec(name); const lambdas = registerRoutes(this, name, routes, swagger); return [swagger, lambdas]; } function createBaseSpec(apiName) { return { swagger: "2.0", info: { title: apiName, version: "1.0" }, paths: {}, "x-amazon-apigateway-binary-media-types": ["*/*"], }; } function registerRoutes(parent, apiName, routes, swagger) { const lambdas = {}; for (const route of routes) { const method = swaggerMethod(route.method); const lambda = function_1.createLambdaFunction(apiName + utils_1.sha1hash(method + ":" + route.path), route.handler); lambdas[method + ":" + route.path] = lambda; if (!swagger.paths[route.path]) { swagger.paths[route.path] = {}; } swagger.paths[route.path][method] = createPathSpecLambda(lambda); } return lambdas; } function swaggerMethod(method) { switch (method.toLowerCase()) { case "get": case "put": case "post": case "delete": case "options": case "head": case "patch": return method.toLowerCase(); case "any": return "x-amazon-apigateway-any-method"; default: throw new Error("Method not supported: " + method); } } function createPathSpecLambda(lambda) { const region = aws.config.requireRegion(); const uri = lambda.arn.apply(lambdaARN => `arn:aws:apigateway:${region}:lambda:path/2015-03-31/functions/${lambdaARN}/invocations`); return { "x-amazon-apigateway-integration": { uri: uri, passthroughBehavior: "when_no_match", httpMethod: "POST", type: "aws_proxy", }, }; } function createSwaggerString(spec) { const pathsOutput = utils_1.outputFromObject(spec.paths, methods => utils_1.outputFromObject(methods, resolveOperationDependencies)); // After all values have settled, we can produce the resulting string. return pathsOutput.apply(paths => JSON.stringify({ swagger: spec.swagger, info: spec.info, paths: paths, "x-amazon-apigateway-binary-media-types": spec["x-amazon-apigateway-binary-media-types"], // Map paths the user doesn't have access to as 404. // http://docs.aws.amazon.com/apigateway/latest/developerguide/supported-gateway-response-types.html "x-amazon-apigateway-gateway-responses": { "MISSING_AUTHENTICATION_TOKEN": { "statusCode": 404, "responseTemplates": { "application/json": "{\"message\": \"404 Not found\" }", }, }, "ACCESS_DENIED": { "statusCode": 404, "responseTemplates": { "application/json": "{\"message\": \"404 Not found\" }", }, }, }, })); function resolveOperationDependencies(op) { return resolveIntegrationDependencies(op["x-amazon-apigateway-integration"]).apply(integration => ({ parameters: op.parameters, responses: op.responses, "x-amazon-apigateway-integration": integration, })); } function resolveIntegrationDependencies(op) { return pulumi.all([op.uri, op.credentials, op.connectionId]).apply(([uri, credentials, connectionId]) => ({ requestParameters: op.requestParameters, passthroughBehavior: op.passthroughBehavior, httpMethod: op.httpMethod, type: op.type, responses: op.responses, connectionType: op.connectionType, uri: uri, credentials: credentials, connectionId: connectionId, })); } } //# sourceMappingURL=api.js.map