Skip to content

Commit

Permalink
Update post-pipeline webhooks to Discord (#536)
Browse files Browse the repository at this point in the history
* add quote cache and less aggressive quote-bombing, due to rate limit

* disable email sending, due to broken SendGrid account token

* convert Slack webhook to Discord webhook, with file attachments for job logs
  • Loading branch information
hi-usui authored Oct 22, 2022
1 parent e0c4ac7 commit cc69811
Show file tree
Hide file tree
Showing 2 changed files with 106 additions and 53 deletions.
1 change: 1 addition & 0 deletions infra/gitlab-notify/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"dependencies": {
"ansi_up": "^4.0.4",
"axios": "^0.18.1",
"discord.js": "^14.6.0",
"express": "^4.15.2",
"nodemailer": "^6.4.16"
},
Expand Down
158 changes: 105 additions & 53 deletions infra/gitlab-notify/server/routes/gitlab.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
// https://discordjs.guide/popular-topics/webhooks.html#using-webhooks

import AU from "ansi_up";
import axios from "axios";
import express from "express";
import nodemailer from "nodemailer";
import Discord from "discord.js";

const ansi_up = new AU.default();

const {
PASSWORD_BT_GITLAB_SENDGRID_SMTP,
USERNAME_BT_GITLAB_SENDGRID_SMTP,
GITLAB_DOMAIN,
GITLAB_PROJECT_BT_ACCESS_TOKEN,
SLACK_WEBHOOK_URL,
DISCORD_WEBHOOK_URL,
} = process.env;

const quoteCache = {};
const alreadyPosted = {};
const avatarURL = "https://i.imgur.com/5TI5N3Q.png";
const webhookClient = new Discord.WebhookClient(
Discord.parseWebhookURL(DISCORD_WEBHOOK_URL)
);

const router = express.Router();
const transporter = nodemailer.createTransport({
Expand All @@ -26,30 +35,21 @@ const transporter = nodemailer.createTransport({
});
await transporter.verify();

router.post("/fail", async (req, res) => {
const {
// Pipeline event payload https://docs.gitlab.com/ee/user/project/integrations/webhooks.html
object_attributes,
project,
commit,
builds,
} = req.body;
console.log(req.body);
const failureDetected = builds.some((build) => build.status == "failed");
if (!failureDetected) {
return res.sendStatus(200);
}
const today = () => {
const ts = new Date(Date.now());
const today = `${ts
.getFullYear()
.toString()}${ts
const today = `${ts.getFullYear().toString()}${ts
.getMonth()
.toString()
.padStart(2, "0")}${ts.getDate().toString().padStart(2, "0")}`;
return today;
};

const inspire = async () => {
const day = today();
let message;
if (quoteCache[today]) {
console.log(`Cache hit for '${today}'`);
message = `"${quoteCache[today].quote}" —${quoteCache[today].author}`;
if (quoteCache[day]) {
console.log(`Cache hit for '${day}'`);
message = `"${quoteCache[day].quote}" —${quoteCache[day].author}`;
} else {
try {
const {
Expand All @@ -62,18 +62,37 @@ router.post("/fail", async (req, res) => {
console.log(`Retrieve quote success: `, quotes[0]);
}
message = `"${quote}" —${author}`;
quoteCache[today] = { quote, author };
quoteCache[day] = { quote, author };
} catch (e) {
console.error("Failed to get inspirational quote, using generic Oski...");
message = `"did u know? 1 build failure = 1 extra budget cut to EECS program" —Oski🐻`;
}
}
return message;
};

router.post("/fail", async (req, res) => {
const {
// Pipeline event payload https://docs.gitlab.com/ee/user/project/integrations/webhooks.html
object_attributes,
project,
commit,
builds,
} = req.body;
console.log(req.body);
const failureDetected = builds.some((build) => build.status == "failed");
if (!failureDetected) {
return res.sendStatus(200);
}
const message = await inspire();
const shortSha = commit.id.slice(0, 8);

let html = `
<h1>${message}</h1>
<p style='color: red'>hi ${commit.author.name.toLowerCase()}, looks like we failed to either build or deploy your branch</p>
<p><b>BRANCH:</b> ${object_attributes.ref}</p>
<p><b>COMMIT:</b> ${commit.id.slice(0, 8)}</p>
<p><b>MESSAGE:</b> ${commit.message}</p><br>`;
<p><b>COMMIT:</b> ${shortSha}</p>
<p><b>MESSAGE:</b> ${commit.message.trim()}</p><br>`;
for (let build of builds) {
if (build.status == "failed") {
html += `
Expand All @@ -99,31 +118,55 @@ router.post("/fail", async (req, res) => {
const sendMail = {
from: '"Oski 🐻" <[email protected]>',
to: `"${commit.author.name}" ${commit.author.email}`,
subject: `❌ Build branch '${object_attributes.ref}' pipeline #${object_attributes.id}, commit: '${commit.message}'`,
subject: `❌ Build branch '${object_attributes.ref}' pipeline #${
object_attributes.id
}, commit: '${commit.message.trim()}'`,
html: `${html}`,
};
console.log(sendMail);
await transporter.sendMail(sendMail);

const day = today();
if (!(day in alreadyPosted)) {
await webhookClient.send({
username: "Oski",
content: message,
avatarURL,
});
alreadyPosted[day] = true;
}
await webhookClient.send({
username: "Oski",
content: `❌ ${commit.author.name} => ${
object_attributes.ref
} (${shortSha}: ${commit.message.trim()}) ==> pipeline #${
object_attributes.id
}`,
avatarURL,
files: [
{
attachment: Buffer.from(html),
name: `pipeline_${object_attributes.id}.html`,
},
],
});

// await transporter.sendMail(sendMail);
return res.sendStatus(200);
});

router.post("/prod", async (req, res) => {
router.post("/deployment", async (req, res) => {
const {
// Deployment event payload
// https://docs.gitlab.com/ee/user/project/integrations/webhooks.html
// https://docs.gitlab.com/ee/api/deployments.html
environment,
project,
ref,
short_sha,
status,
} = req.body;
console.log(req.body);
const channel = "#berkeleytime";
const icon_url = "https://i.imgur.com/5TI5N3Q.png";
if (environment != "prod") {
return res.sendStatus(200);
}
const { author_name } = (
const { author_name, message } = (
await axios.get(
`${GITLAB_DOMAIN}/api/v4/projects/${project.id}/repository/commits/${short_sha}`,
{
Expand All @@ -133,27 +176,36 @@ router.post("/prod", async (req, res) => {
}
)
).data;
if (status == "running") {
await axios.post(SLACK_WEBHOOK_URL, {
username: "Oski",
text: `We're deploying commit ${short_sha} to production, OMG ${author_name.toUpperCase()} I'M SO STRESSED, FINGERS CROSSED!!!🤞`,
icon_url,
channel,
});
} else if (status == "success") {
await axios.post(SLACK_WEBHOOK_URL, {
username: "Oski",
text: `It worked ${author_name}! WE DEPLOYED COMMIT ${short_sha} TO PROD! GO BEARS🐻🎉\n...actually let's manually double check, just to be safe`,
icon_url,
channel,
});
} else if (status == "failed") {
await axios.post(SLACK_WEBHOOK_URL, {
username: "Oski",
text: `😭Sorry ${author_name}, we did our best to deploy ${short_sha} to prod, but we fucked up and now Stanford🌲 gets 1 more Big Game win`,
icon_url,
channel,
});
if (environment == "prod") {
if (status == "running") {
await webhookClient.send({
username: "Oski",
content: `We're deploying commit ${short_sha} to production, OMG ${author_name.toUpperCase()} I'M SO STRESSED, FINGERS CROSSED!!!🤞`,
avatarURL,
});
} else if (status == "success") {
await webhookClient.send({
username: "Oski",
content: `It worked ${author_name}! WE DEPLOYED COMMIT ${short_sha} TO PROD! GO BEARS🐻🎉\n...actually let's manually double check, just to be safe`,
avatarURL,
});
} else if (status == "failed") {
await webhookClient.send({
username: "Oski",
content: `😭Sorry ${author_name}, we did our best to deploy ${short_sha} to prod, but we fucked up and now Stanford🌲 gets 1 more Big Game win`,
avatarURL,
});
}
} else {
if (status == "success") {
await webhookClient.send({
username: "Oski",
content: `✅ ${author_name} => ${ref} (${short_sha}: ${message.trim()}) ==> https://${
ref == "master" ? "staging" : ref
}.berkeleytime.com`,
avatarURL,
});
}
}
return res.sendStatus(200);
});
Expand Down

0 comments on commit cc69811

Please sign in to comment.