kubernetes-fluent-client 3.10.12 → 3.10.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/fluent/watch.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAGtC,OAAO,EAAE,YAAY,EAAwB,MAAM,aAAa,CAAC;AAGjE,OAAO,EAGL,WAAW,EACX,OAAO,EAER,MAAM,mBAAmB,CAAC;AAC3B,oBAAY,UAAU;IACpB,sCAAsC;IACtC,OAAO,YAAY;IACnB,2BAA2B;IAC3B,aAAa,kBAAkB;IAC/B,kDAAkD;IAClD,UAAU,eAAe;IACzB,0BAA0B;IAC1B,SAAS,cAAc;IACvB,8BAA8B;IAC9B,OAAO,YAAY;IACnB,sBAAsB;IACtB,KAAK,UAAU;IACf,mCAAmC;IACnC,IAAI,SAAS;IACb,wCAAwC;IACxC,oBAAoB,yBAAyB;IAC7C,qCAAqC;IACrC,iBAAiB,sBAAsB;IACvC,kCAAkC;IAClC,IAAI,SAAS;IACb,2BAA2B;IAC3B,UAAU,eAAe;IACzB,mBAAmB;IACnB,UAAU,eAAe;IACzB,qCAAqC;IACrC,wBAAwB,6BAA6B;IACrD,iCAAiC;IACjC,eAAe,oBAAoB;CACpC;AAED,4CAA4C;AAC5C,MAAM,MAAM,QAAQ,GAAG;IACrB,+HAA+H;IAC/H,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+FAA+F;IAC/F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wHAAwH;IACxH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAKF,iDAAiD;AACjD,qBAAa,OAAO,CAAC,CAAC,SAAS,YAAY;;IAyBzC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IAc9B;;;;;;;;;;;OAWG;gBACS,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,GAAE,QAAa;IA0CzF;;;;OAIG;IACU,KAAK,IAAI,OAAO,CAAC,eAAe,CAAC;IAO9C,gGAAgG;IACzF,KAAK;IAQZ;;;;;;OAMG;IACH,IAAW,MAAM,IAAI,YAAY,CAEhC;CA0VF"}
1
+ {"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/fluent/watch.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,EAAE,YAAY,EAAwB,MAAM,aAAa,CAAC;AAGjE,OAAO,EAGL,WAAW,EACX,OAAO,EAER,MAAM,mBAAmB,CAAC;AAK3B,oBAAY,UAAU;IACpB,sCAAsC;IACtC,OAAO,YAAY;IACnB,2BAA2B;IAC3B,aAAa,kBAAkB;IAC/B,kDAAkD;IAClD,UAAU,eAAe;IACzB,0BAA0B;IAC1B,SAAS,cAAc;IACvB,8BAA8B;IAC9B,OAAO,YAAY;IACnB,sBAAsB;IACtB,KAAK,UAAU;IACf,mCAAmC;IACnC,IAAI,SAAS;IACb,wCAAwC;IACxC,oBAAoB,yBAAyB;IAC7C,qCAAqC;IACrC,iBAAiB,sBAAsB;IACvC,kCAAkC;IAClC,IAAI,SAAS;IACb,2BAA2B;IAC3B,UAAU,eAAe;IACzB,mBAAmB;IACnB,UAAU,eAAe;IACzB,qCAAqC;IACrC,wBAAwB,6BAA6B;IACrD,iCAAiC;IACjC,eAAe,oBAAoB;CACpC;AAED,4CAA4C;AAC5C,MAAM,MAAM,QAAQ,GAAG;IACrB,+HAA+H;IAC/H,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,+FAA+F;IAC/F,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wHAAwH;IACxH,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B,CAAC;AAKF,iDAAiD;AACjD,qBAAa,OAAO,CAAC,CAAC,SAAS,YAAY;;IAyBzC,YAAY,CAAC,EAAE,MAAM,CAAC,OAAO,CAAC;IAc9B;;;;;;;;;;;OAWG;gBACS,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,GAAE,QAAa;IA0CzF;;;;OAIG;IACU,KAAK,IAAI,OAAO,CAAC,eAAe,CAAC;IAO9C,gGAAgG;IACzF,KAAK;IAQZ;;;;;;OAMG;IACH,IAAW,MAAM,IAAI,YAAY,CAEhC;CAwXF"}
@@ -2,10 +2,11 @@
2
2
  // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
3
3
  import { EventEmitter } from "events";
4
4
  import { fetch } from "undici";
5
- import { fetch as wrappedFetch } from "../fetch.js";
6
5
  import { k8sCfg, pathBuilder, getHeaders } from "./utils.js";
7
6
  import { Readable } from "stream";
8
7
  import { WatchPhase, FetchMethods, } from "./shared-types.js";
8
+ const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
9
+ const startSleep = 5000;
9
10
  export var WatchEvent;
10
11
  (function (WatchEvent) {
11
12
  /** Watch is connected successfully */
@@ -174,16 +175,28 @@ export class Watcher {
174
175
  *
175
176
  * @param continueToken - the continue token for the list
176
177
  * @param removedItems - the list of items that have been removed
178
+ * @param retryCount - current retry attempt count
177
179
  */
178
- #list = async (continueToken, removedItems) => {
180
+ #list = async (continueToken, removedItems, retryCount = 0) => {
181
+ const maxRetries = 5;
182
+ const maxPages = 10;
179
183
  try {
180
184
  const { opts, serverUrl } = await this.#buildURL(false, undefined, continueToken);
181
185
  // Make the request to list the resources
182
- const response = await wrappedFetch(serverUrl, opts);
183
- const list = response.data;
186
+ const response = await fetch(serverUrl, opts);
187
+ const list = (await response.json());
184
188
  // If the request fails, emit an error event and return
185
189
  if (!response.ok) {
186
- this.#events.emit(WatchEvent.LIST_ERROR, new Error(`list failed: ${response.status} ${response.statusText}`));
190
+ this.#events.emit(WatchEvent.LIST_ERROR, new Error(`list failed: ${response.status} ${response.statusText} ${JSON.stringify([...response.headers])}`));
191
+ // Retry with exponential backoff if under retry limit to prevent infinite recursion if the server is returning errors
192
+ if (retryCount < maxRetries) {
193
+ const retryAfterHeader = response.headers.get("retry-after");
194
+ const backoffTime = retryAfterHeader
195
+ ? parseInt(retryAfterHeader) * 1000
196
+ : Math.min(startSleep * Math.pow(2, retryCount), 30000);
197
+ await sleep(backoffTime);
198
+ return this.#list(continueToken, removedItems, retryCount + 1);
199
+ }
187
200
  return;
188
201
  }
189
202
  // Gross hack, thanks upstream library :<
@@ -220,8 +233,13 @@ export class Watcher {
220
233
  }
221
234
  // If there is a continue token, call the list function again with the same removed items
222
235
  if (continueToken) {
223
- // If there is a continue token, call the list function again with the same removed items
224
- await this.#list(continueToken, removedItems);
236
+ // Safeguard against infinite pagination
237
+ if (retryCount >= maxPages) {
238
+ this.#events.emit(WatchEvent.LIST_ERROR, new Error(`Maximum pagination limit (${maxPages}) reached, stopping list operation`));
239
+ return;
240
+ }
241
+ // Continue pagination (not a retry, so reset retryCount to 0)
242
+ await this.#list(continueToken, removedItems, 0);
225
243
  }
226
244
  else {
227
245
  // Otherwise, process the removed items
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kubernetes-fluent-client",
3
- "version": "3.10.12",
3
+ "version": "3.10.13",
4
4
  "description": "A @kubernetes/client-node fluent API wrapper that leverages K8s Server Side Apply.",
5
5
  "bin": "./dist/cli.js",
6
6
  "main": "dist/index.js",
@@ -61,8 +61,8 @@
61
61
  "yargs": "18.0.0"
62
62
  },
63
63
  "devDependencies": {
64
- "@commitlint/cli": "20.3.0",
65
- "@commitlint/config-conventional": "20.3.0",
64
+ "@commitlint/cli": "20.3.1",
65
+ "@commitlint/config-conventional": "20.3.1",
66
66
  "@defenseunicorns/eslint-config": "^1.1.2",
67
67
  "@eslint/eslintrc": "^3.1.0",
68
68
  "@eslint/js": "^9.14.0",
@@ -72,15 +72,15 @@
72
72
  "@types/urijs": "^1.19.25",
73
73
  "@types/ws": "^8.18.1",
74
74
  "@types/yargs": "17.0.35",
75
- "@typescript-eslint/eslint-plugin": "8.52.0",
76
- "@typescript-eslint/parser": "8.52.0",
75
+ "@typescript-eslint/eslint-plugin": "8.53.1",
76
+ "@typescript-eslint/parser": "8.53.1",
77
77
  "@vitest/coverage-v8": "^4.0.1",
78
78
  "command-line-args": "^6.0.1",
79
- "eslint-plugin-jsdoc": "61.5.0",
79
+ "eslint-plugin-jsdoc": "62.2.0",
80
80
  "globals": "^17.0.0",
81
81
  "husky": "^9.1.6",
82
82
  "lint-staged": "^16.0.0",
83
- "prettier": "3.7.4",
83
+ "prettier": "3.8.0",
84
84
  "semantic-release": "25.0.2",
85
85
  "typescript": "5.9.3",
86
86
  "vitest": "^4.0.1"
@@ -3,7 +3,6 @@
3
3
 
4
4
  import { EventEmitter } from "events";
5
5
  import { fetch } from "undici";
6
- import { fetch as wrappedFetch } from "../fetch.js";
7
6
  import { GenericClass, KubernetesListObject } from "../types.js";
8
7
  import { k8sCfg, pathBuilder, getHeaders } from "./utils.js";
9
8
  import { Readable } from "stream";
@@ -14,6 +13,10 @@ import {
14
13
  Filters,
15
14
  FetchMethods,
16
15
  } from "./shared-types.js";
16
+
17
+ const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
18
+ const startSleep = 5000;
19
+
17
20
  export enum WatchEvent {
18
21
  /** Watch is connected successfully */
19
22
  CONNECT = "connect",
@@ -235,22 +238,43 @@ export class Watcher<T extends GenericClass> {
235
238
  *
236
239
  * @param continueToken - the continue token for the list
237
240
  * @param removedItems - the list of items that have been removed
241
+ * @param retryCount - current retry attempt count
238
242
  */
239
- #list = async (continueToken?: string, removedItems?: Map<string, InstanceType<T>>) => {
243
+ #list = async (
244
+ continueToken?: string,
245
+ removedItems?: Map<string, InstanceType<T>>,
246
+ retryCount = 0,
247
+ ): Promise<void> => {
248
+ const maxRetries = 5;
249
+ const maxPages = 10;
250
+
240
251
  try {
241
252
  const { opts, serverUrl } = await this.#buildURL(false, undefined, continueToken);
242
253
 
243
254
  // Make the request to list the resources
244
- const response = await wrappedFetch<KubernetesListObject<InstanceType<T>>>(serverUrl, opts);
245
- const list = response.data;
255
+ const response = await fetch(serverUrl, opts);
256
+ const list = (await response.json()) as KubernetesListObject<InstanceType<T>>;
246
257
 
247
258
  // If the request fails, emit an error event and return
248
259
  if (!response.ok) {
249
260
  this.#events.emit(
250
261
  WatchEvent.LIST_ERROR,
251
- new Error(`list failed: ${response.status} ${response.statusText}`),
262
+ new Error(
263
+ `list failed: ${response.status} ${response.statusText} ${JSON.stringify([...response.headers])}`,
264
+ ),
252
265
  );
253
266
 
267
+ // Retry with exponential backoff if under retry limit to prevent infinite recursion if the server is returning errors
268
+ if (retryCount < maxRetries) {
269
+ const retryAfterHeader = response.headers.get("retry-after");
270
+ const backoffTime = retryAfterHeader
271
+ ? parseInt(retryAfterHeader) * 1000
272
+ : Math.min(startSleep * Math.pow(2, retryCount), 30000);
273
+
274
+ await sleep(backoffTime);
275
+ return this.#list(continueToken, removedItems, retryCount + 1);
276
+ }
277
+
254
278
  return;
255
279
  }
256
280
 
@@ -297,8 +321,17 @@ export class Watcher<T extends GenericClass> {
297
321
 
298
322
  // If there is a continue token, call the list function again with the same removed items
299
323
  if (continueToken) {
300
- // If there is a continue token, call the list function again with the same removed items
301
- await this.#list(continueToken, removedItems);
324
+ // Safeguard against infinite pagination
325
+ if (retryCount >= maxPages) {
326
+ this.#events.emit(
327
+ WatchEvent.LIST_ERROR,
328
+ new Error(`Maximum pagination limit (${maxPages}) reached, stopping list operation`),
329
+ );
330
+ return;
331
+ }
332
+
333
+ // Continue pagination (not a retry, so reset retryCount to 0)
334
+ await this.#list(continueToken, removedItems, 0);
302
335
  } else {
303
336
  // Otherwise, process the removed items
304
337
  for (const item of removedItems.values()) {