kubernetes-fluent-client 3.0.4 → 3.1.0

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.
Files changed (76) hide show
  1. package/e2e/cli.e2e.test.ts +11 -7
  2. package/package.json +6 -6
  3. package/src/fluent/http2-watch.spec.ts +335 -0
  4. package/src/fluent/watch.ts +174 -35
  5. package/dist/cli.d.ts +0 -3
  6. package/dist/cli.d.ts.map +0 -1
  7. package/dist/cli.js +0 -68
  8. package/dist/fetch.d.ts +0 -22
  9. package/dist/fetch.d.ts.map +0 -1
  10. package/dist/fetch.js +0 -82
  11. package/dist/fetch.test.d.ts +0 -2
  12. package/dist/fetch.test.d.ts.map +0 -1
  13. package/dist/fetch.test.js +0 -97
  14. package/dist/fileSystem.d.ts +0 -11
  15. package/dist/fileSystem.d.ts.map +0 -1
  16. package/dist/fileSystem.js +0 -42
  17. package/dist/fileSystem.test.d.ts +0 -2
  18. package/dist/fileSystem.test.d.ts.map +0 -1
  19. package/dist/fileSystem.test.js +0 -75
  20. package/dist/fluent/index.d.ts +0 -12
  21. package/dist/fluent/index.d.ts.map +0 -1
  22. package/dist/fluent/index.js +0 -228
  23. package/dist/fluent/index.test.d.ts +0 -2
  24. package/dist/fluent/index.test.d.ts.map +0 -1
  25. package/dist/fluent/index.test.js +0 -193
  26. package/dist/fluent/types.d.ts +0 -187
  27. package/dist/fluent/types.d.ts.map +0 -1
  28. package/dist/fluent/types.js +0 -16
  29. package/dist/fluent/utils.d.ts +0 -41
  30. package/dist/fluent/utils.d.ts.map +0 -1
  31. package/dist/fluent/utils.js +0 -153
  32. package/dist/fluent/utils.test.d.ts +0 -2
  33. package/dist/fluent/utils.test.d.ts.map +0 -1
  34. package/dist/fluent/utils.test.js +0 -215
  35. package/dist/fluent/watch.d.ts +0 -86
  36. package/dist/fluent/watch.d.ts.map +0 -1
  37. package/dist/fluent/watch.js +0 -425
  38. package/dist/fluent/watch.spec.d.ts +0 -2
  39. package/dist/fluent/watch.spec.d.ts.map +0 -1
  40. package/dist/fluent/watch.spec.js +0 -261
  41. package/dist/generate.d.ts +0 -84
  42. package/dist/generate.d.ts.map +0 -1
  43. package/dist/generate.js +0 -208
  44. package/dist/generate.test.d.ts +0 -2
  45. package/dist/generate.test.d.ts.map +0 -1
  46. package/dist/generate.test.js +0 -320
  47. package/dist/helpers.d.ts +0 -33
  48. package/dist/helpers.d.ts.map +0 -1
  49. package/dist/helpers.js +0 -103
  50. package/dist/helpers.test.d.ts +0 -2
  51. package/dist/helpers.test.d.ts.map +0 -1
  52. package/dist/helpers.test.js +0 -37
  53. package/dist/index.d.ts +0 -14
  54. package/dist/index.d.ts.map +0 -1
  55. package/dist/index.js +0 -60
  56. package/dist/kinds.d.ts +0 -16
  57. package/dist/kinds.d.ts.map +0 -1
  58. package/dist/kinds.js +0 -570
  59. package/dist/kinds.test.d.ts +0 -2
  60. package/dist/kinds.test.d.ts.map +0 -1
  61. package/dist/kinds.test.js +0 -155
  62. package/dist/patch.d.ts +0 -7
  63. package/dist/patch.d.ts.map +0 -1
  64. package/dist/patch.js +0 -2
  65. package/dist/postProcessing.d.ts +0 -246
  66. package/dist/postProcessing.d.ts.map +0 -1
  67. package/dist/postProcessing.js +0 -497
  68. package/dist/postProcessing.test.d.ts +0 -2
  69. package/dist/postProcessing.test.d.ts.map +0 -1
  70. package/dist/postProcessing.test.js +0 -550
  71. package/dist/types.d.ts +0 -32
  72. package/dist/types.d.ts.map +0 -1
  73. package/dist/types.js +0 -16
  74. package/dist/upstream.d.ts +0 -4
  75. package/dist/upstream.d.ts.map +0 -1
  76. package/dist/upstream.js +0 -56
@@ -1,425 +0,0 @@
1
- "use strict";
2
- // SPDX-License-Identifier: Apache-2.0
3
- // SPDX-FileCopyrightText: 2023-Present The Kubernetes Fluent Client Authors
4
- var __importDefault = (this && this.__importDefault) || function (mod) {
5
- return (mod && mod.__esModule) ? mod : { "default": mod };
6
- };
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.Watcher = exports.WatchEvent = void 0;
9
- const byline_1 = __importDefault(require("byline"));
10
- const crypto_1 = require("crypto");
11
- const events_1 = require("events");
12
- const node_fetch_1 = __importDefault(require("node-fetch"));
13
- const fetch_1 = require("../fetch");
14
- const types_1 = require("./types");
15
- const utils_1 = require("./utils");
16
- var WatchEvent;
17
- (function (WatchEvent) {
18
- /** Watch is connected successfully */
19
- WatchEvent["CONNECT"] = "connect";
20
- /** Network error occurs */
21
- WatchEvent["NETWORK_ERROR"] = "network_error";
22
- /** Error decoding data or running the callback */
23
- WatchEvent["DATA_ERROR"] = "data_error";
24
- /** Reconnect is called */
25
- WatchEvent["RECONNECT"] = "reconnect";
26
- /** Retry limit is exceeded */
27
- WatchEvent["GIVE_UP"] = "give_up";
28
- /** Abort is called */
29
- WatchEvent["ABORT"] = "abort";
30
- /** Data is received and decoded */
31
- WatchEvent["DATA"] = "data";
32
- /** 410 (old resource version) occurs */
33
- WatchEvent["OLD_RESOURCE_VERSION"] = "old_resource_version";
34
- /** A reconnect is already pending */
35
- WatchEvent["RECONNECT_PENDING"] = "reconnect_pending";
36
- /** Resource list operation run */
37
- WatchEvent["LIST"] = "list";
38
- /** List operation error */
39
- WatchEvent["LIST_ERROR"] = "list_error";
40
- /** Cache Misses */
41
- WatchEvent["CACHE_MISS"] = "cache_miss";
42
- /** Increment resync failure count */
43
- WatchEvent["INC_RESYNC_FAILURE_COUNT"] = "inc_resync_failure_count";
44
- /** Initialize a relist window */
45
- WatchEvent["INIT_CACHE_MISS"] = "init_cache_miss";
46
- })(WatchEvent || (exports.WatchEvent = WatchEvent = {}));
47
- const NONE = 50;
48
- const OVERRIDE = 100;
49
- /** A wrapper around the Kubernetes watch API. */
50
- class Watcher {
51
- // User-provided properties
52
- #model;
53
- #filters;
54
- #callback;
55
- #watchCfg;
56
- #latestRelistWindow = "";
57
- // Track the last time data was received
58
- #lastSeenTime = NONE;
59
- #lastSeenLimit;
60
- // Create a wrapped AbortController to allow the watch to be aborted externally
61
- #abortController;
62
- // Track the number of retries
63
- #resyncFailureCount = 0;
64
- // Create a stream to read the response body
65
- #stream;
66
- // Create an EventEmitter to emit events
67
- #events = new events_1.EventEmitter();
68
- // Create a timer to relist the watch
69
- $relistTimer;
70
- // Create a timer to resync the watch
71
- #resyncTimer;
72
- // Track if a reconnect is pending
73
- #pendingReconnect = false;
74
- // The resource version to start the watch at, this will be updated after the list operation.
75
- #resourceVersion;
76
- // Track the list of items in the cache
77
- #cache = new Map();
78
- /**
79
- * Setup a Kubernetes watcher for the specified model and filters. The callback function will be called for each event received.
80
- * The watch can be aborted by calling {@link Watcher.close} or by calling abort() on the AbortController returned by {@link Watcher.start}.
81
- *
82
- *
83
- * Kubernetes API docs: {@link https://kubernetes.io/docs/reference/using-api/api-concepts/#efficient-detection-of-changes}
84
- *
85
- * @param model - the model to use for the API
86
- * @param filters - (optional) filter overrides, can also be chained
87
- * @param callback - the callback function to call when an event is received
88
- * @param watchCfg - (optional) watch configuration
89
- */
90
- constructor(model, filters, callback, watchCfg = {}) {
91
- // Set the retry delay to 5 seconds if not specified
92
- watchCfg.resyncDelaySec ??= 5;
93
- // Set the relist interval to 10 minutes if not specified
94
- watchCfg.relistIntervalSec ??= 600;
95
- // Set the resync interval to 10 minutes if not specified
96
- watchCfg.lastSeenLimitSeconds ??= 600;
97
- // Set the last seen limit to the resync interval
98
- this.#lastSeenLimit = watchCfg.lastSeenLimitSeconds * 1000;
99
- // Set the latest relist interval to now
100
- this.#latestRelistWindow = new Date().toISOString();
101
- // Add random jitter to the relist/resync intervals (up to 1 second)
102
- const jitter = Math.floor(Math.random() * 1000);
103
- // Check every relist interval for cache staleness
104
- this.$relistTimer = setInterval(() => {
105
- this.#latestRelistWindow = new Date().toISOString();
106
- this.#events.emit(WatchEvent.INIT_CACHE_MISS, this.#latestRelistWindow);
107
- void this.#list();
108
- }, watchCfg.relistIntervalSec * 1000 + jitter);
109
- // Rebuild the watch every resync delay interval
110
- this.#resyncTimer = setInterval(this.#checkResync, watchCfg.resyncDelaySec * 1000 + jitter);
111
- // Bind class properties
112
- this.#model = model;
113
- this.#filters = filters;
114
- this.#callback = callback;
115
- this.#watchCfg = watchCfg;
116
- // Create a new AbortController
117
- this.#abortController = new AbortController();
118
- }
119
- /**
120
- * Start the watch.
121
- *
122
- * @returns The AbortController for the watch.
123
- */
124
- async start() {
125
- this.#events.emit(WatchEvent.INIT_CACHE_MISS, this.#latestRelistWindow);
126
- await this.#watch();
127
- return this.#abortController;
128
- }
129
- /** Close the watch. Also available on the AbortController returned by {@link Watcher.start}. */
130
- close() {
131
- clearInterval(this.$relistTimer);
132
- clearInterval(this.#resyncTimer);
133
- this.#streamCleanup();
134
- this.#abortController.abort();
135
- }
136
- /**
137
- * Get a unique ID for the watch based on the model and filters.
138
- * This is useful for caching the watch data or resource versions.
139
- *
140
- * @returns the watch CacheID
141
- */
142
- getCacheID() {
143
- // Build the URL, we don't care about the server URL or resourceVersion
144
- const url = (0, utils_1.pathBuilder)("https://ignore", this.#model, this.#filters, false);
145
- // Hash and truncate the ID to 10 characters, cache the result
146
- return (0, crypto_1.createHash)("sha224")
147
- .update(url.pathname + url.search)
148
- .digest("hex")
149
- .substring(0, 10);
150
- }
151
- /**
152
- * Subscribe to watch events. This is an EventEmitter that emits the following events:
153
- *
154
- * Use {@link WatchEvent} for the event names.
155
- *
156
- * @returns an EventEmitter
157
- */
158
- get events() {
159
- return this.#events;
160
- }
161
- /**
162
- * Build the URL and request options for the watch.
163
- *
164
- * @param isWatch - whether the request is for a watch operation
165
- * @param resourceVersion - the resource version to use for the watch
166
- * @param continueToken - the continue token for the watch
167
- *
168
- * @returns the URL and request options
169
- */
170
- #buildURL = async (isWatch, resourceVersion, continueToken) => {
171
- // Build the path and query params for the resource, excluding the name
172
- const { opts, serverUrl } = await (0, utils_1.k8sCfg)("GET");
173
- const url = (0, utils_1.pathBuilder)(serverUrl, this.#model, this.#filters, true);
174
- // Enable the watch query param
175
- if (isWatch) {
176
- url.searchParams.set("watch", "true");
177
- }
178
- if (continueToken) {
179
- url.searchParams.set("continue", continueToken);
180
- }
181
- // If a name is specified, add it to the query params
182
- if (this.#filters.name) {
183
- url.searchParams.set("fieldSelector", `metadata.name=${this.#filters.name}`);
184
- }
185
- // If a resource version is specified, add it to the query params
186
- if (resourceVersion) {
187
- url.searchParams.set("resourceVersion", resourceVersion);
188
- }
189
- // Add the abort signal to the request options
190
- opts.signal = this.#abortController.signal;
191
- return { opts, url };
192
- };
193
- /**
194
- * Retrieve the list of resources and process the events.
195
- *
196
- * @param continueToken - the continue token for the list
197
- * @param removedItems - the list of items that have been removed
198
- */
199
- #list = async (continueToken, removedItems) => {
200
- try {
201
- const { opts, url } = await this.#buildURL(false, undefined, continueToken);
202
- // Make the request to list the resources
203
- const response = await (0, fetch_1.fetch)(url, opts);
204
- const list = response.data;
205
- // If the request fails, emit an error event and return
206
- if (!response.ok) {
207
- this.#events.emit(WatchEvent.LIST_ERROR, new Error(`list failed: ${response.status} ${response.statusText}`));
208
- return;
209
- }
210
- // Gross hack, thanks upstream library :<
211
- if (list.metadata.continue) {
212
- continueToken = list.metadata.continue;
213
- }
214
- // Emit the list event
215
- this.#events.emit(WatchEvent.LIST, list);
216
- // Update the resource version from the list metadata
217
- this.#resourceVersion = list.metadata?.resourceVersion;
218
- // If removed items are not provided, clone the cache
219
- removedItems = removedItems || new Map(this.#cache.entries());
220
- // Process each item in the list
221
- for (const item of list.items || []) {
222
- const { uid } = item.metadata;
223
- // Remove the item from the removed items list
224
- const alreadyExists = removedItems.delete(uid);
225
- // If the item does not exist, it is new and should be added
226
- if (!alreadyExists) {
227
- this.#events.emit(WatchEvent.CACHE_MISS, this.#latestRelistWindow);
228
- // Send added event. Use void here because we don't care about the result (no consequences here if it fails)
229
- void this.#process(item, types_1.WatchPhase.Added);
230
- continue;
231
- }
232
- // Check if the resource version has changed for items that already exist
233
- const cachedRV = parseInt(this.#cache.get(uid)?.metadata?.resourceVersion);
234
- const itemRV = parseInt(item.metadata.resourceVersion);
235
- // Check if the resource version is newer than the cached version
236
- if (itemRV > cachedRV) {
237
- this.#events.emit(WatchEvent.CACHE_MISS, this.#latestRelistWindow);
238
- // Send a modified event if the resource version has changed
239
- void this.#process(item, types_1.WatchPhase.Modified);
240
- }
241
- }
242
- // If there is a continue token, call the list function again with the same removed items
243
- if (continueToken) {
244
- // If there is a continue token, call the list function again with the same removed items
245
- // @todo: using all voids here is important for freshness, but is naive with regard to API load & pod resources
246
- await this.#list(continueToken, removedItems);
247
- }
248
- else {
249
- // Otherwise, process the removed items
250
- for (const item of removedItems.values()) {
251
- this.#events.emit(WatchEvent.CACHE_MISS, this.#latestRelistWindow);
252
- void this.#process(item, types_1.WatchPhase.Deleted);
253
- }
254
- }
255
- }
256
- catch (err) {
257
- this.#events.emit(WatchEvent.LIST_ERROR, err);
258
- }
259
- };
260
- /**
261
- * Process the event payload.
262
- *
263
- * @param payload - the event payload
264
- * @param phase - the event phase
265
- */
266
- #process = async (payload, phase) => {
267
- try {
268
- switch (phase) {
269
- // If the event is added or modified, update the cache
270
- case types_1.WatchPhase.Added:
271
- case types_1.WatchPhase.Modified:
272
- this.#cache.set(payload.metadata.uid, payload);
273
- break;
274
- // If the event is deleted, remove the item from the cache
275
- case types_1.WatchPhase.Deleted:
276
- this.#cache.delete(payload.metadata.uid);
277
- break;
278
- }
279
- // Emit the data event
280
- this.#events.emit(WatchEvent.DATA, payload, phase);
281
- // Call the callback function with the parsed payload
282
- await this.#callback(payload, phase);
283
- }
284
- catch (err) {
285
- this.#events.emit(WatchEvent.DATA_ERROR, err);
286
- }
287
- };
288
- /**
289
- * Watch for changes to the resource.
290
- */
291
- #watch = async () => {
292
- try {
293
- // Start with a list operation
294
- await this.#list();
295
- // Build the URL and request options
296
- const { opts, url } = await this.#buildURL(true, this.#resourceVersion);
297
- // Create a stream to read the response body
298
- this.#stream = byline_1.default.createStream();
299
- // Bind the stream events
300
- this.#stream.on("error", this.#errHandler);
301
- this.#stream.on("close", this.#streamCleanup);
302
- this.#stream.on("finish", this.#streamCleanup);
303
- // Make the actual request
304
- const response = await (0, node_fetch_1.default)(url, { ...opts });
305
- // Reset the pending reconnect flag
306
- this.#pendingReconnect = false;
307
- // If the request is successful, start listening for events
308
- if (response.ok) {
309
- this.#events.emit(WatchEvent.CONNECT, url.pathname);
310
- const { body } = response;
311
- // Reset the retry count
312
- this.#resyncFailureCount = 0;
313
- this.#events.emit(WatchEvent.INC_RESYNC_FAILURE_COUNT, this.#resyncFailureCount);
314
- // Listen for events and call the callback function
315
- this.#stream.on("data", async (line) => {
316
- try {
317
- // Parse the event payload
318
- const { object: payload, type: phase } = JSON.parse(line);
319
- // Update the last seen time
320
- this.#lastSeenTime = Date.now();
321
- // If the watch is too old, remove the resourceVersion and reload the watch
322
- if (phase === types_1.WatchPhase.Error && payload.code === 410) {
323
- throw {
324
- name: "TooOld",
325
- message: this.#resourceVersion,
326
- };
327
- }
328
- // Process the event payload, do not update the resource version as that is handled by the list operation
329
- await this.#process(payload, phase);
330
- }
331
- catch (err) {
332
- if (err.name === "TooOld") {
333
- // Prevent any body events from firing
334
- body.removeAllListeners();
335
- // Reload the watch
336
- void this.#errHandler(err);
337
- return;
338
- }
339
- this.#events.emit(WatchEvent.DATA_ERROR, err);
340
- }
341
- });
342
- // Bind the body events
343
- body.on("error", this.#errHandler);
344
- body.on("close", this.#streamCleanup);
345
- body.on("finish", this.#streamCleanup);
346
- // Pipe the response body to the stream
347
- body.pipe(this.#stream);
348
- }
349
- else {
350
- throw new Error(`watch connect failed: ${response.status} ${response.statusText}`);
351
- }
352
- }
353
- catch (e) {
354
- void this.#errHandler(e);
355
- }
356
- };
357
- /** Clear the resync timer and schedule a new one. */
358
- #checkResync = () => {
359
- // Ignore if the last seen time is not set
360
- if (this.#lastSeenTime === NONE) {
361
- return;
362
- }
363
- const now = Date.now();
364
- // If the last seen time is greater than the limit, trigger a resync
365
- if (this.#lastSeenTime == OVERRIDE || now - this.#lastSeenTime > this.#lastSeenLimit) {
366
- // Reset the last seen time to now to allow the resync to be called again in case of failure
367
- this.#lastSeenTime = now;
368
- // If there are more attempts, retry the watch (undefined is unlimited retries)
369
- if (this.#watchCfg.resyncFailureMax === undefined ||
370
- this.#watchCfg.resyncFailureMax > this.#resyncFailureCount) {
371
- // Increment the retry count
372
- this.#resyncFailureCount++;
373
- this.#events.emit(WatchEvent.INC_RESYNC_FAILURE_COUNT, this.#resyncFailureCount);
374
- if (this.#pendingReconnect) {
375
- // wait for the connection to be re-established
376
- this.#events.emit(WatchEvent.RECONNECT_PENDING);
377
- }
378
- else {
379
- this.#pendingReconnect = true;
380
- this.#events.emit(WatchEvent.RECONNECT, this.#resyncFailureCount);
381
- this.#streamCleanup();
382
- void this.#watch();
383
- }
384
- }
385
- else {
386
- // Otherwise, call the finally function if it exists
387
- this.#events.emit(WatchEvent.GIVE_UP, new Error(`Retry limit (${this.#watchCfg.resyncFailureMax}) exceeded, giving up`));
388
- this.close();
389
- }
390
- }
391
- };
392
- /**
393
- * Handle errors from the stream.
394
- *
395
- * @param err - the error that occurred
396
- */
397
- #errHandler = async (err) => {
398
- switch (err.name) {
399
- case "AbortError":
400
- clearInterval(this.$relistTimer);
401
- clearInterval(this.#resyncTimer);
402
- this.#streamCleanup();
403
- this.#events.emit(WatchEvent.ABORT, err);
404
- return;
405
- case "TooOld":
406
- // Purge the resource version if it is too old
407
- this.#resourceVersion = undefined;
408
- this.#events.emit(WatchEvent.OLD_RESOURCE_VERSION, err.message);
409
- break;
410
- default:
411
- this.#events.emit(WatchEvent.NETWORK_ERROR, err);
412
- break;
413
- }
414
- // Force a resync
415
- this.#lastSeenTime = OVERRIDE;
416
- };
417
- /** Cleanup the stream and listeners. */
418
- #streamCleanup = () => {
419
- if (this.#stream) {
420
- this.#stream.removeAllListeners();
421
- this.#stream.destroy();
422
- }
423
- };
424
- }
425
- exports.Watcher = Watcher;
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=watch.spec.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"watch.spec.d.ts","sourceRoot":"","sources":["../../src/fluent/watch.spec.ts"],"names":[],"mappings":""}
@@ -1,261 +0,0 @@
1
- "use strict";
2
- /* eslint-disable @typescript-eslint/no-explicit-any */
3
- var __importDefault = (this && this.__importDefault) || function (mod) {
4
- return (mod && mod.__esModule) ? mod : { "default": mod };
5
- };
6
- Object.defineProperty(exports, "__esModule", { value: true });
7
- const globals_1 = require("@jest/globals");
8
- const nock_1 = __importDefault(require("nock"));
9
- const readable_stream_1 = require("readable-stream");
10
- const _1 = require(".");
11
- const __1 = require("..");
12
- const types_1 = require("./types");
13
- (0, globals_1.describe)("Watcher", () => {
14
- const evtMock = globals_1.jest.fn();
15
- const errMock = globals_1.jest.fn();
16
- const setupAndStartWatcher = (eventType, handler) => {
17
- watcher.events.on(eventType, handler);
18
- watcher.start().catch(errMock);
19
- };
20
- let watcher;
21
- (0, globals_1.beforeEach)(() => {
22
- globals_1.jest.resetAllMocks();
23
- (0, nock_1.default)("http://jest-test:8080")
24
- .get("/api/v1/pods")
25
- .reply(200, {
26
- kind: "PodList",
27
- apiVersion: "v1",
28
- metadata: {
29
- resourceVersion: "10",
30
- },
31
- items: [createMockPod(`pod-0`, `1`)],
32
- });
33
- (0, nock_1.default)("http://jest-test:8080")
34
- .get("/api/v1/pods")
35
- .query({ watch: "true", resourceVersion: "10" })
36
- .reply(200, () => {
37
- const stream = new readable_stream_1.PassThrough();
38
- const resources = [
39
- { type: "ADDED", object: createMockPod(`pod-0`, `1`) },
40
- { type: "MODIFIED", object: createMockPod(`pod-0`, `2`) },
41
- ];
42
- resources.forEach(resource => {
43
- stream.write(JSON.stringify(resource) + "\n");
44
- });
45
- stream.end();
46
- return stream;
47
- });
48
- });
49
- (0, globals_1.afterEach)(() => {
50
- watcher.close();
51
- });
52
- (0, globals_1.it)("should watch named resources", done => {
53
- nock_1.default.cleanAll();
54
- (0, nock_1.default)("http://jest-test:8080")
55
- .get("/api/v1/namespaces/tester/pods")
56
- .query({ fieldSelector: "metadata.name=demo" })
57
- .reply(200, createMockPod(`demo`, `15`));
58
- (0, nock_1.default)("http://jest-test:8080")
59
- .get("/api/v1/namespaces/tester/pods")
60
- .query({
61
- watch: "true",
62
- fieldSelector: "metadata.name=demo",
63
- resourceVersion: "15",
64
- })
65
- .reply(200);
66
- watcher = (0, _1.K8s)(__1.kind.Pod, { name: "demo" }).InNamespace("tester").Watch(evtMock);
67
- setupAndStartWatcher(__1.WatchEvent.CONNECT, () => {
68
- done();
69
- });
70
- });
71
- (0, globals_1.it)("should handle resource version is too old", done => {
72
- nock_1.default.cleanAll();
73
- (0, nock_1.default)("http://jest-test:8080")
74
- .get("/api/v1/pods")
75
- .reply(200, {
76
- kind: "PodList",
77
- apiVersion: "v1",
78
- metadata: {
79
- resourceVersion: "25",
80
- },
81
- items: [createMockPod(`pod-0`, `1`)],
82
- });
83
- (0, nock_1.default)("http://jest-test:8080")
84
- .get("/api/v1/pods")
85
- .query({ watch: "true", resourceVersion: "25" })
86
- .reply(200, () => {
87
- const stream = new readable_stream_1.PassThrough();
88
- stream.write(JSON.stringify({
89
- type: "ERROR",
90
- object: {
91
- kind: "Status",
92
- apiVersion: "v1",
93
- metadata: {},
94
- status: "Failure",
95
- message: "too old resource version: 123 (391079)",
96
- reason: "Gone",
97
- code: 410,
98
- },
99
- }) + "\n");
100
- stream.end();
101
- return stream;
102
- });
103
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch(evtMock);
104
- setupAndStartWatcher(__1.WatchEvent.OLD_RESOURCE_VERSION, res => {
105
- (0, globals_1.expect)(res).toEqual("25");
106
- done();
107
- });
108
- });
109
- (0, globals_1.it)("should call the event handler for each event", done => {
110
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch((evt, phase) => {
111
- (0, globals_1.expect)(evt.metadata?.name).toEqual(`pod-0`);
112
- (0, globals_1.expect)(phase).toEqual(types_1.WatchPhase.Added);
113
- done();
114
- });
115
- watcher.start().catch(errMock);
116
- });
117
- (0, globals_1.it)("should return the cache id", () => {
118
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch(evtMock, {
119
- resyncDelaySec: 1,
120
- });
121
- (0, globals_1.expect)(watcher.getCacheID()).toEqual("d69b75a611");
122
- });
123
- (0, globals_1.it)("should handle the CONNECT event", done => {
124
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch(evtMock, {
125
- resyncDelaySec: 1,
126
- });
127
- setupAndStartWatcher(__1.WatchEvent.CONNECT, () => {
128
- done();
129
- });
130
- });
131
- (0, globals_1.it)("should handle the DATA event", done => {
132
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch(evtMock, {
133
- resyncDelaySec: 1,
134
- });
135
- setupAndStartWatcher(__1.WatchEvent.DATA, (pod, phase) => {
136
- (0, globals_1.expect)(pod.metadata?.name).toEqual(`pod-0`);
137
- (0, globals_1.expect)(phase).toEqual(types_1.WatchPhase.Added);
138
- done();
139
- });
140
- });
141
- (0, globals_1.it)("should handle the NETWORK_ERROR event", done => {
142
- nock_1.default.cleanAll();
143
- (0, nock_1.default)("http://jest-test:8080")
144
- .get("/api/v1/pods")
145
- .reply(200, {
146
- kind: "PodList",
147
- apiVersion: "v1",
148
- metadata: {
149
- resourceVersion: "45",
150
- },
151
- items: [createMockPod(`pod-0`, `1`)],
152
- });
153
- (0, nock_1.default)("http://jest-test:8080")
154
- .get("/api/v1/pods")
155
- .query({ watch: "true", resourceVersion: "45" })
156
- .replyWithError("Something bad happened");
157
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch(evtMock, {
158
- resyncDelaySec: 1,
159
- });
160
- setupAndStartWatcher(__1.WatchEvent.NETWORK_ERROR, error => {
161
- (0, globals_1.expect)(error.message).toEqual("request to http://jest-test:8080/api/v1/pods?watch=true&resourceVersion=45 failed, reason: Something bad happened");
162
- done();
163
- });
164
- });
165
- (0, globals_1.it)("should handle the RECONNECT event on an error", done => {
166
- nock_1.default.cleanAll();
167
- (0, nock_1.default)("http://jest-test:8080")
168
- .get("/api/v1/pods")
169
- .reply(200, {
170
- kind: "PodList",
171
- apiVersion: "v1",
172
- metadata: {
173
- resourceVersion: "65",
174
- },
175
- items: [createMockPod(`pod-0`, `1`)],
176
- });
177
- (0, nock_1.default)("http://jest-test:8080")
178
- .get("/api/v1/pods")
179
- .query({ watch: "true", resourceVersion: "65" })
180
- .replyWithError("Something bad happened");
181
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch(evtMock, {
182
- resyncDelaySec: 0.01,
183
- });
184
- setupAndStartWatcher(__1.WatchEvent.RECONNECT, count => {
185
- (0, globals_1.expect)(count).toEqual(1);
186
- done();
187
- });
188
- });
189
- (0, globals_1.it)("should perform a resync after the resync interval", done => {
190
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch(evtMock, {
191
- resyncDelaySec: 0.01,
192
- lastSeenLimitSeconds: 0.01,
193
- });
194
- setupAndStartWatcher(__1.WatchEvent.RECONNECT, count => {
195
- (0, globals_1.expect)(count).toEqual(1);
196
- done();
197
- });
198
- });
199
- (0, globals_1.it)("should handle the GIVE_UP event", done => {
200
- nock_1.default.cleanAll();
201
- (0, nock_1.default)("http://jest-test:8080")
202
- .get("/api/v1/pods")
203
- .reply(200, {
204
- kind: "PodList",
205
- apiVersion: "v1",
206
- metadata: {
207
- resourceVersion: "75",
208
- },
209
- items: [createMockPod(`pod-0`, `1`)],
210
- });
211
- (0, nock_1.default)("http://jest-test:8080")
212
- .get("/api/v1/pods")
213
- .query({ watch: "true", resourceVersion: "75" })
214
- .replyWithError("Something bad happened");
215
- watcher = (0, _1.K8s)(__1.kind.Pod).Watch(evtMock, {
216
- resyncFailureMax: 1,
217
- resyncDelaySec: 0.01,
218
- lastSeenLimitSeconds: 1,
219
- });
220
- setupAndStartWatcher(__1.WatchEvent.GIVE_UP, error => {
221
- (0, globals_1.expect)(error.message).toContain("Retry limit (1) exceeded, giving up");
222
- done();
223
- });
224
- });
225
- });
226
- /**
227
- * Creates a mock pod object
228
- *
229
- * @param name The name of the pod
230
- * @param resourceVersion The resource version of the pod
231
- * @returns A mock pod object
232
- */
233
- function createMockPod(name, resourceVersion) {
234
- return {
235
- kind: "Pod",
236
- apiVersion: "v1",
237
- metadata: {
238
- name: name,
239
- resourceVersion: resourceVersion,
240
- uid: Math.random().toString(36).substring(7),
241
- // ... other metadata fields
242
- },
243
- spec: {
244
- containers: [
245
- {
246
- name: "nginx",
247
- image: "nginx:1.14.2",
248
- ports: [
249
- {
250
- containerPort: 80,
251
- protocol: "TCP",
252
- },
253
- ],
254
- },
255
- ],
256
- },
257
- status: {
258
- // ... pod status
259
- },
260
- };
261
- }