fix: remove npm dependency install from go env images

This commit is contained in:
UniDesk Codex
2026-07-07 00:23:35 +08:00
parent 2738ddf220
commit 56ea5199f3
3 changed files with 117 additions and 11 deletions
@@ -0,0 +1,95 @@
import { spawn, spawnSync } from "node:child_process";
import { existsSync, mkdirSync, rmSync } from "node:fs";
import path from "node:path";
const bootRepo = requiredEnv("HWLAB_BOOT_REPO");
const bootCommit = requiredEnv("HWLAB_BOOT_COMMIT");
const bootSh = requiredEnv("HWLAB_BOOT_SH");
const bootRef = requiredEnv("HWLAB_BOOT_REF");
const checkoutDir = process.env.HWLAB_BOOT_CHECKOUT_DIR || "/workspace/hwlab-boot/repo";
if (!/^[a-f0-9]{40}$/u.test(bootCommit)) fail("HWLAB_BOOT_COMMIT must be a full 40-char SHA");
if (path.isAbsolute(bootSh) || bootSh.split("/").includes("..")) fail("HWLAB_BOOT_SH must be repo-relative");
if (!/^[A-Za-z0-9._/-]+$/u.test(bootRef) || bootRef.startsWith("-") || bootRef.includes("..")) {
fail("HWLAB_BOOT_REF must be a safe branch or ref name");
}
const readUrl = process.env.HWLAB_BOOT_READ_URL || mirrorReadUrl(bootRepo);
mkdirSync(path.dirname(checkoutDir), { recursive: true });
rmSync(checkoutDir, { recursive: true, force: true });
run("git", ["clone", "--no-checkout", "--single-branch", "--branch", bootRef, readUrl, checkoutDir], "/");
run("git", ["checkout", "--detach", bootCommit], checkoutDir);
const scriptPath = path.join(checkoutDir, bootSh);
if (!existsSync(scriptPath)) fail(`boot script not found: ${bootSh}`);
run("sh", [scriptPath], checkoutDir, { replace: true });
function requiredEnv(name) {
const value = process.env[name];
if (!value) fail(`${name} is required`);
return value;
}
function mirrorReadUrl(repo) {
if (/github\.com[:/]pikasTech\/HWLAB(?:\.git)?$/u.test(repo)) {
return "http://git-mirror-http.devops-infra.svc.cluster.local/pikasTech/HWLAB.git";
}
fail("no mirror resolver for HWLAB_BOOT_REPO; refusing direct runtime GitHub fallback");
}
function run(command, args, cwd, options = {}) {
if (options.replace) {
runReplacingProcess(command, args, cwd);
return;
}
const result = spawnSync(command, args, { cwd, env: process.env, stdio: "inherit" });
if (result.error) fail(`${command} failed to start: ${result.error.message}`);
if (result.signal) process.kill(process.pid, result.signal);
const code = result.status ?? 0;
if (code !== 0) process.exit(code);
}
function runReplacingProcess(command, args, cwd) {
const child = spawn(command, args, { cwd, env: process.env, stdio: "inherit", detached: true });
let settled = false;
const signalHandlers = [];
const cleanup = () => {
for (const { signal, handler } of signalHandlers.splice(0)) process.off(signal, handler);
};
const forwardSignal = (signal) => {
if (!child.pid) return;
try {
process.kill(-child.pid, signal);
} catch {
try { child.kill(signal); } catch {}
}
};
for (const signal of ["SIGINT", "SIGTERM"]) {
const handler = () => forwardSignal(signal);
process.on(signal, handler);
signalHandlers.push({ signal, handler });
}
child.on("error", (error) => {
if (settled) return;
settled = true;
cleanup();
fail(`${command} failed to start: ${error.message}`);
});
child.on("exit", (code, signal) => {
if (settled) return;
settled = true;
cleanup();
if (signal) process.exit(signalExitCode(signal));
process.exit(code ?? 0);
});
}
function signalExitCode(signal) {
const signals = { SIGHUP: 1, SIGINT: 2, SIGQUIT: 3, SIGTERM: 15 };
return 128 + (signals[signal] ?? 1);
}
function fail(message) {
process.stderr.write(`${JSON.stringify({ status: "failed", error: "hwlab_go_env_reuse_launcher_failed", message })}\n`);
process.exit(1);
}
+18 -10
View File
@@ -908,6 +908,7 @@ function envReuseDockerfile(baseImage, labels = [], recipe, service = null) {
const npmConfigScript = npmDownloadConfigScript(normalizedRecipe.downloadStack);
const goService = service?.runtimeKind === "go-service" || service?.artifactKind === "go-service";
const goCacheEnabled = Boolean(goService && normalizedRecipe.runtimeGoModCachePath && normalizedRecipe.runtimeGoBuildCachePath);
const goLauncherPath = "deploy/runtime/launcher/hwlab-go-env-reuse-launcher.mjs";
const goProxy = normalizedRecipe.downloadStack.goProxy || "https://proxy.golang.org,direct";
const goEnv = goCacheEnabled ? [
`ENV GOMODCACHE=${normalizedRecipe.runtimeGoModCachePath}`,
@@ -944,24 +945,29 @@ function envReuseDockerfile(baseImage, labels = [], recipe, service = null) {
const osPackageSections = [aptInstallSection("base", baseOsPackages), goPackageSection].filter(Boolean).join("; ");
const osPackageScript = `set -eu; export DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC; ${aptRetryHelpers}; ${osPackageSections}; rm -rf /var/lib/apt/lists/*`;
const aliasScript = normalizedRecipe.hwpodAliases.map((alias) => `printf '%s\\n' '#!/usr/bin/env sh' 'exec node /workspace/hwlab-boot/repo/scripts/run-bun.mjs /workspace/hwlab-boot/repo/${alias.script} \"$@\"' > /usr/local/bin/${alias.command}; chmod 755 /usr/local/bin/${alias.command}`).join("; ");
const readinessScript = `set -eu; command -v node >/dev/null; command -v npm >/dev/null; command -v git >/dev/null; command -v sh >/dev/null; test -d ${shellQuote(normalizedRecipe.runtimeNodeModulesPath)}; test -x /usr/local/bin/bun; /usr/local/bin/bun --version >/tmp/hwlab-env-bun-version.txt${goReadiness}; ${aliasScript}`;
const readinessScript = goCacheEnabled
? `set -eu; command -v node >/dev/null; command -v git >/dev/null; command -v sh >/dev/null${goReadiness}; test -f /usr/local/bin/hwlab-go-env-reuse-launcher.mjs`
: `set -eu; command -v node >/dev/null; command -v npm >/dev/null; command -v git >/dev/null; command -v sh >/dev/null; test -d ${shellQuote(normalizedRecipe.runtimeNodeModulesPath)}; test -x /usr/local/bin/bun; /usr/local/bin/bun --version >/tmp/hwlab-env-bun-version.txt; ${aliasScript}`;
return [
`FROM ${baseImage}`,
`WORKDIR ${runtimeRoot}`,
...labels.map(([name, value]) => `LABEL ${name}=${dockerfileQuote(value)}`),
"COPY package.json ./package.json",
"COPY package-lock.json* ./",
...(goCacheEnabled ? [] : ["COPY package.json ./package.json", "COPY package-lock.json* ./"]),
`RUN ${downloadEnvPrefix}${osPackageScript}`,
`RUN ${downloadEnvPrefix}${downloadDependencyScript}`,
...(goCacheEnabled ? [] : [`RUN ${downloadEnvPrefix}${downloadDependencyScript}`]),
...(goCacheEnabled ? ["COPY go.mod ./go.mod", "COPY go.sum* ./"] : []),
...(goDownloadScript ? [goDownloadScript] : []),
`COPY ${normalizedRecipe.launcherPath} /usr/local/bin/hwlab-env-reuse-launcher.ts`,
goCacheEnabled
? `COPY ${goLauncherPath} /usr/local/bin/hwlab-go-env-reuse-launcher.mjs`
: `COPY ${normalizedRecipe.launcherPath} /usr/local/bin/hwlab-env-reuse-launcher.ts`,
`RUN ${readinessScript}`,
"ENV HWLAB_RUNTIME_MODE=env-reuse-git-mirror-checkout",
`ENV HWLAB_RUNTIME_NODE_MODULES=${normalizedRecipe.runtimeNodeModulesPath}`,
...(goCacheEnabled ? [] : [`ENV HWLAB_RUNTIME_NODE_MODULES=${normalizedRecipe.runtimeNodeModulesPath}`]),
...goEnv,
"ENV PATH=/usr/local/bin:$PATH",
"CMD [\"/usr/local/bin/bun\", \"/usr/local/bin/hwlab-env-reuse-launcher.ts\"]",
goCacheEnabled
? "CMD [\"node\", \"/usr/local/bin/hwlab-go-env-reuse-launcher.mjs\"]"
: "CMD [\"/usr/local/bin/bun\", \"/usr/local/bin/hwlab-env-reuse-launcher.ts\"]",
""
].join("\n");
}
@@ -983,13 +989,15 @@ async function copyRepoFileToContext(contextDir, repoRelativePath, { required =
async function prepareEnvReuseBuildContext(tmpDir, normalizedRecipe, goCacheEnabled) {
const contextDir = path.join(tmpDir, "context");
await mkdir(contextDir, { recursive: true });
await copyRepoFileToContext(contextDir, "package.json");
await copyRepoFileToContext(contextDir, "package-lock.json", { required: false });
if (!goCacheEnabled) {
await copyRepoFileToContext(contextDir, "package.json");
await copyRepoFileToContext(contextDir, "package-lock.json", { required: false });
}
if (goCacheEnabled) {
await copyRepoFileToContext(contextDir, "go.mod");
await copyRepoFileToContext(contextDir, "go.sum", { required: false });
}
await copyRepoFileToContext(contextDir, normalizedRecipe.launcherPath);
await copyRepoFileToContext(contextDir, goCacheEnabled ? "deploy/runtime/launcher/hwlab-go-env-reuse-launcher.mjs" : normalizedRecipe.launcherPath);
return contextDir;
}
+4 -1
View File
@@ -362,7 +362,10 @@ test("artifact reuse paths preserve catalog provenance instead of current planne
const artifactPublish = await readFile("scripts/artifact-publish.mjs", "utf8");
assert.match(artifactPublish, /downloadDependencyScript/u);
assert.match(artifactPublish, /RUN \$\{downloadEnvPrefix\}\$\{downloadDependencyScript\}/u);
assert.match(artifactPublish, /RUN \$\{downloadEnvPrefix\}\$\{downloadDependencyScript\}`,[\s\S]{0,120}\["COPY go\.mod \.\/go\.mod", "COPY go\.sum\* \.\/"\]/u);
assert.match(artifactPublish, /goLauncherPath = "deploy\/runtime\/launcher\/hwlab-go-env-reuse-launcher\.mjs"/u);
assert.match(artifactPublish, /goCacheEnabled \? \[\] : \["COPY package\.json \.\/package\.json", "COPY package-lock\.json\* \.\/"\]/u);
assert.match(artifactPublish, /goCacheEnabled \? \[\] : \[`RUN \$\{downloadEnvPrefix\}\$\{downloadDependencyScript\}`\]/u);
assert.match(artifactPublish, /CMD \[\\"node\\", \\"\/usr\/local\/bin\/hwlab-go-env-reuse-launcher\.mjs\\"\]/u);
assert.match(artifactPublish, /ln -sf "\$\{runtimeRoot\}\/\$bun_bin" \/usr\/local\/bin\/bun/u);
assert.doesNotMatch(artifactPublish, /RUN \$\{dependencyScript\}/u);
assert.match(artifactPublish, /const catalogProvenance = serviceImageCatalogProvenance\(catalogService\);/u);