-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Harden Node archive Starlark generation #2101
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
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 |
|---|---|---|
|
|
@@ -3,16 +3,52 @@ const crypto = require("crypto"); | |
| const https = require("https"); | ||
| const fs = require("fs"); | ||
|
|
||
| if (process.argv.length < 3) { | ||
| console.error("Usage: node nodeChecksum.js <nodejs_version>"); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const versions = process.argv[2].split(","); | ||
| let versions = []; | ||
| const architectures = ["amd64", "arm64", "arm", "ppc64le", "s390x"]; | ||
| const NODE_VERSION_RE = /^[0-9]+\.[0-9]+\.[0-9]+$/; | ||
| const ARCHIVE_SUFFIX_RE = /^(x64|arm64|armv7l|ppc64le|s390x)$/; | ||
| const DISTROLESS_ARCH_RE = /^(amd64|arm64|arm|ppc64le|s390x)$/; | ||
| const SHA256_RE = /^[0-9a-f]{64}$/; | ||
|
|
||
| const nodeVersions = {}; | ||
|
|
||
| const starlarkString = (value) => { | ||
| return `"${String(value) | ||
| .replace(/\\/g, "\\\\") | ||
| .replace(/"/g, '\\"') | ||
| .replace(/\n/g, "\\n") | ||
| .replace(/\r/g, "\\r") | ||
| .replace(/\t/g, "\\t")}"`; | ||
| }; | ||
|
|
||
| const validateNodeVersion = (version) => { | ||
| if (!NODE_VERSION_RE.test(version)) { | ||
| throw new Error(`Invalid Node.js version: ${version}`); | ||
| } | ||
| return version; | ||
| }; | ||
|
|
||
| const validateArchiveSuffix = (suffix) => { | ||
| if (!ARCHIVE_SUFFIX_RE.test(suffix)) { | ||
| throw new Error(`Invalid Node.js archive suffix: ${suffix}`); | ||
| } | ||
| return suffix; | ||
| }; | ||
|
|
||
| const validateDistrolessArch = (arch) => { | ||
| if (!DISTROLESS_ARCH_RE.test(arch)) { | ||
| throw new Error(`Invalid distroless architecture: ${arch}`); | ||
| } | ||
| return arch; | ||
| }; | ||
|
|
||
| const validateSha256 = (sha256) => { | ||
| if (!SHA256_RE.test(sha256)) { | ||
| throw new Error(`Invalid SHA256 checksum: ${sha256}`); | ||
| } | ||
| return sha256; | ||
| }; | ||
|
|
||
| const calculateChecksum = (url) => { | ||
| return new Promise((resolve, reject) => { | ||
| https | ||
|
|
@@ -33,6 +69,7 @@ const calculateChecksum = (url) => { | |
|
|
||
| const fetchChecksums = async () => { | ||
| for (const nodeVersion of versions) { | ||
| validateNodeVersion(nodeVersion); | ||
| const major = parseInt(nodeVersion.split(".")[0]); | ||
| nodeVersions[nodeVersion] = {}; | ||
| await Promise.all( | ||
|
|
@@ -46,6 +83,7 @@ const fetchChecksums = async () => { | |
| } else if (key === "arm") { | ||
| arch = "armv7l"; | ||
| } | ||
| validateArchiveSuffix(arch); | ||
| const url = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-${arch}.tar.gz`; | ||
| try { | ||
| const checksum = await calculateChecksum(url); | ||
|
|
@@ -61,7 +99,14 @@ const fetchChecksums = async () => { | |
| } | ||
| }; | ||
|
|
||
| fetchChecksums().then(() => { | ||
| const main = async () => { | ||
| if (process.argv.length < 3) { | ||
| console.error("Usage: node nodeChecksum.js <nodejs_version>"); | ||
| process.exit(1); | ||
| } | ||
| versions = process.argv[2].split(","); | ||
| await fetchChecksums(); | ||
|
|
||
| let nodeArchives = `"node" | ||
|
|
||
| BUILD_TMPL = """\\ | ||
|
|
@@ -172,16 +217,19 @@ def _node_impl(module_ctx): | |
| } | ||
| const arch = nodeVersions[nodeVersion][key]; | ||
| const url = `https://nodejs.org/dist/v${nodeVersion}/node-v${nodeVersion}-linux-${arch.suffix}.tar.gz`; | ||
| validateDistrolessArch(key); | ||
| validateArchiveSuffix(arch.suffix); | ||
| validateSha256(arch.checksum); | ||
|
|
||
| nodeArchives += "\n"; | ||
| nodeArchives += ` | ||
| node_archive( | ||
| name = "nodejs${major}_${key}", | ||
| sha256 = "${arch.checksum}", | ||
| strip_prefix = "node-v${nodeVersion}-linux-${arch.suffix}/", | ||
| urls = ["${url}"], | ||
| version = "${nodeVersion}", | ||
| architecture = "${key}", | ||
| name = ${starlarkString(`nodejs${major}_${key}`)}, | ||
| sha256 = ${starlarkString(arch.checksum)}, | ||
| strip_prefix = ${starlarkString(`node-v${nodeVersion}-linux-${arch.suffix}/`)}, | ||
| urls = [${starlarkString(url)}], | ||
| version = ${starlarkString(nodeVersion)}, | ||
| architecture = ${starlarkString(key)}, | ||
| control = "//nodejs:control", | ||
| )`; | ||
| } | ||
|
|
@@ -212,7 +260,7 @@ commandTests: | |
| continue; | ||
| } | ||
| nodeArchives += ` | ||
| "nodejs${major}_${arch}",`; | ||
| ${starlarkString(`nodejs${major}_${arch}`)},`; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -237,4 +285,19 @@ node = module_extension( | |
| console.error(err); | ||
| } | ||
| }); | ||
|
Comment on lines
285
to
287
Contributor
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. The file writing operation Since this is a build/update script, using synchronous file writing ( Consider refactoring the file writing to use fs.writeFileSync("private/extensions/node.bzl", nodeArchives);Similarly, the test data file writing on line 245 should also be refactored to |
||
| }); | ||
| }; | ||
|
|
||
| if (require.main === module) { | ||
| main().catch((err) => { | ||
| console.error(err); | ||
| process.exit(1); | ||
| }); | ||
| } | ||
|
|
||
| module.exports = { | ||
| starlarkString, | ||
| validateArchiveSuffix, | ||
| validateDistrolessArch, | ||
| validateNodeVersion, | ||
| validateSha256, | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| #!/usr/bin/env node | ||
| const assert = require("node:assert/strict"); | ||
|
|
||
| const { | ||
| starlarkString, | ||
| validateArchiveSuffix, | ||
| validateDistrolessArch, | ||
| validateNodeVersion, | ||
| validateSha256, | ||
| } = require("./update_node_archives.js"); | ||
|
|
||
| assert.equal(validateNodeVersion("26.2.0"), "26.2.0"); | ||
| assert.throws( | ||
| () => validateNodeVersion('26.2.0" + "bad'), | ||
| /Invalid Node.js version/ | ||
| ); | ||
| assert.throws(() => validateNodeVersion("v26.2.0"), /Invalid Node.js version/); | ||
| assert.throws(() => validateNodeVersion("26.2.0\n26.2.1"), /Invalid Node.js version/); | ||
|
|
||
| assert.equal(validateArchiveSuffix("x64"), "x64"); | ||
| assert.throws(() => validateArchiveSuffix('x64" + fail("bad") + "'), /Invalid Node.js archive suffix/); | ||
|
|
||
| assert.equal(validateDistrolessArch("amd64"), "amd64"); | ||
| assert.throws(() => validateDistrolessArch("amd64\nbad"), /Invalid distroless architecture/); | ||
|
|
||
| assert.equal( | ||
| validateSha256("a".repeat(64)), | ||
| "a".repeat(64) | ||
| ); | ||
| assert.throws(() => validateSha256("not-a-sha"), /Invalid SHA256 checksum/); | ||
|
|
||
| assert.equal( | ||
| starlarkString('26.2.0" + "bad'), | ||
| '"26.2.0\\" + \\"bad"' | ||
| ); | ||
| assert.equal(starlarkString("line1\nline2"), '"line1\\nline2"'); | ||
|
|
||
| console.log("update_node_archives validation tests passed"); |
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.
If
fetchChecksumsfails to retrieve the checksum for any architecture (e.g., due to a network error or a 404 response), it logs the error but does not throw. This leavesnodeVersions[nodeVersion][key]undefined, which subsequently causes a silentTypeErrorwhen trying to accessarch.suffixon line 219.To make the script robust and fail-fast with a clear error message, we should validate that all expected version/architecture combinations were successfully fetched immediately after calling
fetchChecksums().