@intentius/chant-lexicon-k8s 0.124.30 → 0.124.32
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/actions/apps.js +18 -0
- package/dist/actions/batch.js +6 -0
- package/dist/actions/core.js +48 -0
- package/dist/actions/index.js +13 -0
- package/dist/actions/networking.js +11 -0
- package/dist/actions/rbac.js +8 -0
- package/dist/api/classify.js +123 -0
- package/dist/api/connect.js +62 -0
- package/dist/api/fake-cluster.js +175 -0
- package/dist/api/operation-surface.d.ts.map +1 -1
- package/dist/api/operation-surface.js +109 -0
- package/dist/api/owner-chain.js +100 -0
- package/dist/api/sweep-types.js +35 -0
- package/dist/behaviour-kinds.js +82 -0
- package/dist/codegen/docs-cli.js +2 -0
- package/dist/codegen/docs.js +146 -0
- package/dist/codegen/generate-cli.js +30 -0
- package/dist/codegen/generate-lexicon.js +74 -0
- package/dist/codegen/generate-list-map-keys.js +88 -0
- package/dist/codegen/generate-operations.js +53 -0
- package/dist/codegen/generate-typescript.js +124 -0
- package/dist/codegen/generate.js +173 -0
- package/dist/codegen/naming.js +174 -0
- package/dist/codegen/package.js +36 -0
- package/dist/codegen/patches.js +88 -0
- package/dist/codegen/typecheck.js +4 -0
- package/dist/codegen/versions.js +41 -0
- package/dist/components/argo-app.js +51 -0
- package/dist/components/builders.js +22 -0
- package/dist/components/capability-plugin.js +31 -0
- package/dist/components/flux-reconcile.js +61 -0
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +18 -0
- package/dist/components/kubectl-apply.d.ts +83 -1
- package/dist/components/kubectl-apply.d.ts.map +1 -1
- package/dist/components/kubectl-apply.js +274 -0
- package/dist/components/kustomize-apply.js +25 -0
- package/dist/composites/adot-collector.js +184 -0
- package/dist/composites/agic-ingress.js +85 -0
- package/dist/composites/aks-external-dns-agent.js +146 -0
- package/dist/composites/alb-ingress.js +86 -0
- package/dist/composites/argo-app.js +199 -0
- package/dist/composites/autoscaled-service.js +174 -0
- package/dist/composites/azure-disk-storage-class.js +47 -0
- package/dist/composites/azure-file-storage-class.js +48 -0
- package/dist/composites/azure-monitor-collector.js +176 -0
- package/dist/composites/batch-job.js +139 -0
- package/dist/composites/catalog.js +4110 -0
- package/dist/composites/cockroachdb-cluster.js +371 -0
- package/dist/composites/cockroachdb-region-stack.js +369 -0
- package/dist/composites/config-connector-context.js +40 -0
- package/dist/composites/configured-app.js +135 -0
- package/dist/composites/cron-schedule.js +38 -0
- package/dist/composites/cron-workload.js +109 -0
- package/dist/composites/ebs-storage-class.js +54 -0
- package/dist/composites/efs-storage-class.js +46 -0
- package/dist/composites/external-dns-agent.js +122 -0
- package/dist/composites/filestore-storage-class.js +49 -0
- package/dist/composites/fluent-bit-agent.js +157 -0
- package/dist/composites/flux-app.js +156 -0
- package/dist/composites/gce-ingress.js +83 -0
- package/dist/composites/gce-pd-storage-class.js +49 -0
- package/dist/composites/gke-external-dns-agent.js +129 -0
- package/dist/composites/gke-fluent-bit-agent.js +165 -0
- package/dist/composites/gke-gateway.js +91 -0
- package/dist/composites/gke-otel-collector.js +94 -0
- package/dist/composites/index.js +51 -0
- package/dist/composites/inference-service.js +76 -0
- package/dist/composites/irsa-service-account.js +77 -0
- package/dist/composites/member-defaults.js +14 -0
- package/dist/composites/metrics-server.js +181 -0
- package/dist/composites/model.js +92 -0
- package/dist/composites/monitored-service.js +152 -0
- package/dist/composites/namespace-env.js +114 -0
- package/dist/composites/network-isolated-app.js +124 -0
- package/dist/composites/node-agent.js +149 -0
- package/dist/composites/operator-stack.js +268 -0
- package/dist/composites/otel-collector-agent.js +142 -0
- package/dist/composites/otel-collector-gateway.js +216 -0
- package/dist/composites/otel-collector-node.js +176 -0
- package/dist/composites/otel-collector-rbac.js +115 -0
- package/dist/composites/otel-collector-shape.js +128 -0
- package/dist/composites/otel-collector.js +115 -0
- package/dist/composites/otel-operator-collector.js +123 -0
- package/dist/composites/ray-cluster.js +378 -0
- package/dist/composites/ray-job.js +136 -0
- package/dist/composites/ray-service.js +178 -0
- package/dist/composites/secure-ingress.js +100 -0
- package/dist/composites/security-context.js +1 -0
- package/dist/composites/sidecar-app.js +105 -0
- package/dist/composites/stateful-app.js +111 -0
- package/dist/composites/vllm-serving-runtime.js +114 -0
- package/dist/composites/web-app.js +157 -0
- package/dist/composites/worker-pool.js +167 -0
- package/dist/composites/workload-identity-sa.js +81 -0
- package/dist/composites/workload-identity-service-account.js +77 -0
- package/dist/config-schema.js +72 -0
- package/dist/config.js +59 -0
- package/dist/coverage.js +27 -0
- package/dist/crd/crd-sources.js +615 -0
- package/dist/crd/loader.js +181 -0
- package/dist/crd/oci-chart.js +144 -0
- package/dist/crd/parser.js +382 -0
- package/dist/crd/project-codegen.js +273 -0
- package/dist/crd/types.js +7 -0
- package/dist/deep-observe-hooks.js +306 -0
- package/dist/deep-observe.js +234 -0
- package/dist/default-labels.js +87 -0
- package/dist/describe-resources.js +699 -0
- package/dist/describe-stack-status.js +99 -0
- package/dist/detect.js +11 -0
- package/dist/effect-receipt-row.js +205 -0
- package/dist/export-resources.js +39 -0
- package/dist/generated/index.js +1092 -0
- package/dist/generated/runtime.js +4 -0
- package/dist/group-namespace.js +96 -0
- package/dist/import/embedded.js +197 -0
- package/dist/import/generator.js +288 -0
- package/dist/import/live-export.js +85 -0
- package/dist/import/parser.js +204 -0
- package/dist/import/testdata/embedded/fixtures.js +60 -0
- package/dist/include/entity.js +78 -0
- package/dist/include/resolve.js +170 -0
- package/dist/index.js +59 -0
- package/dist/integrity.json +3 -3
- package/dist/kube/apply.js +90 -0
- package/dist/kube/connect.js +46 -0
- package/dist/kube/delete.js +138 -0
- package/dist/kube/describe.js +169 -0
- package/dist/kube/events.js +132 -0
- package/dist/kube/flags.js +91 -0
- package/dist/kube/get.js +159 -0
- package/dist/kube/group.js +123 -0
- package/dist/kube/logs.js +82 -0
- package/dist/kube/project.js +94 -0
- package/dist/kube/render.js +227 -0
- package/dist/kube/source.js +122 -0
- package/dist/kube/target.js +63 -0
- package/dist/kube/testing.js +28 -0
- package/dist/kube/top.js +99 -0
- package/dist/kube/verdict.js +72 -0
- package/dist/kube/wait.js +129 -0
- package/dist/kustomize/render.js +73 -0
- package/dist/kustomize/rendered-entity.js +67 -0
- package/dist/kustomize/root.js +68 -0
- package/dist/lint/audit-catalog.js +61 -0
- package/dist/lint/audit-lineage.js +118 -0
- package/dist/lint/post-synth/argo-helpers.js +36 -0
- package/dist/lint/post-synth/argo002.js +31 -0
- package/dist/lint/post-synth/argo003.js +53 -0
- package/dist/lint/post-synth/argo005.js +54 -0
- package/dist/lint/post-synth/crd-schema-helpers.d.ts.map +1 -1
- package/dist/lint/post-synth/crd-schema-helpers.js +214 -0
- package/dist/lint/post-synth/flux002.js +56 -0
- package/dist/lint/post-synth/flux003.js +50 -0
- package/dist/lint/post-synth/index.js +104 -0
- package/dist/lint/post-synth/k8s-helpers.js +186 -0
- package/dist/lint/post-synth/monitoring-selection-helpers.js +163 -0
- package/dist/lint/post-synth/otel-placement-helpers.js +293 -0
- package/dist/lint/post-synth/sops-helpers.js +43 -0
- package/dist/lint/post-synth/wk8005.js +44 -0
- package/dist/lint/post-synth/wk8006.js +56 -0
- package/dist/lint/post-synth/wk8041.js +60 -0
- package/dist/lint/post-synth/wk8042.js +36 -0
- package/dist/lint/post-synth/wk8101.js +52 -0
- package/dist/lint/post-synth/wk8102.js +30 -0
- package/dist/lint/post-synth/wk8103.js +33 -0
- package/dist/lint/post-synth/wk8104.js +58 -0
- package/dist/lint/post-synth/wk8105.js +33 -0
- package/dist/lint/post-synth/wk8201.js +44 -0
- package/dist/lint/post-synth/wk8202.js +34 -0
- package/dist/lint/post-synth/wk8203.js +34 -0
- package/dist/lint/post-synth/wk8204.js +71 -0
- package/dist/lint/post-synth/wk8205.js +38 -0
- package/dist/lint/post-synth/wk8207.js +33 -0
- package/dist/lint/post-synth/wk8208.js +33 -0
- package/dist/lint/post-synth/wk8209.js +33 -0
- package/dist/lint/post-synth/wk8301.js +49 -0
- package/dist/lint/post-synth/wk8302.js +34 -0
- package/dist/lint/post-synth/wk8303.js +68 -0
- package/dist/lint/post-synth/wk8304.js +58 -0
- package/dist/lint/post-synth/wk8305.js +96 -0
- package/dist/lint/post-synth/wk8306.js +38 -0
- package/dist/lint/post-synth/wk8401.js +86 -0
- package/dist/lint/post-synth/wk8402.js +31 -0
- package/dist/lint/post-synth/wk8403.js +49 -0
- package/dist/lint/post-synth/wk8404.js +36 -0
- package/dist/lint/post-synth/wk8405.js +65 -0
- package/dist/lint/post-synth/wk8406.js +50 -0
- package/dist/lint/post-synth/wk8407.js +58 -0
- package/dist/lint/post-synth/wk8501.js +36 -0
- package/dist/lint/post-synth/wk8502.js +35 -0
- package/dist/lint/post-synth/wk8503.js +164 -0
- package/dist/lint/post-synth/wk8504.js +20 -0
- package/dist/lint/post-synth/wk8505.js +31 -0
- package/dist/lint/post-synth/wk8601.js +40 -0
- package/dist/lint/post-synth/wk8602.js +120 -0
- package/dist/lint/post-synth/wk8603.js +63 -0
- package/dist/lint/post-synth/wk8604.js +31 -0
- package/dist/lint/post-synth/wk8605.js +124 -0
- package/dist/lint/post-synth/wk8606.js +22 -0
- package/dist/lint/post-synth/wk8701.js +31 -0
- package/dist/lint/post-synth/wk8702.js +37 -0
- package/dist/lint/post-synth/wk8703.js +31 -0
- package/dist/lint/post-synth/wk8704.js +135 -0
- package/dist/lint/post-synth/wk8705.js +38 -0
- package/dist/lint/rules/argo-appset-single-project.js +56 -0
- package/dist/lint/rules/argo-ast.js +94 -0
- package/dist/lint/rules/argo-automated-prune.js +59 -0
- package/dist/lint/rules/flux-source-ref-pin.js +44 -0
- package/dist/lint/rules/hardcoded-namespace.js +57 -0
- package/dist/lint/rules/latest-image-tag.js +103 -0
- package/dist/lint/rules/missing-resource-limits.js +85 -0
- package/dist/lsp/completions.d.ts.map +1 -1
- package/dist/lsp/completions.js +18 -0
- package/dist/lsp/hover.d.ts.map +1 -1
- package/dist/lsp/hover.js +57 -0
- package/dist/manifest-entity.js +71 -0
- package/dist/manifest.json +1 -1
- package/dist/micro-time.js +49 -0
- package/dist/op/activities/apply-diff.d.ts +31 -0
- package/dist/op/activities/apply-diff.d.ts.map +1 -0
- package/dist/op/activities/apply-diff.js +79 -0
- package/dist/op/activities/argo.js +98 -0
- package/dist/op/activities/ensure-secret.js +38 -0
- package/dist/op/activities/index.js +51 -0
- package/dist/op/activities/kubectl.d.ts +64 -0
- package/dist/op/activities/kubectl.d.ts.map +1 -1
- package/dist/op/activities/kubectl.js +517 -0
- package/dist/op/activities/wait-for-ready.js +229 -0
- package/dist/op/builders.js +68 -0
- package/dist/package-cli.js +21 -0
- package/dist/patch/json-patch.js +187 -0
- package/dist/patch/rendered-patch.js +94 -0
- package/dist/plugin.js +741 -0
- package/dist/project-kinds.js +56 -0
- package/dist/receipt-store.js +216 -0
- package/dist/rules/crd-schema-helpers.ts +2 -1
- package/dist/secret-labels.js +33 -0
- package/dist/secret-store.js +116 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/serializer.js +578 -0
- package/dist/sops/encrypted-secret-file.js +194 -0
- package/dist/sops/entity.js +32 -0
- package/dist/spec/fetch.js +58 -0
- package/dist/spec/parse.js +622 -0
- package/dist/subscribe-changes.js +168 -0
- package/dist/teardown.js +197 -0
- package/dist/telemetry-release.js +86 -0
- package/dist/validate-cli.js +17 -0
- package/dist/validate.js +47 -0
- package/dist/variables.js +34 -0
- package/dist/whoami.js +109 -0
- package/package.json +12 -12
- package/src/api/operation-surface.ts +2 -1
- package/src/codegen/package.ts +2 -2
- package/src/components/index.ts +3 -0
- package/src/components/kubectl-apply-plan.k3d.e2e.test.ts +127 -0
- package/src/components/kubectl-apply.test.ts +262 -0
- package/src/components/kubectl-apply.ts +341 -12
- package/src/deep-observe-hooks.ts +2 -2
- package/src/import/roundtrip-build.test.ts +46 -0
- package/src/lint/post-synth/crd-schema-helpers.ts +2 -1
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
- package/src/op/activities/apply-diff.ts +87 -0
- package/src/op/activities/kubectl-plan.test.ts +224 -0
- package/src/op/activities/kubectl.ts +204 -16
- package/src/serializer.ts +2 -1
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for the custom-resource spec checks (WK8501, WK8502) — chant #1372.
|
|
3
|
+
*
|
|
4
|
+
* A CRD's constructor takes `spec: Record<string, unknown>`, so a misspelled
|
|
5
|
+
* or wrong-typed field type-checks, serializes, and is accepted by the API
|
|
6
|
+
* server (which prunes what the structural schema does not know) — the
|
|
7
|
+
* controller then runs with a default nobody chose. The generated lexicon JSON
|
|
8
|
+
* carries each CRD's `spec` field schema (`specSchema`); these helpers walk a
|
|
9
|
+
* synthesized manifest against it and report what the schema would reject.
|
|
10
|
+
*
|
|
11
|
+
* Excluded from check auto-discovery by the "helper" filename filter.
|
|
12
|
+
*/
|
|
13
|
+
import { createRequire } from "module";
|
|
14
|
+
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
15
|
+
import { docsToManifests } from "./k8s-helpers.js";
|
|
16
|
+
import { projectSpecSchema } from "../../project-kinds.js";
|
|
17
|
+
let cachedRegistry = null;
|
|
18
|
+
/** Registry key: `<apiVersion>/<kind>`, the pair a manifest carries verbatim. */
|
|
19
|
+
function registryKey(apiVersion, kind) {
|
|
20
|
+
return `${apiVersion}/${kind}`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Spec schemas of every CRD the lexicon ships, keyed by `apiVersion/kind`.
|
|
24
|
+
* Built-in kinds carry no `specSchema` and are never in the map.
|
|
25
|
+
*/
|
|
26
|
+
export function getCrdSchemaRegistry() {
|
|
27
|
+
if (cachedRegistry)
|
|
28
|
+
return cachedRegistry;
|
|
29
|
+
cachedRegistry = new Map();
|
|
30
|
+
try {
|
|
31
|
+
// Built lazily so importing this module stays edge-safe: `createRequire`
|
|
32
|
+
// throws where import.meta.url is undefined (bundled Workers), and there
|
|
33
|
+
// the registry is simply empty.
|
|
34
|
+
const require = createRequire(moduleSourceUrl(import.meta.url));
|
|
35
|
+
const lexicon = require("../../generated/lexicon-k8s.json");
|
|
36
|
+
for (const entry of Object.values(lexicon)) {
|
|
37
|
+
if (entry.kind === "resource" && entry.apiVersion && entry.gvkKind && entry.specSchema) {
|
|
38
|
+
cachedRegistry.set(registryKey(entry.apiVersion, entry.gvkKind), entry.specSchema);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Lexicon JSON not yet generated — empty registry, checks pass.
|
|
44
|
+
}
|
|
45
|
+
return cachedRegistry;
|
|
46
|
+
}
|
|
47
|
+
/** Test seam: replace the registry (pass `null` to reload from the lexicon JSON). */
|
|
48
|
+
export function setCrdSchemaRegistry(registry) {
|
|
49
|
+
cachedRegistry = registry;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The schema for a manifest's `apiVersion`/`kind`: one the project generated
|
|
53
|
+
* from its own CRDs (`chant generate`, ../../project-kinds.ts) first, then one
|
|
54
|
+
* the lexicon ships.
|
|
55
|
+
*/
|
|
56
|
+
export function specSchemaFor(manifest) {
|
|
57
|
+
if (typeof manifest.apiVersion !== "string" || typeof manifest.kind !== "string")
|
|
58
|
+
return undefined;
|
|
59
|
+
return (projectSpecSchema(manifest.apiVersion, manifest.kind) ??
|
|
60
|
+
getCrdSchemaRegistry().get(registryKey(manifest.apiVersion, manifest.kind)));
|
|
61
|
+
}
|
|
62
|
+
/** Every manifest in the build that has a shipped spec schema, with that schema. */
|
|
63
|
+
export function customResources(ctx) {
|
|
64
|
+
const out = [];
|
|
65
|
+
for (const manifest of docsToManifests(ctx)) {
|
|
66
|
+
const schema = specSchemaFor(manifest);
|
|
67
|
+
if (schema)
|
|
68
|
+
out.push({ manifest, schema });
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Walk `value` against `schema` and report every field the schema does not
|
|
74
|
+
* list (unless the enclosing object is `open`) and every scalar whose type or
|
|
75
|
+
* enum membership the schema rejects. Untyped nodes pass anything.
|
|
76
|
+
*/
|
|
77
|
+
export function validateSpec(value, schema, path = "spec") {
|
|
78
|
+
const findings = [];
|
|
79
|
+
walk(value, schema, path, findings);
|
|
80
|
+
return findings;
|
|
81
|
+
}
|
|
82
|
+
function walk(value, schema, path, out) {
|
|
83
|
+
if (value === null || value === undefined)
|
|
84
|
+
return;
|
|
85
|
+
// int-or-string and other deliberately untyped nodes accept anything.
|
|
86
|
+
if (!schema.type)
|
|
87
|
+
return;
|
|
88
|
+
switch (schema.type) {
|
|
89
|
+
case "object": {
|
|
90
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
91
|
+
out.push(mismatch(path, "an object", value));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const obj = value;
|
|
95
|
+
for (const [name, child] of Object.entries(obj)) {
|
|
96
|
+
const childSchema = schema.fields?.[name];
|
|
97
|
+
if (childSchema) {
|
|
98
|
+
walk(child, childSchema, `${path}.${name}`, out);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (schema.open)
|
|
102
|
+
continue;
|
|
103
|
+
const known = Object.keys(schema.fields ?? {});
|
|
104
|
+
const suggestion = suggestField(name, known);
|
|
105
|
+
const hint = suggestion ? ` (did you mean "${suggestion}"?)` : "";
|
|
106
|
+
out.push({
|
|
107
|
+
kind: "unknown-field",
|
|
108
|
+
path: `${path}.${name}`,
|
|
109
|
+
message: `unknown field "${path}.${name}"${hint}`,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
case "array": {
|
|
115
|
+
if (!Array.isArray(value)) {
|
|
116
|
+
out.push(mismatch(path, "an array", value));
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (schema.items) {
|
|
120
|
+
value.forEach((item, i) => walk(item, schema.items, `${path}[${i}]`, out));
|
|
121
|
+
}
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
case "string": {
|
|
125
|
+
if (typeof value !== "string") {
|
|
126
|
+
// `x-kubernetes-int-or-string` never reaches here (untyped); a plain
|
|
127
|
+
// string field given a number is a real mismatch the server coerces
|
|
128
|
+
// or rejects depending on version, so say so.
|
|
129
|
+
out.push(mismatch(path, "a string", value));
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
checkEnum(value, schema, path, out);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
case "integer":
|
|
136
|
+
case "number": {
|
|
137
|
+
if (typeof value !== "number" || (schema.type === "integer" && !Number.isInteger(value))) {
|
|
138
|
+
out.push(mismatch(path, schema.type === "integer" ? "an integer" : "a number", value));
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
checkEnum(value, schema, path, out);
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
case "boolean": {
|
|
145
|
+
if (typeof value !== "boolean")
|
|
146
|
+
out.push(mismatch(path, "a boolean", value));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function checkEnum(value, schema, path, out) {
|
|
152
|
+
if (!schema.enum || schema.enum.length === 0)
|
|
153
|
+
return;
|
|
154
|
+
if (schema.enum.includes(String(value)))
|
|
155
|
+
return;
|
|
156
|
+
out.push({
|
|
157
|
+
kind: "type-mismatch",
|
|
158
|
+
path,
|
|
159
|
+
message: `"${path}" must be one of ${schema.enum.map((v) => `"${v}"`).join(", ")}, got ${describe(value)}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
function mismatch(path, expected, value) {
|
|
163
|
+
return {
|
|
164
|
+
kind: "type-mismatch",
|
|
165
|
+
path,
|
|
166
|
+
message: `"${path}" expects ${expected}, got ${describe(value)}`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function describe(value) {
|
|
170
|
+
if (typeof value === "string")
|
|
171
|
+
return `string "${value}"`;
|
|
172
|
+
if (typeof value === "number" || typeof value === "boolean")
|
|
173
|
+
return `${typeof value} ${String(value)}`;
|
|
174
|
+
if (Array.isArray(value))
|
|
175
|
+
return "an array";
|
|
176
|
+
if (value !== null && typeof value === "object")
|
|
177
|
+
return "an object";
|
|
178
|
+
return String(value);
|
|
179
|
+
}
|
|
180
|
+
/** Levenshtein distance between two strings. */
|
|
181
|
+
export function levenshtein(a, b) {
|
|
182
|
+
const m = a.length;
|
|
183
|
+
const n = b.length;
|
|
184
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
185
|
+
for (let i = 0; i <= m; i++)
|
|
186
|
+
dp[i][0] = i;
|
|
187
|
+
for (let j = 0; j <= n; j++)
|
|
188
|
+
dp[0][j] = j;
|
|
189
|
+
for (let i = 1; i <= m; i++) {
|
|
190
|
+
for (let j = 1; j <= n; j++) {
|
|
191
|
+
dp[i][j] = a[i - 1] === b[j - 1]
|
|
192
|
+
? dp[i - 1][j - 1]
|
|
193
|
+
: 1 + Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return dp[m][n];
|
|
197
|
+
}
|
|
198
|
+
/** Closest known field within Levenshtein distance 3 (case-insensitive), if any. */
|
|
199
|
+
export function suggestField(unknown, known) {
|
|
200
|
+
let best;
|
|
201
|
+
let bestDist = 4;
|
|
202
|
+
for (const field of known) {
|
|
203
|
+
const dist = levenshtein(unknown.toLowerCase(), field.toLowerCase());
|
|
204
|
+
if (dist < bestDist) {
|
|
205
|
+
bestDist = dist;
|
|
206
|
+
best = field;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return best;
|
|
210
|
+
}
|
|
211
|
+
/** `metadata.name` or the kind, for diagnostics. */
|
|
212
|
+
export function resourceLabel(manifest) {
|
|
213
|
+
return manifest.metadata?.name ?? manifest.kind ?? "resource";
|
|
214
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { allManifests, manifestsOfKind } from "./argo-helpers.js";
|
|
2
|
+
/** Source kinds the kustomize-controller can reconcile from. */
|
|
3
|
+
const SOURCE_KINDS = ["GitRepository", "OCIRepository", "Bucket"];
|
|
4
|
+
/** The GitRepository `flux bootstrap` creates alongside the controllers. */
|
|
5
|
+
const BOOTSTRAP_SOURCE_NAME = "flux-system";
|
|
6
|
+
export const flux002 = {
|
|
7
|
+
id: "FLUX002",
|
|
8
|
+
description: "Kustomization.spec.sourceRef must reference a declared source (or the bootstrap flux-system repo)",
|
|
9
|
+
check(ctx) {
|
|
10
|
+
const diagnostics = [];
|
|
11
|
+
const manifests = allManifests(ctx);
|
|
12
|
+
// Declared sources, keyed "Kind/name". Namespace is deliberately ignored:
|
|
13
|
+
// an unset sourceRef.namespace resolves in the Kustomization's own, and
|
|
14
|
+
// the common estate keeps everything in flux-system anyway.
|
|
15
|
+
const declaredSources = new Set();
|
|
16
|
+
for (const kind of SOURCE_KINDS) {
|
|
17
|
+
for (const source of manifestsOfKind(manifests, kind)) {
|
|
18
|
+
const name = source.metadata?.name;
|
|
19
|
+
if (typeof name === "string")
|
|
20
|
+
declaredSources.add(`${kind}/${name}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
for (const kustomization of manifestsOfKind(manifests, "Kustomization")) {
|
|
24
|
+
// Flux and kustomize.config.k8s.io share the kind name; only the Flux CR
|
|
25
|
+
// carries a sourceRef-bearing spec.
|
|
26
|
+
const apiVersion = kustomization.apiVersion;
|
|
27
|
+
if (typeof apiVersion === "string" && !apiVersion.startsWith("kustomize.toolkit.fluxcd.io/"))
|
|
28
|
+
continue;
|
|
29
|
+
const name = kustomization.metadata?.name ?? "Kustomization";
|
|
30
|
+
const sourceRef = kustomization.spec?.sourceRef;
|
|
31
|
+
if (!sourceRef || typeof sourceRef.name !== "string" || sourceRef.name === "") {
|
|
32
|
+
diagnostics.push({
|
|
33
|
+
checkId: "FLUX002",
|
|
34
|
+
severity: "error",
|
|
35
|
+
message: `Kustomization "${name}" has no spec.sourceRef.name — the kustomize-controller cannot resolve a source.`,
|
|
36
|
+
entity: name,
|
|
37
|
+
lexicon: "k8s",
|
|
38
|
+
});
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const kind = typeof sourceRef.kind === "string" && sourceRef.kind !== "" ? sourceRef.kind : "GitRepository";
|
|
42
|
+
if (kind === "GitRepository" && sourceRef.name === BOOTSTRAP_SOURCE_NAME)
|
|
43
|
+
continue;
|
|
44
|
+
if (declaredSources.has(`${kind}/${sourceRef.name}`))
|
|
45
|
+
continue;
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
checkId: "FLUX002",
|
|
48
|
+
severity: "error",
|
|
49
|
+
message: `Kustomization "${name}" references ${kind} "${sourceRef.name}", which is not declared. Declare the source (e.g. FluxGitSource) or point sourceRef at the bootstrap flux-system repo.`,
|
|
50
|
+
entity: name,
|
|
51
|
+
lexicon: "k8s",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
return diagnostics;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { allManifests, manifestsOfKind } from "./argo-helpers.js";
|
|
2
|
+
/** Flux Kustomizations only — the kustomize.config.k8s.io kind shares the name. */
|
|
3
|
+
function fluxKustomizations(manifests) {
|
|
4
|
+
return manifestsOfKind(manifests, "Kustomization").filter((m) => {
|
|
5
|
+
const apiVersion = m.apiVersion;
|
|
6
|
+
return typeof apiVersion !== "string" || apiVersion.startsWith("kustomize.toolkit.fluxcd.io/");
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
export const flux003 = {
|
|
10
|
+
id: "FLUX003",
|
|
11
|
+
description: "Kustomization.spec.dependsOn entries should name Kustomizations declared in the build",
|
|
12
|
+
check(ctx) {
|
|
13
|
+
const diagnostics = [];
|
|
14
|
+
const kustomizations = fluxKustomizations(allManifests(ctx));
|
|
15
|
+
const declared = new Set(kustomizations
|
|
16
|
+
.map((k) => k.metadata?.name)
|
|
17
|
+
.filter((n) => typeof n === "string"));
|
|
18
|
+
for (const kustomization of kustomizations) {
|
|
19
|
+
const name = kustomization.metadata?.name ?? "Kustomization";
|
|
20
|
+
const dependsOn = kustomization.spec?.dependsOn;
|
|
21
|
+
if (!Array.isArray(dependsOn))
|
|
22
|
+
continue;
|
|
23
|
+
for (const entry of dependsOn) {
|
|
24
|
+
const depName = entry?.name;
|
|
25
|
+
if (typeof depName !== "string" || depName === "")
|
|
26
|
+
continue;
|
|
27
|
+
if (depName === name) {
|
|
28
|
+
diagnostics.push({
|
|
29
|
+
checkId: "FLUX003",
|
|
30
|
+
severity: "warning",
|
|
31
|
+
message: `Kustomization "${name}" depends on itself — Flux can never satisfy the edge and the app will not reconcile.`,
|
|
32
|
+
entity: name,
|
|
33
|
+
lexicon: "k8s",
|
|
34
|
+
});
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (declared.has(depName))
|
|
38
|
+
continue;
|
|
39
|
+
diagnostics.push({
|
|
40
|
+
checkId: "FLUX003",
|
|
41
|
+
severity: "warning",
|
|
42
|
+
message: `Kustomization "${name}" depends on Kustomization "${depName}", which nothing in the build declares — reconciliation stalls until it exists. Fix the name, or ignore if it is declared in another repo.`,
|
|
43
|
+
entity: name,
|
|
44
|
+
lexicon: "k8s",
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return diagnostics;
|
|
49
|
+
},
|
|
50
|
+
};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { argo002 } from "./argo002.js";
|
|
2
|
+
import { argo003 } from "./argo003.js";
|
|
3
|
+
import { argo005 } from "./argo005.js";
|
|
4
|
+
import { flux002 } from "./flux002.js";
|
|
5
|
+
import { flux003 } from "./flux003.js";
|
|
6
|
+
import { wk8005 } from "./wk8005.js";
|
|
7
|
+
import { wk8006 } from "./wk8006.js";
|
|
8
|
+
import { wk8041 } from "./wk8041.js";
|
|
9
|
+
import { wk8042 } from "./wk8042.js";
|
|
10
|
+
import { wk8101 } from "./wk8101.js";
|
|
11
|
+
import { wk8102 } from "./wk8102.js";
|
|
12
|
+
import { wk8103 } from "./wk8103.js";
|
|
13
|
+
import { wk8104 } from "./wk8104.js";
|
|
14
|
+
import { wk8105 } from "./wk8105.js";
|
|
15
|
+
import { wk8201 } from "./wk8201.js";
|
|
16
|
+
import { wk8202 } from "./wk8202.js";
|
|
17
|
+
import { wk8203 } from "./wk8203.js";
|
|
18
|
+
import { wk8204 } from "./wk8204.js";
|
|
19
|
+
import { wk8205 } from "./wk8205.js";
|
|
20
|
+
import { wk8207 } from "./wk8207.js";
|
|
21
|
+
import { wk8208 } from "./wk8208.js";
|
|
22
|
+
import { wk8209 } from "./wk8209.js";
|
|
23
|
+
import { wk8301 } from "./wk8301.js";
|
|
24
|
+
import { wk8302 } from "./wk8302.js";
|
|
25
|
+
import { wk8303 } from "./wk8303.js";
|
|
26
|
+
import { wk8304 } from "./wk8304.js";
|
|
27
|
+
import { wk8305 } from "./wk8305.js";
|
|
28
|
+
import { wk8306 } from "./wk8306.js";
|
|
29
|
+
import { wk8401 } from "./wk8401.js";
|
|
30
|
+
import { wk8402 } from "./wk8402.js";
|
|
31
|
+
import { wk8403 } from "./wk8403.js";
|
|
32
|
+
import { wk8404 } from "./wk8404.js";
|
|
33
|
+
import { wk8405 } from "./wk8405.js";
|
|
34
|
+
import { wk8406 } from "./wk8406.js";
|
|
35
|
+
import { wk8407 } from "./wk8407.js";
|
|
36
|
+
import { wk8501 } from "./wk8501.js";
|
|
37
|
+
import { wk8502 } from "./wk8502.js";
|
|
38
|
+
import { wk8503 } from "./wk8503.js";
|
|
39
|
+
import { wk8504 } from "./wk8504.js";
|
|
40
|
+
import { wk8505 } from "./wk8505.js";
|
|
41
|
+
import { wk8601 } from "./wk8601.js";
|
|
42
|
+
import { wk8602 } from "./wk8602.js";
|
|
43
|
+
import { wk8603 } from "./wk8603.js";
|
|
44
|
+
import { wk8604 } from "./wk8604.js";
|
|
45
|
+
import { wk8605 } from "./wk8605.js";
|
|
46
|
+
import { wk8606 } from "./wk8606.js";
|
|
47
|
+
import { wk8701 } from "./wk8701.js";
|
|
48
|
+
import { wk8702 } from "./wk8702.js";
|
|
49
|
+
import { wk8703 } from "./wk8703.js";
|
|
50
|
+
import { wk8704 } from "./wk8704.js";
|
|
51
|
+
import { wk8705 } from "./wk8705.js";
|
|
52
|
+
export const postSynthChecks = [
|
|
53
|
+
argo002,
|
|
54
|
+
argo003,
|
|
55
|
+
argo005,
|
|
56
|
+
flux002,
|
|
57
|
+
flux003,
|
|
58
|
+
wk8005,
|
|
59
|
+
wk8006,
|
|
60
|
+
wk8041,
|
|
61
|
+
wk8042,
|
|
62
|
+
wk8101,
|
|
63
|
+
wk8102,
|
|
64
|
+
wk8103,
|
|
65
|
+
wk8104,
|
|
66
|
+
wk8105,
|
|
67
|
+
wk8201,
|
|
68
|
+
wk8202,
|
|
69
|
+
wk8203,
|
|
70
|
+
wk8204,
|
|
71
|
+
wk8205,
|
|
72
|
+
wk8207,
|
|
73
|
+
wk8208,
|
|
74
|
+
wk8209,
|
|
75
|
+
wk8301,
|
|
76
|
+
wk8302,
|
|
77
|
+
wk8303,
|
|
78
|
+
wk8304,
|
|
79
|
+
wk8305,
|
|
80
|
+
wk8306,
|
|
81
|
+
wk8401,
|
|
82
|
+
wk8402,
|
|
83
|
+
wk8403,
|
|
84
|
+
wk8404,
|
|
85
|
+
wk8405,
|
|
86
|
+
wk8406,
|
|
87
|
+
wk8407,
|
|
88
|
+
wk8501,
|
|
89
|
+
wk8502,
|
|
90
|
+
wk8503,
|
|
91
|
+
wk8504,
|
|
92
|
+
wk8505,
|
|
93
|
+
wk8601,
|
|
94
|
+
wk8602,
|
|
95
|
+
wk8603,
|
|
96
|
+
wk8604,
|
|
97
|
+
wk8605,
|
|
98
|
+
wk8606,
|
|
99
|
+
wk8701,
|
|
100
|
+
wk8702,
|
|
101
|
+
wk8703,
|
|
102
|
+
wk8704,
|
|
103
|
+
wk8705,
|
|
104
|
+
];
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared helpers for Kubernetes post-synthesis lint rules.
|
|
3
|
+
*
|
|
4
|
+
* Provides the `ctx.docs` → `K8sManifest[]` bridge every check reads through,
|
|
5
|
+
* and container extraction logic that handles all common workload types.
|
|
6
|
+
*/
|
|
7
|
+
import { parseOutputDocs } from "@intentius/chant/lint/post-synth";
|
|
8
|
+
export { getPrimaryOutput, getAdditionalFiles } from "@intentius/chant/lint/post-synth";
|
|
9
|
+
/**
|
|
10
|
+
* Every parsed Kubernetes manifest across all lexicon outputs, via `ctx.docs`
|
|
11
|
+
* (chant #975) — the shared, parse-once view every k8s post-synth check
|
|
12
|
+
* reads, in place of the old per-check `parseK8sManifests(getPrimaryOutput(output))`
|
|
13
|
+
* splitter (chant #976). Two documents are dropped rather than passed through
|
|
14
|
+
* as a "manifest":
|
|
15
|
+
*
|
|
16
|
+
* - `d.error` — a document `parseOutputDocs` could not make sense of.
|
|
17
|
+
* - `d.file` — a document that came from a `SerializerResult.files` sidecar
|
|
18
|
+
* (a committed-encrypted secret's ciphertext, in this lexicon) rather than
|
|
19
|
+
* a lexicon's primary output. `parseK8sManifests` only ever saw
|
|
20
|
+
* `getPrimaryOutput(output)`, so a sidecar was invisible to every general
|
|
21
|
+
* manifest-consuming check — see `getAdditionalFiles`'s doc comment on why
|
|
22
|
+
* that separation is deliberate. Only `sops-helpers.ts`'s
|
|
23
|
+
* `resolveEncryptedSecretClaims` (WK8504/WK8503's producer join) reads
|
|
24
|
+
* `.files` explicitly; this bridge must keep it that way.
|
|
25
|
+
*
|
|
26
|
+
* `ctx.docs` is populated by every context chant itself builds (`chant
|
|
27
|
+
* build`, `@intentius/chant-test-utils`'s `createPostSynthContext` /
|
|
28
|
+
* `makePostSynthCtx*`) — see `PostSynthContext.docs`'s doc comment for why
|
|
29
|
+
* it is still optional at the type level. A handful of this lexicon's own
|
|
30
|
+
* hand-rolled `PostSynthContext` test literals (`post-synth.test.ts`'s
|
|
31
|
+
* `makeCtx`) predate that field and never set it; for those, fall back to
|
|
32
|
+
* parsing `ctx.outputs` directly with the same `parseOutputDocs` primitive,
|
|
33
|
+
* rather than silently returning no manifests.
|
|
34
|
+
*/
|
|
35
|
+
export function docsToManifests(ctx) {
|
|
36
|
+
const docs = ctx.docs ?? parseOutputDocs(ctx.outputs);
|
|
37
|
+
return docs
|
|
38
|
+
.filter((d) => !d.error && !d.file)
|
|
39
|
+
.map((d) => d.value);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Extract the pod spec from any workload manifest.
|
|
43
|
+
*
|
|
44
|
+
* Handles:
|
|
45
|
+
* - Pod: spec directly
|
|
46
|
+
* - Deployment, StatefulSet, DaemonSet: spec.template.spec
|
|
47
|
+
* - Job: spec.template.spec
|
|
48
|
+
* - CronJob: spec.jobTemplate.spec.template.spec
|
|
49
|
+
*/
|
|
50
|
+
export function extractPodSpec(manifest) {
|
|
51
|
+
const kind = manifest.kind;
|
|
52
|
+
const spec = manifest.spec;
|
|
53
|
+
if (!spec)
|
|
54
|
+
return null;
|
|
55
|
+
switch (kind) {
|
|
56
|
+
case "Pod":
|
|
57
|
+
return spec;
|
|
58
|
+
case "Deployment":
|
|
59
|
+
case "StatefulSet":
|
|
60
|
+
case "DaemonSet":
|
|
61
|
+
case "Job": {
|
|
62
|
+
const template = spec.template;
|
|
63
|
+
return template?.spec ?? null;
|
|
64
|
+
}
|
|
65
|
+
case "CronJob": {
|
|
66
|
+
const jobTemplate = spec.jobTemplate;
|
|
67
|
+
const jobSpec = jobTemplate?.spec;
|
|
68
|
+
const template = jobSpec?.template;
|
|
69
|
+
return template?.spec ?? null;
|
|
70
|
+
}
|
|
71
|
+
// KServe ServingRuntime/ClusterServingRuntime carry `containers` and
|
|
72
|
+
// `tolerations` directly on spec — no template wrapper, unlike the
|
|
73
|
+
// built-in workload kinds above.
|
|
74
|
+
case "ServingRuntime":
|
|
75
|
+
case "ClusterServingRuntime":
|
|
76
|
+
return spec;
|
|
77
|
+
default:
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Extract all containers (including init containers) from a workload manifest.
|
|
83
|
+
*/
|
|
84
|
+
export function extractContainers(manifest) {
|
|
85
|
+
const podSpec = extractPodSpec(manifest);
|
|
86
|
+
if (!podSpec)
|
|
87
|
+
return [];
|
|
88
|
+
const containers = [];
|
|
89
|
+
if (Array.isArray(podSpec.containers)) {
|
|
90
|
+
for (const c of podSpec.containers) {
|
|
91
|
+
if (typeof c === "object" && c !== null) {
|
|
92
|
+
containers.push(c);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (Array.isArray(podSpec.initContainers)) {
|
|
97
|
+
for (const c of podSpec.initContainers) {
|
|
98
|
+
if (typeof c === "object" && c !== null) {
|
|
99
|
+
containers.push(c);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return containers;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Workload kinds that contain pod templates.
|
|
107
|
+
*/
|
|
108
|
+
export const WORKLOAD_KINDS = new Set([
|
|
109
|
+
"Pod",
|
|
110
|
+
"Deployment",
|
|
111
|
+
"StatefulSet",
|
|
112
|
+
"DaemonSet",
|
|
113
|
+
"Job",
|
|
114
|
+
"CronJob",
|
|
115
|
+
]);
|
|
116
|
+
/**
|
|
117
|
+
* Workload kinds that can request `nvidia.com/gpu` — the built-in workload
|
|
118
|
+
* kinds plus KServe's ServingRuntime/ClusterServingRuntime, which declare
|
|
119
|
+
* `containers`/`tolerations` directly on spec (see `extractPodSpec`).
|
|
120
|
+
*/
|
|
121
|
+
export const GPU_POD_KINDS = new Set([
|
|
122
|
+
...WORKLOAD_KINDS,
|
|
123
|
+
"ServingRuntime",
|
|
124
|
+
"ClusterServingRuntime",
|
|
125
|
+
]);
|
|
126
|
+
/** Resource name used for GPU requests/limits across the lexicon. */
|
|
127
|
+
export const NVIDIA_GPU_RESOURCE = "nvidia.com/gpu";
|
|
128
|
+
/**
|
|
129
|
+
* True when any container in `containers` requests or limits
|
|
130
|
+
* `nvidia.com/gpu`.
|
|
131
|
+
*/
|
|
132
|
+
export function requestsGpu(containers) {
|
|
133
|
+
return containers.some((c) => {
|
|
134
|
+
const requests = c.resources?.requests;
|
|
135
|
+
const limits = c.resources?.limits;
|
|
136
|
+
return Boolean(requests?.[NVIDIA_GPU_RESOURCE]) || Boolean(limits?.[NVIDIA_GPU_RESOURCE]);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* True when `tolerations` includes one that tolerates the `nvidia.com/gpu`
|
|
141
|
+
* taint — either an explicit `key: "nvidia.com/gpu"` toleration, or a
|
|
142
|
+
* wildcard `{ operator: "Exists" }` with no key (tolerates every taint).
|
|
143
|
+
*/
|
|
144
|
+
export function toleratesGpu(tolerations) {
|
|
145
|
+
if (!Array.isArray(tolerations))
|
|
146
|
+
return false;
|
|
147
|
+
return tolerations.some((t) => t.key === NVIDIA_GPU_RESOURCE || (t.operator === "Exists" && !t.key));
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Parse a Kubernetes memory string to bytes.
|
|
151
|
+
*
|
|
152
|
+
* Handles binary suffixes (Ki, Mi, Gi, Ti) and SI suffixes (K, M, G, T).
|
|
153
|
+
* Returns NaN for unrecognised strings — callers should skip checks on NaN.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* parseMemoryBytes("4Gi") // 4294967296
|
|
157
|
+
* parseMemoryBytes("512Mi") // 536870912
|
|
158
|
+
* parseMemoryBytes("2048") // 2048
|
|
159
|
+
*/
|
|
160
|
+
export function parseMemoryBytes(s) {
|
|
161
|
+
const m = /^([0-9]+(?:\.[0-9]+)?)(Ki|Mi|Gi|Ti|K|M|G|T)?$/.exec(s.trim());
|
|
162
|
+
if (!m)
|
|
163
|
+
return NaN;
|
|
164
|
+
const n = parseFloat(m[1]);
|
|
165
|
+
const multipliers = {
|
|
166
|
+
Ki: 1024, Mi: 1024 ** 2, Gi: 1024 ** 3, Ti: 1024 ** 4,
|
|
167
|
+
K: 1000, M: 1000 ** 2, G: 1000 ** 3, T: 1000 ** 4,
|
|
168
|
+
};
|
|
169
|
+
return n * (multipliers[m[2] ?? ""] ?? 1);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Extract the Ray version from an image tag.
|
|
173
|
+
*
|
|
174
|
+
* Looks for a semver-style version prefix in the image tag:
|
|
175
|
+
* "rayproject/ray:2.40.0" → "2.40.0"
|
|
176
|
+
* "rayproject/ray:2.40.0-py310" → "2.40.0"
|
|
177
|
+
* "us-docker.pkg.dev/.../ray:2.9.3-gpu" → "2.9.3"
|
|
178
|
+
* "rayproject/ray:latest" → undefined
|
|
179
|
+
*
|
|
180
|
+
* Returns undefined when no version can be found.
|
|
181
|
+
*/
|
|
182
|
+
export function extractRayVersion(image) {
|
|
183
|
+
const tag = image.includes(":") ? image.split(":").pop() : image;
|
|
184
|
+
const m = /^([0-9]+\.[0-9]+\.[0-9]+)/.exec(tag);
|
|
185
|
+
return m ? m[1] : undefined;
|
|
186
|
+
}
|