-
Notifications
You must be signed in to change notification settings - Fork 36
/
index.js
194 lines (169 loc) · 7.41 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
// istanbul ignore file
"use strict";
var t0 = Date.now();
const bl = require("bl"),
bugsWebkit = require('./lib/bugs-webkit'),
checkRequest = require('./lib/check-request'),
comment = require('./lib/comment'),
express = require("express"),
filter = require('./lib/filter'),
flags = require('flags'),
get_metadata = require('./lib/metadata'),
github = require('./lib/github'),
labelModel = require('./lib/label-model'),
logger = require('./lib/logger'),
secretsManager = require('./create-secrets'),
webkit = require('./lib/metadata/webkit');
flags.defineBoolean('dry-run', false, 'Run in dry-run mode (no POSTs to GitHub)');
flags.parse();
function waitFor(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
var app = module.exports = express();
function funkLogMsg(num, msg) {
return function() { logger.info("#" + num + ": " + msg); };
}
function funkLogErr(num, msg) {
return function(err) { logger.error("#" + num + ": " + msg + "\n", err); };
}
var currentlyRunning = {};
/**
* startServer creates the routes for the express server and starts the server.
* @param {object} secrets The secrets needed for the server.
*/
function startServer(secrets) {
app.post('/github-hook', function (req, res) {
req.pipe(bl(function (err, body) {
if (err) {
logger.error(err.message);
} else if (process.env.NODE_ENV != 'production' || checkRequest(body, req.headers["x-hub-signature"], secrets.webhookSecret)) {
res.send(new Date().toISOString());
try {
body = JSON.parse(body);
} catch(e) {
return;
}
if (!filter.event(body, logger.info.bind(logger))) {
return;
}
if (!filter.pullRequest(body.pull_request, logger.info.bind(logger))) {
return;
}
var action = body.action;
var pr = body.pull_request;
var n = pr.number;
var u = (pr.user && pr.user.login) || null;
var content = pr.body || "";
var title = pr.title || "";
if (action == "opened" || action == "synchronize" ||
action == "ready_for_review") {
if (n in currentlyRunning) {
logger.info("#" + n + " is already being processed.");
return;
}
currentlyRunning[n] = true;
logger.info(`#${n}: handling action '${action}'`);
waitFor(5 * 1000).then(function() { // Avoid race condition
return get_metadata(n, u, title, content).then(function(metadata) {
logger.info({metadata: metadata}, `#${n}: Retrieved metadata for pull request`);
return labelModel.post(n, metadata.labels, flags.get('dry-run')).then(
funkLogMsg(n, "Added missing LABELS if any."),
funkLogErr(n, "Something went wrong while adding missing LABELS.")
).then(function() {
return comment(n, metadata, flags.get('dry-run'));
}).then(
funkLogMsg(n, "Added missing REVIEWERS if any."),
funkLogErr(n, "Something went wrong while adding missing REVIEWERS.")
);
});
}).then(function() {
delete currentlyRunning[n];
}, function(err) {
delete currentlyRunning[n];
funkLogErr(n, "THIS SHOULDN'T EVER HAPPEN")(err);
});
} else {
logger.debug(`#${n}: ignoring action '${action}'`);
}
} else {
// Not an error, since anyone can send requests to us.
logger.debug("Unverified request", req);
}
}));
});
var port = process.env.PORT || 5000;
app.listen(port, function() {
logger.info("Express server listening on port %d in %s mode", port, app.settings.env);
logger.info("App started in", (Date.now() - t0) + "ms.");
if (flags.get('dry-run'))
logger.info('Starting in DRY-RUN mode');
});
}
// In addition to listening for notifications from GitHub, we regularly poll the
// set of PRs to keep WebKit exports synchronized with the upstream PR.
async function pullRequestPoller() {
// As currently written, the WebKit PR poller does a huge number of API
// requests and can cause us to hit the GitHub API limit. To mitigate this,
// we currently only run it every 15 minutes.
// See https://github.com/web-platform-tests/wpt-pr-bot/issues/144
await waitFor(15 * 60 * 1000);
logger.info('Checking for changes to WebKit-exported pull requests');
try {
const pull_requests = await github.get("/repos/:owner/:repo/pulls", {});
pull_requests.forEach(async function(pull_request) {
if (!webkit.related(pull_request.title)) {
return;
}
const metadata = await get_metadata(
pull_request.number,
pull_request.user.login,
pull_request.title,
pull_request.body);
const n = pull_request.number;
logger.info({webkitExport: {
issue: metadata.issue,
title: metadata.title,
labels: metadata.labels,
isWebKitVerified: metadata.isWebKitVerified,
isMergeable: metadata.isMergeable,
reviewedDownstream: metadata.reviewedDownstream,
flags: metadata.webkit.flags || {},
}}, `#${n}: Labelling and approving WebKit issue, if necessary`);
return labelModel.post(n, metadata.labels, flags.get('dry-run')).then(
funkLogMsg(n, "Added missing LABELS if any."),
funkLogErr(n, "Something went wrong while adding missing LABELS.")
).then(function() {
return comment(n, metadata, flags.get('dry-run'));
}).then(
funkLogMsg(n, "Added missing REVIEWERS if any."),
funkLogErr(n, "Something went wrong while adding missing REVIEWERS.")
);
});
} catch (e) {
// Assume that errors are likely recoverable, so log them and try again
// next time.
logger.error(e);
}
pullRequestPoller();
}
/**
* The main entrypoint for the application.
*/
function main() {
// To start we need the load the secrets.
secretsManager.loadSecrets()
.then((secrets) => {
logger.info('Received secrets succesfully');
bugsWebkit.setToken(secrets.bugsWebkitToken);
github.setToken(secrets.githubToken);
// TODO(stephenmcgruer): Refactor code to avoid awkward global setter.
logger.info('Starting the server');
startServer(secrets);
logger.info('Staring async pull request poller');
pullRequestPoller();
}).catch((reason) => {
logger.error('Unable to retrieve secrets.' + reason);
process.exit(1);
});
}
main();