From cc4cbe944a53f022785d2f43346cf0e4ff9505fd Mon Sep 17 00:00:00 2001 From: David Gamero Date: Tue, 13 Jan 2026 18:37:27 -0500 Subject: [PATCH 1/4] user agent --- src/config.ts | 9 +++++++ src/config_test.ts | 62 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/src/config.ts b/src/config.ts index b625642c41..5d5b283a3b 100644 --- a/src/config.ts +++ b/src/config.ts @@ -36,11 +36,19 @@ import WebSocket from 'isomorphic-ws'; import child_process from 'node:child_process'; import { SocksProxyAgent } from 'socks-proxy-agent'; import { HttpProxyAgent, HttpProxyAgentOptions, HttpsProxyAgent, HttpsProxyAgentOptions } from 'hpagent'; +import packagejson from '../package.json' with { type: 'json' }; +import { setHeaderMiddleware } from './middleware.js'; const SERVICEACCOUNT_ROOT: string = '/var/run/secrets/kubernetes.io/serviceaccount'; const SERVICEACCOUNT_CA_PATH: string = SERVICEACCOUNT_ROOT + '/ca.crt'; const SERVICEACCOUNT_TOKEN_PATH: string = SERVICEACCOUNT_ROOT + '/token'; const SERVICEACCOUNT_NAMESPACE_PATH: string = SERVICEACCOUNT_ROOT + '/namespace'; +const USER_AGENT_KEY = 'User-Agent'; + +function getUserAgent(): string { + const version = packagejson.version || ''; + return `kubernetes-javascript-client/${version}`; +} // fs.existsSync was removed in node 10 function fileExists(filepath: string): boolean { @@ -491,6 +499,7 @@ export class KubeConfig implements SecurityAuthentication { const config: Configuration = createConfiguration({ baseServer: baseServerConfig, authMethods: authConfig, + middleware: [setHeaderMiddleware(USER_AGENT_KEY, getUserAgent())], }); const apiClient = new apiClientType(config); diff --git a/src/config_test.ts b/src/config_test.ts index aab45ebbfe..f8e70f64da 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -1674,6 +1674,68 @@ describe('KubeConfig', () => { const client = kc.makeApiClient(CoreV1Api); strictEqual(client instanceof CoreV1Api, true); }); + + it('should include User-Agent header with version', async () => { + let capturedUserAgent: string | undefined; + + const { server, host, port } = await createTestHttpsServer((req, res) => { + capturedUserAgent = req.headers['user-agent']; + + res.setHeader('Content-Type', 'application/json'); + res.writeHead(200); + res.end( + JSON.stringify({ + apiVersion: 'v1', + kind: 'NamespaceList', + items: [], + }), + ); + }); + + try { + const kc = new KubeConfig(); + kc.loadFromClusterAndUser( + { + name: 'test-cluster', + server: `https://${host}:${port}`, + skipTLSVerify: true, + } as Cluster, + { + name: 'test-user', + token: 'test-token', + } as User, + ); + + const coreV1Api = kc.makeApiClient(CoreV1Api); + await coreV1Api.listNamespace(); + + // Read version from package.json + const packageJsonPath = join(__dirname, '..', 'package.json'); + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + const expectedVersion = packageJson.version; + + // Verify version is not blank + strictEqual(typeof expectedVersion, 'string'); + strictEqual(expectedVersion.length > 0, true, 'package.json version should not be blank'); + + // Verify User-Agent header contains client name and version + strictEqual(typeof capturedUserAgent, 'string'); + strictEqual( + capturedUserAgent?.startsWith('kubernetes-javascript-client/'), + true, + 'capturedUserAgent should start with "kubernetes-javascript-client/"', + ); + strictEqual( + capturedUserAgent?.includes(expectedVersion), + true, + `User-Agent should include version ${expectedVersion}`, + ); + } finally { + await new Promise((resolve) => { + server.close(() => resolve()); + }); + } + }); }); describe('EmptyConfig', () => { From c0dbb15e924a6c4a0a4d4808dccdde694cabcf66 Mon Sep 17 00:00:00 2001 From: David Gamero Date: Wed, 14 Jan 2026 01:20:22 -0500 Subject: [PATCH 2/4] feedback from cjihrig --- src/config.ts | 2 +- src/config_test.ts | 75 +++++++++++++++++++++++----------------------- 2 files changed, 38 insertions(+), 39 deletions(-) diff --git a/src/config.ts b/src/config.ts index 5d5b283a3b..c0100e78f7 100644 --- a/src/config.ts +++ b/src/config.ts @@ -46,7 +46,7 @@ const SERVICEACCOUNT_NAMESPACE_PATH: string = SERVICEACCOUNT_ROOT + '/namespace' const USER_AGENT_KEY = 'User-Agent'; function getUserAgent(): string { - const version = packagejson.version || ''; + const version = packagejson.version ?? ''; return `kubernetes-javascript-client/${version}`; } diff --git a/src/config_test.ts b/src/config_test.ts index f8e70f64da..25a9d68308 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -1,4 +1,4 @@ -import { after, before, beforeEach, describe, it, mock } from 'node:test'; +import { after, before, beforeEach, describe, it, mock, TestContext } from 'node:test'; import assert, { deepEqual, deepStrictEqual, @@ -1675,7 +1675,7 @@ describe('KubeConfig', () => { strictEqual(client instanceof CoreV1Api, true); }); - it('should include User-Agent header with version', async () => { + it('should include User-Agent header with version', async (t: TestContext) => { let capturedUserAgent: string | undefined; const { server, host, port } = await createTestHttpsServer((req, res) => { @@ -1692,49 +1692,48 @@ describe('KubeConfig', () => { ); }); - try { - const kc = new KubeConfig(); - kc.loadFromClusterAndUser( - { - name: 'test-cluster', - server: `https://${host}:${port}`, - skipTLSVerify: true, - } as Cluster, - { - name: 'test-user', - token: 'test-token', - } as User, - ); + const kc = new KubeConfig(); + kc.loadFromClusterAndUser( + { + name: 'test-cluster', + server: `https://${host}:${port}`, + skipTLSVerify: true, + } as Cluster, + { + name: 'test-user', + token: 'test-token', + } as User, + ); - const coreV1Api = kc.makeApiClient(CoreV1Api); - await coreV1Api.listNamespace(); + const coreV1Api = kc.makeApiClient(CoreV1Api); + await coreV1Api.listNamespace(); - // Read version from package.json - const packageJsonPath = join(__dirname, '..', 'package.json'); - const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); - const expectedVersion = packageJson.version; + // Read version from package.json + const packageJsonPath = join(__dirname, '..', 'package.json'); + const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); + const expectedVersion = packageJson.version; - // Verify version is not blank - strictEqual(typeof expectedVersion, 'string'); - strictEqual(expectedVersion.length > 0, true, 'package.json version should not be blank'); + // Verify version is not blank + strictEqual(typeof expectedVersion, 'string'); + strictEqual(expectedVersion.length > 0, true, 'package.json version should not be blank'); - // Verify User-Agent header contains client name and version - strictEqual(typeof capturedUserAgent, 'string'); - strictEqual( - capturedUserAgent?.startsWith('kubernetes-javascript-client/'), - true, - 'capturedUserAgent should start with "kubernetes-javascript-client/"', - ); - strictEqual( - capturedUserAgent?.includes(expectedVersion), - true, - `User-Agent should include version ${expectedVersion}`, - ); - } finally { + // Verify User-Agent header contains client name and version + strictEqual(typeof capturedUserAgent, 'string'); + strictEqual( + capturedUserAgent!.startsWith('kubernetes-javascript-client/'), + true, + 'capturedUserAgent should start with "kubernetes-javascript-client/"', + ); + strictEqual( + capturedUserAgent!.endsWith(expectedVersion), + true, + `User-Agent should include version ${expectedVersion}`, + ); + t.after(async () => { await new Promise((resolve) => { server.close(() => resolve()); }); - } + }); }); }); From a7cf524ee09bf16aa60c675165076f283b842357 Mon Sep 17 00:00:00 2001 From: David Gamero Date: Wed, 14 Jan 2026 01:24:47 -0500 Subject: [PATCH 3/4] type server close callback resolve --- src/config_test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config_test.ts b/src/config_test.ts index 25a9d68308..8caf1738d6 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -1730,8 +1730,8 @@ describe('KubeConfig', () => { `User-Agent should include version ${expectedVersion}`, ); t.after(async () => { - await new Promise((resolve) => { - server.close(() => resolve()); + await new Promise((resolve) => { + server.close(resolve); }); }); }); From f78746dfd799a7651f7677f712c0c9083affa626 Mon Sep 17 00:00:00 2001 From: David Gamero Date: Wed, 14 Jan 2026 11:54:39 -0500 Subject: [PATCH 4/4] "kubernetes-client-javascript" and move t.after up --- src/config.ts | 2 +- src/config_test.ts | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/config.ts b/src/config.ts index c0100e78f7..a9dd9982b8 100644 --- a/src/config.ts +++ b/src/config.ts @@ -47,7 +47,7 @@ const USER_AGENT_KEY = 'User-Agent'; function getUserAgent(): string { const version = packagejson.version ?? ''; - return `kubernetes-javascript-client/${version}`; + return `kubernetes-client-javascript/${version}`; } // fs.existsSync was removed in node 10 diff --git a/src/config_test.ts b/src/config_test.ts index 8caf1738d6..0e4dc27fd3 100644 --- a/src/config_test.ts +++ b/src/config_test.ts @@ -1691,6 +1691,11 @@ describe('KubeConfig', () => { }), ); }); + t.after(async () => { + await new Promise((resolve) => { + server.close(resolve); + }); + }); const kc = new KubeConfig(); kc.loadFromClusterAndUser( @@ -1720,7 +1725,7 @@ describe('KubeConfig', () => { // Verify User-Agent header contains client name and version strictEqual(typeof capturedUserAgent, 'string'); strictEqual( - capturedUserAgent!.startsWith('kubernetes-javascript-client/'), + capturedUserAgent!.startsWith('kubernetes-client-javascript/'), true, 'capturedUserAgent should start with "kubernetes-javascript-client/"', ); @@ -1729,11 +1734,6 @@ describe('KubeConfig', () => { true, `User-Agent should include version ${expectedVersion}`, ); - t.after(async () => { - await new Promise((resolve) => { - server.close(resolve); - }); - }); }); });