Skip to content
This repository has been archived by the owner on Sep 25, 2018. It is now read-only.

tcbyrd/probot-lambda

Repository files navigation

👋 Archiving this in favor of the new extension published in the Probot Org that doesn't use babel/webpack, and builds off the move to TypeScript in Probot 7: https://github.com/probot/serverless-lambda

Feel free to test that out and open issues over there!

Click here for the old README ## Probot on Lambda

:octocat: + 🤖 + Serverless = ❤️

This is a demonstration for how to run the Probot framework on Lambda. Due the event driven nature of bots, running Probot in a "serverless" environment can make a lot of sense. The GitHub App's Webhook just needs to point to a URL on API Gateway to receive the events, and Lambda will run the bot's logic without having to manage servers.

Demo Setup

  1. First clone this repository and install the dependencies:
$ git clone https://github.com/tcbyrd/probot-lambda.git
$ npm install
  1. Install and configure the Serverless CLI:
$ npm install -g serverless

Note: Also ensure your AWS Credentials are configured. See the Serverless Docs for more info.

  1. Register a new GitHub App and use the information from this app to configure your function's environment:
  • Create the 'Webhook Secret' and assign it to an environment variable with the name WEBHOOK_SECRET

  • On your app's Basic info screen, find the ID and assign this to an environment variable with the name APP_ID

    • Note: There are two main ways to assign environment variables programmatically for Serverless Lambda functions:
    # Example
    aws lambda update-function-configuration --function-name lambda-probot-dev-probot --environment "Variables={APP_ID=0000,WEBHOOK_SECRET=development}"
  • Put the generated Private Key in the root directory with the filename private-key.pem

  1. Deploy the function to AWS with the Serverless CLI:
$ serverless deploy
  1. Once deployed, an API Gateway endpoint will be generated automatically. Use this as the Webhook URL in the GitHub App made above.
endpoints:
  POST - https://9zzzz99999.execute-api.us-east-1.amazonaws.com/dev/probot

This demo app is a simple auto-responder plugin that sends a nice thank you message when anyone opens an issue on the installed repository.

Architecture

Probot's Event Emitter

The Robot class in Probot implements an event emitter already, so getting this wired up to Lambda is relatively simple. Calling webhook.emit directly, you can pass the data coming from the HTTP Request directly to your Probot plugin:

module.exports.probotHandler = function (event, context, callback) {
  const e = event.headers['X-GitHub-Event']
  probot.robot.webhook.emit(e, {
    event: e,
    id: event.headers['X-GitHub-Delivery'],
    payload: JSON.parse(event.body)
  })
}

There are a few different tools that help with testing to deploying serverless applications to AWS, but in general they all do a few things:

  1. Package your code and dependencies in a .zip file and upload it to S3
  2. Provision supporting services such as API Gateway, S3, and CloudWatch Logging using templates.
  3. Give you a way to test functions locally.

The Serverless Framework is what's being demonstrated here, chosen mainly because of its solid documentation, ecosystem of plugins, and its active community.

If you've never built a web application with serverless architectures, the important thing to understand is that Lambda functions are only a piece of that puzzle. There are a number of events that can trigger a function. For web applications, you'll also need to provision an API Gateway endpoint that triggers the function. The Serverless Framework handles this for you by allowing you to specify the http endpoint and method in serverless.yml as an event handler for the Probot function.

By default, Serverless uses Lambda-Proxy, meaning it passes the entire HTTP request to the handler as an event. This allows Probot to process the headers and payload of the webhook just like it does on the server. However, it does transform the body of the request, so the payload must be parsed in the function before being processed by your plugin. Hence, the presence of payload: JSON.parse(event.body) in the example above.

Supporting async/await in AWS Lambda

Probot requires NodeJS v7.7 and makes use of async/await. Since Lambda's NodeJS runtime currently uses v6.10, it doesn't natively handle this syntax. Luckily, there are some tools to help with this.

There's a lot to these tools that you can learn from the above links, but the TL;DR is this: Webpack looks at all the NodeJS modules in the root directory (in this case, skipping the entire node_modules directory) and bundles them into a single file.

While it's loading the modules, it can also transform them with Babel to work with older versions of Javascript. Generally used on the front-end to take advantage of newer features in JS in older browsers, this is also useful on the server for supporting the same new features in older versions of NodeJS.

The serverless-webpack plugin allows you to specify this transpilation step in the serverless.yml file. This makes it possible to keep using async/await in Probot and your Probot plugins, transpile the functions to work with NodeJS v6.10, and deploy to Lambda with a single command: sls deploy.

The specific webpack configuration is located in webpack.config.js and it uses .babelrc to specify the babel presets and plugins. Additionally, to keep from having to bundle all of Probot's dependencies, it ignores the node_modules folder and instead bundles Probot from the root directory. This helps mitigate bugs that sometimes get introduced when attempting to bundle modules intended for server-side use.