Enable /rerun-stage workflow URL lookup for fork PRs (#16851)
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
name: PR Test (AMD)
|
name: PR Test (AMD)
|
||||||
|
# Dynamic run-name for /rerun-stage commands to enable URL lookup
|
||||||
|
# Format: "[stage-name] sha" for fork PRs, "[stage-name]" for non-fork, default for normal runs
|
||||||
|
run-name: ${{ inputs.target_stage && (inputs.pr_head_sha && format('[{0}] {1}', inputs.target_stage, inputs.pr_head_sha) || format('[{0}]', inputs.target_stage)) || '' }}
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
name: PR Test
|
name: PR Test
|
||||||
|
# Dynamic run-name for /rerun-stage commands to enable URL lookup
|
||||||
|
# Format: "[stage-name] sha" for fork PRs, "[stage-name]" for non-fork, default for normal runs
|
||||||
|
run-name: ${{ inputs.target_stage && (inputs.pr_head_sha && format('[{0}] {1}', inputs.target_stage, inputs.pr_head_sha) || format('[{0}]', inputs.target_stage)) || '' }}
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
|
|||||||
@@ -12,11 +12,22 @@ PERMISSIONS_FILE_PATH = ".github/CI_PERMISSIONS.json"
|
|||||||
|
|
||||||
|
|
||||||
def find_workflow_run_url(
|
def find_workflow_run_url(
|
||||||
gh_repo, workflow_id, ref, target_stage, token, dispatch_time, max_wait=30
|
gh_repo,
|
||||||
|
workflow_id,
|
||||||
|
ref,
|
||||||
|
target_stage,
|
||||||
|
token,
|
||||||
|
dispatch_time,
|
||||||
|
pr_head_sha=None,
|
||||||
|
max_wait=30,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Poll for the workflow run URL after dispatch.
|
Poll for the workflow run URL after dispatch.
|
||||||
|
|
||||||
|
Uses the dynamic run-name feature to identify runs:
|
||||||
|
- Fork PRs: display_title = "[stage-name] sha"
|
||||||
|
- Non-fork PRs: display_title = "[stage-name]"
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
gh_repo: PyGithub repository object
|
gh_repo: PyGithub repository object
|
||||||
workflow_id: ID of the workflow that was dispatched
|
workflow_id: ID of the workflow that was dispatched
|
||||||
@@ -24,11 +35,21 @@ def find_workflow_run_url(
|
|||||||
target_stage: The stage name we're looking for
|
target_stage: The stage name we're looking for
|
||||||
token: GitHub API token
|
token: GitHub API token
|
||||||
dispatch_time: Unix timestamp when dispatch was triggered
|
dispatch_time: Unix timestamp when dispatch was triggered
|
||||||
|
pr_head_sha: PR head SHA (for fork PRs, used to match display_title)
|
||||||
max_wait: Maximum seconds to wait for the run to appear
|
max_wait: Maximum seconds to wait for the run to appear
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
The workflow run URL if found, None otherwise.
|
The workflow run URL if found, None otherwise.
|
||||||
"""
|
"""
|
||||||
|
# Build expected display_title pattern based on workflow's run-name
|
||||||
|
# Format: "[stage-name] sha" for fork PRs, "[stage-name]" for non-fork
|
||||||
|
if pr_head_sha:
|
||||||
|
expected_title = f"[{target_stage}] {pr_head_sha}"
|
||||||
|
else:
|
||||||
|
expected_title = f"[{target_stage}]"
|
||||||
|
|
||||||
|
print(f"Looking for workflow run with display_title: {expected_title}")
|
||||||
|
|
||||||
for attempt in range(max_wait // 5):
|
for attempt in range(max_wait // 5):
|
||||||
time.sleep(5)
|
time.sleep(5)
|
||||||
|
|
||||||
@@ -55,25 +76,14 @@ def find_workflow_run_url(
|
|||||||
if run_created < dispatch_time - 10:
|
if run_created < dispatch_time - 10:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Verify: check if target_stage job exists and is NOT skipped
|
# Match by display_title (set by workflow's run-name directive)
|
||||||
# This distinguishes our run from other workflow_dispatch runs
|
# This is immediately available, unlike job names which require waiting
|
||||||
jobs_resp = requests.get(
|
display_title = run.get("display_title", "")
|
||||||
f"https://api.github.com/repos/{gh_repo.full_name}/actions/runs/{run['id']}/jobs",
|
if display_title == expected_title:
|
||||||
headers={
|
print(
|
||||||
"Authorization": f"Bearer {token}",
|
f"Found matching workflow run: {run['id']} with title '{display_title}'"
|
||||||
"Accept": "application/vnd.github+json",
|
)
|
||||||
},
|
return run["html_url"]
|
||||||
)
|
|
||||||
|
|
||||||
if jobs_resp.status_code == 200:
|
|
||||||
for job in jobs_resp.json().get("jobs", []):
|
|
||||||
# Match job name containing target_stage
|
|
||||||
# Job names may include partition suffix like "stage-b-test-small-1-gpu (0)"
|
|
||||||
if target_stage in job["name"] and job["conclusion"] != "skipped":
|
|
||||||
print(
|
|
||||||
f"Found matching workflow run: {run['id']} with job {job['name']}"
|
|
||||||
)
|
|
||||||
return run["html_url"]
|
|
||||||
|
|
||||||
print(f"Could not find workflow run after {max_wait} seconds")
|
print(f"Could not find workflow run after {max_wait} seconds")
|
||||||
return None
|
return None
|
||||||
@@ -293,6 +303,9 @@ def handle_rerun_stage(
|
|||||||
)
|
)
|
||||||
print(f"PR is from fork: {is_fork}")
|
print(f"PR is from fork: {is_fork}")
|
||||||
|
|
||||||
|
# pr_head_sha is used for fork PRs (passed to workflow and used for URL lookup)
|
||||||
|
pr_head_sha = None
|
||||||
|
|
||||||
if is_fork:
|
if is_fork:
|
||||||
# For fork PRs: dispatch on main and pass SHA as input
|
# For fork PRs: dispatch on main and pass SHA as input
|
||||||
# This is needed because fork branch names don't exist in the main repo
|
# This is needed because fork branch names don't exist in the main repo
|
||||||
@@ -352,6 +365,7 @@ def handle_rerun_stage(
|
|||||||
stage_name,
|
stage_name,
|
||||||
token,
|
token,
|
||||||
dispatch_time,
|
dispatch_time,
|
||||||
|
pr_head_sha=pr_head_sha,
|
||||||
max_wait=30,
|
max_wait=30,
|
||||||
)
|
)
|
||||||
if run_url:
|
if run_url:
|
||||||
|
|||||||
Reference in New Issue
Block a user