@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
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
*/
|
|
20
20
|
export { kubectlApply, applyManifest, readManifestDocuments } from "./kubectl.js";
|
|
21
21
|
export type { KubectlApplyArgs, ApplyManifestResult, AppliedRef, ApplyDeleteMode } from "./kubectl.js";
|
|
22
|
+
export { ensureSecret } from "./ensure-secret.js";
|
|
23
|
+
export type { EnsureSecretActivityArgs } from "./ensure-secret.js";
|
|
22
24
|
export { waitForArgoSync, defaultArgoStatusFetcher, ArgoSyncFailedError } from "./argo.js";
|
|
23
25
|
export type { WaitForArgoSyncArgs, ArgoAppStatus, ArgoStatusFetcher } from "./argo.js";
|
|
24
26
|
export { waitForReady, defaultResourceFetcher, apiResourceFetcher, ReadinessFailedError, readinessFor, isReady, firstTerminal, DEFAULT_READINESS, READINESS_OVERRIDES, } from "./wait-for-ready.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/op/activities/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/op/activities/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAC;AAC/E,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAMpG,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,YAAY,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC;AAEhE,OAAO,EAAE,eAAe,EAAE,wBAAwB,EAAE,mBAAmB,EAAE,MAAM,QAAQ,CAAC;AACxF,YAAY,EAAE,mBAAmB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAEpF,OAAO,EACL,YAAY,EACZ,sBAAsB,EACtB,kBAAkB,EAClB,oBAAoB,EACpB,YAAY,EACZ,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,aAAa,EACb,cAAc,EACd,cAAc,EACd,SAAS,GACV,MAAM,kBAAkB,CAAC;AAW1B,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,MAAM,kCAAkC,CAAC;AACrG,YAAY,EAAE,qBAAqB,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,kCAAkC,CAAC"}
|
|
@@ -116,6 +116,15 @@ export interface ApplyManifestResult {
|
|
|
116
116
|
applied: AppliedRef[];
|
|
117
117
|
/** Objects deleted because they carried chant's marker and are no longer declared. */
|
|
118
118
|
pruned: AppliedRef[];
|
|
119
|
+
/**
|
|
120
|
+
* Owned, no longer declared, and deliberately KEPT (#1830, epic #1365
|
|
121
|
+
* decision 5): Secrets carrying the `chant.intentius.io/generated-once`
|
|
122
|
+
* label never enter the prunable set — the stored bytes are the only copy
|
|
123
|
+
* of material chant never held. Reported loudly here and on the console;
|
|
124
|
+
* deletion is an explicit act (`kubectl delete`, or a future gated op),
|
|
125
|
+
* never a prune's. Present whenever the prune ran.
|
|
126
|
+
*/
|
|
127
|
+
retained?: AppliedRef[];
|
|
119
128
|
}
|
|
120
129
|
/** Read a manifest path — one file, or every YAML/JSON file in a directory. */
|
|
121
130
|
export declare function readManifestDocuments(path: string): Record<string, unknown>[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../../src/op/activities/kubectl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAYH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"kubectl.d.ts","sourceRoot":"","sources":["../../../src/op/activities/kubectl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAYH,OAAO,KAAK,EAAa,SAAS,EAAE,MAAM,6BAA6B,CAAC;AACxE,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAK3E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,OAAO,GAAG,YAAY,GAAG,OAAO,CAAC;AAE/D,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,SAAS,EAAE,CAAC;IACxB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB;;;;OAIG;IACH,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B;;;;;;;OAOG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,sFAAsF;IACtF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,oCAAoC;AACpC,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,sCAAsC;AACtC,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,sFAAsF;IACtF,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,UAAU,EAAE,CAAC;CACzB;AAED,+EAA+E;AAC/E,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAiB7E;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,mBAAmB,CAAC,IAAI,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAEjF;AAqHD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,mBAAmB,CAAC,CAkE9B;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,gBAAgB,EACtB,MAAM,CAAC,EAAE,WAAW,EACpB,OAAO,GAAE,YAAkC,GAC1C,OAAO,CAAC,IAAI,CAAC,CAEf"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wait-for-ready.d.ts","sourceRoot":"","sources":["../../../src/op/activities/wait-for-ready.ts"],"names":[],"mappings":"AACA,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAE3E;;;;;;;;;;;;;;;;;GAiBG;AAIH,sEAAsE;AACtE,MAAM,WAAW,cAAc;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,2DAA2D;AAC3D,MAAM,WAAW,SAAS;IACxB,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACnC,4CAA4C;IAC5C,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,CAAC;CAEhC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,oBAAoB;IACnC,aAAa,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,2DAA2D;IAC3D,WAAW,EAAE,MAAM,EAAE,CAAC;CACvB;AAED,MAAM,MAAM,cAAc,GAAG,cAAc,GAAG,SAAS,GAAG,oBAAoB,CAAC;AAE/E,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AAED;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAG/B,CAAC;
|
|
1
|
+
{"version":3,"file":"wait-for-ready.d.ts","sourceRoot":"","sources":["../../../src/op/activities/wait-for-ready.ts"],"names":[],"mappings":"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"}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAqC,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAqC,MAAM,0BAA0B,CAAC;AA6BjG,eAAO,MAAM,SAAS,EAAE,aAmsBvB,CAAC"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { parseYAML } from "@intentius/chant/yaml";
|
|
9
|
-
export { getPrimaryOutput } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
export { getPrimaryOutput, getAdditionalFiles } from "@intentius/chant/lint/post-synth";
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* A parsed Kubernetes manifest (loosely typed).
|
|
@@ -103,6 +103,13 @@ export function extractPodSpec(
|
|
|
103
103
|
return (template?.spec as Record<string, unknown>) ?? null;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// KServe ServingRuntime/ClusterServingRuntime carry `containers` and
|
|
107
|
+
// `tolerations` directly on spec — no template wrapper, unlike the
|
|
108
|
+
// built-in workload kinds above.
|
|
109
|
+
case "ServingRuntime":
|
|
110
|
+
case "ClusterServingRuntime":
|
|
111
|
+
return spec;
|
|
112
|
+
|
|
106
113
|
default:
|
|
107
114
|
return null;
|
|
108
115
|
}
|
|
@@ -148,6 +155,53 @@ export const WORKLOAD_KINDS = new Set([
|
|
|
148
155
|
"CronJob",
|
|
149
156
|
]);
|
|
150
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Workload kinds that can request `nvidia.com/gpu` — the built-in workload
|
|
160
|
+
* kinds plus KServe's ServingRuntime/ClusterServingRuntime, which declare
|
|
161
|
+
* `containers`/`tolerations` directly on spec (see `extractPodSpec`).
|
|
162
|
+
*/
|
|
163
|
+
export const GPU_POD_KINDS = new Set([
|
|
164
|
+
...WORKLOAD_KINDS,
|
|
165
|
+
"ServingRuntime",
|
|
166
|
+
"ClusterServingRuntime",
|
|
167
|
+
]);
|
|
168
|
+
|
|
169
|
+
/** A Kubernetes toleration (loosely typed). */
|
|
170
|
+
export interface K8sToleration {
|
|
171
|
+
key?: string;
|
|
172
|
+
operator?: "Exists" | "Equal";
|
|
173
|
+
value?: string;
|
|
174
|
+
effect?: string;
|
|
175
|
+
[key: string]: unknown;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Resource name used for GPU requests/limits across the lexicon. */
|
|
179
|
+
export const NVIDIA_GPU_RESOURCE = "nvidia.com/gpu";
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* True when any container in `containers` requests or limits
|
|
183
|
+
* `nvidia.com/gpu`.
|
|
184
|
+
*/
|
|
185
|
+
export function requestsGpu(containers: K8sContainer[]): boolean {
|
|
186
|
+
return containers.some((c) => {
|
|
187
|
+
const requests = c.resources?.requests as Record<string, unknown> | undefined;
|
|
188
|
+
const limits = c.resources?.limits as Record<string, unknown> | undefined;
|
|
189
|
+
return Boolean(requests?.[NVIDIA_GPU_RESOURCE]) || Boolean(limits?.[NVIDIA_GPU_RESOURCE]);
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* True when `tolerations` includes one that tolerates the `nvidia.com/gpu`
|
|
195
|
+
* taint — either an explicit `key: "nvidia.com/gpu"` toleration, or a
|
|
196
|
+
* wildcard `{ operator: "Exists" }` with no key (tolerates every taint).
|
|
197
|
+
*/
|
|
198
|
+
export function toleratesGpu(tolerations: unknown): boolean {
|
|
199
|
+
if (!Array.isArray(tolerations)) return false;
|
|
200
|
+
return (tolerations as K8sToleration[]).some(
|
|
201
|
+
(t) => t.key === NVIDIA_GPU_RESOURCE || (t.operator === "Exists" && !t.key),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
151
205
|
/**
|
|
152
206
|
* Parse a Kubernetes memory string to bytes.
|
|
153
207
|
*
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helper for the committed-encrypted secret checks: join each
|
|
3
|
+
* `declareSecret({ provenance: "committed-encrypted" })` in the entity map to
|
|
4
|
+
* the sidecar file the build emitted for it.
|
|
5
|
+
*
|
|
6
|
+
* The join has to go through `output.files`, not `getPrimaryOutput`. The
|
|
7
|
+
* ciphertext is deliberately kept OUT of the primary output — that is what
|
|
8
|
+
* makes applying an undecrypted Secret structurally impossible — so a check
|
|
9
|
+
* that reads only the primary output sees nothing at all here.
|
|
10
|
+
*
|
|
11
|
+
* Excluded from check auto-discovery by the "helper" filename filter.
|
|
12
|
+
*/
|
|
13
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
14
|
+
import type { CommittedEncryptedSecretDeclaration } from "@intentius/chant/secret-provenance";
|
|
15
|
+
import { getAdditionalFiles } from "./k8s-helpers";
|
|
16
|
+
import { committedEncryptedDeclarations, sidecarFilename, validateEncryptedSecretDocument, type EncryptedSecretDocument } from "../../sops/encrypted-secret-file";
|
|
17
|
+
|
|
18
|
+
/** One committed-encrypted declaration, joined to what the build emitted for it. */
|
|
19
|
+
export interface ResolvedEncryptedSecret {
|
|
20
|
+
/** The entity-map key the declaration was discovered under. */
|
|
21
|
+
entityName: string;
|
|
22
|
+
declaration: CommittedEncryptedSecretDeclaration;
|
|
23
|
+
/** The sidecar filename the declaration's path maps to. */
|
|
24
|
+
filename: string;
|
|
25
|
+
/** Undefined when the build emitted no such sidecar — the file did not resolve. */
|
|
26
|
+
text?: string;
|
|
27
|
+
/** Everything wrong with the emitted document. Empty when it is clean. */
|
|
28
|
+
problems: string[];
|
|
29
|
+
/** Parsed structure, when the document parsed at all. */
|
|
30
|
+
document?: EncryptedSecretDocument;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Every sidecar file across every lexicon output, keyed by filename. */
|
|
34
|
+
export function allAdditionalFiles(ctx: PostSynthContext): Map<string, string> {
|
|
35
|
+
const files = new Map<string, string>();
|
|
36
|
+
for (const [, output] of ctx.outputs) {
|
|
37
|
+
for (const [filename, content] of Object.entries(getAdditionalFiles(output))) {
|
|
38
|
+
files.set(filename, content);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return files;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Join every committed-encrypted declaration to its emitted sidecar,
|
|
46
|
+
* validating the document as it goes. A declaration with no sidecar comes
|
|
47
|
+
* back with `text` undefined and one problem naming the missing file.
|
|
48
|
+
*/
|
|
49
|
+
export function resolveEncryptedSecretClaims(ctx: PostSynthContext): ResolvedEncryptedSecret[] {
|
|
50
|
+
const declarations = committedEncryptedDeclarations(ctx.entities);
|
|
51
|
+
if (declarations.size === 0) return [];
|
|
52
|
+
|
|
53
|
+
const files = allAdditionalFiles(ctx);
|
|
54
|
+
const resolved: ResolvedEncryptedSecret[] = [];
|
|
55
|
+
|
|
56
|
+
for (const [entityName, declaration] of declarations) {
|
|
57
|
+
const filename = sidecarFilename(declaration.file);
|
|
58
|
+
const text = files.get(filename);
|
|
59
|
+
if (text === undefined) {
|
|
60
|
+
resolved.push({
|
|
61
|
+
entityName,
|
|
62
|
+
declaration,
|
|
63
|
+
filename,
|
|
64
|
+
problems: [
|
|
65
|
+
`the build emitted no file for "${declaration.file}" — ` +
|
|
66
|
+
`the declared ciphertext did not resolve`,
|
|
67
|
+
],
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const { problems, document } = validateEncryptedSecretDocument(text, declaration);
|
|
72
|
+
resolved.push({ entityName, declaration, filename, text, problems, document });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return resolved;
|
|
76
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8404: GPU request without a matching toleration
|
|
3
|
+
*
|
|
4
|
+
* A pod that requests `nvidia.com/gpu` but carries no toleration for the
|
|
5
|
+
* `nvidia.com/gpu` taint won't schedule onto a tainted GPU node pool (see
|
|
6
|
+
* `GpuNodePool`, #987, which taints its nodes `nvidia.com/gpu=present` by
|
|
7
|
+
* default). The pod sits `Pending` with `0/N nodes are available` until a
|
|
8
|
+
* toleration is added.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
12
|
+
import {
|
|
13
|
+
getPrimaryOutput,
|
|
14
|
+
parseK8sManifests,
|
|
15
|
+
extractContainers,
|
|
16
|
+
extractPodSpec,
|
|
17
|
+
requestsGpu,
|
|
18
|
+
toleratesGpu,
|
|
19
|
+
GPU_POD_KINDS,
|
|
20
|
+
} from "./k8s-helpers";
|
|
21
|
+
|
|
22
|
+
export const wk8404: PostSynthCheck = {
|
|
23
|
+
id: "WK8404",
|
|
24
|
+
description: "GPU request without a matching nvidia.com/gpu toleration",
|
|
25
|
+
|
|
26
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
27
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
28
|
+
|
|
29
|
+
for (const [, output] of ctx.outputs) {
|
|
30
|
+
const yaml = getPrimaryOutput(output);
|
|
31
|
+
const manifests = parseK8sManifests(yaml);
|
|
32
|
+
|
|
33
|
+
for (const manifest of manifests) {
|
|
34
|
+
if (!manifest.kind || !GPU_POD_KINDS.has(manifest.kind)) continue;
|
|
35
|
+
|
|
36
|
+
const containers = extractContainers(manifest);
|
|
37
|
+
if (!requestsGpu(containers)) continue;
|
|
38
|
+
|
|
39
|
+
const podSpec = extractPodSpec(manifest);
|
|
40
|
+
if (toleratesGpu(podSpec?.tolerations)) continue;
|
|
41
|
+
|
|
42
|
+
const resourceName = manifest.metadata?.name ?? manifest.kind;
|
|
43
|
+
diagnostics.push({
|
|
44
|
+
checkId: "WK8404",
|
|
45
|
+
severity: "error",
|
|
46
|
+
message: `${manifest.kind} "${resourceName}" requests nvidia.com/gpu but has no toleration for the nvidia.com/gpu taint — it will not schedule onto a tainted GPU node pool`,
|
|
47
|
+
entity: resourceName,
|
|
48
|
+
lexicon: "k8s",
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return diagnostics;
|
|
54
|
+
},
|
|
55
|
+
};
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8405: Serving workload without a PodDisruptionBudget
|
|
3
|
+
*
|
|
4
|
+
* An InferenceService (or a Deployment carrying a serving
|
|
5
|
+
* `app.kubernetes.io/component` label — e.g. `inference-service` or
|
|
6
|
+
* `vllm-serving-runtime`) with no PodDisruptionBudget selecting it can lose
|
|
7
|
+
* every replica at once during a voluntary disruption (node drain, cluster
|
|
8
|
+
* upgrade), taking the model endpoint down. Serving workloads should ship
|
|
9
|
+
* with a PDB.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
13
|
+
import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
|
|
14
|
+
import type { K8sManifest } from "./k8s-helpers";
|
|
15
|
+
|
|
16
|
+
/** `app.kubernetes.io/component` values this lexicon's composites use for serving workloads. */
|
|
17
|
+
const SERVING_COMPONENTS = new Set(["inference-service", "vllm-serving-runtime", "model-serving"]);
|
|
18
|
+
|
|
19
|
+
function isServingWorkload(manifest: K8sManifest): boolean {
|
|
20
|
+
if (manifest.kind === "InferenceService") return true;
|
|
21
|
+
if (manifest.kind !== "Deployment" && manifest.kind !== "StatefulSet") return false;
|
|
22
|
+
const component = manifest.metadata?.labels?.["app.kubernetes.io/component"];
|
|
23
|
+
return component !== undefined && SERVING_COMPONENTS.has(component);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const wk8405: PostSynthCheck = {
|
|
27
|
+
id: "WK8405",
|
|
28
|
+
description: "Serving workload (InferenceService / serving Deployment) has no PodDisruptionBudget",
|
|
29
|
+
|
|
30
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
31
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
32
|
+
|
|
33
|
+
for (const [, output] of ctx.outputs) {
|
|
34
|
+
const yaml = getPrimaryOutput(output);
|
|
35
|
+
const manifests = parseK8sManifests(yaml);
|
|
36
|
+
|
|
37
|
+
const pdbSelectors = collectPdbSelectors(manifests);
|
|
38
|
+
|
|
39
|
+
for (const manifest of manifests) {
|
|
40
|
+
if (!isServingWorkload(manifest)) continue;
|
|
41
|
+
|
|
42
|
+
const labels = manifest.metadata?.labels;
|
|
43
|
+
const resourceName = manifest.metadata?.name ?? manifest.kind ?? "workload";
|
|
44
|
+
|
|
45
|
+
if (!labels || !hasCoveringPdb(labels, pdbSelectors)) {
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
checkId: "WK8405",
|
|
48
|
+
severity: "info",
|
|
49
|
+
message: `${manifest.kind} "${resourceName}" is a serving workload with no PodDisruptionBudget — add a PDB so a node drain or cluster upgrade cannot take down every replica at once`,
|
|
50
|
+
entity: resourceName,
|
|
51
|
+
lexicon: "k8s",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return diagnostics;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
function collectPdbSelectors(manifests: K8sManifest[]): Array<Record<string, string>> {
|
|
62
|
+
const selectors: Array<Record<string, string>> = [];
|
|
63
|
+
|
|
64
|
+
for (const manifest of manifests) {
|
|
65
|
+
if (manifest.kind !== "PodDisruptionBudget") continue;
|
|
66
|
+
|
|
67
|
+
const spec = manifest.spec;
|
|
68
|
+
if (!spec) continue;
|
|
69
|
+
|
|
70
|
+
const selector = spec.selector as Record<string, unknown> | undefined;
|
|
71
|
+
const matchLabels = selector?.matchLabels as Record<string, string> | undefined;
|
|
72
|
+
|
|
73
|
+
if (matchLabels && typeof matchLabels === "object") {
|
|
74
|
+
selectors.push(matchLabels);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return selectors;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function hasCoveringPdb(
|
|
82
|
+
workloadLabels: Record<string, string>,
|
|
83
|
+
pdbSelectors: Array<Record<string, string>>,
|
|
84
|
+
): boolean {
|
|
85
|
+
return pdbSelectors.some((pdbLabels) =>
|
|
86
|
+
Object.entries(pdbLabels).every(([key, value]) => workloadLabels[key] === value),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8406: No resource limits on a GPU pod
|
|
3
|
+
*
|
|
4
|
+
* A container that requests `nvidia.com/gpu` but sets no cpu/memory limits
|
|
5
|
+
* can starve other pods on the same (expensive, scarce) GPU node of CPU and
|
|
6
|
+
* memory headroom. Unlike WK8201's general resource-limits guidance, this
|
|
7
|
+
* check is scoped to GPU-requesting containers specifically, since GPU node
|
|
8
|
+
* capacity is costlier and more contended than a general-purpose pool.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
12
|
+
import { getPrimaryOutput, parseK8sManifests, extractContainers, GPU_POD_KINDS, NVIDIA_GPU_RESOURCE } from "./k8s-helpers";
|
|
13
|
+
|
|
14
|
+
export const wk8406: PostSynthCheck = {
|
|
15
|
+
id: "WK8406",
|
|
16
|
+
description: "GPU-requesting container has no cpu/memory resource limits",
|
|
17
|
+
|
|
18
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
19
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
20
|
+
|
|
21
|
+
for (const [, output] of ctx.outputs) {
|
|
22
|
+
const yaml = getPrimaryOutput(output);
|
|
23
|
+
const manifests = parseK8sManifests(yaml);
|
|
24
|
+
|
|
25
|
+
for (const manifest of manifests) {
|
|
26
|
+
if (!manifest.kind || !GPU_POD_KINDS.has(manifest.kind)) continue;
|
|
27
|
+
|
|
28
|
+
const containers = extractContainers(manifest);
|
|
29
|
+
const resourceName = manifest.metadata?.name ?? manifest.kind;
|
|
30
|
+
|
|
31
|
+
for (const container of containers) {
|
|
32
|
+
const requests = container.resources?.requests as Record<string, unknown> | undefined;
|
|
33
|
+
const limits = container.resources?.limits as Record<string, unknown> | undefined;
|
|
34
|
+
const requestsGpu = Boolean(requests?.[NVIDIA_GPU_RESOURCE]) || Boolean(limits?.[NVIDIA_GPU_RESOURCE]);
|
|
35
|
+
if (!requestsGpu) continue;
|
|
36
|
+
|
|
37
|
+
const missing: string[] = [];
|
|
38
|
+
if (!limits || typeof limits !== "object") {
|
|
39
|
+
missing.push("cpu", "memory");
|
|
40
|
+
} else {
|
|
41
|
+
if (!limits.cpu) missing.push("cpu");
|
|
42
|
+
if (!limits.memory) missing.push("memory");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (missing.length > 0) {
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
checkId: "WK8406",
|
|
48
|
+
severity: "warning",
|
|
49
|
+
message: `GPU container "${container.name ?? "(unnamed)"}" in ${manifest.kind} "${resourceName}" is missing resource limits for ${missing.join(", ")} — a GPU pod without cpu/memory limits can starve its (scarce, expensive) node`,
|
|
50
|
+
entity: resourceName,
|
|
51
|
+
lexicon: "k8s",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return diagnostics;
|
|
59
|
+
},
|
|
60
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WK8407: Unpinned model version
|
|
3
|
+
*
|
|
4
|
+
* KServe's InferenceService predictor pins a model by `storageUri`. The
|
|
5
|
+
* `Model` composite (#986) always appends `version` as the trailing path
|
|
6
|
+
* segment (`scheme://id/version`), so a resolved `Model` reference is
|
|
7
|
+
* always pinned — but `InferenceService.model` also accepts a raw
|
|
8
|
+
* `storageUri` string (#985), which can bypass that guarantee (e.g.
|
|
9
|
+
* `"gs://bucket/model"` with no version segment, or a floating tag like
|
|
10
|
+
* `"latest"`/`"main"`). An unpinned reference means a redeploy of the same
|
|
11
|
+
* InferenceService can silently pick up different weights.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type { PostSynthCheck, PostSynthContext, PostSynthDiagnostic } from "@intentius/chant/lint/post-synth";
|
|
15
|
+
import { getPrimaryOutput, parseK8sManifests } from "./k8s-helpers";
|
|
16
|
+
|
|
17
|
+
/** Trailing path segments that mean "no explicit version" even though present. */
|
|
18
|
+
const FLOATING_TAGS = new Set(["latest", "main", "master", "head"]);
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* True when `storageUri` has an explicit, non-floating version segment:
|
|
22
|
+
* `scheme://path/version` with at least two path segments after the
|
|
23
|
+
* scheme, and the last segment isn't a known floating tag.
|
|
24
|
+
*/
|
|
25
|
+
export function isPinnedStorageUri(storageUri: string): boolean {
|
|
26
|
+
const m = /^[a-z][a-z0-9+.-]*:\/\/(.+)$/i.exec(storageUri.trim());
|
|
27
|
+
if (!m) return false;
|
|
28
|
+
|
|
29
|
+
const segments = m[1].split("/").filter((s) => s.length > 0);
|
|
30
|
+
if (segments.length < 2) return false;
|
|
31
|
+
|
|
32
|
+
const last = segments[segments.length - 1].toLowerCase();
|
|
33
|
+
return !FLOATING_TAGS.has(last);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const wk8407: PostSynthCheck = {
|
|
37
|
+
id: "WK8407",
|
|
38
|
+
description: "InferenceService model storageUri has no explicit version segment",
|
|
39
|
+
|
|
40
|
+
check(ctx: PostSynthContext): PostSynthDiagnostic[] {
|
|
41
|
+
const diagnostics: PostSynthDiagnostic[] = [];
|
|
42
|
+
|
|
43
|
+
for (const [, output] of ctx.outputs) {
|
|
44
|
+
const yaml = getPrimaryOutput(output);
|
|
45
|
+
const manifests = parseK8sManifests(yaml);
|
|
46
|
+
|
|
47
|
+
for (const manifest of manifests) {
|
|
48
|
+
if (manifest.kind !== "InferenceService") continue;
|
|
49
|
+
|
|
50
|
+
const spec = manifest.spec as Record<string, unknown> | undefined;
|
|
51
|
+
const predictor = spec?.predictor as Record<string, unknown> | undefined;
|
|
52
|
+
const model = predictor?.model as Record<string, unknown> | undefined;
|
|
53
|
+
const storageUri = model?.storageUri as string | undefined;
|
|
54
|
+
if (!storageUri) continue;
|
|
55
|
+
|
|
56
|
+
if (!isPinnedStorageUri(storageUri)) {
|
|
57
|
+
const resourceName = manifest.metadata?.name ?? "InferenceService";
|
|
58
|
+
diagnostics.push({
|
|
59
|
+
checkId: "WK8407",
|
|
60
|
+
severity: "warning",
|
|
61
|
+
message: `InferenceService "${resourceName}": model storageUri "${storageUri}" has no explicit version segment — pin it (e.g. via the Model composite's required \`version\`) so a redeploy can't silently pick up different weights`,
|
|
62
|
+
entity: resourceName,
|
|
63
|
+
lexicon: "k8s",
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return diagnostics;
|
|
70
|
+
},
|
|
71
|
+
};
|