pepr 0.34.1 → 0.35.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.
- package/dist/cli/init/templates.d.ts +1 -0
- package/dist/cli/init/templates.d.ts.map +1 -1
- package/dist/cli.js +48 -23
- package/dist/controller.js +1 -1
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts +7 -0
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +20 -3
- package/dist/lib.js.map +3 -3
- package/package.json +8 -8
- package/src/cli/init/templates.ts +1 -0
- package/src/lib/assets/helm.ts +4 -16
- package/src/lib/assets/pods.ts +4 -0
- package/src/lib/assets/yaml.ts +32 -0
- package/src/lib/watch-processor.test.ts +134 -3
- package/src/lib/watch-processor.ts +33 -3
- package/src/templates/capabilities/hello-pepr.ts +9 -0
package/package.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"/dist",
|
|
14
14
|
"/src"
|
|
15
15
|
],
|
|
16
|
-
"version": "0.
|
|
16
|
+
"version": "0.35.0",
|
|
17
17
|
"main": "dist/lib.js",
|
|
18
18
|
"types": "dist/lib.d.ts",
|
|
19
19
|
"scripts": {
|
|
@@ -38,22 +38,22 @@
|
|
|
38
38
|
"format:fix": "eslint src --fix && prettier src --write"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@types/ramda": "0.30.
|
|
41
|
+
"@types/ramda": "0.30.2",
|
|
42
42
|
"express": "4.19.2",
|
|
43
43
|
"fast-json-patch": "3.1.1",
|
|
44
44
|
"json-pointer": "^0.6.2",
|
|
45
|
-
"kubernetes-fluent-client": "3.0.
|
|
45
|
+
"kubernetes-fluent-client": "3.0.2",
|
|
46
46
|
"pino": "9.3.2",
|
|
47
47
|
"pino-pretty": "11.2.2",
|
|
48
48
|
"prom-client": "15.1.3",
|
|
49
49
|
"ramda": "0.30.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@commitlint/cli": "19.4.
|
|
53
|
-
"@commitlint/config-conventional": "19.
|
|
52
|
+
"@commitlint/cli": "19.4.1",
|
|
53
|
+
"@commitlint/config-conventional": "19.4.1",
|
|
54
54
|
"@fast-check/jest": "^2.0.1",
|
|
55
55
|
"@jest/globals": "29.7.0",
|
|
56
|
-
"@types/eslint": "9.6.
|
|
56
|
+
"@types/eslint": "9.6.1",
|
|
57
57
|
"@types/express": "4.17.21",
|
|
58
58
|
"@types/json-pointer": "^1.0.34",
|
|
59
59
|
"@types/node": "22.x.x",
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"fast-check": "^3.19.0",
|
|
64
64
|
"jest": "29.7.0",
|
|
65
65
|
"js-yaml": "^4.1.0",
|
|
66
|
-
"nock": "13.5.4",
|
|
67
|
-
"ts-jest": "29.2.
|
|
66
|
+
"nock": "^13.5.4",
|
|
67
|
+
"ts-jest": "29.2.5"
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"@typescript-eslint/eslint-plugin": "7.18.0",
|
package/src/lib/assets/helm.ts
CHANGED
|
@@ -90,15 +90,9 @@ export function watcherDeployTemplate(buildTimestamp: string) {
|
|
|
90
90
|
- /app/node_modules/pepr/dist/controller.js
|
|
91
91
|
- {{ .Values.hash }}
|
|
92
92
|
readinessProbe:
|
|
93
|
-
|
|
94
|
-
path: /healthz
|
|
95
|
-
port: 3000
|
|
96
|
-
scheme: HTTPS
|
|
93
|
+
{{- toYaml .Values.watcher.readinessProbe | nindent 12 }}
|
|
97
94
|
livenessProbe:
|
|
98
|
-
|
|
99
|
-
path: /healthz
|
|
100
|
-
port: 3000
|
|
101
|
-
scheme: HTTPS
|
|
95
|
+
{{- toYaml .Values.watcher.livenessProbe | nindent 12 }}
|
|
102
96
|
ports:
|
|
103
97
|
- containerPort: 3000
|
|
104
98
|
resources:
|
|
@@ -176,15 +170,9 @@ export function admissionDeployTemplate(buildTimestamp: string) {
|
|
|
176
170
|
- /app/node_modules/pepr/dist/controller.js
|
|
177
171
|
- {{ .Values.hash }}
|
|
178
172
|
readinessProbe:
|
|
179
|
-
|
|
180
|
-
path: /healthz
|
|
181
|
-
port: 3000
|
|
182
|
-
scheme: HTTPS
|
|
173
|
+
{{- toYaml .Values.admission.readinessProbe | nindent 12 }}
|
|
183
174
|
livenessProbe:
|
|
184
|
-
|
|
185
|
-
path: /healthz
|
|
186
|
-
port: 3000
|
|
187
|
-
scheme: HTTPS
|
|
175
|
+
{{- toYaml .Values.admission.livenessProbe | nindent 12 }}
|
|
188
176
|
ports:
|
|
189
177
|
- containerPort: 3000
|
|
190
178
|
resources:
|
package/src/lib/assets/pods.ts
CHANGED
|
@@ -111,6 +111,7 @@ export function watcher(assets: Assets, hash: string, buildTimestamp: string, im
|
|
|
111
111
|
port: 3000,
|
|
112
112
|
scheme: "HTTPS",
|
|
113
113
|
},
|
|
114
|
+
initialDelaySeconds: 10,
|
|
114
115
|
},
|
|
115
116
|
livenessProbe: {
|
|
116
117
|
httpGet: {
|
|
@@ -118,6 +119,7 @@ export function watcher(assets: Assets, hash: string, buildTimestamp: string, im
|
|
|
118
119
|
port: 3000,
|
|
119
120
|
scheme: "HTTPS",
|
|
120
121
|
},
|
|
122
|
+
initialDelaySeconds: 10,
|
|
121
123
|
},
|
|
122
124
|
ports: [
|
|
123
125
|
{
|
|
@@ -248,6 +250,7 @@ export function deployment(
|
|
|
248
250
|
port: 3000,
|
|
249
251
|
scheme: "HTTPS",
|
|
250
252
|
},
|
|
253
|
+
initialDelaySeconds: 10,
|
|
251
254
|
},
|
|
252
255
|
livenessProbe: {
|
|
253
256
|
httpGet: {
|
|
@@ -255,6 +258,7 @@ export function deployment(
|
|
|
255
258
|
port: 3000,
|
|
256
259
|
scheme: "HTTPS",
|
|
257
260
|
},
|
|
261
|
+
initialDelaySeconds: 10,
|
|
258
262
|
},
|
|
259
263
|
ports: [
|
|
260
264
|
{
|
package/src/lib/assets/yaml.ts
CHANGED
|
@@ -45,6 +45,22 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
|
|
|
45
45
|
runAsNonRoot: true,
|
|
46
46
|
fsGroup: 65532,
|
|
47
47
|
},
|
|
48
|
+
readinessProbe: {
|
|
49
|
+
httpGet: {
|
|
50
|
+
path: "/healthz",
|
|
51
|
+
port: 3000,
|
|
52
|
+
scheme: "HTTPS",
|
|
53
|
+
},
|
|
54
|
+
initialDelaySeconds: 10,
|
|
55
|
+
},
|
|
56
|
+
livenessProbe: {
|
|
57
|
+
httpGet: {
|
|
58
|
+
path: "/healthz",
|
|
59
|
+
port: 3000,
|
|
60
|
+
scheme: "HTTPS",
|
|
61
|
+
},
|
|
62
|
+
initialDelaySeconds: 10,
|
|
63
|
+
},
|
|
48
64
|
resources: {
|
|
49
65
|
requests: {
|
|
50
66
|
memory: "64Mi",
|
|
@@ -95,6 +111,22 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
|
|
|
95
111
|
runAsNonRoot: true,
|
|
96
112
|
fsGroup: 65532,
|
|
97
113
|
},
|
|
114
|
+
readinessProbe: {
|
|
115
|
+
httpGet: {
|
|
116
|
+
path: "/healthz",
|
|
117
|
+
port: 3000,
|
|
118
|
+
scheme: "HTTPS",
|
|
119
|
+
},
|
|
120
|
+
initialDelaySeconds: 10,
|
|
121
|
+
},
|
|
122
|
+
livenessProbe: {
|
|
123
|
+
httpGet: {
|
|
124
|
+
path: "/healthz",
|
|
125
|
+
port: 3000,
|
|
126
|
+
scheme: "HTTPS",
|
|
127
|
+
},
|
|
128
|
+
initialDelaySeconds: 10,
|
|
129
|
+
},
|
|
98
130
|
resources: {
|
|
99
131
|
requests: {
|
|
100
132
|
memory: "64Mi",
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
-
import { beforeEach, describe, expect, it, jest } from "@jest/globals";
|
|
3
|
+
import { afterAll, beforeEach, beforeAll, 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 } from "./watch-processor";
|
|
8
|
+
import { setupWatch, logEvent, queueRecordKey } from "./watch-processor";
|
|
9
9
|
import Log from "./logger";
|
|
10
10
|
import { metricsCollector } from "./metrics";
|
|
11
11
|
|
|
@@ -88,7 +88,7 @@ describe("WatchProcessor", () => {
|
|
|
88
88
|
resyncFailureMax: 5,
|
|
89
89
|
resyncDelaySec: 5,
|
|
90
90
|
lastSeenLimitSeconds: 300,
|
|
91
|
-
relistIntervalSec:
|
|
91
|
+
relistIntervalSec: 600,
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
capabilities.push({
|
|
@@ -320,3 +320,134 @@ describe("logEvent function", () => {
|
|
|
320
320
|
expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.DATA_ERROR} received. ${message}.`);
|
|
321
321
|
});
|
|
322
322
|
});
|
|
323
|
+
|
|
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
|
+
});
|
|
393
|
+
});
|
|
394
|
+
|
|
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
|
+
});
|
|
427
|
+
|
|
428
|
+
it("should handle objects with missing metadata", () => {
|
|
429
|
+
const obj: KubernetesObject = {
|
|
430
|
+
kind: "Pod",
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
expect(queueRecordKey(obj)).toBe("Pod/cluster-scoped");
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
it("should handle objects with missing kind", () => {
|
|
437
|
+
const obj: KubernetesObject = {
|
|
438
|
+
metadata: {
|
|
439
|
+
name: "my-pod",
|
|
440
|
+
namespace: "my-namespace",
|
|
441
|
+
},
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
expect(queueRecordKey(obj)).toBe("UnknownKind/my-namespace");
|
|
445
|
+
});
|
|
446
|
+
|
|
447
|
+
it("should handle completely empty objects", () => {
|
|
448
|
+
const obj: KubernetesObject = {};
|
|
449
|
+
|
|
450
|
+
expect(queueRecordKey(obj)).toBe("UnknownKind/cluster-scoped");
|
|
451
|
+
});
|
|
452
|
+
});
|
|
453
|
+
});
|
|
@@ -9,6 +9,29 @@ import { Queue } from "./queue";
|
|
|
9
9
|
import { Binding, Event } from "./types";
|
|
10
10
|
import { metricsCollector } from "./metrics";
|
|
11
11
|
|
|
12
|
+
// init a queueRecord record to store Queue instances for a given Kubernetes Object
|
|
13
|
+
const queueRecord: Record<string, Queue<KubernetesObject>> = {};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the key for a record in the queueRecord
|
|
17
|
+
*
|
|
18
|
+
* @param obj The object to get the key for
|
|
19
|
+
* @returns The key for the object
|
|
20
|
+
*/
|
|
21
|
+
export function queueRecordKey(obj: KubernetesObject) {
|
|
22
|
+
const options = ["singular", "sharded"]; // TODO : ts-type this fella
|
|
23
|
+
const d3fault = "singular";
|
|
24
|
+
|
|
25
|
+
let strat = process.env.PEPR_RECONCILE_STRATEGY || d3fault;
|
|
26
|
+
strat = options.includes(strat) ? strat : d3fault;
|
|
27
|
+
|
|
28
|
+
const ns = obj.metadata?.namespace ?? "cluster-scoped";
|
|
29
|
+
const kind = obj.kind ?? "UnknownKind";
|
|
30
|
+
const name = obj.metadata?.name ?? "Unnamed";
|
|
31
|
+
|
|
32
|
+
return strat === "singular" ? `${kind}/${ns}` : `${kind}/${name}/${ns}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
12
35
|
// Watch configuration
|
|
13
36
|
const watchCfg: WatchCfg = {
|
|
14
37
|
resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) : 5,
|
|
@@ -18,7 +41,7 @@ const watchCfg: WatchCfg = {
|
|
|
18
41
|
: 300,
|
|
19
42
|
relistIntervalSec: process.env.PEPR_RELIST_INTERVAL_SECONDS
|
|
20
43
|
? parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10)
|
|
21
|
-
:
|
|
44
|
+
: 600,
|
|
22
45
|
};
|
|
23
46
|
|
|
24
47
|
// Map the event to the watch phase
|
|
@@ -74,13 +97,20 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
|
|
|
74
97
|
}
|
|
75
98
|
};
|
|
76
99
|
|
|
77
|
-
|
|
78
|
-
|
|
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
|
+
}
|
|
79
107
|
|
|
80
108
|
// Setup the resource watch
|
|
81
109
|
const watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {
|
|
82
110
|
Log.debug(obj, `Watch event ${type} received`);
|
|
83
111
|
|
|
112
|
+
const queue = getOrCreateQueue(queueRecordKey(obj));
|
|
113
|
+
|
|
84
114
|
// If the binding is a queue, enqueue the object
|
|
85
115
|
if (binding.isQueue) {
|
|
86
116
|
await queue.enqueue(obj, type);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
fetchStatus,
|
|
10
10
|
kind,
|
|
11
11
|
} from "pepr";
|
|
12
|
+
import nock from "nock";
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* The HelloPepr Capability is an example capability to demonstrate some general concepts of Pepr.
|
|
@@ -273,6 +274,14 @@ When(a.ConfigMap)
|
|
|
273
274
|
.Mutate(cm => cm.SetLabel("got-jokes", "true"))
|
|
274
275
|
.Watch(async cm => {
|
|
275
276
|
const jokeURL = "https://icanhazdadjoke.com/";
|
|
277
|
+
|
|
278
|
+
// Set up Nock to mock the API calls globally with header matching
|
|
279
|
+
nock(jokeURL).get("/").reply(200, {
|
|
280
|
+
id: "R7UfaahVfFd",
|
|
281
|
+
joke: "Funny joke goes here.",
|
|
282
|
+
status: 200,
|
|
283
|
+
});
|
|
284
|
+
|
|
276
285
|
// Try/catch is not needed as a response object will always be returned
|
|
277
286
|
const response = await fetch<TheChuckNorrisJoke>(jokeURL, {
|
|
278
287
|
headers: {
|