forked from farhoodlabs/paperclip
fix(plugin): align kubernetes config validation
This commit is contained in:
@@ -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"] }),
|
||||||
|
|||||||
Reference in New Issue
Block a user