fix(plugin): align kubernetes config validation

This commit is contained in:
Dotta
2026-05-12 17:46:54 -05:00
committed by Chris Farhood
parent c37e5919ce
commit b248acd46c
4 changed files with 51 additions and 4 deletions
@@ -34,15 +34,18 @@ const manifest: PaperclipPluginManifestV1 = {
kubeconfig: { kubeconfig: {
type: "string", type: "string",
format: "secret-ref", format: "secret-ref",
pattern: "\\S",
description: description:
"Inline kubeconfig YAML. Paste a kubeconfig or an existing Paperclip secret reference; pasted values are stored as company secrets.", "Inline kubeconfig YAML. Paste a kubeconfig or an existing Paperclip secret reference; pasted values are stored as company secrets.",
}, },
namespacePrefix: { namespacePrefix: {
type: "string", type: "string",
maxLength: 20,
description: "Prefix for the per-company tenant namespace (default: paperclip-).", description: "Prefix for the per-company tenant namespace (default: paperclip-).",
}, },
companySlug: { companySlug: {
type: "string", type: "string",
maxLength: 43,
description: "Override the auto-derived company slug used in the tenant namespace name.", description: "Override the auto-derived company slug used in the tenant namespace name.",
}, },
imageRegistry: { imageRegistry: {
@@ -111,7 +114,10 @@ const manifest: PaperclipPluginManifestV1 = {
}, },
}, },
anyOf: [ anyOf: [
{ required: ["inCluster"] }, {
properties: { inCluster: { const: true } },
required: ["inCluster"],
},
{ required: ["kubeconfig"] }, { required: ["kubeconfig"] },
], ],
}, },
@@ -28,8 +28,8 @@ export const kubernetesProviderConfigSchema = z
inCluster: z.boolean().default(false), inCluster: z.boolean().default(false),
kubeconfig: z.string().optional(), kubeconfig: z.string().optional(),
namespacePrefix: z.string().regex(/^[a-z0-9-]{1,32}$/).default("paperclip-"), namespacePrefix: z.string().regex(/^[a-z0-9-]{1,20}$/).default("paperclip-"),
companySlug: z.string().regex(/^[a-z0-9-]{1,32}$/).optional(), companySlug: z.string().regex(/^[a-z0-9-]{1,43}$/).optional(),
imageRegistry: z.string().url().optional(), imageRegistry: z.string().url().optional(),
imageAllowList: z.array(z.string()).default([]), imageAllowList: z.array(z.string()).default([]),
@@ -80,7 +80,7 @@ export const kubernetesProviderConfigSchema = z
backend: z.enum(["sandbox-cr", "job"]).default("sandbox-cr"), backend: z.enum(["sandbox-cr", "job"]).default("sandbox-cr"),
}) })
.refine( .refine(
(cfg) => cfg.inCluster || cfg.kubeconfig, (cfg) => cfg.inCluster || (typeof cfg.kubeconfig === "string" && cfg.kubeconfig.trim().length > 0),
{ {
message: message:
"kubernetes provider requires one of `inCluster` or `kubeconfig`", "kubernetes provider requires one of `inCluster` or `kubeconfig`",
@@ -0,0 +1,26 @@
import { describe, it, expect } from "vitest";
import manifest from "../../src/manifest.js";
describe("manifest", () => {
const configSchema = manifest.environmentDrivers[0]?.configSchema as {
properties: Record<string, { const?: unknown; maxLength?: number; pattern?: string }>;
anyOf: Array<{
properties?: Record<string, { const?: unknown }>;
required?: string[];
}>;
};
it("keeps namespace inputs within the Kubernetes DNS label length limit", () => {
expect(configSchema.properties.namespacePrefix.maxLength).toBe(20);
expect(configSchema.properties.companySlug.maxLength).toBe(43);
});
it("requires real Kubernetes credentials instead of only inCluster key presence", () => {
expect(configSchema.properties.kubeconfig.pattern).toBe("\\S");
expect(configSchema.anyOf).toContainEqual({
properties: { inCluster: { const: true } },
required: ["inCluster"],
});
expect(configSchema.anyOf).toContainEqual({ required: ["kubeconfig"] });
});
});
@@ -31,6 +31,21 @@ describe("kubernetesProviderConfigSchema", () => {
).toThrow(); ).toThrow();
}); });
it("bounds namespacePrefix and companySlug so their combination fits a Kubernetes namespace", () => {
expect(() =>
parseKubernetesProviderConfig({ inCluster: true, namespacePrefix: "a".repeat(21) }),
).toThrow();
expect(() =>
parseKubernetesProviderConfig({ inCluster: true, companySlug: "a".repeat(44) }),
).toThrow();
});
it("rejects whitespace-only kubeconfig", () => {
expect(() =>
parseKubernetesProviderConfig({ inCluster: false, kubeconfig: " " }),
).toThrow(/requires one of `inCluster` or `kubeconfig`/);
});
it("rejects egressAllowCidrs entries that are not valid CIDR", () => { it("rejects egressAllowCidrs entries that are not valid CIDR", () => {
expect(() => expect(() =>
parseKubernetesProviderConfig({ inCluster: true, egressAllowCidrs: ["not-a-cidr"] }), parseKubernetesProviderConfig({ inCluster: true, egressAllowCidrs: ["not-a-cidr"] }),