pepr 0.36.0 → 0.37.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.
Files changed (96) hide show
  1. package/dist/cli/init/index.d.ts.map +1 -1
  2. package/dist/cli/init/templates.d.ts +3 -1
  3. package/dist/cli/init/templates.d.ts.map +1 -1
  4. package/dist/cli/init/utils.d.ts.map +1 -1
  5. package/dist/cli/init/walkthrough.d.ts +10 -3
  6. package/dist/cli/init/walkthrough.d.ts.map +1 -1
  7. package/dist/cli.js +253 -31
  8. package/dist/controller.js +138 -1
  9. package/dist/lib/adjudicators.d.ts +63 -0
  10. package/dist/lib/adjudicators.d.ts.map +1 -0
  11. package/dist/lib/adjudicators.test.d.ts +2 -0
  12. package/dist/lib/adjudicators.test.d.ts.map +1 -0
  13. package/dist/lib/assets/loader.d.ts.map +1 -1
  14. package/dist/lib/assets/pods.d.ts +1 -0
  15. package/dist/lib/assets/pods.d.ts.map +1 -1
  16. package/dist/lib/capability.d.ts +1 -0
  17. package/dist/lib/capability.d.ts.map +1 -1
  18. package/dist/lib/capability.test.d.ts +2 -0
  19. package/dist/lib/capability.test.d.ts.map +1 -0
  20. package/dist/lib/controller/index.d.ts.map +1 -1
  21. package/dist/lib/controller/store.d.ts +4 -0
  22. package/dist/lib/controller/store.d.ts.map +1 -1
  23. package/dist/lib/controller/store.test.d.ts +2 -0
  24. package/dist/lib/controller/store.test.d.ts.map +1 -0
  25. package/dist/lib/filter.d.ts +2 -3
  26. package/dist/lib/filter.d.ts.map +1 -1
  27. package/dist/lib/filter.test.d.ts +2 -1
  28. package/dist/lib/filter.test.d.ts.map +1 -1
  29. package/dist/lib/finalizer.d.ts +6 -0
  30. package/dist/lib/finalizer.d.ts.map +1 -0
  31. package/dist/lib/finalizer.test.d.ts +2 -0
  32. package/dist/lib/finalizer.test.d.ts.map +1 -0
  33. package/dist/lib/helpers.d.ts +2 -2
  34. package/dist/lib/helpers.d.ts.map +1 -1
  35. package/dist/lib/helpers.test.d.ts +1 -1
  36. package/dist/lib/helpers.test.d.ts.map +1 -1
  37. package/dist/lib/k8s.d.ts.map +1 -1
  38. package/dist/lib/module.d.ts +2 -1
  39. package/dist/lib/module.d.ts.map +1 -1
  40. package/dist/lib/mutate-processor.d.ts +2 -1
  41. package/dist/lib/mutate-processor.d.ts.map +1 -1
  42. package/dist/lib/mutate-request.d.ts +1 -2
  43. package/dist/lib/mutate-request.d.ts.map +1 -1
  44. package/dist/lib/schedule.d.ts +1 -2
  45. package/dist/lib/schedule.d.ts.map +1 -1
  46. package/dist/lib/storage.d.ts.map +1 -1
  47. package/dist/lib/types.d.ts +115 -6
  48. package/dist/lib/types.d.ts.map +1 -1
  49. package/dist/lib/validate-processor.d.ts +4 -2
  50. package/dist/lib/validate-processor.d.ts.map +1 -1
  51. package/dist/lib/validate-request.d.ts +1 -1
  52. package/dist/lib/validate-request.d.ts.map +1 -1
  53. package/dist/lib/watch-processor.d.ts +1 -1
  54. package/dist/lib/watch-processor.d.ts.map +1 -1
  55. package/dist/lib.js +383 -204
  56. package/dist/lib.js.map +4 -4
  57. package/package.json +9 -7
  58. package/src/cli/build.ts +3 -3
  59. package/src/cli/init/index.ts +20 -11
  60. package/src/cli/init/templates.ts +1 -1
  61. package/src/cli/init/utils.test.ts +11 -20
  62. package/src/cli/init/utils.ts +5 -0
  63. package/src/cli/init/walkthrough.test.ts +92 -11
  64. package/src/cli/init/walkthrough.ts +71 -16
  65. package/src/cli/monitor.ts +1 -1
  66. package/src/cli.ts +4 -2
  67. package/src/fixtures/data/create-pod.json +1 -1
  68. package/src/fixtures/data/delete-pod.json +1 -1
  69. package/src/lib/adjudicators.test.ts +1232 -0
  70. package/src/lib/adjudicators.ts +235 -0
  71. package/src/lib/assets/index.ts +1 -1
  72. package/src/lib/assets/loader.ts +1 -0
  73. package/src/lib/assets/webhooks.ts +1 -1
  74. package/src/lib/capability.test.ts +655 -0
  75. package/src/lib/capability.ts +104 -11
  76. package/src/lib/controller/index.ts +7 -4
  77. package/src/lib/controller/store.test.ts +131 -0
  78. package/src/lib/controller/store.ts +43 -5
  79. package/src/lib/filter.test.ts +194 -8
  80. package/src/lib/filter.ts +46 -107
  81. package/src/lib/finalizer.test.ts +236 -0
  82. package/src/lib/finalizer.ts +63 -0
  83. package/src/lib/helpers.test.ts +329 -69
  84. package/src/lib/helpers.ts +141 -100
  85. package/src/lib/k8s.ts +4 -0
  86. package/src/lib/module.ts +3 -3
  87. package/src/lib/mutate-processor.ts +5 -4
  88. package/src/lib/mutate-request.test.ts +1 -2
  89. package/src/lib/mutate-request.ts +1 -3
  90. package/src/lib/schedule.ts +1 -1
  91. package/src/lib/storage.ts +5 -6
  92. package/src/lib/types.ts +151 -5
  93. package/src/lib/validate-processor.ts +5 -2
  94. package/src/lib/validate-request.test.ts +1 -4
  95. package/src/lib/validate-request.ts +1 -1
  96. package/src/lib/watch-processor.ts +19 -5
@@ -2,9 +2,7 @@
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
4
  import { GenericClass, GroupVersionKind, modelToGroupVersionKind } from "kubernetes-fluent-client";
5
- import { WatchAction } from "kubernetes-fluent-client/dist/fluent/types";
6
5
  import { pickBy } from "ramda";
7
-
8
6
  import Log from "./logger";
9
7
  import { isBuildMode, isDevMode, isWatchMode } from "./module";
10
8
  import { PeprStore, Storage } from "./storage";
@@ -20,8 +18,12 @@ import {
20
18
  MutateActionChain,
21
19
  ValidateAction,
22
20
  ValidateActionChain,
21
+ WatchLogAction,
22
+ FinalizeAction,
23
+ FinalizeActionChain,
23
24
  WhenSelector,
24
25
  } from "./types";
26
+ import { addFinalizer } from "./finalizer";
25
27
 
26
28
  const registerAdmission = isBuildMode() || !isWatchMode();
27
29
  const registerWatch = isBuildMode() || isWatchMode() || isDevMode();
@@ -69,6 +71,10 @@ export class Capability implements CapabilityExport {
69
71
  }
70
72
  };
71
73
 
74
+ public getScheduleStore() {
75
+ return this.#scheduleStore;
76
+ }
77
+
72
78
  /**
73
79
  * Store is a key-value data store that can be used to persist data that should be shared
74
80
  * between requests. Each capability has its own store, and the data is persisted in Kubernetes
@@ -196,6 +202,8 @@ export class Capability implements CapabilityExport {
196
202
  filters: {
197
203
  name: "",
198
204
  namespaces: [],
205
+ regexNamespaces: [],
206
+ regexName: "",
199
207
  labels: {},
200
208
  annotations: {},
201
209
  deletionTimestamp: false,
@@ -204,7 +212,7 @@ export class Capability implements CapabilityExport {
204
212
 
205
213
  const bindings = this.#bindings;
206
214
  const prefix = `${this.#name}: ${model.name}`;
207
- const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile };
215
+ const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile, Alias };
208
216
  const isNotEmpty = (value: object) => Object.keys(value).length > 0;
209
217
  const log = (message: string, cbString: string) => {
210
218
  const filteredObj = pickBy(isNotEmpty, binding.filters);
@@ -218,12 +226,18 @@ export class Capability implements CapabilityExport {
218
226
  if (registerAdmission) {
219
227
  log("Validate Action", validateCallback.toString());
220
228
 
229
+ // Create the child logger
230
+ const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" });
231
+
221
232
  // Push the binding to the list of bindings for this capability as a new BindingAction
222
233
  // with the callback function to preserve
223
234
  bindings.push({
224
235
  ...binding,
225
236
  isValidate: true,
226
- validateCallback,
237
+ validateCallback: async (req, logger = aliasLogger) => {
238
+ Log.info(`Executing validate action with alias: ${binding.alias || "no alias provided"}`);
239
+ return await validateCallback(req, logger);
240
+ },
227
241
  });
228
242
  }
229
243
 
@@ -234,12 +248,18 @@ export class Capability implements CapabilityExport {
234
248
  if (registerAdmission) {
235
249
  log("Mutate Action", mutateCallback.toString());
236
250
 
251
+ // Create the child logger
252
+ const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" });
253
+
237
254
  // Push the binding to the list of bindings for this capability as a new BindingAction
238
255
  // with the callback function to preserve
239
256
  bindings.push({
240
257
  ...binding,
241
258
  isMutate: true,
242
- mutateCallback,
259
+ mutateCallback: async (req, logger = aliasLogger) => {
260
+ Log.info(`Executing mutation action with alias: ${binding.alias || "no alias provided"}`);
261
+ await mutateCallback(req, logger);
262
+ },
243
263
  });
244
264
  }
245
265
 
@@ -247,35 +267,93 @@ export class Capability implements CapabilityExport {
247
267
  return { Watch, Validate, Reconcile };
248
268
  }
249
269
 
250
- function Watch(watchCallback: WatchAction<T>) {
270
+ function Watch(watchCallback: WatchLogAction<T>): FinalizeActionChain<T> {
251
271
  if (registerWatch) {
252
272
  log("Watch Action", watchCallback.toString());
253
273
 
274
+ // Create the child logger and cast it to the expected type
275
+ const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" }) as typeof Log;
276
+
277
+ // Push the binding to the list of bindings for this capability as a new BindingAction
278
+ // with the callback function to preserve
254
279
  bindings.push({
255
280
  ...binding,
256
281
  isWatch: true,
257
- watchCallback,
282
+ watchCallback: async (update, phase, logger = aliasLogger) => {
283
+ Log.info(`Executing watch action with alias: ${binding.alias || "no alias provided"}`);
284
+ await watchCallback(update, phase, logger);
285
+ },
258
286
  });
259
287
  }
288
+ return { Finalize };
260
289
  }
261
290
 
262
- function Reconcile(watchCallback: WatchAction<T>) {
291
+ function Reconcile(reconcileCallback: WatchLogAction<T>): FinalizeActionChain<T> {
263
292
  if (registerWatch) {
264
- log("Reconcile Action", watchCallback.toString());
293
+ log("Reconcile Action", reconcileCallback.toString());
265
294
 
295
+ // Create the child logger and cast it to the expected type
296
+ const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" }) as typeof Log;
297
+
298
+ // Push the binding to the list of bindings for this capability as a new BindingAction
299
+ // with the callback function to preserve
266
300
  bindings.push({
267
301
  ...binding,
268
302
  isWatch: true,
269
303
  isQueue: true,
270
- watchCallback,
304
+ watchCallback: async (update, phase, logger = aliasLogger) => {
305
+ Log.info(`Executing reconcile action with alias: ${binding.alias || "no alias provided"}`);
306
+ await reconcileCallback(update, phase, logger);
307
+ },
271
308
  });
272
309
  }
310
+ return { Finalize };
311
+ }
312
+
313
+ function Finalize(finalizeCallback: FinalizeAction<T>): void {
314
+ log("Finalize Action", finalizeCallback.toString());
315
+
316
+ // Create the child logger and cast it to the expected type
317
+ const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" }) as typeof Log;
318
+
319
+ // Add binding to inject Pepr finalizer during admission (Mutate)
320
+ if (registerAdmission) {
321
+ const mutateBinding = {
322
+ ...binding,
323
+ isMutate: true,
324
+ isFinalize: true,
325
+ event: Event.Any,
326
+ mutateCallback: addFinalizer,
327
+ };
328
+ bindings.push(mutateBinding);
329
+ }
330
+
331
+ // Add binding to process finalizer callback / remove Pepr finalizer (Watch)
332
+ if (registerWatch) {
333
+ const watchBinding = {
334
+ ...binding,
335
+ isWatch: true,
336
+ isFinalize: true,
337
+ event: Event.Update,
338
+ finalizeCallback: async (update: InstanceType<T>, logger = aliasLogger) => {
339
+ Log.info(`Executing finalize action with alias: ${binding.alias || "no alias provided"}`);
340
+ await finalizeCallback(update, logger);
341
+ },
342
+ };
343
+ bindings.push(watchBinding);
344
+ }
273
345
  }
274
346
 
275
347
  function InNamespace(...namespaces: string[]): BindingWithName<T> {
276
348
  Log.debug(`Add namespaces filter ${namespaces}`, prefix);
277
349
  binding.filters.namespaces.push(...namespaces);
278
- return { ...commonChain, WithName };
350
+ return { ...commonChain, WithName, WithNameRegex };
351
+ }
352
+
353
+ function InNamespaceRegex(...namespaces: RegExp[]): BindingWithName<T> {
354
+ Log.debug(`Add regex namespaces filter ${namespaces}`, prefix);
355
+ binding.filters.regexNamespaces.push(...namespaces.map(regex => regex.source));
356
+ return { ...commonChain, WithName, WithNameRegex };
279
357
  }
280
358
 
281
359
  function WithDeletionTimestamp(): BindingFilter<T> {
@@ -284,6 +362,12 @@ export class Capability implements CapabilityExport {
284
362
  return commonChain;
285
363
  }
286
364
 
365
+ function WithNameRegex(regexName: RegExp): BindingFilter<T> {
366
+ Log.debug(`Add regex name filter ${regexName}`, prefix);
367
+ binding.filters.regexName = regexName.source;
368
+ return commonChain;
369
+ }
370
+
287
371
  function WithName(name: string): BindingFilter<T> {
288
372
  Log.debug(`Add name filter ${name}`, prefix);
289
373
  binding.filters.name = name;
@@ -302,13 +386,22 @@ export class Capability implements CapabilityExport {
302
386
  return commonChain;
303
387
  }
304
388
 
389
+ function Alias(alias: string) {
390
+ Log.debug(`Adding prefix alias ${alias}`, prefix);
391
+ binding.alias = alias;
392
+ return commonChain;
393
+ }
394
+
305
395
  function bindEvent(event: Event) {
306
396
  binding.event = event;
307
397
  return {
308
398
  ...commonChain,
309
399
  InNamespace,
400
+ InNamespaceRegex,
310
401
  WithName,
402
+ WithNameRegex,
311
403
  WithDeletionTimestamp,
404
+ Alias,
312
405
  };
313
406
  }
314
407
 
@@ -15,6 +15,9 @@ import { validateProcessor } from "../validate-processor";
15
15
  import { PeprControllerStore } from "./store";
16
16
  import { ResponseItem } from "../types";
17
17
 
18
+ if (!process.env.PEPR_NODE_WARNINGS) {
19
+ process.removeAllListeners("warning");
20
+ }
18
21
  export class Controller {
19
22
  // Track whether the server is running
20
23
  #running = false;
@@ -108,7 +111,7 @@ export class Controller {
108
111
  // Handle EADDRINUSE errors
109
112
  server.on("error", (e: { code: string }) => {
110
113
  if (e.code === "EADDRINUSE") {
111
- Log.warn(
114
+ Log.info(
112
115
  `Address in use, retrying in 2 seconds. If this persists, ensure ${port} is not in use, e.g. "lsof -i :${port}"`,
113
116
  );
114
117
  setTimeout(() => {
@@ -162,7 +165,7 @@ export class Controller {
162
165
  const { token } = req.params;
163
166
  if (token !== this.#token) {
164
167
  const err = `Unauthorized: invalid token '${token.replace(/[^\w]/g, "_")}'`;
165
- Log.warn(err);
168
+ Log.info(err);
166
169
  res.status(401).send(err);
167
170
  this.#metricsCollector.alert();
168
171
  return;
@@ -227,7 +230,7 @@ export class Controller {
227
230
  if (admissionKind === "Mutate") {
228
231
  response = await mutateProcessor(this.#config, this.#capabilities, request, reqMetadata);
229
232
  } else {
230
- response = await validateProcessor(this.#capabilities, request, reqMetadata);
233
+ response = await validateProcessor(this.#config, this.#capabilities, request, reqMetadata);
231
234
  }
232
235
 
233
236
  // Run the after hook if it exists
@@ -304,7 +307,7 @@ export class Controller {
304
307
  duration: `${elapsedTime} ms`,
305
308
  };
306
309
 
307
- res.statusCode >= 300 ? Log.warn(message) : Log.info(message);
310
+ Log.info(message);
308
311
  });
309
312
 
310
313
  next();
@@ -0,0 +1,131 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { expect, test, describe, afterEach } from "@jest/globals";
5
+ import * as fc from "fast-check";
6
+ import { redactedStore, redactedPatch } from "./store";
7
+ import { AddOperation } from "fast-json-patch";
8
+
9
+ const redactedValue = "**redacted**";
10
+ const peprStoreFuzz = fc.record({
11
+ kind: fc.constant("PeprStore"),
12
+ apiVersion: fc.constant("v1"),
13
+ metadata: fc.record({
14
+ name: fc.string(),
15
+ namespace: fc.string(),
16
+ }),
17
+ data: fc.dictionary(
18
+ fc.string().filter(str => str !== "__proto__"),
19
+ fc.string().filter(str => str !== "__proto__"),
20
+ ),
21
+ });
22
+ describe("Fuzzing redactedStore", () => {
23
+ afterEach(() => {
24
+ delete process.env.PEPR_STORE_REDACT_VALUES;
25
+ });
26
+
27
+ test("should redact values if PEPR_STORE_REDACT_VALUES is true", () => {
28
+ fc.assert(
29
+ fc.property(peprStoreFuzz, store => {
30
+ process.env.PEPR_STORE_REDACT_VALUES = "true";
31
+ const result = redactedStore(store);
32
+
33
+ Object.values(result.data).forEach(value => {
34
+ expect(value).toBe(redactedValue);
35
+ });
36
+ }),
37
+ );
38
+ });
39
+
40
+ test("should not redact values if PEPR_STORE_REDACT_VALUES is not true", () => {
41
+ fc.assert(
42
+ fc.property(peprStoreFuzz, store => {
43
+ process.env.PEPR_STORE_REDACT_VALUES = "false";
44
+ const result = redactedStore(store);
45
+ expect(result.data).toEqual(store.data);
46
+ }),
47
+ );
48
+ });
49
+
50
+ test("should maintain other properties of the store", () => {
51
+ fc.assert(
52
+ fc.property(peprStoreFuzz, store => {
53
+ const redactionEnabled = fc.boolean();
54
+ process.env.PEPR_STORE_REDACT_VALUES = redactionEnabled ? "true" : "false";
55
+
56
+ const result = redactedStore(store);
57
+
58
+ expect(result.kind).toBe(store.kind);
59
+ expect(result.apiVersion).toBe(store.apiVersion);
60
+ expect(result.metadata).toEqual(store.metadata);
61
+ }),
62
+ );
63
+ });
64
+ });
65
+
66
+ const addOperationKeys = [
67
+ "add:/data/hello-pepr-a:secret",
68
+ "add:/data/hello-pepr-v2-b:secret",
69
+ "add:/data/hello-pepr-v2-c:secret",
70
+ "add:/data/hello-pepr-v2-d:secret",
71
+ "add:/data/hello-pepr-v2-e:secret",
72
+ "add:/data/hello-pepr-v2-f:secret",
73
+ ];
74
+ const addOperationValues: AddOperation<string>[] = [
75
+ {
76
+ op: "add",
77
+ path: "add:/data/hello-pepr-a",
78
+ value: "secret",
79
+ },
80
+ {
81
+ op: "add",
82
+ path: "add:/data/hello-pepr-v2-b",
83
+ value: "secret",
84
+ },
85
+ {
86
+ op: "add",
87
+ path: "add:/data/hello-pepr-v2-c",
88
+ value: "secret",
89
+ },
90
+ {
91
+ op: "add",
92
+ path: "add:/data/hello-pepr-v2-d",
93
+ value: "secret",
94
+ },
95
+ {
96
+ op: "add",
97
+ path: "add:/data/hello-pepr-v2-e",
98
+ value: "secret",
99
+ },
100
+ {
101
+ op: "add",
102
+ path: "add:/data/hello-pepr-v2-f",
103
+ value: "secret",
104
+ },
105
+ ];
106
+
107
+ describe("redactedPatch", () => {
108
+ afterEach(() => {
109
+ delete process.env.PEPR_STORE_REDACT_VALUES;
110
+ });
111
+
112
+ test("should redact keys and values if PEPR_STORE_REDACT_VALUES is true", () => {
113
+ process.env.PEPR_STORE_REDACT_VALUES = "true";
114
+ addOperationKeys.forEach((key, i) => {
115
+ const redactedResult = redactedPatch({ [key]: addOperationValues[i] });
116
+ for (const [k, v] of Object.entries(redactedResult)) {
117
+ expect(k).toContain(`:${redactedValue}`);
118
+ expect(v).toEqual(expect.objectContaining({ value: redactedValue }));
119
+ }
120
+ });
121
+ });
122
+ test("should not redact keys and values if PEPR_STORE_REDACT_VALUES is not true", () => {
123
+ addOperationKeys.forEach((key, i) => {
124
+ const redactedResult = redactedPatch({ [key]: addOperationValues[i] });
125
+ for (const [k, v] of Object.entries(redactedResult)) {
126
+ expect(k).not.toContain(`:${redactedValue}`);
127
+ expect(v).not.toEqual(expect.objectContaining({ value: redactedValue }));
128
+ }
129
+ });
130
+ });
131
+ });
@@ -10,6 +10,7 @@ import { PeprStore } from "../k8s";
10
10
  import Log from "../logger";
11
11
  import { DataOp, DataSender, DataStore, Storage } from "../storage";
12
12
 
13
+ const redactedValue = "**redacted**";
13
14
  const namespace = "pepr-system";
14
15
  export const debounceBackoff = 5000;
15
16
 
@@ -75,7 +76,7 @@ export class PeprControllerStore {
75
76
  };
76
77
 
77
78
  #migrateAndSetupWatch = async (store: PeprStore) => {
78
- Log.debug(store, "Pepr Store migration");
79
+ Log.debug(redactedStore(store), "Pepr Store migration");
79
80
  const data: DataStore = store.data || {};
80
81
  const migrateCache: Record<string, Operation> = {};
81
82
 
@@ -91,7 +92,9 @@ export class PeprControllerStore {
91
92
 
92
93
  try {
93
94
  // Send the patch to the cluster
94
- await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
95
+ if (payload.length > 0) {
96
+ await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
97
+ }
95
98
  } catch (err) {
96
99
  Log.error(err, "Pepr store update failure");
97
100
 
@@ -158,7 +161,7 @@ export class PeprControllerStore {
158
161
  };
159
162
 
160
163
  #receive = (store: PeprStore) => {
161
- Log.debug(store, "Pepr Store update");
164
+ Log.debug(redactedStore(store), "Pepr Store update");
162
165
 
163
166
  // Wrap the update in a debounced function
164
167
  const debounced = () => {
@@ -247,7 +250,9 @@ export class PeprControllerStore {
247
250
 
248
251
  try {
249
252
  // Send the patch to the cluster
250
- await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
253
+ if (payload.length > 0) {
254
+ await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
255
+ }
251
256
  } catch (err) {
252
257
  Log.error(err, "Pepr store update failure");
253
258
 
@@ -270,7 +275,7 @@ export class PeprControllerStore {
270
275
  // Send any cached updates every debounceBackoff milliseconds
271
276
  setInterval(() => {
272
277
  if (Object.keys(sendCache).length > 0) {
273
- Log.debug(sendCache, "Sending updates to Pepr store");
278
+ Log.debug(redactedPatch(sendCache), "Sending updates to Pepr store");
274
279
  void flushCache();
275
280
  }
276
281
  }, debounceBackoff);
@@ -301,3 +306,36 @@ export class PeprControllerStore {
301
306
  }
302
307
  };
303
308
  }
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
+ }