-
Notifications
You must be signed in to change notification settings - Fork 9
feat: auto-roll chromium deps SHA in build-images when files change #161
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { handleBuildImagesChromiumDepsCheck } from './build-images-chromium-deps-handler'; | ||
|
|
||
| if (require.main === module) { | ||
| handleBuildImagesChromiumDepsCheck().catch((err) => { | ||
| console.log('Build Images Chromium Deps Cron Failed'); | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| import * as debug from 'debug'; | ||
|
|
||
| import { REPOS, BUILD_IMAGES_INSTALL_DEPS_FILE, CHROMIUM_DEPS_FILES } from './constants'; | ||
| import { getOctokit } from './utils/octokit'; | ||
| import { getChromiumHeadSha, didChromiumFilesChange } from './utils/chromium-gitiles'; | ||
| import { rollBuildImages, getFileContentFromBuildImages } from './utils/roll-build-images'; | ||
|
|
||
| // Regex to extract CHROMIUM_SRC_SHA from install-deps.sh | ||
| const CHROMIUM_SHA_REGEX = /CHROMIUM_SRC_SHA="([a-f0-9]{40})"/; | ||
|
|
||
| /** | ||
| * Get the currently pinned Chromium SHA from the build-images repo | ||
| */ | ||
| async function getCurrentPinnedSha(): Promise<string> { | ||
| const octokit = await getOctokit(); | ||
| const { raw: content } = await getFileContentFromBuildImages( | ||
| octokit, | ||
| BUILD_IMAGES_INSTALL_DEPS_FILE, | ||
| ); | ||
|
|
||
| const match = content.match(CHROMIUM_SHA_REGEX); | ||
| if (!match || !match[1]) { | ||
| throw new Error(`Could not find CHROMIUM_SRC_SHA in ${BUILD_IMAGES_INSTALL_DEPS_FILE}`); | ||
| } | ||
|
|
||
| return match[1]; | ||
| } | ||
|
|
||
| /** | ||
| * Update the install-deps.sh file with a new Chromium SHA | ||
| */ | ||
| function updateInstallDepsContent(content: string, newSha: string): string { | ||
| return content.replace(CHROMIUM_SHA_REGEX, `CHROMIUM_SRC_SHA="${newSha}"`); | ||
| } | ||
|
|
||
| export async function handleBuildImagesChromiumDepsCheck(): Promise<void> { | ||
| const d = debug('roller/build-images-chromium-deps:handleBuildImagesChromiumDepsCheck()'); | ||
|
|
||
| const octokit = await getOctokit(); | ||
|
|
||
| d('Getting current pinned SHA from build-images'); | ||
| const currentPinnedSha = await getCurrentPinnedSha(); | ||
| d(`Current pinned SHA: ${currentPinnedSha}`); | ||
|
|
||
| d('Getting current HEAD SHA from Chromium'); | ||
| const currentHeadSha = await getChromiumHeadSha(); | ||
| d(`Current HEAD SHA: ${currentHeadSha}`); | ||
|
|
||
| if (currentPinnedSha === currentHeadSha) { | ||
| d('Pinned SHA is already at HEAD, nothing to do'); | ||
| return; | ||
| } | ||
|
|
||
| d( | ||
| `Checking if any of ${CHROMIUM_DEPS_FILES.join(', ')} changed between ${currentPinnedSha} and ${currentHeadSha}`, | ||
| ); | ||
| const filesChanged = await didChromiumFilesChange( | ||
| CHROMIUM_DEPS_FILES, | ||
| currentPinnedSha, | ||
| currentHeadSha, | ||
| ); | ||
|
|
||
| if (!filesChanged) { | ||
| d('No changes detected in monitored files, skipping roll'); | ||
| return; | ||
| } | ||
|
|
||
| d('Changes detected, creating PR to update SHA'); | ||
|
|
||
| const { raw: currentContent } = await getFileContentFromBuildImages( | ||
| octokit, | ||
| BUILD_IMAGES_INSTALL_DEPS_FILE, | ||
| ); | ||
|
|
||
| const newContent = updateInstallDepsContent(currentContent, currentHeadSha); | ||
|
|
||
| await rollBuildImages( | ||
| 'chromium-deps', | ||
| 'chromium deps', | ||
| currentPinnedSha, | ||
| currentHeadSha, | ||
| BUILD_IMAGES_INSTALL_DEPS_FILE, | ||
| newContent, | ||
| ); | ||
|
|
||
| d('Successfully created/updated PR'); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| const CHROMIUM_GITILES_BASE = 'https://chromium.googlesource.com/chromium/src'; | ||
|
|
||
| /** | ||
| * Get the current HEAD SHA from Chromium main branch | ||
| */ | ||
| export async function getChromiumHeadSha(): Promise<string> { | ||
| const response = await fetch(`${CHROMIUM_GITILES_BASE}/+/refs/heads/main?format=JSON`); | ||
| const text = await response.text(); | ||
| // Gitiles returns JSON with )]}' prefix for security | ||
| const data = JSON.parse(text.slice(text.indexOf('{'))); | ||
| return data.commit; | ||
| } | ||
|
|
||
| /** | ||
| * Get file content at a specific SHA from Chromium repo | ||
| */ | ||
| export async function getChromiumFileContent(filePath: string, sha: string): Promise<string> { | ||
| const url = `${CHROMIUM_GITILES_BASE}/+/${sha}/${filePath}?format=TEXT`; | ||
| const response = await fetch(url); | ||
| const base64Content = await response.text(); | ||
| return Buffer.from(base64Content, 'base64').toString('utf8'); | ||
| } | ||
|
|
||
| /** | ||
| * Check if any of the specified files changed between two SHAs | ||
| */ | ||
| export async function didChromiumFilesChange( | ||
| files: string[], | ||
| fromSha: string, | ||
| toSha: string, | ||
| ): Promise<boolean> { | ||
| for (const file of files) { | ||
| const [fromContent, toContent] = await Promise.all([ | ||
| getChromiumFileContent(file, fromSha), | ||
| getChromiumFileContent(file, toSha), | ||
| ]); | ||
|
|
||
| if (fromContent.trim() !== toContent.trim()) { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,164 @@ | ||||||
| import * as debug from 'debug'; | ||||||
|
|
||||||
| import { MAIN_BRANCH, REPOS } from '../constants'; | ||||||
| import { getOctokit } from './octokit'; | ||||||
| import { PullsListResponseItem } from '../types'; | ||||||
| import { Octokit } from '@octokit/rest'; | ||||||
|
|
||||||
| export async function getFileContentFromBuildImages( | ||||||
| octokit: Octokit, | ||||||
| filePath: string, | ||||||
| ref = MAIN_BRANCH, | ||||||
| ) { | ||||||
| const { data } = await octokit.repos.getContent({ | ||||||
| ...REPOS.buildImages, | ||||||
| path: filePath, | ||||||
| ref, | ||||||
| }); | ||||||
| if ('content' in data) { | ||||||
| return { raw: Buffer.from(data.content, 'base64').toString('utf8'), sha: data.sha }; | ||||||
| } | ||||||
| throw new Error(`Failed to get content for ${filePath}`); | ||||||
| } | ||||||
|
|
||||||
| function getBuildImagesPRText(bumpSubject: string, newShortVersion: string, diffLink: string) { | ||||||
| return { | ||||||
| title: `build: bump ${bumpSubject} to ${newShortVersion.substring(0, 12)}`, | ||||||
| body: `Updating ${bumpSubject} to \`${newShortVersion}\` | ||||||
|
|
||||||
| See [changes in Chromium](${diffLink})`, | ||||||
| }; | ||||||
| } | ||||||
|
|
||||||
| export async function rollBuildImages( | ||||||
| rollKey: string, | ||||||
| bumpSubject: string, | ||||||
| previousSha: string, | ||||||
| newSha: string, | ||||||
| filePath: string, | ||||||
| newContent: string, | ||||||
| ): Promise<any> { | ||||||
| const d = debug(`roller/build-images/${rollKey}:rollBuildImages()`); | ||||||
| const octokit = await getOctokit(); | ||||||
|
|
||||||
| const branchName = `roller/build-images/${rollKey}`; | ||||||
| const shortRef = `heads/${branchName}`; | ||||||
| const ref = `refs/${shortRef}`; | ||||||
|
|
||||||
| const { owner, repo } = REPOS.buildImages; | ||||||
|
|
||||||
| const diffLink = | ||||||
| `https://chromium.googlesource.com/chromium/src/+log/` + | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
suggestion(non-blocking): this could be single quotes rather than backticks since there's no substitution in this part which is just being concatenated. |
||||||
| `${previousSha}..${newSha}?n=10000&pretty=fuller`; | ||||||
|
|
||||||
| // Look for a pre-existing PR that targets this branch to see if we can update that. | ||||||
| let existingPrsForBranch: PullsListResponseItem[] = []; | ||||||
| try { | ||||||
| existingPrsForBranch = (await octokit.paginate('GET /repos/:owner/:repo/pulls', { | ||||||
| head: `${owner}:${branchName}`, | ||||||
| owner, | ||||||
| repo, | ||||||
| state: 'open', | ||||||
| })) as PullsListResponseItem[]; | ||||||
| } catch {} | ||||||
|
|
||||||
| const prs = existingPrsForBranch.filter((pr) => | ||||||
| pr.title.startsWith(`build: bump ${bumpSubject}`), | ||||||
| ); | ||||||
|
|
||||||
| const defaultBranchHeadSha = ( | ||||||
| await octokit.repos.getBranch({ | ||||||
| owner, | ||||||
| repo, | ||||||
| branch: MAIN_BRANCH, | ||||||
| }) | ||||||
| ).data.commit.sha; | ||||||
|
|
||||||
| if (prs.length) { | ||||||
| // Update existing PR(s) | ||||||
| for (const pr of prs) { | ||||||
| d(`Found existing PR: #${pr.number} opened by ${pr.user.login}`); | ||||||
|
|
||||||
| // Check to see if automatic roll has been temporarily disabled | ||||||
| const hasPauseLabel = pr.labels.some((label) => label.name === 'roller/pause'); | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. note: at some point we should really pull |
||||||
| if (hasPauseLabel) { | ||||||
| d(`Automatic updates have been paused for #${pr.number}, skipping roll.`); | ||||||
| continue; | ||||||
| } | ||||||
|
|
||||||
| d(`Attempting update for #${pr.number}`); | ||||||
| const { raw: currentContent, sha: currentSha } = await getFileContentFromBuildImages( | ||||||
| octokit, | ||||||
| filePath, | ||||||
| pr.head.ref, | ||||||
| ); | ||||||
| if (currentContent.trim() !== newContent.trim()) { | ||||||
| await updateFile( | ||||||
| octokit, | ||||||
| bumpSubject, | ||||||
| newSha, | ||||||
| filePath, | ||||||
| newContent, | ||||||
| branchName, | ||||||
| currentSha, | ||||||
| ); | ||||||
| } | ||||||
|
|
||||||
| await octokit.pulls.update({ | ||||||
| owner, | ||||||
| repo, | ||||||
| pull_number: pr.number, | ||||||
| ...getBuildImagesPRText(bumpSubject, newSha, diffLink), | ||||||
| }); | ||||||
| } | ||||||
| } else { | ||||||
| try { | ||||||
| d(`roll triggered for ${bumpSubject}=${newSha}`); | ||||||
|
|
||||||
| try { | ||||||
| await octokit.git.getRef({ owner, repo, ref: shortRef }); | ||||||
| d(`Ref ${ref} already exists, deleting`); | ||||||
| await octokit.git.deleteRef({ owner, repo, ref: shortRef }); | ||||||
| } catch { | ||||||
| // Ignore | ||||||
| } finally { | ||||||
| d(`Creating ref=${ref} at sha=${defaultBranchHeadSha}`); | ||||||
| await octokit.git.createRef({ owner, repo, ref, sha: defaultBranchHeadSha }); | ||||||
| } | ||||||
|
|
||||||
| const { sha: currentSha } = await getFileContentFromBuildImages(octokit, filePath); | ||||||
|
|
||||||
| await updateFile(octokit, bumpSubject, newSha, filePath, newContent, branchName, currentSha); | ||||||
|
|
||||||
| d(`Raising a PR for ${branchName} to ${repo}`); | ||||||
| await octokit.pulls.create({ | ||||||
| owner, | ||||||
| repo, | ||||||
| base: MAIN_BRANCH, | ||||||
| head: `${owner}:${branchName}`, | ||||||
| ...getBuildImagesPRText(bumpSubject, newSha, diffLink), | ||||||
| }); | ||||||
| } catch (e) { | ||||||
| d(`Error rolling ${owner}/${repo} to ${newSha}`, e); | ||||||
| } | ||||||
| } | ||||||
| } | ||||||
|
|
||||||
| async function updateFile( | ||||||
| octokit: Octokit, | ||||||
| bumpSubject: string, | ||||||
| newShortVersion: string, | ||||||
| filePath: string, | ||||||
| newContent: string, | ||||||
| branchName: string, | ||||||
| currentSha: string, | ||||||
| ) { | ||||||
| await octokit.repos.createOrUpdateFileContents({ | ||||||
| ...REPOS.buildImages, | ||||||
| path: filePath, | ||||||
| message: `build: bump ${bumpSubject} in ${filePath} to ${newShortVersion.substring(0, 12)}`, | ||||||
| content: Buffer.from(newContent).toString('base64'), | ||||||
| branch: branchName, | ||||||
| sha: currentSha, | ||||||
| }); | ||||||
| } | ||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion(non-blocking): modern syntax is nice