Skip to content

Commit cd47254

Browse files
authored
Autobump nvidia vers on a schedule (#183)
* try this * faux-trigger * Use a real action * remove smoke test
1 parent c648fe2 commit cd47254

3 files changed

Lines changed: 172 additions & 0 deletions

File tree

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Update NVIDIA driver versions
2+
on:
3+
# NVIDIA Unix driver releases are infrequent so a weekly check is plenty.
4+
schedule:
5+
- cron: "0 14 * * 1"
6+
workflow_dispatch:
7+
permissions:
8+
contents: write
9+
pull-requests: write
10+
jobs:
11+
refresh:
12+
name: Open PR if NVIDIA versions changed
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Harden the runner (Audit all outbound calls)
16+
uses: step-security/harden-runner@a5ad31d6a139d249332a2605b85202e8c0b78450 # v2.19.1
17+
with:
18+
egress-policy: audit
19+
- name: checkout repository
20+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v4
21+
- name: scrape upstream and rewrite config.yaml
22+
run: python3 ./hack/build/refresh-nvidia-versions.py
23+
- name: open PR if config.yaml changed
24+
# Uses the GitHub API path under the hood so commits are auto-signed
25+
# with the web-flow key (the repo enforces "Verified signatures", which
26+
# blocks plain `git push` from GITHUB_TOKEN). The action is idempotent:
27+
# repeated runs on the same branch update the existing PR.
28+
#
29+
# AUTO_PR_TOKEN, if configured as a PAT in repo secrets, lets the auto-PR
30+
# trigger downstream `pull_request` workflows (the kernel build test).
31+
# Without it we fall back to GITHUB_TOKEN, which can create the PR but
32+
# will not allow CI jobs to autotrigger on PR open - a person still needs
33+
# to kick over/close-reopen the PR to trigger CI.
34+
# TODO bml generate and define a standalone token
35+
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
36+
with:
37+
token: ${{ secrets.AUTO_PR_TOKEN || secrets.GITHUB_TOKEN }}
38+
sign-commits: true
39+
branch: auto/refresh-nvidia
40+
base: main
41+
add-paths: config.yaml
42+
commit-message: "chore: bump NVIDIA driver versions from upstream"
43+
title: "chore: bump NVIDIA driver versions"
44+
body: |
45+
Automated refresh from https://www.nvidia.com/en-us/drivers/unix/.
46+
Review the diff in `config.yaml` and confirm the bumped image tags
47+
build cleanly before merging.
48+
delete-branch: true

config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ flavors:
2222
# version defined here.
2323
# Note that the version numbers here can be found on this page:
2424
# https://www.nvidia.com/en-us/drivers/unix/
25+
# Note also that we have scripts to bump these version numbers that
26+
# depend on the below comment anchors being present, for the time being
2527
local_tags:
2628
- 'nvidia-590.48.01' # Nvidia: "Latest New Feature Branch Version"
2729
- 'nvidia-595.45.04' # Nvidia: "Latest Beta Version"
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#!/usr/bin/env python3
2+
"""Rewrite zone-nvidiagpu local_tags in config.yaml with the latest versions
3+
published on https://www.nvidia.com/en-us/drivers/unix/.
4+
5+
Stdlib-only. Only the version digits in each of
6+
the three matching lines change. If no new versions found, should not update the file.
7+
Currently only supports amd64 drivers. A human must review the PR opened by the GH Action that runs this.
8+
"""
9+
import re
10+
import sys
11+
import urllib.request
12+
from pathlib import Path
13+
14+
NVIDIA_URL = "https://www.nvidia.com/en-us/drivers/unix/"
15+
CONFIG_PATH = Path("config.yaml")
16+
17+
# The three NVIDIA-page labels we care about, mapped to the literal text used
18+
# in the trailing comment of each local_tags line in config.yaml. The script
19+
# matches lines by the comment label, so the order in config.yaml is free.
20+
LABELS = [
21+
"Latest Production Branch Version",
22+
"Latest New Feature Branch Version",
23+
"Latest Beta Version",
24+
]
25+
26+
# Only match Linux x86_64 paragraph for now.
27+
LINUX_X86_64_BLOCK = re.compile(
28+
r"<strong>Linux x86_64/AMD64/EM64T</strong>(?P<body>.*?)</p>",
29+
re.DOTALL,
30+
)
31+
32+
33+
def fetch_latest_versions(url: str = NVIDIA_URL) -> dict[str, str]:
34+
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
35+
with urllib.request.urlopen(req) as resp:
36+
html = resp.read().decode("utf-8", errors="replace")
37+
38+
block_match = LINUX_X86_64_BLOCK.search(html)
39+
if not block_match:
40+
raise RuntimeError(
41+
"Could not locate the Linux x86_64 block on %s — page layout may have changed." % url
42+
)
43+
body = block_match.group("body")
44+
45+
versions = {}
46+
for label in LABELS:
47+
# The page uses `<span calss="title">LABEL:</span> <a href="...">VERSION</a>`
48+
# (yes, "calss" — NVIDIA's typo). Match liberally on the label and the
49+
# next <a>...</a> so the parser survives small markup tweaks.
50+
pat = re.compile(
51+
re.escape(label) + r":\s*</span>\s*<a[^>]*>([0-9][0-9.]*[0-9])</a>",
52+
re.IGNORECASE,
53+
)
54+
m = pat.search(body)
55+
if not m:
56+
raise RuntimeError(
57+
"Could not find version for %r in Linux x86_64 block." % label
58+
)
59+
versions[label] = m.group(1)
60+
return versions
61+
62+
63+
# Matches a local_tags line like:
64+
# - 'nvidia-580.126.18' # Nvidia: "Latest Production Branch Version"
65+
# Captures: prefix (everything up to and including the opening quote),
66+
# the version digits, and suffix (closing quote onward).
67+
LINE_RE = re.compile(
68+
r"^(?P<prefix>\s*-\s*['\"]nvidia-)(?P<version>[0-9][0-9.]*[0-9])(?P<suffix>['\"].*?\"(?P<label>[^\"]+)\".*)$"
69+
)
70+
71+
72+
def rewrite_config(versions: dict[str, str], path: Path = CONFIG_PATH) -> bool:
73+
original = path.read_text()
74+
new_lines = []
75+
changed = False
76+
seen_labels = set()
77+
for line in original.splitlines(keepends=True):
78+
m = LINE_RE.match(line.rstrip("\n"))
79+
if not m:
80+
new_lines.append(line)
81+
continue
82+
label = m.group("label")
83+
if label not in versions:
84+
new_lines.append(line)
85+
continue
86+
seen_labels.add(label)
87+
new_version = versions[label]
88+
if m.group("version") == new_version:
89+
new_lines.append(line)
90+
continue
91+
ending = "\n" if line.endswith("\n") else ""
92+
rewritten = m.group("prefix") + new_version + m.group("suffix") + ending
93+
new_lines.append(rewritten)
94+
changed = True
95+
print(
96+
" %s: %s -> %s" % (label, m.group("version"), new_version),
97+
file=sys.stderr,
98+
)
99+
100+
missing = set(versions) - seen_labels
101+
if missing:
102+
raise RuntimeError(
103+
"config.yaml is missing local_tags lines for: %s" % ", ".join(sorted(missing))
104+
)
105+
106+
if changed:
107+
path.write_text("".join(new_lines))
108+
return changed
109+
110+
111+
def main() -> int:
112+
versions = fetch_latest_versions()
113+
print("upstream versions:", file=sys.stderr)
114+
for label, ver in versions.items():
115+
print(" %s = %s" % (label, ver), file=sys.stderr)
116+
changed = rewrite_config(versions)
117+
print("changed" if changed else "no changes", file=sys.stderr)
118+
return 0
119+
120+
121+
if __name__ == "__main__":
122+
sys.exit(main())

0 commit comments

Comments
 (0)