Skip to content

Commit 84fd425

Browse files
authored
feat: Probot v11 (#59)
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() }); ```
1 parent c2a6ec8 commit 84fd425

11 files changed

+4639
-9307
lines changed

LICENSE

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
ISC License
2+
3+
Copyright (c) 2021 Probot Contributors
4+
5+
Permission to use, copy, modify, and/or distribute this software for any
6+
purpose with or without fee is hereby granted, provided that the above
7+
copyright notice and this permission notice appear in all copies.
8+
9+
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10+
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11+
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12+
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13+
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14+
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15+
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

README.md

+35-24
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,60 @@
1-
## AWS Lambda Extension for Probot
1+
# `@probot/adapter-aws-lambda-serverless`
22

3-
A [Probot](https://github.com/probot/probot) extension to make it easier to run your Probot Apps in AWS Lambda.
3+
> 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)
4+
5+
[![Build Status](https://github.com/probot/adapter-aws-lambda-serverless/workflows/Test/badge.svg)](https://github.com/probot/adapter-aws-lambda-serverless/actions)
46

57
## Usage
68

79
```shell
8-
$ npm install @probot/serverless-lambda
10+
npm install @probot/adapter-aws-lambda-serverless
911
```
1012

1113
```javascript
1214
// handler.js
13-
const { serverless } = require("@probot/serverless-lambda");
15+
const {
16+
createLambdaFunction,
17+
createProbot,
18+
} = require("@probot/adapter-aws-lambda-serverless");
1419
const appFn = require("./");
15-
module.exports.probot = serverless(appFn);
20+
module.exports.webhooks = createLambdaFunction(appFn, {
21+
probot: createProbot(),
22+
});
1623
```
1724

1825
## Configuration
1926

20-
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.
21-
22-
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.
23-
24-
### Serverless dotenv plugin usage
25-
26-
```yaml
27-
plugins:
28-
- serverless-dotenv-plugin # Load .env as environment variables
27+
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`.
2928

29+
```yml
3030
provider:
3131
name: aws
3232
runtime: nodejs12.x
33+
lambdaHashingVersion: 20201221
34+
environment:
35+
APP_ID: ${param:APP_ID}
36+
PRIVATE_KEY: ${param:PRIVATE_KEY}
37+
WEBHOOK_SECRET: ${param:WEBHOOK_SECRET}
38+
NODE_ENV: production
39+
LOG_LEVEL: debug
40+
41+
functions:
42+
webhooks:
43+
handler: handler.webhooks
44+
events:
45+
- httpApi:
46+
path: /api/github/webhooks
47+
method: post
3348
```
3449
35-
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.
36-
37-
## Differences from `probot run`
38-
39-
#### Local Development
50+
Make sure to configure your GitHub App registration's webhook URL to `<your lambda's URL>/api/github/webhooks`.
4051

41-
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.
52+
## Examples
4253

43-
#### Long running tasks
54+
- [example-aws-lambda-serverless](https://github.com/probot/example-aws-lambda-serverless/#readme) - Official example application that is continuously deployed to AWS Lambda
4455

45-
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).
56+
Add yours!
4657

47-
#### If you use [HTTP routes](https://probot.github.io/docs/http/) or [WEBHOOK_PATH](https://probot.github.io/docs/configuration/)
58+
## LICENSE
4859

49-
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`.
60+
[ISC](LICENSE)

index.js

+17-108
Original file line numberDiff line numberDiff line change
@@ -1,108 +1,17 @@
1-
const { Probot } = require("probot");
2-
const { resolve } = require("probot/lib/helpers/resolve-app-function");
3-
const { getPrivateKey } = require("@probot/get-private-key");
4-
const { template } = require("./views/probot");
5-
6-
let probot;
7-
8-
const loadProbot = (appFn) => {
9-
probot =
10-
probot ||
11-
new Probot({
12-
id: process.env.APP_ID,
13-
secret: process.env.WEBHOOK_SECRET,
14-
privateKey: getPrivateKey(),
15-
});
16-
17-
if (typeof appFn === "string") {
18-
appFn = resolve(appFn);
19-
}
20-
21-
probot.load(appFn);
22-
23-
return probot;
24-
};
25-
26-
const lowerCaseKeys = (obj = {}) =>
27-
Object.keys(obj).reduce(
28-
(accumulator, key) =>
29-
Object.assign(accumulator, { [key.toLocaleLowerCase()]: obj[key] }),
30-
{}
31-
);
32-
33-
module.exports.serverless = (appFn) => {
34-
return async (event, context) => {
35-
// 🤖 A friendly homepage if there isn't a payload
36-
if (event.httpMethod === "GET" && event.path === "/probot") {
37-
const res = {
38-
statusCode: 200,
39-
headers: {
40-
"Content-Type": "text/html",
41-
},
42-
body: template,
43-
};
44-
return res;
45-
}
46-
47-
// Otherwise let's listen handle the payload
48-
probot = probot || loadProbot(appFn);
49-
50-
// Ends function immediately after callback
51-
context.callbackWaitsForEmptyEventLoop = false;
52-
53-
// Determine incoming webhook event type
54-
const headers = lowerCaseKeys(event.headers);
55-
const e = headers["x-github-event"];
56-
if (!e) {
57-
return {
58-
statusCode: 400,
59-
body: "X-Github-Event header is missing",
60-
};
61-
}
62-
63-
// If body is expected to be base64 encoded, decode it and continue
64-
if (event.isBase64Encoded) {
65-
event.body = Buffer.from(event.body, "base64").toString("utf8");
66-
}
67-
68-
// Convert the payload to an Object if API Gateway stringifies it
69-
event.body =
70-
typeof event.body === "string" ? JSON.parse(event.body) : event.body;
71-
72-
// Bail for null body
73-
if (!event.body) {
74-
return {
75-
statusCode: 400,
76-
body: "Event body is null.",
77-
};
78-
}
79-
80-
// Do the thing
81-
console.log(
82-
`Received event ${e}${event.body.action ? "." + event.body.action : ""}`
83-
);
84-
if (event) {
85-
try {
86-
await probot.receive({
87-
name: e,
88-
payload: event.body,
89-
});
90-
return {
91-
statusCode: 200,
92-
body: JSON.stringify({
93-
message: `Received ${e}.${event.body.action}`,
94-
}),
95-
};
96-
} catch (err) {
97-
console.error(err);
98-
return {
99-
statusCode: 500,
100-
body: JSON.stringify(err),
101-
};
102-
}
103-
} else {
104-
console.error({ event, context });
105-
throw new Error("unknown error");
106-
}
107-
};
108-
};
1+
const ProbotExports = require("probot");
2+
const lambdaFunction = require("./lambda-function");
3+
4+
module.exports = { ...ProbotExports, createLambdaFunction };
5+
6+
/**
7+
*
8+
* @param {import('probot').ApplicationFunction} app
9+
* @param { { probot: import('probot').Probot } } options
10+
*/
11+
function createLambdaFunction(app, { probot }) {
12+
// load app once outside of the function to prevent double
13+
// event handlers in case of container reuse
14+
probot.load(app);
15+
16+
return lambdaFunction.bind(null, probot);
17+
}

lambda-function.js

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
module.exports = lambdaFunction;
2+
3+
async function lambdaFunction(probot, event, context) {
4+
try {
5+
// Ends function immediately after callback
6+
context.callbackWaitsForEmptyEventLoop = false;
7+
8+
// this will be simpler once we ship `verifyAndParse()`
9+
// see https://github.com/octokit/webhooks.js/issues/379
10+
await probot.webhooks.verifyAndReceive({
11+
id:
12+
event.headers["X-GitHub-Delivery"] ||
13+
event.headers["x-github-delivery"],
14+
name: event.headers["X-GitHub-Event"] || event.headers["x-github-event"],
15+
signature:
16+
event.headers["X-Hub-Signature-256"] ||
17+
event.headers["x-hub-signature-256"] ||
18+
event.headers["X-Hub-Signature"] ||
19+
event.headers["x-hub-signature"],
20+
payload: JSON.parse(event.body),
21+
});
22+
23+
return {
24+
statusCode: 200,
25+
body: '{"ok":true}',
26+
};
27+
} catch (error) {
28+
return {
29+
statusCode: error.status || 500,
30+
error: "ooops",
31+
};
32+
}
33+
}

0 commit comments

Comments
 (0)