pepr 0.35.0 → 0.37.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 (107) hide show
  1. package/dist/cli/init/index.d.ts.map +1 -1
  2. package/dist/cli/init/templates.d.ts +3 -1
  3. package/dist/cli/init/templates.d.ts.map +1 -1
  4. package/dist/cli/init/utils.d.ts.map +1 -1
  5. package/dist/cli/init/walkthrough.d.ts +10 -3
  6. package/dist/cli/init/walkthrough.d.ts.map +1 -1
  7. package/dist/cli.js +253 -31
  8. package/dist/controller.js +138 -1
  9. package/dist/lib/adjudicators.d.ts +63 -0
  10. package/dist/lib/adjudicators.d.ts.map +1 -0
  11. package/dist/lib/adjudicators.test.d.ts +2 -0
  12. package/dist/lib/adjudicators.test.d.ts.map +1 -0
  13. package/dist/lib/assets/loader.d.ts.map +1 -1
  14. package/dist/lib/assets/pods.d.ts +1 -0
  15. package/dist/lib/assets/pods.d.ts.map +1 -1
  16. package/dist/lib/capability.d.ts +1 -0
  17. package/dist/lib/capability.d.ts.map +1 -1
  18. package/dist/lib/capability.test.d.ts +2 -0
  19. package/dist/lib/capability.test.d.ts.map +1 -0
  20. package/dist/lib/controller/index.d.ts.map +1 -1
  21. package/dist/lib/controller/store.d.ts +4 -0
  22. package/dist/lib/controller/store.d.ts.map +1 -1
  23. package/dist/lib/controller/store.test.d.ts +2 -0
  24. package/dist/lib/controller/store.test.d.ts.map +1 -0
  25. package/dist/lib/filter.d.ts +2 -3
  26. package/dist/lib/filter.d.ts.map +1 -1
  27. package/dist/lib/filter.test.d.ts +2 -1
  28. package/dist/lib/filter.test.d.ts.map +1 -1
  29. package/dist/lib/finalizer.d.ts +6 -0
  30. package/dist/lib/finalizer.d.ts.map +1 -0
  31. package/dist/lib/finalizer.test.d.ts +2 -0
  32. package/dist/lib/finalizer.test.d.ts.map +1 -0
  33. package/dist/lib/helpers.d.ts +2 -2
  34. package/dist/lib/helpers.d.ts.map +1 -1
  35. package/dist/lib/helpers.test.d.ts +1 -1
  36. package/dist/lib/helpers.test.d.ts.map +1 -1
  37. package/dist/lib/k8s.d.ts.map +1 -1
  38. package/dist/lib/logger.d.ts +1 -1
  39. package/dist/lib/logger.d.ts.map +1 -1
  40. package/dist/lib/module.d.ts +2 -1
  41. package/dist/lib/module.d.ts.map +1 -1
  42. package/dist/lib/mutate-processor.d.ts +2 -1
  43. package/dist/lib/mutate-processor.d.ts.map +1 -1
  44. package/dist/lib/mutate-request.d.ts +1 -2
  45. package/dist/lib/mutate-request.d.ts.map +1 -1
  46. package/dist/lib/queue.d.ts +19 -3
  47. package/dist/lib/queue.d.ts.map +1 -1
  48. package/dist/lib/schedule.d.ts +1 -2
  49. package/dist/lib/schedule.d.ts.map +1 -1
  50. package/dist/lib/storage.d.ts.map +1 -1
  51. package/dist/lib/types.d.ts +118 -6
  52. package/dist/lib/types.d.ts.map +1 -1
  53. package/dist/lib/validate-processor.d.ts +4 -2
  54. package/dist/lib/validate-processor.d.ts.map +1 -1
  55. package/dist/lib/validate-request.d.ts +1 -1
  56. package/dist/lib/validate-request.d.ts.map +1 -1
  57. package/dist/lib/watch-processor.d.ts +8 -6
  58. package/dist/lib/watch-processor.d.ts.map +1 -1
  59. package/dist/lib.js +467 -233
  60. package/dist/lib.js.map +4 -4
  61. package/dist/sdk/sdk.d.ts +5 -3
  62. package/dist/sdk/sdk.d.ts.map +1 -1
  63. package/package.json +13 -11
  64. package/src/cli/build.ts +3 -3
  65. package/src/cli/init/index.ts +20 -11
  66. package/src/cli/init/templates.ts +1 -1
  67. package/src/cli/init/utils.test.ts +11 -20
  68. package/src/cli/init/utils.ts +5 -0
  69. package/src/cli/init/walkthrough.test.ts +92 -11
  70. package/src/cli/init/walkthrough.ts +71 -16
  71. package/src/cli/monitor.ts +1 -1
  72. package/src/cli.ts +4 -2
  73. package/src/fixtures/data/create-pod.json +1 -1
  74. package/src/fixtures/data/delete-pod.json +1 -1
  75. package/src/lib/adjudicators.test.ts +1232 -0
  76. package/src/lib/adjudicators.ts +235 -0
  77. package/src/lib/assets/index.ts +1 -1
  78. package/src/lib/assets/loader.ts +1 -0
  79. package/src/lib/assets/webhooks.ts +1 -1
  80. package/src/lib/capability.test.ts +655 -0
  81. package/src/lib/capability.ts +112 -11
  82. package/src/lib/controller/index.ts +7 -4
  83. package/src/lib/controller/store.test.ts +131 -0
  84. package/src/lib/controller/store.ts +43 -5
  85. package/src/lib/filter.test.ts +279 -9
  86. package/src/lib/filter.ts +46 -98
  87. package/src/lib/finalizer.test.ts +236 -0
  88. package/src/lib/finalizer.ts +63 -0
  89. package/src/lib/helpers.test.ts +359 -65
  90. package/src/lib/helpers.ts +141 -95
  91. package/src/lib/k8s.ts +4 -0
  92. package/src/lib/module.ts +3 -3
  93. package/src/lib/mutate-processor.ts +5 -4
  94. package/src/lib/mutate-request.test.ts +1 -2
  95. package/src/lib/mutate-request.ts +1 -3
  96. package/src/lib/queue.test.ts +138 -44
  97. package/src/lib/queue.ts +48 -13
  98. package/src/lib/schedule.ts +1 -1
  99. package/src/lib/storage.ts +5 -6
  100. package/src/lib/types.ts +154 -5
  101. package/src/lib/validate-processor.ts +5 -2
  102. package/src/lib/validate-request.test.ts +1 -4
  103. package/src/lib/validate-request.ts +1 -1
  104. package/src/lib/watch-processor.test.ts +89 -124
  105. package/src/lib/watch-processor.ts +52 -35
  106. package/src/sdk/sdk.test.ts +46 -13
  107. package/src/sdk/sdk.ts +15 -6
@@ -1,11 +1,11 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
- import { afterAll, beforeEach, beforeAll, describe, expect, it, jest } from "@jest/globals";
3
+ import { afterAll, beforeEach, describe, expect, it, jest } from "@jest/globals";
4
4
  import { GenericClass, K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
5
5
  import { K8sInit, WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
6
6
  import { WatchCfg, WatchEvent, Watcher } from "kubernetes-fluent-client/dist/fluent/watch";
7
7
  import { Capability } from "./capability";
8
- import { setupWatch, logEvent, queueRecordKey } from "./watch-processor";
8
+ import { setupWatch, logEvent, queueKey, getOrCreateQueue } from "./watch-processor";
9
9
  import Log from "./logger";
10
10
  import { metricsCollector } from "./metrics";
11
11
 
@@ -40,6 +40,7 @@ describe("WatchProcessor", () => {
40
40
  bindings: [
41
41
  {
42
42
  isWatch: true,
43
+ isQueue: false,
43
44
  model: "someModel",
44
45
  filters: {},
45
46
  event: "Create",
@@ -93,8 +94,15 @@ describe("WatchProcessor", () => {
93
94
 
94
95
  capabilities.push({
95
96
  bindings: [
96
- { isWatch: true, model: "someModel", filters: { name: "bleh" }, event: "Create", watchCallback: jest.fn() },
97
- { isWatch: false, model: "someModel", filters: {}, event: "Create", watchCallback: jest.fn() },
97
+ {
98
+ isWatch: true,
99
+ isQueue: true,
100
+ model: "someModel",
101
+ filters: { name: "bleh" },
102
+ event: "Create",
103
+ watchCallback: jest.fn(),
104
+ },
105
+ { isWatch: false, isQueue: false, model: "someModel", filters: {}, event: "Create", watchCallback: jest.fn() },
98
106
  ],
99
107
  } as unknown as Capability);
100
108
 
@@ -321,133 +329,90 @@ describe("logEvent function", () => {
321
329
  });
322
330
  });
323
331
 
324
- describe("queueRecordKey", () => {
325
- describe("PEPR_RECONCILE_STRATEGY=sharded", () => {
326
- const original = process.env.PEPR_RECONCILE_STRATEGY;
327
-
328
- beforeAll(() => {
329
- process.env.PEPR_RECONCILE_STRATEGY = "sharded";
330
- });
331
- afterAll(() => {
332
- process.env.PEPR_RECONCILE_STRATEGY = original;
333
- });
334
-
335
- it("should return correct key for an object with 'kind/name/namespace'", () => {
336
- const obj: KubernetesObject = {
337
- kind: "Pod",
338
- metadata: {
339
- name: "my-pod",
340
- namespace: "my-namespace",
341
- },
342
- };
343
-
344
- expect(queueRecordKey(obj)).toBe("Pod/my-pod/my-namespace");
345
- });
346
-
347
- it("should handle objects with missing namespace", () => {
348
- const obj: KubernetesObject = {
349
- kind: "Pod",
350
- metadata: {
351
- name: "my-pod",
352
- },
353
- };
354
-
355
- expect(queueRecordKey(obj)).toBe("Pod/my-pod/cluster-scoped");
356
- });
357
-
358
- it("should handle objects with missing name", () => {
359
- const obj: KubernetesObject = {
360
- kind: "Pod",
361
- metadata: {
362
- namespace: "my-namespace",
363
- },
364
- };
365
-
366
- expect(queueRecordKey(obj)).toBe("Pod/Unnamed/my-namespace");
367
- });
368
-
369
- it("should handle objects with missing metadata", () => {
370
- const obj: KubernetesObject = {
371
- kind: "Pod",
372
- };
373
-
374
- expect(queueRecordKey(obj)).toBe("Pod/Unnamed/cluster-scoped");
375
- });
376
-
377
- it("should handle objects with missing kind", () => {
378
- const obj: KubernetesObject = {
379
- metadata: {
380
- name: "my-pod",
381
- namespace: "my-namespace",
382
- },
383
- };
384
-
385
- expect(queueRecordKey(obj)).toBe("UnknownKind/my-pod/my-namespace");
386
- });
387
-
388
- it("should handle completely empty objects", () => {
389
- const obj: KubernetesObject = {};
390
-
391
- expect(queueRecordKey(obj)).toBe("UnknownKind/Unnamed/cluster-scoped");
392
- });
332
+ describe("queueKey", () => {
333
+ const withKindNsName = { kind: "Pod", metadata: { namespace: "my-ns", name: "my-name" } } as KubernetesObject;
334
+ const withKindNs = { kind: "Pod", metadata: { namespace: "my-ns" } } as KubernetesObject;
335
+ const withKindName = { kind: "Pod", metadata: { name: "my-name" } } as KubernetesObject;
336
+ const withNsName = { metadata: { namespace: "my-ns", name: "my-name" } } as KubernetesObject;
337
+ const withKind = { kind: "Pod" } as KubernetesObject;
338
+ const withNs = { metadata: { namespace: "my-ns" } } as KubernetesObject;
339
+ const withName = { metadata: { name: "my-name" } } as KubernetesObject;
340
+ const withNone = {} as KubernetesObject;
341
+
342
+ const original = process.env.PEPR_RECONCILE_STRATEGY;
343
+
344
+ it.each([
345
+ ["kind", withKindNsName, "Pod"],
346
+ ["kind", withKindNs, "Pod"],
347
+ ["kind", withKindName, "Pod"],
348
+ ["kind", withNsName, "UnknownKind"],
349
+ ["kind", withKind, "Pod"],
350
+ ["kind", withNs, "UnknownKind"],
351
+ ["kind", withName, "UnknownKind"],
352
+ ["kind", withNone, "UnknownKind"],
353
+ ["kindNs", withKindNsName, "Pod/my-ns"],
354
+ ["kindNs", withKindNs, "Pod/my-ns"],
355
+ ["kindNs", withKindName, "Pod/cluster-scoped"],
356
+ ["kindNs", withNsName, "UnknownKind/my-ns"],
357
+ ["kindNs", withKind, "Pod/cluster-scoped"],
358
+ ["kindNs", withNs, "UnknownKind/my-ns"],
359
+ ["kindNs", withName, "UnknownKind/cluster-scoped"],
360
+ ["kindNs", withNone, "UnknownKind/cluster-scoped"],
361
+ ["kindNsName", withKindNsName, "Pod/my-ns/my-name"],
362
+ ["kindNsName", withKindNs, "Pod/my-ns/Unnamed"],
363
+ ["kindNsName", withKindName, "Pod/cluster-scoped/my-name"],
364
+ ["kindNsName", withNsName, "UnknownKind/my-ns/my-name"],
365
+ ["kindNsName", withKind, "Pod/cluster-scoped/Unnamed"],
366
+ ["kindNsName", withNs, "UnknownKind/my-ns/Unnamed"],
367
+ ["kindNsName", withName, "UnknownKind/cluster-scoped/my-name"],
368
+ ["kindNsName", withNone, "UnknownKind/cluster-scoped/Unnamed"],
369
+ ["global", withKindNsName, "global"],
370
+ ["global", withKindNs, "global"],
371
+ ["global", withKindName, "global"],
372
+ ["global", withNsName, "global"],
373
+ ["global", withKind, "global"],
374
+ ["global", withNs, "global"],
375
+ ["global", withName, "global"],
376
+ ["global", withNone, "global"],
377
+ ])("PEPR_RECONCILE_STRATEGY='%s' over '%j' becomes '%s'", (strat, obj, key) => {
378
+ process.env.PEPR_RECONCILE_STRATEGY = strat;
379
+ expect(queueKey(obj)).toBe(key);
393
380
  });
394
381
 
395
- describe("PEPR_RECONCILE_STRATEGY=singular", () => {
396
- const original = process.env.PEPR_RECONCILE_STRATEGY;
397
-
398
- beforeAll(() => {
399
- process.env.PEPR_RECONCILE_STRATEGY = "singular";
400
- });
401
- afterAll(() => {
402
- process.env.PEPR_RECONCILE_STRATEGY = original;
403
- });
404
-
405
- it("should return correct key for an object with 'kind/namespace'", () => {
406
- const obj: KubernetesObject = {
407
- kind: "Pod",
408
- metadata: {
409
- name: "my-pod",
410
- namespace: "my-namespace",
411
- },
412
- };
413
-
414
- expect(queueRecordKey(obj)).toBe("Pod/my-namespace");
415
- });
416
-
417
- it("should handle objects with missing namespace", () => {
418
- const obj: KubernetesObject = {
419
- kind: "Pod",
420
- metadata: {
421
- name: "my-pod",
422
- },
423
- };
424
-
425
- expect(queueRecordKey(obj)).toBe("Pod/cluster-scoped");
426
- });
382
+ afterAll(() => {
383
+ process.env.PEPR_RECONCILE_STRATEGY = original;
384
+ });
385
+ });
427
386
 
428
- it("should handle objects with missing metadata", () => {
429
- const obj: KubernetesObject = {
430
- kind: "Pod",
431
- };
387
+ describe("getOrCreateQueue", () => {
388
+ it("creates a Queue instance on first call", () => {
389
+ const obj: KubernetesObject = {
390
+ kind: "queue",
391
+ metadata: {
392
+ name: "nm",
393
+ namespace: "ns",
394
+ },
395
+ };
432
396
 
433
- expect(queueRecordKey(obj)).toBe("Pod/cluster-scoped");
434
- });
397
+ const firstQueue = getOrCreateQueue(obj);
398
+ expect(firstQueue.label()).toBeDefined();
399
+ });
435
400
 
436
- it("should handle objects with missing kind", () => {
437
- const obj: KubernetesObject = {
438
- metadata: {
439
- name: "my-pod",
440
- namespace: "my-namespace",
441
- },
442
- };
401
+ it("returns same Queue instance on subsequent calls", () => {
402
+ const obj: KubernetesObject = {
403
+ kind: "queue",
404
+ metadata: {
405
+ name: "nm",
406
+ namespace: "ns",
407
+ },
408
+ };
443
409
 
444
- expect(queueRecordKey(obj)).toBe("UnknownKind/my-namespace");
445
- });
410
+ const firstQueue = getOrCreateQueue(obj);
411
+ expect(firstQueue.label()).toBeDefined();
446
412
 
447
- it("should handle completely empty objects", () => {
448
- const obj: KubernetesObject = {};
413
+ const secondQueue = getOrCreateQueue(obj);
414
+ expect(secondQueue.label()).toBeDefined();
449
415
 
450
- expect(queueRecordKey(obj)).toBe("UnknownKind/cluster-scoped");
451
- });
416
+ expect(firstQueue).toBe(secondQueue);
452
417
  });
453
418
  });
@@ -4,23 +4,24 @@ import { K8s, KubernetesObject, WatchCfg, WatchEvent } from "kubernetes-fluent-c
4
4
  import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
5
5
  import { Capability } from "./capability";
6
6
  import { filterNoMatchReason } from "./helpers";
7
+ import { removeFinalizer } from "./finalizer";
7
8
  import Log from "./logger";
8
9
  import { Queue } from "./queue";
9
10
  import { Binding, Event } from "./types";
10
11
  import { metricsCollector } from "./metrics";
11
12
 
12
- // init a queueRecord record to store Queue instances for a given Kubernetes Object
13
- const queueRecord: Record<string, Queue<KubernetesObject>> = {};
13
+ // stores Queue instances
14
+ const queues: Record<string, Queue<KubernetesObject>> = {};
14
15
 
15
16
  /**
16
- * Get the key for a record in the queueRecord
17
+ * Get the key for an entry in the queues
17
18
  *
18
- * @param obj The object to get the key for
19
- * @returns The key for the object
19
+ * @param obj The object to derive a key from
20
+ * @returns The key to a Queue in the list of queues
20
21
  */
21
- export function queueRecordKey(obj: KubernetesObject) {
22
- const options = ["singular", "sharded"]; // TODO : ts-type this fella
23
- const d3fault = "singular";
22
+ export function queueKey(obj: KubernetesObject) {
23
+ const options = ["kind", "kindNs", "kindNsName", "global"];
24
+ const d3fault = "kind";
24
25
 
25
26
  let strat = process.env.PEPR_RECONCILE_STRATEGY || d3fault;
26
27
  strat = options.includes(strat) ? strat : d3fault;
@@ -29,7 +30,21 @@ export function queueRecordKey(obj: KubernetesObject) {
29
30
  const kind = obj.kind ?? "UnknownKind";
30
31
  const name = obj.metadata?.name ?? "Unnamed";
31
32
 
32
- return strat === "singular" ? `${kind}/${ns}` : `${kind}/${name}/${ns}`;
33
+ const lookup: Record<string, string> = {
34
+ kind: `${kind}`,
35
+ kindNs: `${kind}/${ns}`,
36
+ kindNsName: `${kind}/${ns}/${name}`,
37
+ global: "global",
38
+ };
39
+ return lookup[strat];
40
+ }
41
+
42
+ export function getOrCreateQueue(obj: KubernetesObject) {
43
+ const key = queueKey(obj);
44
+ if (!queues[key]) {
45
+ queues[key] = new Queue<KubernetesObject>(key);
46
+ }
47
+ return queues[key];
33
48
  }
34
49
 
35
50
  // Watch configuration
@@ -58,11 +73,11 @@ const eventToPhaseMap = {
58
73
  *
59
74
  * @param capabilities The capabilities to load watches for
60
75
  */
61
- export function setupWatch(capabilities: Capability[]) {
76
+ export function setupWatch(capabilities: Capability[], ignoredNamespaces?: string[]) {
62
77
  capabilities.map(capability =>
63
78
  capability.bindings
64
79
  .filter(binding => binding.isWatch)
65
- .forEach(bindingElement => runBinding(bindingElement, capability.namespaces)),
80
+ .forEach(bindingElement => runBinding(bindingElement, capability.namespaces, ignoredNamespaces)),
66
81
  );
67
82
  }
68
83
 
@@ -72,21 +87,34 @@ export function setupWatch(capabilities: Capability[]) {
72
87
  * @param binding the binding to watch
73
88
  * @param capabilityNamespaces list of namespaces to filter on
74
89
  */
75
- async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
90
+ async function runBinding(binding: Binding, capabilityNamespaces: string[], ignoredNamespaces?: string[]) {
76
91
  // Get the phases to match, fallback to any
77
92
  const phaseMatch: WatchPhase[] = eventToPhaseMap[binding.event] || eventToPhaseMap[Event.Any];
78
93
 
79
94
  // The watch callback is run when an object is received or dequeued
80
-
81
95
  Log.debug({ watchCfg }, "Effective WatchConfig");
82
- const watchCallback = async (obj: KubernetesObject, type: WatchPhase) => {
96
+
97
+ const watchCallback = async (obj: KubernetesObject, phase: WatchPhase) => {
83
98
  // First, filter the object based on the phase
84
- if (phaseMatch.includes(type)) {
99
+ if (phaseMatch.includes(phase)) {
85
100
  try {
86
101
  // Then, check if the object matches the filter
87
- const filterMatch = filterNoMatchReason(binding, obj, capabilityNamespaces);
102
+ const filterMatch = filterNoMatchReason(binding, obj, capabilityNamespaces, ignoredNamespaces);
88
103
  if (filterMatch === "") {
89
- await binding.watchCallback?.(obj, type);
104
+ if (binding.isFinalize) {
105
+ if (!obj.metadata?.deletionTimestamp) {
106
+ return;
107
+ }
108
+ try {
109
+ await binding.finalizeCallback?.(obj);
110
+
111
+ // irrespective of callback success / failure, remove pepr finalizer
112
+ } finally {
113
+ await removeFinalizer(binding, obj);
114
+ }
115
+ } else {
116
+ await binding.watchCallback?.(obj, phase);
117
+ }
90
118
  } else {
91
119
  Log.debug(filterMatch);
92
120
  }
@@ -97,26 +125,15 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
97
125
  }
98
126
  };
99
127
 
100
- function getOrCreateQueue(key: string): Queue<KubernetesObject> {
101
- if (!queueRecord[key]) {
102
- queueRecord[key] = new Queue<KubernetesObject>();
103
- queueRecord[key].setReconcile(watchCallback);
104
- }
105
- return queueRecord[key];
106
- }
107
-
108
128
  // Setup the resource watch
109
- const watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {
110
- Log.debug(obj, `Watch event ${type} received`);
111
-
112
- const queue = getOrCreateQueue(queueRecordKey(obj));
129
+ const watcher = K8s(binding.model, binding.filters).Watch(async (obj, phase) => {
130
+ Log.debug(obj, `Watch event ${phase} received`);
113
131
 
114
- // If the binding is a queue, enqueue the object
115
132
  if (binding.isQueue) {
116
- await queue.enqueue(obj, type);
133
+ const queue = getOrCreateQueue(obj);
134
+ await queue.enqueue(obj, phase, watchCallback);
117
135
  } else {
118
- // Otherwise, run the watch callback directly
119
- await watchCallback(obj, type);
136
+ await watchCallback(obj, phase);
120
137
  }
121
138
  }, watchCfg);
122
139
 
@@ -160,8 +177,8 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
160
177
  }
161
178
  }
162
179
 
163
- export function logEvent(type: WatchEvent, message: string = "", obj?: KubernetesObject) {
164
- const logMessage = `Watch event ${type} received${message ? `. ${message}.` : "."}`;
180
+ export function logEvent(event: WatchEvent, message: string = "", obj?: KubernetesObject) {
181
+ const logMessage = `Watch event ${event} received${message ? `. ${message}.` : "."}`;
165
182
  if (obj) {
166
183
  Log.debug(obj, logMessage);
167
184
  } else {
@@ -11,6 +11,7 @@ import { beforeEach, describe, it, jest } from "@jest/globals";
11
11
  import { GenericKind } from "kubernetes-fluent-client";
12
12
  import { K8s, kind } from "kubernetes-fluent-client";
13
13
  import { Mock } from "jest-mock";
14
+ import { V1OwnerReference } from "@kubernetes/client-node";
14
15
 
15
16
  jest.mock("kubernetes-fluent-client", () => ({
16
17
  K8s: jest.fn(),
@@ -163,23 +164,55 @@ describe("writeEvent", () => {
163
164
  });
164
165
 
165
166
  describe("getOwnerRefFrom", () => {
166
- it("should return the owner reference for the CRD", () => {
167
- const cr = {
167
+ const customResource = {
168
+ apiVersion: "v1",
169
+ kind: "Package",
170
+ metadata: { name: "test", namespace: "default", uid: "1" },
171
+ };
172
+
173
+ const ownerRef = [
174
+ {
168
175
  apiVersion: "v1",
169
176
  kind: "Package",
170
- metadata: { name: "test", namespace: "default", uid: "1" },
171
- };
172
- const ownerRef = getOwnerRefFrom(cr as GenericKind);
173
- expect(ownerRef).toEqual([
174
- {
175
- apiVersion: "v1",
176
- kind: "Package",
177
- name: "test",
178
- uid: "1",
179
- },
180
- ]);
177
+ name: "test",
178
+ uid: "1",
179
+ },
180
+ ];
181
+
182
+ const ownerRefWithController = ownerRef.map(item => ({
183
+ ...item,
184
+ controller: true,
185
+ }));
186
+ const ownerRefWithBlockOwnerDeletion = ownerRef.map(item => ({
187
+ ...item,
188
+ blockOwnerDeletion: false,
189
+ }));
190
+ const ownerRefWithAllFields = ownerRef.map(item => ({
191
+ ...item,
192
+ blockOwnerDeletion: true,
193
+ controller: false,
194
+ }));
195
+
196
+ test.each([
197
+ [true, false, ownerRefWithAllFields],
198
+ [false, undefined, ownerRefWithBlockOwnerDeletion],
199
+ [undefined, true, ownerRefWithController],
200
+ [undefined, undefined, ownerRef],
201
+ ])(
202
+ "should return owner reference for the CRD for combinations of V1OwnerReference fields - Optionals: blockOwnerDeletion (%s), controller (%s)",
203
+ (blockOwnerDeletion, controller, expected) => {
204
+ const result = getOwnerRefFrom(customResource, blockOwnerDeletion, controller);
205
+ expect(result).toStrictEqual(expected);
206
+ },
207
+ );
208
+
209
+ it("should support all defined fields in the V1OwnerReference type", () => {
210
+ const V1OwnerReferenceFieldCount = Object.getOwnPropertyNames(V1OwnerReference).length;
211
+ const result = getOwnerRefFrom(customResource, false, true);
212
+ expect(Object.keys(result[0]).length).toEqual(V1OwnerReferenceFieldCount);
181
213
  });
182
214
  });
215
+
183
216
  describe("sanitizeResourceName Fuzzing Tests", () => {
184
217
  test("should handle any random string input", () => {
185
218
  fc.assert(
package/src/sdk/sdk.ts CHANGED
@@ -79,18 +79,27 @@ export async function writeEvent(
79
79
 
80
80
  /**
81
81
  * Get the owner reference for a custom resource
82
- * @param cr the custom resource to get the owner reference for
83
- * @returns the owner reference for the custom resource
82
+ * @param customResource the custom resource to get the owner reference for
83
+ * @param blockOwnerDeletion if true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed.
84
+ * @param controller if true, this reference points to the managing controller.
85
+ * @returns the owner reference array for the custom resource
84
86
  */
85
- export function getOwnerRefFrom(cr: GenericKind): V1OwnerReference[] {
86
- const { name, uid } = cr.metadata!;
87
+ export function getOwnerRefFrom(
88
+ customResource: GenericKind,
89
+ blockOwnerDeletion?: boolean,
90
+ controller?: boolean,
91
+ ): V1OwnerReference[] {
92
+ const { apiVersion, kind, metadata } = customResource;
93
+ const { name, uid } = metadata!;
87
94
 
88
95
  return [
89
96
  {
90
- apiVersion: cr.apiVersion!,
91
- kind: cr.kind!,
97
+ apiVersion: apiVersion!,
98
+ kind: kind!,
92
99
  uid: uid!,
93
100
  name: name!,
101
+ ...(blockOwnerDeletion !== undefined && { blockOwnerDeletion }),
102
+ ...(controller !== undefined && { controller }),
94
103
  },
95
104
  ];
96
105
  }