fix(code-queue): add issue 3 gateway diagnostics

This commit is contained in:
Codex
2026-05-19 03:32:16 +00:00
parent c00c695197
commit 9d46ca2531
18 changed files with 1494 additions and 39 deletions
+103 -1
View File
@@ -853,6 +853,40 @@ function buildImageTag(service: UniDeskMicroserviceConfig): string {
return `unidesk-${service.id}:${service.providerId.toLowerCase()}`;
}
export function codeQueueContainerdImagePreflight(imageListText: string, expectedImage: string): { ok: boolean; expectedImage: string; matchedLine: string | null; error: string | null } {
const matchedLine = imageListText
.split(/\r?\n/u)
.map((line) => line.trim())
.find((line) => line.length > 0 && line.includes(expectedImage)) ?? null;
return matchedLine === null
? {
ok: false,
expectedImage,
matchedLine: null,
error: `native k3s containerd is missing required image tag: ${expectedImage}`,
}
: { ok: true, expectedImage, matchedLine, error: null };
}
export function codeQueueManifestImagePreflight(manifestText: string, expectedImage: string): { ok: boolean; expectedImage: string; objects: Array<{ kind: string; name: string; images: string[]; ok: boolean }>; errors: string[] } {
const requiredObjectNames = new Set(["code-queue", "code-queue-read", "code-queue-write", "d601-provider-egress-proxy", "d601-tcp-egress-gateway"]);
const objects: Array<{ kind: string; name: string; images: string[]; ok: boolean }> = [];
for (const documentText of manifestText.split(/^---\s*$/mu)) {
const kind = documentText.match(/^kind:\s*(\S+)\s*$/mu)?.[1] ?? "";
if (kind !== "Deployment") continue;
const deploymentName = documentText.match(/^metadata:\s*$[\s\S]*?^\s{2}name:\s*([A-Za-z0-9_.-]+)\s*$/mu)?.[1] ?? "";
if (!requiredObjectNames.has(deploymentName)) continue;
const images = Array.from(documentText.matchAll(/^\s*image:\s*(?:"([^"]+)"|([^\s#]+))/gmu)).map((match) => match[1] ?? match[2] ?? "");
objects.push({ kind, name: deploymentName, images, ok: images.length > 0 && images.every((image) => image === expectedImage) });
}
const presentNames = new Set(objects.map((object) => object.name));
const errors = [
...Array.from(requiredObjectNames).filter((name) => !presentNames.has(name)).map((name) => `required deployment missing from manifest: ${name}`),
...objects.filter((object) => !object.ok).map((object) => `deployment ${object.name} must use only ${expectedImage}; found ${object.images.join(",") || "<none>"}`),
];
return { ok: errors.length === 0, expectedImage, objects, errors };
}
function directComposeFile(service: UniDeskMicroserviceConfig): string {
return targetIsMain(service)
? rootPath("docker-compose.yml")
@@ -1554,17 +1588,54 @@ function ensureNativeK3sScript(): string {
function importK3sImageScript(service: UniDeskMicroserviceConfig): string {
const image = buildImageTag(service);
const archive = `/tmp/unidesk-${safeId(service.id)}-k3s-image.tar`;
const manifest = `${targetWorkDir(service)}/${k8sManifestPath(service)}`;
return [
"set -euo pipefail",
...rootAccessPrelude(),
`image=${shellQuote(image)}`,
`archive=${shellQuote(archive)}`,
`manifest=${shellQuote(manifest)}`,
"docker image inspect \"$image\" >/dev/null",
"if [ -f \"$manifest\" ]; then",
" bad_manifest_images=$(python3 - \"$manifest\" \"$image\" <<'PY'",
"import re, sys",
"path, expected = sys.argv[1], sys.argv[2]",
"required = {'code-queue','code-queue-read','code-queue-write','d601-provider-egress-proxy','d601-tcp-egress-gateway'}",
"text = open(path, encoding='utf-8').read()",
"bad = []",
"seen = set()",
"for doc in re.split(r'(?m)^---\\s*$', text):",
" if not re.search(r'(?m)^kind:\\s*Deployment\\s*$', doc):",
" continue",
" match = re.search(r'(?ms)^metadata:\\s*$.*?^ name:\\s*([A-Za-z0-9_.-]+)\\s*$', doc)",
" name = match.group(1) if match else ''",
" if name not in required:",
" continue",
" seen.add(name)",
" images = re.findall(r'(?m)^\\s*image:\\s*\"?([^\"\\s#]+)\"?', doc)",
" if not images or any(image != expected for image in images):",
" found = ','.join(images) if images else '<none>'",
" bad.append(f'{name}:{found}')",
"missing = sorted(required - seen)",
"bad.extend(f'{name}:<missing>' for name in missing)",
"print('\\n'.join(bad))",
"PY",
" )",
" if [ -n \"$bad_manifest_images\" ]; then",
" printf 'code_queue_manifest_image_preflight_failed image=%s\\n%s\\n' \"$image\" \"$bad_manifest_images\" >&2",
" exit 1",
" fi",
" echo code_queue_manifest_image_preflight=ok image=$image",
"fi",
"rm -f \"$archive\"",
"docker save \"$image\" -o \"$archive\"",
`root_exec ctr --address ${shellQuote(nativeK3sCtrAddress)} -n k8s.io images import "$archive"`,
"rm -f \"$archive\"",
`root_exec ctr --address ${shellQuote(nativeK3sCtrAddress)} -n k8s.io images ls | grep -F "$image" || true`,
`if ! root_exec ctr --address ${shellQuote(nativeK3sCtrAddress)} -n k8s.io images ls | grep -F "$image"; then`,
" printf 'native_k3s_containerd_image_missing=%s\\n' \"$image\" >&2",
" exit 1",
"fi",
"printf 'native_k3s_containerd_image_present=%s\\n' \"$image\"",
].join("\n");
}
@@ -1621,6 +1692,35 @@ function applyK8sScript(service: UniDeskMicroserviceConfig): string {
].filter(Boolean).join("\n");
}
function verifyK8sImagesScript(service: UniDeskMicroserviceConfig): string {
const namespace = k8sNamespaceForService(service);
const image = buildImageTag(service);
const deployments = k8sDeploymentsForService(service);
return [
"set -euo pipefail",
`image=${shellQuote(image)}`,
`namespace=${shellQuote(namespace)}`,
`deployments=(${deployments.map(shellQuote).join(" ")})`,
"for deployment in \"${deployments[@]}\"; do",
` actual=$(KUBECONFIG=${shellQuote(k8sKubeconfig)} kubectl -n "$namespace" get deployment "$deployment" -o jsonpath='{range .spec.template.spec.containers[*]}{.image}{\"\\n\"}{end}')`,
" if [ -z \"$actual\" ]; then",
" echo \"deployment_image_missing=$deployment\" >&2",
" exit 1",
" fi",
" while IFS= read -r actual_image; do",
" [ -z \"$actual_image\" ] && continue",
" if [ \"$actual_image\" != \"$image\" ]; then",
" printf 'deployment_image_mismatch deployment=%s expected=%s actual=%s\\n' \"$deployment\" \"$image\" \"$actual_image\" >&2",
" exit 1",
" fi",
" done <<EOF",
"$actual",
"EOF",
" printf 'deployment_image_ok deployment=%s image=%s\\n' \"$deployment\" \"$image\"",
"done",
].join("\n");
}
function stampK8sScript(service: UniDeskMicroserviceConfig, desired: DeployManifestService, resolvedCommit: string): string {
const deployments = k8sDeploymentsForService(service).map((name) => `deployment/${name}`);
const namespace = k8sNamespaceForService(service);
@@ -2223,6 +2323,8 @@ async function applyOneService(config: UniDeskConfig, service: UniDeskMicroservi
if (!pushStep(steps, imageImport)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
const apply = await step(config, service, "kubectl-apply", applyK8sScript(service), targetWorkDir(service), 60_000, false);
if (!pushStep(steps, apply)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
const k8sImages = await step(config, service, "k8s-image-preflight", verifyK8sImagesScript(service), targetWorkDir(service), 60_000, false);
if (!pushStep(steps, k8sImages)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
const stamp = await step(config, service, "stamp-deploy-commit", stampK8sScript(service, desired, resolvedCommit), targetWorkDir(service), 60_000, false);
if (!pushStep(steps, stamp)) return { ok: false, serviceId: service.id, startedAt, finishedAt: nowIso(), resolvedCommit, before, steps };
const rollout = await step(config, service, "rollout", rolloutK8sScript(service), targetWorkDir(service), 240_000, true);