Release 2.6.2-rc1

This commit is contained in:
2026-10-05 19:35:58 +00:00 Unverified
parent 269675c562
commit 198b7866ef
14 changed files with 699 additions and 113 deletions
+42
View File
@@ -61,4 +61,46 @@ describe("maintenance command", () => {
output.mockRestore();
}
});
test("wildcard host requires a second call before posting the normalized host", async () => {
const calls: Array<{ path: string; init?: ApiRequestInit }> = [];
const confirmations: string[] = [];
const output = spyOn(console, "log").mockImplementation(() => {});
try {
const request = async <T>(path: string, init?: ApiRequestInit): Promise<T> => {
calls.push({ path, init });
return {
host: "*.example.com",
enabled: init?.method === "POST",
hosts: init?.method === "POST" ? ["*.example.com"] : [],
} as T;
};
const confirm = async (host: string) => {
confirmations.push(host);
return confirmations.length === 2;
};
await runMaintenance("*.EXAMPLE.COM.", request, confirm);
expect(calls).toEqual([{ path: "/maintenance/*.EXAMPLE.COM.", init: undefined }]);
expect(output).toHaveBeenCalledWith(
expect.stringContaining("*.example.com maintenance is currently"),
);
await runMaintenance("*.EXAMPLE.COM.", request, confirm);
expect(confirmations).toEqual(["*.example.com", "*.example.com"]);
expect(calls).toEqual([
{ path: "/maintenance/*.EXAMPLE.COM.", init: undefined },
{ path: "/maintenance/*.EXAMPLE.COM.", init: undefined },
{
path: "/maintenance/*.example.com",
init: { method: "POST", json: { enabled: true } },
},
]);
expect(output).toHaveBeenCalledWith(
"*.example.com maintenance is now \x1b[31mON\x1b[0m",
);
} finally {
output.mockRestore();
}
});
});
+24 -1
View File
@@ -74,6 +74,7 @@ afterEach(async () => {
describe("authenticated build API pipeline", () => {
test("negotiates and uploads the manifest through resumable blob routes", async () => {
const snapshot = emptySnapshot();
const controller = new AbortController();
const calls: Array<{
path: string;
init?: ApiRequestInit;
@@ -105,7 +106,14 @@ describe("authenticated build API pipeline", () => {
return { complete: true } as T;
};
await uploadWorkspaceSnapshot(snapshot, request);
await uploadWorkspaceSnapshot(
snapshot,
request,
undefined,
undefined,
undefined,
controller.signal,
);
expect(
calls.map(({ path, init }) => [init?.method ?? "GET", path]),
@@ -120,6 +128,21 @@ describe("authenticated build API pipeline", () => {
["POST", "/snapshots/negotiate"],
]);
expect(new Headers(calls[2]!.init?.headers).get("upload-offset")).toBe("0");
expect(calls.filter(({ path }) => path === "/snapshots/negotiate")).toEqual(
[
expect.objectContaining({
init: expect.objectContaining({ signal: controller.signal }),
options: { timeoutMs: 300_000 },
}),
expect.objectContaining({
init: expect.objectContaining({ signal: controller.signal }),
options: { timeoutMs: 300_000 },
}),
],
);
expect(calls.every(({ init }) => init?.signal === controller.signal)).toBe(
true,
);
});
test("uses project-scoped URLs for every resumable blob upload endpoint", async () => {
+85
View File
@@ -106,6 +106,91 @@ describe("process request lines", () => {
}
});
test("only emits compact access lines from the default logger", async () => {
const output = spyOn(console, "log").mockImplementation(() => {});
const logs: Record<string, unknown>[] = [];
const logger = {
log(entry: Record<string, unknown>) {
logs.push(entry);
processLogger.log(entry);
},
};
try {
const request = new Request(
"https://kuber.astrxl.dev/items?token=query-private",
{
method: "POST",
headers: {
authorization: "Bearer header-private",
cookie: "session=cookie-private",
"x-api-key": "key-private",
},
body: "body-private",
},
);
await logServerRequest(
request,
"request-private",
() => {
processLogger.log({
event: "kuber.server.request.body",
headers: Object.fromEntries(request.headers),
body: { encoding: "base64", data: "Ym9keS1wcml2YXRl" },
});
return new Response(null, {
status: 201,
headers: { "set-cookie": "session=response-private" },
});
},
logger,
);
await expect(
logServerRequest(
new Request("https://kuber.astrxl.dev/fail", {
headers: { authorization: "Bearer failure-private" },
}),
"failure-request",
() => {
throw new Error("failed");
},
logger,
),
).rejects.toThrow("failed");
await logServerRequest(
new Request("https://kuber.astrxl.dev/items", {
headers: { cookie: "session=get-private" },
}),
"get-request",
() => new Response(null, { status: 200 }),
logger,
);
expect(output.mock.calls.map(([line]) => line)).toEqual([
"PST /items 201",
"GET /fail ERR",
"GET /items 200",
]);
expect(logs).toContainEqual(
expect.objectContaining({
event: "kuber.server.request.start",
headers: expect.objectContaining({
authorization: "Bearer header-private",
}),
}),
);
expect(logs).toContainEqual(
expect.objectContaining({
event: "kuber.server.request.end",
responseHeaders: expect.objectContaining({
"set-cookie": "session=response-private",
}),
}),
);
} finally {
output.mockRestore();
}
});
test("indents Kubernetes lines for incoming requests without a CLI marker", async () => {
const output = spyOn(console, "log").mockImplementation(() => {});
try {
+149
View File
@@ -236,6 +236,155 @@ async function internalFixture(
}
describe("build controller", () => {
test("correlates a UUID build request with the stored build and emits only summary keys", async () => {
const { controller, request, store } = await fixture();
request.id = "123e4567-e89b-42d3-a456-426614174000";
const lines: string[] = [];
const output = spyOn(console, "info").mockImplementation((line) => {
lines.push(String(line));
});
try {
await controller.submitBuild(request);
const summary = JSON.parse(lines[0]!);
const record = await store.getBuild(request.id);
expect(summary.buildRequestId).toBe(request.id);
expect(record?.metadata.name).toBe(summary.buildRequestId);
expect(record?.spec.jobName).toMatch(/^kuber-build-/);
expect(Object.keys(summary).sort()).toEqual(
[
"buildRequestId",
"elapsedMs",
"event",
"jobCreated",
"materialize",
"outcome",
"phases",
"queueWaitMs",
].sort(),
);
expect(lines[0]).not.toContain(request.spec.image);
expect(lines[0]).not.toContain(request.spec.workspace);
} finally {
output.mockRestore();
}
});
test("ignores summary logger failures without changing build success", async () => {
const { controller, request } = await fixture();
const output = spyOn(console, "info").mockImplementation(() => {
throw new Error("logger unavailable");
});
try {
const status = await controller.submitBuild(request);
expect(status.state).toBe("queued");
} finally {
output.mockRestore();
}
});
test("attributes deferred store work and serialized queue wait without logging identifiers", async () => {
let release!: () => void;
let entered!: () => void;
const blocked = new Promise<void>((resolve) => {
release = resolve;
});
const started = new Promise<void>((resolve) => {
entered = resolve;
});
class SlowStore extends MemoryBuildStore {
override async createBuild(record: BuildRecord) {
entered();
await blocked;
return super.createBuild(record);
}
}
const { controller, request } = await fixture(1024, new SlowStore());
const lines: string[] = [];
const output = spyOn(console, "info").mockImplementation((line) => {
lines.push(String(line));
});
try {
const first = controller.submitBuild(request);
await started;
const second = controller.submitBuild(request);
await Bun.sleep(25);
release();
await Promise.all([first, second]);
expect(lines).toHaveLength(2);
const [created, existing] = lines.map((line) => JSON.parse(line));
expect(created.phases.recordCreateMs).toBeGreaterThan(15);
expect(created.phases.materializeMs).toBeGreaterThan(0);
expect(existing.queueWaitMs).toBeGreaterThan(15);
expect(existing.jobCreated).toBe(false);
expect(existing.phases.materializeMs).toBeUndefined();
for (const line of lines) {
expect(line).not.toContain(request.id);
expect(line).not.toContain(request.spec.workspace);
expect(line).not.toContain(request.spec.image);
expect(line).not.toContain("Dockerfile");
}
} finally {
release();
output.mockRestore();
}
});
test("attributes deferred materialization and logs one safe failure summary", async () => {
const { cas, store, kubernetes, request, root } = await fixture();
let release!: () => void;
let entered!: () => void;
const blocked = new Promise<void>((resolve) => {
release = resolve;
});
const started = new Promise<void>((resolve) => {
entered = resolve;
});
const secret = "private-path-and-credential";
const controller = new BuildController({
cas,
store,
kubernetes,
namespace: "builds",
workspaceRoot: join(root, "workspaces"),
workspaceClaimName: "workspaces",
cacheImage: "cache",
materialize: async () => {
entered();
await blocked;
throw new Error(secret);
},
});
const lines: string[] = [];
const output = spyOn(console, "info").mockImplementation((line) => {
lines.push(String(line));
});
try {
const submission = controller.submitBuild(request);
const failure = submission.catch((error: unknown) => error);
await started;
await Bun.sleep(25);
release();
const error = await failure;
expect(error).toBeInstanceOf(Error);
expect((error as Error).message).toBe(secret);
expect(lines).toHaveLength(1);
const summary = JSON.parse(lines[0]!);
expect(summary).toMatchObject({
event: "build_submission_timing",
outcome: "failure",
jobCreated: false,
});
expect(summary.phases.materializeMs).toBeGreaterThan(15);
expect(summary.phases.failureCleanupMs).toBeGreaterThanOrEqual(0);
expect(summary.phases.jobCreateMs).toBeUndefined();
expect(lines[0]).not.toContain(secret);
expect(lines[0]).not.toContain(request.id);
} finally {
release();
output.mockRestore();
}
});
test("publishes multiple service destinations in one Job and resolves every canonical reference", async () => {
const resolved: string[] = [];
const { controller, request, kubernetes } = await internalFixture(
+36 -2
View File
@@ -40,16 +40,25 @@ class MemoryPersistence implements MaintenancePersistence {
const lease = { acquire: async () => ({ release: async () => {} }) };
describe("maintenance override", () => {
test("normalizes only DNS hostnames", () => {
test("normalizes DNS hostnames and leading wildcards only", () => {
expect(normalizeMaintenanceHost(" Sub.Domain.COM. ")).toBe(
"sub.domain.com",
);
expect(normalizeMaintenanceHost(" *.EXAMPLE.COM. ")).toBe("*.example.com");
expect(normalizeMaintenanceHost(" *.Sub.Example.COM. ")).toBe("*.sub.example.com");
for (const host of [
"http://example.com",
"example.com:443",
"127.0.0.1",
"[::1]",
"*.example.com",
"*",
"*.",
"example.*.com",
"a.*.example.com",
"**.example.com",
"*.*.example.com",
"*.example.com:443",
"*.127.0.0.1",
"example",
"a..com",
])
@@ -74,6 +83,28 @@ describe("maintenance override", () => {
expect(persistence.state).toEqual({ hosts: [] });
});
test("persists normalized wildcards and renders them as Host rules", async () => {
const persistence = new MemoryPersistence();
const service = new MaintenanceService(persistence, lease);
expect(await service.set(" *.EXAMPLE.COM. ", true)).toMatchObject({
host: "*.example.com",
enabled: true,
hosts: ["*.example.com"],
});
expect(persistence.state).toEqual({ hosts: ["*.example.com"] });
expect(persistence.resources.at(-1)).toMatchObject({
spec: { routes: [{ match: "Host(`*.example.com`)" }] },
});
expect(await service.status("*.EXAMPLE.COM.")).toMatchObject({
host: "*.example.com",
enabled: true,
});
expect(await service.set("*.example.com", false)).toMatchObject({
enabled: false,
hosts: [],
});
});
test("renders the single shared error route and rewrite middleware", () => {
expect(maintenanceMiddleware()).toMatchObject({
metadata: { name: "maintenance-override", namespace: "routing" },
@@ -100,6 +131,9 @@ describe("maintenance override", () => {
],
},
});
expect(maintenanceRoute(["*.example.com", "a.example.com"])).toMatchObject({
spec: { routes: [{ match: "Host(`*.example.com`) || Host(`a.example.com`)" }] },
});
});
test("maps an unavailable global lease to a retryable API conflict", async () => {
+91
View File
@@ -26,6 +26,59 @@ function digest(data: Uint8Array | string): Sha256Digest {
}
describe("source materialization", () => {
test("collects aggregate CAS and filesystem timing with manifest counts", async () => {
const root = await mkdtemp(join(tmpdir(), "kuber-materialize-"));
roots.push(root);
const data = Buffer.from("hello");
const manifest: WorkspaceManifest = {
version: BUILD_PROTOCOL_VERSION,
files: [
{
path: "example",
type: "file",
digest: digest(data),
size: data.byteLength,
mode: 0o644,
},
],
};
const manifestData = Buffer.from(JSON.stringify(manifest));
const workspace = digest(manifestData);
let release!: () => void;
let entered!: () => void;
const blocked = new Promise<void>((resolve) => {
release = resolve;
});
const started = new Promise<void>((resolve) => {
entered = resolve;
});
const timing = { casReadMs: 0, fsWriteMs: 0 };
const submission = materializeWorkspace(
{
get: async (requested) => {
if (requested === workspace) return manifestData;
entered();
await blocked;
return data;
},
},
workspace,
join(root, "workspace"),
timing,
);
await started;
await Bun.sleep(25);
release();
await submission;
expect(timing).toMatchObject({
fileCount: 1,
manifestBytes: manifestData.byteLength,
fileBytes: data.byteLength,
});
expect(timing.casReadMs).toBeGreaterThan(15);
expect(timing.fsWriteMs).toBeGreaterThan(0);
});
test("bounds concurrent CAS reads while materializing many files", async () => {
const root = await mkdtemp(join(tmpdir(), "kuber-materialize-"));
roots.push(root);
@@ -151,6 +204,28 @@ describe("source materialization", () => {
),
),
).toThrow("conflicts");
for (const [files, conflict] of [
[["a/b/c", "a"], "Workspace path conflicts with a directory: a"],
[["a/b/c", "a/b"], "Workspace path conflicts with a directory: a/b"],
[["a", "a/b/c"], "Workspace path conflicts with a file: a/b/c"],
] as const) {
expect(() =>
parseWorkspaceManifest(
Buffer.from(
JSON.stringify({
version: BUILD_PROTOCOL_VERSION,
files: files.map((path) => ({
path,
type: "file",
digest: `sha256:${"a".repeat(64)}`,
size: 0,
mode: 0o644,
})),
}),
),
),
).toThrow(conflict);
}
const root = await mkdtemp(join(tmpdir(), "kuber-materialize-"));
roots.push(root);
@@ -178,4 +253,20 @@ describe("source materialization", () => {
).rejects.toThrow("Unsafe symlink");
await expect(lstat(destination)).rejects.toMatchObject({ code: "ENOENT" });
});
test("accepts a large manifest with distinct nested paths", () => {
const manifest: WorkspaceManifest = {
version: BUILD_PROTOCOL_VERSION,
files: Array.from({ length: 3_000 }, (_, index) => ({
path: `dir-${index}/nested/file`,
type: "file" as const,
digest: `sha256:${"a".repeat(64)}` as Sha256Digest,
size: 0,
mode: 0o644 as const,
})),
};
expect(
parseWorkspaceManifest(Buffer.from(JSON.stringify(manifest))),
).toEqual(manifest);
});
});