UNPKG

serverless

Version:

Serverless Framework - Build web, mobile and IoT applications with serverless architectures using AWS Lambda, Azure Functions, Google CloudFunctions & more

313 lines (288 loc) • 9.95 kB
'use strict'; /* eslint-disable global-require */ const BbPromise = require('bluebird'); const _ = require('lodash'); const validate = require('./lib/validate'); const compileRestApi = require('./lib/restApi'); const compileApiKeys = require('./lib/apiKeys'); const compileUsagePlan = require('./lib/usagePlan'); const compileUsagePlanKeys = require('./lib/usagePlanKeys'); const compileResources = require('./lib/resources'); const compileCors = require('./lib/cors'); const compileMethods = require('./lib/method/index'); const compileAuthorizers = require('./lib/authorizers'); const compileDeployment = require('./lib/deployment'); const compilePermissions = require('./lib/permissions'); const compileStage = require('./lib/stage'); const getMethodAuthorization = require('./lib/method/authorization'); const getMethodIntegration = require('./lib/method/integration'); const getMethodResponses = require('./lib/method/responses'); function caseInsensitive(str) { return { type: 'string', regexp: new RegExp(`^${str}$`, 'i').toString() }; } const contentHandlingSchema = { enum: ['CONVERT_TO_BINARY', 'CONVERT_TO_TEXT'] }; const allowedMethods = ['GET', 'POST', 'PUT', 'PATCH', 'OPTIONS', 'HEAD', 'DELETE', 'ANY']; const methodPattern = new RegExp(`^(?:\\*|${allowedMethods.join('|')})$`, 'i'); const methodPathPattern = new RegExp(`^(?:\\*|(${allowedMethods.join('|')}) (\\/\\S*))$`, 'i'); const requestParametersSchema = { type: 'object', additionalProperties: { anyOf: [ { type: 'boolean' }, { type: 'object', properties: { required: { type: 'boolean' }, mappedValue: { type: 'string' }, }, additionalProperties: false, }, ], }, }; const authorizerSchema = { anyOf: [ { type: 'string' }, { type: 'object', properties: { arn: { $ref: '#/definitions/awsArn' }, authorizerId: { $ref: '#/definitions/awsCfInstruction' }, claims: { type: 'array', items: { type: 'string' } }, identitySource: { type: 'string' }, identityValidationExpression: { type: 'string' }, managedExternally: { type: 'boolean' }, name: { type: 'string' }, resultTtlInSeconds: { type: 'integer', minimum: 0, maximum: 3600 }, scopes: { type: 'array', items: { type: 'string' } }, type: { anyOf: ['token', 'cognito_user_pools', 'request', 'aws_iam'].map(caseInsensitive), }, }, required: [], additionalProperties: false, }, ], }; const corsSchema = { anyOf: [ { type: 'boolean' }, { type: 'object', properties: { allowCredentials: { type: 'boolean' }, cacheControl: { type: 'string' }, headers: { type: 'array', items: { type: 'string' } }, maxAge: { type: 'integer', minimum: 1 }, methods: { type: 'array', items: { enum: allowedMethods } }, origin: { type: 'string' }, origins: { type: 'array', items: { type: 'string' }, }, }, oneOf: [{ required: ['origin'] }, { required: ['origins'] }], additionalProperties: false, }, ], }; const requestSchema = { type: 'object', properties: { contentHandling: contentHandlingSchema, method: { type: 'string', regexp: methodPattern.toString() }, parameters: { type: 'object', properties: { querystrings: requestParametersSchema, headers: requestParametersSchema, paths: requestParametersSchema, }, additionalProperties: false, }, passThrough: { enum: ['NEVER', 'WHEN_NO_MATCH', 'WHEN_NO_TEMPLATES'] }, schema: { type: 'object', additionalProperties: { type: 'object' }, }, template: { type: 'object', additionalProperties: { type: 'string' }, }, uri: { type: 'string' }, }, additionalProperties: false, }; const responseSchema = { type: 'object', properties: { contentHandling: contentHandlingSchema, headers: { type: 'object', additionalProperties: { type: 'string' }, }, template: { type: 'string' }, statusCodes: { type: 'object', propertyNames: { type: 'string', pattern: '^\\d{3}$', }, additionalProperties: { type: 'object', properties: { headers: { type: 'object', additionalProperties: { type: 'string' }, }, pattern: { type: 'string' }, template: { anyOf: [ { type: 'string' }, { type: 'object', additionalProperties: { type: 'string' }, }, ], }, }, additionalProperties: false, }, }, }, additionalProperties: false, }; class AwsCompileApigEvents { constructor(serverless, options) { this.serverless = serverless; this.options = options; this.provider = this.serverless.getProvider('aws'); this.serverless.configSchemaHandler.defineFunctionEvent('aws', 'http', { anyOf: [ { type: 'string', regexp: methodPathPattern.toString() }, { type: 'object', properties: { async: { type: 'boolean' }, authorizer: authorizerSchema, connectionId: { type: 'string' }, connectionType: { anyOf: ['vpc-link', 'VPC_LINK'].map(caseInsensitive) }, cors: corsSchema, integration: { anyOf: [ 'LAMBDA_PROXY', 'LAMBDA-PROXY', 'LAMBDA', 'AWS', 'AWS_PROXY', 'AWS-PROXY', 'HTTP', 'HTTP_PROXY', 'HTTP-PROXY', 'MOCK', ].map(caseInsensitive), }, method: { type: 'string', regexp: methodPattern.toString() }, operationId: { type: 'string' }, path: { type: 'string', regexp: /^(?:\*|\/?\S*)$/.toString() }, private: { type: 'boolean' }, request: requestSchema, response: responseSchema, }, required: ['path', 'method'], additionalProperties: false, }, ], }); // used for the generated method logical ids (GET, PATCH, PUT, DELETE, OPTIONS, ...) this.apiGatewayMethodLogicalIds = []; Object.assign( this, validate, compileRestApi, compileApiKeys, compileUsagePlan, compileUsagePlanKeys, compileResources, compileCors, compileMethods, compileAuthorizers, compileDeployment, compilePermissions, compileStage, getMethodAuthorization, getMethodIntegration, getMethodResponses ); this.hooks = { 'initialize': () => { if ( this.serverless.service.provider.name === 'aws' && !this.serverless.service.provider.apiName && !_.get(this.serverless.service.provider.apiGateway, 'shouldStartNameWithService') && !_.get(this.serverless.service.provider.apiGateway, 'restApiId') && Object.values(this.serverless.service.functions).some(({ events }) => events.some(({ http }) => http) ) ) { this.serverless._logDeprecation( 'AWS_API_GATEWAY_NAME_STARTING_WITH_SERVICE', 'Starting with next major version, API Gateway naming will be changed from ' + '"{stage}-{service}" to "{service}-{stage}".\n' + 'Set "provider.apiGateway.shouldStartNameWithService" to "true" ' + 'to adapt to the new behavior now.' ); } if ( this.serverless.service.provider.name === 'aws' && (this.serverless.service.provider.apiKeys || this.serverless.service.provider.resourcePolicy || this.serverless.service.provider.usagePlan) ) { this.serverless._logDeprecation( 'AWS_API_GATEWAY_SPECIFIC_KEYS', 'Starting with next major version, API Gateway-specific configuration keys ' + '"apiKeys", "resourcePolicy" and "usagePlan" will be relocated from "provider" ' + 'to "provider.apiGateway"' ); } }, 'package:compileEvents': () => { this.validated = this.validate(); if (this.validated.events.length === 0) { return BbPromise.resolve(); } return BbPromise.bind(this) .then(this.compileRestApi) .then(this.compileResources) .then(this.compileCors) .then(this.compileMethods) .then(this.compileAuthorizers) .then(this.compileDeployment) .then(this.compileApiKeys) .then(this.compileUsagePlan) .then(this.compileUsagePlanKeys) .then(this.compilePermissions) .then(this.compileStage); }, // TODO should be removed once AWS fixes the CloudFormation problems using a separate Stage 'after:deploy:deploy': () => { const getServiceState = require('../../../../lib/getServiceState').getServiceState; const state = getServiceState.call(this); const updateStage = require('./lib/hack/updateStage').updateStage; this.state = state; return updateStage.call(this); }, // TODO should be removed once AWS fixes the removal via CloudFormation 'before:remove:remove': () => { // eslint-disable-next-line no-shadow const validate = require('../../../../lib/validate').validate; // eslint-disable-next-line max-len const disassociateUsagePlan = require('./lib/hack/disassociateUsagePlan') .disassociateUsagePlan; return BbPromise.bind(this).then(validate).then(disassociateUsagePlan); }, }; } } module.exports = AwsCompileApigEvents;