Skip to content

Commit b8833f8

Browse files
committed
Consolidate GitHub workflow job retrieval
1 parent 3ebd4dd commit b8833f8

10 files changed

Lines changed: 78 additions & 123 deletions

.github/scripts/calculate_run_times_for_label_combinations.py

Lines changed: 2 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from collections import defaultdict
2525
from dataclasses import dataclass
2626
from datetime import datetime, timedelta, timezone
27-
from typing import DefaultDict, Dict, Iterable, List, Optional, Tuple, Union
27+
from typing import DefaultDict, Dict, Iterable, List, Optional, Tuple
2828

2929
import numpy as np
3030
from tabulate import tabulate
@@ -69,29 +69,6 @@ def label_combo_key(labels: Iterable[str]) -> Tuple[str, ...]:
6969
return tuple(sorted(labels))
7070

7171

72-
#
73-
# PyGithub requester helper: handle variable return shapes from requestJsonAndCheck
74-
#
75-
def _req_json_and_headers(requester, path: str):
76-
try:
77-
res = requester.requestJsonAndCheck("GET", path, headers={})
78-
except Exception:
79-
raise
80-
if isinstance(res, (tuple, list)):
81-
if len(res) == 3:
82-
data, _, headers = res
83-
return data, headers or {}
84-
elif len(res) == 2:
85-
data, headers = res
86-
return data, headers or {}
87-
else:
88-
data = res[0]
89-
headers = res[-1] if len(res) > 1 else {}
90-
return data, headers or {}
91-
else:
92-
return res, {}
93-
94-
9572
def get_workflow_id_by_path(repo, workflow_ref: str) -> int:
9673
LOG.info("Resolving workflow reference: %r", workflow_ref)
9774

@@ -190,47 +167,6 @@ def iter_successful_runs(
190167
)
191168

192169

193-
def iter_jobs_for_run(run) -> Iterable[Union[dict, object]]:
194-
"""
195-
Yield job dicts or PyGithub Job objects for the run.
196-
Prefer run.jobs() (PyGithub), else fallback to raw jobs endpoint with pagination.
197-
"""
198-
try:
199-
yield from run.jobs()
200-
return
201-
except Exception as e:
202-
LOG.debug(
203-
"run.jobs() failed for run %s: %s. Falling back to raw jobs API.",
204-
getattr(run, "id", None),
205-
e,
206-
)
207-
208-
try:
209-
requester = run._requester
210-
owner = run.repository.owner.login
211-
repo = run.repository.name
212-
path = f"/repos/{owner}/{repo}/actions/runs/{run.id}/jobs?per_page=100"
213-
while path:
214-
data, headers = _req_json_and_headers(requester, path)
215-
yield from data.get("jobs", [])
216-
217-
link = headers.get("link") or headers.get("Link")
218-
next_url = None
219-
if link:
220-
parts = [p.strip() for p in link.split(",")]
221-
for p in parts:
222-
if 'rel="next"' in p:
223-
next_url = p.split(";")[0].strip().strip("<>")
224-
break
225-
226-
if next_url and next_url.startswith("https://api.github.com"):
227-
path = next_url.replace("https://api.github.com", "")
228-
else:
229-
path = None
230-
except Exception as e:
231-
LOG.error("Raw jobs API failed for run %s: %s", getattr(run, "id", None), e)
232-
233-
234170
def _extract_steps_from_job(job_obj) -> List[Dict]:
235171
if isinstance(job_obj, dict):
236172
return job_obj.get("steps") or []
@@ -341,7 +277,7 @@ def matches(n: Optional[str]) -> bool:
341277

342278

343279
def get_job_duration_seconds(run, job_name: str, match_mode: str) -> Optional[float]:
344-
jobs = list(iter_jobs_for_run(run))
280+
jobs = list(run.jobs(_filter="latest"))
345281
LOG.debug(
346282
"Run %s has %d jobs (via chosen method)", getattr(run, "id", None), len(jobs)
347283
)

.github/scripts/helpers.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,12 @@ def job_name_matches(expected_name: str, actual_name: str) -> bool:
137137
return False
138138

139139

140-
def find_current_job_url(current_job_name: str, runner_name: str) -> str:
140+
def find_current_job_url(
141+
github: Github, current_job_name: str, runner_name: str
142+
) -> str:
141143
try:
142-
jobs = get_jobs_raw(
143-
os.environ["GITHUB_TOKEN"],
144+
jobs = get_jobs(
145+
github,
144146
os.environ["GITHUB_REPOSITORY"],
145147
int(os.environ["GITHUB_RUN_ID"]),
146148
)
@@ -466,10 +468,14 @@ def extract_github_runner_release(payload: dict) -> GithubRunnerRelease:
466468
return GithubRunnerRelease(version=version, sha256_by_arch=sha256_by_arch)
467469

468470

469-
def github_client(github_token: str | None = None) -> Github:
471+
def github_client(
472+
github_token: str | None = None,
473+
*,
474+
base_url: str = "https://api.github.com",
475+
) -> Github:
470476
if github_token:
471-
return Github(auth=GithubAuth.Token(github_token))
472-
return Github()
477+
return Github(auth=GithubAuth.Token(github_token), base_url=base_url)
478+
return Github(base_url=base_url)
473479

474480

475481
def github_client_from_env() -> Github:
@@ -826,6 +832,16 @@ def parse_actions_job_url(job_url: str) -> tuple[int, int] | None:
826832
interval_sec=GITHUB_API_RETRY_INTERVAL_SEC,
827833
retry_exceptions=PYGITHUB_RETRY_EXCEPTIONS,
828834
)
829-
def get_jobs_raw(token, repo_full_name, run_id) -> list[WorkflowJob]:
830-
repo = github_client(token).get_repo(repo_full_name)
831-
return list(repo.get_workflow_run(run_id).jobs())
835+
def get_jobs(
836+
github: Github,
837+
repo_full_name: str,
838+
run_id: int,
839+
*,
840+
run_attempt: int | None = None,
841+
) -> list[WorkflowJob]:
842+
repo = github.get_repo(repo_full_name)
843+
run = repo.get_workflow_run(run_id)
844+
jobs = list(run.jobs(_filter="all" if run_attempt is not None else "latest"))
845+
if run_attempt is not None:
846+
jobs = [job for job in jobs if job.run_attempt == run_attempt]
847+
return jobs

.github/scripts/helpers_test.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -448,11 +448,16 @@ def get_repo(self, repo):
448448
assert release.sha256_by_arch["arm64"] == "b" * 64
449449

450450

451-
def test_get_jobs_raw_fetches_workflow_jobs_with_pygithub(monkeypatch):
452-
jobs = [SimpleNamespace(name="job-1")]
451+
def test_get_jobs_fetches_latest_or_one_attempt():
452+
jobs = [
453+
SimpleNamespace(name="old", run_attempt=1),
454+
SimpleNamespace(name="current", run_attempt=2),
455+
]
456+
filters = []
453457

454458
class FakeRun:
455-
def jobs(self):
459+
def jobs(self, *, _filter):
460+
filters.append(_filter)
456461
return jobs
457462

458463
class FakeRepo:
@@ -465,22 +470,22 @@ def get_repo(self, repo):
465470
assert repo == "owner/repo"
466471
return FakeRepo()
467472

468-
def fake_github_client(token):
469-
assert token == "token"
470-
return FakeGithub()
471-
472-
monkeypatch.setattr(h, "github_client", fake_github_client)
473+
github = FakeGithub()
474+
assert h.get_jobs(github, "owner/repo", 123) == jobs
475+
assert [
476+
job.name for job in h.get_jobs(github, "owner/repo", 123, run_attempt=2)
477+
] == ["current"]
478+
assert filters == ["latest", "all"]
473479

474-
assert h.get_jobs_raw("token", "owner/repo", 123) == jobs
475480

476-
477-
def test_get_jobs_raw_retries_pygithub_failures(monkeypatch):
481+
def test_get_jobs_retries_pygithub_failures(monkeypatch):
478482
jobs = [SimpleNamespace(name="job-1")]
479483
attempts = []
480484
sleeps = []
481485

482486
class FakeRun:
483-
def jobs(self):
487+
def jobs(self, *, _filter):
488+
assert _filter == "latest"
484489
return jobs
485490

486491
class FakeRepo:
@@ -496,14 +501,9 @@ def get_repo(self, repo):
496501
assert repo == "owner/repo"
497502
return FakeRepo()
498503

499-
def fake_github_client(token):
500-
assert token == "token"
501-
return FakeGithub()
502-
503-
monkeypatch.setattr(h, "github_client", fake_github_client)
504504
monkeypatch.setattr(h.time, "sleep", sleeps.append)
505505

506-
assert h.get_jobs_raw("token", "owner/repo", 123) == jobs
506+
assert h.get_jobs(FakeGithub(), "owner/repo", 123) == jobs
507507
assert attempts == [123, 123]
508508
assert sleeps == [h.GITHUB_API_RETRY_INTERVAL_SEC]
509509

.github/scripts/nebius_populate_vms.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from grpc import StatusCode
88
from github import Github
99
from typing import List
10-
from .helpers import github_client
10+
from .helpers import get_jobs, github_client
1111
from nebius.sdk import SDK
1212
from nebius.aio.cli_config import Config
1313
from nebius.api.nebius.compute.v1 import (
@@ -320,7 +320,11 @@ async def run(github: Github, sdk: SDK, args: argparse.Namespace) -> bool:
320320
queued_workflows_count = 0
321321
for workflow in queued_workflows:
322322
# search through workflow jobs to get labels for jobs with status queued
323-
jobs = workflow.jobs()
323+
jobs = get_jobs(
324+
github,
325+
f"{args.github_repo_owner}/{args.github_repo}",
326+
workflow.id,
327+
)
324328
for job in jobs:
325329
if job.status != "queued":
326330
continue

.github/scripts/nebius_runners_wait_times.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from collections import defaultdict
99
from dateutil import parser as dateparser
1010
from dateutil.relativedelta import relativedelta
11-
from .helpers import setup_logger, github_client, get_jobs_raw, classify_runner
11+
from .helpers import setup_logger, github_client, get_jobs, classify_runner
1212

1313
logger = setup_logger()
1414

@@ -97,7 +97,7 @@ def output_results(all_jobs: list[dict], summary, threshold: int):
9797
)
9898

9999

100-
def main(start, end, threshold, github_token, repo):
100+
def main(start, end, threshold, github, repo):
101101
logger.info(f"Fetching workflow runs from {start} to {end}")
102102
all_jobs = []
103103
summary = defaultdict(lambda: {"total_wait": 0.0, "count": 0, "waits": []})
@@ -112,7 +112,7 @@ def main(start, end, threshold, github_token, repo):
112112
continue
113113

114114
try:
115-
jobs = get_jobs_raw(github_token, repo.full_name, run.id)
115+
jobs = get_jobs(github, repo.full_name, run.id)
116116
except Exception as e:
117117
logger.warning(f"Failed to get jobs for run {run.id}: {e}")
118118
continue
@@ -211,4 +211,4 @@ def main(start, end, threshold, github_token, repo):
211211
g = github_client(github_token)
212212
repo = g.get_repo(f"{args.owner}/{args.repo}")
213213

214-
main(start, end, args.threshold, github_token, repo)
214+
main(start, end, args.threshold, g, repo)

.github/scripts/nebius_watch_runners.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from .helpers import (
77
setup_logger,
88
github_client,
9-
get_jobs_raw,
9+
get_jobs,
1010
compact_workflow_name,
1111
compact_job_name,
1212
date_to_hms,
@@ -86,7 +86,7 @@ async def main():
8686
queued_workflows_runs.append(run.id)
8787

8888
for run in workflow_runs:
89-
for job in get_jobs_raw(token, repo.full_name, run.id):
89+
for job in get_jobs(g, f"{args.owner}/{args.repo}", run.id):
9090
if job.status in ("in_progress", "queued") and job.runner_name:
9191
active_jobs[job.runner_name] = {
9292
"job_name": job.name,

.github/scripts/nightly_pr_check.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,7 @@ def load_context(gh) -> tuple[
849849
runs = selected_workflows(labels)
850850
marker = f"pr-{pr_number}-run-{os.environ['GITHUB_RUN_ID']}-attempt-{os.environ.get('GITHUB_RUN_ATTEMPT', '1')}"
851851
collector_url = find_current_job_url(
852+
gh,
852853
os.environ.get("GITHUB_JOB", "nightly-builds"),
853854
os.environ.get("RUNNER_NAME", ""),
854855
)

.github/scripts/tests/finalize_workload_comments.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from ..helpers import (
1212
BUILD_AND_TEST_JOB_NAME_PREFIX,
1313
PYGITHUB_RETRY_EXCEPTIONS,
14-
get_jobs_raw,
14+
get_jobs,
1515
github_client_from_env,
1616
load_github_event,
1717
parse_actions_job_url,
@@ -162,10 +162,8 @@ def main() -> None:
162162
):
163163
return
164164

165-
pr = pull_request_from_event(
166-
github_client_from_env(),
167-
load_github_event(),
168-
)
165+
github = github_client_from_env()
166+
pr = pull_request_from_event(github, load_github_event())
169167
run_number = int(os.environ.get("GITHUB_RUN_NUMBER", "0"))
170168
current_run_id = int(os.environ.get("GITHUB_RUN_ID", "0"))
171169
jobs_cache: JobsCache = {}
@@ -175,8 +173,8 @@ def jobs_for_run(run_id: WorkflowRunId) -> WorkflowJobs:
175173
if run_id in jobs_cache:
176174
return jobs_cache[run_id]
177175
try:
178-
jobs = get_jobs_raw(
179-
os.environ["GITHUB_TOKEN"],
176+
jobs = get_jobs(
177+
github,
180178
os.environ["GITHUB_REPOSITORY"],
181179
run_id,
182180
)

.github/scripts/tests/workload_comment.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,8 @@ def main() -> None:
8282
):
8383
return
8484

85-
pr = pull_request_from_event(
86-
github_client_from_env(),
87-
load_github_event(),
88-
)
85+
github = github_client_from_env()
86+
pr = pull_request_from_event(github, load_github_event())
8987
run_number = int(os.environ.get("GITHUB_RUN_NUMBER", "0"))
9088

9189
if args.command == "init":
@@ -101,7 +99,7 @@ def main() -> None:
10199

102100
job_url = ""
103101
if args.current_job_name:
104-
job_url = find_current_job_url(args.current_job_name, args.runner_name)
102+
job_url = find_current_job_url(github, args.current_job_name, args.runner_name)
105103
write_output(args.job_url_out, job_url)
106104
gs.update_pr_comment_workload_check(
107105
run_number=run_number,

0 commit comments

Comments
 (0)