fix(plugin): address kubernetes greptile timeouts
This commit is contained in:
@@ -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");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user