Skip to content

Instantly share code, notes, and snippets.

@Sleavely
Created November 2, 2020 23:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Sleavely/174dbf5d0642ef86b3f7263b2f792c3d to your computer and use it in GitHub Desktop.
Save Sleavely/174dbf5d0642ef86b3f7263b2f792c3d to your computer and use it in GitHub Desktop.
A Serverless extension for orchestrating API->Lambda setups from your Swagger/OpenAPI definitions. This workflow encourages that the documentation is kept up-to-date.
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment