This commit is contained in:
Tim Taubert 2016-05-29 19:14:59 +02:00
Коммит b501efe68b
7 изменённых файлов: 279 добавлений и 0 удалений

4
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,4 @@
npm-debug.log
node_modules/
*.swp
lib/

2
Procfile Normal file
Просмотреть файл

@ -0,0 +1,2 @@
web: node lib/index server
worker: node lib/index worker

28
package.json Normal file
Просмотреть файл

@ -0,0 +1,28 @@
{
"name": "nss-taskcluster",
"version": "1.0.0",
"private": true,
"author": "Tim Taubert <ttaubert@mozilla.com>",
"description": "Notifications for NSS on Taskcluster",
"scripts": {
"compile": "babel-compile -p taskcluster src:lib",
"install": "npm run compile"
},
"repository": {
"type": "git",
"url": "https://github.com/ttaubert/nss-taskcluster.git"
},
"devDependencies": {
"babel-cli": "^6.0.0",
"babel-compile": "^2.0.0",
"babel-preset-taskcluster": "^2.3.0"
},
"dependencies": {
"array-unique": "^0.2.1",
"irc-colors": "^1.2.1",
"jerk": "^1.1.23",
"peoplestring-parse": "^2.0.3",
"request-json": "^0.5.6",
"taskcluster-client": "^1.0.1"
}
}

65
src/hg.js Normal file
Просмотреть файл

@ -0,0 +1,65 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import request from "request-json";
import parse from "peoplestring-parse";
const HG_HOST = "https://hg.mozilla.org/";
const HG_REPO = "projects/nss";
async function jsonRequest(url) {
let client = request.createClient(HG_HOST);
return new Promise((resolve, reject) => {
client.get(url, (err, res, json) => {
if (err) {
reject(err);
} else {
resolve(json);
}
});
});
}
async function shortenRevision(revision) {
async function tryShortenedRevisionHash(length) {
let shortened = revision.slice(0, length);
let json = jsonRequest(HG_REPO + "/json-pushes?changeset=" + shortened);
if (typeof(json) == "object") {
return shortened;
}
return tryShortenedRevisionHash(length + 2);
}
return tryShortenedRevisionHash(12);
};
async function fetchChangesets(revision, options = {}) {
let includeHref = options && options.includeHref;
let json = await jsonRequest(HG_REPO + "/json-pushes?full&changeset=" + revision);
let id = Object.keys(json)[0];
let changesets = json[id].changesets;
for (let changeset of changesets) {
changeset.author = parse(changeset.author);
if (includeHref) {
let short_rev = await shortenRevision(revision);
changeset.href = HG_HOST + HG_REPO + "/rev/" + short_rev;
}
}
return changesets;
}
async function fetchBlamelist(revision) {
let changesets = await fetchChangesets(revision);
return changesets.map(changeset => changeset.author);
};
module.exports.fetchChangesets = fetchChangesets;
module.exports.fetchBlamelist = fetchBlamelist;

69
src/index.js Normal file
Просмотреть файл

@ -0,0 +1,69 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import hg from "./hg";
import irc from "./irc";
import tcc from "./tcc";
import colors from "irc-colors";
import unique from "array-unique";
const TASK_INSPECTOR_URL = "https://tools.taskcluster.net/task-inspector/#";
const PLATFORMS = {
linux32: "Linux",
linux64: "Linux x64"
};
function containsNssRoute(routes) {
return routes.some(r => /^tc-treeherder\.nss\./.test(r));
}
tcc.onTaskDefined(async function (msg) {
// Check for NSS tasks.
if (!containsNssRoute(msg.routes)) {
return;
}
let taskId = msg.payload.status.taskId;
let task = await tcc.fetchTask(taskId);
let th = task.extra.treeherder;
// Check for decision tasks.
if (th.symbol != "D") {
return;
}
let options = {includeHref: true};
let changesets = await hg.fetchChangesets(th.revision, options);
for (let changeset of changesets) {
let level = colors.blue("push");
let desc = changeset.desc.split("\n")[0];
irc.say(`[${level}] ${changeset.href} - ${changeset.author.name} - ${desc}`);
}
});
tcc.onTaskFailed(async function (msg) {
// Check for NSS tasks.
if (!containsNssRoute(msg.routes)) {
return;
}
let taskId = msg.payload.status.taskId;
let task = await tcc.fetchTask(taskId);
let th = task.extra.treeherder;
// Determine platform.
let collection = Object.keys(th.collection || {})[0] || "opt";
let platform = PLATFORMS[th.build.platform] || th.build.platform;
let level = colors.red("failure");
let url = TASK_INSPECTOR_URL + taskId;
let authors = await hg.fetchBlamelist(th.revision);
let blame = unique(authors.map(author => author.name)).join(", ");
irc.say(`[${level}] ${url} - ${task.metadata.name} @ ${platform} ${collection} (blame: ${blame})`);
});
// Join ASAP.
irc.connect();

58
src/irc.js Normal file
Просмотреть файл

@ -0,0 +1,58 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import jerk from "jerk";
const NICK = "nss-tc";
const CHANNEL = "#nss-test";
const SERVER = "irc.mozilla.org";
let waiting, bot, connected;
let queue = [];
function say(msg) {
queue.push([CHANNEL, msg]);
processQueue();
};
function processQueue() {
if (!connected) {
connect();
return;
}
if (!queue.length || waiting) {
return;
}
bot.say.apply(bot, queue.shift());
waiting = true;
setTimeout(() => {
waiting = false;
processQueue();
}, 500);
}
function connect() {
if (bot) {
return;
}
function onConnect() {
connected = true;
processQueue();
}
bot = jerk(() => {}).connect({
server: SERVER,
onConnect: onConnect,
channels: [CHANNEL],
waitForPing: true,
nick: NICK
});
}
module.exports.say = say;
module.exports.connect = connect;

53
src/tcc.js Normal file
Просмотреть файл

@ -0,0 +1,53 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import taskcluster from "taskcluster-client";
let queueEvents = new taskcluster.QueueEvents();
var scheduler = new taskcluster.Scheduler();
let queue = new taskcluster.Queue();
function onTaskDefined(callback) {
onTaskEvent("taskDefined", callback);
}
function onTaskFailed(callback) {
onTaskEvent("taskFailed", async function (msg) {
let {taskId, taskGroupId} = msg.payload.status;
let info = await scheduler.inspectTask(taskGroupId, taskId);
// Report failures when the last rerun fails.
if (info.reruns == msg.payload.runId) {
callback(msg);
}
});
}
function onTaskEvent(type, callback) {
let listener = new taskcluster.PulseListener({
credentials: {
username: process.env.PG_USERNAME,
password: process.env.PG_PASSWORD
}
});
listener.bind(queueEvents[type]({
provisionerId: "aws-provisioner-v1",
workerType: "hg-worker"
}));
listener.on("message", callback);
listener.connect().then(() => {
return listener.resume();
});
}
async function fetchTask(taskId) {
return queue.task(taskId);
}
module.exports.onTaskDefined = onTaskDefined;
module.exports.onTaskFailed = onTaskFailed;
module.exports.fetchTask = fetchTask;