Skip to content

Commit 60b5576

Browse files
committed
fix(ci): authenticated github requess
1 parent 0833b2d commit 60b5576

3 files changed

Lines changed: 145 additions & 7 deletions

File tree

.github/workflows/release-published.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@ jobs:
244244
shell: bash
245245
env:
246246
CODEX_BINARY_RELEASE_TAG: ${{ vars.CODEX_BINARY_RELEASE_TAG || 'latest' }}
247+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
247248
run: |
248249
set -euo pipefail
249250
IFS=',' read -r -a targets <<< "${{ matrix.codex-targets }}"

scripts/fetch_codex_binary.py

Lines changed: 73 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from urllib.request import Request, urlopen
1919

2020
GITHUB_API = "https://api.github.com"
21+
GITHUB_WEB = "https://github.com"
2122
USER_AGENT = "codex-python-binary-fetcher"
2223
ZSTD_TIMEOUT_SECONDS = 300
2324

@@ -59,10 +60,17 @@ def parse_args() -> argparse.Namespace:
5960
def main() -> int:
6061
args = parse_args()
6162
token = _read_optional_env("GITHUB_TOKEN")
62-
release_assets = list_release_assets(args.repo, args.release_tag, token)
6363
dest_root = Path(args.dest_root).resolve()
6464
targets: list[str] = list(dict.fromkeys(args.target_triple))
65+
resolved_tag = resolve_release_tag(args.repo, args.release_tag, token)
66+
67+
release_assets: list[ReleaseAsset] | None = None
6568
for target in targets:
69+
if try_install_direct_asset(args.repo, resolved_tag, target, dest_root, token):
70+
continue
71+
72+
if release_assets is None:
73+
release_assets = list_release_assets(args.repo, resolved_tag, token)
6674
asset = select_asset_for_target(release_assets, target)
6775
if asset is None:
6876
raise RuntimeError(
@@ -82,10 +90,7 @@ def _read_optional_env(name: str) -> str | None:
8290

8391

8492
def list_release_assets(repo: str, release_tag: str, token: str | None) -> list[ReleaseAsset]:
85-
if release_tag == "latest":
86-
url = f"{GITHUB_API}/repos/{repo}/releases/latest"
87-
else:
88-
url = f"{GITHUB_API}/repos/{repo}/releases/tags/{release_tag}"
93+
url = f"{GITHUB_API}/repos/{repo}/releases/tags/{release_tag}"
8994
payload = github_json(url, token)
9095
assets = payload.get("assets")
9196
if not isinstance(assets, list):
@@ -101,16 +106,33 @@ def list_release_assets(repo: str, release_tag: str, token: str | None) -> list[
101106
return release_assets
102107

103108

104-
def select_asset_for_target(assets: list[ReleaseAsset], target: str) -> ReleaseAsset | None:
109+
def resolve_release_tag(repo: str, release_tag: str, token: str | None) -> str:
110+
if release_tag != "latest":
111+
return release_tag
112+
113+
url = f"{GITHUB_WEB}/{repo}/releases/latest"
114+
final_url = github_redirect_url(url, token)
115+
marker = "/releases/tag/"
116+
if marker not in final_url:
117+
raise RuntimeError(f"Could not resolve latest release tag from redirect URL: {final_url}")
118+
return final_url.split(marker, 1)[1]
119+
120+
121+
def candidate_asset_names(target: str) -> list[str]:
105122
prefix = f"codex-{target}"
106-
exact_candidates = [
123+
return [
107124
f"{prefix}.tar.gz",
108125
f"{prefix}.zip",
109126
f"{prefix}.exe",
110127
f"{prefix}",
111128
f"{prefix}.zst",
112129
f"{prefix}.exe.zst",
113130
]
131+
132+
133+
def select_asset_for_target(assets: list[ReleaseAsset], target: str) -> ReleaseAsset | None:
134+
prefix = f"codex-{target}"
135+
exact_candidates = candidate_asset_names(target)
114136
by_name = {asset.name: asset for asset in assets}
115137
for candidate in exact_candidates:
116138
if candidate in by_name:
@@ -123,6 +145,28 @@ def select_asset_for_target(assets: list[ReleaseAsset], target: str) -> ReleaseA
123145
return None
124146

125147

148+
def try_install_direct_asset(
149+
repo: str,
150+
release_tag: str,
151+
target: str,
152+
dest_root: Path,
153+
token: str | None,
154+
) -> bool:
155+
for asset_name in candidate_asset_names(target):
156+
asset = ReleaseAsset(
157+
name=asset_name,
158+
url=f"{GITHUB_WEB}/{repo}/releases/download/{release_tag}/{asset_name}",
159+
)
160+
try:
161+
install_asset(asset, target, dest_root, token)
162+
except RuntimeError as exc:
163+
if _is_not_found_download_error(exc):
164+
continue
165+
raise
166+
return True
167+
return False
168+
169+
126170
def install_asset(asset: ReleaseAsset, target: str, dest_root: Path, token: str | None) -> None:
127171
with tempfile.TemporaryDirectory(prefix=f"codex-asset-{target}-") as tmp:
128172
tmp_dir = Path(tmp)
@@ -227,6 +271,24 @@ def github_json(url: str, token: str | None) -> dict[str, Any]:
227271
return data
228272

229273

274+
def github_redirect_url(url: str, token: str | None) -> str:
275+
_require_https_url(url)
276+
headers = {"User-Agent": USER_AGENT}
277+
if token is not None:
278+
headers["Authorization"] = f"Bearer {token}"
279+
request = Request(url, headers=headers)
280+
try:
281+
with urlopen(request) as response: # nosec B310
282+
return response.geturl()
283+
except HTTPError as exc:
284+
body = exc.read().decode("utf-8", errors="replace")
285+
raise RuntimeError(
286+
f"GitHub redirect request failed ({exc.code}) for {url}: {body}"
287+
) from exc
288+
except URLError as exc:
289+
raise RuntimeError(f"Network error while requesting {url}: {exc}") from exc
290+
291+
230292
def download(url: str, destination: Path, token: str | None) -> None:
231293
_require_https_url(url)
232294
headers = {"User-Agent": USER_AGENT}
@@ -249,5 +311,9 @@ def _require_https_url(url: str) -> None:
249311
raise RuntimeError(f"Refusing to fetch non-HTTPS URL: {url}")
250312

251313

314+
def _is_not_found_download_error(exc: RuntimeError) -> bool:
315+
return str(exc).startswith("Download failed (404)")
316+
317+
252318
if __name__ == "__main__":
253319
raise SystemExit(main())

tests/test_fetch_codex_binary.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,77 @@ def test_select_asset_for_target_prefers_exact_names() -> None:
5050
)
5151

5252

53+
def test_resolve_release_tag_uses_latest_redirect(monkeypatch: pytest.MonkeyPatch) -> None:
54+
module = _load_script_module("fetch_codex_binary", "scripts/fetch_codex_binary.py")
55+
56+
def fake_redirect(url: str, token: str | None) -> str:
57+
assert url == "https://github.com/openai/codex/releases/latest"
58+
assert token == "secret"
59+
return "https://github.com/openai/codex/releases/tag/rust-v1.2.3"
60+
61+
monkeypatch.setattr(module, "github_redirect_url", fake_redirect)
62+
63+
assert module.resolve_release_tag("openai/codex", "latest", "secret") == "rust-v1.2.3"
64+
65+
66+
def test_try_install_direct_asset_skips_missing_candidates(monkeypatch: pytest.MonkeyPatch) -> None:
67+
module = _load_script_module("fetch_codex_binary", "scripts/fetch_codex_binary.py")
68+
attempted: list[str] = []
69+
70+
def fake_install_asset(
71+
asset: object,
72+
target: str,
73+
dest_root: Path,
74+
token: str | None,
75+
) -> None:
76+
_ = (target, dest_root, token)
77+
attempted.append(asset.name)
78+
if asset.name.endswith(".tar.gz"):
79+
raise RuntimeError(f"Download failed (404) for {asset.url}: missing")
80+
81+
monkeypatch.setattr(module, "install_asset", fake_install_asset)
82+
83+
installed = module.try_install_direct_asset(
84+
"openai/codex",
85+
"rust-v1.2.3",
86+
"x86_64-unknown-linux-musl",
87+
Path("/tmp/vendor"),
88+
None,
89+
)
90+
91+
assert installed is True
92+
assert attempted == [
93+
"codex-x86_64-unknown-linux-musl.tar.gz",
94+
"codex-x86_64-unknown-linux-musl.zip",
95+
]
96+
97+
98+
def test_try_install_direct_asset_propagates_non_404_errors(
99+
monkeypatch: pytest.MonkeyPatch,
100+
) -> None:
101+
module = _load_script_module("fetch_codex_binary", "scripts/fetch_codex_binary.py")
102+
103+
def fake_install_asset(
104+
asset: object,
105+
target: str,
106+
dest_root: Path,
107+
token: str | None,
108+
) -> None:
109+
_ = (asset, target, dest_root, token)
110+
raise RuntimeError("Download failed (403) for https://example.test: rate limit")
111+
112+
monkeypatch.setattr(module, "install_asset", fake_install_asset)
113+
114+
with pytest.raises(RuntimeError, match="Download failed \\(403\\)"):
115+
module.try_install_direct_asset(
116+
"openai/codex",
117+
"rust-v1.2.3",
118+
"x86_64-unknown-linux-musl",
119+
Path("/tmp/vendor"),
120+
None,
121+
)
122+
123+
53124
def test_select_asset_for_target_falls_back_to_sorted_prefix_match() -> None:
54125
module = _load_script_module("fetch_codex_binary", "scripts/fetch_codex_binary.py")
55126
assets = [

0 commit comments

Comments
 (0)