-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BREAKING CHANGE: The package was renamed from `@probot/serverless-lambda` to `@probot/adapter-aws-lambda-serverless` for consistency with the other adapters BREAKING CHANGE: Usage changed Before ```js // handler.js const { serverless } = require("@probot/serverless-lambda"); const appFn = require("./"); module.exports.probot = serverless(appFn); ``` After ```js const { createLambdaFunction, createProbot } = require("@probot/adapter-aws-lambda-serverless"); const appFn = require("./"); module.exports.webhooks = createLambdaFunction(appFn, { probot: createProbot() }); ```
- Loading branch information
Showing
11 changed files
with
4,639 additions
and
9,307 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
ISC License | ||
|
||
Copyright (c) 2021 Probot Contributors | ||
|
||
Permission to use, copy, modify, and/or distribute this software for any | ||
purpose with or without fee is hereby granted, provided that the above | ||
copyright notice and this permission notice appear in all copies. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | ||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | ||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | ||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | ||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | ||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | ||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,49 +1,60 @@ | ||
## AWS Lambda Extension for Probot | ||
# `@probot/adapter-aws-lambda-serverless` | ||
|
||
A [Probot](https://github.com/probot/probot) extension to make it easier to run your Probot Apps in AWS Lambda. | ||
> Adapter to run a [Probot](https://probot.github.io/) application function in [AWS Lambda](https://aws.amazon.com/lambda/) using the [Serverless Framework](https://github.com/serverless/serverless) | ||
[![Build Status](https://github.com/probot/adapter-aws-lambda-serverless/workflows/Test/badge.svg)](https://github.com/probot/adapter-aws-lambda-serverless/actions) | ||
|
||
## Usage | ||
|
||
```shell | ||
$ npm install @probot/serverless-lambda | ||
npm install @probot/adapter-aws-lambda-serverless | ||
``` | ||
|
||
```javascript | ||
// handler.js | ||
const { serverless } = require("@probot/serverless-lambda"); | ||
const { | ||
createLambdaFunction, | ||
createProbot, | ||
} = require("@probot/adapter-aws-lambda-serverless"); | ||
const appFn = require("./"); | ||
module.exports.probot = serverless(appFn); | ||
module.exports.webhooks = createLambdaFunction(appFn, { | ||
probot: createProbot(), | ||
}); | ||
``` | ||
|
||
## Configuration | ||
|
||
This package moves the functionality of `probot run` into a handler suitable for usage on AWS Lambda + API Gateway. Follow the documentation on [Environment Configuration](https://probot.github.io/docs/configuration/) to setup your app's environment variables. You can add these to `.env`, but for security reasons you may want to use the [AWS CLI](https://aws.amazon.com/cli/) or [Serverless Framework](https://github.com/serverless/serverless) to set Environment Variables for the function so you don't have to include any secrets in the deployed package. | ||
|
||
To use `.env` files with the [Serverless Framework](https://github.com/serverless/serverless), you can install the [serverless-dotenv-plugin](https://www.serverless.com/plugins/serverless-dotenv-plugin). This will take care of keeping your secrets out of your deployed package. | ||
|
||
### Serverless dotenv plugin usage | ||
|
||
```yaml | ||
plugins: | ||
- serverless-dotenv-plugin # Load .env as environment variables | ||
You need to add [environment variables to configure Probot](https://probot.github.io/docs/configuration/) to your Lambda function. If you use the [Serverless App](https://app.serverless.com/), you can add parameters for `APP_ID`, `PRIVATE_KEY`, `WEBHOOK_SECRET`, the use these parameters in `serverless.yaml`. | ||
|
||
```yml | ||
provider: | ||
name: aws | ||
runtime: nodejs12.x | ||
lambdaHashingVersion: 20201221 | ||
environment: | ||
APP_ID: ${param:APP_ID} | ||
PRIVATE_KEY: ${param:PRIVATE_KEY} | ||
WEBHOOK_SECRET: ${param:WEBHOOK_SECRET} | ||
NODE_ENV: production | ||
LOG_LEVEL: debug | ||
|
||
functions: | ||
webhooks: | ||
handler: handler.webhooks | ||
events: | ||
- httpApi: | ||
path: /api/github/webhooks | ||
method: post | ||
``` | ||
For the private key, since AWS environment variables cannot be multiline strings, you could [Base64 encode](https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings) the `.pem` file you get from the GitHub App or use [KMS](https://aws.amazon.com/kms/) to encrypt and store the key. | ||
|
||
## Differences from `probot run` | ||
|
||
#### Local Development | ||
Make sure to configure your GitHub App registration's webhook URL to `<your lambda's URL>/api/github/webhooks`. | ||
|
||
Since Lambda functions do not start a normal node process, the best way we've found to test this out locally is to use [`serverless-offline`](https://github.com/dherault/serverless-offline). This plugin for the serverless framework emulates AWS Lambda and API Gateway on your local machine, allowing you to continue working from `https://localhost:3000/probot` before deploying your function to AWS. | ||
## Examples | ||
|
||
#### Long running tasks | ||
- [example-aws-lambda-serverless](https://github.com/probot/example-aws-lambda-serverless/#readme) - Official example application that is continuously deployed to AWS Lambda | ||
|
||
Some Probot Apps that depend on long running processes or intervals will not work with this extension. This is due to the inherent architecture of serverless functions, which are designed to respond to events and stop running as quickly as possible. For longer running apps we recommend using [other deployment options](https://probot.github.io/docs/deployment). | ||
Add yours! | ||
|
||
#### If you use [HTTP routes](https://probot.github.io/docs/http/) or [WEBHOOK_PATH](https://probot.github.io/docs/configuration/) | ||
## LICENSE | ||
|
||
This extension is designed primarily for receiving webhooks from GitHub and responding back as a GitHub App. If you are using [HTTP Routes](https://probot.github.io/docs/http/) in your app to serve additional pages, you should take a look at [`serverless-http`](https://github.com/dougmoscrop/serverless-http), which can be used with Probot's [express server](https://github.com/probot/probot/blob/master/src/server.ts) by wrapping `probot.server`. | ||
[ISC](LICENSE) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,108 +1,17 @@ | ||
const { Probot } = require("probot"); | ||
const { resolve } = require("probot/lib/helpers/resolve-app-function"); | ||
const { getPrivateKey } = require("@probot/get-private-key"); | ||
const { template } = require("./views/probot"); | ||
|
||
let probot; | ||
|
||
const loadProbot = (appFn) => { | ||
probot = | ||
probot || | ||
new Probot({ | ||
id: process.env.APP_ID, | ||
secret: process.env.WEBHOOK_SECRET, | ||
privateKey: getPrivateKey(), | ||
}); | ||
|
||
if (typeof appFn === "string") { | ||
appFn = resolve(appFn); | ||
} | ||
|
||
probot.load(appFn); | ||
|
||
return probot; | ||
}; | ||
|
||
const lowerCaseKeys = (obj = {}) => | ||
Object.keys(obj).reduce( | ||
(accumulator, key) => | ||
Object.assign(accumulator, { [key.toLocaleLowerCase()]: obj[key] }), | ||
{} | ||
); | ||
|
||
module.exports.serverless = (appFn) => { | ||
return async (event, context) => { | ||
// 🤖 A friendly homepage if there isn't a payload | ||
if (event.httpMethod === "GET" && event.path === "/probot") { | ||
const res = { | ||
statusCode: 200, | ||
headers: { | ||
"Content-Type": "text/html", | ||
}, | ||
body: template, | ||
}; | ||
return res; | ||
} | ||
|
||
// Otherwise let's listen handle the payload | ||
probot = probot || loadProbot(appFn); | ||
|
||
// Ends function immediately after callback | ||
context.callbackWaitsForEmptyEventLoop = false; | ||
|
||
// Determine incoming webhook event type | ||
const headers = lowerCaseKeys(event.headers); | ||
const e = headers["x-github-event"]; | ||
if (!e) { | ||
return { | ||
statusCode: 400, | ||
body: "X-Github-Event header is missing", | ||
}; | ||
} | ||
|
||
// If body is expected to be base64 encoded, decode it and continue | ||
if (event.isBase64Encoded) { | ||
event.body = Buffer.from(event.body, "base64").toString("utf8"); | ||
} | ||
|
||
// Convert the payload to an Object if API Gateway stringifies it | ||
event.body = | ||
typeof event.body === "string" ? JSON.parse(event.body) : event.body; | ||
|
||
// Bail for null body | ||
if (!event.body) { | ||
return { | ||
statusCode: 400, | ||
body: "Event body is null.", | ||
}; | ||
} | ||
|
||
// Do the thing | ||
console.log( | ||
`Received event ${e}${event.body.action ? "." + event.body.action : ""}` | ||
); | ||
if (event) { | ||
try { | ||
await probot.receive({ | ||
name: e, | ||
payload: event.body, | ||
}); | ||
return { | ||
statusCode: 200, | ||
body: JSON.stringify({ | ||
message: `Received ${e}.${event.body.action}`, | ||
}), | ||
}; | ||
} catch (err) { | ||
console.error(err); | ||
return { | ||
statusCode: 500, | ||
body: JSON.stringify(err), | ||
}; | ||
} | ||
} else { | ||
console.error({ event, context }); | ||
throw new Error("unknown error"); | ||
} | ||
}; | ||
}; | ||
const ProbotExports = require("probot"); | ||
const lambdaFunction = require("./lambda-function"); | ||
|
||
module.exports = { ...ProbotExports, createLambdaFunction }; | ||
|
||
/** | ||
* | ||
* @param {import('probot').ApplicationFunction} app | ||
* @param { { probot: import('probot').Probot } } options | ||
*/ | ||
function createLambdaFunction(app, { probot }) { | ||
// load app once outside of the function to prevent double | ||
// event handlers in case of container reuse | ||
probot.load(app); | ||
|
||
return lambdaFunction.bind(null, probot); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
module.exports = lambdaFunction; | ||
|
||
async function lambdaFunction(probot, event, context) { | ||
try { | ||
// Ends function immediately after callback | ||
context.callbackWaitsForEmptyEventLoop = false; | ||
|
||
// this will be simpler once we ship `verifyAndParse()` | ||
// see https://github.com/octokit/webhooks.js/issues/379 | ||
await probot.webhooks.verifyAndReceive({ | ||
id: | ||
event.headers["X-GitHub-Delivery"] || | ||
event.headers["x-github-delivery"], | ||
name: event.headers["X-GitHub-Event"] || event.headers["x-github-event"], | ||
signature: | ||
event.headers["X-Hub-Signature-256"] || | ||
event.headers["x-hub-signature-256"] || | ||
event.headers["X-Hub-Signature"] || | ||
event.headers["x-hub-signature"], | ||
payload: JSON.parse(event.body), | ||
}); | ||
|
||
return { | ||
statusCode: 200, | ||
body: '{"ok":true}', | ||
}; | ||
} catch (error) { | ||
return { | ||
statusCode: error.status || 500, | ||
error: "ooops", | ||
}; | ||
} | ||
} |
Oops, something went wrong.