pepr 0.28.5 → 0.28.7
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/README.md +2 -2
- package/dist/cli.js +6 -6
- package/dist/controller.js +1 -1
- package/dist/lib/helpers.d.ts +4 -5
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/queue.d.ts +3 -2
- package/dist/lib/queue.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts +5 -0
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +90 -102
- package/dist/lib.js.map +3 -3
- package/package.json +3 -3
- package/src/lib/helpers.ts +10 -10
- package/src/lib/queue.ts +8 -5
- package/src/lib/watch-processor.ts +60 -61
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=18.0.0"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.28.
|
|
12
|
+
"version": "0.28.7",
|
|
13
13
|
"main": "dist/lib.js",
|
|
14
14
|
"types": "dist/lib.d.ts",
|
|
15
15
|
"scripts": {
|
|
@@ -32,12 +32,12 @@
|
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"@types/ramda": "0.29.11",
|
|
35
|
-
"express": "4.19.
|
|
35
|
+
"express": "4.19.2",
|
|
36
36
|
"fast-json-patch": "3.1.1",
|
|
37
37
|
"kubernetes-fluent-client": "2.3.0",
|
|
38
38
|
"pino": "8.19.0",
|
|
39
39
|
"pino-pretty": "11.0.0",
|
|
40
|
-
"prom-client": "15.1.
|
|
40
|
+
"prom-client": "15.1.1",
|
|
41
41
|
"ramda": "0.29.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
package/src/lib/helpers.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
|
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?: (
|
|
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: (
|
|
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
|
-
//
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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 => {
|