170 lines
5.8 KiB
TypeScript
170 lines
5.8 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { createBuildJob } from "../../server/build-job";
|
|
import type { BuildSpec } from "../../shared/build-protocol";
|
|
|
|
function spec(architecture: "arm64" | "amd64"): BuildSpec {
|
|
return {
|
|
architecture,
|
|
image: "registry.example.com/kuber/demo-web:latest",
|
|
context: "apps/web",
|
|
dockerfile: "docker/Web.Dockerfile",
|
|
target: "production",
|
|
buildArgs: ["NODE_ENV=production"],
|
|
workspace: `sha256:${"a".repeat(64)}`,
|
|
};
|
|
}
|
|
|
|
describe("BuildKit Job generation", () => {
|
|
test.each(["arm64", "amd64"] as const)(
|
|
"generates a rootless %s job",
|
|
(architecture) => {
|
|
const job = createBuildJob({
|
|
name: `build-${architecture}`,
|
|
namespace: "kuber-system",
|
|
spec: spec(architecture),
|
|
workspaceClaimName: "build-workspaces",
|
|
workspaceSubPath: "snapshot",
|
|
cacheImage: "registry.example.com/cache/demo-web",
|
|
registrySecretName: "registry-auth",
|
|
nodeSelector: { "kubernetes.io/arch": "wrong", pool: "builders" },
|
|
});
|
|
const jobSpec = job.spec as any;
|
|
const pod = jobSpec.template.spec;
|
|
const container = pod.containers[0];
|
|
expect(pod.nodeSelector["kubernetes.io/arch"]).toBe(architecture);
|
|
expect(pod.nodeSelector.pool).toBe("builders");
|
|
expect(pod.automountServiceAccountToken).toBe(false);
|
|
expect(pod.securityContext).toMatchObject({
|
|
runAsNonRoot: true,
|
|
runAsUser: 1000,
|
|
seccompProfile: { type: "Unconfined" },
|
|
});
|
|
expect(container.securityContext).toEqual({
|
|
runAsNonRoot: true,
|
|
runAsUser: 1000,
|
|
allowPrivilegeEscalation: true,
|
|
seccompProfile: { type: "Unconfined" },
|
|
appArmorProfile: { type: "Unconfined" },
|
|
});
|
|
expect(container.env).toContainEqual({
|
|
name: "BUILDKITD_FLAGS",
|
|
value: "--oci-worker-no-process-sandbox",
|
|
});
|
|
expect(container.args).toContain(`--opt=platform=linux/${architecture}`);
|
|
expect(container.args).toContain(
|
|
"--import-cache=type=registry,ref=registry.example.com/cache/demo-web",
|
|
);
|
|
expect(container.args).toContain(
|
|
"--export-cache=type=registry,ref=registry.example.com/cache/demo-web,mode=max",
|
|
);
|
|
expect(container.args).toContain(
|
|
"--output=type=image,name=registry.example.com/kuber/demo-web:latest,push=true",
|
|
);
|
|
expect(container.volumeMounts).toContainEqual({
|
|
name: "workspace",
|
|
mountPath: "/workspace",
|
|
readOnly: true,
|
|
subPath: "snapshot",
|
|
});
|
|
expect(pod.volumes).toContainEqual({
|
|
name: "registry-auth",
|
|
secret: {
|
|
secretName: "registry-auth",
|
|
items: [{ key: ".dockerconfigjson", path: "config.json" }],
|
|
},
|
|
});
|
|
expect(
|
|
jobSpec.template.metadata.annotations[
|
|
"container.apparmor.security.beta.kubernetes.io/buildkit"
|
|
],
|
|
).toBe("unconfined");
|
|
},
|
|
);
|
|
|
|
test("uses pushImage for output when set", () => {
|
|
const job = createBuildJob({
|
|
name: "build-push",
|
|
namespace: "default",
|
|
spec: spec("amd64"),
|
|
workspaceClaimName: "workspace",
|
|
cacheImage: "registry.example.com/cache/demo-web",
|
|
pushImage: "internal.registry:5000/kuber/demo-web:latest",
|
|
});
|
|
const container = (job.spec as any).template.spec.containers[0];
|
|
expect(container.args).toContain(
|
|
"--output=type=image,name=internal.registry:5000/kuber/demo-web:latest,push=true",
|
|
);
|
|
expect(container.args).not.toContainEqual(
|
|
expect.stringContaining("--output=type=image,name=registry.example.com"),
|
|
);
|
|
});
|
|
|
|
test("adds insecure flags when configured", () => {
|
|
const job = createBuildJob({
|
|
name: "build-insecure",
|
|
namespace: "default",
|
|
spec: spec("amd64"),
|
|
workspaceClaimName: "workspace",
|
|
cacheImage: "internal.registry:5000/cache/demo-web",
|
|
pushImage: "internal.registry:5000/kuber/demo-web:latest",
|
|
pushRegistryInsecure: true,
|
|
cacheRegistryInsecure: true,
|
|
});
|
|
const container = (job.spec as any).template.spec.containers[0];
|
|
expect(container.args).toContain(
|
|
"--import-cache=type=registry,ref=internal.registry:5000/cache/demo-web,registry.insecure=true",
|
|
);
|
|
expect(container.args).toContain(
|
|
"--export-cache=type=registry,ref=internal.registry:5000/cache/demo-web,mode=max,registry.insecure=true",
|
|
);
|
|
expect(container.args).toContain(
|
|
"--output=type=image,name=internal.registry:5000/kuber/demo-web:latest,push=true,registry.insecure=true",
|
|
);
|
|
});
|
|
|
|
test("no insecure flags when not configured", () => {
|
|
const job = createBuildJob({
|
|
name: "build-secure",
|
|
namespace: "default",
|
|
spec: spec("amd64"),
|
|
workspaceClaimName: "workspace",
|
|
cacheImage: "registry.example.com/cache/demo-web",
|
|
});
|
|
const container = (job.spec as any).template.spec.containers[0];
|
|
for (const arg of container.args) {
|
|
expect(arg).not.toContain("registry.insecure");
|
|
}
|
|
});
|
|
|
|
test("rejects traversal and invalid Kubernetes names", () => {
|
|
expect(() =>
|
|
createBuildJob({
|
|
name: "Invalid_Name",
|
|
namespace: "default",
|
|
spec: spec("arm64"),
|
|
workspaceClaimName: "workspace",
|
|
cacheImage: "cache",
|
|
}),
|
|
).toThrow("DNS label");
|
|
expect(() =>
|
|
createBuildJob({
|
|
name: "valid",
|
|
namespace: "default",
|
|
spec: { ...spec("arm64"), context: "../outside" },
|
|
workspaceClaimName: "workspace",
|
|
cacheImage: "cache",
|
|
}),
|
|
).toThrow("safe workspace-relative");
|
|
expect(() =>
|
|
createBuildJob({
|
|
name: "valid",
|
|
namespace: "default",
|
|
spec: spec("arm64"),
|
|
workspaceClaimName: "workspace",
|
|
workspaceSubPath: "../outside",
|
|
cacheImage: "cache",
|
|
}),
|
|
).toThrow("Workspace subPath");
|
|
});
|
|
});
|