pepr 0.28.0 → 0.28.2
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 +16 -8
- 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/filter.d.ts.map +1 -1
- package/dist/lib/helpers.d.ts +1 -1
- package/dist/lib/helpers.d.ts.map +1 -1
- 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 +36 -50
- package/dist/lib.js.map +3 -3
- package/package.json +4 -4
- 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/filter.ts +16 -1
- package/src/lib/helpers.ts +24 -11
- package/src/lib/module.ts +4 -2
- package/src/lib/mutate-processor.ts +2 -2
- package/src/lib/validate-processor.ts +2 -2
- package/src/lib/watch-processor.ts +1 -55
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.2",
|
|
13
13
|
"main": "dist/lib.js",
|
|
14
14
|
"types": "dist/lib.d.ts",
|
|
15
15
|
"scripts": {
|
|
@@ -34,15 +34,15 @@
|
|
|
34
34
|
"@types/ramda": "0.29.11",
|
|
35
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.0
|
|
44
|
+
"@commitlint/cli": "19.1.0",
|
|
45
|
+
"@commitlint/config-conventional": "19.1.0",
|
|
46
46
|
"@jest/globals": "29.7.0",
|
|
47
47
|
"@types/eslint": "8.56.5",
|
|
48
48
|
"@types/express": "4.17.21",
|
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/filter.ts
CHANGED
|
@@ -51,7 +51,22 @@ export function shouldSkipRequest(binding: Binding, req: AdmissionRequest, capab
|
|
|
51
51
|
(combinedNamespaces.length && !combinedNamespaces.includes(req.namespace || "")) ||
|
|
52
52
|
(!namespaces.includes(req.namespace || "") && capabilityNamespaces.length !== 0 && namespaces.length !== 0)
|
|
53
53
|
) {
|
|
54
|
-
|
|
54
|
+
let type = "";
|
|
55
|
+
let label = "";
|
|
56
|
+
|
|
57
|
+
if (binding.isMutate) {
|
|
58
|
+
type = "Mutate";
|
|
59
|
+
label = binding.mutateCallback!.name;
|
|
60
|
+
} else if (binding.isValidate) {
|
|
61
|
+
type = "Validate";
|
|
62
|
+
label = binding.validateCallback!.name;
|
|
63
|
+
} else if (binding.isWatch) {
|
|
64
|
+
type = "Watch";
|
|
65
|
+
label = binding.watchCallback!.name;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
logger.debug(`${type} binding (${label}) does not match request namespace "${req.namespace}"`);
|
|
69
|
+
|
|
55
70
|
return true;
|
|
56
71
|
}
|
|
57
72
|
|
package/src/lib/helpers.ts
CHANGED
|
@@ -15,20 +15,33 @@ type RBACMap = {
|
|
|
15
15
|
};
|
|
16
16
|
|
|
17
17
|
// check for overlap with labels and annotations between bindings and kubernetes objects
|
|
18
|
-
export function checkOverlap(
|
|
19
|
-
if
|
|
18
|
+
export function checkOverlap(bindingFilters: Record<string, string>, objectFilters: Record<string, string>): boolean {
|
|
19
|
+
// True if labels/annotations are empty
|
|
20
|
+
if (Object.keys(bindingFilters).length === 0) {
|
|
20
21
|
return true;
|
|
21
22
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
) {
|
|
28
|
-
|
|
23
|
+
|
|
24
|
+
let matchCount = 0;
|
|
25
|
+
|
|
26
|
+
for (const key in bindingFilters) {
|
|
27
|
+
// object must have label/annotation
|
|
28
|
+
if (Object.prototype.hasOwnProperty.call(objectFilters, key)) {
|
|
29
|
+
const val1 = bindingFilters[key];
|
|
30
|
+
const val2 = objectFilters[key];
|
|
31
|
+
|
|
32
|
+
// If bindingFilter has empty value for this key, only need to ensure objectFilter has this key
|
|
33
|
+
if (val1 === "" && key in objectFilters) {
|
|
34
|
+
matchCount++;
|
|
35
|
+
}
|
|
36
|
+
// If bindingFilter has a value, it must match the value in objectFilter
|
|
37
|
+
else if (val1 !== "" && val1 === val2) {
|
|
38
|
+
matchCount++;
|
|
39
|
+
}
|
|
29
40
|
}
|
|
30
41
|
}
|
|
31
|
-
|
|
42
|
+
|
|
43
|
+
// For single-key objects in bindingFilter or matching all keys in multiple-keys scenario
|
|
44
|
+
return matchCount === Object.keys(bindingFilters).length;
|
|
32
45
|
}
|
|
33
46
|
|
|
34
47
|
/**
|
|
@@ -189,7 +202,7 @@ export function generateWatchNamespaceError(
|
|
|
189
202
|
if (bindingAndCapabilityNSConflict(bindingNamespaces, capabilityNamespaces)) {
|
|
190
203
|
err += `Binding uses namespace not governed by capability: bindingNamespaces: [${bindingNamespaces.join(
|
|
191
204
|
", ",
|
|
192
|
-
)}] capabilityNamespaces
|
|
205
|
+
)}] capabilityNamespaces: [${capabilityNamespaces.join(", ")}].`;
|
|
193
206
|
}
|
|
194
207
|
|
|
195
208
|
// add a space if there is a period in the middle of the string
|
package/src/lib/module.ts
CHANGED
|
@@ -107,10 +107,12 @@ export class PeprModule {
|
|
|
107
107
|
this.#controller = new Controller(config, capabilities, opts.beforeHook, opts.afterHook, () => {
|
|
108
108
|
// Wait for the controller to be ready before setting up watches
|
|
109
109
|
if (isWatchMode() || isDevMode()) {
|
|
110
|
-
|
|
110
|
+
try {
|
|
111
|
+
setupWatch(capabilities);
|
|
112
|
+
} catch (e) {
|
|
111
113
|
Log.error(e, "Error setting up watch");
|
|
112
114
|
process.exit(1);
|
|
113
|
-
}
|
|
115
|
+
}
|
|
114
116
|
}
|
|
115
117
|
});
|
|
116
118
|
|
|
@@ -55,7 +55,7 @@ export async function mutateProcessor(
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
const label = action.mutateCallback.name;
|
|
58
|
-
Log.info(actionMetadata, `Processing
|
|
58
|
+
Log.info(actionMetadata, `Processing mutation action (${label})`);
|
|
59
59
|
|
|
60
60
|
matchedAction = true;
|
|
61
61
|
|
|
@@ -79,7 +79,7 @@ export async function mutateProcessor(
|
|
|
79
79
|
// Run the action
|
|
80
80
|
await action.mutateCallback(wrapped);
|
|
81
81
|
|
|
82
|
-
Log.info(actionMetadata, `
|
|
82
|
+
Log.info(actionMetadata, `Mutation action succeeded (${label})`);
|
|
83
83
|
|
|
84
84
|
// Add annotations to the request to indicate that the capability succeeded
|
|
85
85
|
updateStatus("succeeded");
|
|
@@ -46,7 +46,7 @@ export async function validateProcessor(
|
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
const label = action.validateCallback.name;
|
|
49
|
-
Log.info(actionMetadata, `Processing
|
|
49
|
+
Log.info(actionMetadata, `Processing validation action (${label})`);
|
|
50
50
|
|
|
51
51
|
try {
|
|
52
52
|
// Run the validation callback, if it fails set allowed to false
|
|
@@ -61,7 +61,7 @@ export async function validateProcessor(
|
|
|
61
61
|
};
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
Log.info(actionMetadata, `Validation
|
|
64
|
+
Log.info(actionMetadata, `Validation action complete (${label}): ${resp.allowed ? "allowed" : "denied"}`);
|
|
65
65
|
} catch (e) {
|
|
66
66
|
// If any validation throws an error, note the failure in the Response
|
|
67
67
|
Log.error(actionMetadata, `Action failed: ${JSON.stringify(e)}`);
|
|
@@ -6,59 +6,15 @@ 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
13
|
import { filterMatcher } from "./helpers";
|
|
15
14
|
|
|
16
|
-
// Track if the store has been updated
|
|
17
|
-
let storeUpdates = false;
|
|
18
|
-
|
|
19
15
|
const store: Record<string, string> = {};
|
|
20
16
|
|
|
21
|
-
export
|
|
22
|
-
const name = `pepr-${uuid}-watch`;
|
|
23
|
-
const namespace = "pepr-system";
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
// Try to read the watch store if it exists
|
|
27
|
-
const k8sStore = await K8s(PeprStore).InNamespace(namespace).Get(name);
|
|
28
|
-
|
|
29
|
-
// Iterate over the store and add the values to the local store
|
|
30
|
-
Object.entries(k8sStore.data).forEach(([key, value]) => {
|
|
31
|
-
store[key] = value;
|
|
32
|
-
});
|
|
33
|
-
} catch (e) {
|
|
34
|
-
// A store not existing is expected behavior on the first run
|
|
35
|
-
Log.debug(e, "Watch store does not exist yet");
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
// Update the store every 10 seconds if there are changes
|
|
39
|
-
setInterval(() => {
|
|
40
|
-
if (storeUpdates) {
|
|
41
|
-
K8s(PeprStore)
|
|
42
|
-
.Apply({
|
|
43
|
-
metadata: {
|
|
44
|
-
name,
|
|
45
|
-
namespace,
|
|
46
|
-
},
|
|
47
|
-
data: store,
|
|
48
|
-
})
|
|
49
|
-
// Reset the store updates flag
|
|
50
|
-
.then(() => (storeUpdates = false))
|
|
51
|
-
// Log the error if the store update fails, but don't reset the store updates flag
|
|
52
|
-
.catch(e => {
|
|
53
|
-
Log.error(e, "Error updating watch store");
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
}, 10 * 1000);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function setupWatch(uuid: string, capabilities: Capability[]) {
|
|
60
|
-
await setupStore(uuid);
|
|
61
|
-
|
|
17
|
+
export function setupWatch(capabilities: Capability[]) {
|
|
62
18
|
capabilities.map(capability =>
|
|
63
19
|
capability.bindings
|
|
64
20
|
.filter(binding => binding.isWatch)
|
|
@@ -134,16 +90,6 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
|
|
|
134
90
|
const cacheSuffix = createHash("sha224").update(binding.watchCallback!.toString()).digest("hex").substring(0, 5);
|
|
135
91
|
const cacheID = [watcher.getCacheID(), cacheSuffix].join("-");
|
|
136
92
|
|
|
137
|
-
// Track the resource version in the local store
|
|
138
|
-
watcher.events.on(WatchEvent.RESOURCE_VERSION, version => {
|
|
139
|
-
Log.debug(`Received watch cache: ${cacheID}:${version}`);
|
|
140
|
-
if (store[cacheID] !== version) {
|
|
141
|
-
Log.debug(`Updating watch cache: ${cacheID}: ${store[cacheID]} => ${version}`);
|
|
142
|
-
store[cacheID] = version;
|
|
143
|
-
storeUpdates = true;
|
|
144
|
-
}
|
|
145
|
-
});
|
|
146
|
-
|
|
147
93
|
// If failure continues, log and exit
|
|
148
94
|
watcher.events.on(WatchEvent.GIVE_UP, err => {
|
|
149
95
|
Log.error(err, "Watch failed after 5 attempts, giving up");
|