forked from denbeigh2000/denbeigh-bot
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
*: added buildkite command/webhook handling
- Loading branch information
1 parent
87e8093
commit 4c7ef3f
Showing
22 changed files
with
1,407 additions
and
712 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
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
Binary file not shown.
Binary file not shown.
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 |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { Snowflake } from "discord-api-types/globals"; | ||
import { Env } from "../env"; | ||
import { BuildkiteErrorShape, Sentry } from "../sentry"; | ||
import { Build, BuildInfo, BuildState, Pipeline } from "./common"; | ||
|
||
export interface Attribution { | ||
user: Snowflake, | ||
message: Snowflake, | ||
} | ||
|
||
export interface Environment { [key: string]: string }; | ||
|
||
export interface BuildParams { | ||
commit: string, | ||
branch: string, | ||
env: Environment, | ||
} | ||
|
||
export class BuildkiteClient { | ||
organisation: string; | ||
token: string; | ||
baseURL: string; | ||
sentry: Sentry; | ||
|
||
constructor(sentry: Sentry, organisation: string, token: string, baseURL: string = "https://api.buildkite.com/v2/") { | ||
this.organisation = organisation; | ||
this.token = token; | ||
this.baseURL = baseURL; | ||
this.sentry = sentry; | ||
} | ||
|
||
async post(endpoint: string, data: any): Promise<Response> { | ||
const body = JSON.stringify(data); | ||
const url = new URL(endpoint, this.baseURL); | ||
const req = new Request(url, { | ||
method: "POST", | ||
body, | ||
headers: { | ||
"Content-Type": "application/json", | ||
"Authorization": `Bearer ${this.token}`, | ||
}, | ||
}); | ||
|
||
const resp = await fetch(req); | ||
if (resp.status >= 400) { | ||
const body = await resp.json() as BuildkiteErrorShape; | ||
this.sentry.logBuildkiteError(req, resp, body); | ||
throw body.message; | ||
} | ||
return resp; | ||
} | ||
|
||
public pipelineURL(pipelineName: string): string { | ||
return `https://buildkite.com/${this.organisation}/${pipelineName}`; | ||
} | ||
|
||
public async createBuild(_env: Env, pipelineName: string, params: Partial<BuildParams>, attr: Attribution): Promise<BuildInfo | null> { | ||
const endpoint = `organizations/${this.organisation}/pipelines/${pipelineName}/builds`; | ||
const data = { | ||
...params, | ||
meta_data: { | ||
"source": "discordbot", | ||
"discord_requester_id": attr.user, | ||
"discord_message_id": attr.message, | ||
}, | ||
}; | ||
|
||
const resp = await this.post(endpoint, data); | ||
const body = await resp.json() as any; | ||
|
||
const build: Build = { | ||
id: body.id as string, | ||
url: body.web_url as string, | ||
state: body.state as BuildState, | ||
commit: body.commit as string || "HEAD", | ||
number: body.number as number, | ||
branch: body.branch as string || "master", | ||
message: body.message as string || "", | ||
}; | ||
|
||
const pipeline: Pipeline = { | ||
id: body.pipeline.id as string, | ||
name: body.pipeline.name as string, | ||
slug: body.pipeline.slug as string, | ||
}; | ||
|
||
const user = (body.author ? body.author : body.creator) || {}; | ||
const author = { | ||
name: "name" in user ? user.name : "", | ||
imageUrl: "avatar_url" in user ? user.avatar_url : undefined, | ||
}; | ||
|
||
return { | ||
build, | ||
pipeline, | ||
author, | ||
}; | ||
} | ||
} |
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 |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Snowflake } from "discord-api-types/v10" | ||
|
||
export interface BuildRequest { | ||
type: "requestedBuild"; | ||
user: Snowflake, | ||
// Interaction ID of slash command that triggered build | ||
interaction: Snowflake, | ||
|
||
channel: Snowflake, | ||
message: Snowflake, | ||
|
||
// NOTE: may need to add more here (e.g., interaction response ID, any | ||
// state we create) | ||
} | ||
|
||
export interface IncomingBuild { | ||
type: "build" | ||
buildID: string, | ||
channel: Snowflake, | ||
message: Snowflake, | ||
} | ||
|
||
export type BuildSource = IncomingBuild | BuildRequest; | ||
|
||
export type BuildState = "running" | "scheduled" | "blocked" | "canceled" | "failed" | "passed" | "skipped" | "canceling" | "not run" | "started"; | ||
|
||
export interface Author { | ||
name: string, | ||
imageUrl: string | undefined, | ||
} | ||
|
||
export interface Build { | ||
id: string, | ||
url: string, | ||
number: number, | ||
branch: string, | ||
commit: string, | ||
message: string, | ||
state: BuildState, | ||
} | ||
|
||
export interface BuildInfo { | ||
build: Build, | ||
pipeline: Pipeline, | ||
author: Author, | ||
} | ||
|
||
export interface Pipeline { | ||
id: string, | ||
name: string, | ||
slug: string, | ||
} | ||
|
||
export interface TrackedBuild { | ||
build: BuildInfo, | ||
|
||
// Source of this request (and associated metadata for updates etc) | ||
source: BuildSource; | ||
} |
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 |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import { APIEmbed } from "discord-api-types/v10"; | ||
import { Sentry } from "../sentry"; | ||
import { BuildInfo, BuildState } from "./common"; | ||
|
||
// #F83F23 | ||
const RED = 16269091; | ||
// #FDF5F5 | ||
const RED_LIGHT = 16643573; | ||
// #FFBA11 | ||
const YELLOW = 16759313; | ||
// #FFF8E7 | ||
const YELLOW_LIGHT = 16775399; | ||
// #00BE13 | ||
const GREEN = 48659; | ||
// #FAFDFA | ||
const GREEN_LIGHT = 16449018; | ||
// #C2CACE | ||
const GREY = 12765902; | ||
// #F9FAFB | ||
const GREY_LIGHT = 16382715; | ||
|
||
// const STALL_IMG = "https://em-content.zobj.net/source/animated-noto-colour-emoji/356/dotted-line-face_1fae5.gif"; | ||
// const RUNNING_IMG = "https://i.kym-cdn.com/photos/images/original/002/429/796/96c.gif"; | ||
// const SUCCESS_IMG = "https://static-cdn.jtvnw.net/emoticons/v2/emotesv2_d3100900bce94eb99e7251d741926564/animated/light/3.0"; | ||
// const FAILURE_IMG = "https://cdn3.emoji.gg/emojis/4438_Pensive_Bread.png"; | ||
const STALL_IMG = "https://pub-0faf9f8a28c14050a1d2a3decae82f38.r2.dev/dotted-line-face.gif"; | ||
const RUNNING_IMG = "https://pub-0faf9f8a28c14050a1d2a3decae82f38.r2.dev/duck-in-hat.gif"; | ||
const SUCCESS_IMG = "https://pub-0faf9f8a28c14050a1d2a3decae82f38.r2.dev/limesDance.gif"; | ||
const FAILURE_IMG = "https://pub-0faf9f8a28c14050a1d2a3decae82f38.r2.dev/pensive-bread.png"; | ||
|
||
// Not sure if this is just for me, but this default gravatar image is very | ||
// unappealing lol. | ||
const DEFAULT_BK_IMG = "https://www.gravatar.com/avatar/3f0e71403ee9fefd2a1cc0df38e14c81"; | ||
|
||
export interface State { | ||
thumbnail: string, | ||
colour: number, | ||
colourLight: number, | ||
emoji: string, | ||
} | ||
|
||
export enum BuildColour { | ||
STALLED = "stalled", | ||
RUNNING = "running", | ||
PASSED = "passed", | ||
FAILED = "failed", | ||
} | ||
|
||
export function stateMap(given: BuildState, sentry: Sentry): BuildColour { | ||
switch (given) { | ||
case "running": | ||
case "started": | ||
return BuildColour.RUNNING; | ||
case "scheduled": | ||
case "blocked": | ||
case "skipped": | ||
case "not run": | ||
return BuildColour.STALLED; | ||
case "passed": | ||
return BuildColour.PASSED; | ||
case "failed": | ||
case "canceled": | ||
case "canceling": | ||
return BuildColour.FAILED; | ||
default: | ||
sentry.setExtra("givenState", given); | ||
sentry.sendMessage("unhandled state"); | ||
return BuildColour.STALLED; | ||
} | ||
} | ||
|
||
export type ColourSet = { | ||
[k in BuildColour]: State | ||
} | ||
|
||
export const STATE_COLOURS: ColourSet = { | ||
[BuildColour.STALLED]: { | ||
thumbnail: STALL_IMG, | ||
colour: GREY, | ||
colourLight: GREY_LIGHT, | ||
emoji: "😪", | ||
}, | ||
[BuildColour.RUNNING]: { | ||
thumbnail: RUNNING_IMG, | ||
colour: YELLOW, | ||
colourLight: YELLOW_LIGHT, | ||
emoji: "⏱️", | ||
}, | ||
[BuildColour.PASSED]: { | ||
thumbnail: SUCCESS_IMG, | ||
colour: GREEN, | ||
colourLight: GREEN_LIGHT, | ||
emoji: "✅" | ||
}, | ||
[BuildColour.FAILED]: { | ||
thumbnail: FAILURE_IMG, | ||
colour: RED, | ||
colourLight: RED_LIGHT, | ||
emoji: "❌", | ||
}, | ||
}; | ||
|
||
export function buildEmbed(build: BuildInfo, sentry: Sentry): APIEmbed { | ||
const stateType = stateMap(build.build.state, sentry); | ||
const stateData = STATE_COLOURS[stateType]; | ||
|
||
const fullCommit = build.build.commit || "HEAD"; | ||
const commit = fullCommit.substring(0, 13); | ||
const title = `${stateData.emoji} ${build.pipeline.name} (#${build.build.number})` | ||
const { url } = build.build; | ||
let { message } = build.build; | ||
if (message && message.length > 50) { | ||
message = message.substring(0, 47) + "..."; | ||
} | ||
|
||
const imageUrl = build.author.imageUrl !== DEFAULT_BK_IMG | ||
? build.author.imageUrl | ||
: undefined; | ||
|
||
const msgField = message | ||
? [{ name: "Message", value: message }] | ||
: []; | ||
|
||
return { | ||
title, | ||
url, | ||
author: { | ||
name: build.author.name, | ||
icon_url: imageUrl, | ||
}, | ||
color: stateData.colour, | ||
thumbnail: { url: stateData.thumbnail }, | ||
fields: msgField.concat([ | ||
{ name: "Commit", value: `\`${commit}\`` }, | ||
{ name: "State", value: `${stateData.emoji} ${build.build.state}`, }, | ||
]), | ||
}; | ||
} |
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 |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Environment, BuildParams, BuildkiteClient, Attribution } from "./client"; | ||
import { BuildRequest, BuildSource, TrackedBuild, BuildState, Pipeline, Build, IncomingBuild, BuildInfo } from "./common"; | ||
import { Tracker as BuildTracker } from "./tracker"; | ||
|
||
import { Sentry } from "../sentry"; | ||
|
||
function discordUserFromWebhook(payload: any, sentry: Sentry): Attribution | null { | ||
const meta = payload.build.meta_data as any; | ||
const { discord_requester_id: user, discord_message_id: message } = meta; | ||
if (user && message) { | ||
return { user, message }; | ||
} else if (!user && !message) { | ||
return null; | ||
} else { | ||
// Only partial info here, ideally this shouldn't occur | ||
sentry.setExtra("user", user); | ||
sentry.setExtra("message", message); | ||
sentry.sendMessage("inconsistent user/message in build event", "warning"); | ||
return null; | ||
} | ||
} | ||
|
||
export { | ||
BuildSource as Associations, | ||
BuildRequest, | ||
BuildInfo, | ||
BuildParams, | ||
BuildTracker, | ||
BuildkiteClient, | ||
Environment, | ||
TrackedBuild, | ||
}; |
Oops, something went wrong.