pepr 0.38.3 → 0.39.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/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 +278 -225
- package/dist/controller.js +37 -27
- 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} +15 -13
- 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 +220 -295
- 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 +11 -7
- 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} +39 -28
- 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,7 @@
|
|
|
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
5
|
import {
|
|
6
6
|
__,
|
|
7
7
|
allPass,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
/*
|
|
23
23
|
Naming scheme:
|
|
24
24
|
- AdmissionRequest - "declares" / "neglects"
|
|
25
|
-
-
|
|
25
|
+
- KuberneteskubernetesObjectect - "carries" / "missing"
|
|
26
26
|
- Binding - "defines" / "ignores"
|
|
27
27
|
*/
|
|
28
28
|
|
|
@@ -36,27 +36,33 @@ export const declaredKind = pipe(request => request?.kind?.kind, defaultTo(""));
|
|
|
36
36
|
export const declaredUid = pipe(request => request?.uid, defaultTo(""));
|
|
37
37
|
|
|
38
38
|
/*
|
|
39
|
-
|
|
39
|
+
KuberneteskubernetesObjectect collectors
|
|
40
40
|
*/
|
|
41
|
-
export const carriesDeletionTimestamp = pipe(
|
|
41
|
+
export const carriesDeletionTimestamp = pipe(
|
|
42
|
+
kubernetesObject => !!kubernetesObject.metadata?.deletionTimestamp,
|
|
43
|
+
defaultTo(false),
|
|
44
|
+
);
|
|
42
45
|
export const missingDeletionTimestamp = complement(carriesDeletionTimestamp);
|
|
43
46
|
|
|
44
|
-
export const
|
|
47
|
+
export const carriedKind = pipe(kubernetesObject => kubernetesObject?.metadata?.kind, defaultTo("not set"));
|
|
48
|
+
export const carriedVersion = pipe(kubernetesObject => kubernetesObject?.metadata?.version, defaultTo("not set"));
|
|
49
|
+
export const carriedName = pipe(kubernetesObject => kubernetesObject?.metadata?.name, defaultTo(""));
|
|
45
50
|
export const carriesName = pipe(carriedName, equals(""), not);
|
|
46
51
|
export const missingName = complement(carriesName);
|
|
47
52
|
|
|
48
|
-
export const carriedNamespace = pipe(
|
|
53
|
+
export const carriedNamespace = pipe(kubernetesObject => kubernetesObject?.metadata?.namespace, defaultTo(""));
|
|
49
54
|
export const carriesNamespace = pipe(carriedNamespace, equals(""), not);
|
|
50
55
|
|
|
51
|
-
export const carriedAnnotations = pipe(
|
|
56
|
+
export const carriedAnnotations = pipe(kubernetesObject => kubernetesObject?.metadata?.annotations, defaultTo({}));
|
|
52
57
|
export const carriesAnnotations = pipe(carriedAnnotations, equals({}), not);
|
|
53
58
|
|
|
54
|
-
export const carriedLabels = pipe(
|
|
59
|
+
export const carriedLabels = pipe(kubernetesObject => kubernetesObject?.metadata?.labels, defaultTo({}));
|
|
55
60
|
export const carriesLabels = pipe(carriedLabels, equals({}), not);
|
|
56
61
|
|
|
57
62
|
/*
|
|
58
63
|
Binding collectors
|
|
59
64
|
*/
|
|
65
|
+
|
|
60
66
|
export const definesDeletionTimestamp = pipe(binding => binding?.filters?.deletionTimestamp, defaultTo(false));
|
|
61
67
|
export const ignoresDeletionTimestamp = complement(definesDeletionTimestamp);
|
|
62
68
|
|
|
@@ -112,7 +118,7 @@ export const definedCallback = pipe(binding => {
|
|
|
112
118
|
null
|
|
113
119
|
);
|
|
114
120
|
});
|
|
115
|
-
export const definedCallbackName = pipe(definedCallback, defaultTo({ name: "" }),
|
|
121
|
+
export const definedCallbackName = pipe(definedCallback, defaultTo({ name: "" }), callback => callback.name);
|
|
116
122
|
|
|
117
123
|
/*
|
|
118
124
|
post-collection comparitors
|
|
@@ -124,32 +130,32 @@ export const mismatchedDeletionTimestamp = allPass([
|
|
|
124
130
|
|
|
125
131
|
export const mismatchedName = allPass([
|
|
126
132
|
pipe(nthArg(0), definesName),
|
|
127
|
-
pipe((
|
|
133
|
+
pipe((binding, kubernetesObject) => definedName(binding) !== carriedName(kubernetesObject)),
|
|
128
134
|
]);
|
|
129
135
|
|
|
130
136
|
export const mismatchedNameRegex = allPass([
|
|
131
137
|
pipe(nthArg(0), definesNameRegex),
|
|
132
|
-
pipe((
|
|
138
|
+
pipe((binding, kubernetesObject) => new RegExp(definedNameRegex(binding)).test(carriedName(kubernetesObject)), not),
|
|
133
139
|
]);
|
|
134
140
|
|
|
135
141
|
export const bindsToKind = curry(
|
|
136
|
-
allPass([pipe(nthArg(0), definedKind, equals(""), not), pipe((
|
|
142
|
+
allPass([pipe(nthArg(0), definedKind, equals(""), not), pipe((binding, kind) => definedKind(binding) === kind)]),
|
|
137
143
|
);
|
|
138
144
|
export const bindsToNamespace = curry(pipe(bindsToKind(__, "Namespace")));
|
|
139
145
|
export const misboundNamespace = allPass([bindsToNamespace, definesNamespaces]);
|
|
140
146
|
|
|
141
147
|
export const mismatchedNamespace = allPass([
|
|
142
148
|
pipe(nthArg(0), definesNamespaces),
|
|
143
|
-
pipe((
|
|
149
|
+
pipe((binding, kubernetesObject) => definedNamespaces(binding).includes(carriedNamespace(kubernetesObject)), not),
|
|
144
150
|
]);
|
|
145
151
|
|
|
146
152
|
export const mismatchedNamespaceRegex = allPass([
|
|
147
153
|
pipe(nthArg(0), definesNamespaceRegexes),
|
|
148
|
-
pipe((
|
|
154
|
+
pipe((binding, kubernetesObject) =>
|
|
149
155
|
pipe(
|
|
150
|
-
any((
|
|
156
|
+
any((regEx: string) => new RegExp(regEx).test(carriedNamespace(kubernetesObject))),
|
|
151
157
|
not,
|
|
152
|
-
)(definedNamespaceRegexes(
|
|
158
|
+
)(definedNamespaceRegexes(binding)),
|
|
153
159
|
),
|
|
154
160
|
]);
|
|
155
161
|
|
|
@@ -158,18 +164,18 @@ export const metasMismatch = pipe(
|
|
|
158
164
|
const result = { defined, carried, unalike: {} };
|
|
159
165
|
|
|
160
166
|
result.unalike = Object.entries(result.defined)
|
|
161
|
-
.map(([key,
|
|
167
|
+
.map(([key, value]) => {
|
|
162
168
|
const keyMissing = !Object.hasOwn(result.carried, key);
|
|
163
|
-
const noValue = !
|
|
169
|
+
const noValue = !value;
|
|
164
170
|
const valMissing = !result.carried[key];
|
|
165
171
|
const valDiffers = result.carried[key] !== result.defined[key];
|
|
166
172
|
|
|
167
173
|
// prettier-ignore
|
|
168
174
|
return (
|
|
169
|
-
keyMissing ? { [key]:
|
|
175
|
+
keyMissing ? { [key]: value } :
|
|
170
176
|
noValue ? {} :
|
|
171
|
-
valMissing ? { [key]:
|
|
172
|
-
valDiffers ? { [key]:
|
|
177
|
+
valMissing ? { [key]: value } :
|
|
178
|
+
valDiffers ? { [key]: value } :
|
|
173
179
|
{}
|
|
174
180
|
)
|
|
175
181
|
})
|
|
@@ -182,38 +188,43 @@ export const metasMismatch = pipe(
|
|
|
182
188
|
|
|
183
189
|
export const mismatchedAnnotations = allPass([
|
|
184
190
|
pipe(nthArg(0), definesAnnotations),
|
|
185
|
-
pipe((
|
|
191
|
+
pipe((binding, kubernetesObject) => metasMismatch(definedAnnotations(binding), carriedAnnotations(kubernetesObject))),
|
|
186
192
|
]);
|
|
187
193
|
|
|
188
194
|
export const mismatchedLabels = allPass([
|
|
189
195
|
pipe(nthArg(0), definesLabels),
|
|
190
|
-
pipe((
|
|
196
|
+
pipe((binding, kubernetesObject) => metasMismatch(definedLabels(binding), carriedLabels(kubernetesObject))),
|
|
191
197
|
]);
|
|
192
198
|
|
|
193
199
|
export const uncarryableNamespace = allPass([
|
|
194
200
|
pipe(nthArg(0), length, gt(__, 0)),
|
|
195
201
|
pipe(nthArg(1), carriesNamespace),
|
|
196
|
-
pipe((
|
|
202
|
+
pipe((namespaceSelector, kubernetesObject) => namespaceSelector.includes(carriedNamespace(kubernetesObject)), not),
|
|
197
203
|
]);
|
|
198
204
|
|
|
199
205
|
export const carriesIgnoredNamespace = allPass([
|
|
200
206
|
pipe(nthArg(0), length, gt(__, 0)),
|
|
201
207
|
pipe(nthArg(1), carriesNamespace),
|
|
202
|
-
pipe((
|
|
208
|
+
pipe((namespaceSelector, kubernetesObject) => namespaceSelector.includes(carriedNamespace(kubernetesObject))),
|
|
203
209
|
]);
|
|
204
210
|
|
|
205
211
|
export const unbindableNamespaces = allPass([
|
|
206
212
|
pipe(nthArg(0), length, gt(__, 0)),
|
|
207
213
|
pipe(nthArg(1), definesNamespaces),
|
|
208
|
-
pipe(
|
|
214
|
+
pipe(
|
|
215
|
+
(namespaceSelector, binding) => difference(definedNamespaces(binding), namespaceSelector),
|
|
216
|
+
length,
|
|
217
|
+
equals(0),
|
|
218
|
+
not,
|
|
219
|
+
),
|
|
209
220
|
]);
|
|
210
221
|
|
|
211
222
|
export const misboundDeleteWithDeletionTimestamp = allPass([definesDelete, definesDeletionTimestamp]);
|
|
212
223
|
|
|
213
224
|
export const operationMatchesEvent = anyPass([
|
|
214
225
|
pipe(nthArg(1), equals(Event.Any)),
|
|
215
|
-
pipe((
|
|
216
|
-
pipe((
|
|
226
|
+
pipe((operation, event) => operation === event),
|
|
227
|
+
pipe((operation, event) => (operation ? event.includes(operation) : false)),
|
|
217
228
|
]);
|
|
218
229
|
|
|
219
230
|
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>) {
|