-
Notifications
You must be signed in to change notification settings - Fork 98
Check Chromium revision pinning scripts into repo #4220
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
Merged
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
jcscottiii marked this conversation as resolved.
Show resolved
Hide resolved
|
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,149 @@ | ||
# Copyright 2025 The WPT Dashboard Project. All rights reserved. | ||
# Use of this source code is governed by a BSD-style license that can be | ||
# found in the LICENSE file. | ||
|
||
""" | ||
This script requires the following environment variables to be set: | ||
|
||
GIT_CHECK_PR_STATUS_TOKEN: A GitHub personal access token with permissions to | ||
update pull request statuses. | ||
REPO_OWNER: The owner of the GitHub repository (e.g., "owner_name"). | ||
REPO_NAME: The name of the GitHub repository (e.g., "repo_name"). | ||
PR_NUMBER: The number of the pull request. | ||
|
||
Please ensure these variables are configured before running the script. | ||
""" | ||
|
||
import os | ||
import requests | ||
from time import time | ||
from google.cloud import storage | ||
|
||
DEFAULT_TIMEOUT = 600.0 | ||
BUCKET_NAME = 'wpt-versions' | ||
NEW_REVISION_FILE = 'pinned_chromium_revision_NEW' | ||
OLD_REVISION_FILE = 'pinned_chromium_revision' | ||
PLATFORM_INFO = [ | ||
("Win_x64", "chrome-win.zip"), | ||
("Win", "chrome-win.zip"), | ||
("Linux_x64", "chrome-linux.zip"), | ||
("Mac", "chrome-mac.zip") | ||
jcscottiii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
] | ||
SNAPSHOTS_PATH = "https://storage.googleapis.com/chromium-browser-snapshots/" | ||
|
||
|
||
def trigger_ci_tests() -> str | None: | ||
# Reopen the PR to run the CI tests. | ||
s = requests.Session() | ||
s.headers.update({ | ||
"Authorization": f"token {get_token()}", | ||
# Specified API version. See https://docs.github.com/en/rest/about-the-rest-api/api-versions | ||
"X-GitHub-Api-Version": "2022-11-28", | ||
}) | ||
repo_owner = os.environ["REPO_OWNER"] | ||
repo_name = os.environ["REPO_NAME"] | ||
pr_number = os.environ["PR_NUMBER"] | ||
url = f"https://api.github.com/repos/{repo_owner}/{repo_name}/pulls/{pr_number}" | ||
|
||
response = s.patch(url, data='{"state": "closed"}') | ||
if response.status_code != 200: | ||
return f'Failed to close PR {pr_number}' | ||
|
||
response = s.patch(url, data='{"state": "open"}') | ||
if response.status_code != 200: | ||
return f'Failed to open PR {pr_number}' | ||
|
||
|
||
def get_token() -> str | None: | ||
"""Get token to check on the CI runs.""" | ||
return os.environ["GIT_CHECK_PR_STATUS_TOKEN"] | ||
|
||
|
||
def get_start_revision() -> int: | ||
"""Get the latest revision for Linux as a starting point to check for a | ||
valid revision for all platforms.""" | ||
try: | ||
url = f"{SNAPSHOTS_PATH}Linux_x64/LAST_CHANGE" | ||
start_revision = int(requests.get(url).text.strip()) | ||
except requests.RequestException as e: | ||
raise requests.RequestException(f"Failed LAST_CHANGE lookup: {e}") | ||
|
||
return start_revision | ||
|
||
|
||
def check_new_chromium_revision() -> str: | ||
"""Find a new Chromium revision that is available for all major platforms (Win/Mac/Linux)""" | ||
timeout = DEFAULT_TIMEOUT | ||
start = time() | ||
|
||
# Load existing pinned revision. | ||
storage_client = storage.Client() | ||
bucket = storage_client.bucket(BUCKET_NAME) | ||
# Read new revision number. | ||
blob = bucket.blob(OLD_REVISION_FILE) | ||
existing_revision = int(blob.download_as_string()) | ||
|
||
start_revision = get_start_revision() | ||
|
||
if start_revision == existing_revision: | ||
print("No new revision.") | ||
return "No new revision." | ||
|
||
# Step backwards through revision numbers until we find one | ||
# that is available for all platforms. | ||
candidate_revision = start_revision | ||
new_revision = -1 | ||
timed_out = False | ||
while new_revision == -1 and candidate_revision > existing_revision: | ||
available_for_all = True | ||
# For each platform, check if Chromium is available for download from snapshots. | ||
for platform, filename in PLATFORM_INFO: | ||
try: | ||
url = (f"{SNAPSHOTS_PATH}{platform}/" | ||
f"{candidate_revision}/{filename}") | ||
# Check the headers of each possible download URL. | ||
r = requests.head(url) | ||
# If the file is not available for download, decrement the revision and try again. | ||
if r.status_code != 200: | ||
candidate_revision -= 1 | ||
available_for_all = False | ||
break | ||
except requests.RequestException: | ||
print(f"Failed to fetch headers for revision {candidate_revision}. Skipping it.") | ||
candidate_revision -= 1 | ||
available_for_all = False | ||
break | ||
|
||
if available_for_all: | ||
new_revision = candidate_revision | ||
if time() - start > timeout: | ||
timed_out = True | ||
break | ||
|
||
end = time() | ||
if timed_out: | ||
raise Exception(f"Reached timeout {timeout}s while checking revision {candidate_revision}") | ||
|
||
if new_revision <= existing_revision: | ||
message = ("No new mutually available revision found after " | ||
f"{'{:.2f}'.format(end - start)} seconds. Keeping revision {existing_revision}.") | ||
print(message) | ||
return message | ||
|
||
|
||
# Replace old revision number with new number. | ||
blob = bucket.blob(NEW_REVISION_FILE) | ||
blob.upload_from_string(str(new_revision)) | ||
pr_error_msg = trigger_ci_tests() | ||
message = (f"Found mutually available revision at {new_revision}.\n" | ||
f"This process started at {start_revision} and checked " | ||
f"{start_revision - new_revision} revisions.\n" | ||
f"The whole process took {'{:.2f}'.format(end - start)} seconds.\n") | ||
if pr_error_msg: | ||
raise Exception(f"PR interaction error: {pr_error_msg}") | ||
print(message) | ||
return message | ||
|
||
|
||
def main(args, _) -> None: | ||
return check_new_chromium_revision() |
File renamed without changes.
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,123 @@ | ||
# Copyright 2025 The WPT Dashboard Project. All rights reserved. | ||
# Use of this source code is governed by a BSD-style license that can be | ||
# found in the LICENSE file. | ||
|
||
""" | ||
This script requires the following environment variables to be set: | ||
|
||
GIT_CHECK_PR_STATUS_TOKEN: A GitHub personal access token with permissions to | ||
update pull request statuses. | ||
REPO_OWNER: The owner of the GitHub repository (e.g., "owner_name"). | ||
REPO_NAME: The name of the GitHub repository (e.g., "repo_name"). | ||
PR_NUMBER: The number of the pull request. | ||
|
||
Please ensure these variables are configured before running the script. | ||
""" | ||
|
||
import os | ||
import requests | ||
from datetime import date | ||
from google.cloud import storage | ||
|
||
|
||
BUCKET_NAME = 'wpt-versions' | ||
NEW_REVISION_FILE = 'pinned_chromium_revision_NEW' | ||
OLD_REVISION_FILE = 'pinned_chromium_revision' | ||
|
||
|
||
def all_passing_checks(repo_owner: str, repo_name: str, pr_number: str) -> bool: | ||
"""Check if all CI tests passed.""" | ||
s = requests.Session() | ||
sha = get_sha(repo_owner, repo_name, pr_number) | ||
s.headers.update(get_github_api_headers()) | ||
url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/commits/{sha}/check-suites' | ||
response = s.get(url) | ||
if response.status_code != 200: | ||
print(f'Received response status {response.status_code} from {url}') | ||
check_info = response.json() | ||
for check in check_info['check_suites']: | ||
if check['conclusion'] != 'success': | ||
return False | ||
return True | ||
|
||
|
||
def update_pr_body( | ||
new_revision: str, | ||
tests_passed: bool, | ||
repo_owner: str, | ||
repo_name: str, | ||
pr_number: str, | ||
) -> bool: | ||
outcome = 'Passed' if tests_passed else 'Failed' | ||
body = ( | ||
'This pull request is used for automated runs of the WPT check suites ' | ||
'against a new available Chromium revision. If all tests pass, the new ' | ||
'revision will be pinned for use.\\n\\nLast revision checked: ' | ||
f'{new_revision.decode('utf-8')}\\nCheck run date: {date.today()}' | ||
f'\\nOutcome: **{outcome}**' | ||
) | ||
|
||
body = '{"body":"' + body + '"}' | ||
s = requests.Session() | ||
s.headers.update(get_github_api_headers()) | ||
url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/pulls/{pr_number}' | ||
jcscottiii marked this conversation as resolved.
Show resolved
Hide resolved
|
||
response = s.patch(url, data=body) | ||
return response.status_code == 200 | ||
|
||
|
||
def get_new_revision() -> str: | ||
storage_client = storage.Client() | ||
bucket = storage_client.bucket(BUCKET_NAME) | ||
blob = bucket.blob(NEW_REVISION_FILE) | ||
return blob.download_as_string() | ||
|
||
|
||
def update_chromium_revision(new_revision) -> None: | ||
storage_client = storage.Client() | ||
bucket = storage_client.bucket(BUCKET_NAME) | ||
|
||
# Replace old revision number with new number. | ||
blob = bucket.blob(OLD_REVISION_FILE) | ||
blob.upload_from_string(new_revision) | ||
|
||
|
||
def get_github_api_headers(): | ||
return { | ||
'Authorization': f'token {get_token()}', | ||
# Specified API version. See https://docs.github.com/en/rest/about-the-rest-api/api-versions | ||
'X-GitHub-Api-Version': '2022-11-28', | ||
} | ||
|
||
|
||
def get_token() -> str: | ||
"""Get token to check on the CI runs.""" | ||
return os.environ['GIT_CHECK_PR_STATUS_TOKEN'] | ||
|
||
|
||
def get_sha(repo_owner: str, repo_name: str, pr_number: str) -> str: | ||
"""Get head sha from PR.""" | ||
s = requests.Session() | ||
s.headers.update(get_github_api_headers()) | ||
url = f'https://api.github.com/repos/{repo_owner}/{repo_name}/pulls/{pr_number}' | ||
response = s.get(url) | ||
pr_info = response.json() | ||
return pr_info['head']['sha'] | ||
|
||
|
||
def main(args, _): | ||
repo_owner = os.environ['REPO_OWNER'] | ||
repo_name = os.environ['REPO_NAME'] | ||
pr_number = os.environ['PR_NUMBER'] | ||
|
||
tests_passed = all_passing_checks(repo_owner, repo_name, pr_number) | ||
new_revision = get_new_revision() | ||
|
||
if tests_passed: | ||
update_chromium_revision(new_revision) | ||
if not update_pr_body(new_revision, tests_passed, repo_owner, repo_name, pr_number): | ||
print('Failed to update PR body description.') | ||
if tests_passed: | ||
print(f'Revision updated to {new_revision}.') | ||
return f'Revision updated to {new_revision}.' | ||
print(f'Some checks failed for PR {pr_number}. Revision not updated.') | ||
return f'Some checks failed for PR {pr_number}. Revision not updated.' |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Could you add the command(s) to deploy these scripts in each section?
Also, do you know how to generate the GIT_CHECK_PR_STATUS_TOKEN stored in secret manager that is used in both of these scripts?
Also, do you know about the instructions for process_test_history?
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.
Any instructions on how to create these PRs in the future?
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.
I've been doing it through the GCP UI, but I'll look up and add instructions for this.
These are instructions I need to add as well, but I was thinking this might be better suited to live in our rotation docs.
The instructions file
build_test_history.py
is what describes this, but I now realize I've not coordinated the names correctly here. It should probably be that "build_test_history" should be renamed to "process_test_history" to match the Cloud Function name.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.
Do you mean creating new cloud functions in the future? Or making changes to these files?
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.
That's good to know. I thought you were deploying it with the CLI. So I was curious what arguments you passed in. You could just reference this document and call out any gotchas that need to be filled in.
That works!
I saw that name and I was wondering if they were the same. Yeah could you either rename the file here or change the name in GCP?edit I see that you did that already.