pepr 0.35.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 (107) 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/logger.d.ts +1 -1
  39. package/dist/lib/logger.d.ts.map +1 -1
  40. package/dist/lib/module.d.ts +2 -1
  41. package/dist/lib/module.d.ts.map +1 -1
  42. package/dist/lib/mutate-processor.d.ts +2 -1
  43. package/dist/lib/mutate-processor.d.ts.map +1 -1
  44. package/dist/lib/mutate-request.d.ts +1 -2
  45. package/dist/lib/mutate-request.d.ts.map +1 -1
  46. package/dist/lib/queue.d.ts +19 -3
  47. package/dist/lib/queue.d.ts.map +1 -1
  48. package/dist/lib/schedule.d.ts +1 -2
  49. package/dist/lib/schedule.d.ts.map +1 -1
  50. package/dist/lib/storage.d.ts.map +1 -1
  51. package/dist/lib/types.d.ts +118 -6
  52. package/dist/lib/types.d.ts.map +1 -1
  53. package/dist/lib/validate-processor.d.ts +4 -2
  54. package/dist/lib/validate-processor.d.ts.map +1 -1
  55. package/dist/lib/validate-request.d.ts +1 -1
  56. package/dist/lib/validate-request.d.ts.map +1 -1
  57. package/dist/lib/watch-processor.d.ts +8 -6
  58. package/dist/lib/watch-processor.d.ts.map +1 -1
  59. package/dist/lib.js +467 -233
  60. package/dist/lib.js.map +4 -4
  61. package/dist/sdk/sdk.d.ts +5 -3
  62. package/dist/sdk/sdk.d.ts.map +1 -1
  63. package/package.json +13 -11
  64. package/src/cli/build.ts +3 -3
  65. package/src/cli/init/index.ts +20 -11
  66. package/src/cli/init/templates.ts +1 -1
  67. package/src/cli/init/utils.test.ts +11 -20
  68. package/src/cli/init/utils.ts +5 -0
  69. package/src/cli/init/walkthrough.test.ts +92 -11
  70. package/src/cli/init/walkthrough.ts +71 -16
  71. package/src/cli/monitor.ts +1 -1
  72. package/src/cli.ts +4 -2
  73. package/src/fixtures/data/create-pod.json +1 -1
  74. package/src/fixtures/data/delete-pod.json +1 -1
  75. package/src/lib/adjudicators.test.ts +1232 -0
  76. package/src/lib/adjudicators.ts +235 -0
  77. package/src/lib/assets/index.ts +1 -1
  78. package/src/lib/assets/loader.ts +1 -0
  79. package/src/lib/assets/webhooks.ts +1 -1
  80. package/src/lib/capability.test.ts +655 -0
  81. package/src/lib/capability.ts +112 -11
  82. package/src/lib/controller/index.ts +7 -4
  83. package/src/lib/controller/store.test.ts +131 -0
  84. package/src/lib/controller/store.ts +43 -5
  85. package/src/lib/filter.test.ts +279 -9
  86. package/src/lib/filter.ts +46 -98
  87. package/src/lib/finalizer.test.ts +236 -0
  88. package/src/lib/finalizer.ts +63 -0
  89. package/src/lib/helpers.test.ts +359 -65
  90. package/src/lib/helpers.ts +141 -95
  91. package/src/lib/k8s.ts +4 -0
  92. package/src/lib/module.ts +3 -3
  93. package/src/lib/mutate-processor.ts +5 -4
  94. package/src/lib/mutate-request.test.ts +1 -2
  95. package/src/lib/mutate-request.ts +1 -3
  96. package/src/lib/queue.test.ts +138 -44
  97. package/src/lib/queue.ts +48 -13
  98. package/src/lib/schedule.ts +1 -1
  99. package/src/lib/storage.ts +5 -6
  100. package/src/lib/types.ts +154 -5
  101. package/src/lib/validate-processor.ts +5 -2
  102. package/src/lib/validate-request.test.ts +1 -4
  103. package/src/lib/validate-request.ts +1 -1
  104. package/src/lib/watch-processor.test.ts +89 -124
  105. package/src/lib/watch-processor.ts +52 -35
  106. package/src/sdk/sdk.test.ts +46 -13
  107. package/src/sdk/sdk.ts +15 -6
@@ -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,14 +202,17 @@ export class Capability implements CapabilityExport {
196
202
  filters: {
197
203
  name: "",
198
204
  namespaces: [],
205
+ regexNamespaces: [],
206
+ regexName: "",
199
207
  labels: {},
200
208
  annotations: {},
209
+ deletionTimestamp: false,
201
210
  },
202
211
  };
203
212
 
204
213
  const bindings = this.#bindings;
205
214
  const prefix = `${this.#name}: ${model.name}`;
206
- const commonChain = { WithLabel, WithAnnotation, Mutate, Validate, Watch, Reconcile };
215
+ const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile, Alias };
207
216
  const isNotEmpty = (value: object) => Object.keys(value).length > 0;
208
217
  const log = (message: string, cbString: string) => {
209
218
  const filteredObj = pickBy(isNotEmpty, binding.filters);
@@ -217,12 +226,18 @@ export class Capability implements CapabilityExport {
217
226
  if (registerAdmission) {
218
227
  log("Validate Action", validateCallback.toString());
219
228
 
229
+ // Create the child logger
230
+ const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" });
231
+
220
232
  // Push the binding to the list of bindings for this capability as a new BindingAction
221
233
  // with the callback function to preserve
222
234
  bindings.push({
223
235
  ...binding,
224
236
  isValidate: true,
225
- 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
+ },
226
241
  });
227
242
  }
228
243
 
@@ -233,12 +248,18 @@ export class Capability implements CapabilityExport {
233
248
  if (registerAdmission) {
234
249
  log("Mutate Action", mutateCallback.toString());
235
250
 
251
+ // Create the child logger
252
+ const aliasLogger = Log.child({ alias: binding.alias || "no alias provided" });
253
+
236
254
  // Push the binding to the list of bindings for this capability as a new BindingAction
237
255
  // with the callback function to preserve
238
256
  bindings.push({
239
257
  ...binding,
240
258
  isMutate: true,
241
- 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
+ },
242
263
  });
243
264
  }
244
265
 
@@ -246,35 +267,105 @@ export class Capability implements CapabilityExport {
246
267
  return { Watch, Validate, Reconcile };
247
268
  }
248
269
 
249
- function Watch(watchCallback: WatchAction<T>) {
270
+ function Watch(watchCallback: WatchLogAction<T>): FinalizeActionChain<T> {
250
271
  if (registerWatch) {
251
272
  log("Watch Action", watchCallback.toString());
252
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
253
279
  bindings.push({
254
280
  ...binding,
255
281
  isWatch: true,
256
- 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
+ },
257
286
  });
258
287
  }
288
+ return { Finalize };
259
289
  }
260
290
 
261
- function Reconcile(watchCallback: WatchAction<T>) {
291
+ function Reconcile(reconcileCallback: WatchLogAction<T>): FinalizeActionChain<T> {
262
292
  if (registerWatch) {
263
- log("Reconcile Action", watchCallback.toString());
293
+ log("Reconcile Action", reconcileCallback.toString());
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;
264
297
 
298
+ // Push the binding to the list of bindings for this capability as a new BindingAction
299
+ // with the callback function to preserve
265
300
  bindings.push({
266
301
  ...binding,
267
302
  isWatch: true,
268
303
  isQueue: true,
269
- 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
+ },
270
308
  });
271
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
+ }
272
345
  }
273
346
 
274
347
  function InNamespace(...namespaces: string[]): BindingWithName<T> {
275
348
  Log.debug(`Add namespaces filter ${namespaces}`, prefix);
276
349
  binding.filters.namespaces.push(...namespaces);
277
- 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 };
357
+ }
358
+
359
+ function WithDeletionTimestamp(): BindingFilter<T> {
360
+ Log.debug("Add deletionTimestamp filter");
361
+ binding.filters.deletionTimestamp = true;
362
+ return commonChain;
363
+ }
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;
278
369
  }
279
370
 
280
371
  function WithName(name: string): BindingFilter<T> {
@@ -295,12 +386,22 @@ export class Capability implements CapabilityExport {
295
386
  return commonChain;
296
387
  }
297
388
 
389
+ function Alias(alias: string) {
390
+ Log.debug(`Adding prefix alias ${alias}`, prefix);
391
+ binding.alias = alias;
392
+ return commonChain;
393
+ }
394
+
298
395
  function bindEvent(event: Event) {
299
396
  binding.event = event;
300
397
  return {
301
398
  ...commonChain,
302
399
  InNamespace,
400
+ InNamespaceRegex,
303
401
  WithName,
402
+ WithNameRegex,
403
+ WithDeletionTimestamp,
404
+ Alias,
304
405
  };
305
406
  }
306
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
+ }