fix: guard provider gateway runtime dependencies
Pipelines as Code CI / hwlab-web-probe-sentinel-jd01- Failed
Pipelines as Code CI / hwlab-web-probe-sentinel-jd01- Failed
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM oven/bun:1-alpine
|
||||
FROM oven/bun:1.2.22-alpine
|
||||
ARG ALPINE_REPOSITORY=
|
||||
RUN if [ -n "$ALPINE_REPOSITORY" ]; then printf '%s\n' "$ALPINE_REPOSITORY/main" "$ALPINE_REPOSITORY/community" > /etc/apk/repositories; fi
|
||||
RUN apk add --no-cache bash docker-cli docker-cli-compose openssh-client
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
||||
import { createConnection, type Socket } from "node:net";
|
||||
import {
|
||||
@@ -1974,6 +1975,7 @@ function startHostSshSession(message: CoreHostSshOpenMessage): void {
|
||||
}
|
||||
try {
|
||||
const command = typeof message.command === "string" && message.command.length > 0 ? message.command : null;
|
||||
const commandDigest = command === null ? null : createHash("sha256").update(command).digest("hex").slice(0, 16);
|
||||
const allocateTty = typeof message.tty === "boolean" ? message.tty : command === null;
|
||||
const remoteScript = hostSshRemoteScript(command, message.cwd ?? null, allocateTty, message.cols, message.rows);
|
||||
const proc = Bun.spawn(["ssh", ...hostSshArgs(remoteScript, allocateTty)], {
|
||||
@@ -2009,7 +2011,16 @@ function startHostSshSession(message: CoreHostSshOpenMessage): void {
|
||||
hostSshSessions.delete(message.sessionId);
|
||||
releaseSshDataChannel(message.dataChannelId, message.sessionId);
|
||||
});
|
||||
logger("info", "host_ssh_session_started", { sessionId: message.sessionId, hasCommand: command !== null, tty: allocateTty, cwd: message.cwd ?? null, transport: "tcp-pool", dataChannelId: message.dataChannelId });
|
||||
logger("info", "host_ssh_session_started", {
|
||||
sessionId: message.sessionId,
|
||||
hasCommand: command !== null,
|
||||
commandBytes: command === null ? 0 : Buffer.byteLength(command),
|
||||
commandDigest,
|
||||
tty: allocateTty,
|
||||
cwd: message.cwd ?? null,
|
||||
transport: "tcp-pool",
|
||||
dataChannelId: message.dataChannelId,
|
||||
});
|
||||
} catch (error) {
|
||||
sendHostSshError(message.sessionId, error);
|
||||
releaseSshDataChannel(message.dataChannelId, message.sessionId);
|
||||
@@ -2155,6 +2166,21 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
` echo "rollback_to_last_known_good restored old gateway container name=$old_name image=$old_image reason=$reason" >&2`,
|
||||
`}`,
|
||||
].join("\n");
|
||||
const runtimeDependencyGuardScript = [
|
||||
`validate_gateway_runtime_dependencies() {`,
|
||||
` container="$1"`,
|
||||
` phase="$2"`,
|
||||
` docker exec "$container" /bin/sh -lc 'set -eu`,
|
||||
`test -x /bin/sh`,
|
||||
`test -x /usr/bin/ssh`,
|
||||
`/usr/bin/ssh -V >/tmp/unidesk-provider-ssh-version 2>&1 || { cat /tmp/unidesk-provider-ssh-version >&2; exit 41; }`,
|
||||
`command -v docker >/dev/null`,
|
||||
`docker version --format "{{.Client.Version}}" >/dev/null`,
|
||||
`test -e /lib/libz.so.1 -o -e /usr/lib/libz.so.1`,
|
||||
`command -v bun >/dev/null' || { echo "$phase provider-gateway runtime dependency guard failed" >&2; return 1; }`,
|
||||
` echo "$phase provider-gateway runtime dependency guard passed: container=$container"`,
|
||||
`}`,
|
||||
].join("\n");
|
||||
const script = [
|
||||
"set -eu",
|
||||
`cd ${shellQuote(workspace)}`,
|
||||
@@ -2174,6 +2200,7 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
`first_network=""`,
|
||||
`network_arg=""`,
|
||||
rollbackToLastKnownGoodScript,
|
||||
runtimeDependencyGuardScript,
|
||||
`if [ -n "$old_ids" ]; then docker update --restart always $old_ids >/dev/null 2>&1 || true; fi`,
|
||||
`if [ -z "$first_old" ]; then echo "no existing provider-gateway compose container found; cannot perform safe in-place upgrade" >&2; exit 1; fi`,
|
||||
`old_name=$(docker inspect --format '{{.Name}}' "$first_old")`,
|
||||
@@ -2211,6 +2238,7 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
`candidate_restart=$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' ${shellQuote(candidateName)})`,
|
||||
`candidate_pid_mode=$(docker inspect --format '{{.HostConfig.PidMode}}' ${shellQuote(candidateName)})`,
|
||||
`if [ "$candidate_pid_mode" != "host" ]; then echo "candidate runtime guard failed: restart=$candidate_restart pid=$candidate_pid_mode" >&2; docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true; rm -f "$candidate_env_file"; exit 1; fi`,
|
||||
`validate_gateway_runtime_dependencies ${shellQuote(candidateName)} candidate || { docker logs ${shellQuote(candidateName)} >&2 || true; docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true; rm -f "$candidate_env_file"; exit 1; }`,
|
||||
`if [ -n "$old_ids" ]; then docker rm -f $old_ids; fi`,
|
||||
`docker rm -f ${shellQuote(candidateName)} >/dev/null 2>&1 || true`,
|
||||
composeUpCommand.map(shellQuote).join(" "),
|
||||
@@ -2221,6 +2249,7 @@ function upgradePlan(taskId: string): Record<string, JsonValue> {
|
||||
`final_restart=$(docker inspect --format '{{.HostConfig.RestartPolicy.Name}}' "$final_container")`,
|
||||
`final_pid_mode=$(docker inspect --format '{{.HostConfig.PidMode}}' "$final_container")`,
|
||||
`if [ "$final_restart" != "always" ] || [ "$final_pid_mode" != "host" ]; then echo "final provider-gateway runtime guard failed: restart=$final_restart pid=$final_pid_mode" >&2; rollback_to_last_known_good "final-runtime-guard-failed" || true; rm -f "$candidate_env_file"; exit 1; fi`,
|
||||
`if ! validate_gateway_runtime_dependencies "$final_container" final; then rollback_to_last_known_good "final-runtime-dependency-guard-failed" || true; rm -f "$candidate_env_file"; exit 1; fi`,
|
||||
`final_attempt=0`,
|
||||
`final_validated=0`,
|
||||
`while [ "$final_attempt" -lt "${finalValidationAttempts}" ]; do final_logs=$(docker logs "$final_container" 2>&1 || true); final_has_open=0; final_has_ack=0; final_has_ok=0; case "$final_logs" in *${shellQuote(validationNeedleOpen)}*) final_has_open=1;; esac; case "$final_logs" in *${shellQuote(validationNeedleAck)}*) final_has_ack=1;; esac; case "$final_logs" in *${shellQuote(validationNeedleOk)}*) final_has_ok=1;; esac; if [ "$final_has_open" = "1" ] && [ "$final_has_ack" = "1" ] && [ "$final_has_ok" = "1" ]; then final_validated=1; break; fi; final_running=$(docker inspect --format '{{.State.Running}}' "$final_container" 2>/dev/null || true); if [ "$final_running" != "true" ]; then break; fi; final_attempt=$((final_attempt + 1)); sleep 2; done`,
|
||||
|
||||
Reference in New Issue
Block a user