pepr 0.34.0 → 0.35.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/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "/dist",
14
14
  "/src"
15
15
  ],
16
- "version": "0.34.0",
16
+ "version": "0.35.0",
17
17
  "main": "dist/lib.js",
18
18
  "types": "dist/lib.d.ts",
19
19
  "scripts": {
@@ -38,22 +38,22 @@
38
38
  "format:fix": "eslint src --fix && prettier src --write"
39
39
  },
40
40
  "dependencies": {
41
- "@types/ramda": "0.30.1",
41
+ "@types/ramda": "0.30.2",
42
42
  "express": "4.19.2",
43
43
  "fast-json-patch": "3.1.1",
44
44
  "json-pointer": "^0.6.2",
45
- "kubernetes-fluent-client": "3.0.1",
45
+ "kubernetes-fluent-client": "3.0.2",
46
46
  "pino": "9.3.2",
47
47
  "pino-pretty": "11.2.2",
48
48
  "prom-client": "15.1.3",
49
49
  "ramda": "0.30.1"
50
50
  },
51
51
  "devDependencies": {
52
- "@commitlint/cli": "19.3.0",
53
- "@commitlint/config-conventional": "19.2.2",
52
+ "@commitlint/cli": "19.4.1",
53
+ "@commitlint/config-conventional": "19.4.1",
54
54
  "@fast-check/jest": "^2.0.1",
55
55
  "@jest/globals": "29.7.0",
56
- "@types/eslint": "9.6.0",
56
+ "@types/eslint": "9.6.1",
57
57
  "@types/express": "4.17.21",
58
58
  "@types/json-pointer": "^1.0.34",
59
59
  "@types/node": "22.x.x",
@@ -63,8 +63,8 @@
63
63
  "fast-check": "^3.19.0",
64
64
  "jest": "29.7.0",
65
65
  "js-yaml": "^4.1.0",
66
- "nock": "13.5.4",
67
- "ts-jest": "29.2.4"
66
+ "nock": "^13.5.4",
67
+ "ts-jest": "29.2.5"
68
68
  },
69
69
  "peerDependencies": {
70
70
  "@typescript-eslint/eslint-plugin": "7.18.0",
@@ -58,6 +58,7 @@ export function genPkgJSON(opts: InitOptions, pgkVerOverride?: string) {
58
58
  },
59
59
  dependencies: {
60
60
  pepr: pgkVerOverride || version,
61
+ nock: "13.5.4",
61
62
  },
62
63
  devDependencies: {
63
64
  typescript,
@@ -90,15 +90,9 @@ export function watcherDeployTemplate(buildTimestamp: string) {
90
90
  - /app/node_modules/pepr/dist/controller.js
91
91
  - {{ .Values.hash }}
92
92
  readinessProbe:
93
- httpGet:
94
- path: /healthz
95
- port: 3000
96
- scheme: HTTPS
93
+ {{- toYaml .Values.watcher.readinessProbe | nindent 12 }}
97
94
  livenessProbe:
98
- httpGet:
99
- path: /healthz
100
- port: 3000
101
- scheme: HTTPS
95
+ {{- toYaml .Values.watcher.livenessProbe | nindent 12 }}
102
96
  ports:
103
97
  - containerPort: 3000
104
98
  resources:
@@ -176,15 +170,9 @@ export function admissionDeployTemplate(buildTimestamp: string) {
176
170
  - /app/node_modules/pepr/dist/controller.js
177
171
  - {{ .Values.hash }}
178
172
  readinessProbe:
179
- httpGet:
180
- path: /healthz
181
- port: 3000
182
- scheme: HTTPS
173
+ {{- toYaml .Values.admission.readinessProbe | nindent 12 }}
183
174
  livenessProbe:
184
- httpGet:
185
- path: /healthz
186
- port: 3000
187
- scheme: HTTPS
175
+ {{- toYaml .Values.admission.livenessProbe | nindent 12 }}
188
176
  ports:
189
177
  - containerPort: 3000
190
178
  resources:
@@ -111,6 +111,7 @@ export function watcher(assets: Assets, hash: string, buildTimestamp: string, im
111
111
  port: 3000,
112
112
  scheme: "HTTPS",
113
113
  },
114
+ initialDelaySeconds: 10,
114
115
  },
115
116
  livenessProbe: {
116
117
  httpGet: {
@@ -118,6 +119,7 @@ export function watcher(assets: Assets, hash: string, buildTimestamp: string, im
118
119
  port: 3000,
119
120
  scheme: "HTTPS",
120
121
  },
122
+ initialDelaySeconds: 10,
121
123
  },
122
124
  ports: [
123
125
  {
@@ -248,6 +250,7 @@ export function deployment(
248
250
  port: 3000,
249
251
  scheme: "HTTPS",
250
252
  },
253
+ initialDelaySeconds: 10,
251
254
  },
252
255
  livenessProbe: {
253
256
  httpGet: {
@@ -255,6 +258,7 @@ export function deployment(
255
258
  port: 3000,
256
259
  scheme: "HTTPS",
257
260
  },
261
+ initialDelaySeconds: 10,
258
262
  },
259
263
  ports: [
260
264
  {
@@ -45,6 +45,22 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
45
45
  runAsNonRoot: true,
46
46
  fsGroup: 65532,
47
47
  },
48
+ readinessProbe: {
49
+ httpGet: {
50
+ path: "/healthz",
51
+ port: 3000,
52
+ scheme: "HTTPS",
53
+ },
54
+ initialDelaySeconds: 10,
55
+ },
56
+ livenessProbe: {
57
+ httpGet: {
58
+ path: "/healthz",
59
+ port: 3000,
60
+ scheme: "HTTPS",
61
+ },
62
+ initialDelaySeconds: 10,
63
+ },
48
64
  resources: {
49
65
  requests: {
50
66
  memory: "64Mi",
@@ -95,6 +111,22 @@ export async function overridesFile({ hash, name, image, config, apiToken }: Ass
95
111
  runAsNonRoot: true,
96
112
  fsGroup: 65532,
97
113
  },
114
+ readinessProbe: {
115
+ httpGet: {
116
+ path: "/healthz",
117
+ port: 3000,
118
+ scheme: "HTTPS",
119
+ },
120
+ initialDelaySeconds: 10,
121
+ },
122
+ livenessProbe: {
123
+ httpGet: {
124
+ path: "/healthz",
125
+ port: 3000,
126
+ scheme: "HTTPS",
127
+ },
128
+ initialDelaySeconds: 10,
129
+ },
98
130
  resources: {
99
131
  requests: {
100
132
  memory: "64Mi",
@@ -152,8 +152,6 @@ export class PeprControllerStore {
152
152
  fillCache(name, "add", [key.slice(offset)], data[key]);
153
153
  }
154
154
  }
155
-
156
- // await K8s(PeprStore, { namespace, name: this.#name }).Patch(payload);
157
155
  }
158
156
  await flushCache();
159
157
  this.#setupWatch();
@@ -2,7 +2,7 @@
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
4
  import { beforeEach, describe, expect, it, jest } from "@jest/globals";
5
- import { DataStore, Storage, v2StoreKey, stripV2Prefix } from "./storage";
5
+ import { DataStore, Storage, v2StoreKey, v2UnescapedStoreKey, stripV2Prefix } from "./storage";
6
6
  import fc from "fast-check";
7
7
 
8
8
  describe("stripV2Prefix", () => {
@@ -27,6 +27,18 @@ describe("v2StoreKey", () => {
27
27
  }
28
28
  });
29
29
  });
30
+
31
+ describe("v2UnescapedStoreKey", () => {
32
+ it("should prefix the key with v2", () => {
33
+ const keys = ["https://google.com", "sso-client-http://bin", "key3", "key4", "key5"];
34
+ const results = ["v2-https://google.com", "v2-sso-client-http://bin", "v2-key3", "v2-key4", "v2-key5"];
35
+
36
+ for (let i = 0; i < keys.length; i++) {
37
+ const result = v2UnescapedStoreKey(keys[i]);
38
+ expect(result).toEqual(results[i]);
39
+ }
40
+ });
41
+ });
30
42
  describe("Storage with fuzzing and property-based tests", () => {
31
43
  let storage: Storage;
32
44
 
@@ -39,7 +51,7 @@ describe("Storage with fuzzing and property-based tests", () => {
39
51
  fc.assert(
40
52
  fc.property(fc.string(), fc.string(), (key, value) => {
41
53
  storage.setItem(key, value);
42
- const mockData: DataStore = { [v2StoreKey(key)]: value };
54
+ const mockData: DataStore = { [v2UnescapedStoreKey(key)]: value };
43
55
  storage.receive(mockData);
44
56
  if (value === "") {
45
57
  expect(storage.getItem(key)).toBeNull();
@@ -75,7 +87,7 @@ describe("Storage with fuzzing and property-based tests", () => {
75
87
  fc.assert(
76
88
  fc.property(fc.string(), fc.string(), (key, value) => {
77
89
  storage.setItem(key, value);
78
- const mockData: DataStore = { [v2StoreKey(key)]: value };
90
+ const mockData: DataStore = { [v2UnescapedStoreKey(key)]: value };
79
91
  storage.receive(mockData);
80
92
  if (value === "") {
81
93
  expect(storage.getItem(key)).toBeNull();
@@ -100,19 +112,20 @@ describe("Storage", () => {
100
112
  const key = "key1";
101
113
  storage.setItem(key, "value1");
102
114
 
103
- expect(mockSender).toHaveBeenCalledWith("add", [v2StoreKey(key)], "value1");
115
+ expect(mockSender).toHaveBeenCalledWith("add", [v2UnescapedStoreKey(key)], "value1");
104
116
  });
105
117
 
106
118
  it("should set an item and wait", () => {
107
119
  const mockSender = jest.fn();
108
120
  storage.registerSender(mockSender);
109
121
  jest.useFakeTimers();
110
- const key = "key1";
122
+ const keys = ["key1", "https://google.com", "sso-client-http://bin"];
111
123
 
112
- // asserting on sender invocation rather than Promise so no need to wait
113
- void storage.setItemAndWait(key, "value1");
124
+ keys.map(key => {
125
+ void storage.setItemAndWait(key, "value1");
126
+ expect(mockSender).toHaveBeenCalledWith("add", [v2StoreKey(key)], "value1");
127
+ });
114
128
 
115
- expect(mockSender).toHaveBeenCalledWith("add", [v2StoreKey(key)], "value1");
116
129
  jest.useRealTimers();
117
130
  });
118
131
 
@@ -142,10 +155,10 @@ describe("Storage", () => {
142
155
  const mockSender = jest.fn();
143
156
  storage.registerSender(mockSender);
144
157
 
145
- storage.receive({ key1: "value1", key2: "value2" });
158
+ storage.receive({ key1: "value1", key2: "value2", "sso-client-http://bin": "value3" });
146
159
  storage.clear();
147
160
 
148
- expect(mockSender).toHaveBeenCalledWith("remove", ["key1", "key2"], undefined);
161
+ expect(mockSender).toHaveBeenCalledWith("remove", ["key1", "key2", "sso-client-http:~1~1bin"], undefined);
149
162
  });
150
163
 
151
164
  it("should get an item", () => {
@@ -153,7 +166,7 @@ describe("Storage", () => {
153
166
  const results = ["value1", null, "!", "was-here", "3f7dd007-568f-4f4a-bbac-2e6bfff93860", "your-machine", " "];
154
167
 
155
168
  keys.map((key, i) => {
156
- const mockData: DataStore = { [v2StoreKey(key)]: results[i]! };
169
+ const mockData: DataStore = { [v2UnescapedStoreKey(key)]: results[i]! };
157
170
 
158
171
  storage.receive(mockData);
159
172
  const value = storage.getItem(keys[i]);
@@ -17,6 +17,10 @@ export function v2StoreKey(key: string) {
17
17
  return `${STORE_VERSION_PREFIX}-${pointer.escape(key)}`;
18
18
  }
19
19
 
20
+ export function v2UnescapedStoreKey(key: string) {
21
+ return `${STORE_VERSION_PREFIX}-${key}`;
22
+ }
23
+
20
24
  export function stripV2Prefix(key: string) {
21
25
  return key.replace(/^v2-/, "");
22
26
  }
@@ -95,7 +99,7 @@ export class Storage implements PeprStore {
95
99
  };
96
100
 
97
101
  getItem = (key: string) => {
98
- const result = this.#store[v2StoreKey(key)] || null;
102
+ const result = this.#store[v2UnescapedStoreKey(key)] || null;
99
103
  if (result !== null && typeof result !== "function" && typeof result !== "object") {
100
104
  return result;
101
105
  }
@@ -103,7 +107,10 @@ export class Storage implements PeprStore {
103
107
  };
104
108
 
105
109
  clear = () => {
106
- this.#dispatchUpdate("remove", Object.keys(this.#store));
110
+ this.#dispatchUpdate(
111
+ "remove",
112
+ Object.keys(this.#store).map(key => pointer.escape(key)),
113
+ );
107
114
  };
108
115
 
109
116
  removeItem = (key: string) => {
@@ -124,9 +131,10 @@ export class Storage implements PeprStore {
124
131
  */
125
132
  setItemAndWait = (key: string, value: string) => {
126
133
  this.#dispatchUpdate("add", [v2StoreKey(key)], value);
134
+
127
135
  return new Promise<void>((resolve, reject) => {
128
136
  const unsubscribe = this.subscribe(data => {
129
- if (data[`${v2StoreKey(key)}`] === value) {
137
+ if (data[`${v2UnescapedStoreKey(key)}`] === value) {
130
138
  unsubscribe();
131
139
  resolve();
132
140
  }
@@ -151,7 +159,7 @@ export class Storage implements PeprStore {
151
159
  this.#dispatchUpdate("remove", [v2StoreKey(key)]);
152
160
  return new Promise<void>((resolve, reject) => {
153
161
  const unsubscribe = this.subscribe(data => {
154
- if (!Object.hasOwn(data, `${v2StoreKey(key)}`)) {
162
+ if (!Object.hasOwn(data, `${v2UnescapedStoreKey(key)}`)) {
155
163
  unsubscribe();
156
164
  resolve();
157
165
  }
@@ -1,11 +1,11 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
- import { beforeEach, describe, expect, it, jest } from "@jest/globals";
3
+ import { afterAll, beforeEach, beforeAll, describe, expect, it, jest } from "@jest/globals";
4
4
  import { GenericClass, K8s, KubernetesObject, kind } from "kubernetes-fluent-client";
5
5
  import { K8sInit, WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
6
6
  import { WatchCfg, WatchEvent, Watcher } from "kubernetes-fluent-client/dist/fluent/watch";
7
7
  import { Capability } from "./capability";
8
- import { setupWatch, logEvent } from "./watch-processor";
8
+ import { setupWatch, logEvent, queueRecordKey } from "./watch-processor";
9
9
  import Log from "./logger";
10
10
  import { metricsCollector } from "./metrics";
11
11
 
@@ -88,7 +88,7 @@ describe("WatchProcessor", () => {
88
88
  resyncFailureMax: 5,
89
89
  resyncDelaySec: 5,
90
90
  lastSeenLimitSeconds: 300,
91
- relistIntervalSec: 1800,
91
+ relistIntervalSec: 600,
92
92
  };
93
93
 
94
94
  capabilities.push({
@@ -320,3 +320,134 @@ describe("logEvent function", () => {
320
320
  expect(Log.debug).toHaveBeenCalledWith(`Watch event ${WatchEvent.DATA_ERROR} received. ${message}.`);
321
321
  });
322
322
  });
323
+
324
+ describe("queueRecordKey", () => {
325
+ describe("PEPR_RECONCILE_STRATEGY=sharded", () => {
326
+ const original = process.env.PEPR_RECONCILE_STRATEGY;
327
+
328
+ beforeAll(() => {
329
+ process.env.PEPR_RECONCILE_STRATEGY = "sharded";
330
+ });
331
+ afterAll(() => {
332
+ process.env.PEPR_RECONCILE_STRATEGY = original;
333
+ });
334
+
335
+ it("should return correct key for an object with 'kind/name/namespace'", () => {
336
+ const obj: KubernetesObject = {
337
+ kind: "Pod",
338
+ metadata: {
339
+ name: "my-pod",
340
+ namespace: "my-namespace",
341
+ },
342
+ };
343
+
344
+ expect(queueRecordKey(obj)).toBe("Pod/my-pod/my-namespace");
345
+ });
346
+
347
+ it("should handle objects with missing namespace", () => {
348
+ const obj: KubernetesObject = {
349
+ kind: "Pod",
350
+ metadata: {
351
+ name: "my-pod",
352
+ },
353
+ };
354
+
355
+ expect(queueRecordKey(obj)).toBe("Pod/my-pod/cluster-scoped");
356
+ });
357
+
358
+ it("should handle objects with missing name", () => {
359
+ const obj: KubernetesObject = {
360
+ kind: "Pod",
361
+ metadata: {
362
+ namespace: "my-namespace",
363
+ },
364
+ };
365
+
366
+ expect(queueRecordKey(obj)).toBe("Pod/Unnamed/my-namespace");
367
+ });
368
+
369
+ it("should handle objects with missing metadata", () => {
370
+ const obj: KubernetesObject = {
371
+ kind: "Pod",
372
+ };
373
+
374
+ expect(queueRecordKey(obj)).toBe("Pod/Unnamed/cluster-scoped");
375
+ });
376
+
377
+ it("should handle objects with missing kind", () => {
378
+ const obj: KubernetesObject = {
379
+ metadata: {
380
+ name: "my-pod",
381
+ namespace: "my-namespace",
382
+ },
383
+ };
384
+
385
+ expect(queueRecordKey(obj)).toBe("UnknownKind/my-pod/my-namespace");
386
+ });
387
+
388
+ it("should handle completely empty objects", () => {
389
+ const obj: KubernetesObject = {};
390
+
391
+ expect(queueRecordKey(obj)).toBe("UnknownKind/Unnamed/cluster-scoped");
392
+ });
393
+ });
394
+
395
+ describe("PEPR_RECONCILE_STRATEGY=singular", () => {
396
+ const original = process.env.PEPR_RECONCILE_STRATEGY;
397
+
398
+ beforeAll(() => {
399
+ process.env.PEPR_RECONCILE_STRATEGY = "singular";
400
+ });
401
+ afterAll(() => {
402
+ process.env.PEPR_RECONCILE_STRATEGY = original;
403
+ });
404
+
405
+ it("should return correct key for an object with 'kind/namespace'", () => {
406
+ const obj: KubernetesObject = {
407
+ kind: "Pod",
408
+ metadata: {
409
+ name: "my-pod",
410
+ namespace: "my-namespace",
411
+ },
412
+ };
413
+
414
+ expect(queueRecordKey(obj)).toBe("Pod/my-namespace");
415
+ });
416
+
417
+ it("should handle objects with missing namespace", () => {
418
+ const obj: KubernetesObject = {
419
+ kind: "Pod",
420
+ metadata: {
421
+ name: "my-pod",
422
+ },
423
+ };
424
+
425
+ expect(queueRecordKey(obj)).toBe("Pod/cluster-scoped");
426
+ });
427
+
428
+ it("should handle objects with missing metadata", () => {
429
+ const obj: KubernetesObject = {
430
+ kind: "Pod",
431
+ };
432
+
433
+ expect(queueRecordKey(obj)).toBe("Pod/cluster-scoped");
434
+ });
435
+
436
+ it("should handle objects with missing kind", () => {
437
+ const obj: KubernetesObject = {
438
+ metadata: {
439
+ name: "my-pod",
440
+ namespace: "my-namespace",
441
+ },
442
+ };
443
+
444
+ expect(queueRecordKey(obj)).toBe("UnknownKind/my-namespace");
445
+ });
446
+
447
+ it("should handle completely empty objects", () => {
448
+ const obj: KubernetesObject = {};
449
+
450
+ expect(queueRecordKey(obj)).toBe("UnknownKind/cluster-scoped");
451
+ });
452
+ });
453
+ });
@@ -9,6 +9,29 @@ import { Queue } from "./queue";
9
9
  import { Binding, Event } from "./types";
10
10
  import { metricsCollector } from "./metrics";
11
11
 
12
+ // init a queueRecord record to store Queue instances for a given Kubernetes Object
13
+ const queueRecord: Record<string, Queue<KubernetesObject>> = {};
14
+
15
+ /**
16
+ * Get the key for a record in the queueRecord
17
+ *
18
+ * @param obj The object to get the key for
19
+ * @returns The key for the object
20
+ */
21
+ export function queueRecordKey(obj: KubernetesObject) {
22
+ const options = ["singular", "sharded"]; // TODO : ts-type this fella
23
+ const d3fault = "singular";
24
+
25
+ let strat = process.env.PEPR_RECONCILE_STRATEGY || d3fault;
26
+ strat = options.includes(strat) ? strat : d3fault;
27
+
28
+ const ns = obj.metadata?.namespace ?? "cluster-scoped";
29
+ const kind = obj.kind ?? "UnknownKind";
30
+ const name = obj.metadata?.name ?? "Unnamed";
31
+
32
+ return strat === "singular" ? `${kind}/${ns}` : `${kind}/${name}/${ns}`;
33
+ }
34
+
12
35
  // Watch configuration
13
36
  const watchCfg: WatchCfg = {
14
37
  resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) : 5,
@@ -18,7 +41,7 @@ const watchCfg: WatchCfg = {
18
41
  : 300,
19
42
  relistIntervalSec: process.env.PEPR_RELIST_INTERVAL_SECONDS
20
43
  ? parseInt(process.env.PEPR_RELIST_INTERVAL_SECONDS, 10)
21
- : 1800,
44
+ : 600,
22
45
  };
23
46
 
24
47
  // Map the event to the watch phase
@@ -74,13 +97,20 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[]) {
74
97
  }
75
98
  };
76
99
 
77
- const queue = new Queue();
78
- queue.setReconcile(watchCallback);
100
+ function getOrCreateQueue(key: string): Queue<KubernetesObject> {
101
+ if (!queueRecord[key]) {
102
+ queueRecord[key] = new Queue<KubernetesObject>();
103
+ queueRecord[key].setReconcile(watchCallback);
104
+ }
105
+ return queueRecord[key];
106
+ }
79
107
 
80
108
  // Setup the resource watch
81
109
  const watcher = K8s(binding.model, binding.filters).Watch(async (obj, type) => {
82
110
  Log.debug(obj, `Watch event ${type} received`);
83
111
 
112
+ const queue = getOrCreateQueue(queueRecordKey(obj));
113
+
84
114
  // If the binding is a queue, enqueue the object
85
115
  if (binding.isQueue) {
86
116
  await queue.enqueue(obj, type);
@@ -9,6 +9,7 @@ import {
9
9
  fetchStatus,
10
10
  kind,
11
11
  } from "pepr";
12
+ import nock from "nock";
12
13
 
13
14
  /**
14
15
  * The HelloPepr Capability is an example capability to demonstrate some general concepts of Pepr.
@@ -268,24 +269,47 @@ interface TheChuckNorrisJoke {
268
269
  }
269
270
 
270
271
  When(a.ConfigMap)
271
- .IsCreated()
272
+ .IsCreatedOrUpdated()
272
273
  .WithLabel("chuck-norris")
273
- .Mutate(async change => {
274
+ .Mutate(cm => cm.SetLabel("got-jokes", "true"))
275
+ .Watch(async cm => {
276
+ const jokeURL = "https://icanhazdadjoke.com/";
277
+
278
+ // Set up Nock to mock the API calls globally with header matching
279
+ nock(jokeURL).get("/").reply(200, {
280
+ id: "R7UfaahVfFd",
281
+ joke: "Funny joke goes here.",
282
+ status: 200,
283
+ });
284
+
274
285
  // Try/catch is not needed as a response object will always be returned
275
- const response = await fetch<TheChuckNorrisJoke>(
276
- "https://icanhazdadjoke.com/",
277
- {
278
- headers: {
279
- Accept: "application/json",
280
- },
286
+ const response = await fetch<TheChuckNorrisJoke>(jokeURL, {
287
+ headers: {
288
+ Accept: "application/json",
281
289
  },
282
- );
290
+ });
283
291
 
284
292
  // Instead, check the `response.ok` field
285
293
  if (response.ok) {
294
+ const { joke } = response.data;
295
+ // Add Joke to the Store
296
+ await Store.setItemAndWait(jokeURL, joke);
286
297
  // Add the Chuck Norris joke to the configmap
287
- change.Raw.data["chuck-says"] = response.data.joke;
288
- return;
298
+ try {
299
+ await K8s(kind.ConfigMap).Apply({
300
+ metadata: {
301
+ name: cm.metadata.name,
302
+ namespace: cm.metadata.namespace,
303
+ },
304
+ data: {
305
+ "chuck-says": Store.getItem(jokeURL),
306
+ },
307
+ });
308
+ } catch (error) {
309
+ Log.error(error, "Failed to apply ConfigMap using server-side apply.", {
310
+ cm,
311
+ });
312
+ }
289
313
  }
290
314
 
291
315
  // You can also assert on different HTTP response codes