Skip to content

Commit 0c2e6d2

Browse files
committed
feat: enable rolling build image shas
1 parent b378dcd commit 0c2e6d2

File tree

4 files changed

+967
-5
lines changed

4 files changed

+967
-5
lines changed

src/build-images-handler.ts

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import debug from 'debug';
2+
3+
import { getOctokit } from './utils/octokit';
4+
import { RegistryPackagePublishedEvent } from '@octokit/webhooks-types';
5+
import { MAIN_BRANCH, REPOS } from './constants';
6+
import { Octokit } from '@octokit/rest';
7+
8+
const files = [
9+
'.github/workflows/clean-src-cache.yml',
10+
'.github/workflows/build.yml',
11+
'.github/workflows/linux-publish.yml',
12+
'.github/workflows/macos-publish.yml',
13+
'.github/workflows/windows-publish.yml',
14+
'.github/workflows/build.yml',
15+
'.devcontainer/docker-compose.yml',
16+
];
17+
18+
export async function getPreviousTargetOid(payload: RegistryPackagePublishedEvent) {
19+
const { registry_package, organization } = payload;
20+
const { target_oid, name } = registry_package.package_version;
21+
22+
try {
23+
const octokit = await getOctokit();
24+
const { data: packages } =
25+
await octokit.rest.packages.getAllPackageVersionsForPackageOwnedByOrg({
26+
org: organization.login,
27+
package_type: 'container',
28+
package_name: name,
29+
direction: 'desc',
30+
});
31+
32+
const previousPackage = packages.find(({ metadata }) => {
33+
return metadata.container.tags[0] !== target_oid;
34+
});
35+
36+
return previousPackage.metadata.container.tags[0] || null;
37+
} catch (error) {
38+
console.error('Error fetching previous target_oid:', error);
39+
return null;
40+
}
41+
}
42+
43+
export async function updateFilesWithNewOid(
44+
octokit: Octokit,
45+
filePaths: string[],
46+
previousTargetOid: string,
47+
targetOid: string,
48+
branchName: string,
49+
) {
50+
const d = debug(`roller/github:updateFilesWithNewOid`);
51+
let updatedAny = false;
52+
53+
for (const filePath of filePaths) {
54+
try {
55+
const { data: file } = await octokit.rest.repos.getContent({
56+
...REPOS.electron,
57+
path: filePath,
58+
});
59+
60+
if (!('content' in file)) {
61+
throw new Error(`Incorrectly received array when fetching content for ${filePath}`);
62+
}
63+
64+
const fileContent = Buffer.from(file.content, 'base64').toString('utf-8');
65+
const match = fileContent.match(previousTargetOid);
66+
if (!match) {
67+
d(`No match found for ${filePath}`);
68+
continue;
69+
}
70+
71+
const currentSha = match[0];
72+
if (currentSha === targetOid) {
73+
d(`No update needed for ${filePath}`);
74+
continue;
75+
}
76+
77+
d(`Updating ${filePath} from ${currentSha} to ${targetOid}`);
78+
const newContent = fileContent.replace(currentSha, targetOid);
79+
await octokit.rest.repos.createOrUpdateFileContents({
80+
...REPOS.electron,
81+
path: filePath,
82+
content: Buffer.from(newContent).toString('base64'),
83+
message: `chore: bump build image tag in ${filePath} to ${targetOid.slice(0, 7)}`,
84+
sha: file.sha,
85+
branch: branchName,
86+
});
87+
updatedAny = true;
88+
} catch (error) {
89+
d(`Failed to update ${filePath}: ${error.message}`);
90+
}
91+
}
92+
93+
return updatedAny;
94+
}
95+
96+
export async function prepareGitBranch(octokit: Octokit, branchName: string, mainBranch: string) {
97+
const d = debug(`roller/github:prepareGitBranch`);
98+
99+
const { data: branch } = await octokit.rest.repos.getBranch({
100+
...REPOS.electron,
101+
branch: mainBranch,
102+
});
103+
104+
const sha = branch.commit.sha;
105+
const shortRef = `heads/${branchName}`;
106+
const ref = `refs/${shortRef}`;
107+
108+
// Clean up any orphan refs
109+
d(`Checking that no orphan ref exists from a previous roll`);
110+
try {
111+
const maybeOldRef = await octokit.rest.git.getRef({ ...REPOS.electron, ref: shortRef });
112+
if (maybeOldRef.status === 200) {
113+
d(`Found orphan ref ${ref} with no open PR - deleting`);
114+
await octokit.rest.git.deleteRef({ ...REPOS.electron, ref: shortRef });
115+
await new Promise<void>((r) => setTimeout(r, 2000));
116+
}
117+
} catch (error) {
118+
d(`No orphan ref exists at ${ref} - proceeding, `, error);
119+
}
120+
121+
return { ref, shortRef, branchName, sha };
122+
}
123+
124+
export async function handleBuildImagesCheck(payload: RegistryPackagePublishedEvent) {
125+
const d = debug(`roller/github:handleBuildImagesCheck`);
126+
const octokit = await getOctokit();
127+
128+
const { target_oid } = payload.registry_package.package_version;
129+
const previousTargetOid = await getPreviousTargetOid(payload);
130+
console.log('Previous target OID:', previousTargetOid);
131+
132+
if (!previousTargetOid) {
133+
d('No previous target OID found, cannot proceed with updates');
134+
return;
135+
}
136+
137+
const branchName = `roller/build-images/${MAIN_BRANCH}`;
138+
const { ref, sha } = await prepareGitBranch(octokit, branchName, MAIN_BRANCH);
139+
140+
d(`Preparing to update files with new OID: ${target_oid}`);
141+
const updatedFiles = await updateFilesWithNewOid(
142+
octokit,
143+
files,
144+
previousTargetOid,
145+
target_oid,
146+
branchName,
147+
);
148+
149+
if (!updatedFiles) {
150+
d('No files were updated, skipping PR creation');
151+
return;
152+
}
153+
154+
d(`Creating ref=${ref} at sha=${sha}`);
155+
await octokit.rest.git.createRef({ ...REPOS.electron, ref, sha });
156+
157+
d(`Raising a PR for ${branchName} to ${MAIN_BRANCH}`);
158+
const pr = await octokit.rest.pulls.create({
159+
...REPOS.electron,
160+
base: MAIN_BRANCH,
161+
head: `${REPOS.electron.owner}:${branchName}`,
162+
title: `build: update build-images to ${target_oid.slice(0, 7)}`,
163+
body: `This PR updates the build-images references from ${previousTargetOid.slice(
164+
0,
165+
7,
166+
)} to ${target_oid.slice(0, 7)}.`,
167+
});
168+
169+
d(`New PR: ${pr.data.html_url}`);
170+
}

src/index.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
11
import debug from 'debug';
22
import { Context, Probot } from 'probot';
3-
import { IssueCommentCreatedEvent, PullRequestClosedEvent } from '@octokit/webhooks-types';
3+
import {
4+
IssueCommentCreatedEvent,
5+
PullRequestClosedEvent,
6+
RegistryPackagePublishedEvent,
7+
} from '@octokit/webhooks-types';
48
import { handleNodeCheck } from './node-handler';
59
import { handleChromiumCheck } from './chromium-handler';
10+
import { handleBuildImagesCheck } from './build-images-handler';
611
import { ROLL_TARGETS } from './constants';
712

8-
const handler = (robot: Probot) => {
13+
export const handler = (robot: Probot) => {
914
robot.on('pull_request.closed', async (context: Context) => {
1015
const d = debug('roller/github:pull_request.closed');
1116

@@ -30,9 +35,27 @@ const handler = (robot: Probot) => {
3035
}
3136
});
3237

38+
robot.on('registry_package.published', async (context: Context) => {
39+
const d = debug('roller/github:package.published');
40+
41+
const { repository, registry_package } = context.payload as RegistryPackagePublishedEvent;
42+
43+
const payload = context.payload as RegistryPackagePublishedEvent;
44+
if (repository.full_name !== 'electron/build-images') return;
45+
46+
// There are three packages in the build-images repo (build, devcontainer, test)
47+
// that will all have the same target_oid. We only need to update the shas once.
48+
if (registry_package.name !== 'build') return;
49+
50+
try {
51+
await handleBuildImagesCheck(payload);
52+
} catch (error) {
53+
d(`Failed to autoroll new build-images version: ${error.message}`);
54+
}
55+
});
56+
3357
robot.on('issue_comment.created', async (context: Context) => {
3458
const d = debug('roller/github:issue_comment.created');
35-
3659
const { issue, comment } = context.payload as IssueCommentCreatedEvent;
3760

3861
const match = comment.body.match(/^\/roll (main|\d+-x-y)$/);
@@ -74,5 +97,3 @@ const handler = (robot: Probot) => {
7497
}
7598
});
7699
};
77-
78-
module.exports = handler;

0 commit comments

Comments
 (0)