mirror of
https://github.com/openai/codex.git
synced 2026-09-04 15:08:45 +00:00
## Why The standalone installers currently perform separate unauthenticated GitHub REST API lookups while resolving the latest version, locating the platform package, locating its checksum manifest, and retrieving asset digests. A single install can therefore make up to four release-metadata requests. When GitHub's shared unauthenticated rate limit is exhausted, valid releases fail to install. The shell installer also suppresses the metadata request failure while probing assets, so a `403` is misreported as though the release assets do not exist. This makes the failure both more likely and harder to diagnose. Fixes #28538. ## What changed - Resolve the selected version and fetch its release metadata together. - Reuse that one metadata response for package, checksum, and legacy-package selection in both `install.sh` and `install.ps1`. - Report metadata fetch failures as possible GitHub availability or rate-limit failures instead of missing assets. - Add a mocked-`curl` regression suite covering exact releases, `latest`, and a simulated metadata `403`, and run it in `repo-checks`. For `latest`, the metadata returned by `/releases/latest` now supplies both the resolved version and the asset list. For an explicitly selected version, the installer makes one request to that release's tag endpoint. ## Verification - `python3 -m unittest discover -s scripts/install -p 'test_*.py' -v` - `sh -n scripts/install/install.sh` - Parsed `scripts/install/install.ps1` with the PowerShell language parser. ## Scope This change reduces GitHub API usage and preserves the underlying error, but it does not move release artifacts away from GitHub's CDN.
160 lines
4.8 KiB
Python
160 lines
4.8 KiB
Python
#!/usr/bin/env python3
|
|
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import subprocess
|
|
import tempfile
|
|
import textwrap
|
|
import unittest
|
|
|
|
|
|
INSTALL_SCRIPT = Path(__file__).with_name("install.sh")
|
|
VERSION = "0.142.5"
|
|
|
|
|
|
class InstallShTest(unittest.TestCase):
|
|
def test_metadata_fetch_failure_is_not_reported_as_missing_assets(self) -> None:
|
|
result, requests = run_installer(VERSION, metadata_failure=True)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertEqual(
|
|
requests,
|
|
[
|
|
"https://api.github.com/repos/openai/codex/releases/tags/"
|
|
f"rust-v{VERSION}"
|
|
],
|
|
)
|
|
self.assertIn(
|
|
f"Could not fetch GitHub release metadata for Codex {VERSION}",
|
|
result.stderr,
|
|
)
|
|
self.assertNotIn("Could not find Codex package", result.stderr)
|
|
|
|
def test_exact_release_fetches_metadata_once(self) -> None:
|
|
result, requests = run_installer(VERSION)
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertEqual(
|
|
requests,
|
|
[
|
|
"https://api.github.com/repos/openai/codex/releases/tags/"
|
|
f"rust-v{VERSION}",
|
|
"https://github.com/openai/codex/releases/download/"
|
|
f"rust-v{VERSION}/codex-package_SHA256SUMS",
|
|
],
|
|
)
|
|
self.assertIn(f"Resolved version: {VERSION}", result.stdout)
|
|
|
|
def test_latest_release_reuses_version_metadata(self) -> None:
|
|
result, requests = run_installer("latest")
|
|
|
|
self.assertNotEqual(result.returncode, 0)
|
|
self.assertEqual(
|
|
requests,
|
|
[
|
|
"https://api.github.com/repos/openai/codex/releases/latest",
|
|
"https://github.com/openai/codex/releases/download/"
|
|
f"rust-v{VERSION}/codex-package_SHA256SUMS",
|
|
],
|
|
)
|
|
self.assertIn(f"Resolved version: {VERSION}", result.stdout)
|
|
|
|
|
|
def run_installer(
|
|
release: str, *, metadata_failure: bool = False
|
|
) -> tuple[subprocess.CompletedProcess[str], list[str]]:
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
root = Path(temp_dir)
|
|
bin_dir = root / "bin"
|
|
bin_dir.mkdir()
|
|
request_log = root / "requests.log"
|
|
fake_curl = bin_dir / "curl"
|
|
fake_curl.write_text(
|
|
textwrap.dedent(
|
|
"""\
|
|
#!/bin/sh
|
|
url=""
|
|
for arg in "$@"; do
|
|
case "$arg" in
|
|
https://*) url="$arg" ;;
|
|
esac
|
|
done
|
|
printf '%s\n' "$url" >>"$CODEX_TEST_REQUEST_LOG"
|
|
|
|
case "$url" in
|
|
https://api.github.com/*)
|
|
if [ "$CODEX_TEST_METADATA_FAILURE" = "1" ]; then
|
|
echo "curl: (22) The requested URL returned error: 403" >&2
|
|
exit 22
|
|
fi
|
|
printf '%s\n' "$CODEX_TEST_METADATA_JSON"
|
|
;;
|
|
*)
|
|
exit 22
|
|
;;
|
|
esac
|
|
"""
|
|
),
|
|
encoding="utf-8",
|
|
)
|
|
fake_curl.chmod(0o755)
|
|
|
|
env = os.environ.copy()
|
|
env.update(
|
|
{
|
|
"CODEX_HOME": str(root / "codex-home"),
|
|
"CODEX_INSTALL_DIR": str(root / "install-bin"),
|
|
"CODEX_NON_INTERACTIVE": "1",
|
|
"CODEX_RELEASE": release,
|
|
"CODEX_TEST_METADATA_FAILURE": "1" if metadata_failure else "0",
|
|
"CODEX_TEST_METADATA_JSON": release_metadata(),
|
|
"CODEX_TEST_REQUEST_LOG": str(request_log),
|
|
"HOME": str(root / "home"),
|
|
"PATH": f"{bin_dir}:/usr/bin:/bin",
|
|
"SHELL": "/bin/sh",
|
|
}
|
|
)
|
|
result = subprocess.run(
|
|
["/bin/sh", str(INSTALL_SCRIPT)],
|
|
capture_output=True,
|
|
check=False,
|
|
env=env,
|
|
text=True,
|
|
)
|
|
requests = (
|
|
request_log.read_text(encoding="utf-8").splitlines()
|
|
if request_log.exists()
|
|
else []
|
|
)
|
|
return result, requests
|
|
|
|
|
|
def release_metadata() -> str:
|
|
assets = [
|
|
{
|
|
"name": f"codex-package-{target}.tar.gz",
|
|
"digest": f"sha256:{'a' * 64}",
|
|
}
|
|
for target in (
|
|
"aarch64-apple-darwin",
|
|
"x86_64-apple-darwin",
|
|
"aarch64-unknown-linux-musl",
|
|
"x86_64-unknown-linux-musl",
|
|
)
|
|
]
|
|
assets.append(
|
|
{
|
|
"name": "codex-package_SHA256SUMS",
|
|
"digest": f"sha256:{'b' * 64}",
|
|
}
|
|
)
|
|
return json.dumps(
|
|
{"tag_name": f"rust-v{VERSION}", "assets": assets},
|
|
indent=2,
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|