pepr 0.27.0 → 0.28.1
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.js +71 -25
- package/dist/controller.js +1 -1
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/pods.d.ts +1 -0
- package/dist/lib/assets/pods.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/controller/index.d.ts +2 -2
- package/dist/lib/controller/index.d.ts.map +1 -1
- package/dist/lib/helpers.d.ts +7 -0
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/k8s.d.ts +0 -11
- package/dist/lib/k8s.d.ts.map +1 -1
- package/dist/lib/module.d.ts +0 -2
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts +1 -2
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +87 -61
- package/dist/lib.js.map +4 -4
- package/package.json +8 -8
- package/src/cli.ts +2 -0
- package/src/lib/assets/helm.ts +2 -0
- package/src/lib/assets/pods.ts +2 -0
- package/src/lib/assets/yaml.ts +8 -4
- package/src/lib/controller/index.ts +2 -2
- package/src/lib/controller/store.ts +1 -1
- package/src/lib/helpers.ts +96 -6
- package/src/lib/k8s.ts +0 -11
- package/src/lib/module.ts +4 -4
- package/src/lib/watch-processor.ts +22 -65
- package/src/templates/package.json +1 -2
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"engines": {
|
|
10
10
|
"node": ">=18.0.0"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.28.1",
|
|
13
13
|
"main": "dist/lib.js",
|
|
14
14
|
"types": "dist/lib.d.ts",
|
|
15
15
|
"scripts": {
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test": "npm run test:unit && npm run test:journey",
|
|
20
20
|
"test:unit": "npm run gen-data-json && jest src --coverage --detectOpenHandles --coverageDirectory=./coverage",
|
|
21
21
|
"test:journey": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run",
|
|
22
|
-
"test:journey:prep": "git clone https://github.com/defenseunicorns/pepr-upgrade-test.git",
|
|
22
|
+
"test:journey:prep": "if [ ! -d ./pepr-upgrade-test ]; then git clone https://github.com/defenseunicorns/pepr-upgrade-test.git ; fi",
|
|
23
23
|
"test:journey-wasm": "npm run test:journey:k3d && npm run test:journey:build && npm run test:journey:image && npm run test:journey:run-wasm",
|
|
24
24
|
"test:journey:k3d": "k3d cluster delete pepr-dev && k3d cluster create pepr-dev --k3s-arg '--debug@server:0' --wait && kubectl rollout status deployment -n kube-system",
|
|
25
25
|
"test:journey:build": "npm run build && npm pack",
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
"format:fix": "eslint src --fix && prettier src --write"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@types/ramda": "0.29.
|
|
35
|
-
"express": "4.18.
|
|
34
|
+
"@types/ramda": "0.29.11",
|
|
35
|
+
"express": "4.18.3",
|
|
36
36
|
"fast-json-patch": "3.1.1",
|
|
37
|
-
"kubernetes-fluent-client": "2.2.
|
|
37
|
+
"kubernetes-fluent-client": "2.2.3",
|
|
38
38
|
"pino": "8.19.0",
|
|
39
39
|
"pino-pretty": "10.3.1",
|
|
40
40
|
"prom-client": "15.1.0",
|
|
41
41
|
"ramda": "0.29.1"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
|
-
"@commitlint/cli": "19.0
|
|
45
|
-
"@commitlint/config-conventional": "19.
|
|
44
|
+
"@commitlint/cli": "19.1.0",
|
|
45
|
+
"@commitlint/config-conventional": "19.1.0",
|
|
46
46
|
"@jest/globals": "29.7.0",
|
|
47
|
-
"@types/eslint": "8.56.
|
|
47
|
+
"@types/eslint": "8.56.5",
|
|
48
48
|
"@types/express": "4.17.21",
|
|
49
49
|
"@types/node": "18.x.x",
|
|
50
50
|
"@types/node-forge": "1.3.11",
|
package/src/cli.ts
CHANGED
|
@@ -14,6 +14,7 @@ import uuid from "./cli/uuid";
|
|
|
14
14
|
import { version } from "./cli/init/templates";
|
|
15
15
|
import { RootCmd } from "./cli/root";
|
|
16
16
|
import update from "./cli/update";
|
|
17
|
+
import kfc from "./cli/kfc";
|
|
17
18
|
|
|
18
19
|
if (process.env.npm_lifecycle_event !== "npx") {
|
|
19
20
|
console.warn("Pepr should be run via `npx pepr <command>` instead of `pepr <command>`.");
|
|
@@ -43,4 +44,5 @@ update(program);
|
|
|
43
44
|
format(program);
|
|
44
45
|
monitor(program);
|
|
45
46
|
uuid(program);
|
|
47
|
+
kfc(program);
|
|
46
48
|
program.parse();
|
package/src/lib/assets/helm.ts
CHANGED
|
@@ -74,6 +74,7 @@ export function watcherDeployTemplate(buildTimestamp: string) {
|
|
|
74
74
|
app: {{ .Values.uuid }}-watcher
|
|
75
75
|
pepr.dev/controller: watcher
|
|
76
76
|
spec:
|
|
77
|
+
terminationGracePeriodSeconds: {{ .Values.watcher.terminationGracePeriodSeconds }}
|
|
77
78
|
serviceAccountName: {{ .Values.uuid }}
|
|
78
79
|
securityContext:
|
|
79
80
|
{{- toYaml .Values.admission.securityContext | nindent 8 }}
|
|
@@ -145,6 +146,7 @@ export function admissionDeployTemplate(buildTimestamp: string) {
|
|
|
145
146
|
app: {{ .Values.uuid }}
|
|
146
147
|
pepr.dev/controller: admission
|
|
147
148
|
spec:
|
|
149
|
+
terminationGracePeriodSeconds: {{ .Values.admission.terminationGracePeriodSeconds }}
|
|
148
150
|
priorityClassName: system-node-critical
|
|
149
151
|
serviceAccountName: {{ .Values.uuid }}
|
|
150
152
|
securityContext:
|
package/src/lib/assets/pods.ts
CHANGED
|
@@ -91,6 +91,7 @@ export function watcher(assets: Assets, hash: string, buildTimestamp: string) {
|
|
|
91
91
|
},
|
|
92
92
|
},
|
|
93
93
|
spec: {
|
|
94
|
+
terminationGracePeriodSeconds: 5,
|
|
94
95
|
serviceAccountName: name,
|
|
95
96
|
securityContext: {
|
|
96
97
|
runAsUser: 65532,
|
|
@@ -215,6 +216,7 @@ export function deployment(assets: Assets, hash: string, buildTimestamp: string)
|
|
|
215
216
|
},
|
|
216
217
|
},
|
|
217
218
|
spec: {
|
|
219
|
+
terminationGracePeriodSeconds: 5,
|
|
218
220
|
priorityClassName: "system-node-critical",
|
|
219
221
|
serviceAccountName: name,
|
|
220
222
|
securityContext: {
|
package/src/lib/assets/yaml.ts
CHANGED
|
@@ -26,14 +26,13 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
|
|
|
26
26
|
},
|
|
27
27
|
uuid: name,
|
|
28
28
|
admission: {
|
|
29
|
+
terminationGracePeriodSeconds: 5,
|
|
29
30
|
failurePolicy: config.onError === "reject" ? "Fail" : "Ignore",
|
|
30
31
|
webhookTimeout: config.webhookTimeout,
|
|
31
32
|
env: [
|
|
32
33
|
{ name: "PEPR_WATCH_MODE", value: "false" },
|
|
33
34
|
{ name: "PEPR_PRETTY_LOG", value: "false" },
|
|
34
35
|
{ name: "LOG_LEVEL", value: "debug" },
|
|
35
|
-
process.env.PEPR_MODE === "dev" && { name: "MY_CUSTOM_VAR", value: "example-value" },
|
|
36
|
-
process.env.PEPR_MODE === "dev" && { name: "ZARF_VAR", value: "###ZARF_VAR_THING###" },
|
|
37
36
|
],
|
|
38
37
|
image,
|
|
39
38
|
annotations: {
|
|
@@ -74,12 +73,11 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
|
|
|
74
73
|
affinity: {},
|
|
75
74
|
},
|
|
76
75
|
watcher: {
|
|
76
|
+
terminationGracePeriodSeconds: 5,
|
|
77
77
|
env: [
|
|
78
78
|
{ name: "PEPR_WATCH_MODE", value: "true" },
|
|
79
79
|
{ name: "PEPR_PRETTY_LOG", value: "false" },
|
|
80
80
|
{ name: "LOG_LEVEL", value: "debug" },
|
|
81
|
-
process.env.PEPR_MODE === "dev" && { name: "MY_CUSTOM_VAR", value: "example-value" },
|
|
82
|
-
process.env.PEPR_MODE === "dev" && { name: "ZARF_VAR", value: "###ZARF_VAR_THING###" },
|
|
83
81
|
],
|
|
84
82
|
image,
|
|
85
83
|
annotations: {
|
|
@@ -120,6 +118,12 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
|
|
|
120
118
|
affinity: {},
|
|
121
119
|
},
|
|
122
120
|
};
|
|
121
|
+
if (process.env.PEPR_MODE === "dev") {
|
|
122
|
+
overrides.admission.env.push({ name: "ZARF_VAR", value: "###ZARF_VAR_THING###" });
|
|
123
|
+
overrides.watcher.env.push({ name: "ZARF_VAR", value: "###ZARF_VAR_THING###" });
|
|
124
|
+
overrides.admission.env.push({ name: "MY_CUSTOM_VAR", value: "example-value" });
|
|
125
|
+
overrides.watcher.env.push({ name: "MY_CUSTOM_VAR", value: "example-value" });
|
|
126
|
+
}
|
|
123
127
|
|
|
124
128
|
await fs.writeFile(path, dumpYaml(overrides, { noRefs: true, forceQuotes: true }));
|
|
125
129
|
}
|
|
@@ -32,13 +32,13 @@ export class Controller {
|
|
|
32
32
|
readonly #config: ModuleConfig;
|
|
33
33
|
readonly #capabilities: Capability[];
|
|
34
34
|
readonly #beforeHook?: (req: AdmissionRequest) => void;
|
|
35
|
-
readonly #afterHook?: (res: MutateResponse) => void;
|
|
35
|
+
readonly #afterHook?: (res: MutateResponse | ValidateResponse) => void;
|
|
36
36
|
|
|
37
37
|
constructor(
|
|
38
38
|
config: ModuleConfig,
|
|
39
39
|
capabilities: Capability[],
|
|
40
40
|
beforeHook?: (req: AdmissionRequest) => void,
|
|
41
|
-
afterHook?: (res: MutateResponse) => void,
|
|
41
|
+
afterHook?: (res: MutateResponse | ValidateResponse) => void,
|
|
42
42
|
onReady?: () => void,
|
|
43
43
|
) {
|
|
44
44
|
this.#config = config;
|
|
@@ -112,7 +112,7 @@ export class PeprControllerStore {
|
|
|
112
112
|
|
|
113
113
|
// Debounce the update to 1 second to avoid multiple rapid calls
|
|
114
114
|
clearTimeout(this.#sendDebounce);
|
|
115
|
-
this.#sendDebounce = setTimeout(debounced, debounceBackoff);
|
|
115
|
+
this.#sendDebounce = setTimeout(debounced, this.#onReady ? 0 : debounceBackoff);
|
|
116
116
|
};
|
|
117
117
|
|
|
118
118
|
#send = (capabilityName: string) => {
|
package/src/lib/helpers.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import { K8s, kind } from "kubernetes-fluent-client";
|
|
4
|
+
import { K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
|
|
5
5
|
import Log from "./logger";
|
|
6
6
|
import { CapabilityExport } from "./types";
|
|
7
7
|
import { promises as fs } from "fs";
|
|
8
|
-
import
|
|
8
|
+
import { Binding } from "./types";
|
|
9
9
|
|
|
10
10
|
type RBACMap = {
|
|
11
11
|
[key: string]: {
|
|
@@ -14,6 +14,96 @@ type RBACMap = {
|
|
|
14
14
|
};
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
// check for overlap with labels and annotations between bindings and kubernetes objects
|
|
18
|
+
export function checkOverlap(record1: Record<string, string>, record2: Record<string, string>) {
|
|
19
|
+
if (Object.keys(record1).length === 0) {
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
for (const key in record1) {
|
|
23
|
+
if (
|
|
24
|
+
Object.prototype.hasOwnProperty.call(record1, key) &&
|
|
25
|
+
Object.prototype.hasOwnProperty.call(record2, key) &&
|
|
26
|
+
record1[key] === record2[key]
|
|
27
|
+
) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Decide to run callback after the event comes back from API Server
|
|
36
|
+
**/
|
|
37
|
+
export const filterMatcher = (
|
|
38
|
+
binding: Partial<Binding>,
|
|
39
|
+
obj: Partial<KubernetesObject>,
|
|
40
|
+
capabilityNamespaces: string[],
|
|
41
|
+
): string => {
|
|
42
|
+
// binding kind is namespace with a InNamespace filter
|
|
43
|
+
if (binding.kind && binding.kind.kind === "Namespace" && binding.filters && binding.filters.namespaces.length !== 0) {
|
|
44
|
+
return `Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (typeof obj === "object" && obj !== null && "metadata" in obj && obj.metadata !== undefined && binding.filters) {
|
|
48
|
+
// binding labels and object labels dont match
|
|
49
|
+
if (obj.metadata.labels && !checkOverlap(binding.filters.labels, obj.metadata.labels)) {
|
|
50
|
+
return `Ignoring Watch Callback: No overlap between binding and object labels. Binding labels ${JSON.stringify(
|
|
51
|
+
binding.filters.labels,
|
|
52
|
+
)}, Object Labels ${JSON.stringify(obj.metadata.labels)}.`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// binding annotations and object annotations dont match
|
|
56
|
+
if (obj.metadata.annotations && !checkOverlap(binding.filters.annotations, obj.metadata.annotations)) {
|
|
57
|
+
return `Ignoring Watch Callback: No overlap between binding and object annotations. Binding annotations ${JSON.stringify(
|
|
58
|
+
binding.filters.annotations,
|
|
59
|
+
)}, Object annotations ${JSON.stringify(obj.metadata.annotations)}.`;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Check object is in the capability namespace
|
|
64
|
+
if (
|
|
65
|
+
Array.isArray(capabilityNamespaces) &&
|
|
66
|
+
capabilityNamespaces.length > 0 &&
|
|
67
|
+
obj.metadata &&
|
|
68
|
+
obj.metadata.namespace &&
|
|
69
|
+
!capabilityNamespaces.includes(obj.metadata.namespace)
|
|
70
|
+
) {
|
|
71
|
+
return `Ignoring Watch Callback: Object is not in the capability namespace. Capability namespaces: ${capabilityNamespaces.join(
|
|
72
|
+
", ",
|
|
73
|
+
)}, Object namespace: ${obj.metadata.namespace}.`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// chceck every filter namespace is a capability namespace
|
|
77
|
+
if (
|
|
78
|
+
Array.isArray(capabilityNamespaces) &&
|
|
79
|
+
capabilityNamespaces.length > 0 &&
|
|
80
|
+
binding.filters &&
|
|
81
|
+
Array.isArray(binding.filters.namespaces) &&
|
|
82
|
+
binding.filters.namespaces.length > 0 &&
|
|
83
|
+
!binding.filters.namespaces.every(ns => capabilityNamespaces.includes(ns))
|
|
84
|
+
) {
|
|
85
|
+
return `Ignoring Watch Callback: Binding namespace is not part of capability namespaces. Capability namespaces: ${capabilityNamespaces.join(
|
|
86
|
+
", ",
|
|
87
|
+
)}, Binding namespaces: ${binding.filters.namespaces.join(", ")}.`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// filter namespace is not the same of object namespace
|
|
91
|
+
if (
|
|
92
|
+
binding.filters &&
|
|
93
|
+
Array.isArray(binding.filters.namespaces) &&
|
|
94
|
+
binding.filters.namespaces.length > 0 &&
|
|
95
|
+
obj.metadata &&
|
|
96
|
+
obj.metadata.namespace &&
|
|
97
|
+
!binding.filters.namespaces.includes(obj.metadata.namespace)
|
|
98
|
+
) {
|
|
99
|
+
return `Ignoring Watch Callback: Binding namespace and object namespace are not the same. Binding namespaces: ${binding.filters.namespaces.join(
|
|
100
|
+
", ",
|
|
101
|
+
)}, Object namespace: ${obj.metadata.namespace}.`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// no problems
|
|
105
|
+
return "";
|
|
106
|
+
};
|
|
17
107
|
export const addVerbIfNotExists = (verbs: string[], verb: string) => {
|
|
18
108
|
if (!verbs.includes(verb)) {
|
|
19
109
|
verbs.push(verb);
|
|
@@ -99,7 +189,7 @@ export function generateWatchNamespaceError(
|
|
|
99
189
|
if (bindingAndCapabilityNSConflict(bindingNamespaces, capabilityNamespaces)) {
|
|
100
190
|
err += `Binding uses namespace not governed by capability: bindingNamespaces: [${bindingNamespaces.join(
|
|
101
191
|
", ",
|
|
102
|
-
)}] capabilityNamespaces
|
|
192
|
+
)}] capabilityNamespaces: [${capabilityNamespaces.join(", ")}].`;
|
|
103
193
|
}
|
|
104
194
|
|
|
105
195
|
// add a space if there is a period in the middle of the string
|
|
@@ -177,11 +267,11 @@ export const parseTimeout = (value: string, previous: unknown): number => {
|
|
|
177
267
|
const parsedValue = parseInt(value, 10);
|
|
178
268
|
const floatValue = parseFloat(value);
|
|
179
269
|
if (isNaN(parsedValue)) {
|
|
180
|
-
throw new
|
|
270
|
+
throw new Error("Not a number.");
|
|
181
271
|
} else if (parsedValue !== floatValue) {
|
|
182
|
-
throw new
|
|
272
|
+
throw new Error("Value must be an integer.");
|
|
183
273
|
} else if (parsedValue < 1 || parsedValue > 30) {
|
|
184
|
-
throw new
|
|
274
|
+
throw new Error("Number must be between 1 and 30.");
|
|
185
275
|
}
|
|
186
276
|
return parsedValue;
|
|
187
277
|
};
|
package/src/lib/k8s.ts
CHANGED
|
@@ -166,15 +166,4 @@ export type WebhookIgnore = {
|
|
|
166
166
|
* Note: `kube-system` and `pepr-system` are always ignored.
|
|
167
167
|
*/
|
|
168
168
|
namespaces?: string[];
|
|
169
|
-
/**
|
|
170
|
-
* List of Kubernetes labels to always ignore.
|
|
171
|
-
* Any resources with these labels will be ignored by Pepr.
|
|
172
|
-
*
|
|
173
|
-
* The example below will ignore any resources with the label `my-label=ulta-secret`:
|
|
174
|
-
* ```
|
|
175
|
-
* alwaysIgnore:
|
|
176
|
-
* labels: [{ "my-label": "ultra-secret" }]
|
|
177
|
-
* ```
|
|
178
|
-
*/
|
|
179
|
-
labels?: Record<string, string>[];
|
|
180
169
|
};
|
package/src/lib/module.ts
CHANGED
|
@@ -15,8 +15,6 @@ export interface CustomLabels {
|
|
|
15
15
|
}
|
|
16
16
|
/** Global configuration for the Pepr runtime. */
|
|
17
17
|
export type ModuleConfig = {
|
|
18
|
-
/** The user-defined name for the module */
|
|
19
|
-
name: string;
|
|
20
18
|
/** The Pepr version this module uses */
|
|
21
19
|
peprVersion?: string;
|
|
22
20
|
/** The user-defined version of the module */
|
|
@@ -109,10 +107,12 @@ export class PeprModule {
|
|
|
109
107
|
this.#controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook, () => {
|
|
110
108
|
// Wait for the controller to be ready before setting up watches
|
|
111
109
|
if (isWatchMode() || isDevMode()) {
|
|
112
|
-
|
|
110
|
+
try {
|
|
111
|
+
setupWatch(capabilities);
|
|
112
|
+
} catch (e) {
|
|
113
113
|
Log.error(e, "Error setting up watch");
|
|
114
114
|
process.exit(1);
|
|
115
|
-
}
|
|
115
|
+
}
|
|
116
116
|
}
|
|
117
117
|
});
|
|
118
118
|
|
|
@@ -6,65 +6,23 @@ import { K8s, WatchCfg, WatchEvent } from "kubernetes-fluent-client";
|
|
|
6
6
|
import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
|
|
7
7
|
import { Queue } from "./queue";
|
|
8
8
|
import { Capability } from "./capability";
|
|
9
|
-
import { PeprStore } from "./k8s";
|
|
10
9
|
import Log from "./logger";
|
|
11
10
|
import { Binding, Event } from "./types";
|
|
12
11
|
import { Watcher } from "kubernetes-fluent-client/dist/fluent/watch";
|
|
13
12
|
import { GenericClass } from "kubernetes-fluent-client";
|
|
14
|
-
|
|
15
|
-
// Track if the store has been updated
|
|
16
|
-
let storeUpdates = false;
|
|
13
|
+
import { filterMatcher } from "./helpers";
|
|
17
14
|
|
|
18
15
|
const store: Record<string, string> = {};
|
|
19
16
|
|
|
20
|
-
export
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const k8sStore = await K8s(PeprStore).InNamespace(namespace).Get(name);
|
|
27
|
-
|
|
28
|
-
// Iterate over the store and add the values to the local store
|
|
29
|
-
Object.entries(k8sStore.data).forEach(([key, value]) => {
|
|
30
|
-
store[key] = value;
|
|
31
|
-
});
|
|
32
|
-
} catch (e) {
|
|
33
|
-
// A store not existing is expected behavior on the first run
|
|
34
|
-
Log.debug(e, "Watch store does not exist yet");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
// Update the store every 10 seconds if there are changes
|
|
38
|
-
setInterval(() => {
|
|
39
|
-
if (storeUpdates) {
|
|
40
|
-
K8s(PeprStore)
|
|
41
|
-
.Apply({
|
|
42
|
-
metadata: {
|
|
43
|
-
name,
|
|
44
|
-
namespace,
|
|
45
|
-
},
|
|
46
|
-
data: store,
|
|
47
|
-
})
|
|
48
|
-
// Reset the store updates flag
|
|
49
|
-
.then(() => (storeUpdates = false))
|
|
50
|
-
// Log the error if the store update fails, but don't reset the store updates flag
|
|
51
|
-
.catch(e => {
|
|
52
|
-
Log.error(e, "Error updating watch store");
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
}, 10 * 1000);
|
|
17
|
+
export function setupWatch(capabilities: Capability[]) {
|
|
18
|
+
capabilities.map(capability =>
|
|
19
|
+
capability.bindings
|
|
20
|
+
.filter(binding => binding.isWatch)
|
|
21
|
+
.forEach(bindingElement => runBinding(bindingElement, capability.namespaces)),
|
|
22
|
+
);
|
|
56
23
|
}
|
|
57
24
|
|
|
58
|
-
|
|
59
|
-
await setupStore(uuid);
|
|
60
|
-
|
|
61
|
-
capabilities
|
|
62
|
-
.flatMap(c => c.bindings)
|
|
63
|
-
.filter(binding => binding.isWatch)
|
|
64
|
-
.forEach(runBinding);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function runBinding(binding: Binding) {
|
|
25
|
+
async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
|
|
68
26
|
// Map the event to the watch phase
|
|
69
27
|
const eventToPhaseMap = {
|
|
70
28
|
[Event.Create]: [WatchPhase.Added],
|
|
@@ -92,9 +50,14 @@ async function runBinding(binding: Binding) {
|
|
|
92
50
|
// If the type matches the phase, call the watch callback
|
|
93
51
|
if (phaseMatch.includes(type)) {
|
|
94
52
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
53
|
+
const filterMatch = filterMatcher(binding, obj, capabilityNamespaces);
|
|
54
|
+
if (filterMatch === "") {
|
|
55
|
+
queue.setReconcile(async () => await binding.watchCallback?.(obj, type));
|
|
56
|
+
// Enqueue the object for reconciliation through callback
|
|
57
|
+
await queue.enqueue(obj);
|
|
58
|
+
} else {
|
|
59
|
+
Log.debug(filterMatch);
|
|
60
|
+
}
|
|
98
61
|
} catch (e) {
|
|
99
62
|
// Errors in the watch callback should not crash the controller
|
|
100
63
|
Log.error(e, "Error executing watch callback");
|
|
@@ -109,8 +72,12 @@ async function runBinding(binding: Binding) {
|
|
|
109
72
|
// If the type matches the phase, call the watch callback
|
|
110
73
|
if (phaseMatch.includes(type)) {
|
|
111
74
|
try {
|
|
112
|
-
|
|
113
|
-
|
|
75
|
+
const filterMatch = filterMatcher(binding, obj, capabilityNamespaces);
|
|
76
|
+
if (filterMatch === "") {
|
|
77
|
+
await binding.watchCallback?.(obj, type);
|
|
78
|
+
} else {
|
|
79
|
+
Log.debug(filterMatch);
|
|
80
|
+
}
|
|
114
81
|
} catch (e) {
|
|
115
82
|
// Errors in the watch callback should not crash the controller
|
|
116
83
|
Log.error(e, "Error executing watch callback");
|
|
@@ -123,16 +90,6 @@ async function runBinding(binding: Binding) {
|
|
|
123
90
|
const cacheSuffix = createHash("sha224").update(binding.watchCallback!.toString()).digest("hex").substring(0, 5);
|
|
124
91
|
const cacheID = [watcher.getCacheID(), cacheSuffix].join("-");
|
|
125
92
|
|
|
126
|
-
// Track the resource version in the local store
|
|
127
|
-
watcher.events.on(WatchEvent.RESOURCE_VERSION, version => {
|
|
128
|
-
Log.debug(`Received watch cache: ${cacheID}:${version}`);
|
|
129
|
-
if (store[cacheID] !== version) {
|
|
130
|
-
Log.debug(`Updating watch cache: ${cacheID}: ${store[cacheID]} => ${version}`);
|
|
131
|
-
store[cacheID] = version;
|
|
132
|
-
storeUpdates = true;
|
|
133
|
-
}
|
|
134
|
-
});
|
|
135
|
-
|
|
136
93
|
// If failure continues, log and exit
|
|
137
94
|
watcher.events.on(WatchEvent.GIVE_UP, err => {
|
|
138
95
|
Log.error(err, "Watch failed after 5 attempts, giving up");
|