@@ -229,92 +229,339 @@ The `InClusterConfig()` flow specifically:
229229
230230# # Future Work
231231
232- # ## Kubeconfig Support
232+ The following sections describe features that client-go provides which we plan to port to kubernetesjs. Each section includes the client-go reference, proposed TypeScript API, and implementation notes.
233233
234- Parsing `~/.kube/config` (or `$KUBECONFIG`) to extract cluster endpoints, contexts, and user credentials. This would enable :
234+ # ## Priority 1: Kubeconfig Support
235+
236+ **client-go reference**: `clientcmd.LoadFromFile()`, `ClientConfigLoadingRules`, `DirectClientConfig`
237+
238+ Parsing `~/.kube/config` (or `$KUBECONFIG`) to extract cluster endpoints, contexts, and user credentials.
239+
240+ **Kubeconfig YAML structure** (all fields we need to parse):
241+
242+ ` ` ` yaml
243+ apiVersion: v1
244+ kind: Config
245+ current-context: my-context
246+ clusters:
247+ - name: my-cluster
248+ cluster:
249+ server: https://api.example.com:6443
250+ certificate-authority: /path/to/ca.crt # or inline:
251+ certificate-authority-data: <base64-pem>
252+ insecure-skip-tls-verify: false
253+ tls-server-name: custom-sni.example.com
254+ proxy-url: socks5://proxy:1080
255+ users:
256+ - name: my-user
257+ user:
258+ token: static-bearer-token
259+ tokenFile: /path/to/token
260+ client-certificate: /path/to/cert.crt # or inline:
261+ client-certificate-data: <base64-pem>
262+ client-key: /path/to/key.pem # or inline:
263+ client-key-data: <base64-pem>
264+ username: basic-auth-user
265+ password: basic-auth-pass
266+ exec: # exec-based plugin
267+ command: gke-gcloud-auth-plugin
268+ args: ["--flag"]
269+ env: [{name: "FOO", value: "bar"}]
270+ apiVersion: client.authentication.k8s.io/v1
271+ provideClusterInfo: true
272+ installHint: "Install via: gcloud components install gke-gcloud-auth-plugin"
273+ auth-provider: # OIDC/OAuth2
274+ name: oidc
275+ config:
276+ idp-issuer-url: https://accounts.google.com
277+ client-id: my-client-id
278+ contexts:
279+ - name: my-context
280+ context:
281+ cluster: my-cluster
282+ user: my-user
283+ namespace: default
284+ ` ` `
285+
286+ **Proposed API**:
235287
236288` ` ` typescript
237- // Future API
289+ // Load from default location (~/.kube/config or $KUBECONFIG)
290+ const client = new KubernetesClient(KubernetesClient.fromKubeConfig());
291+
292+ // Load from specific file with context override
238293const client = new KubernetesClient(
239- KubernetesClient.fromKubeConfig({ context: 'my-cluster' })
294+ KubernetesClient.fromKubeConfig({
295+ kubeconfig: '/path/to/config',
296+ context: 'staging-cluster',
297+ })
240298);
241299` ` `
242300
243- **Challenge**: Requires a YAML parser. Options include adding `js-yaml` as an optional peer dependency, or implementing minimal YAML parsing for the kubeconfig subset.
301+ **client-go merge rules** (to implement):
302+ - ` KUBECONFIG` can list multiple files separated by `:` (Unix) or `;` (Windows)
303+ - Map entries (clusters, users, contexts) : **first file wins**
304+ - Scalar values (current-context) : **last file wins**
305+ - Relative paths resolved against each file's directory
306+ - If no kubeconfig found, fall back to in-cluster config
307+
308+ **Implementation notes**:
309+ - Requires a YAML parser — options : ` js-yaml` as optional peer dep, or minimal JSON-subset parser for kubeconfig
310+ - Should support both file paths and inline data (`-data` fields are base64-encoded PEM)
311+ - The `KUBECONFIG` env var handling should match client-go exactly
244312
245- # ## TLS / CA Certificate Handling
313+ # ## Priority 2: TLS / CA Certificate Handling
246314
247- Native support for custom CA certificates without relying on `NODE_EXTRA_CA_CERTS`. This would require creating a custom `fetch` agent with TLS options :
315+ **client-go reference**: `TLSClientConfig`, `transport.TLSConfigFor()`
316+
317+ client-go builds a `tls.Config` with :
318+ - ` RootCAs` from `CAFile`/`CAData` for server verification
319+ - Minimum TLS 1.2
320+ - Optional `InsecureSkipVerify` for dev
321+ - SNI via `ServerName`
322+
323+ **Proposed API**:
248324
249325` ` ` typescript
250- // Future API
251326const client = new KubernetesClient({
252327 restEndpoint: 'https://my-cluster:6443',
253328 token: myToken,
254329 tls: {
255330 caFile: '/path/to/ca.crt',
256- // or caData: Buffer.from('...')
257- insecureSkipVerify: false, // for dev only
331+ caData: '<base64-pem>', // inline CA (overrides caFile)
332+ insecureSkipVerify: false, // for dev only
333+ serverName: 'custom-sni.example.com',
258334 }
259335});
260336` ` `
261337
262- # ## Client Certificate Auth (mTLS)
338+ **Implementation notes**:
339+ - Node.js `fetch` (undici) supports custom `dispatcher` with TLS options as of Node 18.x
340+ - For the `ca` option, use `node:tls` to create a custom agent
341+ - ` NODE_EXTRA_CA_CERTS` remains the simplest approach for in-cluster; native TLS config is for advanced use cases
342+ - Browser environments don't support custom CA certs — document this limitation
343+
344+ # ## Priority 3: Token File Watching
345+
346+ **client-go reference**: `BearerTokenFile` field, `BearerAuthWithRefreshRoundTripper`
347+
348+ In client-go, when `BearerTokenFile` is set, the token is re-read from disk on every request. This supports Kubernetes bound service account tokens which rotate automatically (default expiry : 1 hour).
349+
350+ **Proposed API**:
351+
352+ ` ` ` typescript
353+ const client = new KubernetesClient({
354+ restEndpoint: 'https://...',
355+ tokenFile: '/var/run/secrets/kubernetes.io/serviceaccount/token',
356+ // Token is re-read from file before each request
357+ });
358+ ` ` `
359+
360+ **Implementation notes**:
361+ - Read token on each `request()` call (like client-go's round tripper)
362+ - Cache the token + file mtime to avoid unnecessary reads
363+ - Fall back to static `token` if `tokenFile` read fails after initial success
364+ - ` tokenFile` takes precedence over `token` (matching client-go behavior)
365+
366+ # ## Priority 4: Client Certificate Auth (mTLS)
367+
368+ **client-go reference**: `TLSClientConfig.CertFile/CertData/KeyFile/KeyData`
369+
370+ Used when kubeconfig specifies `client-certificate` and `client-key`.
263371
264372` ` ` typescript
265- // Future API
266373const client = new KubernetesClient({
267374 restEndpoint: 'https://my-cluster:6443',
268375 tls: {
269376 certFile: '/path/to/client.crt',
377+ certData: '<base64-pem>',
270378 keyFile: '/path/to/client.key',
379+ keyData: '<base64-pem>',
271380 caFile: '/path/to/ca.crt',
272381 }
273382});
274383` ` `
275384
276- # ## Token File Watching
385+ **Implementation notes**:
386+ - Requires `node:https` Agent with `cert`/`key`/`ca` options
387+ - Inline data (`-Data` fields) takes precedence over file paths (matching client-go)
388+ - Certificate rotation : client-go uses `GetClientCertificate` callback to reload certs, with a 5-minute refresh interval
389+
390+ # ## Priority 5: Exec-Based Credential Plugins
391+
392+ **client-go reference**: `ExecProvider`, `exec.Authenticator`
393+
394+ This is how GKE, EKS, and AKS authenticate — an external command produces credentials.
395+
396+ **Protocol**:
397+ 1. Client executes the command with `KUBERNETES_EXEC_INFO` env var containing a JSON `ExecCredential` object
398+ 2. Plugin outputs a JSON `ExecCredential` with `status.token` or `status.clientCertificateData`/`status.clientKeyData`
399+ 3. Client caches credentials until `status.expirationTimestamp`
400+
401+ **ExecCredential format**:
402+
403+ ` ` ` json
404+ {
405+ "apiVersion": "client.authentication.k8s.io/v1",
406+ "kind": "ExecCredential",
407+ "spec": {
408+ "interactive": false,
409+ "cluster": {
410+ "server": "https://...",
411+ "certificateAuthorityData": "...",
412+ "config": null
413+ }
414+ },
415+ "status": {
416+ "token": "the-bearer-token",
417+ "expirationTimestamp": "2024-01-01T00:00:00Z"
418+ }
419+ }
420+ ` ` `
277421
278- Automatic re-reading of the SA token file on each request (or periodically), supporting Kubernetes bound service account token rotation :
422+ **Proposed API** :
279423
280424` ` ` typescript
281- // Future API
282425const client = new KubernetesClient({
283426 restEndpoint: 'https://...',
284- tokenFile: '/var/run/secrets/kubernetes.io/serviceaccount/token',
285- // Token is re-read from file before each request
427+ exec: {
428+ command: 'gke-gcloud-auth-plugin',
429+ args: ['--flag'],
430+ env: [{ name: 'FOO', value: 'bar' }],
431+ apiVersion: 'client.authentication.k8s.io/v1',
432+ provideClusterInfo: true,
433+ installHint: 'Install via: gcloud components install gke-gcloud-auth-plugin',
434+ }
286435});
287436` ` `
288437
289- # ## Exec-Based Credential Plugins
438+ **Implementation notes**:
439+ - Use `node:child_process.execFile` (not `exec`, to prevent shell injection)
440+ - Cache credentials based on `expirationTimestamp`
441+ - Show `installHint` when command is not found
442+ - `provideClusterInfo : true` passes cluster server + CA data to the plugin via `KUBERNETES_EXEC_INFO`
443+
444+ # ## Priority 6: Watch / Streaming Connections
290445
291- Support for external credential commands (used by GKE, EKS, AKS) :
446+ **client-go reference**: `Watcher`, `Reflector`, `SharedInformer`
447+
448+ client-go's watch system :
449+ 1. Makes HTTP GET with `?watch=true` query param
450+ 2. API server streams line-delimited JSON `WatchEvent` objects
451+ 3. Each event has `type` (ADDED/MODIFIED/DELETED/BOOKMARK/ERROR) and `object`
452+ 4. On disconnect, reconnects using `resourceVersion` to resume
453+ 5. `SharedInformer` provides a local cache with event handlers
454+
455+ **Proposed API**:
292456
293457` ` ` typescript
294- // Future API
295- const client = new KubernetesClient({
296- restEndpoint: 'https://...',
297- exec: {
298- command: 'gke-gcloud-auth-plugin',
299- apiVersion: 'client.authentication.k8s.io/v1beta1',
300- }
458+ // Low-level watch (returns async iterator)
459+ const watcher = await client.watchCoreV1NamespacedPod({
460+ path: { namespace: 'default' },
461+ query: { resourceVersion: '12345' },
301462});
463+
464+ for await (const event of watcher) {
465+ console.log(event.type, event.object.metadata.name);
466+ // event.type: 'ADDED' | 'MODIFIED' | 'DELETED' | 'BOOKMARK' | 'ERROR'
467+ }
468+
469+ // High-level informer (future)
470+ const informer = client.inform('v1', 'pods', 'default');
471+ informer.on('add', (pod) => { ... });
472+ informer.on('update', (oldPod, newPod) => { ... });
473+ informer.on('delete', (pod) => { ... });
474+ await informer.start();
302475` ` `
303476
304- # ## OIDC / Auth Provider Plugins
477+ **Implementation notes**:
478+ - Use `fetch` with streaming response body (`response.body` is a `ReadableStream`)
479+ - Parse line-delimited JSON using a `TransformStream` or manual line splitting
480+ - Track `resourceVersion` from each event for reconnection
481+ - Watch requests should NOT be rate-limited (matching client-go)
482+ - Implement reconnection with exponential backoff
483+ - `SharedInformer` pattern : single watch connection + local cache + event distribution
305484
306- OpenID Connect token authentication for identity-based access.
485+ # ## Priority 7: Impersonation
307486
308- # ## Impersonation
487+ **client-go reference**: `Impersonate` in `rest.Config`
309488
310489` ` ` typescript
311- // Future API
312490const client = new KubernetesClient({
313491 restEndpoint: 'https://...',
314492 token: adminToken,
315493 impersonate: {
316494 user: 'jane@example.com',
317- groups: ['developers'],
495+ uid: '12345',
496+ groups: ['developers', 'qa'],
497+ extra: { 'scopes': ['view', 'edit'] },
318498 }
319499});
320500` ` `
501+
502+ Injects headers : ` Impersonate-User` , `Impersonate-Uid`, `Impersonate-Group`, `Impersonate-Extra-<key>`.
503+
504+ # ## Priority 8: Rate Limiting & Retry
505+
506+ **client-go reference**: `QPS`/`Burst` fields, `BackoffManager`, `Retry-After` handling
507+
508+ client-go defaults : QPS=5, Burst=10. Uses token bucket rate limiter. Retries on 429 and 5xx with `Retry-After` header respect.
509+
510+ **Proposed API**:
511+
512+ ` ` ` typescript
513+ const client = new KubernetesClient({
514+ restEndpoint: 'https://...',
515+ rateLimiter: {
516+ qps: 5,
517+ burst: 10,
518+ },
519+ retry: {
520+ maxRetries: 3,
521+ retryOn: [429, 500, 502, 503, 504],
522+ respectRetryAfter: true,
523+ }
524+ });
525+ ` ` `
526+
527+ # ## Priority 9: Connection Pooling & HTTP/2
528+
529+ **client-go reference**: `http.Transport` settings
530+
531+ client-go defaults :
532+ - `MaxIdleConnsPerHost` : 25
533+ - `IdleConnTimeout` : 90s (Go default)
534+ - HTTP/2 enabled by default via ALPN `["h2", "http/1.1"]`
535+
536+ In Node.js, `fetch` (undici) handles connection pooling internally. HTTP/2 support depends on the Node.js version and server configuration. These settings would be exposed via the custom dispatcher/agent.
537+
538+ # ## Priority 10: Patch Helpers
539+
540+ **client-go reference**: `types.JSONPatchType`, `types.MergePatchType`, `types.StrategicMergePatchType`, `types.ApplyPatchType`
541+
542+ Kubernetes supports four patch strategies :
543+ - **JSON Patch** (`application/json-patch+json`) — RFC 6902 operations array
544+ - **Merge Patch** (`application/merge-patch+json`) — partial JSON merge
545+ - **Strategic Merge Patch** (`application/strategic-merge-patch+json`) — K8s-aware list merging
546+ - **Server-Side Apply** (`application/apply-patch+yaml`) — field ownership management
547+
548+ Currently kubernetesjs uses `Content-Type : application/json` for PATCH. We should support setting the patch type:
549+
550+ ` ` ` typescript
551+ await client.patchCoreV1NamespacedPod({
552+ path: { namespace: 'default', name: 'my-pod' },
553+ body: [{ op: 'replace', path: '/metadata/labels/app', value: 'new-value' }],
554+ }, { patchType: 'json' });
555+
556+ // Server-side apply
557+ await client.patchCoreV1NamespacedDeployment({
558+ path: { namespace: 'default', name: 'my-deploy' },
559+ body: deploymentManifest,
560+ query: { fieldManager: 'my-controller', force: true },
561+ }, { patchType: 'apply' });
562+ ` ` `
563+
564+ # ## Not Planned
565+
566+ - **Basic auth** (`username`/`password`) — deprecated in Kubernetes, removed from most distributions
567+ - **OIDC auth provider plugin** — deprecated in client-go in favor of exec-based plugins
0 commit comments