feat: v2
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
import { afterEach, describe, expect, test } from "bun:test";
|
||||
import { createHash } from "node:crypto";
|
||||
import { lstat, mkdtemp, readFile, readlink, rm } from "node:fs/promises";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { FilesystemCas } from "../../server/cas";
|
||||
import {
|
||||
materializeWorkspace,
|
||||
parseWorkspaceManifest,
|
||||
} from "../../server/materialize";
|
||||
import {
|
||||
BUILD_PROTOCOL_VERSION,
|
||||
type Sha256Digest,
|
||||
type WorkspaceManifest,
|
||||
} from "../../shared/build-protocol";
|
||||
|
||||
const roots: string[] = [];
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
roots.splice(0).map((root) => rm(root, { recursive: true, force: true })),
|
||||
);
|
||||
});
|
||||
|
||||
function digest(data: Uint8Array | string): Sha256Digest {
|
||||
return `sha256:${createHash("sha256").update(data).digest("hex")}`;
|
||||
}
|
||||
|
||||
describe("source materialization", () => {
|
||||
test("materializes files and safe symlinks with declared modes", async () => {
|
||||
const root = await mkdtemp(join(tmpdir(), "kuber-materialize-"));
|
||||
roots.push(root);
|
||||
const cas = new FilesystemCas(join(root, "cas"));
|
||||
const executable = Buffer.from("#!/bin/sh\necho ok\n");
|
||||
const link = Buffer.from("bin/run");
|
||||
await cas.put(executable, digest(executable));
|
||||
await cas.put(link, digest(link));
|
||||
const manifest: WorkspaceManifest = {
|
||||
version: BUILD_PROTOCOL_VERSION,
|
||||
files: [
|
||||
{
|
||||
path: "bin/run",
|
||||
type: "file",
|
||||
digest: digest(executable),
|
||||
size: executable.byteLength,
|
||||
mode: 0o755,
|
||||
},
|
||||
{
|
||||
path: "run",
|
||||
type: "symlink",
|
||||
digest: digest(link),
|
||||
size: link.byteLength,
|
||||
mode: 0o777,
|
||||
},
|
||||
],
|
||||
};
|
||||
const manifestBytes = Buffer.from(JSON.stringify(manifest));
|
||||
const workspace = await cas.put(manifestBytes);
|
||||
const destination = join(root, "workspaces", "build-1");
|
||||
|
||||
expect(await materializeWorkspace(cas, workspace, destination)).toEqual(
|
||||
manifest,
|
||||
);
|
||||
expect(await readFile(join(destination, "bin/run"), "utf8")).toContain(
|
||||
"echo ok",
|
||||
);
|
||||
expect((await lstat(join(destination, "bin/run"))).mode & 0o777).toBe(
|
||||
0o755,
|
||||
);
|
||||
expect(await readlink(join(destination, "run"))).toBe("bin/run");
|
||||
});
|
||||
|
||||
test("rejects traversal, path collisions, unsafe links, and size mismatches atomically", async () => {
|
||||
expect(() =>
|
||||
parseWorkspaceManifest(
|
||||
Buffer.from(
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
files: [
|
||||
{
|
||||
path: "../x",
|
||||
type: "file",
|
||||
digest: `sha256:${"a".repeat(64)}`,
|
||||
size: 0,
|
||||
mode: 420,
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
),
|
||||
).toThrow("invalid file");
|
||||
expect(() =>
|
||||
parseWorkspaceManifest(
|
||||
Buffer.from(
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
files: [
|
||||
{
|
||||
path: "a",
|
||||
type: "file",
|
||||
digest: `sha256:${"a".repeat(64)}`,
|
||||
size: 0,
|
||||
mode: 420,
|
||||
},
|
||||
{
|
||||
path: "a/b",
|
||||
type: "file",
|
||||
digest: `sha256:${"b".repeat(64)}`,
|
||||
size: 0,
|
||||
mode: 420,
|
||||
},
|
||||
],
|
||||
}),
|
||||
),
|
||||
),
|
||||
).toThrow("conflicts");
|
||||
|
||||
const root = await mkdtemp(join(tmpdir(), "kuber-materialize-"));
|
||||
roots.push(root);
|
||||
const cas = new FilesystemCas(join(root, "cas"));
|
||||
const link = Buffer.from("../../outside");
|
||||
const linkDigest = await cas.put(link);
|
||||
const manifest = Buffer.from(
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
files: [
|
||||
{
|
||||
path: "nested/link",
|
||||
type: "symlink",
|
||||
digest: linkDigest,
|
||||
size: link.byteLength,
|
||||
mode: 0o777,
|
||||
},
|
||||
],
|
||||
}),
|
||||
);
|
||||
const workspace = await cas.put(manifest);
|
||||
const destination = join(root, "workspace");
|
||||
await expect(
|
||||
materializeWorkspace(cas, workspace, destination),
|
||||
).rejects.toThrow("Unsafe symlink");
|
||||
await expect(lstat(destination)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user