@intentius/chant-lexicon-k8s 0.45.0 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/kubectl-apply.d.ts +5 -0
- package/dist/components/kubectl-apply.d.ts.map +1 -1
- package/dist/composites/flux-app.d.ts +12 -0
- package/dist/composites/flux-app.d.ts.map +1 -1
- package/dist/composites/index.d.ts +6 -0
- package/dist/composites/index.d.ts.map +1 -1
- package/dist/composites/inference-service.d.ts +86 -0
- package/dist/composites/inference-service.d.ts.map +1 -0
- package/dist/composites/model.d.ts +80 -0
- package/dist/composites/model.d.ts.map +1 -0
- package/dist/composites/vllm-serving-runtime.d.ts +100 -0
- package/dist/composites/vllm-serving-runtime.d.ts.map +1 -0
- package/dist/crd/crd-sources.d.ts.map +1 -1
- package/dist/deep-observe-hooks.d.ts.map +1 -1
- package/dist/describe-resources.d.ts.map +1 -1
- package/dist/generated/index.d.ts +167 -40
- package/dist/generated/index.d.ts.map +1 -1
- package/dist/group-namespace.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +13 -5
- package/dist/kube/apply.d.ts.map +1 -1
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/lint/post-synth/k8s-helpers.d.ts +28 -1
- package/dist/lint/post-synth/k8s-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/sops-helpers.d.ts +38 -0
- package/dist/lint/post-synth/sops-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8404.d.ts +12 -0
- package/dist/lint/post-synth/wk8404.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8405.d.ts +13 -0
- package/dist/lint/post-synth/wk8405.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8406.d.ts +12 -0
- package/dist/lint/post-synth/wk8406.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8407.d.ts +21 -0
- package/dist/lint/post-synth/wk8407.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8503.d.ts +39 -0
- package/dist/lint/post-synth/wk8503.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8504.d.ts +28 -0
- package/dist/lint/post-synth/wk8504.d.ts.map +1 -0
- package/dist/lint/post-synth/wk8505.d.ts +28 -0
- package/dist/lint/post-synth/wk8505.d.ts.map +1 -0
- package/dist/manifest.json +1 -1
- package/dist/meta.json +1039 -68
- package/dist/okf/index.md +26 -2
- package/dist/okf/rules/FLUX002.md +0 -1
- package/dist/okf/rules/WK8404.md +11 -0
- package/dist/okf/rules/WK8405.md +17 -0
- package/dist/okf/rules/WK8406.md +11 -0
- package/dist/okf/rules/WK8407.md +15 -0
- package/dist/okf/rules/WK8503.md +17 -0
- package/dist/okf/rules/WK8504.md +11 -0
- package/dist/okf/rules/WK8505.md +15 -0
- package/dist/okf/types/AWSClusterControllerIdentity.md +14 -0
- package/dist/okf/types/AWSManagedCluster.md +14 -0
- package/dist/okf/types/AWSManagedControlPlane.md +14 -0
- package/dist/okf/types/AWSManagedMachinePool.md +14 -0
- package/dist/okf/types/CAPICluster.md +14 -0
- package/dist/okf/types/Certificate.md +1 -0
- package/dist/okf/types/ClusterResourceSet.md +14 -0
- package/dist/okf/types/ClusterServingRuntime.md +14 -0
- package/dist/okf/types/{Cluster.md → CnpgCluster.md} +2 -2
- package/dist/okf/types/DBInstance.md +14 -0
- package/dist/okf/types/{Bucket.md → FluxBucket.md} +2 -6
- package/dist/okf/types/HelmChartProxy.md +14 -0
- package/dist/okf/types/IamRole.md +14 -0
- package/dist/okf/types/InferenceService.md +19 -0
- package/dist/okf/types/InfisicalDynamicSecret.md +4 -0
- package/dist/okf/types/InfisicalSecret.md +4 -0
- package/dist/okf/types/Kustomization.md +1 -0
- package/dist/okf/types/MachinePool.md +14 -0
- package/dist/okf/types/PDB.md +1 -0
- package/dist/okf/types/PodIdentityAssociation.md +14 -0
- package/dist/okf/types/Policy.md +14 -0
- package/dist/okf/types/S3Bucket.md +14 -0
- package/dist/okf/types/ServingRuntime.md +14 -0
- package/dist/okf/types/StatefulSet.md +1 -0
- package/dist/okf/types/User.md +14 -0
- package/dist/op/activities/ensure-secret.d.ts +41 -0
- package/dist/op/activities/ensure-secret.d.ts.map +1 -0
- package/dist/op/activities/index.d.ts +2 -0
- package/dist/op/activities/index.d.ts.map +1 -1
- package/dist/op/activities/kubectl.d.ts +9 -0
- package/dist/op/activities/kubectl.d.ts.map +1 -1
- package/dist/op/activities/wait-for-ready.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/rules/k8s-helpers.ts +55 -1
- package/dist/rules/sops-helpers.ts +76 -0
- package/dist/rules/wk8404.ts +55 -0
- package/dist/rules/wk8405.ts +88 -0
- package/dist/rules/wk8406.ts +60 -0
- package/dist/rules/wk8407.ts +71 -0
- package/dist/rules/wk8503.ts +220 -0
- package/dist/rules/wk8504.ts +51 -0
- package/dist/rules/wk8505.ts +63 -0
- package/dist/secret-labels.d.ts +31 -0
- package/dist/secret-labels.d.ts.map +1 -0
- package/dist/secret-store.d.ts +73 -0
- package/dist/secret-store.d.ts.map +1 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/sops/encrypted-secret-file.d.ts +52 -0
- package/dist/sops/encrypted-secret-file.d.ts.map +1 -0
- package/dist/sops/entity.d.ts +31 -0
- package/dist/sops/entity.d.ts.map +1 -0
- package/dist/teardown.d.ts +46 -0
- package/dist/teardown.d.ts.map +1 -0
- package/dist/types/index.d.ts +261 -9
- package/package.json +4 -4
- package/src/components/kubectl-apply.ts +5 -0
- package/src/composites/composites.test.ts +273 -0
- package/src/composites/flux-app.ts +23 -0
- package/src/composites/index.ts +11 -0
- package/src/composites/inference-service.ts +140 -0
- package/src/composites/model.ts +150 -0
- package/src/composites/vllm-serving-runtime.test.ts +167 -0
- package/src/composites/vllm-serving-runtime.ts +196 -0
- package/src/crd/cnpg.test.ts +8 -3
- package/src/crd/crd-sources.ts +183 -0
- package/src/crd/parser.test.ts +14 -0
- package/src/deep-observe-hooks.ts +24 -0
- package/src/deep-observe.test.ts +89 -0
- package/src/describe-resources.ts +6 -1
- package/src/generated/index.d.ts +261 -9
- package/src/generated/index.ts +167 -40
- package/src/generated/lexicon-k8s.json +1039 -68
- package/src/generated/operations.json +272 -0
- package/src/group-namespace.test.ts +31 -0
- package/src/group-namespace.ts +13 -0
- package/src/index.ts +6 -0
- package/src/kube/apply.ts +5 -0
- package/src/lifecycle-integration.test.ts +42 -0
- package/src/lint/audit-catalog.ts +7 -0
- package/src/lint/post-synth/index.ts +14 -0
- package/src/lint/post-synth/k8s-helpers.ts +55 -1
- package/src/lint/post-synth/post-synth.test.ts +826 -0
- package/src/lint/post-synth/sops-helpers.ts +76 -0
- package/src/lint/post-synth/wk8404.ts +55 -0
- package/src/lint/post-synth/wk8405.ts +88 -0
- package/src/lint/post-synth/wk8406.ts +60 -0
- package/src/lint/post-synth/wk8407.ts +71 -0
- package/src/lint/post-synth/wk8503.ts +220 -0
- package/src/lint/post-synth/wk8504.ts +51 -0
- package/src/lint/post-synth/wk8505.ts +63 -0
- package/src/op/activities/ensure-secret.ts +71 -0
- package/src/op/activities/index.ts +7 -0
- package/src/op/activities/kubectl.test.ts +32 -0
- package/src/op/activities/kubectl.ts +28 -5
- package/src/op/activities/wait-for-ready.test.ts +34 -0
- package/src/op/activities/wait-for-ready.ts +26 -0
- package/src/plugin.ts +34 -2
- package/src/secret-labels.ts +36 -0
- package/src/secret-store.test.ts +184 -0
- package/src/secret-store.ts +165 -0
- package/src/serializer.ts +33 -2
- package/src/sops/encrypted-secret-file.test.ts +333 -0
- package/src/sops/encrypted-secret-file.ts +264 -0
- package/src/sops/entity.ts +53 -0
- package/src/teardown.test.ts +240 -0
- package/src/teardown.ts +239 -0
- package/src/testdata/sops/db-credentials.sops.yaml +23 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { describe, test, expect } from "vitest";
|
|
2
|
+
import { emitYAML } from "@intentius/chant/yaml";
|
|
3
|
+
import { VllmServingRuntime } from "./vllm-serving-runtime";
|
|
4
|
+
|
|
5
|
+
/** Helper to access props on a Declarable member. */
|
|
6
|
+
function p(member: unknown): Record<string, unknown> {
|
|
7
|
+
return (member as any).props;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
describe("VllmServingRuntime", () => {
|
|
11
|
+
test("emits a namespaced ServingRuntime by default", () => {
|
|
12
|
+
const result = VllmServingRuntime({
|
|
13
|
+
name: "vllm-runtime",
|
|
14
|
+
namespace: "models",
|
|
15
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
16
|
+
});
|
|
17
|
+
const props = p(result.servingRuntime);
|
|
18
|
+
expect(props.metadata).toMatchObject({ name: "vllm-runtime", namespace: "models" });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("emits a ClusterServingRuntime when clusterScoped is true", () => {
|
|
22
|
+
const result = VllmServingRuntime({
|
|
23
|
+
name: "vllm-runtime",
|
|
24
|
+
clusterScoped: true,
|
|
25
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
26
|
+
});
|
|
27
|
+
const props = p(result.servingRuntime);
|
|
28
|
+
expect(props.metadata).toMatchObject({ name: "vllm-runtime" });
|
|
29
|
+
expect((props.metadata as any).namespace).toBeUndefined();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("throws when namespace is missing and clusterScoped is false", () => {
|
|
33
|
+
expect(() =>
|
|
34
|
+
VllmServingRuntime({
|
|
35
|
+
name: "vllm-runtime",
|
|
36
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
37
|
+
} as any),
|
|
38
|
+
).toThrow(/namespace is required/);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("registers the vllm supportedModelFormats entry", () => {
|
|
42
|
+
const result = VllmServingRuntime({
|
|
43
|
+
name: "vllm-runtime",
|
|
44
|
+
namespace: "models",
|
|
45
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
46
|
+
});
|
|
47
|
+
const spec = p(result.servingRuntime).spec as any;
|
|
48
|
+
expect(spec.supportedModelFormats).toEqual([{ name: "vllm", autoSelect: true }]);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test("supports overriding the model format", () => {
|
|
52
|
+
const result = VllmServingRuntime({
|
|
53
|
+
name: "vllm-runtime",
|
|
54
|
+
namespace: "models",
|
|
55
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
56
|
+
modelFormat: { name: "vllm", version: "1", priority: 1 },
|
|
57
|
+
});
|
|
58
|
+
const spec = p(result.servingRuntime).spec as any;
|
|
59
|
+
expect(spec.supportedModelFormats).toEqual([{ name: "vllm", version: "1", priority: 1 }]);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// ── Golden test: vLLM args are spec-true (real `vllm serve` flag names) ──
|
|
63
|
+
|
|
64
|
+
test("wires typed vLLM props to real container args, in flag order", () => {
|
|
65
|
+
const result = VllmServingRuntime({
|
|
66
|
+
name: "vllm-runtime",
|
|
67
|
+
namespace: "models",
|
|
68
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
69
|
+
tensorParallelSize: 2,
|
|
70
|
+
maxModelLen: 8192,
|
|
71
|
+
dtype: "bfloat16",
|
|
72
|
+
quantization: "awq",
|
|
73
|
+
gpuMemoryUtilization: 0.9,
|
|
74
|
+
maxNumSeqs: 256,
|
|
75
|
+
});
|
|
76
|
+
const spec = p(result.servingRuntime).spec as any;
|
|
77
|
+
const container = spec.containers[0];
|
|
78
|
+
expect(container.name).toBe("kserve-container");
|
|
79
|
+
expect(container.command).toEqual(["vllm", "serve", "/mnt/models"]);
|
|
80
|
+
expect(container.args).toEqual([
|
|
81
|
+
"--tensor-parallel-size", "2",
|
|
82
|
+
"--max-model-len", "8192",
|
|
83
|
+
"--dtype", "bfloat16",
|
|
84
|
+
"--quantization", "awq",
|
|
85
|
+
"--gpu-memory-utilization", "0.9",
|
|
86
|
+
"--max-num-seqs", "256",
|
|
87
|
+
]);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("containerArgs escape hatch appends after typed flags", () => {
|
|
91
|
+
const result = VllmServingRuntime({
|
|
92
|
+
name: "vllm-runtime",
|
|
93
|
+
namespace: "models",
|
|
94
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
95
|
+
tensorParallelSize: 1,
|
|
96
|
+
containerArgs: ["--enable-prefix-caching", "--disable-log-requests"],
|
|
97
|
+
});
|
|
98
|
+
const spec = p(result.servingRuntime).spec as any;
|
|
99
|
+
expect(spec.containers[0].args).toEqual([
|
|
100
|
+
"--tensor-parallel-size", "1",
|
|
101
|
+
"--enable-prefix-caching",
|
|
102
|
+
"--disable-log-requests",
|
|
103
|
+
]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("omits unset vLLM flags rather than emitting empty/undefined args", () => {
|
|
107
|
+
const result = VllmServingRuntime({
|
|
108
|
+
name: "vllm-runtime",
|
|
109
|
+
namespace: "models",
|
|
110
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
111
|
+
});
|
|
112
|
+
const spec = p(result.servingRuntime).spec as any;
|
|
113
|
+
expect(spec.containers[0].args).toEqual([]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
test("GPU count maps to nvidia.com/gpu in resource requests and limits", () => {
|
|
117
|
+
const result = VllmServingRuntime({
|
|
118
|
+
name: "vllm-runtime",
|
|
119
|
+
namespace: "models",
|
|
120
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
121
|
+
resources: { cpu: "8", memory: "32Gi", gpu: 2 },
|
|
122
|
+
});
|
|
123
|
+
const spec = p(result.servingRuntime).spec as any;
|
|
124
|
+
const resources = spec.containers[0].resources;
|
|
125
|
+
expect(resources).toEqual({
|
|
126
|
+
requests: { cpu: "8", memory: "32Gi", "nvidia.com/gpu": "2" },
|
|
127
|
+
limits: { cpu: "8", memory: "32Gi", "nvidia.com/gpu": "2" },
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test("resources is omitted entirely when not provided", () => {
|
|
132
|
+
const result = VllmServingRuntime({
|
|
133
|
+
name: "vllm-runtime",
|
|
134
|
+
namespace: "models",
|
|
135
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
136
|
+
});
|
|
137
|
+
const spec = p(result.servingRuntime).spec as any;
|
|
138
|
+
expect(spec.containers[0].resources).toBeUndefined();
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
test("serializes to valid YAML", () => {
|
|
142
|
+
const result = VllmServingRuntime({
|
|
143
|
+
name: "vllm-runtime",
|
|
144
|
+
namespace: "models",
|
|
145
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
146
|
+
tensorParallelSize: 2,
|
|
147
|
+
resources: { cpu: "8", memory: "32Gi", gpu: 2 },
|
|
148
|
+
});
|
|
149
|
+
const yaml = emitYAML(p(result.servingRuntime), 0);
|
|
150
|
+
expect(yaml).toContain("vllm-runtime");
|
|
151
|
+
expect(yaml).toContain("tensor-parallel-size");
|
|
152
|
+
expect(yaml).not.toContain("[object Object]");
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("defaults.servingRuntime passthrough merges into the resource", () => {
|
|
156
|
+
const result = VllmServingRuntime({
|
|
157
|
+
name: "vllm-runtime",
|
|
158
|
+
namespace: "models",
|
|
159
|
+
image: "vllm/vllm-openai:v0.7.0",
|
|
160
|
+
defaults: {
|
|
161
|
+
servingRuntime: { spec: { replicas: 3 } },
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
const spec = p(result.servingRuntime).spec as any;
|
|
165
|
+
expect(spec.replicas).toBe(3);
|
|
166
|
+
});
|
|
167
|
+
});
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VllmServingRuntime composite — KServe ServingRuntime/ClusterServingRuntime
|
|
3
|
+
* running vLLM.
|
|
4
|
+
*
|
|
5
|
+
* Design rule (chant #982, #984): spec-true, not chant-flavored. Props mirror
|
|
6
|
+
* vLLM's real `vllm serve` CLI args — no generic runtime abstraction. KServe's
|
|
7
|
+
* ServingRuntime CRD is the pluggability seam; this composite types vLLM
|
|
8
|
+
* faithfully on top of it, it doesn't invent a wrapper around it.
|
|
9
|
+
*
|
|
10
|
+
* The model itself is not a prop here — an InferenceService (chant #985)
|
|
11
|
+
* references this runtime and supplies storageUri, which KServe mounts at
|
|
12
|
+
* /mnt/models inside the pod. That's the path this composite passes to
|
|
13
|
+
* `vllm serve`.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { Composite, mergeDefaults } from "@intentius/chant";
|
|
17
|
+
import {
|
|
18
|
+
ServingRuntime,
|
|
19
|
+
ClusterServingRuntime,
|
|
20
|
+
} from "../generated";
|
|
21
|
+
|
|
22
|
+
/** Container resource spec. GPU count maps to nvidia.com/gpu requests/limits. */
|
|
23
|
+
export interface VllmResourceSpec {
|
|
24
|
+
/** CPU request/limit (e.g. "4", "500m"). */
|
|
25
|
+
cpu?: string;
|
|
26
|
+
/** Memory request/limit (e.g. "16Gi"). */
|
|
27
|
+
memory?: string;
|
|
28
|
+
/** GPU count — adds nvidia.com/gpu to resource requests and limits. */
|
|
29
|
+
gpu?: number;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface VllmServingRuntimeProps {
|
|
33
|
+
/** ServingRuntime/ClusterServingRuntime name. */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Namespace — required unless clusterScoped is true. */
|
|
36
|
+
namespace?: string;
|
|
37
|
+
/**
|
|
38
|
+
* Emit a cluster-scoped ClusterServingRuntime instead of a namespaced
|
|
39
|
+
* ServingRuntime. Default false.
|
|
40
|
+
*/
|
|
41
|
+
clusterScoped?: boolean;
|
|
42
|
+
/** vLLM container image (e.g. "vllm/vllm-openai:v0.7.0"). */
|
|
43
|
+
image: string;
|
|
44
|
+
|
|
45
|
+
// ── vLLM args (spec-true to `vllm serve --help`) ──────────────────────
|
|
46
|
+
/** --tensor-parallel-size */
|
|
47
|
+
tensorParallelSize?: number;
|
|
48
|
+
/** --max-model-len */
|
|
49
|
+
maxModelLen?: number;
|
|
50
|
+
/** --dtype */
|
|
51
|
+
dtype?: "auto" | "half" | "float16" | "bfloat16" | "float" | "float32";
|
|
52
|
+
/** --quantization (e.g. "awq", "gptq", "fp8") */
|
|
53
|
+
quantization?: string;
|
|
54
|
+
/** --gpu-memory-utilization (0.0-1.0) */
|
|
55
|
+
gpuMemoryUtilization?: number;
|
|
56
|
+
/** --max-num-seqs */
|
|
57
|
+
maxNumSeqs?: number;
|
|
58
|
+
|
|
59
|
+
/** Container resources. gpu maps to nvidia.com/gpu. */
|
|
60
|
+
resources?: VllmResourceSpec;
|
|
61
|
+
/**
|
|
62
|
+
* Additional raw args appended after the typed vLLM flags — escape hatch
|
|
63
|
+
* for anything not yet modeled as a typed prop.
|
|
64
|
+
*/
|
|
65
|
+
containerArgs?: string[];
|
|
66
|
+
/**
|
|
67
|
+
* supportedModelFormats entry registered on the runtime. Default
|
|
68
|
+
* `{ name: "vllm", autoSelect: true }`.
|
|
69
|
+
*/
|
|
70
|
+
modelFormat?: { name: string; version?: string; autoSelect?: boolean; priority?: number };
|
|
71
|
+
/** Additional labels applied to the resource. */
|
|
72
|
+
labels?: Record<string, string>;
|
|
73
|
+
/** Per-member defaults for fine-grained overrides via mergeDefaults. */
|
|
74
|
+
defaults?: {
|
|
75
|
+
servingRuntime?: Partial<Record<string, unknown>>;
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface VllmServingRuntimeResult {
|
|
80
|
+
servingRuntime: InstanceType<typeof ServingRuntime> | InstanceType<typeof ClusterServingRuntime>;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Build the container args list, in `vllm serve --help` flag order. */
|
|
84
|
+
function buildVllmArgs(props: VllmServingRuntimeProps): string[] {
|
|
85
|
+
const {
|
|
86
|
+
tensorParallelSize,
|
|
87
|
+
maxModelLen,
|
|
88
|
+
dtype,
|
|
89
|
+
quantization,
|
|
90
|
+
gpuMemoryUtilization,
|
|
91
|
+
maxNumSeqs,
|
|
92
|
+
containerArgs = [],
|
|
93
|
+
} = props;
|
|
94
|
+
|
|
95
|
+
const args: string[] = [];
|
|
96
|
+
if (tensorParallelSize !== undefined) args.push("--tensor-parallel-size", String(tensorParallelSize));
|
|
97
|
+
if (maxModelLen !== undefined) args.push("--max-model-len", String(maxModelLen));
|
|
98
|
+
if (dtype !== undefined) args.push("--dtype", dtype);
|
|
99
|
+
if (quantization !== undefined) args.push("--quantization", quantization);
|
|
100
|
+
if (gpuMemoryUtilization !== undefined) args.push("--gpu-memory-utilization", String(gpuMemoryUtilization));
|
|
101
|
+
if (maxNumSeqs !== undefined) args.push("--max-num-seqs", String(maxNumSeqs));
|
|
102
|
+
args.push(...containerArgs);
|
|
103
|
+
return args;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Build container resource requests/limits. GPU count maps to nvidia.com/gpu. */
|
|
107
|
+
function buildResources(spec?: VllmResourceSpec): Record<string, unknown> | undefined {
|
|
108
|
+
if (!spec) return undefined;
|
|
109
|
+
const base: Record<string, unknown> = {};
|
|
110
|
+
if (spec.cpu) base.cpu = spec.cpu;
|
|
111
|
+
if (spec.memory) base.memory = spec.memory;
|
|
112
|
+
if (spec.gpu) base["nvidia.com/gpu"] = String(spec.gpu);
|
|
113
|
+
if (Object.keys(base).length === 0) return undefined;
|
|
114
|
+
return { requests: { ...base }, limits: { ...base } };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create a VllmServingRuntime composite — a KServe ServingRuntime (or, with
|
|
119
|
+
* `clusterScoped: true`, a ClusterServingRuntime) that runs vLLM's OpenAI-
|
|
120
|
+
* compatible server, with vLLM's own flags surfaced as typed props.
|
|
121
|
+
*
|
|
122
|
+
* @example
|
|
123
|
+
* ```ts
|
|
124
|
+
* import { VllmServingRuntime } from "@intentius/chant-lexicon-k8s";
|
|
125
|
+
*
|
|
126
|
+
* const runtime = VllmServingRuntime({
|
|
127
|
+
* name: "vllm-runtime",
|
|
128
|
+
* namespace: "models",
|
|
129
|
+
* image: "vllm/vllm-openai:v0.7.0",
|
|
130
|
+
* tensorParallelSize: 2,
|
|
131
|
+
* maxModelLen: 8192,
|
|
132
|
+
* dtype: "bfloat16",
|
|
133
|
+
* gpuMemoryUtilization: 0.9,
|
|
134
|
+
* resources: { cpu: "8", memory: "32Gi", gpu: 2 },
|
|
135
|
+
* });
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
export const VllmServingRuntime = Composite((props: VllmServingRuntimeProps) => {
|
|
139
|
+
const {
|
|
140
|
+
name,
|
|
141
|
+
namespace,
|
|
142
|
+
clusterScoped = false,
|
|
143
|
+
image,
|
|
144
|
+
modelFormat = { name: "vllm", autoSelect: true },
|
|
145
|
+
labels: extraLabels = {},
|
|
146
|
+
defaults: defs,
|
|
147
|
+
} = props;
|
|
148
|
+
|
|
149
|
+
if (!clusterScoped && !namespace) {
|
|
150
|
+
throw new Error(
|
|
151
|
+
`VllmServingRuntime("${name}"): namespace is required unless clusterScoped is true.`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const commonLabels: Record<string, string> = {
|
|
156
|
+
"app.kubernetes.io/name": name,
|
|
157
|
+
"app.kubernetes.io/managed-by": "chant",
|
|
158
|
+
"app.kubernetes.io/component": "vllm-serving-runtime",
|
|
159
|
+
...extraLabels,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const resources = buildResources(props.resources);
|
|
163
|
+
|
|
164
|
+
const spec: Record<string, unknown> = {
|
|
165
|
+
supportedModelFormats: [modelFormat],
|
|
166
|
+
containers: [
|
|
167
|
+
{
|
|
168
|
+
name: "kserve-container",
|
|
169
|
+
image,
|
|
170
|
+
command: ["vllm", "serve", "/mnt/models"],
|
|
171
|
+
args: buildVllmArgs(props),
|
|
172
|
+
ports: [{ containerPort: 8000, protocol: "TCP", name: "http1" }],
|
|
173
|
+
...(resources && { resources }),
|
|
174
|
+
},
|
|
175
|
+
],
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
const servingRuntime = clusterScoped
|
|
179
|
+
? new ClusterServingRuntime(mergeDefaults({
|
|
180
|
+
metadata: {
|
|
181
|
+
name,
|
|
182
|
+
labels: commonLabels,
|
|
183
|
+
},
|
|
184
|
+
spec,
|
|
185
|
+
}, defs?.servingRuntime))
|
|
186
|
+
: new ServingRuntime(mergeDefaults({
|
|
187
|
+
metadata: {
|
|
188
|
+
name,
|
|
189
|
+
namespace,
|
|
190
|
+
labels: commonLabels,
|
|
191
|
+
},
|
|
192
|
+
spec,
|
|
193
|
+
}, defs?.servingRuntime));
|
|
194
|
+
|
|
195
|
+
return { servingRuntime };
|
|
196
|
+
}, "VllmServingRuntime");
|
package/src/crd/cnpg.test.ts
CHANGED
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { describe, test, expect } from "vitest";
|
|
12
|
-
|
|
12
|
+
// chant #10 — CAPI's own `Cluster` kind (K8s::CAPI::Cluster) collides with
|
|
13
|
+
// CNPG's `Cluster` (K8s::Cnpg::Cluster) on the bare class name, so codegen's
|
|
14
|
+
// disambiguation now exports this one as `CnpgCluster`. The generated
|
|
15
|
+
// resource type name (K8s::Cnpg::Cluster) and the serialized `kind: Cluster`
|
|
16
|
+
// are unaffected — only the TS identifier changed.
|
|
17
|
+
import { CnpgCluster, ScheduledBackup, ObjectStore } from "../generated";
|
|
13
18
|
import { k8sSerializer } from "../serializer";
|
|
14
19
|
import { parseYAML } from "@intentius/chant/yaml";
|
|
15
20
|
|
|
@@ -20,7 +25,7 @@ function synth(logicalName: string, resource: unknown): any {
|
|
|
20
25
|
}
|
|
21
26
|
|
|
22
27
|
describe("CNPG Cluster", () => {
|
|
23
|
-
const cluster = new
|
|
28
|
+
const cluster = new CnpgCluster({
|
|
24
29
|
metadata: { name: "fountain-pg", namespace: "fountain", labels: { app: "fountain" } },
|
|
25
30
|
spec: {
|
|
26
31
|
instances: 1,
|
|
@@ -166,7 +171,7 @@ describe("barman-cloud ObjectStore", () => {
|
|
|
166
171
|
// not exist.
|
|
167
172
|
const clusterDoc = synth(
|
|
168
173
|
"fountainPg",
|
|
169
|
-
new
|
|
174
|
+
new CnpgCluster({
|
|
170
175
|
metadata: { name: "fountain-pg", namespace: "fountain" },
|
|
171
176
|
spec: {
|
|
172
177
|
instances: 1,
|
package/src/crd/crd-sources.ts
CHANGED
|
@@ -311,6 +311,172 @@ const K3S_API_VERSION = "v0.1.4"; // vendored by k3s v1.36.3+k3s1
|
|
|
311
311
|
const K3S_HELM_CONTROLLER_CRD_BASE = `https://raw.githubusercontent.com/k3s-io/helm-controller/${K3S_HELM_CONTROLLER_VERSION}/pkg/crds/yaml/generated`;
|
|
312
312
|
const K3S_API_CRD_BASE = `https://raw.githubusercontent.com/k3s-io/api/${K3S_API_VERSION}/pkg/crds/yaml/generated`;
|
|
313
313
|
|
|
314
|
+
/**
|
|
315
|
+
* KServe CRDs — serving.kserve.io
|
|
316
|
+
*
|
|
317
|
+
* The model-serving control plane chant #982 builds vLLM composites on top
|
|
318
|
+
* of. Produces (the `serving.kserve.io` group maps to the `KServe` namespace
|
|
319
|
+
* — see GROUP_NAMESPACE_OVERRIDES in group-namespace.ts; the first-segment
|
|
320
|
+
* rule would otherwise give `K8s::Serving::*`):
|
|
321
|
+
* K8s::KServe::InferenceService → serving.kserve.io/v1beta1, kind: InferenceService
|
|
322
|
+
* K8s::KServe::ServingRuntime → serving.kserve.io/v1alpha1, kind: ServingRuntime
|
|
323
|
+
* K8s::KServe::ClusterServingRuntime → serving.kserve.io/v1alpha1, kind: ClusterServingRuntime
|
|
324
|
+
*
|
|
325
|
+
* Sourced from `config/crd/full`, not `config/crd/minimal` — `full` is what
|
|
326
|
+
* the release's own kustomization.yaml (and `kubectl apply -k config/crd`)
|
|
327
|
+
* actually installs; `minimal` is a stripped, non-validating variant with no
|
|
328
|
+
* OpenAPI schema to speak of. `full`'s InferenceService schema is the entire
|
|
329
|
+
* spec-true predictor/transformer/explainer union (~1MB of YAML) — see
|
|
330
|
+
* schemaPatches in codegen/patches.ts if generation trips on its recursion
|
|
331
|
+
* depth.
|
|
332
|
+
*
|
|
333
|
+
* Pinned to the last patch of the 0.15 line rather than latest (0.20.0,
|
|
334
|
+
* weeks old at pin time) — a release that's had over a year to settle,
|
|
335
|
+
* matching the CNPG precedent above of tracking what a real operator runs
|
|
336
|
+
* rather than the newest tag.
|
|
337
|
+
*
|
|
338
|
+
* Operator install: kubectl apply --server-side -k
|
|
339
|
+
* "github.com/kserve/kserve/config/default?ref=v0.15.2"
|
|
340
|
+
*/
|
|
341
|
+
const KSERVE_VERSION = "v0.15.2";
|
|
342
|
+
const KSERVE_CRD_BASE = `https://raw.githubusercontent.com/kserve/kserve/${KSERVE_VERSION}/config/crd/full`;
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Cluster API (CAPI) core CRDs — cluster.x-k8s.io/v1beta2
|
|
346
|
+
*
|
|
347
|
+
* The provider-agnostic cluster-lifecycle surface CAPA, CAAPH, and every
|
|
348
|
+
* other CAPI provider build on. Only the two kinds a project actually
|
|
349
|
+
* declares are taken; `ClusterClass`, `MachineDeployment`, `MachineSet`,
|
|
350
|
+
* `MachineHealthCheck`, `MachineDrainRule`, the `ipam.cluster.x-k8s.io` pair,
|
|
351
|
+
* and `ExtensionConfig` are deliberately left out (the scale note at the top
|
|
352
|
+
* of this file: add the kinds a project uses). `ClusterResourceSet`, CAPI
|
|
353
|
+
* core's other addons.cluster.x-k8s.io kind, is a separate source below —
|
|
354
|
+
* see the CAAPH block for why it's grouped there. Produces (the group takes
|
|
355
|
+
* the `CAPI` override — see GROUP_NAMESPACE_OVERRIDES in group-namespace.ts,
|
|
356
|
+
* which avoids the `K8s::Cluster::Cluster` stutter):
|
|
357
|
+
* K8s::CAPI::Cluster → cluster.x-k8s.io/v1beta2, kind: Cluster
|
|
358
|
+
* K8s::CAPI::MachinePool → cluster.x-k8s.io/v1beta2, kind: MachinePool
|
|
359
|
+
*
|
|
360
|
+
* Controller install: kubectl apply -f
|
|
361
|
+
* https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.14.0/cluster-api-components.yaml
|
|
362
|
+
*/
|
|
363
|
+
const CAPI_VERSION = "v1.14.0";
|
|
364
|
+
const CAPI_CRD_BASE = `https://raw.githubusercontent.com/kubernetes-sigs/cluster-api/${CAPI_VERSION}/core/config/crd/bases`;
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Cluster API Provider AWS (CAPA) CRDs — infrastructure.cluster.x-k8s.io + controlplane.cluster.x-k8s.io
|
|
368
|
+
*
|
|
369
|
+
* The AWS infrastructure and EKS control-plane providers for CAPI. Produces
|
|
370
|
+
* (first-segment rule, no override — the `AWS` kind prefix already keeps
|
|
371
|
+
* these from reading like anything else in the file):
|
|
372
|
+
* K8s::Infrastructure::AWSManagedCluster → infrastructure.cluster.x-k8s.io/v1beta2
|
|
373
|
+
* K8s::Infrastructure::AWSManagedMachinePool → infrastructure.cluster.x-k8s.io/v1beta2
|
|
374
|
+
* K8s::Infrastructure::AWSClusterControllerIdentity → infrastructure.cluster.x-k8s.io/v1beta2
|
|
375
|
+
* K8s::Controlplane::AWSManagedControlPlane → controlplane.cluster.x-k8s.io/v1beta2
|
|
376
|
+
*
|
|
377
|
+
* Contradicts chant #10's own text: the issue describes AWSManagedControlPlane
|
|
378
|
+
* as an `infrastructure.cluster.x-k8s.io` kind alongside the other three. The
|
|
379
|
+
* shipped CRD (`config/crd/bases/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml`
|
|
380
|
+
* in the v2.13.0 tag) puts it under `controlplane.cluster.x-k8s.io` instead —
|
|
381
|
+
* CAPA's EKS control-plane provider is a `controlplane.*` provider like
|
|
382
|
+
* kubeadm's, not an infra one. The parser reads `spec.group` from the fetched
|
|
383
|
+
* CRD document itself (`parseCRDSpec` in `crd/parser.ts`), so the source list
|
|
384
|
+
* below only needs the right URL; the resulting type name follows upstream's
|
|
385
|
+
* actual group, not the issue text's.
|
|
386
|
+
*
|
|
387
|
+
* Operator install: with clusterctl,
|
|
388
|
+
* clusterctl init --infrastructure aws:v2.13.0
|
|
389
|
+
*/
|
|
390
|
+
const CAPA_VERSION = "v2.13.0";
|
|
391
|
+
const CAPA_CRD_BASE = `https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-provider-aws/${CAPA_VERSION}/config/crd/bases`;
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Cluster API Addon Provider for Helm (CAAPH) CRD, plus CAPI core's own
|
|
395
|
+
* addon-binding kind — both addons.cluster.x-k8s.io/v1alpha1|v1beta2
|
|
396
|
+
*
|
|
397
|
+
* `HelmChartProxy` is CAAPH's kind — installs a Helm chart across every
|
|
398
|
+
* cluster a label selector matches. `ClusterResourceSet` ships in CAPI
|
|
399
|
+
* core's own `core/` component (same CAPI_VERSION/CAPI_CRD_BASE pin as the
|
|
400
|
+
* `Cluster`/`MachinePool` source above), not a separate provider — it binds
|
|
401
|
+
* a set of raw manifests (ConfigMaps/Secrets) to matching clusters. They're
|
|
402
|
+
* grouped in one source block because both are what the RegionCluster
|
|
403
|
+
* composite's flux-addon wiring needs (chant#11) and both share the
|
|
404
|
+
* `addons.cluster.x-k8s.io` group, so both land under `K8s::Addons::*`:
|
|
405
|
+
* K8s::Addons::HelmChartProxy → addons.cluster.x-k8s.io/v1alpha1, kind: HelmChartProxy
|
|
406
|
+
* K8s::Addons::ClusterResourceSet → addons.cluster.x-k8s.io/v1beta2, kind: ClusterResourceSet
|
|
407
|
+
*
|
|
408
|
+
* `HelmReleaseProxy` (CAAPH's per-cluster status-tracking CR, never authored
|
|
409
|
+
* directly) is deliberately left out.
|
|
410
|
+
*
|
|
411
|
+
* Operator install: with clusterctl,
|
|
412
|
+
* clusterctl init --addon helm:v0.6.4
|
|
413
|
+
*/
|
|
414
|
+
const CAAPH_VERSION = "v0.6.4";
|
|
415
|
+
const CAAPH_CRD_BASE = `https://raw.githubusercontent.com/kubernetes-sigs/cluster-api-addon-provider-helm/${CAAPH_VERSION}/config/crd/bases`;
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* AWS Controllers for Kubernetes (ACK) — S3 controller CRD — s3.services.k8s.aws/v1alpha1
|
|
419
|
+
*
|
|
420
|
+
* Each ACK service controller is its own release train with its own CRDs, so
|
|
421
|
+
* (per the docs' "add the specific kinds a project uses" guidance, and
|
|
422
|
+
* matching how the ACK IAM group below stays split from EKS/RDS/S3) it is
|
|
423
|
+
* its own scoped source rather than one entry per AWS "provider". Produces:
|
|
424
|
+
* K8s::S3::Bucket → s3.services.k8s.aws/v1alpha1, kind: Bucket
|
|
425
|
+
*
|
|
426
|
+
* Controller install: helm install ack-s3-controller
|
|
427
|
+
* oci://public.ecr.aws/aws-controllers-k8s/s3-chart --version 1.10.0
|
|
428
|
+
*/
|
|
429
|
+
const ACK_S3_VERSION = "v1.10.0";
|
|
430
|
+
const ACK_S3_CRD_BASE = `https://raw.githubusercontent.com/aws-controllers-k8s/s3-controller/${ACK_S3_VERSION}/config/crd/bases`;
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* ACK RDS controller CRD — rds.services.k8s.aws/v1alpha1
|
|
434
|
+
*
|
|
435
|
+
* Only `DBInstance` — the standalone-instance kind a project provisions
|
|
436
|
+
* directly. `DBCluster` and its endpoint/parameter-group/snapshot satellites
|
|
437
|
+
* are left out (no known consumer; add them when one shows up). Produces:
|
|
438
|
+
* K8s::Rds::DBInstance → rds.services.k8s.aws/v1alpha1, kind: DBInstance
|
|
439
|
+
*
|
|
440
|
+
* Controller install: helm install ack-rds-controller
|
|
441
|
+
* oci://public.ecr.aws/aws-controllers-k8s/rds-chart --version 1.11.1
|
|
442
|
+
*/
|
|
443
|
+
const ACK_RDS_VERSION = "v1.11.1";
|
|
444
|
+
const ACK_RDS_CRD_BASE = `https://raw.githubusercontent.com/aws-controllers-k8s/rds-controller/${ACK_RDS_VERSION}/config/crd/bases`;
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* ACK IAM controller CRDs — iam.services.k8s.aws/v1alpha1
|
|
448
|
+
*
|
|
449
|
+
* `Role`, `User`, `Policy` — the three IAM primitives a cluster workload
|
|
450
|
+
* needing AWS access declares (a Role's trust policy, a User's access keys,
|
|
451
|
+
* a Policy document attached to either). `Group`, `InstanceProfile`,
|
|
452
|
+
* `OpenIDConnectProvider`, and `ServiceLinkedRole` are left out. Produces:
|
|
453
|
+
* K8s::Iam::Role → iam.services.k8s.aws/v1alpha1, kind: Role
|
|
454
|
+
* K8s::Iam::User → iam.services.k8s.aws/v1alpha1, kind: User
|
|
455
|
+
* K8s::Iam::Policy → iam.services.k8s.aws/v1alpha1, kind: Policy
|
|
456
|
+
*
|
|
457
|
+
* Controller install: helm install ack-iam-controller
|
|
458
|
+
* oci://public.ecr.aws/aws-controllers-k8s/iam-chart --version 1.8.1
|
|
459
|
+
*/
|
|
460
|
+
const ACK_IAM_VERSION = "v1.8.1";
|
|
461
|
+
const ACK_IAM_CRD_BASE = `https://raw.githubusercontent.com/aws-controllers-k8s/iam-controller/${ACK_IAM_VERSION}/config/crd/bases`;
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* ACK EKS controller CRD — eks.services.k8s.aws/v1alpha1
|
|
465
|
+
*
|
|
466
|
+
* Only `PodIdentityAssociation` — EKS Pod Identity is the modern replacement
|
|
467
|
+
* for IRSA, and the ACK-managed way to bind a Kubernetes ServiceAccount to
|
|
468
|
+
* an IAM role without a webhook. `Cluster`, `Addon`, `Nodegroup`,
|
|
469
|
+
* `FargateProfile`, `AccessEntry`, `IdentityProviderConfig`, and
|
|
470
|
+
* `Capability` are left out (this k8s lexicon doesn't provision the EKS
|
|
471
|
+
* cluster itself). Produces:
|
|
472
|
+
* K8s::Eks::PodIdentityAssociation → eks.services.k8s.aws/v1alpha1, kind: PodIdentityAssociation
|
|
473
|
+
*
|
|
474
|
+
* Controller install: helm install ack-eks-controller
|
|
475
|
+
* oci://public.ecr.aws/aws-controllers-k8s/eks-chart --version 1.20.0
|
|
476
|
+
*/
|
|
477
|
+
const ACK_EKS_VERSION = "v1.20.0";
|
|
478
|
+
const ACK_EKS_CRD_BASE = `https://raw.githubusercontent.com/aws-controllers-k8s/eks-controller/${ACK_EKS_VERSION}/config/crd/bases`;
|
|
479
|
+
|
|
314
480
|
export const CRD_SOURCES: CRDSource[] = [
|
|
315
481
|
{ type: "url", url: `${KUBERAY_CRD_BASE}/ray.io_rayclusters.yaml` },
|
|
316
482
|
{ type: "url", url: `${KUBERAY_CRD_BASE}/ray.io_rayjobs.yaml` },
|
|
@@ -378,4 +544,21 @@ export const CRD_SOURCES: CRDSource[] = [
|
|
|
378
544
|
version: KUBEMICROVM_VERSION,
|
|
379
545
|
kinds: ["MicroVM", "MicroVMImage", "MicroVMNetwork", "MicroVMClass", "MicroVMReplicaSet"],
|
|
380
546
|
},
|
|
547
|
+
{ type: "url", url: `${KSERVE_CRD_BASE}/serving.kserve.io_inferenceservices.yaml` },
|
|
548
|
+
{ type: "url", url: `${KSERVE_CRD_BASE}/serving.kserve.io_servingruntimes.yaml` },
|
|
549
|
+
{ type: "url", url: `${KSERVE_CRD_BASE}/serving.kserve.io_clusterservingruntimes.yaml` },
|
|
550
|
+
{ type: "url", url: `${CAPI_CRD_BASE}/cluster.x-k8s.io_clusters.yaml` },
|
|
551
|
+
{ type: "url", url: `${CAPI_CRD_BASE}/cluster.x-k8s.io_machinepools.yaml` },
|
|
552
|
+
{ type: "url", url: `${CAPA_CRD_BASE}/controlplane.cluster.x-k8s.io_awsmanagedcontrolplanes.yaml` },
|
|
553
|
+
{ type: "url", url: `${CAPA_CRD_BASE}/infrastructure.cluster.x-k8s.io_awsmanagedclusters.yaml` },
|
|
554
|
+
{ type: "url", url: `${CAPA_CRD_BASE}/infrastructure.cluster.x-k8s.io_awsmanagedmachinepools.yaml` },
|
|
555
|
+
{ type: "url", url: `${CAPA_CRD_BASE}/infrastructure.cluster.x-k8s.io_awsclustercontrolleridentities.yaml` },
|
|
556
|
+
{ type: "url", url: `${CAAPH_CRD_BASE}/addons.cluster.x-k8s.io_helmchartproxies.yaml` },
|
|
557
|
+
{ type: "url", url: `${CAPI_CRD_BASE}/addons.cluster.x-k8s.io_clusterresourcesets.yaml` },
|
|
558
|
+
{ type: "url", url: `${ACK_S3_CRD_BASE}/s3.services.k8s.aws_buckets.yaml` },
|
|
559
|
+
{ type: "url", url: `${ACK_RDS_CRD_BASE}/rds.services.k8s.aws_dbinstances.yaml` },
|
|
560
|
+
{ type: "url", url: `${ACK_IAM_CRD_BASE}/iam.services.k8s.aws_roles.yaml` },
|
|
561
|
+
{ type: "url", url: `${ACK_IAM_CRD_BASE}/iam.services.k8s.aws_users.yaml` },
|
|
562
|
+
{ type: "url", url: `${ACK_IAM_CRD_BASE}/iam.services.k8s.aws_policies.yaml` },
|
|
563
|
+
{ type: "url", url: `${ACK_EKS_CRD_BASE}/eks.services.k8s.aws_podidentityassociations.yaml` },
|
|
381
564
|
];
|
package/src/crd/parser.test.ts
CHANGED
|
@@ -286,6 +286,20 @@ describe("parseCRDSpec", () => {
|
|
|
286
286
|
expect(results[0].resource.typeName).toBe("K8s::Argo::Application");
|
|
287
287
|
});
|
|
288
288
|
|
|
289
|
+
test("serving.kserve.io group maps to the KServe namespace (override)", () => {
|
|
290
|
+
const spec = {
|
|
291
|
+
group: "serving.kserve.io",
|
|
292
|
+
names: { kind: "InferenceService", plural: "inferenceservices" },
|
|
293
|
+
scope: "Namespaced" as const,
|
|
294
|
+
versions: [
|
|
295
|
+
{ name: "v1beta1", served: true, storage: true },
|
|
296
|
+
],
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
const results = parseCRDSpec(spec);
|
|
300
|
+
expect(results[0].resource.typeName).toBe("K8s::KServe::InferenceService");
|
|
301
|
+
});
|
|
302
|
+
|
|
289
303
|
test("all Flux toolkit + operator groups collapse to the Flux namespace", () => {
|
|
290
304
|
const cases: Array<[string, string]> = [
|
|
291
305
|
["source.toolkit.fluxcd.io", "GitRepository"],
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
k8sListMapOrderKey,
|
|
56
56
|
} from "@intentius/chant/managed-fields";
|
|
57
57
|
import { LABEL_OWNERSHIP_KEYS, OWNERSHIP_MANAGED_BY_VALUE } from "@intentius/chant/ownership";
|
|
58
|
+
import { GENERATED_ONCE_LABEL_KEY } from "./secret-labels";
|
|
58
59
|
|
|
59
60
|
/**
|
|
60
61
|
* Kubernetes-defaulted fields, per entity type, as index-erased property
|
|
@@ -115,6 +116,10 @@ export const K8S_SERVICE_DEFAULTS: Record<string, Record<string, unknown>> = {
|
|
|
115
116
|
"spec.ipFamilyPolicy": "SingleStack",
|
|
116
117
|
"spec.ports[].protocol": "TCP",
|
|
117
118
|
},
|
|
119
|
+
"K8s::Core::Secret": {
|
|
120
|
+
// The API server defaults an untyped Secret to Opaque (#1830).
|
|
121
|
+
"type": "Opaque",
|
|
122
|
+
},
|
|
118
123
|
};
|
|
119
124
|
|
|
120
125
|
/**
|
|
@@ -142,6 +147,9 @@ export const K8S_SERVER_ASSIGNED_PATTERNS: Record<string, ReadonlySet<string>> =
|
|
|
142
147
|
const K8S_OWNERSHIP_LABEL_PATTERNS: ReadonlySet<string> = new Set([
|
|
143
148
|
`metadata.labels.${LABEL_OWNERSHIP_KEYS.stack}`,
|
|
144
149
|
`metadata.labels.${LABEL_OWNERSHIP_KEYS.env}`,
|
|
150
|
+
// The generated-once marker (#1830) is stamped by the secret store at
|
|
151
|
+
// mint time, never by a serializer — chant's own signature, not drift.
|
|
152
|
+
`metadata.labels.${GENERATED_ONCE_LABEL_KEY}`,
|
|
145
153
|
]);
|
|
146
154
|
|
|
147
155
|
const K8S_MANAGED_BY_LABEL_PATTERN = `metadata.labels.${LABEL_OWNERSHIP_KEYS.managedBy}`;
|
|
@@ -306,7 +314,23 @@ function isRecordLike(value: unknown): value is Record<string, unknown> {
|
|
|
306
314
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
307
315
|
}
|
|
308
316
|
|
|
317
|
+
/**
|
|
318
|
+
* The paths on a `K8s::Core::Secret` whose values are secret material —
|
|
319
|
+
* `data.<key>` (live and declared base64) and `stringData.<key>` (declared
|
|
320
|
+
* plaintext). Masked on BOTH sides by core's normalization pass, so presence
|
|
321
|
+
* and key names still classify as drift while no value (or value-derived
|
|
322
|
+
* comparison) ever reaches a diff row — #1365 decision 6, the contract #1830's
|
|
323
|
+
* generated-once secrets rely on. Key-name masking alone cannot do this:
|
|
324
|
+
* Secret keys are arbitrary (`app.conf`), not name-shaped.
|
|
325
|
+
*/
|
|
326
|
+
const K8S_SECRET_ENTITY_TYPE = "K8s::Core::Secret";
|
|
327
|
+
const K8S_SECRET_VALUE_PATTERN = /^(?:data|stringData)\./;
|
|
328
|
+
|
|
309
329
|
export const k8sDeepNormalizationHooks: DeepNormalizationHooks = {
|
|
330
|
+
mask(node: DeepNode): boolean {
|
|
331
|
+
return node.entityType === K8S_SECRET_ENTITY_TYPE && K8S_SECRET_VALUE_PATTERN.test(node.pattern);
|
|
332
|
+
},
|
|
333
|
+
|
|
310
334
|
prune(node: DeepNode): boolean {
|
|
311
335
|
if (K8S_OBJECT_ENVELOPE_PRUNE_PATTERNS.has(node.pattern)) return true;
|
|
312
336
|
|