UNPKG

serverless-sam

Version:

Serverless framework plugin to export AWS SAM templates for a service

237 lines (205 loc) 7.7 kB
/* * Copyright 2017 Stefano Buliani (@sapessi) * * 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. */ 'use strict' const Utils = require('../utils'); const DEFAULT_STAGE_NAME = "prod"; class HttpEventConverter { constructor(serverless) { this.serverless = serverless; this.apiDefinition = { swagger: "2.0", info: { title: { Ref: "AWS::StackName" } }, paths: {} }; this.apiResourceName = ""; this.customResources = {}; } getEventType() { return "Api"; } convertEvent(event, targetResourceName) { let http = { path: "", method: "" }; if (typeof(event) === "string") { const params = event.trim().split(" "); if (params.length != 2) { throw new Error("Invalid http event setup: " + event); } http.method = params[0]; http.path = params[1]; } else { http = event; } if (http.path.charAt(0) != "/") { http.path = "/" + http.path; } this.addHttpMethod(http, targetResourceName); this.apiResourceName = Utils.stringToResourceName(this.serverless.service.service); this.customResources[this.apiResourceName] = { Type: "AWS::Serverless::Api", Properties: { StageName: this.serverless.service.provider.stage || DEFAULT_STAGE_NAME, DefinitionBody: this.apiDefinition } }; let returnEvent = { event: { Path: http.path, Method: http.method, RestApiId: { Ref: this.apiResourceName } }, resources: this.customResources }; return returnEvent; } addHttpMethod(httpConfig, targetResourceName) { let httpMethod = httpConfig.method.toLowerCase(); if (!this.apiDefinition.paths[httpConfig.path]) { this.apiDefinition.paths[httpConfig.path] = {}; } if (this.apiDefinition.paths[httpConfig.path][httpMethod] && Object.keys(this.apiDefinition.paths[httpConfig.path][httpMethod]).length > 0) { throw new Error("Error while generating Swagger definition: " + "Method " + httpMethod + " already exists on " + httpConfig.path + " resource"); } let methodConfig = { "x-amazon-apigateway-integration": { httpMethod: "POST", type: "aws_proxy", uri: { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${" + targetResourceName + ".Arn}/invocations" } }, responses: {} } if (httpConfig.cors) { let optionsMethod = this.getDefaultOptionsConfig(); let headers = optionsMethod["x-amazon-apigateway-integration"].responses.default.responseParameters["method.response.header.Access-Control-Allow-Headers"]; let methods = Object.keys(this.apiDefinition.paths[httpConfig.path]).concat([httpMethod]).join(","); let origins = optionsMethod["x-amazon-apigateway-integration"].responses.default.responseParameters["method.response.header.Access-Control-Allow-Origin"]; if (typeof(httpConfig.cors) === "object") { if (httpConfig.cors.origins) { origins = httpConfig.cors.origins; } if (httpConfig.cors.headers && Array.isArray(httpConfig.cors.headers)) { headers += headers.split(",").concat(httpConfig.cors.headers).join(","); } } optionsMethod["x-amazon-apigateway-integration"].responses.default.responseParameters["method.response.header.Access-Control-Allow-Origin"] = "'" + origins + "'"; optionsMethod["x-amazon-apigateway-integration"].responses.default.responseParameters["method.response.header.Access-Control-Allow-Headers"] = "'" + headers + "'"; optionsMethod["x-amazon-apigateway-integration"].responses.default.responseParameters["method.response.header.Access-Control-Allow-Methods"] = "'" + methods + "'"; methodConfig["x-amazon-apigateway-integration"]["responses"] = JSON.parse(JSON.stringify(optionsMethod["x-amazon-apigateway-integration"].responses)); methodConfig.responses = this.getCorsResponses() this.apiDefinition.paths[httpConfig.path]["options"] = optionsMethod; } if (httpConfig.authorizer && typeof(httpConfig.authorizer) === "string") { const authorizerName = this.createAuthorizer(httpConfig.authorizer); let authConfig = {}; authConfig[authorizerName] = []; methodConfig["security"] = [ authConfig ]; } this.apiDefinition.paths[httpConfig.path][httpMethod] = methodConfig; } getApiResourceName() { return Utils.stringToResourceName(this.serverless.service.service); } hasMethods() { if (!this.apiDefinition || !this.apiDefinition.paths) { return false; } return Object.keys(this.apiDefinition.paths).length > 0 } createAuthorizer(name) { if (!this.apiDefinition.securityDefinitions) { this.apiDefinition["securityDefinitions"] = {}; } const authorizerName = Utils.stringToResourceName(name); this.apiDefinition.securityDefinitions[authorizerName] = { type: "apiKey", name : authorizerName, in : "header", "x-amazon-apigateway-authtype": "oauth2", "x-amazon-apigateway-authorizer": { type: "token", authorizerUri: { "Fn::Sub": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${" + authorizerName + ".Arn}/invocations" }, identityValidationExpression: "^x-[a-z]+", authorizerResultTtlInSeconds: 60 } } // set resource policies for the authorizer this.customResources[authorizerName + "ResourcePolicy"] = { Type: "AWS::Lambda::Permission", Properties: { Action: "lambda:InvokeFunction", FunctionName: { "Fn::GetAtt" : [authorizerName, "Arn"] }, Principal: "apigateway.amazonaws.com", SourceAccount: { "Ref" : "AWS::AccountId" } } }; return authorizerName; } getDefaultOptionsConfig() { return { "x-amazon-apigateway-integration": { type: "mock", requestTemplates: { "application/json": "{ \"statusCode\" : 200 }" }, responses: { default: { statusCode: 200, responseParameters: { "method.response.header.Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key", "method.response.header.Access-Control-Allow-Methods": "*", "method.response.header.Access-Control-Allow-Origin": "*" }, responseTemplates: { "application/json": "{}" } } } }, responses: this.getCorsResponses() }; } getCorsResponses() { return { "200": { headers: { "Access-Control-Allow-Headers": { type: "string" }, "Access-Control-Allow-Methods": { type: "string" }, "Access-Control-Allow-Origin": { type: "string" } } } }; } } module.exports = HttpEventConverter;