fix: expose PikaOA runtime query failures

This commit is contained in:
Codex
2026-07-14 20:48:33 +02:00
parent f88d4a16f0
commit 9800ca5ca0
3 changed files with 59 additions and 7 deletions
+37 -1
View File
@@ -80,6 +80,10 @@ if printf '%s' "$args" | grep -q ' wait '; then
fi
if printf '%s' "$args" | grep -Eq '(^| )get namespace '; then
name="$(namespace_after_resource "$@")"
if [ -f "$root/fail-runtime-query" ]; then
printf '%s\n' 'unable to connect to the server' >&2
exit 1
fi
if [ ! -f "$root/namespaces/$name" ]; then
printf 'Error from server (NotFound): namespaces "%s" not found\n' "$name" >&2
exit 1
@@ -92,6 +96,10 @@ if printf '%s' "$args" | grep -Eq '(^| )get namespace '; then
exit 0
fi
if printf '%s' "$args" | grep -q ' get deployment,statefulset,job,service,persistentvolumeclaim,pod '; then
if [ -f "$root/fail-runtime-resource-query" ]; then
printf '%s\n' 'resource query failed' >&2
exit 1
fi
printf '%s\n' '{"items":[]}'
exit 0
fi
@@ -108,6 +116,8 @@ exit 64
const fixturePath = join(root, "pikaoa.yaml");
const fixture = readFileSync(rootPath("config", "fixtures", "pikaoa-test-target.yaml"), "utf8")
.replace("validationOnly: true", "validationOnly: false")
.replace("statusRequestTimeoutSeconds: 3", "statusRequestTimeoutSeconds: 31")
.replace("logTailLines: 8", "logTailLines: 101")
.replace("/tmp/unidesk-pikaoa-test-target-fixture", stateRoot);
writeFileSync(fixturePath, fixture);
@@ -155,13 +165,39 @@ exit 64
const submitted = await projection("start", "success", true);
assert.equal(submitted.code, "start-submitted", JSON.stringify(lastCapture));
assert.equal(submitted.mutation, true);
assert.deepEqual((submitted.target as Record<string, unknown>).taskState, {
rootPath: stateRoot,
statusRequestTimeoutSeconds: 31,
logTailLines: 101,
});
const running = await projection("status", "success");
assert.equal((running.status as Record<string, unknown>).code, "start-running");
rmSync(join(fakeRoot, "slow"));
const succeeded = await waitFor("success", "start-succeeded");
const succeededStatus = succeeded.status as Record<string, unknown>;
assert.equal((succeededStatus.task as Record<string, unknown>).exitCode, 0);
assert.ok((succeededStatus.logTail as unknown[]).length <= 8);
assert.ok((succeededStatus.logTail as unknown[]).length <= 101);
writeFileSync(join(fakeRoot, "fail-runtime-query"), "1\n");
const runtimeQueryFailed = await projection("status", "success");
const runtimeQueryFailedStatus = runtimeQueryFailed.status as Record<string, unknown>;
assert.equal(runtimeQueryFailed.ok, false);
assert.equal(runtimeQueryFailedStatus.code, "runtime-query-failed");
assert.equal(runtimeQueryFailedStatus.taskCode, "start-succeeded");
assert.equal((runtimeQueryFailedStatus.runtime as Record<string, unknown>).code, "runtime-query-failed");
const runtimeQueryFailedText = await runPikaoaCommand(config, args("status", "success").slice(0, -2), localCapture);
assert.match(runtimeQueryFailedText.renderedText, /status=runtime-query-failed/);
rmSync(join(fakeRoot, "fail-runtime-query"));
writeFileSync(join(fakeRoot, "fail-runtime-resource-query"), "1\n");
const runtimeResourceQueryFailed = await projection("status", "success");
const runtimeResourceQueryFailedStatus = runtimeResourceQueryFailed.status as Record<string, unknown>;
assert.equal(runtimeResourceQueryFailed.ok, false);
assert.equal(runtimeResourceQueryFailedStatus.code, "runtime-resource-query-failed");
assert.equal(runtimeResourceQueryFailedStatus.taskCode, "start-succeeded");
assert.equal((runtimeResourceQueryFailedStatus.runtime as Record<string, unknown>).code, "runtime-resource-query-failed");
rmSync(join(fakeRoot, "fail-runtime-resource-query"));
const repeated = await projection("start", "success", true);
assert.equal(repeated.code, "start-already-succeeded");
assert.equal(repeated.mutation, false);
+21 -5
View File
@@ -299,8 +299,8 @@ function parseTarget(id: string, root: Record<string, unknown>, configLabel: str
fieldManager: kubernetesName(nonEmpty(root.fieldManager, `${path}.fieldManager`), `${path}.fieldManager`, 63),
taskState: {
rootPath: taskStateRoot(taskState.rootPath, `${path}.taskState.rootPath`),
statusRequestTimeoutSeconds: positiveInteger(taskState.statusRequestTimeoutSeconds, `${path}.taskState.statusRequestTimeoutSeconds`, 30),
logTailLines: positiveInteger(taskState.logTailLines, `${path}.taskState.logTailLines`, 100),
statusRequestTimeoutSeconds: positiveSafeInteger(taskState.statusRequestTimeoutSeconds, `${path}.taskState.statusRequestTimeoutSeconds`),
logTailLines: positiveSafeInteger(taskState.logTailLines, `${path}.taskState.logTailLines`),
},
cleanup: {
mode: exactString(cleanup.mode, `${path}.cleanup.mode`, "delete-namespace") as "delete-namespace",
@@ -1124,14 +1124,24 @@ else:
code = {"queued": "start-running", "running": "start-running", "succeeded": "start-succeeded", "failed": "start-failed", "canceled": "start-canceled"}.get(state, "start-state-unknown")
ok = state not in ("failed", "canceled", "unknown")
exists = True
task_code = code
runtime = {"queried": True, "exists": runtime_code in ("runtime-present", "runtime-resource-query-failed"), "code": runtime_code}
for key, path in (("namespace", namespace_path), ("resources", resources_path)):
if path.exists():
runtime_paths = []
if runtime_code in ("runtime-present", "runtime-resource-query-failed"):
runtime_paths.append(("namespace", namespace_path))
if runtime_code == "runtime-present":
runtime_paths.append(("resources", resources_path))
for key, path in runtime_paths:
if path.exists() and path.stat().st_size > 0:
try:
runtime[key] = json.loads(path.read_text())
except Exception:
runtime["code"] = "runtime-response-unreadable"
payload = {"ok": ok, "mutation": False, "code": code, "jobId": job_id, "taskExists": exists, "task": task, "runnerAlive": runner_alive, "logTail": events, "runtime": runtime, "valuesPrinted": False}
else:
runtime["code"] = "runtime-response-unreadable"
if runtime["code"] in ("runtime-query-failed", "runtime-resource-query-failed", "runtime-response-unreadable"):
code, ok = runtime["code"], False
payload = {"ok": ok, "mutation": False, "code": code, "taskCode": task_code, "jobId": job_id, "taskExists": exists, "task": task, "runnerAlive": runner_alive, "logTail": events, "runtime": runtime, "valuesPrinted": False}
print(json.dumps(payload, separators=(",", ":")))
PY
`;
@@ -1213,6 +1223,7 @@ function summarizeRemoteStatus(parsed: Record<string, unknown>, target: TestTarg
return {
ok: parsed.ok === true,
code: parsed.code ?? "start-status-unknown",
taskCode: parsed.taskCode ?? "start-status-unknown",
jobId: parsed.jobId ?? asyncJobIdentity(target, context).id,
taskExists: parsed.taskExists === true,
task: optionalRecord(parsed.task, "status.task"),
@@ -1540,6 +1551,11 @@ function positiveInteger(value: unknown, path: string, max: number): number {
return Number(value);
}
function positiveSafeInteger(value: unknown, path: string): number {
if (!Number.isSafeInteger(value) || Number(value) <= 0) throw new Error(`${path} 必须是正安全整数`);
return Number(value);
}
function nonNegativeInteger(value: unknown, path: string): number {
if (!Number.isSafeInteger(value) || Number(value) < 0) throw new Error(`${path} 必须是非负安全整数`);
return Number(value);