const ServerlessAWSCloudFormationSubVariables = require('serverless-cloudformation-sub-variables') class ApiOrchestrator { constructor(serverless) { this.serverless = serverless // Register ${lambda:myFunctionKey} so that we can // refer to it from our Swagger/OpenAPI definition this.variableResolvers = { lambda: this.referenceLambda.bind(this), } // Somewhere to store our referenced lambdas across the lifecycle this.referencedKeys = [] this.hooks = { // Prior to packaging we want to inject a fake HTTP event on the referenced function(s) // to piggyback on Serverless' logic for automatically creating API Gateway and their // deployments and allowing SLS to think that the method can be invoked by events. 'after:package:setupProviderConfiguration': () => { // Lets loop through the lambdas we referenced earlier with our // ${lambda:something} syntax and add an HTTP event on them const functions = this.serverless.service.functions for (const functionKey of this.referencedKeys) { const func = functions[functionKey] const hasHttp = this.serverless.utils.isEventUsed([func], 'http') if (hasHttp) continue; functions[functionKey].events.push({ http: { path: 'sample/path', method: 'post', }}) } }, // After Serverless has generated the CloudFormation template we want to // clean it up from APIGateway-methods and -resources since the API definition // will create its own. Also clean up the artificial event. 'aws:package:finalize:mergeCustomProviderResources': () => { const cfnTemplate = this.serverless.service.provider.compiledCloudFormationTemplate const cfnResourcesToBeRemoved = ['AWS::ApiGateway::Method', 'AWS::ApiGateway::Resource'] for (const [key, resource] of Object.entries(cfnTemplate.Resources)) { // Filter unwanted resources if (cfnResourcesToBeRemoved.includes(resource.Type)) { delete cfnTemplate.Resources[key] } // Clean up refs to those resources if(resource.Type === 'AWS::ApiGateway::Deployment') { delete resource.DependsOn } } // Remove the artifical http event to avoid confusion in stored state // (manifests in e.g. post-deploy endpoint list) for (const functionKey of this.referencedKeys) { const func = this.serverless.service.functions[functionKey] func.events = func.events.filter((event) => { if (event.http) return false return true }) } // Now before finishing up, lets replace all the fake Lambda references we added this.convertCfnVariables() }, // Finally, if someone for some reason uses `serverless info`.. 'before:aws:info:displayEndpoints': () => { //TODO: For display purposes we should show the paths from Swagger const functions = this.serverless.service.functions for (const functionKey of this.referencedKeys) { functions[functionKey].events.push({ http: { path: '{?}', method: 'POTATO', }}) } } } } async referenceLambda(src) { const functionKey = src.slice('lambda:'.length) const logicalId = this.serverless.getProvider('aws').naming.getLambdaLogicalId(functionKey) this.referencedKeys.push(functionKey) // this relies on another plugin to magically turn this string into // an actual reference in CloudFormation: serverless-cloudformation-sub-variables return `#{${logicalId}}` } convertCfnVariables() { const cfnVarConverter = new ServerlessAWSCloudFormationSubVariables(this.serverless) cfnVarConverter.convertSubVariables() } } module.exports = exports = ApiOrchestrator