-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update post-pipeline webhooks to Discord (#536)
* 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
Showing
2 changed files
with
106 additions
and
53 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
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,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({ | ||
|
@@ -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 { | ||
|
@@ -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 += ` | ||
|
@@ -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}`, | ||
{ | ||
|
@@ -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); | ||
}); | ||
|