Repository navigation
Add windows/amd64 Node 26.12.1 + linux/arm64 Go support #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a45fe8f
5d95b3e
12103a7
a75968f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ def setUp(self) -> None: | |
| shutil.copyfile(ACTION / name, self.action_dir / name) | ||
| shutil.copyfile(ROOT / "scripts" / name, self.scripts / name) | ||
| shutil.copyfile(ACTION / "locked-tools.sh", self.action_dir / "locked-tools.sh") | ||
| shutil.copyfile(ACTION / "install-tool.ps1", self.action_dir / "install-tool.ps1") | ||
| for name in ("verify-release-checkout.sh", "setup-release-buildx.sh"): | ||
| shutil.copyfile(ROOT / "scripts" / name, self.scripts / name) | ||
| self.lock = self.action_dir / "tool-locks.txt" | ||
|
|
@@ -76,14 +77,17 @@ def archive_member(self, tool: str, platform: str) -> str: | |
| if line.startswith("node ") and line.split()[2] == platform) | ||
| os_name, arch = platform.split("/") | ||
| node_arch = "x64" if arch == "amd64" else arch | ||
| if os_name == "windows": | ||
| return f"node-{version}-win-{node_arch}/node.exe" | ||
| return f"node-{version}-{os_name}-{node_arch}/bin/node" | ||
| return tool | ||
|
|
||
| def fixture_download(self, tool: str, platform: str, member: str | None = None, mode: int = 0o755) -> Path: | ||
| archive = self.root / ("fixture.zip" if tool == "minisign" else "fixture.tar.gz") | ||
| is_zip = tool == "minisign" or (tool == "node" and platform == "windows/amd64") | ||
| archive = self.root / ("fixture.zip" if is_zip else "fixture.tar.gz") | ||
| member = member or self.archive_member(tool, platform) | ||
| data = b"#!/bin/sh\necho 'binary must not be invoked' >&2\nexit 77\n" | ||
| if tool == "minisign": | ||
| if is_zip: | ||
| with zipfile.ZipFile(archive, "w") as zipped: | ||
| zipped.writestr(member, data) | ||
| else: | ||
|
|
@@ -122,6 +126,35 @@ def test_verified_install_never_invokes_binary(self) -> None: | |
| if tool == "go": | ||
| self.assertIn(f"GOROOT={dest}/go", self.env_file.read_text()) | ||
|
|
||
| def test_go_linux_arm64_verified_install(self) -> None: | ||
| for machine in ("aarch64", "arm64"): | ||
| with self.subTest(machine=machine): | ||
| self.fixture_download("go", "linux/arm64") | ||
| self.env.update(TEST_OS="Linux", TEST_ARCH=machine) | ||
| dest = self.root / f"go-linux-{machine}" | ||
| self.run_script("install-tool.sh", "go", dest) | ||
| binary = dest / "go/bin/go" | ||
| self.assertTrue(binary.is_file()) | ||
| self.assertTrue(os.access(binary, os.X_OK)) | ||
| self.assertIn(str(binary.parent), self.path_file.read_text().splitlines()) | ||
| self.assertIn(f"GOROOT={dest}/go", self.env_file.read_text()) | ||
| row = next(line.split() for line in self.lock.read_text().splitlines() | ||
| if line.startswith("go ") and line.split()[2] == "linux/arm64") | ||
| self.assertEqual(row[4], f"https://go.dev/dl/{row[1]}.linux-arm64.tar.gz") | ||
| self.assertIn(row[4], self.calls.read_text()) | ||
|
|
||
| def test_go_linux_arm64_hash_mismatch_precedes_extraction(self) -> None: | ||
| archive = self.fixture_download("go", "linux/arm64") | ||
| archive.write_bytes(archive.read_bytes() + b"tampered") | ||
| self.env.update(TEST_OS="Linux", TEST_ARCH="aarch64") | ||
| dest = self.root / "bad-go-arm64" | ||
| result = self.run_script("install-tool.sh", "go", dest, ok=False) | ||
| self.assertIn("::error title=GF13::go sha256 mismatch", result.stderr) | ||
| self.assertNotIn("tar\n", self.calls.read_text()) | ||
| self.assertFalse(dest.exists()) | ||
| self.assertFalse(self.path_file.exists()) | ||
| self.assertFalse(self.env_file.exists()) | ||
|
|
||
| def test_mismatch_fails_before_extraction_or_path_update(self) -> None: | ||
| for tool in ("syft", "goreleaser", "go", "node"): | ||
| with self.subTest(tool=tool): | ||
|
|
@@ -339,6 +372,79 @@ def test_action_node_phases_and_provenance_outputs(self) -> None: | |
| self.assertFalse(self.calls.exists()) | ||
| self.run_script("locked-tools.sh", "outputs", ok=False) | ||
|
|
||
| def test_windows_node_zip_and_action_dispatch(self) -> None: | ||
| row = next(line.split() for line in self.lock.read_text().splitlines() | ||
| if line.startswith("node ") and line.split()[2] == "windows/amd64") | ||
| version = row[1] | ||
| self.assertEqual(row[4], f"https://nodejs.org/dist/{version}/node-{version}-win-x64.zip") | ||
| archive = self.fixture_download("node", "windows/amd64") | ||
| member = f"node-{version}-win-x64/node.exe" | ||
| with zipfile.ZipFile(archive) as zipped: | ||
| self.assertEqual(zipped.namelist(), [member]) | ||
| self.assertFalse(zipped.getinfo(member).is_dir()) | ||
| self.env.update(TEST_OS="MINGW64_NT", TEST_ARCH="x86_64", LOCKED_TOOLS="node", | ||
| LOCKS_INPUT="", ACTION_DIR=str(self.action_dir)) | ||
| self.executable("cygpath", '[[ "$1" = -u || "$1" = -w ]]; printf "%s\\n" "$2"\n') | ||
| self.executable("pwsh", 'printf "pwsh %s\\n" "$*" >> "$CALLS"\n') | ||
| self.run_script("locked-tools.sh", "prepare") | ||
| plan = self.phase_outputs() | ||
| self.assertEqual(plan["tools"], "node") | ||
| self.env.update(TOOL_LOCK_FILE=plan["lock_file"], EXPECTED_LOCK_SHA256=plan["lock_sha256"]) | ||
| self.run_script("locked-tools.sh", "install") | ||
| self.assertEqual(self.calls.read_text().splitlines(), [ | ||
| f"pwsh -NoProfile -File {self.action_dir}/install-tool.ps1 -Tool node -DestDir {self.root / 'dest/node'}" | ||
| ]) | ||
| self.run_script("locked-tools.sh", "outputs") | ||
| self.assertEqual(self.phase_outputs()["node"], version) | ||
| self.assertEqual(json.loads(self.phase_outputs()["versions"]), {"node": version}) | ||
|
|
||
| @unittest.skipUnless(os.name == "nt" and shutil.which("pwsh"), "requires native Windows PowerShell") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Suggestion (medium confidence): No workflow in |
||
| def test_windows_node_installer_fixture(self) -> None: | ||
| original = (ACTION / "tool-locks.txt").read_text() | ||
| version = next(line.split()[1] for line in original.splitlines() | ||
| if line.startswith("node ") and line.split()[2] == "windows/amd64") | ||
| harness = """ | ||
| $ErrorActionPreference = 'Stop' | ||
| function Invoke-WebRequest { | ||
| param([string]$Uri, [string]$OutFile, [int]$MaximumRetryCount) | ||
| Add-Content -LiteralPath $env:CALLS -Value "download $Uri" | ||
| Copy-Item -LiteralPath $env:ARCHIVE -Destination $OutFile | ||
| } | ||
| & $env:INSTALLER -Tool node -DestDir $env:INSTALL_DEST | ||
| """ | ||
| for case in ("valid", "bad-hash", "bad-layout", "bad-url"): | ||
| with self.subTest(case=case): | ||
| self.lock.write_text(original) | ||
| member = "unexpected-node/node.exe" if case == "bad-layout" else None | ||
| archive = self.fixture_download("node", "windows/amd64", member=member) | ||
| if case == "bad-hash": | ||
| archive.write_bytes(archive.read_bytes() + b"tampered") | ||
| if case == "bad-url": | ||
| self.lock.write_text(self.lock.read_text().replace( | ||
| f"node-{version}-win-x64.zip", "unexpected-node.zip")) | ||
| for path in (self.path_file, self.env_file, self.calls): | ||
| path.unlink(missing_ok=True) | ||
| dest = self.root / case | ||
| env = dict(self.env, TOOL_LOCK_FILE=str(self.lock), | ||
| INSTALLER=str(self.action_dir / "install-tool.ps1"), INSTALL_DEST=str(dest)) | ||
| result = subprocess.run([shutil.which("pwsh"), "-NoProfile", "-NonInteractive", "-Command", harness], | ||
| env=env, text=True, capture_output=True) | ||
| if case == "valid": | ||
| self.assertEqual(result.returncode, 0, result.stdout + result.stderr) | ||
| package = dest / f"node-{version}-win-x64" | ||
| self.assertTrue((package / "node.exe").is_file()) | ||
| # PowerShell may expand the Windows 8.3 temporary-directory alias. | ||
| published = self.path_file.read_text(encoding="utf-8-sig").splitlines() | ||
| self.assertTrue(any(Path(path).samefile(package) for path in published)) | ||
| self.assertFalse(self.env_file.exists(), "Node must not publish GOROOT") | ||
| else: | ||
| self.assertNotEqual(result.returncode, 0, result.stdout + result.stderr) | ||
| self.assertFalse(self.path_file.exists()) | ||
| if case in ("bad-hash", "bad-url"): | ||
| self.assertFalse(dest.exists(), "must reject before extraction") | ||
| if case == "bad-url": | ||
| self.assertFalse(self.calls.exists(), "must reject before downloading") | ||
|
|
||
| def test_action_empty_lock_input_resolves_in_action_context(self) -> None: | ||
| self.fixture_download("node", "linux/amd64") | ||
| self.env.update(LOCKED_TOOLS="node", LOCKS_INPUT="", ACTION_DIR=str(self.action_dir), | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Suggestion: This
go go1.27.3 linux/arm64lock goes beyond the PR's stated scope (Windows Node), and the PR body gives no provenance for this hash. The Windows Node row does have provenance. The installer, URL canonicalization, and tests look correct, but this repo can't verify the hash itself. Please add a note on where it came from (e.g. the go.dev/dl JSON manifest), or split it into its own PR. (confidence: medium)