Merge pull request #828 from pikasTech/fix/issue820-ls-output-visibility
fix(v02): widen device-pod job output visibility
This commit is contained in:
@@ -142,7 +142,7 @@ test("device pod executor lists and isolates multiple logical device pods", asyn
|
||||
});
|
||||
|
||||
test("device-pod executor bounds gateway output text", async () => {
|
||||
const longText = "z".repeat(25000);
|
||||
const longText = "z".repeat(65000);
|
||||
const cloudApi = createServer(async (request, response) => {
|
||||
const body = await requestJson(request);
|
||||
response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
||||
@@ -184,11 +184,11 @@ test("device-pod executor bounds gateway output text", async () => {
|
||||
});
|
||||
assert.equal(jobResponse.status, 202);
|
||||
const output = await waitForJobStatus(service.port, "device-pod-test", "job_bounded_output_test", "completed");
|
||||
assert.equal(output.output.text.length, 24000);
|
||||
assert.equal(output.text.length, 24000);
|
||||
assert.equal(output.bytes, 24000);
|
||||
assert.equal(output.output.text.length, 64000);
|
||||
assert.equal(output.text.length, 64000);
|
||||
assert.equal(output.bytes, 64000);
|
||||
assert.equal(output.truncation.truncated, true);
|
||||
assert.equal(output.truncation.originalBytes, 25000);
|
||||
assert.equal(output.truncation.originalBytes, 65000);
|
||||
assert.equal(output.output.omitted.reason, "device_job_output_truncated");
|
||||
assert.equal(JSON.stringify(output).includes(longText), false);
|
||||
} finally {
|
||||
|
||||
@@ -15,7 +15,7 @@ const environment = process.env.HWLAB_ENVIRONMENT || process.env.HWLAB_GITOPS_PR
|
||||
const cloudApiInternalUrl = normalizeBaseUrl(process.env.HWLAB_CLOUD_API_INTERNAL_URL || process.env.HWLAB_CLOUD_API_URL);
|
||||
const internalToken = textOr(process.env.HWLAB_DEVICE_POD_INTERNAL_TOKEN, "");
|
||||
const dispatchTimeoutMs = numberOr(process.env.HWLAB_DEVICE_POD_GATEWAY_DISPATCH_TIMEOUT_MS, 120000);
|
||||
const DEVICE_JOB_OUTPUT_MAX_BYTES = 24000;
|
||||
const DEVICE_JOB_OUTPUT_MAX_BYTES = 64000;
|
||||
const jobs = new Map();
|
||||
|
||||
listen(createServer(async (request, response) => {
|
||||
|
||||
@@ -2055,7 +2055,7 @@ test("cloud api dispatches authorized device jobs to the internal device-pod exe
|
||||
});
|
||||
|
||||
test("cloud api bounds device-pod job output payloads", async () => {
|
||||
const longText = "x".repeat(25000);
|
||||
const longText = "x".repeat(65000);
|
||||
const executor = createServer(async (request, response) => {
|
||||
const body = await requestJson(request);
|
||||
response.writeHead(200, { "content-type": "application/json; charset=utf-8" });
|
||||
@@ -2100,11 +2100,11 @@ test("cloud api bounds device-pod job output payloads", async () => {
|
||||
assert.equal(job.status, 200);
|
||||
const output = await getJson(port, `/v1/device-pods/device-pod-71-freq/jobs/${job.body.job.id}/output`, aliceLogin.cookie);
|
||||
assert.equal(output.status, 200);
|
||||
assert.equal(output.body.bytes, 24000);
|
||||
assert.equal(output.body.text.length, 24000);
|
||||
assert.equal(output.body.output.text.length, 24000);
|
||||
assert.equal(output.body.bytes, 64000);
|
||||
assert.equal(output.body.text.length, 64000);
|
||||
assert.equal(output.body.output.text.length, 64000);
|
||||
assert.equal(output.body.truncation.truncated, true);
|
||||
assert.equal(output.body.truncation.originalBytes, 25000);
|
||||
assert.equal(output.body.truncation.originalBytes, 65000);
|
||||
assert.equal(output.body.output.omitted.reason, "device_job_output_truncated");
|
||||
assert.equal(JSON.stringify(output.body).includes(longText), false);
|
||||
} finally {
|
||||
|
||||
@@ -202,7 +202,7 @@ const DEVICE_JOB_ACTIONABLE_INTENTS = new Set([
|
||||
"workspace.build",
|
||||
"debug.download"
|
||||
]);
|
||||
const DEVICE_JOB_OUTPUT_MAX_BYTES = 24000;
|
||||
const DEVICE_JOB_OUTPUT_MAX_BYTES = 64000;
|
||||
const DEVICE_POD_PROFILE_SECRET_KEY_PATTERN = /(?:token|secret|password|passphrase|credential|api[_-]?key|access[_-]?key|private[_-]?key|git[_-]?key|cloud[_-]?token|kubeconfig|database[_-]?url|db[_-]?url|connection[_-]?string)/iu;
|
||||
const DEVICE_POD_PROFILE_SECRET_VALUE_PATTERN = /(?:-----BEGIN [A-Z ]*PRIVATE KEY-----|postgres(?:ql)?:\/\/|mysql:\/\/|mongodb(?:\+srv)?:\/\/|redis:\/\/|gh[pousr]_[A-Za-z0-9_]{20,}|sk-[A-Za-z0-9_-]{20,})/u;
|
||||
|
||||
|
||||
@@ -290,6 +290,7 @@ test("device-pod-cli bootsharp creates a formal workspace bootsharp REST job", a
|
||||
});
|
||||
|
||||
test("device-pod-cli job output is compact by default and full with --full", async () => {
|
||||
const longText = `${"x".repeat(30000)}FREQ_Controller_FW.axf${"y".repeat(30000)}FREQ_Controller_FW.hex${"z".repeat(30000)}`;
|
||||
const payload = {
|
||||
serviceId: "hwlab-cloud-api",
|
||||
contractVersion: "device-pod-job-v1",
|
||||
@@ -299,9 +300,9 @@ test("device-pod-cli job output is compact by default and full with --full", asy
|
||||
operationId: "op_job",
|
||||
job: { id: "job_1", status: "completed" },
|
||||
output: {
|
||||
text: "x".repeat(5000),
|
||||
text: longText,
|
||||
dispatch: { command: "verbose command that should stay out of compact output" },
|
||||
bytes: 5000,
|
||||
bytes: Buffer.byteLength(longText, "utf8"),
|
||||
summary: "gateway/device-host-cli dispatch completed"
|
||||
}
|
||||
};
|
||||
@@ -313,7 +314,10 @@ test("device-pod-cli job output is compact by default and full with --full", asy
|
||||
], { fetchImpl, now: () => "2026-05-31T00:00:00.000Z" });
|
||||
assert.equal(compact.exitCode, 0);
|
||||
assert.equal(compact.payload.body.compacted, true);
|
||||
assert.equal(compact.payload.body.text.length, 4000);
|
||||
assert.equal(compact.payload.body.text.length, 64000);
|
||||
assert.equal(compact.payload.body.text.includes("device-pod job output compacted"), true);
|
||||
assert.equal(compact.payload.body.text.includes("FREQ_Controller_FW.hex"), true);
|
||||
assert.equal(compact.payload.body.text.includes("FREQ_Controller_FW.axf"), true);
|
||||
assert.equal(JSON.stringify(compact.payload).includes("verbose command"), false);
|
||||
|
||||
const full = await runAssembledDevicePodCli([
|
||||
|
||||
@@ -766,9 +766,9 @@ function compactJobOutputBody(body: any) {
|
||||
if (!body || typeof body !== "object") return body;
|
||||
const output = body.output && typeof body.output === "object" ? body.output : {};
|
||||
const textValue = typeof body.text === "string" ? body.text : typeof output.text === "string" ? output.text : "";
|
||||
const maxTextBytes = 4000;
|
||||
const maxTextBytes = 64000;
|
||||
const textBuffer = Buffer.from(textValue, "utf8");
|
||||
const clippedText = textBuffer.length > maxTextBytes ? textBuffer.subarray(0, maxTextBytes).toString("utf8") : textValue;
|
||||
const clippedText = compactTextPreservingTail(textBuffer, textValue, maxTextBytes);
|
||||
return clean({
|
||||
serviceId: body.serviceId,
|
||||
contractVersion: body.contractVersion,
|
||||
@@ -800,3 +800,17 @@ function compactJobOutputBody(body: any) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function compactTextPreservingTail(textBuffer: Buffer, textValue: string, maxTextBytes: number) {
|
||||
if (textBuffer.length <= maxTextBytes) return textValue;
|
||||
const marker = "\n... device-pod job output compacted; middle omitted ...\n";
|
||||
const markerBuffer = Buffer.from(marker, "utf8");
|
||||
if (markerBuffer.length >= maxTextBytes) return clipUtf8(textBuffer, 0, maxTextBytes);
|
||||
const headBytes = Math.floor((maxTextBytes - markerBuffer.length) / 2);
|
||||
const tailBytes = maxTextBytes - markerBuffer.length - headBytes;
|
||||
return `${clipUtf8(textBuffer, 0, headBytes)}${marker}${clipUtf8(textBuffer, textBuffer.length - tailBytes, textBuffer.length)}`;
|
||||
}
|
||||
|
||||
function clipUtf8(buffer: Buffer, start: number, end: number) {
|
||||
return buffer.subarray(start, end).toString("utf8").replace(/\uFFFD$/u, "").replace(/^\uFFFD/u, "");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user