-
-
Notifications
You must be signed in to change notification settings - Fork 229
/
Copy pathBuildkiteTrigger.ts
140 lines (122 loc) · 4.42 KB
/
BuildkiteTrigger.ts
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
import { DeployMetadata } from "@ourworldindata/utils"
import {
BUILDKITE_API_ACCESS_TOKEN,
BUILDKITE_DEPLOY_CONTENT_PIPELINE_SLUG,
BUILDKITE_BRANCH,
BUILDKITE_DEPLOY_CONTENT_SLACK_CHANNEL,
} from "../settings/serverSettings.js"
import { defaultCommitMessage } from "./DeployUtils.js"
type BuildState =
| "running"
| "scheduled"
| "passed"
| "failing"
| "failed"
| "blocked"
| "canceled"
| "canceling"
| "skipped"
| "not_run"
| "finished"
export class BuildkiteTrigger {
private organizationSlug = "our-world-in-data"
private pipelineSlug = BUILDKITE_DEPLOY_CONTENT_PIPELINE_SLUG
private branch = BUILDKITE_BRANCH
async triggerBuild(
message: string,
env: { [key: string]: string }
): Promise<number> {
// Trigger buildkite build and return its number.
const url = `https://api.buildkite.com/v2/organizations/${this.organizationSlug}/pipelines/${this.pipelineSlug}/builds`
const apiAccessToken = BUILDKITE_API_ACCESS_TOKEN
if (!apiAccessToken) {
throw new Error(
"BUILDKITE_API_ACCESS_TOKEN environment variable not set"
)
}
const headers = {
"Content-Type": "application/json",
Authorization: `Bearer ${BUILDKITE_API_ACCESS_TOKEN}`,
}
const payload = {
commit: "HEAD",
branch: this.branch,
message: message,
env: { ...env, BUILDKITE_DEPLOY_CONTENT_SLACK_CHANNEL },
}
const response = await fetch(url, {
method: "POST",
headers: headers,
body: JSON.stringify(payload),
})
if (response.status === 201) {
console.log("Build successfully triggered!")
const resp = await response.json()
return resp.number
} else {
const errorText = await response.text()
throw new Error(`Error: ${response.status}\n${errorText}`)
}
}
async waitForBuildToFinish(buildNumber: number): Promise<void> {
// Wait for build to finish.
const url = `https://api.buildkite.com/v2/organizations/${this.organizationSlug}/pipelines/${this.pipelineSlug}/builds/${buildNumber}`
const headers = {
Authorization: `Bearer ${BUILDKITE_API_ACCESS_TOKEN}`,
}
let state: BuildState = "scheduled"
while (
["running", "scheduled", "canceling", "failing"].includes(state)
) {
// Wait for 10 seconds
await new Promise((res) => setTimeout(res, 10000))
const response = await fetch(url, {
method: "GET",
headers: headers,
})
if (!response.ok) {
const errorText = await response.text()
throw new Error(`Error: ${response.status}\n${errorText}`)
}
const buildData = await response.json()
state = buildData.state
}
if (!["passed", "skipped", "canceled", "finished"].includes(state)) {
// failing states: failed, blocked, not_run
throw new Error(
`Build ${buildNumber} failed with state "${state}". See Buildkite for details.`
)
}
}
async runLightningBuild(
gdocSlugs: string[],
{ title, changesSlackMentions }: DeployMetadata
): Promise<void> {
const message = `⚡️ ${title}${
gdocSlugs.length > 1
? ` and ${gdocSlugs.length - 1} more updates`
: ""
}`
const buildNumber = await this.triggerBuild(message, {
LIGHTNING_GDOC_SLUGS: gdocSlugs.join(" "),
CHANGES_SLACK_MENTIONS: changesSlackMentions.join("\n"),
})
await this.waitForBuildToFinish(buildNumber)
}
async runFullBuild({
title,
changesSlackMentions,
}: DeployMetadata): Promise<void> {
const message = changesSlackMentions.length
? `🚚 ${title}${
changesSlackMentions.length > 1
? ` and ${changesSlackMentions.length - 1} more updates`
: ""
} `
: await defaultCommitMessage()
const buildNumber = await this.triggerBuild(message, {
CHANGES_SLACK_MENTIONS: changesSlackMentions.join("\n"),
})
await this.waitForBuildToFinish(buildNumber)
}
}