pepr 0.28.5 → 0.28.6

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.
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "engines": {
10
10
  "node": ">=18.0.0"
11
11
  },
12
- "version": "0.28.5",
12
+ "version": "0.28.6",
13
13
  "main": "dist/lib.js",
14
14
  "types": "dist/lib.d.ts",
15
15
  "scripts": {
@@ -1,11 +1,10 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
+ import { promises as fs } from "fs";
4
5
  import { K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
5
6
  import Log from "./logger";
6
- import { CapabilityExport } from "./types";
7
- import { promises as fs } from "fs";
8
- import { Binding } from "./types";
7
+ import { Binding, CapabilityExport } from "./types";
9
8
 
10
9
  type RBACMap = {
11
10
  [key: string]: {
@@ -47,11 +46,11 @@ export function checkOverlap(bindingFilters: Record<string, string>, objectFilte
47
46
  /**
48
47
  * Decide to run callback after the event comes back from API Server
49
48
  **/
50
- export const filterMatcher = (
49
+ export function filterNoMatchReason(
51
50
  binding: Partial<Binding>,
52
51
  obj: Partial<KubernetesObject>,
53
52
  capabilityNamespaces: string[],
54
- ): string => {
53
+ ): string {
55
54
  // binding kind is namespace with a InNamespace filter
56
55
  if (binding.kind && binding.kind.kind === "Namespace" && binding.filters && binding.filters.namespaces.length !== 0) {
57
56
  return `Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.`;
@@ -116,14 +115,15 @@ export const filterMatcher = (
116
115
 
117
116
  // no problems
118
117
  return "";
119
- };
120
- export const addVerbIfNotExists = (verbs: string[], verb: string) => {
118
+ }
119
+
120
+ export function addVerbIfNotExists(verbs: string[], verb: string) {
121
121
  if (!verbs.includes(verb)) {
122
122
  verbs.push(verb);
123
123
  }
124
- };
124
+ }
125
125
 
126
- export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => {
126
+ export function createRBACMap(capabilities: CapabilityExport[]): RBACMap {
127
127
  return capabilities.reduce((acc: RBACMap, capability: CapabilityExport) => {
128
128
  capability.bindings.forEach(binding => {
129
129
  const key = `${binding.kind.group}/${binding.kind.version}/${binding.kind.kind}`;
@@ -148,7 +148,7 @@ export const createRBACMap = (capabilities: CapabilityExport[]): RBACMap => {
148
148
 
149
149
  return acc;
150
150
  }, {});
151
- };
151
+ }
152
152
 
153
153
  export async function createDirectoryIfNotExists(path: string) {
154
154
  try {
package/src/lib/queue.ts CHANGED
@@ -1,10 +1,12 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
  import { KubernetesObject } from "@kubernetes/client-node";
4
+ import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
4
5
  import Log from "./logger";
5
6
 
6
7
  type QueueItem<K extends KubernetesObject> = {
7
8
  item: K;
9
+ type: WatchPhase;
8
10
  resolve: (value: void | PromiseLike<void>) => void;
9
11
  reject: (reason?: string) => void;
10
12
  };
@@ -15,15 +17,16 @@ type QueueItem<K extends KubernetesObject> = {
15
17
  export class Queue<K extends KubernetesObject> {
16
18
  #queue: QueueItem<K>[] = [];
17
19
  #pendingPromise = false;
18
- #reconcile?: (...args: unknown[]) => Promise<void>;
20
+ #reconcile?: (obj: KubernetesObject, type: WatchPhase) => Promise<void>;
19
21
 
20
22
  constructor() {
21
23
  this.#reconcile = async () => await new Promise(resolve => resolve());
22
24
  }
23
25
 
24
- setReconcile(reconcile: (...args: unknown[]) => Promise<void>) {
26
+ setReconcile(reconcile: (obj: KubernetesObject, type: WatchPhase) => Promise<void>) {
25
27
  this.#reconcile = reconcile;
26
28
  }
29
+
27
30
  /**
28
31
  * Enqueue adds an item to the queue and returns a promise that resolves when the item is
29
32
  * reconciled.
@@ -31,10 +34,10 @@ export class Queue<K extends KubernetesObject> {
31
34
  * @param item The object to reconcile
32
35
  * @returns A promise that resolves when the object is reconciled
33
36
  */
34
- enqueue(item: K) {
37
+ enqueue(item: K, type: WatchPhase) {
35
38
  Log.debug(`Enqueueing ${item.metadata!.namespace}/${item.metadata!.name}`);
36
39
  return new Promise<void>((resolve, reject) => {
37
- this.#queue.push({ item, resolve, reject });
40
+ this.#queue.push({ item, type, resolve, reject });
38
41
  return this.#dequeue();
39
42
  });
40
43
  }
@@ -67,7 +70,7 @@ export class Queue<K extends KubernetesObject> {
67
70
  // Reconcile the element
68
71
  if (this.#reconcile) {
69
72
  Log.debug(`Reconciling ${element.item.metadata!.name}`);
70
- await this.#reconcile(element.item);
73
+ await this.#reconcile(element.item, element.type);
71
74
  }
72
75
 
73
76
  element.resolve();
@@ -1,15 +1,33 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
- import { K8s, WatchCfg, WatchEvent } from "kubernetes-fluent-client";
3
+ import { K8s, KubernetesObject, WatchCfg, WatchEvent } from "kubernetes-fluent-client";
4
4
  import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
5
- import { Queue } from "./queue";
6
5
  import { Capability } from "./capability";
6
+ import { filterNoMatchReason } from "./helpers";
7
7
  import Log from "./logger";
8
+ import { Queue } from "./queue";
8
9
  import { Binding, Event } from "./types";
9
- import { Watcher } from "kubernetes-fluent-client/dist/fluent/watch";
10
- import { GenericClass } from "kubernetes-fluent-client";
11
- import { filterMatcher } from "./helpers";
12
10
 
11
+ // Watch configuration
12
+ const watchCfg: WatchCfg = {
13
+ retryMax: 5,
14
+ retryDelaySec: 5,
15
+ };
16
+
17
+ // Map the event to the watch phase
18
+ const eventToPhaseMap = {
19
+ [Event.Create]: [WatchPhase.Added],
20
+ [Event.Update]: [WatchPhase.Modified],
21
+ [Event.CreateOrUpdate]: [WatchPhase.Added, WatchPhase.Modified],
22
+ [Event.Delete]: [WatchPhase.Deleted],
23
+ [Event.Any]: [WatchPhase.Added, WatchPhase.Modified, WatchPhase.Deleted],
24
+ };
25
+
26
+ /**
27
+ * Entrypoint for setting up watches for all capabilities
28
+ *
29
+ * @param capabilities The capabilities to load watches for
30
+ */
13
31
  export function setupWatch(capabilities: Capability[]) {
14
32
  capabilities.map(capability =>
15
33
  capability.bindings
@@ -18,69 +36,50 @@ export function setupWatch(capabilities: Capability[]) {
18
36
  );
19
37
  }
20
38
 
39
+ /**
40
+ * Setup a watch for a binding
41
+ *
42
+ * @param binding the binding to watch
43
+ * @param capabilityNamespaces list of namespaces to filter on
44
+ */
21
45
  async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
22
- // Map the event to the watch phase
23
- const eventToPhaseMap = {
24
- [Event.Create]: [WatchPhase.Added],
25
- [Event.Update]: [WatchPhase.Modified],
26
- [Event.CreateOrUpdate]: [WatchPhase.Added, WatchPhase.Modified],
27
- [Event.Delete]: [WatchPhase.Deleted],
28
- [Event.Any]: [WatchPhase.Added, WatchPhase.Modified, WatchPhase.Deleted],
29
- };
30
-
31
- // Get the phases to match, default to any
46
+ // Get the phases to match, fallback to any
32
47
  const phaseMatch: WatchPhase[] = eventToPhaseMap[binding.event] || eventToPhaseMap[Event.Any];
33
48
 
34
- const watchCfg: WatchCfg = {
35
- retryMax: 5,
36
- retryDelaySec: 5,
49
+ // The watch callback is run when an object is received or dequeued
50
+ const watchCallback = async (obj: KubernetesObject, type: WatchPhase) => {
51
+ // First, filter the object based on the phase
52
+ if (phaseMatch.includes(type)) {
53
+ try {
54
+ // Then, check if the object matches the filter
55
+ const filterMatch = filterNoMatchReason(binding, obj, capabilityNamespaces);
56
+ if (filterMatch === "") {
57
+ await binding.watchCallback?.(obj, type);
58
+ } else {
59
+ Log.debug(filterMatch);
60
+ }
61
+ } catch (e) {
62
+ // Errors in the watch callback should not crash the controller
63
+ Log.error(e, "Error executing watch callback");
64
+ }
65
+ }
37
66
  };
38
67
 
39
- let watcher: Watcher<GenericClass>;
40
- if (binding.isQueue) {
41
- const queue = new Queue();
42
- // Watch the resource
43
- watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {
44
- Log.debug(obj, `Watch event ${type} received`);
68
+ const queue = new Queue();
69
+ queue.setReconcile(watchCallback);
45
70
 
46
- // If the type matches the phase, call the watch callback
47
- if (phaseMatch.includes(type)) {
48
- try {
49
- const filterMatch = filterMatcher(binding, obj, capabilityNamespaces);
50
- if (filterMatch === "") {
51
- queue.setReconcile(async () => await binding.watchCallback?.(obj, type));
52
- // Enqueue the object for reconciliation through callback
53
- await queue.enqueue(obj);
54
- } else {
55
- Log.debug(filterMatch);
56
- }
57
- } catch (e) {
58
- // Errors in the watch callback should not crash the controller
59
- Log.error(e, "Error executing watch callback");
60
- }
61
- }
62
- }, watchCfg);
63
- } else {
64
- // Watch the resource
65
- watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {
66
- Log.debug(obj, `Watch event ${type} received`);
71
+ // Setup the resource watch
72
+ const watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {
73
+ Log.debug(obj, `Watch event ${type} received`);
67
74
 
68
- // If the type matches the phase, call the watch callback
69
- if (phaseMatch.includes(type)) {
70
- try {
71
- const filterMatch = filterMatcher(binding, obj, capabilityNamespaces);
72
- if (filterMatch === "") {
73
- await binding.watchCallback?.(obj, type);
74
- } else {
75
- Log.debug(filterMatch);
76
- }
77
- } catch (e) {
78
- // Errors in the watch callback should not crash the controller
79
- Log.error(e, "Error executing watch callback");
80
- }
81
- }
82
- }, watchCfg);
83
- }
75
+ // If the binding is a queue, enqueue the object
76
+ if (binding.isQueue) {
77
+ await queue.enqueue(obj, type);
78
+ } else {
79
+ // Otherwise, run the watch callback directly
80
+ await watchCallback(obj, type);
81
+ }
82
+ }, watchCfg);
84
83
 
85
84
  // If failure continues, log and exit
86
85
  watcher.events.on(WatchEvent.GIVE_UP, err => {