@intentius/chant-k8s-client 0.44.4 → 0.44.6
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 +26 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/kubeconfig.d.ts +57 -0
- package/dist/kubeconfig.d.ts.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +61 -18
- package/src/index.ts +3 -0
- package/src/kubeconfig.test.ts +231 -0
- package/src/kubeconfig.ts +136 -0
- package/src/types.ts +21 -0
package/dist/client.d.ts
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* path that writes the `Authorization` header — runs for real, which is what
|
|
35
35
|
* makes a test that injects one worth writing.
|
|
36
36
|
*/
|
|
37
|
-
import type { ApiResourceInfo, ClientProvenance, K8sClientOptions, K8sObject, ObjectRef, ResourceSelector } from "./types.js";
|
|
37
|
+
import type { ApiResourceInfo, ClientProvenance, K8sClientOptions, K8sObject, ObjectRef, ResourceSelector, KubeconfigView } from "./types.js";
|
|
38
38
|
type ClientNode = typeof import("@kubernetes/client-node");
|
|
39
39
|
/**
|
|
40
40
|
* Load `@kubernetes/client-node`, once per process.
|
|
@@ -58,6 +58,31 @@ export declare function isK8sClientAvailable(): Promise<boolean>;
|
|
|
58
58
|
* kubeconfig can be read at all.
|
|
59
59
|
*/
|
|
60
60
|
export declare function readAmbientContext(options?: Pick<K8sClientOptions, "kubeconfig" | "kubeconfigPath">): Promise<string | undefined>;
|
|
61
|
+
/**
|
|
62
|
+
* The merged kubeconfig's contexts and their apiservers, without building a
|
|
63
|
+
* client (chant #1630).
|
|
64
|
+
*
|
|
65
|
+
* A lens showing an operator which cluster the k8s half is being read from
|
|
66
|
+
* needs an address out of a file, not a client about to talk to one. Going
|
|
67
|
+
* through `createK8sClient` for that answer refuses in two places it should
|
|
68
|
+
* not: an auth plugin outside the exec allowlist throws, so a cluster whose
|
|
69
|
+
* address is sitting right there in the kubeconfig — and which kubectl reads
|
|
70
|
+
* without complaint — reports as no target at all; and a context resolving to
|
|
71
|
+
* no cluster throws where the caller wants "nothing to report". Both refusals
|
|
72
|
+
* are right for a client that is about to send a request and wrong for a
|
|
73
|
+
* config read. Enumerating also takes one client per context, and enumerating
|
|
74
|
+
* is exactly what a lens does.
|
|
75
|
+
*
|
|
76
|
+
* So: no credential touched, no exec plugin run, no request issued. Every
|
|
77
|
+
* failure — no kubeconfig, an unreadable one, `@kubernetes/client-node` not
|
|
78
|
+
* installed — is an empty result rather than a throw, because a lens on an
|
|
79
|
+
* aws-only project running on a machine that has never seen Kubernetes has
|
|
80
|
+
* nothing to report and nothing to fail about.
|
|
81
|
+
*
|
|
82
|
+
* `server` per context rather than a separate cluster map: joining the two is
|
|
83
|
+
* what every caller does anyway.
|
|
84
|
+
*/
|
|
85
|
+
export declare function readKubeconfigView(options?: Pick<K8sClientOptions, "kubeconfig" | "kubeconfigPath">): Promise<KubeconfigView>;
|
|
61
86
|
/** Options for a single object read. */
|
|
62
87
|
export interface ReadOptions {
|
|
63
88
|
signal?: AbortSignal;
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* `examples/k8s-client-boundary.test.ts` walks the static import graph to prove
|
|
12
12
|
* it stays that way.
|
|
13
13
|
*/
|
|
14
|
-
export { createK8sClient, readAmbientContext, loadClientNode, isK8sClientAvailable, apiVersionPath, splitApiVersion, selectorText, refText, } from "./client.js";
|
|
14
|
+
export { createK8sClient, readAmbientContext, readKubeconfigView, loadClientNode, isK8sClientAvailable, apiVersionPath, splitApiVersion, selectorText, refText, } from "./client.js";
|
|
15
15
|
export type { K8sClient, ReadOptions, ApplyOptions, DeleteOptions, ListOptions, ReadLogOptions } from "./client.js";
|
|
16
16
|
export { K8sApiError, K8sTransportError, K8sClientUnavailableError, ExecCredentialNotAllowedError, FieldManagerError, KubeConfigError, UnknownResourceError, } from "./errors.js";
|
|
17
17
|
export type { K8sStatus } from "./errors.js";
|
|
@@ -24,5 +24,5 @@ export type { ManagedFieldsEntry, ManagerFieldSet } from "./managed-fields.js";
|
|
|
24
24
|
export { DEFAULT_EXEC_ALLOWLIST, assertExecCredentialAllowed, credentialPathOf, execConfigOf, execCommandName, } from "./credentials.js";
|
|
25
25
|
export type { ExecConfig, KubeConfigUser } from "./credentials.js";
|
|
26
26
|
export { mapConcurrent, DEFAULT_CONCURRENCY } from "./concurrency.js";
|
|
27
|
-
export type { ApiResourceInfo, ClientProvenance, CredentialPath, K8sClientOptions, K8sObject, ObjectRef, RequestContextLike, RequestLayer, ResourceSelector, ResponseContextLike, } from "./types.js";
|
|
27
|
+
export type { ApiResourceInfo, ClientProvenance, CredentialPath, K8sClientOptions, KubeconfigContextInfo, KubeconfigView, K8sObject, ObjectRef, RequestContextLike, RequestLayer, ResourceSelector, ResponseContextLike, } from "./types.js";
|
|
28
28
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,OAAO,GACR,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAEjH,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,6BAA6B,EAC7B,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,EACf,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGhE,OAAO,EACL,eAAe,EACf,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,gBAAgB,EAChB,YAAY,EACZ,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEnE,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,SAAS,EACT,SAAS,EACT,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,kBAAkB,EAClB,cAAc,EACd,oBAAoB,EACpB,cAAc,EACd,eAAe,EACf,YAAY,EACZ,OAAO,GACR,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAEjH,OAAO,EACL,WAAW,EACX,iBAAiB,EACjB,yBAAyB,EACzB,6BAA6B,EAC7B,iBAAiB,EACjB,eAAe,EACf,oBAAoB,GACrB,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAG1C,OAAO,EACL,mBAAmB,EACnB,uBAAuB,EACvB,wBAAwB,EACxB,eAAe,EACf,uBAAuB,EACvB,mBAAmB,EACnB,YAAY,GACb,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAE5D,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAGhE,OAAO,EACL,eAAe,EACf,WAAW,EACX,UAAU,EACV,aAAa,EACb,gBAAgB,EAChB,WAAW,EACX,YAAY,EACZ,aAAa,GACd,MAAM,kBAAkB,CAAC;AAC1B,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,EACL,sBAAsB,EACtB,2BAA2B,EAC3B,gBAAgB,EAChB,YAAY,EACZ,eAAe,GAChB,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAEnE,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,gBAAgB,EAChB,qBAAqB,EACrB,cAAc,EACd,SAAS,EACT,SAAS,EACT,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading the kubeconfig the way kubectl loads it — chant #1630.
|
|
3
|
+
*
|
|
4
|
+
* `@kubernetes/client-node` parses a kubeconfig faithfully and merges a
|
|
5
|
+
* `KUBECONFIG` path list on two rules that are not kubectl's. Both are
|
|
6
|
+
* reproducible with two files and no cluster (checked against client-node
|
|
7
|
+
* 1.4.0), and both make chant bind a different cluster than the `kubectl get`
|
|
8
|
+
* the operator ran to check:
|
|
9
|
+
*
|
|
10
|
+
* - **Duplicate names throw.** `mergeConfig` funnels every entry through
|
|
11
|
+
* `addCluster`/`addUser`/`addContext`, each of which throws `Duplicate
|
|
12
|
+
* cluster: <name>` on a name it already holds. kubectl's documented rule is
|
|
13
|
+
* the opposite — keep the first, ignore later ones, never fail. Overlapping
|
|
14
|
+
* names across a list are ordinary: a team-wide file plus a personal one
|
|
15
|
+
* both naming `dev` is the usual way people get there. Today that throw is
|
|
16
|
+
* swallowed by `readAmbientContext`'s try/catch, so chant reads those users
|
|
17
|
+
* as having no kubeconfig at all.
|
|
18
|
+
* - **current-context is last-wins.** `loadFromDefault(opts,
|
|
19
|
+
* contextFromStartingConfig)` defaults the second argument to `false`, and
|
|
20
|
+
* `mergeConfig` then overwrites `currentContext` from each later file.
|
|
21
|
+
* kubectl takes the FIRST file that sets one.
|
|
22
|
+
*
|
|
23
|
+
* So the multi-file case is merged here instead: one `KubeConfig` per file
|
|
24
|
+
* (which is also what keeps each file's relative certificate paths resolved
|
|
25
|
+
* against its own directory), then a first-wins union by name and the first
|
|
26
|
+
* `current-context` that appears. Everything else — the single-file path, the
|
|
27
|
+
* `~/.kube/config` fallback, in-cluster credentials — stays client-node's
|
|
28
|
+
* `loadFromDefault`, with the one-word fix that gives it kubectl's
|
|
29
|
+
* current-context.
|
|
30
|
+
*
|
|
31
|
+
* A file in the list that cannot be read is skipped rather than fatal, which
|
|
32
|
+
* is also kubectl's rule: a stale entry in a shared `KUBECONFIG` export must
|
|
33
|
+
* not take the whole config down with it.
|
|
34
|
+
*/
|
|
35
|
+
import type * as k8s from "@kubernetes/client-node";
|
|
36
|
+
type ClientNode = typeof import("@kubernetes/client-node");
|
|
37
|
+
/** Where the loaded kubeconfig came from, for {@link ClientProvenance}. */
|
|
38
|
+
export type KubeconfigSource = "explicit-string" | "explicit-path" | "default" | "in-cluster";
|
|
39
|
+
export interface LoadedKubeConfig {
|
|
40
|
+
kc: k8s.KubeConfig;
|
|
41
|
+
source: KubeconfigSource;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The kubeconfig this invocation should read, by the same precedence every
|
|
45
|
+
* entry point in this package uses: an explicit literal, then an explicit
|
|
46
|
+
* path, then the ambient default (`KUBECONFIG`, `~/.kube/config`, in-cluster).
|
|
47
|
+
*
|
|
48
|
+
* Throws whatever client-node throws for an explicit string or path — those
|
|
49
|
+
* are the caller naming a file, and a caller who names an unreadable one wants
|
|
50
|
+
* to hear about it. The ambient path never throws for a duplicate name.
|
|
51
|
+
*/
|
|
52
|
+
export declare function loadKubeConfig(mod: ClientNode, options?: {
|
|
53
|
+
kubeconfig?: string;
|
|
54
|
+
kubeconfigPath?: string;
|
|
55
|
+
}): LoadedKubeConfig;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=kubeconfig.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kubeconfig.d.ts","sourceRoot":"","sources":["../src/kubeconfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,KAAK,KAAK,GAAG,MAAM,yBAAyB,CAAC;AAEpD,KAAK,UAAU,GAAG,cAAc,yBAAyB,CAAC,CAAC;AAE3D,2EAA2E;AAC3E,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,GAAG,eAAe,GAAG,SAAS,GAAG,YAAY,CAAC;AAE9F,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,GAAG,CAAC,UAAU,CAAC;IACnB,MAAM,EAAE,gBAAgB,CAAC;CAC1B;AAwDD;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,UAAU,EACf,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7D,gBAAgB,CAqBlB"}
|
package/dist/types.d.ts
CHANGED
|
@@ -85,6 +85,25 @@ export interface ClientProvenance {
|
|
|
85
85
|
/** Where the kubeconfig itself came from. */
|
|
86
86
|
kubeconfigSource: "explicit-string" | "explicit-path" | "default" | "in-cluster";
|
|
87
87
|
}
|
|
88
|
+
/**
|
|
89
|
+
* One context of the merged kubeconfig, joined to the apiserver its cluster
|
|
90
|
+
* resolves to (chant #1630). Plain data read out of a file: no credential is
|
|
91
|
+
* touched, no exec plugin runs, no request is issued.
|
|
92
|
+
*/
|
|
93
|
+
export interface KubeconfigContextInfo {
|
|
94
|
+
name: string;
|
|
95
|
+
/** Cluster name the context names. Empty when the context declares none. */
|
|
96
|
+
cluster: string;
|
|
97
|
+
user?: string;
|
|
98
|
+
namespace?: string;
|
|
99
|
+
/** The apiserver URL the named cluster resolves to, when the config has one. */
|
|
100
|
+
server?: string;
|
|
101
|
+
}
|
|
102
|
+
/** What {@link import("./client.js").readKubeconfigView} returns. */
|
|
103
|
+
export interface KubeconfigView {
|
|
104
|
+
contexts: KubeconfigContextInfo[];
|
|
105
|
+
currentContext?: string;
|
|
106
|
+
}
|
|
88
107
|
/** Options for {@link import("./client.js").createK8sClient}. */
|
|
89
108
|
export interface K8sClientOptions {
|
|
90
109
|
/**
|
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.44.
|
|
3
|
+
"version": "0.44.6",
|
|
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.ts
CHANGED
|
@@ -47,6 +47,7 @@ import { assertExecCredentialAllowed, credentialPathOf, DEFAULT_EXEC_ALLOWLIST }
|
|
|
47
47
|
import { asFieldManagerConflict } from "./conflict";
|
|
48
48
|
import { assertValidFieldManager, CHANT_FIELD_MANAGER } from "./field-manager";
|
|
49
49
|
import { DEFAULT_CONCURRENCY, mapConcurrent } from "./concurrency";
|
|
50
|
+
import { loadKubeConfig } from "./kubeconfig";
|
|
50
51
|
import type {
|
|
51
52
|
ApiResourceInfo,
|
|
52
53
|
ClientProvenance,
|
|
@@ -56,6 +57,8 @@ import type {
|
|
|
56
57
|
RequestContextLike,
|
|
57
58
|
ResourceSelector,
|
|
58
59
|
ResponseContextLike,
|
|
60
|
+
KubeconfigContextInfo,
|
|
61
|
+
KubeconfigView,
|
|
59
62
|
} from "./types";
|
|
60
63
|
|
|
61
64
|
type ClientNode = typeof import("@kubernetes/client-node");
|
|
@@ -103,16 +106,64 @@ export async function isK8sClientAvailable(): Promise<boolean> {
|
|
|
103
106
|
export async function readAmbientContext(
|
|
104
107
|
options: Pick<K8sClientOptions, "kubeconfig" | "kubeconfigPath"> = {},
|
|
105
108
|
): Promise<string | undefined> {
|
|
106
|
-
const mod = await loadClientNode();
|
|
107
|
-
const kc = new mod.KubeConfig();
|
|
108
109
|
try {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
else kc.loadFromDefault();
|
|
110
|
+
const { kc } = loadKubeConfig(await loadClientNode(), options);
|
|
111
|
+
return kc.getCurrentContext() || undefined;
|
|
112
112
|
} catch {
|
|
113
113
|
return undefined;
|
|
114
114
|
}
|
|
115
|
-
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The merged kubeconfig's contexts and their apiservers, without building a
|
|
119
|
+
* client (chant #1630).
|
|
120
|
+
*
|
|
121
|
+
* A lens showing an operator which cluster the k8s half is being read from
|
|
122
|
+
* needs an address out of a file, not a client about to talk to one. Going
|
|
123
|
+
* through `createK8sClient` for that answer refuses in two places it should
|
|
124
|
+
* not: an auth plugin outside the exec allowlist throws, so a cluster whose
|
|
125
|
+
* address is sitting right there in the kubeconfig — and which kubectl reads
|
|
126
|
+
* without complaint — reports as no target at all; and a context resolving to
|
|
127
|
+
* no cluster throws where the caller wants "nothing to report". Both refusals
|
|
128
|
+
* are right for a client that is about to send a request and wrong for a
|
|
129
|
+
* config read. Enumerating also takes one client per context, and enumerating
|
|
130
|
+
* is exactly what a lens does.
|
|
131
|
+
*
|
|
132
|
+
* So: no credential touched, no exec plugin run, no request issued. Every
|
|
133
|
+
* failure — no kubeconfig, an unreadable one, `@kubernetes/client-node` not
|
|
134
|
+
* installed — is an empty result rather than a throw, because a lens on an
|
|
135
|
+
* aws-only project running on a machine that has never seen Kubernetes has
|
|
136
|
+
* nothing to report and nothing to fail about.
|
|
137
|
+
*
|
|
138
|
+
* `server` per context rather than a separate cluster map: joining the two is
|
|
139
|
+
* what every caller does anyway.
|
|
140
|
+
*/
|
|
141
|
+
export async function readKubeconfigView(
|
|
142
|
+
options: Pick<K8sClientOptions, "kubeconfig" | "kubeconfigPath"> = {},
|
|
143
|
+
): Promise<KubeconfigView> {
|
|
144
|
+
let kc;
|
|
145
|
+
try {
|
|
146
|
+
({ kc } = loadKubeConfig(await loadClientNode(), options));
|
|
147
|
+
} catch {
|
|
148
|
+
return { contexts: [] };
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const servers = new Map<string, string | undefined>();
|
|
152
|
+
for (const cluster of kc.getClusters()) servers.set(cluster.name, cluster.server || undefined);
|
|
153
|
+
const contexts: KubeconfigContextInfo[] = kc.getContexts().map((context) => {
|
|
154
|
+
const server = servers.get(context.cluster);
|
|
155
|
+
return {
|
|
156
|
+
name: context.name,
|
|
157
|
+
cluster: context.cluster ?? "",
|
|
158
|
+
...(context.user ? { user: context.user } : {}),
|
|
159
|
+
...(context.namespace ? { namespace: context.namespace } : {}),
|
|
160
|
+
...(server ? { server } : {}),
|
|
161
|
+
};
|
|
162
|
+
});
|
|
163
|
+
return { contexts, ...(kc.getCurrentContext() ? { currentContext: kc.getCurrentContext() } : {}) };
|
|
164
|
+
} catch {
|
|
165
|
+
return { contexts: [] };
|
|
166
|
+
}
|
|
116
167
|
}
|
|
117
168
|
|
|
118
169
|
/** Options for a single object read. */
|
|
@@ -268,18 +319,10 @@ interface RootDiscovery {
|
|
|
268
319
|
export async function createK8sClient(options: K8sClientOptions = {}): Promise<K8sClient> {
|
|
269
320
|
const mod = await loadClientNode();
|
|
270
321
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
kubeconfigSource = "explicit-string";
|
|
276
|
-
} else if (options.kubeconfigPath !== undefined) {
|
|
277
|
-
kc.loadFromFile(options.kubeconfigPath);
|
|
278
|
-
kubeconfigSource = "explicit-path";
|
|
279
|
-
} else {
|
|
280
|
-
kc.loadFromDefault();
|
|
281
|
-
kubeconfigSource = kc.getCurrentContext() === "inCluster" ? "in-cluster" : "default";
|
|
282
|
-
}
|
|
322
|
+
// kubectl's merge semantics, not client-node's (#1630): a KUBECONFIG list
|
|
323
|
+
// with overlapping names resolves first-wins instead of throwing, and
|
|
324
|
+
// current-context comes from the first file that sets one.
|
|
325
|
+
const { kc, source: kubeconfigSource } = loadKubeConfig(mod, options);
|
|
283
326
|
|
|
284
327
|
if (options.context !== undefined) {
|
|
285
328
|
if (!kc.getContextObject(options.context)) {
|
package/src/index.ts
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
export {
|
|
16
16
|
createK8sClient,
|
|
17
17
|
readAmbientContext,
|
|
18
|
+
readKubeconfigView,
|
|
18
19
|
loadClientNode,
|
|
19
20
|
isK8sClientAvailable,
|
|
20
21
|
apiVersionPath,
|
|
@@ -85,6 +86,8 @@ export type {
|
|
|
85
86
|
ClientProvenance,
|
|
86
87
|
CredentialPath,
|
|
87
88
|
K8sClientOptions,
|
|
89
|
+
KubeconfigContextInfo,
|
|
90
|
+
KubeconfigView,
|
|
88
91
|
K8sObject,
|
|
89
92
|
ObjectRef,
|
|
90
93
|
RequestContextLike,
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The kubeconfig read surface and kubectl's merge semantics — chant #1630.
|
|
3
|
+
*
|
|
4
|
+
* Every case writes its own kubeconfig files into a temp directory and points
|
|
5
|
+
* `KUBECONFIG` at them, so nothing reads the developer's real config and
|
|
6
|
+
* nothing contacts a cluster: these are file reads, and the point of the
|
|
7
|
+
* surface under test is that they stay file reads.
|
|
8
|
+
*
|
|
9
|
+
* Where kubectl is on PATH, `kubectl config view` is asked the same questions
|
|
10
|
+
* and its answers are asserted to match — the merge rules are only worth
|
|
11
|
+
* having because they are kubectl's, and a hardcoded expectation cannot prove
|
|
12
|
+
* that. Skipped (with the expectations still asserted on their own) where it
|
|
13
|
+
* is not installed.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { describe, test, expect, beforeEach, afterEach } from "vitest";
|
|
17
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { spawnSync } from "node:child_process";
|
|
19
|
+
import { tmpdir } from "node:os";
|
|
20
|
+
import { delimiter, join } from "node:path";
|
|
21
|
+
import { readAmbientContext, readKubeconfigView, createK8sClient } from "./client";
|
|
22
|
+
import { fakeKubeconfig, fakeRequestLayer } from "./testing";
|
|
23
|
+
|
|
24
|
+
/** A kubeconfig naming one cluster, one user and one context, with a chosen
|
|
25
|
+
* current-context — the material both merge divergences are reproduced with. */
|
|
26
|
+
function configYaml(opts: { cluster: string; server: string; context: string; user?: string }): string {
|
|
27
|
+
const user = opts.user ?? `user-${opts.context}`;
|
|
28
|
+
return [
|
|
29
|
+
"apiVersion: v1",
|
|
30
|
+
"kind: Config",
|
|
31
|
+
`current-context: ${opts.context}`,
|
|
32
|
+
"clusters:",
|
|
33
|
+
` - name: ${opts.cluster}`,
|
|
34
|
+
" cluster:",
|
|
35
|
+
` server: ${opts.server}`,
|
|
36
|
+
" insecure-skip-tls-verify: true",
|
|
37
|
+
"users:",
|
|
38
|
+
` - name: ${user}`,
|
|
39
|
+
" user:",
|
|
40
|
+
' token: "test-token"',
|
|
41
|
+
"contexts:",
|
|
42
|
+
` - name: ${opts.context}`,
|
|
43
|
+
" context:",
|
|
44
|
+
` cluster: ${opts.cluster}`,
|
|
45
|
+
` user: ${user}`,
|
|
46
|
+
"",
|
|
47
|
+
].join("\n");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const kubectlAvailable = spawnSync("kubectl", ["version", "--client=true"], { encoding: "utf8" }).status === 0;
|
|
51
|
+
|
|
52
|
+
/** `kubectl config view` against a KUBECONFIG list, or undefined without kubectl. */
|
|
53
|
+
function kubectlView(kubeconfig: string, args: string[]): string | undefined {
|
|
54
|
+
if (!kubectlAvailable) return undefined;
|
|
55
|
+
const r = spawnSync("kubectl", ["config", ...args], {
|
|
56
|
+
encoding: "utf8",
|
|
57
|
+
env: { ...process.env, KUBECONFIG: kubeconfig },
|
|
58
|
+
});
|
|
59
|
+
return r.status === 0 ? r.stdout.trim() : undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
let dir: string;
|
|
63
|
+
let savedKubeconfig: string | undefined;
|
|
64
|
+
|
|
65
|
+
beforeEach(() => {
|
|
66
|
+
dir = mkdtempSync(join(tmpdir(), "chant-kubeconfig-"));
|
|
67
|
+
savedKubeconfig = process.env.KUBECONFIG;
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
if (savedKubeconfig === undefined) delete process.env.KUBECONFIG;
|
|
72
|
+
else process.env.KUBECONFIG = savedKubeconfig;
|
|
73
|
+
rmSync(dir, { recursive: true, force: true });
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
function write(name: string, content: string): string {
|
|
77
|
+
const path = join(dir, name);
|
|
78
|
+
writeFileSync(path, content);
|
|
79
|
+
return path;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
describe("readKubeconfigView (#1630)", () => {
|
|
83
|
+
test("returns each context joined to its apiserver, plus the current context", async () => {
|
|
84
|
+
const view = await readKubeconfigView({
|
|
85
|
+
kubeconfig: fakeKubeconfig({
|
|
86
|
+
contexts: [
|
|
87
|
+
{ name: "ctx-a", cluster: "cluster-a", user: "user-a", namespace: "team" },
|
|
88
|
+
{ name: "ctx-b", cluster: "cluster-b", user: "user-b" },
|
|
89
|
+
],
|
|
90
|
+
currentContext: "ctx-b",
|
|
91
|
+
server: "https://cluster.test:6443",
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
expect(view.currentContext).toBe("ctx-b");
|
|
96
|
+
expect(view.contexts).toEqual([
|
|
97
|
+
{ name: "ctx-a", cluster: "cluster-a", user: "user-a", namespace: "team", server: "https://cluster.test:6443" },
|
|
98
|
+
{ name: "ctx-b", cluster: "cluster-b", user: "user-b", server: "https://cluster.test:6443" },
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("reads an address behind an auth plugin the client would refuse to run", async () => {
|
|
103
|
+
// `createK8sClient` throws ExecCredentialNotAllowedError here, correctly —
|
|
104
|
+
// it is about to run the plugin. Reading the address out of the file runs
|
|
105
|
+
// nothing, so refusing would report "no target" for a cluster kubectl
|
|
106
|
+
// reads without complaint.
|
|
107
|
+
const view = await readKubeconfigView({
|
|
108
|
+
kubeconfig: fakeKubeconfig({ exec: { command: "harvest-credentials" }, server: "https://plugin.test:6443" }),
|
|
109
|
+
});
|
|
110
|
+
expect(view.contexts).toHaveLength(1);
|
|
111
|
+
expect(view.contexts[0].server).toBe("https://plugin.test:6443");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("a context resolving to no cluster is reported without a server, not as a failure", async () => {
|
|
115
|
+
const view = await readKubeconfigView({
|
|
116
|
+
kubeconfig: [
|
|
117
|
+
"apiVersion: v1",
|
|
118
|
+
"kind: Config",
|
|
119
|
+
"current-context: dangling",
|
|
120
|
+
"clusters: []",
|
|
121
|
+
"users: []",
|
|
122
|
+
"contexts:",
|
|
123
|
+
" - name: dangling",
|
|
124
|
+
" context:",
|
|
125
|
+
" cluster: gone",
|
|
126
|
+
" user: nobody",
|
|
127
|
+
"",
|
|
128
|
+
].join("\n"),
|
|
129
|
+
});
|
|
130
|
+
expect(view.currentContext).toBe("dangling");
|
|
131
|
+
expect(view.contexts).toEqual([{ name: "dangling", cluster: "gone", user: "nobody" }]);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
test("an unreadable or absent kubeconfig is an empty view, never a throw", async () => {
|
|
135
|
+
await expect(readKubeconfigView({ kubeconfigPath: join(dir, "nope.yaml") })).resolves.toEqual({ contexts: [] });
|
|
136
|
+
await expect(readKubeconfigView({ kubeconfig: "%%% not yaml : [" })).resolves.toEqual({ contexts: [] });
|
|
137
|
+
// A machine that has never seen Kubernetes: KUBECONFIG points at nothing
|
|
138
|
+
// that exists, and a lens over an aws-only project still renders.
|
|
139
|
+
process.env.KUBECONFIG = [join(dir, "a.yaml"), join(dir, "b.yaml")].join(delimiter);
|
|
140
|
+
await expect(readKubeconfigView()).resolves.toEqual({ contexts: [] });
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("KUBECONFIG merge semantics match kubectl (#1630)", () => {
|
|
145
|
+
test("overlapping names resolve first-wins instead of failing", async () => {
|
|
146
|
+
// client-node's `mergeConfig` throws `Duplicate cluster: shared` here. A
|
|
147
|
+
// team-wide file plus a personal one both naming `dev` is the ordinary way
|
|
148
|
+
// people get into this, and kubectl keeps the first.
|
|
149
|
+
const a = write("a.yaml", configYaml({ cluster: "shared", server: "https://from-a:6443", context: "ctx-a" }));
|
|
150
|
+
const b = write("b.yaml", configYaml({ cluster: "shared", server: "https://from-b:6443", context: "ctx-b" }));
|
|
151
|
+
const kubeconfig = [a, b].join(delimiter);
|
|
152
|
+
process.env.KUBECONFIG = kubeconfig;
|
|
153
|
+
|
|
154
|
+
const view = await readKubeconfigView();
|
|
155
|
+
expect(view.contexts.map((c) => c.name)).toEqual(["ctx-a", "ctx-b"]);
|
|
156
|
+
// Both contexts name the cluster `shared`, and the first file's server is
|
|
157
|
+
// the one `shared` means.
|
|
158
|
+
expect(view.contexts.map((c) => c.server)).toEqual(["https://from-a:6443", "https://from-a:6443"]);
|
|
159
|
+
|
|
160
|
+
const kubectlServer = kubectlView(kubeconfig, [
|
|
161
|
+
"view",
|
|
162
|
+
"-o",
|
|
163
|
+
'jsonpath={.clusters[?(@.name=="shared")].cluster.server}',
|
|
164
|
+
]);
|
|
165
|
+
if (kubectlServer !== undefined) expect(kubectlServer).toBe("https://from-a:6443");
|
|
166
|
+
const kubectlContexts = kubectlView(kubeconfig, ["view", "-o", 'jsonpath={range .contexts[*]}{.name}{"\\n"}{end}']);
|
|
167
|
+
if (kubectlContexts !== undefined) expect(kubectlContexts.split("\n")).toEqual(["ctx-a", "ctx-b"]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
test("readAmbientContext answers for a duplicate-name list instead of reporting no kubeconfig", async () => {
|
|
171
|
+
// The load lives inside readAmbientContext's try/catch, so client-node's
|
|
172
|
+
// throw came out as `undefined` — chant read these users as having no
|
|
173
|
+
// kubeconfig at all when they have a perfectly good one.
|
|
174
|
+
const a = write("a.yaml", configYaml({ cluster: "shared", server: "https://from-a:6443", context: "ctx-a" }));
|
|
175
|
+
const b = write("b.yaml", configYaml({ cluster: "shared", server: "https://from-b:6443", context: "ctx-b" }));
|
|
176
|
+
process.env.KUBECONFIG = [a, b].join(delimiter);
|
|
177
|
+
|
|
178
|
+
await expect(readAmbientContext()).resolves.toBe("ctx-a");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
test("current-context comes from the FIRST file that sets one, not the last", async () => {
|
|
182
|
+
const a = write("a.yaml", configYaml({ cluster: "cluster-a", server: "https://from-a:6443", context: "ctx-a" }));
|
|
183
|
+
const c = write("c.yaml", configYaml({ cluster: "cluster-c", server: "https://from-c:6443", context: "ctx-c" }));
|
|
184
|
+
const kubeconfig = [a, c].join(delimiter);
|
|
185
|
+
process.env.KUBECONFIG = kubeconfig;
|
|
186
|
+
|
|
187
|
+
// client-node's mergeConfig overwrites currentContext from each later
|
|
188
|
+
// file, so the bare loadFromDefault() answered `ctx-c` — the opposite of
|
|
189
|
+
// every `kubectl get` the operator ran to check.
|
|
190
|
+
await expect(readAmbientContext()).resolves.toBe("ctx-a");
|
|
191
|
+
expect((await readKubeconfigView()).currentContext).toBe("ctx-a");
|
|
192
|
+
|
|
193
|
+
const kubectlCurrent = kubectlView(kubeconfig, ["current-context"]);
|
|
194
|
+
if (kubectlCurrent !== undefined) expect(kubectlCurrent).toBe("ctx-a");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("a file the list names but cannot be read is skipped, not fatal", async () => {
|
|
198
|
+
const a = write("a.yaml", configYaml({ cluster: "cluster-a", server: "https://from-a:6443", context: "ctx-a" }));
|
|
199
|
+
process.env.KUBECONFIG = [join(dir, "missing.yaml"), a].join(delimiter);
|
|
200
|
+
|
|
201
|
+
const view = await readKubeconfigView();
|
|
202
|
+
expect(view.currentContext).toBe("ctx-a");
|
|
203
|
+
expect(view.contexts.map((c) => c.server)).toEqual(["https://from-a:6443"]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("createK8sClient binds the cluster kubectl would bind", async () => {
|
|
207
|
+
const a = write("a.yaml", configYaml({ cluster: "shared", server: "https://from-a:6443", context: "ctx-a" }));
|
|
208
|
+
const b = write("b.yaml", configYaml({ cluster: "shared", server: "https://from-b:6443", context: "ctx-b" }));
|
|
209
|
+
process.env.KUBECONFIG = [a, b].join(delimiter);
|
|
210
|
+
|
|
211
|
+
// Constructing the client issues no request; the layer is here so nothing
|
|
212
|
+
// could reach a network if it tried.
|
|
213
|
+
const client = await createK8sClient({ requestLayer: fakeRequestLayer(() => ({ body: {} })) });
|
|
214
|
+
expect(client.provenance.server).toBe("https://from-a:6443");
|
|
215
|
+
expect(client.provenance.context).toBe("ctx-a");
|
|
216
|
+
expect(client.provenance.kubeconfigSource).toBe("default");
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
test("an explicit kubeconfig string or path is untouched by the list", async () => {
|
|
220
|
+
const a = write("a.yaml", configYaml({ cluster: "cluster-a", server: "https://from-a:6443", context: "ctx-a" }));
|
|
221
|
+
const explicit = write("explicit.yaml", configYaml({ cluster: "cluster-x", server: "https://from-x:6443", context: "ctx-x" }));
|
|
222
|
+
process.env.KUBECONFIG = a;
|
|
223
|
+
|
|
224
|
+
expect((await readKubeconfigView({ kubeconfigPath: explicit })).currentContext).toBe("ctx-x");
|
|
225
|
+
await expect(readAmbientContext({ kubeconfigPath: explicit })).resolves.toBe("ctx-x");
|
|
226
|
+
expect(
|
|
227
|
+
(await readKubeconfigView({ kubeconfig: configYaml({ cluster: "c", server: "https://s:6443", context: "ctx-lit" }) }))
|
|
228
|
+
.currentContext,
|
|
229
|
+
).toBe("ctx-lit");
|
|
230
|
+
});
|
|
231
|
+
});
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loading the kubeconfig the way kubectl loads it — chant #1630.
|
|
3
|
+
*
|
|
4
|
+
* `@kubernetes/client-node` parses a kubeconfig faithfully and merges a
|
|
5
|
+
* `KUBECONFIG` path list on two rules that are not kubectl's. Both are
|
|
6
|
+
* reproducible with two files and no cluster (checked against client-node
|
|
7
|
+
* 1.4.0), and both make chant bind a different cluster than the `kubectl get`
|
|
8
|
+
* the operator ran to check:
|
|
9
|
+
*
|
|
10
|
+
* - **Duplicate names throw.** `mergeConfig` funnels every entry through
|
|
11
|
+
* `addCluster`/`addUser`/`addContext`, each of which throws `Duplicate
|
|
12
|
+
* cluster: <name>` on a name it already holds. kubectl's documented rule is
|
|
13
|
+
* the opposite — keep the first, ignore later ones, never fail. Overlapping
|
|
14
|
+
* names across a list are ordinary: a team-wide file plus a personal one
|
|
15
|
+
* both naming `dev` is the usual way people get there. Today that throw is
|
|
16
|
+
* swallowed by `readAmbientContext`'s try/catch, so chant reads those users
|
|
17
|
+
* as having no kubeconfig at all.
|
|
18
|
+
* - **current-context is last-wins.** `loadFromDefault(opts,
|
|
19
|
+
* contextFromStartingConfig)` defaults the second argument to `false`, and
|
|
20
|
+
* `mergeConfig` then overwrites `currentContext` from each later file.
|
|
21
|
+
* kubectl takes the FIRST file that sets one.
|
|
22
|
+
*
|
|
23
|
+
* So the multi-file case is merged here instead: one `KubeConfig` per file
|
|
24
|
+
* (which is also what keeps each file's relative certificate paths resolved
|
|
25
|
+
* against its own directory), then a first-wins union by name and the first
|
|
26
|
+
* `current-context` that appears. Everything else — the single-file path, the
|
|
27
|
+
* `~/.kube/config` fallback, in-cluster credentials — stays client-node's
|
|
28
|
+
* `loadFromDefault`, with the one-word fix that gives it kubectl's
|
|
29
|
+
* current-context.
|
|
30
|
+
*
|
|
31
|
+
* A file in the list that cannot be read is skipped rather than fatal, which
|
|
32
|
+
* is also kubectl's rule: a stale entry in a shared `KUBECONFIG` export must
|
|
33
|
+
* not take the whole config down with it.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { delimiter } from "node:path";
|
|
37
|
+
import type * as k8s from "@kubernetes/client-node";
|
|
38
|
+
|
|
39
|
+
type ClientNode = typeof import("@kubernetes/client-node");
|
|
40
|
+
|
|
41
|
+
/** Where the loaded kubeconfig came from, for {@link ClientProvenance}. */
|
|
42
|
+
export type KubeconfigSource = "explicit-string" | "explicit-path" | "default" | "in-cluster";
|
|
43
|
+
|
|
44
|
+
export interface LoadedKubeConfig {
|
|
45
|
+
kc: k8s.KubeConfig;
|
|
46
|
+
source: KubeconfigSource;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The `KUBECONFIG` entries, in order. Empty when the variable is unset. */
|
|
50
|
+
function kubeconfigPathList(): string[] {
|
|
51
|
+
const raw = process.env.KUBECONFIG;
|
|
52
|
+
if (!raw) return [];
|
|
53
|
+
return raw.split(delimiter).filter((f) => f.length > 0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Merge a `KUBECONFIG` path list on kubectl's rules: first-wins for every
|
|
58
|
+
* cluster/user/context name, and the first `current-context` that any file
|
|
59
|
+
* sets. Unreadable files are skipped.
|
|
60
|
+
*/
|
|
61
|
+
function mergeKubeconfigFiles(mod: ClientNode, files: readonly string[]): k8s.KubeConfig {
|
|
62
|
+
const clusters: k8s.Cluster[] = [];
|
|
63
|
+
const users: k8s.User[] = [];
|
|
64
|
+
const contexts: k8s.Context[] = [];
|
|
65
|
+
const seenClusters = new Set<string>();
|
|
66
|
+
const seenUsers = new Set<string>();
|
|
67
|
+
const seenContexts = new Set<string>();
|
|
68
|
+
let currentContext: string | undefined;
|
|
69
|
+
|
|
70
|
+
for (const file of files) {
|
|
71
|
+
const part = new mod.KubeConfig();
|
|
72
|
+
try {
|
|
73
|
+
part.loadFromFile(file);
|
|
74
|
+
} catch {
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
for (const cluster of part.getClusters()) {
|
|
78
|
+
if (seenClusters.has(cluster.name)) continue;
|
|
79
|
+
seenClusters.add(cluster.name);
|
|
80
|
+
clusters.push(cluster);
|
|
81
|
+
}
|
|
82
|
+
for (const user of part.getUsers()) {
|
|
83
|
+
if (seenUsers.has(user.name)) continue;
|
|
84
|
+
seenUsers.add(user.name);
|
|
85
|
+
users.push(user);
|
|
86
|
+
}
|
|
87
|
+
for (const context of part.getContexts()) {
|
|
88
|
+
if (seenContexts.has(context.name)) continue;
|
|
89
|
+
seenContexts.add(context.name);
|
|
90
|
+
contexts.push(context);
|
|
91
|
+
}
|
|
92
|
+
if (currentContext === undefined) {
|
|
93
|
+
const current = part.getCurrentContext();
|
|
94
|
+
if (current) currentContext = current;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const kc = new mod.KubeConfig();
|
|
99
|
+
kc.loadFromOptions({ clusters, users, contexts, currentContext });
|
|
100
|
+
return kc;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The kubeconfig this invocation should read, by the same precedence every
|
|
105
|
+
* entry point in this package uses: an explicit literal, then an explicit
|
|
106
|
+
* path, then the ambient default (`KUBECONFIG`, `~/.kube/config`, in-cluster).
|
|
107
|
+
*
|
|
108
|
+
* Throws whatever client-node throws for an explicit string or path — those
|
|
109
|
+
* are the caller naming a file, and a caller who names an unreadable one wants
|
|
110
|
+
* to hear about it. The ambient path never throws for a duplicate name.
|
|
111
|
+
*/
|
|
112
|
+
export function loadKubeConfig(
|
|
113
|
+
mod: ClientNode,
|
|
114
|
+
options: { kubeconfig?: string; kubeconfigPath?: string } = {},
|
|
115
|
+
): LoadedKubeConfig {
|
|
116
|
+
if (options.kubeconfig !== undefined) {
|
|
117
|
+
const kc = new mod.KubeConfig();
|
|
118
|
+
kc.loadFromString(options.kubeconfig);
|
|
119
|
+
return { kc, source: "explicit-string" };
|
|
120
|
+
}
|
|
121
|
+
if (options.kubeconfigPath !== undefined) {
|
|
122
|
+
const kc = new mod.KubeConfig();
|
|
123
|
+
kc.loadFromFile(options.kubeconfigPath);
|
|
124
|
+
return { kc, source: "explicit-path" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const files = kubeconfigPathList();
|
|
128
|
+
if (files.length > 1) return { kc: mergeKubeconfigFiles(mod, files), source: "default" };
|
|
129
|
+
|
|
130
|
+
const kc = new mod.KubeConfig();
|
|
131
|
+
// `true` is the whole current-context fix for the single-file and
|
|
132
|
+
// `~/.kube/config` paths: it tells client-node to keep the starting config's
|
|
133
|
+
// context rather than letting a later merge overwrite it.
|
|
134
|
+
kc.loadFromDefault(undefined, true);
|
|
135
|
+
return { kc, source: kc.getCurrentContext() === "inCluster" ? "in-cluster" : "default" };
|
|
136
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -95,6 +95,27 @@ export interface ClientProvenance {
|
|
|
95
95
|
kubeconfigSource: "explicit-string" | "explicit-path" | "default" | "in-cluster";
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
/**
|
|
99
|
+
* One context of the merged kubeconfig, joined to the apiserver its cluster
|
|
100
|
+
* resolves to (chant #1630). Plain data read out of a file: no credential is
|
|
101
|
+
* touched, no exec plugin runs, no request is issued.
|
|
102
|
+
*/
|
|
103
|
+
export interface KubeconfigContextInfo {
|
|
104
|
+
name: string;
|
|
105
|
+
/** Cluster name the context names. Empty when the context declares none. */
|
|
106
|
+
cluster: string;
|
|
107
|
+
user?: string;
|
|
108
|
+
namespace?: string;
|
|
109
|
+
/** The apiserver URL the named cluster resolves to, when the config has one. */
|
|
110
|
+
server?: string;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** What {@link import("./client").readKubeconfigView} returns. */
|
|
114
|
+
export interface KubeconfigView {
|
|
115
|
+
contexts: KubeconfigContextInfo[];
|
|
116
|
+
currentContext?: string;
|
|
117
|
+
}
|
|
118
|
+
|
|
98
119
|
/** Options for {@link import("./client").createK8sClient}. */
|
|
99
120
|
export interface K8sClientOptions {
|
|
100
121
|
/**
|