fix(plugin): address kubernetes greptile timeouts

This commit is contained in:
Dotta
2026-05-12 12:45:46 -05:00
committed by Chris Farhood
parent 39d81c732c
commit 45621aac53
5 changed files with 22 additions and 10 deletions
@@ -92,6 +92,7 @@ export function buildNetworkPolicyManifests(input: BuildNetworkPolicyInput): Rec
: []), : []),
...input.egressAllowCidrs.map((cidr) => ({ ...input.egressAllowCidrs.map((cidr) => ({
to: [{ ipBlock: { cidr } }], to: [{ ipBlock: { cidr } }],
ports: [{ protocol: "TCP", port: 443 }],
})), })),
], ],
}, },
@@ -490,7 +490,7 @@ const plugin = definePlugin({
return { return {
exitCode: execResult.exitCode, exitCode: execResult.exitCode,
timedOut: false, timedOut: execResult.timedOut,
stdout: execResult.stdout, stdout: execResult.stdout,
stderr: execResult.stderr, stderr: execResult.stderr,
metadata: { metadata: {
@@ -14,6 +14,13 @@ import { Exec } from "@kubernetes/client-node";
import { PassThrough } from "node:stream"; import { PassThrough } from "node:stream";
import type { KubeConfig } from "@kubernetes/client-node"; import type { KubeConfig } from "@kubernetes/client-node";
export interface ExecInPodResult {
exitCode: number;
timedOut: boolean;
stdout: string;
stderr: string;
}
export async function execInPod( export async function execInPod(
kc: KubeConfig, kc: KubeConfig,
namespace: string, namespace: string,
@@ -22,7 +29,7 @@ export async function execInPod(
command: string[], command: string[],
stdin?: string, stdin?: string,
timeoutMs?: number, timeoutMs?: number,
): Promise<{ exitCode: number; stdout: string; stderr: string }> { ): Promise<ExecInPodResult> {
const exec = new Exec(kc); const exec = new Exec(kc);
const stdoutStream = new PassThrough(); const stdoutStream = new PassThrough();
const stderrStream = new PassThrough(); const stderrStream = new PassThrough();
@@ -43,25 +50,26 @@ export async function execInPod(
stderrData += chunk.toString("utf-8"); stderrData += chunk.toString("utf-8");
}); });
return await new Promise<{ exitCode: number; stdout: string; stderr: string }>( return await new Promise<ExecInPodResult>(
(resolve, reject) => { (resolve, reject) => {
let settled = false; let settled = false;
const timeout = const timeout =
typeof timeoutMs === "number" && timeoutMs > 0 typeof timeoutMs === "number" && timeoutMs > 0
? setTimeout(() => { ? setTimeout(() => {
finishWithTransportFailure(`Kubernetes exec timed out after ${timeoutMs}ms`); finishWithTransportFailure(`Kubernetes exec timed out after ${timeoutMs}ms`, true);
}, timeoutMs) }, timeoutMs)
: null; : null;
const finish = (result: { exitCode: number; stdout: string; stderr: string }) => { const finish = (result: ExecInPodResult) => {
if (settled) return; if (settled) return;
settled = true; settled = true;
if (timeout) clearTimeout(timeout); if (timeout) clearTimeout(timeout);
resolve(result); resolve(result);
}; };
const finishWithTransportFailure = (message: string) => { const finishWithTransportFailure = (message: string, timedOut = false) => {
const separator = stderrData.length > 0 && !stderrData.endsWith("\n") ? "\n" : ""; const separator = stderrData.length > 0 && !stderrData.endsWith("\n") ? "\n" : "";
finish({ finish({
exitCode: 1, exitCode: 1,
timedOut,
stdout: stdoutData, stdout: stdoutData,
stderr: `${stderrData}${separator}${message}`, stderr: `${stderrData}${separator}${message}`,
}); });
@@ -80,7 +88,7 @@ export async function execInPod(
(status) => { (status) => {
// status.status is "Success" | "Failure" // status.status is "Success" | "Failure"
if (status.status === "Success") { if (status.status === "Success") {
finish({ exitCode: 0, stdout: stdoutData, stderr: stderrData }); finish({ exitCode: 0, timedOut: false, stdout: stdoutData, stderr: stderrData });
return; return;
} }
// On failure, the exit code surfaces via // On failure, the exit code surfaces via
@@ -93,7 +101,7 @@ export async function execInPod(
const exitCode = exitCodeCause?.message const exitCode = exitCodeCause?.message
? Number(exitCodeCause.message) ? Number(exitCodeCause.message)
: 1; : 1;
finish({ exitCode, stdout: stdoutData, stderr: stderrData }); finish({ exitCode, timedOut: false, stdout: stdoutData, stderr: stderrData });
}, },
); );
@@ -39,10 +39,11 @@ describe("buildNetworkPolicyManifests", () => {
it("includes user-supplied CIDRs in egress allow", () => { it("includes user-supplied CIDRs in egress allow", () => {
const [, egress] = buildNetworkPolicyManifests({ ...baseInput, egressAllowCidrs: ["10.0.0.0/8"] }); const [, egress] = buildNetworkPolicyManifests({ ...baseInput, egressAllowCidrs: ["10.0.0.0/8"] });
const cidrRule = egress.spec.egress.find((r: { to: { ipBlock?: { cidr: string } }[] }) => const cidrRule = egress.spec.egress.find((r: { to: { ipBlock?: { cidr: string } }[]; ports?: { protocol: string; port: number }[] }) =>
r.to.some((t) => t.ipBlock?.cidr === "10.0.0.0/8"), r.to.some((t) => t.ipBlock?.cidr === "10.0.0.0/8"),
); );
expect(cidrRule).toBeDefined(); expect(cidrRule).toBeDefined();
expect(cidrRule?.ports).toEqual([{ protocol: "TCP", port: 443 }]);
}); });
it("adds a public HTTPS fallback when standard mode receives FQDN allow-list entries", () => { it("adds a public HTTPS fallback when standard mode receives FQDN allow-list entries", () => {
@@ -22,7 +22,7 @@ describe("execInPod", () => {
}); });
const result = await execInPod({} as never, "ns", "pod-1", "agent", ["echo", "ok"]); const result = await execInPod({} as never, "ns", "pod-1", "agent", ["echo", "ok"]);
expect(result).toEqual({ exitCode: 0, stdout: "ok\n", stderr: "" }); expect(result).toEqual({ exitCode: 0, timedOut: false, stdout: "ok\n", stderr: "" });
}); });
it("returns an execution failure if the websocket closes before a status frame", async () => { it("returns an execution failure if the websocket closes before a status frame", async () => {
@@ -35,6 +35,7 @@ describe("execInPod", () => {
await expect(resultPromise).resolves.toMatchObject({ await expect(resultPromise).resolves.toMatchObject({
exitCode: 1, exitCode: 1,
timedOut: false,
stderr: expect.stringContaining("websocket closed before status frame"), stderr: expect.stringContaining("websocket closed before status frame"),
}); });
}); });
@@ -45,6 +46,7 @@ describe("execInPod", () => {
const result = await execInPod({} as never, "ns", "pod-1", "agent", ["sleep", "60"], undefined, 5); const result = await execInPod({} as never, "ns", "pod-1", "agent", ["sleep", "60"], undefined, 5);
expect(result.exitCode).toBe(1); expect(result.exitCode).toBe(1);
expect(result.timedOut).toBe(true);
expect(result.stderr).toContain("Kubernetes exec timed out after 5ms"); expect(result.stderr).toContain("Kubernetes exec timed out after 5ms");
}); });
}); });