@intentius/chant-lexicon-k8s 0.41.1 → 0.41.3

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.
@@ -1,23 +1,20 @@
1
1
  /**
2
2
  * Getting from a chant environment to a live API client — chant #1074.
3
3
  *
4
- * The cluster binding (chant #1100, landed in #1155) is unchanged by the move
5
- * off `kubectl`, and deliberately so: `resolveClusterTarget` in core stays the
6
- * single resolver, shared with the GCP lexicon's Config Connector observation,
7
- * and the refusal it throws on a bound-but-mismatched context still aborts
8
- * before any resource is touched. What changes is only *how the ambient
9
- * context is read*: the typed client parses the kubeconfig it is about to use
10
- * rather than shelling `kubectl config current-context`, so a worker image with
11
- * no `kubectl` in it can still check the binding. Same question, same three
4
+ * The cluster binding (chant #1100/#1155, revised by #1488):
5
+ * `resolveClusterTarget` in core stays the single resolver, and a declared
6
+ * binding is now *used*, never policed against the ambient context. Two
12
7
  * outcomes:
13
8
  *
14
- * - **Bound and ambient agrees** — the bound context is passed explicitly to
15
- * the client, never left to the ambient default.
16
- * - **Bound and ambient disagrees** `ClusterBindingMismatchError`, thrown
17
- * before the first request. Core turns the throw into NOT-OBSERVED for every
18
- * declared entity (chant #1089), which is what stops a wrong-cluster read
19
- * becoming a confident list of creates.
20
- * - **Unbound** the kubeconfig's own current-context, with the same visible
9
+ * - **Bound** — the bound context is passed explicitly to the client,
10
+ * regardless of what `kubectl` is ambiently pointed at. A binding that
11
+ * names a context the kubeconfig does not have fails in the client with an
12
+ * error naming both the context and the `k8s.profiles.<env>.context`
13
+ * binding never by falling back to ambient. (#1488: the old
14
+ * ambient-mismatch refusal meant any other project's k3d cluster stealing
15
+ * the current-context turned a healthy estate grey with `read-failed` as
16
+ * the only explanation.)
17
+ * - **Unbound** — the kubeconfig's own current-context, with a visible
21
18
  * warning naming the environment and the missing binding.
22
19
  */
23
20
  import { type ResolvedClusterTarget } from "@intentius/chant/kubectl-context";
@@ -1 +1 @@
1
- {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/api/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,OAAO,EAAwB,KAAK,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACpG,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/E;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAEjF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,YAwCjC,CAAC"}
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../../src/api/connect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAGH,OAAO,EAAwB,KAAK,qBAAqB,EAAE,MAAM,kCAAkC,CAAC;AACpG,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AAG/E;;;;GAIG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,eAAe,CAAC,CAAC;AAEjF,MAAM,WAAW,cAAc;IAC7B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iFAAiF;IACjF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kEAAkE;IAClE,MAAM,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;CACpC;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,qBAAqB,CAAC;CAC/B;AAED;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,YAiCjC,CAAC"}
@@ -1,4 +1,4 @@
1
- import type { CapabilityPlugin } from "@intentius/chant/components/capability-plugin";
1
+ import { type CapabilityPlugin } from "@intentius/chant/components/capability-plugin";
2
2
  export declare const K8S_VERB_FAMILIES: {
3
3
  readonly apply: readonly ["kubectl-apply"];
4
4
  };
@@ -1 +1 @@
1
- {"version":3,"file":"capability-plugin.d.ts","sourceRoot":"","sources":["../../src/components/capability-plugin.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAGtF,eAAO,MAAM,iBAAiB;;CAEpB,CAAC;AAEX,eAAO,MAAM,mBAAmB,EAAE,gBASjC,CAAC"}
1
+ {"version":3,"file":"capability-plugin.d.ts","sourceRoot":"","sources":["../../src/components/capability-plugin.ts"],"names":[],"mappings":"AAWA,OAAO,EAAqB,KAAK,gBAAgB,EAAE,MAAM,+CAA+C,CAAC;AAGzG,eAAO,MAAM,iBAAiB;;CAEpB,CAAC;AAEX,eAAO,MAAM,mBAAmB,EAAE,gBAWjC,CAAC"}
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "algorithm": "sha256",
3
3
  "artifacts": {
4
- "manifest.json": "df06efa4031426355eb12f159a646012593f5bc9d4b4df424b453dde81aab0be",
4
+ "manifest.json": "39d19df8745aba927066c7eb32a493c8e8dfb2d65ff250646d8d0631f3bfdb9b",
5
5
  "meta.json": "c0b141b882c51483c1d1aead748a0eb171aceca7432edd2a0404b062bf6d7831",
6
6
  "types/index.d.ts": "3be2af7494aca94adfa16269cb9c4e7a155529872207a3d70b0c4d39914f9eec",
7
7
  "rules/argo-appset-single-project.ts": "afa9f310753aa2d475f35012b13135b2dfaebed2a35d44edbe185ebc07673674",
@@ -50,5 +50,5 @@
50
50
  "skills/chant-k8s-aks.md": "e18f0e2b055f72cd7a37deaf258d7027c2d4d3e286e8fd4975b27a1f981a3ad9",
51
51
  "skills/chant-k8s-argo.md": "b1a0b826559d8c5033a479c5781efaf650320f0aee4419d8841170bd3393cea5"
52
52
  },
53
- "composite": "58171232438afe93a43c83a5685856c5992179242282773d1cc41bea86ee7c41"
53
+ "composite": "8bbc540152a360b1ddbe2ee903691becafd0bf4d4be8719f5523e5e3c1cb68fb"
54
54
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "k8s",
3
- "version": "0.41.1",
3
+ "version": "0.41.3",
4
4
  "chantVersion": ">=0.1.0",
5
5
  "namespace": "K8s",
6
6
  "intrinsics": [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant-lexicon-k8s",
3
- "version": "0.41.1",
3
+ "version": "0.41.3",
4
4
  "description": "Kubernetes lexicon for chant — declarative IaC in TypeScript",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -84,7 +84,7 @@
84
84
  },
85
85
  "peerDependencies": {
86
86
  "zod": "^4.3.6",
87
- "@intentius/chant": "^0.41.1",
87
+ "@intentius/chant": "^0.41.3",
88
88
  "typescript": "^5.9.3"
89
89
  }
90
90
  }
@@ -1,23 +1,20 @@
1
1
  /**
2
2
  * Getting from a chant environment to a live API client — chant #1074.
3
3
  *
4
- * The cluster binding (chant #1100, landed in #1155) is unchanged by the move
5
- * off `kubectl`, and deliberately so: `resolveClusterTarget` in core stays the
6
- * single resolver, shared with the GCP lexicon's Config Connector observation,
7
- * and the refusal it throws on a bound-but-mismatched context still aborts
8
- * before any resource is touched. What changes is only *how the ambient
9
- * context is read*: the typed client parses the kubeconfig it is about to use
10
- * rather than shelling `kubectl config current-context`, so a worker image with
11
- * no `kubectl` in it can still check the binding. Same question, same three
4
+ * The cluster binding (chant #1100/#1155, revised by #1488):
5
+ * `resolveClusterTarget` in core stays the single resolver, and a declared
6
+ * binding is now *used*, never policed against the ambient context. Two
12
7
  * outcomes:
13
8
  *
14
- * - **Bound and ambient agrees** — the bound context is passed explicitly to
15
- * the client, never left to the ambient default.
16
- * - **Bound and ambient disagrees** `ClusterBindingMismatchError`, thrown
17
- * before the first request. Core turns the throw into NOT-OBSERVED for every
18
- * declared entity (chant #1089), which is what stops a wrong-cluster read
19
- * becoming a confident list of creates.
20
- * - **Unbound** the kubeconfig's own current-context, with the same visible
9
+ * - **Bound** — the bound context is passed explicitly to the client,
10
+ * regardless of what `kubectl` is ambiently pointed at. A binding that
11
+ * names a context the kubeconfig does not have fails in the client with an
12
+ * error naming both the context and the `k8s.profiles.<env>.context`
13
+ * binding never by falling back to ambient. (#1488: the old
14
+ * ambient-mismatch refusal meant any other project's k3d cluster stealing
15
+ * the current-context turned a healthy estate grey with `read-failed` as
16
+ * the only explanation.)
17
+ * - **Unbound** — the kubeconfig's own current-context, with a visible
21
18
  * warning naming the environment and the missing binding.
22
19
  */
23
20
 
@@ -62,7 +59,7 @@ export interface ConnectedClient {
62
59
  * build a client honoring it.
63
60
  */
64
61
  export const defaultK8sConnector: K8sConnector = async (options) => {
65
- const { createK8sClient, readAmbientContext } = await import("@intentius/chant-k8s-client");
62
+ const { createK8sClient } = await import("@intentius/chant-k8s-client");
66
63
 
67
64
  if (options.context !== undefined) {
68
65
  const client = await createK8sClient({
@@ -82,17 +79,10 @@ export const defaultK8sConnector: K8sConnector = async (options) => {
82
79
  const k8sConfig = (config as { k8s?: K8sChantConfig }).k8s;
83
80
  const execAllowlist = k8sConfig?.execCredentialPlugins;
84
81
 
85
- // The binding check reads the kubeconfig's current-context directly. Only a
86
- // context name is needed, and requiring a *usable* ambient cluster to check a
87
- // binding would defeat the binding: pointing an environment at a specific
88
- // context is how you recover from an ambient one that is wrong or broken.
89
- const target = await resolveClusterTarget(config as Record<string, unknown>, options.environment, "k8s", {
90
- ambientContext: () =>
91
- readAmbientContext({
92
- kubeconfig: options.client?.kubeconfig,
93
- kubeconfigPath: options.client?.kubeconfigPath,
94
- }),
95
- });
82
+ // #1488 the resolver returns the declared binding (or ambient-with-warning
83
+ // when unbound); it no longer reads the ambient context at all, so no reader
84
+ // is supplied.
85
+ const target = await resolveClusterTarget(config as Record<string, unknown>, options.environment, "k8s");
96
86
 
97
87
  const client = await createK8sClient({
98
88
  ...options.client,
@@ -9,7 +9,7 @@
9
9
  * components.
10
10
  */
11
11
  import type { Capability } from "@intentius/chant/components/capability";
12
- import type { CapabilityPlugin } from "@intentius/chant/components/capability-plugin";
12
+ import { ownPackageVersion, type CapabilityPlugin } from "@intentius/chant/components/capability-plugin";
13
13
  import { kubectlApplyCapability } from "./kubectl-apply";
14
14
 
15
15
  export const K8S_VERB_FAMILIES = {
@@ -18,7 +18,9 @@ export const K8S_VERB_FAMILIES = {
18
18
 
19
19
  export const k8sCapabilityPlugin: CapabilityPlugin = {
20
20
  name: "k8s",
21
- version: "0.41.0",
21
+ // The lexicon package's own version (#1505) — lockstep releases bump it, so
22
+ // a literal here was stale one release after it was written.
23
+ version: ownPackageVersion(import.meta.url),
22
24
  capabilities(): Array<Capability<never, unknown>> {
23
25
  return [kubectlApplyCapability as Capability<never, unknown>];
24
26
  },
@@ -9,6 +9,14 @@ describe("kubectl-apply capability (#1495 piece 2)", () => {
9
9
  expect(kinds).toContain("kubectl-apply");
10
10
  });
11
11
 
12
+ test("the plugin's version is the lexicon package's own, not a literal (#1505)", async () => {
13
+ const { readFileSync } = await import("node:fs");
14
+ const { version } = JSON.parse(
15
+ readFileSync(new URL("../../package.json", import.meta.url), "utf-8"),
16
+ ) as { version: string };
17
+ expect(k8sCapabilityPlugin.version).toBe(version);
18
+ });
19
+
12
20
  test("run delegates to the server-side apply with the component's env and the step's stack", async () => {
13
21
  let seen: unknown;
14
22
  const cap = createKubectlApplyCapability(async (args) => {
@@ -420,8 +420,11 @@ describe("k8s describeResources", () => {
420
420
  expect(result.resources.web).toMatchObject({ type: "K8s::Apps::Deployment", physicalId: "uid-1", status: "READY" });
421
421
  });
422
422
 
423
- test("bound and ambient context mismatches: refuses loudly instead of reading the wrong cluster", async () => {
423
+ test("bound while a different context is ambient: the binding wins and the estate observes (#1488)", async () => {
424
424
  loadChantConfigMock.mockResolvedValue({ config: { k8s: { profiles: { prod: { context: "prod-eks" } } } } });
425
+ // Ambient points at staging — the state any other project's k3d cluster
426
+ // leaves behind. Before #1488 this refused and the whole estate read
427
+ // grey; the declared binding must simply be used instead.
425
428
  const ambientIsStaging = fakeKubeconfig({
426
429
  contexts: [
427
430
  { name: "prod-eks", cluster: "prod", user: "prod-user" },
@@ -429,16 +432,17 @@ describe("k8s describeResources", () => {
429
432
  ],
430
433
  currentContext: "staging-eks",
431
434
  });
432
- const cluster = fakeCluster({ kubeconfig: ambientIsStaging });
435
+ const cluster = fakeCluster({
436
+ kubeconfig: ambientIsStaging,
437
+ objects: { [objectKey("apps/v1", "Deployment", "web", "prod")]: web },
438
+ });
433
439
 
434
- await expect(
435
- describeResources({ environment: "prod", buildOutput: "", entityNames: ["web"], entities: entities() }, (o) =>
436
- defaultK8sConnector({ ...o, client: { kubeconfig: ambientIsStaging, requestLayer: cluster.layer } }),
437
- ),
438
- ).rejects.toThrow(/environment "prod".*"prod-eks".*"staging-eks"/s);
440
+ const result = await describeResources(
441
+ { environment: "prod", buildOutput: "", entityNames: ["web"], entities: entities() },
442
+ (o) => defaultK8sConnector({ ...o, client: { kubeconfig: ambientIsStaging, requestLayer: cluster.layer } }),
443
+ );
439
444
 
440
- // Refused before a single request left the process.
441
- expect(cluster.layer.requests).toHaveLength(0);
445
+ expect(result.resources.web).toMatchObject({ type: "K8s::Apps::Deployment", physicalId: "uid-1", status: "READY" });
442
446
  });
443
447
 
444
448
  test("unbound: the kubeconfig's own context is used, and the fallback is visible", async () => {