in github_bot/src/buildkite/finished_build.ts [28:147]
export async function handleFinishedBuild(body: BuildkiteWebhookPayload<any, MetaData>, res: Response) {
const build = body?.build;
if (!build || !build?.commit) {
res.sendStatus(400); // need sha to set statuses
return;
}
const { github } = getConfig();
const { syncCommit } = build.meta_data;
const {
status: resStatus,
data: { total_count: totalCount, check_runs: checkRuns },
} = await githubClient.octokit.checks.listForRef({
...githubClient.repoParams,
ref: build?.commit,
app_id: Number(github.auth.appId),
per_page: 100, // max
});
if (resStatus !== 200) throw new Error('Failed to get checks for ref');
if (checkRuns.length < totalCount) {
// TODO handle this with pagination if check runs exceed 100
throw new Error('Missing check runs, pagination required');
}
const buildStatus = {
status: 'completed',
conclusion:
{
passed: 'success',
canceled: 'cancelled',
failed: 'failure',
}[build.state] ?? 'failure',
};
const output = {
title: `Build ${build.state}`,
summary: `Build ${build.state}`,
};
const { main } = getBuildConfig(false);
const mainCheck = checkRuns.find(({ external_id }) => external_id === main.id);
if (mainCheck) {
if (mainCheck.status !== 'completed') {
const { data: check } = await githubClient.octokit.checks.update({
...githubClient.repoParams,
check_run_id: mainCheck.id, // required
details_url: build.web_url,
output,
...buildStatus,
});
await syncCheckRun(check, syncCommit);
}
} else {
const { data: check } = await githubClient.octokit.checks.create({
...githubClient.repoParams,
head_sha: build?.commit,
name: main.name,
external_id: main.id,
details_url: build.web_url,
output,
...buildStatus,
});
await syncCheckRun(check, syncCommit);
}
// All incomplete check after build is finished
// TODO add a way to set initial PR checks when build never fires
// This requires knowing the external_ids of all check to be set
const unresolvedChecks = checkRuns.filter(({ status }) => status !== 'completed');
await Promise.all(
unresolvedChecks.map(async ({ id, external_id }) => {
const { data: check } = await githubClient.octokit.checks.update({
...githubClient.repoParams,
check_run_id: id, // required
output,
external_id,
...buildStatus,
});
await syncCheckRun(check, syncCommit);
}),
);
// TODO improve this edge case logic when buildkite gets better conditional handling
// This is needed to update the deployment status whenever the deploy step fails and
// thus cannot update the deployment status itself.
const deployRun = checkRuns.find(({ external_id }) => external_id === 'deploy_fb');
if (deployRun?.status !== 'completed' || deployRun.conclusion !== 'success') {
const {
data: [deployment],
} = await githubClient.octokit.repos.listDeployments({
...githubClient.repoParams,
head_sha: build?.commit,
per_page: 1,
});
if (deployment) {
const {
data: [status],
} = await githubClient.octokit.repos.listDeploymentStatuses({
...githubClient.repoParams,
deployment_id: deployment.id,
});
if (status && ['pending', 'queued', 'in_progress'].includes(status.state)) {
const { log_url, environment_url } = status;
await githubClient.octokit.repos.createDeploymentStatus({
...githubClient.repoParams,
description: 'Obsolete deployment, newer deployment pending.',
log_url,
environment_url,
deployment_id: deployment.id,
state: 'inactive',
});
}
}
}
res.sendStatus(200);
}