@intentius/chant-k8s-client 0.42.1 → 0.44.2
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/client.d.ts +21 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client.test.ts +96 -0
- package/src/client.ts +104 -30
- package/src/errors.ts +12 -0
- package/src/types.ts +9 -0
package/dist/client.d.ts
CHANGED
|
@@ -125,6 +125,18 @@ export interface K8sClient {
|
|
|
125
125
|
resolve(selector: ResourceSelector, signal?: AbortSignal): Promise<ApiResourceInfo | undefined>;
|
|
126
126
|
/** GET one object. Throws {@link K8sApiError} with `notFound` when absent. */
|
|
127
127
|
read(ref: ObjectRef, options?: ReadOptions): Promise<K8sObject>;
|
|
128
|
+
/**
|
|
129
|
+
* The exact request path {@link read} would GET for this ref — apiVersion and
|
|
130
|
+
* kind resolved against the cluster's own discovery (which supplies the
|
|
131
|
+
* plural and the scope), and the namespace defaulted from the resolved
|
|
132
|
+
* context when the ref carries none. `undefined` when discovery does not
|
|
133
|
+
* serve the kind, where no path exists.
|
|
134
|
+
*
|
|
135
|
+
* This is the observation wire's resolved query address (chant #1620): the
|
|
136
|
+
* per-entity record of what was actually asked of the cluster, so a
|
|
137
|
+
* consumer can tell "looked in the wrong place" from "not there".
|
|
138
|
+
*/
|
|
139
|
+
pathFor(ref: ObjectRef, signal?: AbortSignal): Promise<string | undefined>;
|
|
128
140
|
/** GET one object, returning undefined instead of throwing on a 404. */
|
|
129
141
|
readIfPresent(ref: ObjectRef, options?: ReadOptions): Promise<K8sObject | undefined>;
|
|
130
142
|
/** LIST a kind, optionally namespaced and label-filtered. Follows `continue` tokens. */
|
|
@@ -153,6 +165,15 @@ export interface K8sClient {
|
|
|
153
165
|
* such groupVersion — an answer, not a failure. Cached like `resolve`.
|
|
154
166
|
*/
|
|
155
167
|
resources(apiVersion: string, signal?: AbortSignal): Promise<ApiResourceInfo[]>;
|
|
168
|
+
/**
|
|
169
|
+
* One groupVersion per API group the cluster serves — each group's own
|
|
170
|
+
* `preferredVersion` (falling back to its first listed version), with the
|
|
171
|
+
* core group's `v1` first (chant #1517). This is the discovery-driven
|
|
172
|
+
* sweep's enumeration unit: one version per group names every kind exactly
|
|
173
|
+
* once, without re-listing the same resources at a second served version.
|
|
174
|
+
* Cached for the client's lifetime, like the rest of discovery.
|
|
175
|
+
*/
|
|
176
|
+
preferredGroupVersions(signal?: AbortSignal): Promise<string[]>;
|
|
156
177
|
/** The API resource lists discovery has been asked for so far, for tests and diagnostics. */
|
|
157
178
|
discoveryCacheKeys(): string[];
|
|
158
179
|
}
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
CHANGED
|
@@ -30,6 +30,14 @@ export declare class K8sApiError extends Error {
|
|
|
30
30
|
/** What was being addressed, e.g. `apps/v1 Deployment prod/web`. */
|
|
31
31
|
readonly target?: string | undefined;
|
|
32
32
|
readonly status?: K8sStatus | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* Which cluster the failing read actually talked to, e.g.
|
|
35
|
+
* `context "k3d-fountain-local" (bound by k8s.profiles.local.context)`.
|
|
36
|
+
* Stamped by the client that issued the request (chant #1488) — a
|
|
37
|
+
* `read-failed` that does not name the cluster it read cost an afternoon on
|
|
38
|
+
* a laptop with two k3d clusters, so the failure carries it from birth.
|
|
39
|
+
*/
|
|
40
|
+
contextNote?: string;
|
|
33
41
|
constructor(statusCode: number, reason: string | undefined, apiMessage: string,
|
|
34
42
|
/** What was being addressed, e.g. `apps/v1 Deployment prod/web`. */
|
|
35
43
|
target?: string | undefined, status?: K8sStatus | undefined);
|
|
@@ -58,6 +66,8 @@ export declare class K8sApiError extends Error {
|
|
|
58
66
|
*/
|
|
59
67
|
export declare class K8sTransportError extends Error {
|
|
60
68
|
readonly target?: string | undefined;
|
|
69
|
+
/** Which cluster context the failed request was aimed at — see {@link K8sApiError.contextNote}. */
|
|
70
|
+
contextNote?: string;
|
|
61
71
|
constructor(message: string, target?: string | undefined, options?: {
|
|
62
72
|
cause?: unknown;
|
|
63
73
|
});
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,KAAK;
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,+EAA+E;AAC/E,MAAM,WAAW,SAAS;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,iFAAiF;IACjF,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,qBAAa,WAAY,SAAQ,KAAK;aAWlB,UAAU,EAAE,MAAM;aAClB,MAAM,EAAE,MAAM,GAAG,SAAS;aAC1B,UAAU,EAAE,MAAM;IAClC,oEAAoE;aACpD,MAAM,CAAC,EAAE,MAAM;aACf,MAAM,CAAC,EAAE,SAAS;IAfpC;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;gBAGH,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,UAAU,EAAE,MAAM;IAClC,oEAAoE;IACpD,MAAM,CAAC,EAAE,MAAM,YAAA,EACf,MAAM,CAAC,EAAE,SAAS,YAAA;IASpC,0EAA0E;IAC1E,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED,4EAA4E;IAC5E,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,8CAA8C;IAC9C,IAAI,YAAY,IAAI,OAAO,CAE1B;IAED;;;;OAIG;IACH,IAAI,QAAQ,IAAI,OAAO,CAEtB;IAED;;;OAGG;IACH,MAAM,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,WAAW;CAapF;AAED;;;;GAIG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;aAMxB,MAAM,CAAC,EAAE,MAAM;IALjC,mGAAmG;IACnG,WAAW,CAAC,EAAE,MAAM,CAAC;gBAGnB,OAAO,EAAE,MAAM,EACC,MAAM,CAAC,EAAE,MAAM,YAAA,EAC/B,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAE;CAMhC;AAED;;;;;GAKG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;gBACtC,KAAK,CAAC,EAAE,OAAO;CAS5B;AAED;;;;;;;GAOG;AACH,qBAAa,6BAA8B,SAAQ,KAAK;aAEpC,OAAO,EAAE,MAAM;aACf,OAAO,EAAE,SAAS,MAAM,EAAE;gBAD1B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,SAAS,MAAM,EAAE;CAU7C;AAED;;;;;GAKG;AACH,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED,4EAA4E;AAC5E,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;aAE3B,YAAY,EAAE,MAAM;gBAApB,YAAY,EAAE,MAAM,EACpC,OAAO,CAAC,EAAE,MAAM;CAKnB"}
|
package/dist/types.d.ts
CHANGED
|
@@ -115,6 +115,15 @@ export interface K8sClientOptions {
|
|
|
115
115
|
concurrency?: number;
|
|
116
116
|
/** Where the context came from, for provenance. Default `ambient`. */
|
|
117
117
|
contextSource?: "bound" | "ambient";
|
|
118
|
+
/**
|
|
119
|
+
* How the context was selected, in words — e.g.
|
|
120
|
+
* `bound by k8s.profiles.prod.context` or
|
|
121
|
+
* `ambient; no k8s.profiles.local binding`. Stamped (with the resolved
|
|
122
|
+
* context name) onto every API/transport failure the client throws, so a
|
|
123
|
+
* `read-failed` reason names the cluster it read (chant #1488). Defaults to
|
|
124
|
+
* the bare `contextSource`.
|
|
125
|
+
*/
|
|
126
|
+
contextLabel?: string;
|
|
118
127
|
}
|
|
119
128
|
/**
|
|
120
129
|
* `@kubernetes/client-node`'s promise-shaped HTTP library: it receives the
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intentius/chant-k8s-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.2",
|
|
4
4
|
"description": "Typed Kubernetes API client for chant — the read/write path of the k8s lexicon, kept out of the build path",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://intentius.io/chant/lexicons/k8s/api-client/",
|
package/src/client.test.ts
CHANGED
|
@@ -117,6 +117,47 @@ describe("createK8sClient — kubeconfig and credential policy", () => {
|
|
|
117
117
|
expect(layer.requests).toHaveLength(0);
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
+
test("an API failure names the context it read (#1488)", async () => {
|
|
121
|
+
const layer = cluster({}, (req) =>
|
|
122
|
+
req.path.endsWith("/deployments/web")
|
|
123
|
+
? { status: 500, body: statusBody(500, "InternalError", "etcdserver: leader changed") }
|
|
124
|
+
: undefined,
|
|
125
|
+
);
|
|
126
|
+
const c = await client(layer);
|
|
127
|
+
const err = await c.read({ apiVersion: "apps/v1", kind: "Deployment", name: "web", namespace: "prod" }).catch((e: unknown) => e);
|
|
128
|
+
expect(err).toBeInstanceOf(K8sApiError);
|
|
129
|
+
expect((err as K8sApiError).contextNote).toBe('context "test-context" (ambient)');
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
test("a bound client's failures carry the binding story via contextLabel (#1488)", async () => {
|
|
133
|
+
const layer = cluster({}, (req) =>
|
|
134
|
+
req.path.endsWith("/deployments/web") ? { status: 503, body: statusBody(503, "ServiceUnavailable", "apiserver down") } : undefined,
|
|
135
|
+
);
|
|
136
|
+
const c = await createK8sClient({
|
|
137
|
+
kubeconfig: fakeKubeconfig({
|
|
138
|
+
contexts: [{ name: "staging-eks" }, { name: "prod-eks", cluster: "prod", user: "prod-user" }],
|
|
139
|
+
currentContext: "staging-eks",
|
|
140
|
+
}),
|
|
141
|
+
context: "prod-eks",
|
|
142
|
+
contextSource: "bound",
|
|
143
|
+
contextLabel: "bound by k8s.profiles.prod.context",
|
|
144
|
+
requestLayer: layer,
|
|
145
|
+
});
|
|
146
|
+
const err = await c.read({ apiVersion: "apps/v1", kind: "Deployment", name: "web", namespace: "prod" }).catch((e: unknown) => e);
|
|
147
|
+
expect(err).toBeInstanceOf(K8sApiError);
|
|
148
|
+
expect((err as K8sApiError).contextNote).toBe('context "prod-eks" (bound by k8s.profiles.prod.context)');
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
test("a transport failure names the context it was aimed at (#1488)", async () => {
|
|
152
|
+
const layer = fakeRequestLayer(() => {
|
|
153
|
+
throw new Error("connect ECONNREFUSED 127.0.0.1:6443");
|
|
154
|
+
});
|
|
155
|
+
const c = await createK8sClient({ kubeconfig: fakeKubeconfig(), requestLayer: layer });
|
|
156
|
+
const err = await c.read({ apiVersion: "apps/v1", kind: "Deployment", name: "web" }).catch((e: unknown) => e);
|
|
157
|
+
expect(err).toBeInstanceOf(K8sTransportError);
|
|
158
|
+
expect((err as K8sTransportError).contextNote).toBe('context "test-context" (ambient)');
|
|
159
|
+
});
|
|
160
|
+
|
|
120
161
|
test("an exec credential plugin off the allowlist refuses before it can run", async () => {
|
|
121
162
|
const layer = cluster();
|
|
122
163
|
await expect(
|
|
@@ -201,6 +242,39 @@ describe("resource resolution through the cluster's own discovery", () => {
|
|
|
201
242
|
expect(c.discoveryCacheKeys()).toEqual(["apps/v1"]);
|
|
202
243
|
});
|
|
203
244
|
|
|
245
|
+
// chant #1517 — the runtime-children sweep's enumeration unit: one version
|
|
246
|
+
// per group, so every kind is named exactly once.
|
|
247
|
+
test("preferredGroupVersions returns core v1 plus each group's own preferredVersion, once", async () => {
|
|
248
|
+
const layer = fakeRequestLayer((req) => {
|
|
249
|
+
if (req.path === "/api") return { body: { kind: "APIVersions", versions: ["v1"] } };
|
|
250
|
+
if (req.path === "/apis") {
|
|
251
|
+
return {
|
|
252
|
+
body: {
|
|
253
|
+
kind: "APIGroupList",
|
|
254
|
+
groups: [
|
|
255
|
+
{
|
|
256
|
+
name: "apps",
|
|
257
|
+
preferredVersion: { groupVersion: "apps/v1", version: "v1" },
|
|
258
|
+
versions: [{ groupVersion: "apps/v1" }, { groupVersion: "apps/v1beta1" }],
|
|
259
|
+
},
|
|
260
|
+
// A group reporting no preferredVersion falls back to its first
|
|
261
|
+
// listed version rather than being dropped.
|
|
262
|
+
{ name: "widgets.example.com", versions: [{ groupVersion: "widgets.example.com/v1alpha1" }] },
|
|
263
|
+
],
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
return { status: 404, body: statusBody(404, "NotFound", `${req.path} not found`) };
|
|
268
|
+
});
|
|
269
|
+
const c = await client(layer);
|
|
270
|
+
|
|
271
|
+
expect(await c.preferredGroupVersions()).toEqual(["v1", "apps/v1", "widgets.example.com/v1alpha1"]);
|
|
272
|
+
|
|
273
|
+
// Cached: a second call issues no further discovery requests.
|
|
274
|
+
await c.preferredGroupVersions();
|
|
275
|
+
expect(layer.paths().filter((p) => p === "/apis")).toHaveLength(1);
|
|
276
|
+
});
|
|
277
|
+
|
|
204
278
|
test("kubectl-style resource strings resolve the way kubectl resolves them", async () => {
|
|
205
279
|
const c = await client(cluster());
|
|
206
280
|
// plural.group — how waitForReady's callers have always spelled CRDs
|
|
@@ -304,6 +378,28 @@ describe("reads", () => {
|
|
|
304
378
|
c.readIfPresent({ apiVersion: "apps/v1", kind: "Deployment", name: "denied", namespace: "prod" }),
|
|
305
379
|
).rejects.toThrow(K8sApiError);
|
|
306
380
|
});
|
|
381
|
+
|
|
382
|
+
test("pathFor returns the exact path read would GET, with the namespace defaulted (chant #1620)", async () => {
|
|
383
|
+
const c = await client(cluster());
|
|
384
|
+
// No namespace on the ref: the resolved path shows where the read actually
|
|
385
|
+
// went — the context's default — which is the whole diagnostic point.
|
|
386
|
+
expect(await c.pathFor({ apiVersion: "apps/v1", kind: "Deployment", name: "web" })).toBe(
|
|
387
|
+
"/apis/apps/v1/namespaces/default/deployments/web",
|
|
388
|
+
);
|
|
389
|
+
expect(await c.pathFor({ apiVersion: "apps/v1", kind: "Deployment", name: "web", namespace: "prod" })).toBe(
|
|
390
|
+
"/apis/apps/v1/namespaces/prod/deployments/web",
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("pathFor on a cluster-scoped kind carries no namespace segment", async () => {
|
|
395
|
+
const c = await client(cluster());
|
|
396
|
+
expect(await c.pathFor({ apiVersion: "v1", kind: "Namespace", name: "ns-a" })).toBe("/api/v1/namespaces/ns-a");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
test("pathFor is undefined for a kind discovery does not serve — no path exists", async () => {
|
|
400
|
+
const c = await client(cluster());
|
|
401
|
+
expect(await c.pathFor({ apiVersion: "widgets.example.com/v1", kind: "Widget", name: "w" })).toBeUndefined();
|
|
402
|
+
});
|
|
307
403
|
});
|
|
308
404
|
|
|
309
405
|
describe("concurrency", () => {
|
package/src/client.ts
CHANGED
|
@@ -187,6 +187,18 @@ export interface K8sClient {
|
|
|
187
187
|
resolve(selector: ResourceSelector, signal?: AbortSignal): Promise<ApiResourceInfo | undefined>;
|
|
188
188
|
/** GET one object. Throws {@link K8sApiError} with `notFound` when absent. */
|
|
189
189
|
read(ref: ObjectRef, options?: ReadOptions): Promise<K8sObject>;
|
|
190
|
+
/**
|
|
191
|
+
* The exact request path {@link read} would GET for this ref — apiVersion and
|
|
192
|
+
* kind resolved against the cluster's own discovery (which supplies the
|
|
193
|
+
* plural and the scope), and the namespace defaulted from the resolved
|
|
194
|
+
* context when the ref carries none. `undefined` when discovery does not
|
|
195
|
+
* serve the kind, where no path exists.
|
|
196
|
+
*
|
|
197
|
+
* This is the observation wire's resolved query address (chant #1620): the
|
|
198
|
+
* per-entity record of what was actually asked of the cluster, so a
|
|
199
|
+
* consumer can tell "looked in the wrong place" from "not there".
|
|
200
|
+
*/
|
|
201
|
+
pathFor(ref: ObjectRef, signal?: AbortSignal): Promise<string | undefined>;
|
|
190
202
|
/** GET one object, returning undefined instead of throwing on a 404. */
|
|
191
203
|
readIfPresent(ref: ObjectRef, options?: ReadOptions): Promise<K8sObject | undefined>;
|
|
192
204
|
/** LIST a kind, optionally namespaced and label-filtered. Follows `continue` tokens. */
|
|
@@ -215,6 +227,15 @@ export interface K8sClient {
|
|
|
215
227
|
* such groupVersion — an answer, not a failure. Cached like `resolve`.
|
|
216
228
|
*/
|
|
217
229
|
resources(apiVersion: string, signal?: AbortSignal): Promise<ApiResourceInfo[]>;
|
|
230
|
+
/**
|
|
231
|
+
* One groupVersion per API group the cluster serves — each group's own
|
|
232
|
+
* `preferredVersion` (falling back to its first listed version), with the
|
|
233
|
+
* core group's `v1` first (chant #1517). This is the discovery-driven
|
|
234
|
+
* sweep's enumeration unit: one version per group names every kind exactly
|
|
235
|
+
* once, without re-listing the same resources at a second served version.
|
|
236
|
+
* Cached for the client's lifetime, like the rest of discovery.
|
|
237
|
+
*/
|
|
238
|
+
preferredGroupVersions(signal?: AbortSignal): Promise<string[]>;
|
|
218
239
|
/** The API resource lists discovery has been asked for so far, for tests and diagnostics. */
|
|
219
240
|
discoveryCacheKeys(): string[];
|
|
220
241
|
}
|
|
@@ -231,6 +252,14 @@ interface ApiResourceListResponse {
|
|
|
231
252
|
}>;
|
|
232
253
|
}
|
|
233
254
|
|
|
255
|
+
/** The `/api` + `/apis` root discovery documents, kept structured so both the
|
|
256
|
+
* flat enumeration (`servedGroupVersions`) and the one-per-group enumeration
|
|
257
|
+
* (`preferredGroupVersions`, chant #1517) derive from a single cached fetch. */
|
|
258
|
+
interface RootDiscovery {
|
|
259
|
+
coreVersions: string[];
|
|
260
|
+
groups: Array<{ preferred?: string; versions: string[] }>;
|
|
261
|
+
}
|
|
262
|
+
|
|
234
263
|
/**
|
|
235
264
|
* Build a client. Nothing is read from the network here — the kubeconfig is
|
|
236
265
|
* parsed, the context resolved, and the credential policy enforced, all before
|
|
@@ -301,12 +330,23 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
301
330
|
const defaultNamespace = kc.getContextObject(kc.getCurrentContext())?.namespace || "default";
|
|
302
331
|
const concurrency = options.concurrency ?? DEFAULT_CONCURRENCY;
|
|
303
332
|
|
|
333
|
+
// Which cluster this client reads, in words, stamped onto every failure it
|
|
334
|
+
// throws (chant #1488). A `read-failed` that does not say which context it
|
|
335
|
+
// read turned "the ambient context moved" into an afternoon of debugging;
|
|
336
|
+
// naming it here — at construction, once — means every read path's
|
|
337
|
+
// classification carries it without each caller remembering to.
|
|
338
|
+
const contextNote = `context "${provenance.context ?? "(unset)"}" (${options.contextLabel ?? provenance.contextSource})`;
|
|
339
|
+
function noted<T extends { contextNote?: string }>(err: T): T {
|
|
340
|
+
err.contextNote = contextNote;
|
|
341
|
+
return err;
|
|
342
|
+
}
|
|
343
|
+
|
|
304
344
|
// apiVersion → its APIResourceList, or null when the cluster does not serve
|
|
305
345
|
// that group/version at all. Promises are cached, not values, so N entities
|
|
306
346
|
// resolved concurrently issue one discovery request between them rather
|
|
307
347
|
// than N identical ones.
|
|
308
348
|
const discoveryCache = new Map<string, Promise<ApiResourceListResponse | null>>();
|
|
309
|
-
let
|
|
349
|
+
let rootDiscoveryCache: Promise<RootDiscovery> | undefined;
|
|
310
350
|
|
|
311
351
|
async function send(
|
|
312
352
|
path: string,
|
|
@@ -338,10 +378,10 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
338
378
|
.send(ctx)
|
|
339
379
|
.toPromise()) as unknown as ResponseContextLike;
|
|
340
380
|
} catch (err) {
|
|
341
|
-
throw
|
|
342
|
-
err instanceof Error ? err.message : String(err),
|
|
343
|
-
|
|
344
|
-
|
|
381
|
+
throw noted(
|
|
382
|
+
new K8sTransportError(err instanceof Error ? err.message : String(err), opts.target ?? `${method} ${path}`, {
|
|
383
|
+
cause: err,
|
|
384
|
+
}),
|
|
345
385
|
);
|
|
346
386
|
}
|
|
347
387
|
|
|
@@ -349,10 +389,12 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
349
389
|
try {
|
|
350
390
|
text = await response.body.text();
|
|
351
391
|
} catch (err) {
|
|
352
|
-
throw
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
392
|
+
throw noted(
|
|
393
|
+
new K8sTransportError(
|
|
394
|
+
`response body could not be read: ${err instanceof Error ? err.message : String(err)}`,
|
|
395
|
+
opts.target ?? `${method} ${path}`,
|
|
396
|
+
{ cause: err },
|
|
397
|
+
),
|
|
356
398
|
);
|
|
357
399
|
}
|
|
358
400
|
return { status: response.httpStatusCode, body: text };
|
|
@@ -365,15 +407,17 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
365
407
|
): Promise<T> {
|
|
366
408
|
const { status, body } = await send(path, method, opts);
|
|
367
409
|
if (status < 200 || status > 299) {
|
|
368
|
-
throw K8sApiError.fromResponse(status, body, opts.target);
|
|
410
|
+
throw noted(K8sApiError.fromResponse(status, body, opts.target));
|
|
369
411
|
}
|
|
370
412
|
try {
|
|
371
413
|
return JSON.parse(body) as T;
|
|
372
414
|
} catch (err) {
|
|
373
|
-
throw
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
415
|
+
throw noted(
|
|
416
|
+
new K8sTransportError(
|
|
417
|
+
`the API server returned HTTP ${status} with a body that is not JSON`,
|
|
418
|
+
opts.target ?? `${method} ${path}`,
|
|
419
|
+
{ cause: err },
|
|
420
|
+
),
|
|
377
421
|
);
|
|
378
422
|
}
|
|
379
423
|
}
|
|
@@ -399,15 +443,13 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
399
443
|
return pending;
|
|
400
444
|
}
|
|
401
445
|
|
|
402
|
-
async function
|
|
403
|
-
if (
|
|
404
|
-
|
|
405
|
-
const out: string[] = [];
|
|
446
|
+
async function rootDiscovery(signal?: AbortSignal): Promise<RootDiscovery> {
|
|
447
|
+
if (rootDiscoveryCache) return rootDiscoveryCache;
|
|
448
|
+
rootDiscoveryCache = (async (): Promise<RootDiscovery> => {
|
|
406
449
|
const core = await sendJson<{ versions?: string[] }>("/api", "GET", {
|
|
407
450
|
signal,
|
|
408
451
|
target: "discovery /api",
|
|
409
452
|
});
|
|
410
|
-
out.push(...(core.versions ?? ["v1"]));
|
|
411
453
|
const groups = await sendJson<{
|
|
412
454
|
groups?: Array<{
|
|
413
455
|
name?: string;
|
|
@@ -415,19 +457,43 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
415
457
|
versions?: Array<{ groupVersion?: string }>;
|
|
416
458
|
}>;
|
|
417
459
|
}>("/apis", "GET", { signal, target: "discovery /apis" });
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
460
|
+
return {
|
|
461
|
+
coreVersions: core.versions ?? ["v1"],
|
|
462
|
+
groups: (groups.groups ?? []).map((group) => ({
|
|
463
|
+
preferred: group.preferredVersion?.groupVersion,
|
|
464
|
+
versions: (group.versions ?? [])
|
|
465
|
+
.map((v) => v.groupVersion)
|
|
466
|
+
.filter((gv): gv is string => typeof gv === "string" && gv.length > 0),
|
|
467
|
+
})),
|
|
468
|
+
};
|
|
426
469
|
})().catch((err: unknown) => {
|
|
427
|
-
|
|
470
|
+
rootDiscoveryCache = undefined;
|
|
428
471
|
throw err;
|
|
429
472
|
});
|
|
430
|
-
return
|
|
473
|
+
return rootDiscoveryCache;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async function servedGroupVersions(signal?: AbortSignal): Promise<string[]> {
|
|
477
|
+
const root = await rootDiscovery(signal);
|
|
478
|
+
const out: string[] = [...root.coreVersions];
|
|
479
|
+
for (const group of root.groups) {
|
|
480
|
+
if (group.preferred) out.push(group.preferred);
|
|
481
|
+
for (const gv of group.versions) {
|
|
482
|
+
if (gv !== group.preferred) out.push(gv);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
return [...new Set(out)];
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function preferredGroupVersions(signal?: AbortSignal): Promise<string[]> {
|
|
489
|
+
const root = await rootDiscovery(signal);
|
|
490
|
+
const core = root.coreVersions.includes("v1") ? "v1" : root.coreVersions[0];
|
|
491
|
+
const out: string[] = core ? [core] : [];
|
|
492
|
+
for (const group of root.groups) {
|
|
493
|
+
const gv = group.preferred ?? group.versions[0];
|
|
494
|
+
if (gv) out.push(gv);
|
|
495
|
+
}
|
|
496
|
+
return [...new Set(out)];
|
|
431
497
|
}
|
|
432
498
|
|
|
433
499
|
function toInfo(apiVersion: string, entry: NonNullable<ApiResourceListResponse["resources"]>[number]): ApiResourceInfo {
|
|
@@ -518,6 +584,12 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
518
584
|
return parts.join("/");
|
|
519
585
|
}
|
|
520
586
|
|
|
587
|
+
async function pathFor(ref: ObjectRef, signal?: AbortSignal): Promise<string | undefined> {
|
|
588
|
+
const info = await resolve({ apiVersion: ref.apiVersion, kind: ref.kind }, signal);
|
|
589
|
+
if (!info) return undefined;
|
|
590
|
+
return objectPath(info, ref.name, ref.namespace);
|
|
591
|
+
}
|
|
592
|
+
|
|
521
593
|
async function read(ref: ObjectRef, opts: ReadOptions = {}): Promise<K8sObject> {
|
|
522
594
|
const info = await resolveOrThrow({ apiVersion: ref.apiVersion, kind: ref.kind }, opts.signal);
|
|
523
595
|
return sendJson<K8sObject>(objectPath(info, ref.name, ref.namespace), "GET", {
|
|
@@ -618,7 +690,7 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
618
690
|
target,
|
|
619
691
|
});
|
|
620
692
|
if (status < 200 || status > 299) {
|
|
621
|
-
throw K8sApiError.fromResponse(status, body, target);
|
|
693
|
+
throw noted(K8sApiError.fromResponse(status, body, target));
|
|
622
694
|
}
|
|
623
695
|
return body;
|
|
624
696
|
}
|
|
@@ -648,6 +720,7 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
648
720
|
defaultNamespace,
|
|
649
721
|
resolve,
|
|
650
722
|
read,
|
|
723
|
+
pathFor,
|
|
651
724
|
readIfPresent,
|
|
652
725
|
list,
|
|
653
726
|
readLog,
|
|
@@ -655,6 +728,7 @@ export async function createK8sClient(options: K8sClientOptions = {}): Promise<K
|
|
|
655
728
|
delete: remove,
|
|
656
729
|
concurrently: (items, fn) => mapConcurrent(items, fn, concurrency),
|
|
657
730
|
resources: resourcesOf,
|
|
731
|
+
preferredGroupVersions,
|
|
658
732
|
discoveryCacheKeys: () => [...discoveryCache.keys()].sort(),
|
|
659
733
|
};
|
|
660
734
|
}
|
package/src/errors.ts
CHANGED
|
@@ -26,6 +26,15 @@ export interface K8sStatus {
|
|
|
26
26
|
* `reason` come from the response, not from parsing text.
|
|
27
27
|
*/
|
|
28
28
|
export class K8sApiError extends Error {
|
|
29
|
+
/**
|
|
30
|
+
* Which cluster the failing read actually talked to, e.g.
|
|
31
|
+
* `context "k3d-fountain-local" (bound by k8s.profiles.local.context)`.
|
|
32
|
+
* Stamped by the client that issued the request (chant #1488) — a
|
|
33
|
+
* `read-failed` that does not name the cluster it read cost an afternoon on
|
|
34
|
+
* a laptop with two k3d clusters, so the failure carries it from birth.
|
|
35
|
+
*/
|
|
36
|
+
contextNote?: string;
|
|
37
|
+
|
|
29
38
|
constructor(
|
|
30
39
|
public readonly statusCode: number,
|
|
31
40
|
public readonly reason: string | undefined,
|
|
@@ -90,6 +99,9 @@ export class K8sApiError extends Error {
|
|
|
90
99
|
* connect" and "the server said no" are different observations.
|
|
91
100
|
*/
|
|
92
101
|
export class K8sTransportError extends Error {
|
|
102
|
+
/** Which cluster context the failed request was aimed at — see {@link K8sApiError.contextNote}. */
|
|
103
|
+
contextNote?: string;
|
|
104
|
+
|
|
93
105
|
constructor(
|
|
94
106
|
message: string,
|
|
95
107
|
public readonly target?: string,
|
package/src/types.ts
CHANGED
|
@@ -125,6 +125,15 @@ export interface K8sClientOptions {
|
|
|
125
125
|
concurrency?: number;
|
|
126
126
|
/** Where the context came from, for provenance. Default `ambient`. */
|
|
127
127
|
contextSource?: "bound" | "ambient";
|
|
128
|
+
/**
|
|
129
|
+
* How the context was selected, in words — e.g.
|
|
130
|
+
* `bound by k8s.profiles.prod.context` or
|
|
131
|
+
* `ambient; no k8s.profiles.local binding`. Stamped (with the resolved
|
|
132
|
+
* context name) onto every API/transport failure the client throws, so a
|
|
133
|
+
* `read-failed` reason names the cluster it read (chant #1488). Defaults to
|
|
134
|
+
* the bare `contextSource`.
|
|
135
|
+
*/
|
|
136
|
+
contextLabel?: string;
|
|
128
137
|
}
|
|
129
138
|
|
|
130
139
|
/**
|