@intentius/chant-lexicon-k8s 0.1.0 → 0.1.4
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/integrity.json +27 -24
- package/dist/manifest.json +1 -1
- package/dist/meta.json +121 -0
- package/dist/rules/k8s-helpers.ts +39 -0
- package/dist/rules/wk8401.ts +98 -0
- package/dist/rules/wk8402.ts +43 -0
- package/dist/rules/wk8403.ts +60 -0
- package/dist/types/index.d.ts +30 -0
- package/package.json +4 -2
- package/src/codegen/generate.ts +22 -0
- package/src/composites/index.ts +6 -0
- package/src/composites/ray-cluster.ts +590 -0
- package/src/composites/ray-job.ts +235 -0
- package/src/composites/ray-service.ts +271 -0
- package/src/crd/crd-sources.ts +29 -0
- package/src/crd/parser.ts +17 -12
- package/src/generated/index.d.ts +30 -0
- package/src/generated/index.ts +13 -0
- package/src/generated/lexicon-k8s.json +121 -0
- package/src/index.ts +4 -0
- package/src/lint/post-synth/k8s-helpers.ts +39 -0
- package/src/lint/post-synth/post-synth.test.ts +148 -0
- package/src/lint/post-synth/wk8401.ts +98 -0
- package/src/lint/post-synth/wk8402.ts +43 -0
- package/src/lint/post-synth/wk8403.ts +60 -0
- package/src/plugin.test.ts +2 -2
- package/src/serializer.ts +2 -0
- package/src/skills/chant-k8s-ray.md +252 -0
package/dist/integrity.json
CHANGED
|
@@ -1,36 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "xxhash64",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
5
|
-
"meta.json": "
|
|
6
|
-
"types/index.d.ts": "
|
|
4
|
+
"manifest.json": "ba34543f183fd269",
|
|
5
|
+
"meta.json": "939607658c0e8f4a",
|
|
6
|
+
"types/index.d.ts": "cbd8853f04c0849c",
|
|
7
|
+
"rules/hardcoded-namespace.ts": "54b216c71018e101",
|
|
7
8
|
"rules/missing-resource-limits.ts": "a6f776d2ff477948",
|
|
8
9
|
"rules/latest-image-tag.ts": "eb48e8d61e4ca84e",
|
|
9
|
-
"rules/hardcoded-namespace.ts": "54b216c71018e101",
|
|
10
|
-
"rules/wk8201.ts": "4dbbd20e21b5fa04",
|
|
11
|
-
"rules/wk8204.ts": "9244d6fdd6d2f7d",
|
|
12
|
-
"rules/wk8301.ts": "283265ab0c5b8511",
|
|
13
|
-
"rules/wk8306.ts": "5338575bfb0f9251",
|
|
14
|
-
"rules/wk8102.ts": "78d4aac387107b56",
|
|
15
|
-
"rules/wk8305.ts": "5c4b9482a0f3b91d",
|
|
16
|
-
"rules/wk8101.ts": "f8ffcf6e5c89076b",
|
|
17
|
-
"rules/wk8302.ts": "a80d1eab37c0dbe4",
|
|
18
|
-
"rules/wk8005.ts": "a9a1b93b80f3aa51",
|
|
19
10
|
"rules/wk8209.ts": "820df53f304e0a59",
|
|
20
|
-
"rules/wk8203.ts": "f5bf17539c0428c5",
|
|
21
|
-
"rules/wk8104.ts": "94bd75c16b8d50b7",
|
|
22
|
-
"rules/wk8303.ts": "c545464e2af8fbb9",
|
|
23
|
-
"rules/wk8103.ts": "e5d6a506d966e312",
|
|
24
|
-
"rules/wk8205.ts": "d000527c6371a05",
|
|
25
|
-
"rules/wk8042.ts": "6064c84481ae8551",
|
|
26
|
-
"rules/wk8006.ts": "6e04f754f79f076e",
|
|
27
|
-
"rules/wk8041.ts": "4df512c93caaef50",
|
|
28
|
-
"rules/wk8304.ts": "f51bf894c5a08dbe",
|
|
29
11
|
"rules/wk8202.ts": "6bd950ae2128256c",
|
|
30
12
|
"rules/wk8208.ts": "1133f9e53c174ae9",
|
|
13
|
+
"rules/k8s-helpers.ts": "4611ed839c0219b5",
|
|
14
|
+
"rules/wk8302.ts": "a80d1eab37c0dbe4",
|
|
15
|
+
"rules/wk8205.ts": "d000527c6371a05",
|
|
16
|
+
"rules/wk8006.ts": "6e04f754f79f076e",
|
|
17
|
+
"rules/wk8305.ts": "5c4b9482a0f3b91d",
|
|
18
|
+
"rules/wk8204.ts": "9244d6fdd6d2f7d",
|
|
19
|
+
"rules/wk8402.ts": "83b420543e246d20",
|
|
20
|
+
"rules/wk8401.ts": "62d5acc6a674b8ed",
|
|
21
|
+
"rules/wk8201.ts": "4dbbd20e21b5fa04",
|
|
22
|
+
"rules/wk8104.ts": "94bd75c16b8d50b7",
|
|
23
|
+
"rules/wk8101.ts": "f8ffcf6e5c89076b",
|
|
31
24
|
"rules/wk8105.ts": "8dbcfe399f23656a",
|
|
32
|
-
"rules/
|
|
25
|
+
"rules/wk8041.ts": "4df512c93caaef50",
|
|
33
26
|
"rules/wk8207.ts": "6f2bc621d530afa2",
|
|
27
|
+
"rules/wk8102.ts": "78d4aac387107b56",
|
|
28
|
+
"rules/wk8005.ts": "a9a1b93b80f3aa51",
|
|
29
|
+
"rules/wk8301.ts": "283265ab0c5b8511",
|
|
30
|
+
"rules/wk8304.ts": "f51bf894c5a08dbe",
|
|
31
|
+
"rules/wk8042.ts": "6064c84481ae8551",
|
|
32
|
+
"rules/wk8403.ts": "81397f7658d888a",
|
|
33
|
+
"rules/wk8103.ts": "e5d6a506d966e312",
|
|
34
|
+
"rules/wk8306.ts": "5338575bfb0f9251",
|
|
35
|
+
"rules/wk8203.ts": "f5bf17539c0428c5",
|
|
36
|
+
"rules/wk8303.ts": "c545464e2af8fbb9",
|
|
34
37
|
"skills/chant-k8s.md": "bf3ac0c5bddd5d2a",
|
|
35
38
|
"skills/chant-k8s-patterns.md": "c5151ed799145c4b",
|
|
36
39
|
"skills/chant-k8s-deployment-strategies.md": "74f179e7cdb15ed5",
|
|
@@ -39,5 +42,5 @@
|
|
|
39
42
|
"skills/chant-k8s-gke.md": "2f65ca45aef40c22",
|
|
40
43
|
"skills/chant-k8s-aks.md": "764fa4b1408b618d"
|
|
41
44
|
},
|
|
42
|
-
"composite": "
|
|
45
|
+
"composite": "71a7bf45b20b1ebd"
|
|
43
46
|
}
|
package/dist/manifest.json
CHANGED
package/dist/meta.json
CHANGED
|
@@ -46,6 +46,11 @@
|
|
|
46
46
|
"kind": "property",
|
|
47
47
|
"lexicon": "k8s"
|
|
48
48
|
},
|
|
49
|
+
"AutoscalerOptions": {
|
|
50
|
+
"resourceType": "K8s::Ray::RayCluster.autoscalerOptions",
|
|
51
|
+
"kind": "property",
|
|
52
|
+
"lexicon": "k8s"
|
|
53
|
+
},
|
|
49
54
|
"BatchJob": {
|
|
50
55
|
"resourceType": "K8s::Batch::Job",
|
|
51
56
|
"kind": "resource",
|
|
@@ -439,6 +444,11 @@
|
|
|
439
444
|
"apiVersion": "flowcontrol.apiserver.k8s.io/v1",
|
|
440
445
|
"gvkKind": "FlowSchemaList"
|
|
441
446
|
},
|
|
447
|
+
"GcsFaultToleranceOptions": {
|
|
448
|
+
"resourceType": "K8s::Ray::RayCluster.gcsFaultToleranceOptions",
|
|
449
|
+
"kind": "property",
|
|
450
|
+
"lexicon": "k8s"
|
|
451
|
+
},
|
|
442
452
|
"HPA": {
|
|
443
453
|
"resourceType": "K8s::Autoscaling::HorizontalPodAutoscaler",
|
|
444
454
|
"kind": "resource",
|
|
@@ -456,6 +466,11 @@
|
|
|
456
466
|
"kind": "property",
|
|
457
467
|
"lexicon": "k8s"
|
|
458
468
|
},
|
|
469
|
+
"HeadGroupSpec": {
|
|
470
|
+
"resourceType": "K8s::Ray::RayCluster.headGroupSpec",
|
|
471
|
+
"kind": "property",
|
|
472
|
+
"lexicon": "k8s"
|
|
473
|
+
},
|
|
459
474
|
"HorizontalPodAutoscaler": {
|
|
460
475
|
"resourceType": "K8s::Autoscaling::HorizontalPodAutoscaler",
|
|
461
476
|
"kind": "resource",
|
|
@@ -942,6 +957,87 @@
|
|
|
942
957
|
}
|
|
943
958
|
}
|
|
944
959
|
},
|
|
960
|
+
"RayCluster": {
|
|
961
|
+
"resourceType": "K8s::Ray::RayCluster",
|
|
962
|
+
"kind": "resource",
|
|
963
|
+
"lexicon": "k8s",
|
|
964
|
+
"apiVersion": "ray.io/v1",
|
|
965
|
+
"gvkKind": "RayCluster"
|
|
966
|
+
},
|
|
967
|
+
"RayClusterConfig": {
|
|
968
|
+
"resourceType": "K8s::Ray::RayService.rayClusterConfig",
|
|
969
|
+
"kind": "property",
|
|
970
|
+
"lexicon": "k8s"
|
|
971
|
+
},
|
|
972
|
+
"RayClusterSpec": {
|
|
973
|
+
"resourceType": "K8s::Ray::RayJob.rayClusterSpec",
|
|
974
|
+
"kind": "property",
|
|
975
|
+
"lexicon": "k8s"
|
|
976
|
+
},
|
|
977
|
+
"RayCluster_AutoscalerOptions": {
|
|
978
|
+
"resourceType": "K8s::Ray::RayCluster.autoscalerOptions",
|
|
979
|
+
"kind": "property",
|
|
980
|
+
"lexicon": "k8s"
|
|
981
|
+
},
|
|
982
|
+
"RayCluster_GcsFaultToleranceOptions": {
|
|
983
|
+
"resourceType": "K8s::Ray::RayCluster.gcsFaultToleranceOptions",
|
|
984
|
+
"kind": "property",
|
|
985
|
+
"lexicon": "k8s"
|
|
986
|
+
},
|
|
987
|
+
"RayCluster_HeadGroupSpec": {
|
|
988
|
+
"resourceType": "K8s::Ray::RayCluster.headGroupSpec",
|
|
989
|
+
"kind": "property",
|
|
990
|
+
"lexicon": "k8s"
|
|
991
|
+
},
|
|
992
|
+
"RayCluster_WorkerGroupSpec": {
|
|
993
|
+
"resourceType": "K8s::Ray::RayCluster.workerGroupSpecs",
|
|
994
|
+
"kind": "property",
|
|
995
|
+
"lexicon": "k8s"
|
|
996
|
+
},
|
|
997
|
+
"RayJob": {
|
|
998
|
+
"resourceType": "K8s::Ray::RayJob",
|
|
999
|
+
"kind": "resource",
|
|
1000
|
+
"lexicon": "k8s",
|
|
1001
|
+
"apiVersion": "ray.io/v1",
|
|
1002
|
+
"gvkKind": "RayJob"
|
|
1003
|
+
},
|
|
1004
|
+
"RayJob_RayClusterSpec": {
|
|
1005
|
+
"resourceType": "K8s::Ray::RayJob.rayClusterSpec",
|
|
1006
|
+
"kind": "property",
|
|
1007
|
+
"lexicon": "k8s"
|
|
1008
|
+
},
|
|
1009
|
+
"RayJob_SubmitterConfig": {
|
|
1010
|
+
"resourceType": "K8s::Ray::RayJob.submitterConfig",
|
|
1011
|
+
"kind": "property",
|
|
1012
|
+
"lexicon": "k8s"
|
|
1013
|
+
},
|
|
1014
|
+
"RayJob_SubmitterPodTemplate": {
|
|
1015
|
+
"resourceType": "K8s::Ray::RayJob.submitterPodTemplate",
|
|
1016
|
+
"kind": "property",
|
|
1017
|
+
"lexicon": "k8s"
|
|
1018
|
+
},
|
|
1019
|
+
"RayService": {
|
|
1020
|
+
"resourceType": "K8s::Ray::RayService",
|
|
1021
|
+
"kind": "resource",
|
|
1022
|
+
"lexicon": "k8s",
|
|
1023
|
+
"apiVersion": "ray.io/v1",
|
|
1024
|
+
"gvkKind": "RayService"
|
|
1025
|
+
},
|
|
1026
|
+
"RayService_RayClusterConfig": {
|
|
1027
|
+
"resourceType": "K8s::Ray::RayService.rayClusterConfig",
|
|
1028
|
+
"kind": "property",
|
|
1029
|
+
"lexicon": "k8s"
|
|
1030
|
+
},
|
|
1031
|
+
"RayService_ServeService": {
|
|
1032
|
+
"resourceType": "K8s::Ray::RayService.serveService",
|
|
1033
|
+
"kind": "property",
|
|
1034
|
+
"lexicon": "k8s"
|
|
1035
|
+
},
|
|
1036
|
+
"RayService_UpgradeStrategy": {
|
|
1037
|
+
"resourceType": "K8s::Ray::RayService.upgradeStrategy",
|
|
1038
|
+
"kind": "property",
|
|
1039
|
+
"lexicon": "k8s"
|
|
1040
|
+
},
|
|
945
1041
|
"ReplicaSet": {
|
|
946
1042
|
"resourceType": "K8s::Apps::ReplicaSet",
|
|
947
1043
|
"kind": "resource",
|
|
@@ -1164,6 +1260,11 @@
|
|
|
1164
1260
|
"apiVersion": "authorization.k8s.io/v1",
|
|
1165
1261
|
"gvkKind": "SelfSubjectRulesReview"
|
|
1166
1262
|
},
|
|
1263
|
+
"ServeService": {
|
|
1264
|
+
"resourceType": "K8s::Ray::RayService.serveService",
|
|
1265
|
+
"kind": "property",
|
|
1266
|
+
"lexicon": "k8s"
|
|
1267
|
+
},
|
|
1167
1268
|
"Service": {
|
|
1168
1269
|
"resourceType": "K8s::Core::Service",
|
|
1169
1270
|
"kind": "resource",
|
|
@@ -1281,6 +1382,16 @@
|
|
|
1281
1382
|
"apiVersion": "authorization.k8s.io/v1",
|
|
1282
1383
|
"gvkKind": "SubjectAccessReview"
|
|
1283
1384
|
},
|
|
1385
|
+
"SubmitterConfig": {
|
|
1386
|
+
"resourceType": "K8s::Ray::RayJob.submitterConfig",
|
|
1387
|
+
"kind": "property",
|
|
1388
|
+
"lexicon": "k8s"
|
|
1389
|
+
},
|
|
1390
|
+
"SubmitterPodTemplate": {
|
|
1391
|
+
"resourceType": "K8s::Ray::RayJob.submitterPodTemplate",
|
|
1392
|
+
"kind": "property",
|
|
1393
|
+
"lexicon": "k8s"
|
|
1394
|
+
},
|
|
1284
1395
|
"TCPSocketAction": {
|
|
1285
1396
|
"resourceType": "K8s::Core::TCPSocketAction",
|
|
1286
1397
|
"kind": "property",
|
|
@@ -1323,6 +1434,11 @@
|
|
|
1323
1434
|
}
|
|
1324
1435
|
}
|
|
1325
1436
|
},
|
|
1437
|
+
"UpgradeStrategy": {
|
|
1438
|
+
"resourceType": "K8s::Ray::RayService.upgradeStrategy",
|
|
1439
|
+
"kind": "property",
|
|
1440
|
+
"lexicon": "k8s"
|
|
1441
|
+
},
|
|
1326
1442
|
"ValidatingAdmissionPolicy": {
|
|
1327
1443
|
"resourceType": "K8s::Admissionregistration::ValidatingAdmissionPolicy",
|
|
1328
1444
|
"kind": "resource",
|
|
@@ -1409,5 +1525,10 @@
|
|
|
1409
1525
|
"lexicon": "k8s",
|
|
1410
1526
|
"apiVersion": "v1",
|
|
1411
1527
|
"gvkKind": "WatchEvent"
|
|
1528
|
+
},
|
|
1529
|
+
"WorkerGroupSpec": {
|
|
1530
|
+
"resourceType": "K8s::Ray::RayCluster.workerGroupSpecs",
|
|
1531
|
+
"kind": "property",
|
|
1532
|
+
"lexicon": "k8s"
|
|
1412
1533
|
}
|
|
1413
1534
|
}
|
|
@@ -147,3 +147,42 @@ export const WORKLOAD_KINDS = new Set([
|
|
|
147
147
|
"Job",
|
|
148
148
|
"CronJob",
|
|
149
149
|
]);
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Parse a Kubernetes memory string to bytes.
|
|
153
|
+
*
|
|
154
|
+
* Handles binary suffixes (Ki, Mi, Gi, Ti) and SI suffixes (K, M, G, T).
|
|
155
|
+
* Returns NaN for unrecognised strings — callers should skip checks on NaN.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* parseMemoryBytes("4Gi") // 4294967296
|
|
159
|
+
* parseMemoryBytes("512Mi") // 536870912
|
|
160
|
+
* parseMemoryBytes("2048") // 2048
|
|
161
|
+
*/
|
|
162
|
+
export function parseMemoryBytes(s: string): number {
|
|
163
|
+
const m = /^([0-9]+(?:\.[0-9]+)?)(Ki|Mi|Gi|Ti|K|M|G|T)?$/.exec(s.trim());
|
|
164
|
+
if (!m) return NaN;
|
|
165
|
+
const n = parseFloat(m[1]);
|
|
166
|
+
const multipliers: Record<string, number> = {
|
|
167
|
+
Ki: 1024, Mi: 1024 ** 2, Gi: 1024 ** 3, Ti: 1024 ** 4,
|
|
168
|
+
K: 1000, M: 1000 ** 2, G: 1000 ** 3, T: 1000 ** 4,
|
|
169
|
+
};
|
|
170
|
+
return n * (multipliers[m[2] ?? ""] ?? 1);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Extract the Ray version from an image tag.
|
|
175
|
+
*
|
|
176
|
+
* Looks for a semver-style version prefix in the image tag:
|
|
177
|
+
* "rayproject/ray:2.40.0" → "2.40.0"
|
|
178
|
+
* "rayproject/ray:2.40.0-py310" → "2.40.0"
|
|
179
|
+
* "us-docker.pkg.dev/.../ray:2.9.3-gpu" → "2.9.3"
|
|
180
|
+
* "rayproject/ray:latest" → undefined
|
|
181
|
+
*
|
|
182
|
+
* Returns undefined when no version can be found.
|
|
183
|
+
*/
|
|
184
|
+
export function extractRayVersion(image: string): string | undefined {
|
|
185
|
+
const tag = image.includes(":") ? image.split(":").pop()! : image;
|
|
186
|
+
const m = /^([0-9]+\.[0-9]+\.[0-9]+)/.exec(tag);
|
|
187
|
+
return m ? m[1] : undefined;
|
|
188
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8401: shmSize exceeds container memory limit
|
|
3
|
+
*
|
|
4
|
+
* A RayCluster pod uses an emptyDir volume with medium: Memory for /dev/shm.
|
|
5
|
+
* Kubernetes counts that memory against the container's memory limit — if the
|
|
6
|
+
* sizeLimit exceeds the container's memory limit the pod will never schedule
|
|
7
|
+
* (Kubelet rejects it with "Invalid value … must be less than or equal to memory limit").
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
11
|
+
import { getPrimaryOutput, parseK8sManifests, parseMemoryBytes } from "./k8s-helpers";
|
|
12
|
+
|
|
13
|
+
export const wk8401: PostSynthCheck = {
|
|
14
|
+
id: "WK8401",
|
|
15
|
+
description: "shmSize must not exceed the container memory limit — pod will not schedule if it does",
|
|
16
|
+
|
|
17
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
18
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
19
|
+
|
|
20
|
+
for (const [, output] of ctx.outputs) {
|
|
21
|
+
const yaml = getPrimaryOutput(output);
|
|
22
|
+
const manifests = parseK8sManifests(yaml);
|
|
23
|
+
|
|
24
|
+
for (const manifest of manifests) {
|
|
25
|
+
if (manifest.kind !== "RayCluster") continue;
|
|
26
|
+
|
|
27
|
+
const clusterName = manifest.metadata?.name ?? "RayCluster";
|
|
28
|
+
const spec = manifest.spec as Record<string, unknown> | undefined;
|
|
29
|
+
if (!spec) continue;
|
|
30
|
+
|
|
31
|
+
const groups: Array<{ label: string; templateSpec: Record<string, unknown> }> = [];
|
|
32
|
+
|
|
33
|
+
// Head group
|
|
34
|
+
const headGroupSpec = spec.headGroupSpec as Record<string, unknown> | undefined;
|
|
35
|
+
if (headGroupSpec) {
|
|
36
|
+
const tmpl = headGroupSpec.template as Record<string, unknown> | undefined;
|
|
37
|
+
const podSpec = tmpl?.spec as Record<string, unknown> | undefined;
|
|
38
|
+
if (podSpec) groups.push({ label: "head", templateSpec: podSpec });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Worker groups
|
|
42
|
+
const workerGroupSpecs = spec.workerGroupSpecs as Array<Record<string, unknown>> | undefined;
|
|
43
|
+
if (Array.isArray(workerGroupSpecs)) {
|
|
44
|
+
for (const wg of workerGroupSpecs) {
|
|
45
|
+
const name = (wg.groupName as string | undefined) ?? "worker";
|
|
46
|
+
const tmpl = wg.template as Record<string, unknown> | undefined;
|
|
47
|
+
const podSpec = tmpl?.spec as Record<string, unknown> | undefined;
|
|
48
|
+
if (podSpec) groups.push({ label: `worker "${name}"`, templateSpec: podSpec });
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const { label, templateSpec } of groups) {
|
|
53
|
+
// Find the emptyDir Memory volume (dshm)
|
|
54
|
+
const volumes = templateSpec.volumes as Array<Record<string, unknown>> | undefined;
|
|
55
|
+
if (!Array.isArray(volumes)) continue;
|
|
56
|
+
|
|
57
|
+
for (const vol of volumes) {
|
|
58
|
+
const emptyDir = vol.emptyDir as Record<string, unknown> | undefined;
|
|
59
|
+
if (!emptyDir || emptyDir.medium !== "Memory") continue;
|
|
60
|
+
|
|
61
|
+
const sizeLimit = emptyDir.sizeLimit as string | undefined;
|
|
62
|
+
if (!sizeLimit) continue;
|
|
63
|
+
|
|
64
|
+
const shmBytes = parseMemoryBytes(sizeLimit);
|
|
65
|
+
if (isNaN(shmBytes)) continue;
|
|
66
|
+
|
|
67
|
+
// Find memory limit from the first container that mounts this volume
|
|
68
|
+
const containers = templateSpec.containers as Array<Record<string, unknown>> | undefined;
|
|
69
|
+
if (!Array.isArray(containers)) continue;
|
|
70
|
+
|
|
71
|
+
for (const container of containers) {
|
|
72
|
+
const resources = container.resources as Record<string, unknown> | undefined;
|
|
73
|
+
const limits = resources?.limits as Record<string, unknown> | undefined;
|
|
74
|
+
const memLimit = limits?.memory as string | undefined;
|
|
75
|
+
if (!memLimit) continue;
|
|
76
|
+
|
|
77
|
+
const memBytes = parseMemoryBytes(memLimit);
|
|
78
|
+
if (isNaN(memBytes)) continue;
|
|
79
|
+
|
|
80
|
+
if (shmBytes > memBytes) {
|
|
81
|
+
diagnostics.push({
|
|
82
|
+
checkId: "WK8401",
|
|
83
|
+
severity: "error",
|
|
84
|
+
message: `RayCluster "${clusterName}" ${label}: shmSize "${sizeLimit}" exceeds memory limit "${memLimit}" — pod will not schedule`,
|
|
85
|
+
entity: clusterName,
|
|
86
|
+
lexicon: "k8s",
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
break; // Only check the first container per group
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return diagnostics;
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8402: RayCluster missing spec.rayVersion
|
|
3
|
+
*
|
|
4
|
+
* KubeRay uses spec.rayVersion to select the Ray autoscaler sidecar image.
|
|
5
|
+
* Without it, KubeRay defaults to the "latest" tag — autoscaler and Ray head
|
|
6
|
+
* may run mismatched versions, leading to silent protocol failures.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
10
|
+
import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
|
|
11
|
+
|
|
12
|
+
export const wk8402: PostSynthCheck = {
|
|
13
|
+
id: "WK8402",
|
|
14
|
+
description: "RayCluster should set spec.rayVersion so KubeRay selects the correct autoscaler image",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
|
|
19
|
+
for (const [, output] of ctx.outputs) {
|
|
20
|
+
const yaml = getPrimaryOutput(output);
|
|
21
|
+
const manifests = parseK8sManifests(yaml);
|
|
22
|
+
|
|
23
|
+
for (const manifest of manifests) {
|
|
24
|
+
if (manifest.kind !== "RayCluster") continue;
|
|
25
|
+
|
|
26
|
+
const name = manifest.metadata?.name ?? "RayCluster";
|
|
27
|
+
const rayVersion = (manifest.spec as Record<string, unknown> | undefined)?.rayVersion;
|
|
28
|
+
|
|
29
|
+
if (!rayVersion) {
|
|
30
|
+
diagnostics.push({
|
|
31
|
+
checkId: "WK8402",
|
|
32
|
+
severity: "warning",
|
|
33
|
+
message: `RayCluster "${name}" is missing spec.rayVersion — KubeRay autoscaler will pull the "latest" image tag`,
|
|
34
|
+
entity: name,
|
|
35
|
+
lexicon: "k8s",
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return diagnostics;
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8403: spec.rayVersion does not match head image tag
|
|
3
|
+
*
|
|
4
|
+
* When spec.rayVersion is set but doesn't match the version in the head
|
|
5
|
+
* container image tag, the KubeRay autoscaler sidecar will run a different
|
|
6
|
+
* Ray version than the cluster. This can cause gRPC compatibility failures.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
10
|
+
import { getPrimaryOutput, parseK8sManifests, extractRayVersion } from "./k8s-helpers";
|
|
11
|
+
|
|
12
|
+
export const wk8403: PostSynthCheck = {
|
|
13
|
+
id: "WK8403",
|
|
14
|
+
description: "spec.rayVersion should match the Ray version in the head container image tag",
|
|
15
|
+
|
|
16
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
17
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
18
|
+
|
|
19
|
+
for (const [, output] of ctx.outputs) {
|
|
20
|
+
const yaml = getPrimaryOutput(output);
|
|
21
|
+
const manifests = parseK8sManifests(yaml);
|
|
22
|
+
|
|
23
|
+
for (const manifest of manifests) {
|
|
24
|
+
if (manifest.kind !== "RayCluster") continue;
|
|
25
|
+
|
|
26
|
+
const clusterName = manifest.metadata?.name ?? "RayCluster";
|
|
27
|
+
const spec = manifest.spec as Record<string, unknown> | undefined;
|
|
28
|
+
if (!spec) continue;
|
|
29
|
+
|
|
30
|
+
const rayVersion = spec.rayVersion as string | undefined;
|
|
31
|
+
if (!rayVersion) continue; // WK8402 covers the missing case
|
|
32
|
+
|
|
33
|
+
// Extract version from head container image
|
|
34
|
+
const headGroupSpec = spec.headGroupSpec as Record<string, unknown> | undefined;
|
|
35
|
+
const tmpl = headGroupSpec?.template as Record<string, unknown> | undefined;
|
|
36
|
+
const podSpec = tmpl?.spec as Record<string, unknown> | undefined;
|
|
37
|
+
const containers = podSpec?.containers as Array<Record<string, unknown>> | undefined;
|
|
38
|
+
if (!Array.isArray(containers) || containers.length === 0) continue;
|
|
39
|
+
|
|
40
|
+
const image = containers[0].image as string | undefined;
|
|
41
|
+
if (!image) continue;
|
|
42
|
+
|
|
43
|
+
const imageVersion = extractRayVersion(image);
|
|
44
|
+
if (!imageVersion) continue; // Can't determine version from tag
|
|
45
|
+
|
|
46
|
+
if (imageVersion !== rayVersion) {
|
|
47
|
+
diagnostics.push({
|
|
48
|
+
checkId: "WK8403",
|
|
49
|
+
severity: "warning",
|
|
50
|
+
message: `RayCluster "${clusterName}": spec.rayVersion "${rayVersion}" does not match head image tag "${imageVersion}" — autoscaler may run a mismatched Ray version`,
|
|
51
|
+
entity: clusterName,
|
|
52
|
+
lexicon: "k8s",
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return diagnostics;
|
|
59
|
+
},
|
|
60
|
+
};
|
package/dist/types/index.d.ts
CHANGED
|
@@ -1244,6 +1244,36 @@ export declare class PVC {
|
|
|
1244
1244
|
readonly uid: string;
|
|
1245
1245
|
}
|
|
1246
1246
|
|
|
1247
|
+
export declare class RayCluster {
|
|
1248
|
+
constructor(props: {
|
|
1249
|
+
metadata?: Record<string, unknown>;
|
|
1250
|
+
spec?: Record<string, unknown>;
|
|
1251
|
+
});
|
|
1252
|
+
readonly name: string;
|
|
1253
|
+
readonly namespace: string;
|
|
1254
|
+
readonly uid: string;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
export declare class RayJob {
|
|
1258
|
+
constructor(props: {
|
|
1259
|
+
metadata?: Record<string, unknown>;
|
|
1260
|
+
spec?: Record<string, unknown>;
|
|
1261
|
+
});
|
|
1262
|
+
readonly name: string;
|
|
1263
|
+
readonly namespace: string;
|
|
1264
|
+
readonly uid: string;
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
export declare class RayService {
|
|
1268
|
+
constructor(props: {
|
|
1269
|
+
metadata?: Record<string, unknown>;
|
|
1270
|
+
spec?: Record<string, unknown>;
|
|
1271
|
+
});
|
|
1272
|
+
readonly name: string;
|
|
1273
|
+
readonly namespace: string;
|
|
1274
|
+
readonly uid: string;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1247
1277
|
export declare class ReplicaSet {
|
|
1248
1278
|
constructor(props: {
|
|
1249
1279
|
/** If the Labels of a ReplicaSet are empty, they are defaulted to be the same as the Pod(s) that the ReplicaSet manages. Standard object's metadata. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-lexicon-k8s",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant",
|
|
@@ -43,7 +43,9 @@
|
|
|
43
43
|
"prepack": "bun run generate && bun run bundle && bun run validate"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
-
"@intentius/chant": "0.1.
|
|
46
|
+
"@intentius/chant": "0.1.4",
|
|
47
|
+
"@types/js-yaml": "^4.0.9",
|
|
48
|
+
"js-yaml": "^4.1.1",
|
|
47
49
|
"typescript": "^5.9.3"
|
|
48
50
|
},
|
|
49
51
|
"peerDependencies": {
|
package/src/codegen/generate.ts
CHANGED
|
@@ -13,6 +13,8 @@ import {
|
|
|
13
13
|
} from "@intentius/chant/codegen/generate";
|
|
14
14
|
import { fetchSchemas } from "../spec/fetch";
|
|
15
15
|
import { parseK8sSwagger, k8sShortName, type K8sParseResult } from "../spec/parse";
|
|
16
|
+
import { loadMultipleCRDs } from "../crd/loader";
|
|
17
|
+
import { CRD_SOURCES } from "../crd/crd-sources";
|
|
16
18
|
import { NamingStrategy, propertyTypeName, extractDefName } from "./naming";
|
|
17
19
|
import { generateLexiconJSON } from "./generate-lexicon";
|
|
18
20
|
import { generateTypeScriptDeclarations } from "./generate-typescript";
|
|
@@ -54,6 +56,26 @@ export async function generate(opts: K8sGenerateOptions = {}): Promise<GenerateR
|
|
|
54
56
|
|
|
55
57
|
createNaming: (results) => new NamingStrategy(results),
|
|
56
58
|
|
|
59
|
+
augmentSchemas: async (schemas, _opts, log) => {
|
|
60
|
+
// Load third-party CRDs and return as extraResults so they are
|
|
61
|
+
// included in the generated types alongside the core K8s resources.
|
|
62
|
+
const crdResults: K8sParseResult[] = [];
|
|
63
|
+
const warnings: Array<{ file: string; error: string }> = [];
|
|
64
|
+
for (const source of CRD_SOURCES) {
|
|
65
|
+
try {
|
|
66
|
+
const parsed = await loadMultipleCRDs([source]);
|
|
67
|
+
crdResults.push(...parsed);
|
|
68
|
+
log(`Loaded ${parsed.length} CRD type(s) from ${source.url ?? source.path ?? "cluster"}`);
|
|
69
|
+
} catch (err) {
|
|
70
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
71
|
+
warnings.push({ file: source.url ?? source.path ?? "cluster", error: msg });
|
|
72
|
+
log(`Warning: failed to load CRD: ${msg}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
log(`Total CRD types loaded: ${crdResults.length}`);
|
|
76
|
+
return { schemas, extraResults: crdResults, warnings };
|
|
77
|
+
},
|
|
78
|
+
|
|
57
79
|
augmentResults: (results, _opts, log) => {
|
|
58
80
|
// Add the remaining results from the single-schema parse
|
|
59
81
|
if (pendingResults.length > 0) {
|
package/src/composites/index.ts
CHANGED
|
@@ -73,3 +73,9 @@ export { AksExternalDnsAgent } from "./aks-external-dns-agent";
|
|
|
73
73
|
export type { AksExternalDnsAgentProps, AksExternalDnsAgentResult } from "./aks-external-dns-agent";
|
|
74
74
|
export { CockroachDbCluster } from "./cockroachdb-cluster";
|
|
75
75
|
export type { CockroachDbClusterProps, CockroachDbClusterResult } from "./cockroachdb-cluster";
|
|
76
|
+
export { RayCluster } from "./ray-cluster";
|
|
77
|
+
export type { RayClusterProps, RayClusterResult, RayClusterSpec, ResourceSpec, HeadGroupSpec, WorkerGroupSpec } from "./ray-cluster";
|
|
78
|
+
export { RayJob } from "./ray-job";
|
|
79
|
+
export type { RayJobProps, RayJobResult } from "./ray-job";
|
|
80
|
+
export { RayService } from "./ray-service";
|
|
81
|
+
export type { RayServiceProps, RayServiceResult } from "./ray-service";
|