@intentius/chant-lexicon-helm 0.124.31 → 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/archive-render.js +76 -0
- package/dist/codegen/docs-cli.js +3 -0
- package/dist/codegen/docs.js +113 -0
- package/dist/codegen/generate-cli.js +19 -0
- package/dist/codegen/generate.js +210 -0
- package/dist/codegen/naming.js +29 -0
- package/dist/codegen/package.js +44 -0
- package/dist/codegen/values-type.js +87 -0
- package/dist/commands.js +413 -0
- package/dist/components/builders.js +9 -0
- package/dist/components/capability-plugin.js +17 -0
- package/dist/components/helm-upgrade.js +109 -0
- package/dist/components/index.js +11 -0
- package/dist/composites/catalog.js +1119 -0
- package/dist/composites/helm-batch-job.js +148 -0
- package/dist/composites/helm-crd-lifecycle.js +145 -0
- package/dist/composites/helm-cron-job.js +130 -0
- package/dist/composites/helm-daemon-set.js +125 -0
- package/dist/composites/helm-external-secret.js +63 -0
- package/dist/composites/helm-library.js +24 -0
- package/dist/composites/helm-microservice.js +260 -0
- package/dist/composites/helm-monitored-service.js +188 -0
- package/dist/composites/helm-namespace-env.js +116 -0
- package/dist/composites/helm-secure-ingress.js +86 -0
- package/dist/composites/helm-stateful-service.js +160 -0
- package/dist/composites/helm-web-app.js +201 -0
- package/dist/composites/helm-worker.js +164 -0
- package/dist/composites/index.js +13 -0
- package/dist/config.js +161 -0
- package/dist/coverage.js +37 -0
- package/dist/deep-observe-hooks.js +19 -0
- package/dist/deep-observe.js +55 -0
- package/dist/describe-resources.js +119 -0
- package/dist/describe-stack-status.js +66 -0
- package/dist/detect.js +11 -0
- package/dist/generated/index.js +11 -0
- package/dist/helpers.js +84 -0
- package/dist/import/generator.js +292 -0
- package/dist/import/parser.js +141 -0
- package/dist/import/template-stripper.js +86 -0
- package/dist/index.js +55 -0
- package/dist/integrity.json +2 -2
- package/dist/intrinsics.js +482 -0
- package/dist/lint/audit-catalog.js +33 -0
- package/dist/lint/audit-lineage.js +53 -0
- package/dist/lint/post-synth/helm-helpers.js +60 -0
- package/dist/lint/post-synth/index.js +48 -0
- package/dist/lint/post-synth/whm005-no-empty-wrapper.js +47 -0
- package/dist/lint/post-synth/whm101.js +38 -0
- package/dist/lint/post-synth/whm102.js +26 -0
- package/dist/lint/post-synth/whm103.js +51 -0
- package/dist/lint/post-synth/whm104.js +29 -0
- package/dist/lint/post-synth/whm105.js +23 -0
- package/dist/lint/post-synth/whm201.js +29 -0
- package/dist/lint/post-synth/whm202.js +42 -0
- package/dist/lint/post-synth/whm203.js +32 -0
- package/dist/lint/post-synth/whm204.js +51 -0
- package/dist/lint/post-synth/whm301.js +31 -0
- package/dist/lint/post-synth/whm302.js +33 -0
- package/dist/lint/post-synth/whm401.js +52 -0
- package/dist/lint/post-synth/whm402.js +38 -0
- package/dist/lint/post-synth/whm403.js +38 -0
- package/dist/lint/post-synth/whm404.js +30 -0
- package/dist/lint/post-synth/whm405.js +47 -0
- package/dist/lint/post-synth/whm406.js +27 -0
- package/dist/lint/post-synth/whm407.js +72 -0
- package/dist/lint/post-synth/whm501.js +89 -0
- package/dist/lint/post-synth/whm502.js +85 -0
- package/dist/lint/post-synth/whm503.js +82 -0
- package/dist/lint/post-synth/whm504.js +47 -0
- package/dist/lint/rules/chart-metadata.js +53 -0
- package/dist/lint/rules/no-hardcoded-image.js +50 -0
- package/dist/lint/rules/values-no-helm-tpl.js +78 -0
- package/dist/lint/rules/values-no-secrets.js +65 -0
- package/dist/list-artifacts.js +134 -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 +40 -0
- package/dist/manifest.json +1 -1
- package/dist/op/activities/cluster-probe.js +116 -0
- package/dist/op/activities/helm.js +449 -0
- package/dist/op/activities/index.js +10 -0
- package/dist/op/builders.js +45 -0
- package/dist/package-cli.js +21 -0
- package/dist/pinnability/actions.js +207 -0
- package/dist/pinnability/classify.js +416 -0
- package/dist/pinnability/conditions.js +0 -0
- package/dist/pinnability/index.js +16 -0
- package/dist/pinnability/localize.js +518 -0
- package/dist/pinnability/render-stream.js +53 -0
- package/dist/pinnability/values.js +151 -0
- package/dist/plugin.js +458 -0
- package/dist/project-codegen.js +215 -0
- package/dist/release-observe.js +287 -0
- package/dist/render-diff.js +279 -0
- package/dist/render-digest.js +0 -0
- package/dist/render-records.js +29 -0
- package/dist/render-store.js +401 -0
- package/dist/render-wrapper.js +164 -0
- package/dist/render.js +394 -0
- package/dist/resources.js +109 -0
- package/dist/serializer.js +903 -0
- package/dist/validate-cli.js +17 -0
- package/dist/validate.js +30 -0
- package/dist/values-probe.js +504 -0
- package/dist/wrapper-chart.js +95 -0
- package/package.json +11 -11
- package/src/codegen/package.ts +2 -2
- package/src/lsp/completions.ts +2 -1
- package/src/lsp/hover.ts +2 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live introspection of Helm releases via `helm list -A -o json`.
|
|
3
|
+
*
|
|
4
|
+
* The Helm lexicon's chant entities describe chart-authoring primitives
|
|
5
|
+
* (Chart.yaml, templates/, values.yaml). The runtime concept — a Helm
|
|
6
|
+
* release installed in a kubeconfig context — is created by `helm install`
|
|
7
|
+
* outside chant's entity model. This implementation reports those releases
|
|
8
|
+
* as artifacts so `state diff --live` / `WatchOp` can detect manual
|
|
9
|
+
* installs/upgrades/rollbacks that slip past CI.
|
|
10
|
+
*
|
|
11
|
+
* Helm-not-installed (binary missing) → returns `{}` cleanly so other
|
|
12
|
+
* lexicons' snapshots aren't blocked.
|
|
13
|
+
*/
|
|
14
|
+
import { exec } from "node:child_process";
|
|
15
|
+
import { promisify } from "node:util";
|
|
16
|
+
import { loadChantConfigUpward } from "@intentius/chant/config";
|
|
17
|
+
import { resolveClusterTarget } from "@intentius/chant/kubectl-context";
|
|
18
|
+
import { readReleaseLedger, latestPerComponent } from "@intentius/chant/lifecycle/release-ledger";
|
|
19
|
+
const execAsync = promisify(exec);
|
|
20
|
+
/**
|
|
21
|
+
* The render identity a chant deploy recorded for a release (#2031). `helm
|
|
22
|
+
* list` cannot know which bytes a release was installed from — but the
|
|
23
|
+
* release ledger already records it on deploy (#1243): the input digest for
|
|
24
|
+
* an unpinned deploy, contentDigest + inputDigest for a pinned one. This
|
|
25
|
+
* reads that record back, so an observed release joins `helm renders` /
|
|
26
|
+
* `helm diff` by digest instead of by chart-name prefix matching.
|
|
27
|
+
*
|
|
28
|
+
* The join key is the ledger record's `component`, which the helm deploy
|
|
29
|
+
* activity defaults to the release name (`args.component ?? args.name`,
|
|
30
|
+
* ./op/activities/helm.ts). Deliberately best-effort and honest about
|
|
31
|
+
* absence:
|
|
32
|
+
*
|
|
33
|
+
* - a release deployed outside chant has no record, and reports nothing;
|
|
34
|
+
* - a ledger that cannot be read (no repo, no lifecycle branch) joins
|
|
35
|
+
* nothing rather than failing the snapshot;
|
|
36
|
+
* - two observed releases sharing one name (different namespaces) are
|
|
37
|
+
* ambiguous — the record does not carry a namespace — so neither joins;
|
|
38
|
+
* - a record whose `component` was overridden away from the release name
|
|
39
|
+
* does not join (the honest miss, same as today).
|
|
40
|
+
*/
|
|
41
|
+
function renderIdentityOf(record) {
|
|
42
|
+
if (!record)
|
|
43
|
+
return {};
|
|
44
|
+
if (record.inputDigest) {
|
|
45
|
+
// Pinned deploy (#1242): `digest` is the render's contentDigest, the
|
|
46
|
+
// input identity rides alongside.
|
|
47
|
+
return { inputDigest: record.inputDigest, contentDigest: record.digest };
|
|
48
|
+
}
|
|
49
|
+
// Unpinned deploy: the record is keyed by the input digest itself
|
|
50
|
+
// (`ReleaseRecord.inputDigest` is "absent when digest is already input-side").
|
|
51
|
+
return { inputDigest: record.digest };
|
|
52
|
+
}
|
|
53
|
+
function pruneUndefined(obj) {
|
|
54
|
+
const out = {};
|
|
55
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
56
|
+
if (v !== undefined)
|
|
57
|
+
out[k] = v;
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
export async function listArtifacts(options) {
|
|
62
|
+
const result = {};
|
|
63
|
+
// The same cluster binding every other helm read resolves (chant#1488 —
|
|
64
|
+
// `k8s.profiles.<env>.context`, shared with the k8s lexicon). Listing on
|
|
65
|
+
// the AMBIENT context read whichever cluster the operator's shell last
|
|
66
|
+
// pointed at: verified live on kubemicrovm-ops, whose releases came back
|
|
67
|
+
// `{}` because the shell's current-context was an unrelated EKS cluster.
|
|
68
|
+
// Unresolvable → ambient, which is chant's own fallback when no profile is
|
|
69
|
+
// declared.
|
|
70
|
+
let context;
|
|
71
|
+
try {
|
|
72
|
+
const { config } = await loadChantConfigUpward(process.cwd());
|
|
73
|
+
context = (await resolveClusterTarget(config, options.environment, "helm")).context;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
context = undefined;
|
|
77
|
+
}
|
|
78
|
+
let stdout;
|
|
79
|
+
try {
|
|
80
|
+
({ stdout } = await execAsync(`helm list -A -o json${context ? ` --kube-context '${context.replace(/'/g, "'\\''")}'` : ""}`));
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
// Binary not installed, no kubeconfig, or some other error — return
|
|
84
|
+
// empty rather than blocking the whole snapshot.
|
|
85
|
+
return result;
|
|
86
|
+
}
|
|
87
|
+
let entries;
|
|
88
|
+
try {
|
|
89
|
+
entries = JSON.parse(stdout);
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
// The environment's latest release record per component (#2031) — the
|
|
95
|
+
// deploy-side identity read back. Best-effort: an unreadable ledger joins
|
|
96
|
+
// nothing rather than blocking the snapshot.
|
|
97
|
+
let recorded = new Map();
|
|
98
|
+
try {
|
|
99
|
+
const { records } = await readReleaseLedger(options.environment);
|
|
100
|
+
recorded = latestPerComponent(records);
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
recorded = new Map();
|
|
104
|
+
}
|
|
105
|
+
// A name observed in several namespaces is ambiguous against a record that
|
|
106
|
+
// carries no namespace — neither joins.
|
|
107
|
+
const nameCounts = new Map();
|
|
108
|
+
for (const entry of entries) {
|
|
109
|
+
if (entry.name)
|
|
110
|
+
nameCounts.set(entry.name, (nameCounts.get(entry.name) ?? 0) + 1);
|
|
111
|
+
}
|
|
112
|
+
for (const entry of entries) {
|
|
113
|
+
if (!entry.name || !entry.namespace)
|
|
114
|
+
continue;
|
|
115
|
+
const key = `release/${entry.namespace}/${entry.name}`;
|
|
116
|
+
const record = nameCounts.get(entry.name) === 1 ? recorded.get(entry.name) : undefined;
|
|
117
|
+
const { inputDigest, contentDigest } = renderIdentityOf(record);
|
|
118
|
+
result[key] = {
|
|
119
|
+
type: "Helm::Release",
|
|
120
|
+
physicalId: `${entry.namespace}/${entry.name}`,
|
|
121
|
+
status: entry.status ?? "unknown",
|
|
122
|
+
lastUpdated: entry.updated,
|
|
123
|
+
attributes: pruneUndefined({
|
|
124
|
+
chart: entry.chart,
|
|
125
|
+
revision: entry.revision,
|
|
126
|
+
appVersion: entry.app_version,
|
|
127
|
+
namespace: entry.namespace,
|
|
128
|
+
inputDigest,
|
|
129
|
+
contentDigest,
|
|
130
|
+
}),
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return result;
|
|
134
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/lsp/completions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"completions.d.ts","sourceRoot":"","sources":["../../src/lsp/completions.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,cAAc,EAAE,MAAM,4BAA4B,CAAC;AAapF;;GAEG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,iBAAiB,GAAG,cAAc,EAAE,CAExE"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
3
|
+
import { LexiconIndex, lexiconCompletions } from "@intentius/chant/lsp/lexicon-providers";
|
|
4
|
+
const require = createRequire(moduleSourceUrl(import.meta.url));
|
|
5
|
+
let cachedIndex = null;
|
|
6
|
+
function getIndex() {
|
|
7
|
+
if (cachedIndex)
|
|
8
|
+
return cachedIndex;
|
|
9
|
+
const data = require("../generated/lexicon-helm.json");
|
|
10
|
+
cachedIndex = new LexiconIndex(data);
|
|
11
|
+
return cachedIndex;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Provide Helm resource completions based on context.
|
|
15
|
+
*/
|
|
16
|
+
export function helmCompletions(ctx) {
|
|
17
|
+
return lexiconCompletions(ctx, getIndex(), "Helm chart resource");
|
|
18
|
+
}
|
package/dist/lsp/hover.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hover.d.ts","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"hover.d.ts","sourceRoot":"","sources":["../../src/lsp/hover.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,4BAA4B,CAAC;AAa1E;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,GAAG,SAAS,GAAG,SAAS,CAElE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createRequire } from "module";
|
|
2
|
+
import { moduleSourceUrl } from "@intentius/chant/module-source";
|
|
3
|
+
import { LexiconIndex, lexiconHover } from "@intentius/chant/lsp/lexicon-providers";
|
|
4
|
+
const require = createRequire(moduleSourceUrl(import.meta.url));
|
|
5
|
+
let cachedIndex = null;
|
|
6
|
+
function getIndex() {
|
|
7
|
+
if (cachedIndex)
|
|
8
|
+
return cachedIndex;
|
|
9
|
+
const data = require("../generated/lexicon-helm.json");
|
|
10
|
+
cachedIndex = new LexiconIndex(data);
|
|
11
|
+
return cachedIndex;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Provide hover information for Helm resource types.
|
|
15
|
+
*/
|
|
16
|
+
export function helmHover(ctx) {
|
|
17
|
+
return lexiconHover(ctx, getIndex(), resourceHover);
|
|
18
|
+
}
|
|
19
|
+
function resourceHover(className, entry) {
|
|
20
|
+
const lines = [];
|
|
21
|
+
lines.push(`**${className}**`);
|
|
22
|
+
lines.push("");
|
|
23
|
+
lines.push(`Helm type: \`${entry.resourceType}\``);
|
|
24
|
+
if (entry.kind === "resource") {
|
|
25
|
+
lines.push("");
|
|
26
|
+
lines.push("*Resource — serialized as a top-level Helm chart artifact*");
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
lines.push("");
|
|
30
|
+
lines.push("*Property — used as a nested value in Helm resources*");
|
|
31
|
+
}
|
|
32
|
+
if (entry.attrs && Object.keys(entry.attrs).length > 0) {
|
|
33
|
+
lines.push("");
|
|
34
|
+
lines.push("**Attributes:**");
|
|
35
|
+
for (const [key, value] of Object.entries(entry.attrs)) {
|
|
36
|
+
lines.push(`- \`${key}\` \u2192 \`${value}\``);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { contents: lines.join("\n") };
|
|
40
|
+
}
|
package/dist/manifest.json
CHANGED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Live-cluster capability probe for the deploy-time profile assertion
|
|
3
|
+
* (#1244, epic #1228 phase 4).
|
|
4
|
+
*
|
|
5
|
+
* A render pinned for one capability profile and deployed to a cluster with
|
|
6
|
+
* another is silently skewed: the bytes were computed against a declared
|
|
7
|
+
* `kubeVersion` and `apiVersions` set, and nothing at deploy time checked
|
|
8
|
+
* the target actually matches. This module reads the target cluster's real
|
|
9
|
+
* capabilities — `kubectl version -o json` for the server version,
|
|
10
|
+
* `kubectl api-versions` for the served API set — through the same
|
|
11
|
+
* exec-and-ambient-kubeconfig convention `helmInstall` already uses for the
|
|
12
|
+
* helm binary, and names every divergence from a declared profile.
|
|
13
|
+
*
|
|
14
|
+
* The comparison tolerance is deliberate: kubeVersion matches on
|
|
15
|
+
* major.minor. A profile declaring `1.33.6` matches a live `v1.33.4+k3s1` —
|
|
16
|
+
* the capability surface helm renders against (`.Capabilities.KubeVersion`)
|
|
17
|
+
* is a major.minor fact, and patch skew within a minor changes no API
|
|
18
|
+
* surface a chart can probe. Declared apiVersions are exact-match: each one
|
|
19
|
+
* the profile names must be served by the cluster.
|
|
20
|
+
*/
|
|
21
|
+
import { exec } from "node:child_process";
|
|
22
|
+
import { promisify } from "node:util";
|
|
23
|
+
const execAsync = promisify(exec);
|
|
24
|
+
/**
|
|
25
|
+
* Thrown when the cluster's capabilities cannot be read at all — kubectl
|
|
26
|
+
* missing, cluster unreachable, or unparseable output. A deploy that
|
|
27
|
+
* declared a profile cannot verify its guarantee through this error, so the
|
|
28
|
+
* caller must refuse to proceed rather than deploy blind.
|
|
29
|
+
*/
|
|
30
|
+
export class ClusterProbeError extends Error {
|
|
31
|
+
constructor(message) {
|
|
32
|
+
super(message);
|
|
33
|
+
this.name = "ClusterProbeError";
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** `major.minor` from a declared or live version string (`1.33`, `1.33.6`, `v1.33.6+k3s1`). Undefined when the string has no parseable major.minor. */
|
|
37
|
+
export function majorMinorOf(version) {
|
|
38
|
+
const match = /^v?(\d+)\.(\d+)/.exec(version.trim());
|
|
39
|
+
return match ? `${match[1]}.${match[2]}` : undefined;
|
|
40
|
+
}
|
|
41
|
+
function parseServerVersion(raw) {
|
|
42
|
+
let parsed;
|
|
43
|
+
try {
|
|
44
|
+
parsed = JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
throw new ClusterProbeError(`kubectl version -o json produced unparseable output: ${raw.slice(0, 200)}`);
|
|
48
|
+
}
|
|
49
|
+
const server = parsed.serverVersion;
|
|
50
|
+
if (!server) {
|
|
51
|
+
throw new ClusterProbeError("kubectl version -o json reported no serverVersion — the cluster did not answer");
|
|
52
|
+
}
|
|
53
|
+
const gitVersion = typeof server.gitVersion === "string" ? server.gitVersion : "";
|
|
54
|
+
// major/minor are the authoritative fields; minor can carry a vendor
|
|
55
|
+
// suffix (GKE reports "33+"), so digits are extracted rather than trusted.
|
|
56
|
+
const major = typeof server.major === "string" ? /\d+/.exec(server.major)?.[0] : undefined;
|
|
57
|
+
const minor = typeof server.minor === "string" ? /\d+/.exec(server.minor)?.[0] : undefined;
|
|
58
|
+
const majorMinor = major && minor ? `${major}.${minor}` : gitVersion ? majorMinorOf(gitVersion) : undefined;
|
|
59
|
+
if (!majorMinor) {
|
|
60
|
+
throw new ClusterProbeError(`kubectl version -o json reported no usable server version (gitVersion: ${gitVersion || "<absent>"})`);
|
|
61
|
+
}
|
|
62
|
+
return { kubeVersion: gitVersion || majorMinor, majorMinor };
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Read the target cluster's capabilities. Uses whatever kubeconfig/context
|
|
66
|
+
* the process environment provides — the same ambient target `helm upgrade`
|
|
67
|
+
* itself will act on, so the probe and the deploy see one cluster.
|
|
68
|
+
*/
|
|
69
|
+
export async function probeClusterCapabilities(signal) {
|
|
70
|
+
let versionRaw;
|
|
71
|
+
let apiVersionsRaw;
|
|
72
|
+
try {
|
|
73
|
+
const [versionResult, apiResult] = await Promise.all([
|
|
74
|
+
execAsync("kubectl version -o json", { signal }),
|
|
75
|
+
execAsync("kubectl api-versions", { signal }),
|
|
76
|
+
]);
|
|
77
|
+
versionRaw = versionResult.stdout;
|
|
78
|
+
apiVersionsRaw = apiResult.stdout;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
throw new ClusterProbeError(`could not read the target cluster's capabilities: ${err instanceof Error ? err.message : String(err)}`);
|
|
82
|
+
}
|
|
83
|
+
const { kubeVersion, majorMinor } = parseServerVersion(versionRaw);
|
|
84
|
+
const apiVersions = apiVersionsRaw
|
|
85
|
+
.split("\n")
|
|
86
|
+
.map((line) => line.trim())
|
|
87
|
+
.filter((line) => line.length > 0);
|
|
88
|
+
return { kubeVersion, majorMinor, apiVersions };
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Every way `live` diverges from what `profile` declares, one message per
|
|
92
|
+
* divergence, each naming the declared and the live value. Empty when the
|
|
93
|
+
* cluster matches the profile.
|
|
94
|
+
*
|
|
95
|
+
* kubeVersion compares on major.minor (see module doc). apiVersions is a
|
|
96
|
+
* subset check: everything the profile declares must be served; extra API
|
|
97
|
+
* versions on the cluster are not a divergence, since a render only ever
|
|
98
|
+
* probed the declared set.
|
|
99
|
+
*/
|
|
100
|
+
export function compareCapabilityProfile(profile, live) {
|
|
101
|
+
const divergences = [];
|
|
102
|
+
if (profile.kubeVersion) {
|
|
103
|
+
const declared = majorMinorOf(profile.kubeVersion);
|
|
104
|
+
if (declared !== live.majorMinor) {
|
|
105
|
+
divergences.push(`kubeVersion: profile declares ${profile.kubeVersion} (${declared ?? "unparseable"}), ` +
|
|
106
|
+
`cluster runs ${live.kubeVersion} (${live.majorMinor})`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const served = new Set(live.apiVersions);
|
|
110
|
+
for (const apiVersion of profile.apiVersions ?? []) {
|
|
111
|
+
if (!served.has(apiVersion)) {
|
|
112
|
+
divergences.push(`apiVersion ${apiVersion}: declared by the profile, not served by the cluster`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return divergences;
|
|
116
|
+
}
|