pepr 0.38.3 → 0.39.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/build.d.ts +1 -1
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.helpers.d.ts +19 -0
- package/dist/cli/build.helpers.d.ts.map +1 -0
- package/dist/cli/deploy.d.ts.map +1 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/init/index.d.ts.map +1 -1
- package/dist/cli/init/templates.d.ts +6 -2
- package/dist/cli/init/templates.d.ts.map +1 -1
- package/dist/cli/monitor.d.ts.map +1 -1
- package/dist/cli.js +294 -229
- package/dist/controller.js +53 -31
- package/dist/lib/assets/deploy.d.ts.map +1 -1
- package/dist/lib/assets/helm.d.ts +1 -0
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/index.d.ts +1 -1
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/rbac.d.ts +31 -4
- package/dist/lib/assets/rbac.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts +2 -2
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/capability.d.ts +2 -8
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts +1 -5
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/controller/storeCache.d.ts +11 -0
- package/dist/lib/controller/storeCache.d.ts.map +1 -0
- package/dist/lib/enums.d.ts +17 -0
- package/dist/lib/enums.d.ts.map +1 -0
- package/dist/lib/{adjudicators.d.ts → filter/adjudicators.d.ts} +23 -18
- package/dist/lib/filter/adjudicators.d.ts.map +1 -0
- package/dist/lib/{filter.d.ts → filter/filter.d.ts} +1 -1
- package/dist/lib/filter/filter.d.ts.map +1 -0
- package/dist/lib/helpers.d.ts +1 -2
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/k8s.d.ts +1 -1
- package/dist/lib/k8s.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +4 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/module.d.ts +5 -0
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/mutate-processor.d.ts.map +1 -1
- package/dist/lib/mutate-request.d.ts +1 -60
- package/dist/lib/mutate-request.d.ts.map +1 -1
- package/dist/lib/types.d.ts +8 -24
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/validate-request.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +236 -299
- package/dist/lib.js.map +4 -4
- package/dist/sdk/cosign.d.ts +18 -0
- package/dist/sdk/cosign.d.ts.map +1 -0
- package/dist/sdk/heredoc.d.ts +2 -0
- package/dist/sdk/heredoc.d.ts.map +1 -0
- package/dist/sdk/sdk.d.ts +1 -2
- package/dist/sdk/sdk.d.ts.map +1 -1
- package/package.json +12 -8
- package/src/cli/build.helpers.ts +28 -0
- package/src/cli/build.ts +124 -121
- package/src/cli/deploy.ts +27 -24
- package/src/cli/dev.ts +3 -3
- package/src/cli/format.ts +3 -6
- package/src/cli/init/index.ts +23 -19
- package/src/cli/monitor.ts +34 -36
- package/src/lib/assets/deploy.ts +12 -3
- package/src/lib/assets/helm.ts +14 -0
- package/src/lib/assets/index.ts +12 -8
- package/src/lib/assets/rbac.ts +69 -17
- package/src/lib/assets/webhooks.ts +1 -1
- package/src/lib/assets/yaml.ts +8 -4
- package/src/lib/capability.ts +7 -12
- package/src/lib/controller/index.ts +3 -3
- package/src/lib/controller/store.ts +42 -202
- package/src/lib/controller/storeCache.ts +63 -0
- package/src/lib/enums.ts +21 -0
- package/src/lib/{adjudicators.ts → filter/adjudicators.ts} +56 -31
- package/src/lib/{filter.ts → filter/filter.ts} +3 -2
- package/src/lib/finalizer.ts +1 -1
- package/src/lib/helpers.ts +19 -15
- package/src/lib/k8s.ts +2 -2
- package/src/lib/logger.ts +41 -0
- package/src/lib/metrics.ts +3 -1
- package/src/lib/module.ts +5 -0
- package/src/lib/mutate-processor.ts +14 -12
- package/src/lib/mutate-request.ts +4 -69
- package/src/lib/types.ts +9 -28
- package/src/lib/validate-processor.ts +1 -1
- package/src/lib/validate-request.ts +2 -1
- package/src/lib/watch-processor.ts +34 -20
- package/src/sdk/cosign.ts +327 -0
- package/src/sdk/heredoc.ts +36 -0
- package/src/sdk/sdk.ts +1 -2
- package/dist/lib/adjudicators.d.ts.map +0 -1
- package/dist/lib/filter.d.ts.map +0 -1
|
@@ -6,15 +6,15 @@ import { K8s } from "kubernetes-fluent-client";
|
|
|
6
6
|
import { startsWith } from "ramda";
|
|
7
7
|
|
|
8
8
|
import { Capability } from "../capability";
|
|
9
|
-
import {
|
|
10
|
-
import Log from "../logger";
|
|
9
|
+
import { Store } from "../k8s";
|
|
10
|
+
import Log, { redactedPatch, redactedStore } from "../logger";
|
|
11
11
|
import { DataOp, DataSender, DataStore, Storage } from "../storage";
|
|
12
|
+
import { fillStoreCache, sendUpdatesAndFlushCache } from "./storeCache";
|
|
12
13
|
|
|
13
|
-
const redactedValue = "**redacted**";
|
|
14
14
|
const namespace = "pepr-system";
|
|
15
15
|
export const debounceBackoff = 5000;
|
|
16
16
|
|
|
17
|
-
export class
|
|
17
|
+
export class StoreController {
|
|
18
18
|
#name: string;
|
|
19
19
|
#stores: Record<string, Storage> = {};
|
|
20
20
|
#sendDebounce: NodeJS.Timeout | undefined;
|
|
@@ -23,124 +23,53 @@ export class PeprControllerStore {
|
|
|
23
23
|
constructor(capabilities: Capability[], name: string, onReady?: () => void) {
|
|
24
24
|
this.#onReady = onReady;
|
|
25
25
|
|
|
26
|
-
// Setup Pepr State bindings
|
|
27
26
|
this.#name = name;
|
|
28
27
|
|
|
28
|
+
const setStorageInstance = (registrationFunction: () => Storage, name: string) => {
|
|
29
|
+
const scheduleStore = registrationFunction();
|
|
30
|
+
|
|
31
|
+
// Bind the store sender to the capability
|
|
32
|
+
scheduleStore.registerSender(this.#send(name));
|
|
33
|
+
|
|
34
|
+
// Store the storage instance
|
|
35
|
+
this.#stores[name] = scheduleStore;
|
|
36
|
+
};
|
|
37
|
+
|
|
29
38
|
if (name.includes("schedule")) {
|
|
30
39
|
// Establish the store for each capability
|
|
31
40
|
for (const { name, registerScheduleStore, hasSchedule } of capabilities) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
if (hasSchedule === true) {
|
|
42
|
+
// Register the scheduleStore with the capability
|
|
43
|
+
setStorageInstance(registerScheduleStore, name);
|
|
35
44
|
}
|
|
36
|
-
// Register the scheduleStore with the capability
|
|
37
|
-
const { scheduleStore } = registerScheduleStore();
|
|
38
|
-
|
|
39
|
-
// Bind the store sender to the capability
|
|
40
|
-
scheduleStore.registerSender(this.#send(name));
|
|
41
|
-
|
|
42
|
-
// Store the storage instance
|
|
43
|
-
this.#stores[name] = scheduleStore;
|
|
44
45
|
}
|
|
45
46
|
} else {
|
|
46
47
|
// Establish the store for each capability
|
|
47
48
|
for (const { name, registerStore } of capabilities) {
|
|
48
|
-
|
|
49
|
-
const { store } = registerStore();
|
|
50
|
-
|
|
51
|
-
// Bind the store sender to the capability
|
|
52
|
-
store.registerSender(this.#send(name));
|
|
53
|
-
|
|
54
|
-
// Store the storage instance
|
|
55
|
-
this.#stores[name] = store;
|
|
49
|
+
setStorageInstance(registerStore, name);
|
|
56
50
|
}
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
// Add a jitter to the Store creation to avoid collisions
|
|
60
53
|
setTimeout(
|
|
61
54
|
() =>
|
|
62
|
-
K8s(
|
|
55
|
+
K8s(Store)
|
|
63
56
|
.InNamespace(namespace)
|
|
64
57
|
.Get(this.#name)
|
|
65
|
-
|
|
66
|
-
.then(async (store: PeprStore) => await this.#migrateAndSetupWatch(store))
|
|
67
|
-
// Otherwise, create the resource
|
|
58
|
+
.then(async (store: Store) => await this.#migrateAndSetupWatch(store))
|
|
68
59
|
.catch(this.#createStoreResource),
|
|
69
|
-
Math.random() * 3000,
|
|
60
|
+
Math.random() * 3000, // Add a jitter to the Store creation to avoid collisions
|
|
70
61
|
);
|
|
71
62
|
}
|
|
72
63
|
|
|
73
64
|
#setupWatch = () => {
|
|
74
|
-
const watcher = K8s(
|
|
65
|
+
const watcher = K8s(Store, { name: this.#name, namespace }).Watch(this.#receive);
|
|
75
66
|
watcher.start().catch(e => Log.error(e, "Error starting Pepr store watch"));
|
|
76
67
|
};
|
|
77
68
|
|
|
78
|
-
#migrateAndSetupWatch = async (store:
|
|
69
|
+
#migrateAndSetupWatch = async (store: Store) => {
|
|
79
70
|
Log.debug(redactedStore(store), "Pepr Store migration");
|
|
80
71
|
const data: DataStore = store.data || {};
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
// Send the cached updates to the cluster
|
|
84
|
-
const flushCache = async () => {
|
|
85
|
-
const indexes = Object.keys(migrateCache);
|
|
86
|
-
const payload = Object.values(migrateCache);
|
|
87
|
-
|
|
88
|
-
// Loop over each key in the cache and delete it to avoid collisions with other sender calls
|
|
89
|
-
for (const idx of indexes) {
|
|
90
|
-
delete migrateCache[idx];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
// Send the patch to the cluster
|
|
95
|
-
if (payload.length > 0) {
|
|
96
|
-
await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
|
|
97
|
-
}
|
|
98
|
-
} catch (err) {
|
|
99
|
-
Log.error(err, "Pepr store update failure");
|
|
100
|
-
|
|
101
|
-
if (err.status === 422) {
|
|
102
|
-
Object.keys(migrateCache).forEach(key => delete migrateCache[key]);
|
|
103
|
-
} else {
|
|
104
|
-
// On failure to update, re-add the operations to the cache to be retried
|
|
105
|
-
for (const idx of indexes) {
|
|
106
|
-
migrateCache[idx] = payload[Number(idx)];
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const fillCache = (name: string, op: DataOp, key: string[], val?: string) => {
|
|
113
|
-
if (op === "add") {
|
|
114
|
-
// adjust the path for the capability
|
|
115
|
-
const path = `/data/${name}-v2-${key}`;
|
|
116
|
-
const value = val || "";
|
|
117
|
-
const cacheIdx = [op, path, value].join(":");
|
|
118
|
-
|
|
119
|
-
// Add the operation to the cache
|
|
120
|
-
migrateCache[cacheIdx] = { op, path, value };
|
|
121
|
-
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (op === "remove") {
|
|
126
|
-
if (key.length < 1) {
|
|
127
|
-
throw new Error(`Key is required for REMOVE operation`);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
for (const k of key) {
|
|
131
|
-
const path = `/data/${name}-${k}`;
|
|
132
|
-
const cacheIdx = [op, path].join(":");
|
|
133
|
-
|
|
134
|
-
// Add the operation to the cache
|
|
135
|
-
migrateCache[cacheIdx] = { op, path };
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// If we get here, the operation is not supported
|
|
142
|
-
throw new Error(`Unsupported operation: ${op}`);
|
|
143
|
-
};
|
|
72
|
+
let storeCache: Record<string, Operation> = {};
|
|
144
73
|
|
|
145
74
|
for (const name of Object.keys(this.#stores)) {
|
|
146
75
|
// Get the prefix offset for the keys
|
|
@@ -151,16 +80,23 @@ export class PeprControllerStore {
|
|
|
151
80
|
// Match on the capability name as a prefix for non v2 keys
|
|
152
81
|
if (startsWith(name, key) && !startsWith(`${name}-v2`, key)) {
|
|
153
82
|
// populate migrate cache
|
|
154
|
-
|
|
155
|
-
|
|
83
|
+
storeCache = fillStoreCache(storeCache, name, "remove", {
|
|
84
|
+
key: [key.slice(offset)],
|
|
85
|
+
value: data[key],
|
|
86
|
+
});
|
|
87
|
+
storeCache = fillStoreCache(storeCache, name, "add", {
|
|
88
|
+
key: [key.slice(offset)],
|
|
89
|
+
value: data[key],
|
|
90
|
+
version: "v2",
|
|
91
|
+
});
|
|
156
92
|
}
|
|
157
93
|
}
|
|
158
94
|
}
|
|
159
|
-
await
|
|
95
|
+
storeCache = await sendUpdatesAndFlushCache(storeCache, namespace, this.#name);
|
|
160
96
|
this.#setupWatch();
|
|
161
97
|
};
|
|
162
98
|
|
|
163
|
-
#receive = (store:
|
|
99
|
+
#receive = (store: Store) => {
|
|
164
100
|
Log.debug(redactedStore(store), "Pepr Store update");
|
|
165
101
|
|
|
166
102
|
// Wrap the update in a debounced function
|
|
@@ -202,81 +138,18 @@ export class PeprControllerStore {
|
|
|
202
138
|
};
|
|
203
139
|
|
|
204
140
|
#send = (capabilityName: string) => {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
// Load the sendCache with patch operations
|
|
208
|
-
const fillCache = (op: DataOp, key: string[], val?: string) => {
|
|
209
|
-
if (op === "add") {
|
|
210
|
-
// adjust the path for the capability
|
|
211
|
-
const path = `/data/${capabilityName}-${key}`;
|
|
212
|
-
const value = val || "";
|
|
213
|
-
const cacheIdx = [op, path, value].join(":");
|
|
214
|
-
|
|
215
|
-
// Add the operation to the cache
|
|
216
|
-
sendCache[cacheIdx] = { op, path, value };
|
|
217
|
-
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
if (op === "remove") {
|
|
222
|
-
if (key.length < 1) {
|
|
223
|
-
throw new Error(`Key is required for REMOVE operation`);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
for (const k of key) {
|
|
227
|
-
const path = `/data/${capabilityName}-${k}`;
|
|
228
|
-
const cacheIdx = [op, path].join(":");
|
|
229
|
-
|
|
230
|
-
// Add the operation to the cache
|
|
231
|
-
sendCache[cacheIdx] = { op, path };
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
// If we get here, the operation is not supported
|
|
238
|
-
throw new Error(`Unsupported operation: ${op}`);
|
|
239
|
-
};
|
|
240
|
-
|
|
241
|
-
// Send the cached updates to the cluster
|
|
242
|
-
const flushCache = async () => {
|
|
243
|
-
const indexes = Object.keys(sendCache);
|
|
244
|
-
const payload = Object.values(sendCache);
|
|
245
|
-
|
|
246
|
-
// Loop over each key in the cache and delete it to avoid collisions with other sender calls
|
|
247
|
-
for (const idx of indexes) {
|
|
248
|
-
delete sendCache[idx];
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
try {
|
|
252
|
-
// Send the patch to the cluster
|
|
253
|
-
if (payload.length > 0) {
|
|
254
|
-
await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
|
|
255
|
-
}
|
|
256
|
-
} catch (err) {
|
|
257
|
-
Log.error(err, "Pepr store update failure");
|
|
258
|
-
|
|
259
|
-
if (err.status === 422) {
|
|
260
|
-
Object.keys(sendCache).forEach(key => delete sendCache[key]);
|
|
261
|
-
} else {
|
|
262
|
-
// On failure to update, re-add the operations to the cache to be retried
|
|
263
|
-
for (const idx of indexes) {
|
|
264
|
-
sendCache[idx] = payload[Number(idx)];
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
};
|
|
141
|
+
let storeCache: Record<string, Operation> = {};
|
|
269
142
|
|
|
270
143
|
// Create a sender function for the capability to add/remove data from the store
|
|
271
|
-
const sender: DataSender = async (op: DataOp, key: string[],
|
|
272
|
-
|
|
144
|
+
const sender: DataSender = async (op: DataOp, key: string[], value?: string) => {
|
|
145
|
+
storeCache = fillStoreCache(storeCache, capabilityName, op, { key, value });
|
|
273
146
|
};
|
|
274
147
|
|
|
275
148
|
// Send any cached updates every debounceBackoff milliseconds
|
|
276
149
|
setInterval(() => {
|
|
277
|
-
if (Object.keys(
|
|
278
|
-
Log.debug(redactedPatch(
|
|
279
|
-
void
|
|
150
|
+
if (Object.keys(storeCache).length > 0) {
|
|
151
|
+
Log.debug(redactedPatch(storeCache), "Sending updates to Pepr store");
|
|
152
|
+
void sendUpdatesAndFlushCache(storeCache, namespace, this.#name);
|
|
280
153
|
}
|
|
281
154
|
}, debounceBackoff);
|
|
282
155
|
|
|
@@ -288,7 +161,7 @@ export class PeprControllerStore {
|
|
|
288
161
|
Log.debug(e);
|
|
289
162
|
|
|
290
163
|
try {
|
|
291
|
-
await K8s(
|
|
164
|
+
await K8s(Store).Apply({
|
|
292
165
|
metadata: {
|
|
293
166
|
name: this.#name,
|
|
294
167
|
namespace,
|
|
@@ -306,36 +179,3 @@ export class PeprControllerStore {
|
|
|
306
179
|
}
|
|
307
180
|
};
|
|
308
181
|
}
|
|
309
|
-
|
|
310
|
-
export function redactedStore(store: PeprStore): PeprStore {
|
|
311
|
-
const redacted = process.env.PEPR_STORE_REDACT_VALUES === "true";
|
|
312
|
-
return {
|
|
313
|
-
...store,
|
|
314
|
-
data: Object.keys(store.data).reduce((acc: Record<string, string>, key: string) => {
|
|
315
|
-
acc[key] = redacted ? redactedValue : store.data[key];
|
|
316
|
-
return acc;
|
|
317
|
-
}, {}),
|
|
318
|
-
};
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
export function redactedPatch(patch: Record<string, Operation> = {}): Record<string, Operation> {
|
|
322
|
-
const redacted = process.env.PEPR_STORE_REDACT_VALUES === "true";
|
|
323
|
-
|
|
324
|
-
if (!redacted) {
|
|
325
|
-
return patch;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
const redactedCache: Record<string, Operation> = {};
|
|
329
|
-
|
|
330
|
-
Object.keys(patch).forEach(key => {
|
|
331
|
-
const operation = patch[key];
|
|
332
|
-
const redactedKey = key.includes(":") ? key.substring(0, key.lastIndexOf(":")) + ":**redacted**" : key;
|
|
333
|
-
const redactedOperation: Operation = {
|
|
334
|
-
...operation,
|
|
335
|
-
...("value" in operation ? { value: "**redacted**" } : {}),
|
|
336
|
-
};
|
|
337
|
-
redactedCache[redactedKey] = redactedOperation;
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
return redactedCache;
|
|
341
|
-
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { DataOp } from "../storage";
|
|
2
|
+
import Log from "../logger";
|
|
3
|
+
import { K8s } from "kubernetes-fluent-client";
|
|
4
|
+
import { Store } from "../k8s";
|
|
5
|
+
import { StatusCodes } from "http-status-codes";
|
|
6
|
+
import { Operation } from "fast-json-patch";
|
|
7
|
+
|
|
8
|
+
export const sendUpdatesAndFlushCache = async (cache: Record<string, Operation>, namespace: string, name: string) => {
|
|
9
|
+
const indexes = Object.keys(cache);
|
|
10
|
+
const payload = Object.values(cache);
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
if (payload.length > 0) {
|
|
14
|
+
await K8s(Store, { namespace, name }).Patch(payload); // Send patch to cluster
|
|
15
|
+
Object.keys(cache).forEach(key => delete cache[key]);
|
|
16
|
+
}
|
|
17
|
+
} catch (err) {
|
|
18
|
+
Log.error(err, "Pepr store update failure");
|
|
19
|
+
|
|
20
|
+
if (err.status === StatusCodes.UNPROCESSABLE_ENTITY) {
|
|
21
|
+
Object.keys(cache).forEach(key => delete cache[key]);
|
|
22
|
+
} else {
|
|
23
|
+
indexes.forEach(index => {
|
|
24
|
+
cache[index] = payload[Number(index)]; // On failure to update, re-add the operations to the cache to be retried
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return cache;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
type CacheItem = {
|
|
32
|
+
key: string[];
|
|
33
|
+
value?: string;
|
|
34
|
+
version?: string;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const fillStoreCache = (
|
|
38
|
+
cache: Record<string, Operation>,
|
|
39
|
+
capabilityName: string,
|
|
40
|
+
op: DataOp,
|
|
41
|
+
cacheItem: CacheItem,
|
|
42
|
+
): Record<string, Operation> => {
|
|
43
|
+
const path = [`/data/${capabilityName}`, cacheItem.version, cacheItem.key] // adjust the path, see ADR-0008
|
|
44
|
+
.filter(str => str !== "" && str !== undefined)
|
|
45
|
+
.join("-");
|
|
46
|
+
if (op === "add") {
|
|
47
|
+
const value = cacheItem.value || "";
|
|
48
|
+
const cacheIdx = [op, path, value].join(":");
|
|
49
|
+
|
|
50
|
+
// Add the operation to the cache
|
|
51
|
+
cache[cacheIdx] = { op, path, value };
|
|
52
|
+
} else if (op === "remove") {
|
|
53
|
+
if (cacheItem.key.length < 1) {
|
|
54
|
+
throw new Error(`Key is required for REMOVE operation`);
|
|
55
|
+
}
|
|
56
|
+
const cacheIndex = [op, path].join(":");
|
|
57
|
+
// Add the operation to the cache
|
|
58
|
+
cache[cacheIndex] = { op, path };
|
|
59
|
+
} else {
|
|
60
|
+
throw new Error(`Unsupported operation: ${op}`);
|
|
61
|
+
}
|
|
62
|
+
return cache;
|
|
63
|
+
};
|
package/src/lib/enums.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
|
+
|
|
4
|
+
// Operation type for mutation operations
|
|
5
|
+
export enum Operation {
|
|
6
|
+
CREATE = "CREATE",
|
|
7
|
+
UPDATE = "UPDATE",
|
|
8
|
+
DELETE = "DELETE",
|
|
9
|
+
CONNECT = "CONNECT",
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The type of Kubernetes mutating webhook event that the action is registered for.
|
|
14
|
+
*/
|
|
15
|
+
export enum Event {
|
|
16
|
+
Create = "CREATE",
|
|
17
|
+
Update = "UPDATE",
|
|
18
|
+
Delete = "DELETE",
|
|
19
|
+
CreateOrUpdate = "CREATEORUPDATE",
|
|
20
|
+
Any = "*",
|
|
21
|
+
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import { Event, Operation } from "
|
|
4
|
+
import { Event, Operation } from "../enums";
|
|
5
|
+
import { AdmissionRequest } from "../../lib/types";
|
|
5
6
|
import {
|
|
6
7
|
__,
|
|
7
8
|
allPass,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
nthArg,
|
|
19
20
|
pipe,
|
|
20
21
|
} from "ramda";
|
|
22
|
+
import { KubernetesObject } from "kubernetes-fluent-client";
|
|
21
23
|
|
|
22
24
|
/*
|
|
23
25
|
Naming scheme:
|
|
@@ -29,34 +31,52 @@ import {
|
|
|
29
31
|
/*
|
|
30
32
|
AdmissionRequest collectors
|
|
31
33
|
*/
|
|
32
|
-
export const declaredOperation = pipe(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
export const
|
|
34
|
+
export const declaredOperation = pipe(
|
|
35
|
+
(request: AdmissionRequest<KubernetesObject>): Operation => request?.operation,
|
|
36
|
+
defaultTo(""),
|
|
37
|
+
);
|
|
38
|
+
export const declaredGroup = pipe(
|
|
39
|
+
(request: AdmissionRequest<KubernetesObject>): string => request?.kind?.group,
|
|
40
|
+
defaultTo(""),
|
|
41
|
+
);
|
|
42
|
+
export const declaredVersion = pipe(
|
|
43
|
+
(request: AdmissionRequest<KubernetesObject>): string | undefined => request?.kind?.version,
|
|
44
|
+
defaultTo(""),
|
|
45
|
+
);
|
|
46
|
+
export const declaredKind = pipe(
|
|
47
|
+
(request: AdmissionRequest<KubernetesObject>): string => request?.kind?.kind,
|
|
48
|
+
defaultTo(""),
|
|
49
|
+
);
|
|
50
|
+
export const declaredUid = pipe((request: AdmissionRequest<KubernetesObject>): string => request?.uid, defaultTo(""));
|
|
37
51
|
|
|
38
52
|
/*
|
|
39
53
|
KubernetesObject collectors
|
|
40
54
|
*/
|
|
41
|
-
export const carriesDeletionTimestamp = pipe(
|
|
55
|
+
export const carriesDeletionTimestamp = pipe(
|
|
56
|
+
kubernetesObject => !!kubernetesObject.metadata?.deletionTimestamp,
|
|
57
|
+
defaultTo(false),
|
|
58
|
+
);
|
|
42
59
|
export const missingDeletionTimestamp = complement(carriesDeletionTimestamp);
|
|
43
60
|
|
|
44
|
-
export const
|
|
61
|
+
export const carriedKind = pipe(kubernetesObject => kubernetesObject?.metadata?.kind, defaultTo("not set"));
|
|
62
|
+
export const carriedVersion = pipe(kubernetesObject => kubernetesObject?.metadata?.version, defaultTo("not set"));
|
|
63
|
+
export const carriedName = pipe(kubernetesObject => kubernetesObject?.metadata?.name, defaultTo(""));
|
|
45
64
|
export const carriesName = pipe(carriedName, equals(""), not);
|
|
46
65
|
export const missingName = complement(carriesName);
|
|
47
66
|
|
|
48
|
-
export const carriedNamespace = pipe(
|
|
67
|
+
export const carriedNamespace = pipe(kubernetesObject => kubernetesObject?.metadata?.namespace, defaultTo(""));
|
|
49
68
|
export const carriesNamespace = pipe(carriedNamespace, equals(""), not);
|
|
50
69
|
|
|
51
|
-
export const carriedAnnotations = pipe(
|
|
70
|
+
export const carriedAnnotations = pipe(kubernetesObject => kubernetesObject?.metadata?.annotations, defaultTo({}));
|
|
52
71
|
export const carriesAnnotations = pipe(carriedAnnotations, equals({}), not);
|
|
53
72
|
|
|
54
|
-
export const carriedLabels = pipe(
|
|
73
|
+
export const carriedLabels = pipe(kubernetesObject => kubernetesObject?.metadata?.labels, defaultTo({}));
|
|
55
74
|
export const carriesLabels = pipe(carriedLabels, equals({}), not);
|
|
56
75
|
|
|
57
76
|
/*
|
|
58
77
|
Binding collectors
|
|
59
78
|
*/
|
|
79
|
+
|
|
60
80
|
export const definesDeletionTimestamp = pipe(binding => binding?.filters?.deletionTimestamp, defaultTo(false));
|
|
61
81
|
export const ignoresDeletionTimestamp = complement(definesDeletionTimestamp);
|
|
62
82
|
|
|
@@ -112,7 +132,7 @@ export const definedCallback = pipe(binding => {
|
|
|
112
132
|
null
|
|
113
133
|
);
|
|
114
134
|
});
|
|
115
|
-
export const definedCallbackName = pipe(definedCallback, defaultTo({ name: "" }),
|
|
135
|
+
export const definedCallbackName = pipe(definedCallback, defaultTo({ name: "" }), callback => callback.name);
|
|
116
136
|
|
|
117
137
|
/*
|
|
118
138
|
post-collection comparitors
|
|
@@ -124,32 +144,32 @@ export const mismatchedDeletionTimestamp = allPass([
|
|
|
124
144
|
|
|
125
145
|
export const mismatchedName = allPass([
|
|
126
146
|
pipe(nthArg(0), definesName),
|
|
127
|
-
pipe((
|
|
147
|
+
pipe((binding, kubernetesObject) => definedName(binding) !== carriedName(kubernetesObject)),
|
|
128
148
|
]);
|
|
129
149
|
|
|
130
150
|
export const mismatchedNameRegex = allPass([
|
|
131
151
|
pipe(nthArg(0), definesNameRegex),
|
|
132
|
-
pipe((
|
|
152
|
+
pipe((binding, kubernetesObject) => new RegExp(definedNameRegex(binding)).test(carriedName(kubernetesObject)), not),
|
|
133
153
|
]);
|
|
134
154
|
|
|
135
155
|
export const bindsToKind = curry(
|
|
136
|
-
allPass([pipe(nthArg(0), definedKind, equals(""), not), pipe((
|
|
156
|
+
allPass([pipe(nthArg(0), definedKind, equals(""), not), pipe((binding, kind) => definedKind(binding) === kind)]),
|
|
137
157
|
);
|
|
138
158
|
export const bindsToNamespace = curry(pipe(bindsToKind(__, "Namespace")));
|
|
139
159
|
export const misboundNamespace = allPass([bindsToNamespace, definesNamespaces]);
|
|
140
160
|
|
|
141
161
|
export const mismatchedNamespace = allPass([
|
|
142
162
|
pipe(nthArg(0), definesNamespaces),
|
|
143
|
-
pipe((
|
|
163
|
+
pipe((binding, kubernetesObject) => definedNamespaces(binding).includes(carriedNamespace(kubernetesObject)), not),
|
|
144
164
|
]);
|
|
145
165
|
|
|
146
166
|
export const mismatchedNamespaceRegex = allPass([
|
|
147
167
|
pipe(nthArg(0), definesNamespaceRegexes),
|
|
148
|
-
pipe((
|
|
168
|
+
pipe((binding, kubernetesObject) =>
|
|
149
169
|
pipe(
|
|
150
|
-
any((
|
|
170
|
+
any((regEx: string) => new RegExp(regEx).test(carriedNamespace(kubernetesObject))),
|
|
151
171
|
not,
|
|
152
|
-
)(definedNamespaceRegexes(
|
|
172
|
+
)(definedNamespaceRegexes(binding)),
|
|
153
173
|
),
|
|
154
174
|
]);
|
|
155
175
|
|
|
@@ -158,18 +178,18 @@ export const metasMismatch = pipe(
|
|
|
158
178
|
const result = { defined, carried, unalike: {} };
|
|
159
179
|
|
|
160
180
|
result.unalike = Object.entries(result.defined)
|
|
161
|
-
.map(([key,
|
|
181
|
+
.map(([key, value]) => {
|
|
162
182
|
const keyMissing = !Object.hasOwn(result.carried, key);
|
|
163
|
-
const noValue = !
|
|
183
|
+
const noValue = !value;
|
|
164
184
|
const valMissing = !result.carried[key];
|
|
165
185
|
const valDiffers = result.carried[key] !== result.defined[key];
|
|
166
186
|
|
|
167
187
|
// prettier-ignore
|
|
168
188
|
return (
|
|
169
|
-
keyMissing ? { [key]:
|
|
189
|
+
keyMissing ? { [key]: value } :
|
|
170
190
|
noValue ? {} :
|
|
171
|
-
valMissing ? { [key]:
|
|
172
|
-
valDiffers ? { [key]:
|
|
191
|
+
valMissing ? { [key]: value } :
|
|
192
|
+
valDiffers ? { [key]: value } :
|
|
173
193
|
{}
|
|
174
194
|
)
|
|
175
195
|
})
|
|
@@ -182,38 +202,43 @@ export const metasMismatch = pipe(
|
|
|
182
202
|
|
|
183
203
|
export const mismatchedAnnotations = allPass([
|
|
184
204
|
pipe(nthArg(0), definesAnnotations),
|
|
185
|
-
pipe((
|
|
205
|
+
pipe((binding, kubernetesObject) => metasMismatch(definedAnnotations(binding), carriedAnnotations(kubernetesObject))),
|
|
186
206
|
]);
|
|
187
207
|
|
|
188
208
|
export const mismatchedLabels = allPass([
|
|
189
209
|
pipe(nthArg(0), definesLabels),
|
|
190
|
-
pipe((
|
|
210
|
+
pipe((binding, kubernetesObject) => metasMismatch(definedLabels(binding), carriedLabels(kubernetesObject))),
|
|
191
211
|
]);
|
|
192
212
|
|
|
193
213
|
export const uncarryableNamespace = allPass([
|
|
194
214
|
pipe(nthArg(0), length, gt(__, 0)),
|
|
195
215
|
pipe(nthArg(1), carriesNamespace),
|
|
196
|
-
pipe((
|
|
216
|
+
pipe((namespaceSelector, kubernetesObject) => namespaceSelector.includes(carriedNamespace(kubernetesObject)), not),
|
|
197
217
|
]);
|
|
198
218
|
|
|
199
219
|
export const carriesIgnoredNamespace = allPass([
|
|
200
220
|
pipe(nthArg(0), length, gt(__, 0)),
|
|
201
221
|
pipe(nthArg(1), carriesNamespace),
|
|
202
|
-
pipe((
|
|
222
|
+
pipe((namespaceSelector, kubernetesObject) => namespaceSelector.includes(carriedNamespace(kubernetesObject))),
|
|
203
223
|
]);
|
|
204
224
|
|
|
205
225
|
export const unbindableNamespaces = allPass([
|
|
206
226
|
pipe(nthArg(0), length, gt(__, 0)),
|
|
207
227
|
pipe(nthArg(1), definesNamespaces),
|
|
208
|
-
pipe(
|
|
228
|
+
pipe(
|
|
229
|
+
(namespaceSelector, binding) => difference(definedNamespaces(binding), namespaceSelector),
|
|
230
|
+
length,
|
|
231
|
+
equals(0),
|
|
232
|
+
not,
|
|
233
|
+
),
|
|
209
234
|
]);
|
|
210
235
|
|
|
211
236
|
export const misboundDeleteWithDeletionTimestamp = allPass([definesDelete, definesDeletionTimestamp]);
|
|
212
237
|
|
|
213
238
|
export const operationMatchesEvent = anyPass([
|
|
214
239
|
pipe(nthArg(1), equals(Event.Any)),
|
|
215
|
-
pipe((
|
|
216
|
-
pipe((
|
|
240
|
+
pipe((operation, event) => operation === event),
|
|
241
|
+
pipe((operation, event) => (operation ? event.includes(operation) : false)),
|
|
217
242
|
]);
|
|
218
243
|
|
|
219
244
|
export const mismatchedEvent = pipe(
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2023-Present The Pepr Authors
|
|
3
3
|
|
|
4
|
-
import { AdmissionRequest, Binding
|
|
4
|
+
import { AdmissionRequest, Binding } from "../types";
|
|
5
|
+
import { Operation } from "../enums";
|
|
5
6
|
import {
|
|
6
7
|
carriesIgnoredNamespace,
|
|
7
8
|
carriedName,
|
|
@@ -56,7 +57,7 @@ export function shouldSkipRequest(
|
|
|
56
57
|
|
|
57
58
|
// prettier-ignore
|
|
58
59
|
return (
|
|
59
|
-
misboundDeleteWithDeletionTimestamp(binding) ?
|
|
60
|
+
misboundDeleteWithDeletionTimestamp(binding) ?
|
|
60
61
|
`${prefix} Cannot use deletionTimestamp filter on a DELETE operation.` :
|
|
61
62
|
|
|
62
63
|
mismatchedDeletionTimestamp(binding, obj) ?
|
package/src/lib/finalizer.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { K8s, KubernetesObject, RegisterKind } from "kubernetes-fluent-client";
|
|
5
5
|
import Log from "./logger";
|
|
6
6
|
import { Binding, DeepPartial } from "./types";
|
|
7
|
-
import { Operation } from "./
|
|
7
|
+
import { Operation } from "./enums";
|
|
8
8
|
import { PeprMutateRequest } from "./mutate-request";
|
|
9
9
|
|
|
10
10
|
export function addFinalizer<K extends KubernetesObject>(request: PeprMutateRequest<K>) {
|