pepr 0.34.1 → 0.36.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 (40) hide show
  1. package/dist/cli/init/templates.d.ts +1 -0
  2. package/dist/cli/init/templates.d.ts.map +1 -1
  3. package/dist/cli.js +48 -23
  4. package/dist/controller.js +1 -1
  5. package/dist/lib/assets/helm.d.ts.map +1 -1
  6. package/dist/lib/assets/pods.d.ts.map +1 -1
  7. package/dist/lib/assets/yaml.d.ts.map +1 -1
  8. package/dist/lib/capability.d.ts.map +1 -1
  9. package/dist/lib/filter.d.ts.map +1 -1
  10. package/dist/lib/helpers.d.ts.map +1 -1
  11. package/dist/lib/logger.d.ts +1 -1
  12. package/dist/lib/logger.d.ts.map +1 -1
  13. package/dist/lib/queue.d.ts +19 -3
  14. package/dist/lib/queue.d.ts.map +1 -1
  15. package/dist/lib/types.d.ts +3 -0
  16. package/dist/lib/types.d.ts.map +1 -1
  17. package/dist/lib/watch-processor.d.ts +10 -1
  18. package/dist/lib/watch-processor.d.ts.map +1 -1
  19. package/dist/lib.js +104 -32
  20. package/dist/lib.js.map +3 -3
  21. package/dist/sdk/sdk.d.ts +5 -3
  22. package/dist/sdk/sdk.d.ts.map +1 -1
  23. package/package.json +10 -10
  24. package/src/cli/init/templates.ts +1 -0
  25. package/src/lib/assets/helm.ts +4 -16
  26. package/src/lib/assets/pods.ts +4 -0
  27. package/src/lib/assets/yaml.ts +32 -0
  28. package/src/lib/capability.ts +9 -1
  29. package/src/lib/filter.test.ts +85 -1
  30. package/src/lib/filter.ts +9 -0
  31. package/src/lib/helpers.test.ts +34 -0
  32. package/src/lib/helpers.ts +5 -0
  33. package/src/lib/queue.test.ts +138 -44
  34. package/src/lib/queue.ts +48 -13
  35. package/src/lib/types.ts +3 -0
  36. package/src/lib/watch-processor.test.ts +101 -5
  37. package/src/lib/watch-processor.ts +49 -16
  38. package/src/sdk/sdk.test.ts +46 -13
  39. package/src/sdk/sdk.ts +15 -6
  40. package/src/templates/capabilities/hello-pepr.ts +9 -0
package/dist/sdk/sdk.d.ts CHANGED
@@ -24,10 +24,12 @@ export declare function containers(request: PeprValidateRequest<a.Pod> | PeprMut
24
24
  export declare function writeEvent(cr: GenericKind, event: Partial<kind.CoreEvent>, eventType: string, eventReason: string, reportingComponent: string, reportingInstance: string): Promise<void>;
25
25
  /**
26
26
  * Get the owner reference for a custom resource
27
- * @param cr the custom resource to get the owner reference for
28
- * @returns the owner reference for the custom resource
27
+ * @param customResource the custom resource to get the owner reference for
28
+ * @param blockOwnerDeletion if true, AND if the owner has the "foregroundDeletion" finalizer, then the owner cannot be deleted from the key-value store until this reference is removed.
29
+ * @param controller if true, this reference points to the managing controller.
30
+ * @returns the owner reference array for the custom resource
29
31
  */
30
- export declare function getOwnerRefFrom(cr: GenericKind): V1OwnerReference[];
32
+ export declare function getOwnerRefFrom(customResource: GenericKind, blockOwnerDeletion?: boolean, controller?: boolean): V1OwnerReference[];
31
33
  /**
32
34
  * Sanitize a resource name to make it a valid Kubernetes resource name.
33
35
  *
@@ -1 +1 @@
1
- {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/sdk/sdk.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAO,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAGrD;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAC9D,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,mDAgBxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,iBAwB1B;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,gBAAgB,EAAE,CAWnE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,UAYhD"}
1
+ {"version":3,"file":"sdk.d.ts","sourceRoot":"","sources":["../../src/sdk/sdk.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,CAAC,EAAE,MAAM,QAAQ,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAO,IAAI,EAAE,MAAM,0BAA0B,CAAC;AAGrD;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,CAAC,EAC9D,aAAa,CAAC,EAAE,YAAY,GAAG,gBAAgB,GAAG,qBAAqB,mDAgBxE;AAED;;;;;;;;;GASG;AACH,wBAAsB,UAAU,CAC9B,EAAE,EAAE,WAAW,EACf,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,EAC9B,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,kBAAkB,EAAE,MAAM,EAC1B,iBAAiB,EAAE,MAAM,iBAwB1B;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,cAAc,EAAE,WAAW,EAC3B,kBAAkB,CAAC,EAAE,OAAO,EAC5B,UAAU,CAAC,EAAE,OAAO,GACnB,gBAAgB,EAAE,CAcpB;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,UAYhD"}
package/package.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "/dist",
14
14
  "/src"
15
15
  ],
16
- "version": "0.34.1",
16
+ "version": "0.36.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",
42
- "express": "4.19.2",
41
+ "@types/ramda": "0.30.2",
42
+ "express": "4.21.0",
43
43
  "fast-json-patch": "3.1.1",
44
44
  "json-pointer": "^0.6.2",
45
- "kubernetes-fluent-client": "3.0.1",
46
- "pino": "9.3.2",
45
+ "kubernetes-fluent-client": "3.0.3",
46
+ "pino": "9.4.0",
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.4.0",
53
- "@commitlint/config-conventional": "19.2.2",
52
+ "@commitlint/cli": "19.5.0",
53
+ "@commitlint/config-conventional": "19.5.0",
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",
@@ -198,12 +198,13 @@ export class Capability implements CapabilityExport {
198
198
  namespaces: [],
199
199
  labels: {},
200
200
  annotations: {},
201
+ deletionTimestamp: false,
201
202
  },
202
203
  };
203
204
 
204
205
  const bindings = this.#bindings;
205
206
  const prefix = `${this.#name}: ${model.name}`;
206
- const commonChain = { WithLabel, WithAnnotation, Mutate, Validate, Watch, Reconcile };
207
+ const commonChain = { WithLabel, WithAnnotation, WithDeletionTimestamp, Mutate, Validate, Watch, Reconcile };
207
208
  const isNotEmpty = (value: object) => Object.keys(value).length > 0;
208
209
  const log = (message: string, cbString: string) => {
209
210
  const filteredObj = pickBy(isNotEmpty, binding.filters);
@@ -277,6 +278,12 @@ export class Capability implements CapabilityExport {
277
278
  return { ...commonChain, WithName };
278
279
  }
279
280
 
281
+ function WithDeletionTimestamp(): BindingFilter<T> {
282
+ Log.debug("Add deletionTimestamp filter");
283
+ binding.filters.deletionTimestamp = true;
284
+ return commonChain;
285
+ }
286
+
280
287
  function WithName(name: string): BindingFilter<T> {
281
288
  Log.debug(`Add name filter ${name}`, prefix);
282
289
  binding.filters.name = name;
@@ -301,6 +308,7 @@ export class Capability implements CapabilityExport {
301
308
  ...commonChain,
302
309
  InNamespace,
303
310
  WithName,
311
+ WithDeletionTimestamp,
304
312
  };
305
313
  }
306
314
 
@@ -29,6 +29,7 @@ describe("Fuzzing shouldSkipRequest", () => {
29
29
  namespaces: fc.array(fc.string()),
30
30
  labels: fc.dictionary(fc.string(), fc.string()),
31
31
  annotations: fc.dictionary(fc.string(), fc.string()),
32
+ deletionTimestamp: fc.boolean(),
32
33
  }),
33
34
  }),
34
35
  fc.record({
@@ -41,6 +42,11 @@ describe("Fuzzing shouldSkipRequest", () => {
41
42
  version: fc.string(),
42
43
  kind: fc.string(),
43
44
  }),
45
+ object: fc.record({
46
+ metadata: fc.record({
47
+ deletionTimestamp: fc.option(fc.date()),
48
+ }),
49
+ }),
44
50
  }),
45
51
  fc.array(fc.string()),
46
52
  (binding, req, capabilityNamespaces) => {
@@ -69,6 +75,7 @@ describe("Property-Based Testing shouldSkipRequest", () => {
69
75
  namespaces: fc.array(fc.string()),
70
76
  labels: fc.dictionary(fc.string(), fc.string()),
71
77
  annotations: fc.dictionary(fc.string(), fc.string()),
78
+ deletionTimestamp: fc.boolean(),
72
79
  }),
73
80
  }),
74
81
  fc.record({
@@ -81,6 +88,11 @@ describe("Property-Based Testing shouldSkipRequest", () => {
81
88
  version: fc.string(),
82
89
  kind: fc.string(),
83
90
  }),
91
+ object: fc.record({
92
+ metadata: fc.record({
93
+ deletionTimestamp: fc.option(fc.date()),
94
+ }),
95
+ }),
84
96
  }),
85
97
  fc.array(fc.string()),
86
98
  (binding, req, capabilityNamespaces) => {
@@ -103,6 +115,7 @@ test("should reject when name does not match", () => {
103
115
  namespaces: [],
104
116
  labels: {},
105
117
  annotations: {},
118
+ deletionTimestamp: false,
106
119
  },
107
120
  callback,
108
121
  };
@@ -121,6 +134,7 @@ test("should reject when kind does not match", () => {
121
134
  namespaces: [],
122
135
  labels: {},
123
136
  annotations: {},
137
+ deletionTimestamp: false,
124
138
  },
125
139
  callback,
126
140
  };
@@ -139,6 +153,7 @@ test("should reject when group does not match", () => {
139
153
  namespaces: [],
140
154
  labels: {},
141
155
  annotations: {},
156
+ deletionTimestamp: false,
142
157
  },
143
158
  callback,
144
159
  };
@@ -161,6 +176,7 @@ test("should reject when version does not match", () => {
161
176
  namespaces: [],
162
177
  labels: {},
163
178
  annotations: {},
179
+ deletionTimestamp: false,
164
180
  },
165
181
  callback,
166
182
  };
@@ -179,6 +195,7 @@ test("should allow when group, version, and kind match", () => {
179
195
  namespaces: [],
180
196
  labels: {},
181
197
  annotations: {},
198
+ deletionTimestamp: false,
182
199
  },
183
200
  callback,
184
201
  };
@@ -201,6 +218,7 @@ test("should allow when kind match and others are empty", () => {
201
218
  namespaces: [],
202
219
  labels: {},
203
220
  annotations: {},
221
+ deletionTimestamp: false,
204
222
  },
205
223
  callback,
206
224
  };
@@ -219,6 +237,7 @@ test("should reject when teh capability namespace does not match", () => {
219
237
  namespaces: [],
220
238
  labels: {},
221
239
  annotations: {},
240
+ deletionTimestamp: false,
222
241
  },
223
242
  callback,
224
243
  };
@@ -237,6 +256,7 @@ test("should reject when namespace does not match", () => {
237
256
  namespaces: ["bleh"],
238
257
  labels: {},
239
258
  annotations: {},
259
+ deletionTimestamp: false,
240
260
  },
241
261
  callback,
242
262
  };
@@ -255,6 +275,7 @@ test("should allow when namespace is match", () => {
255
275
  namespaces: ["default", "unicorn", "things"],
256
276
  labels: {},
257
277
  annotations: {},
278
+ deletionTimestamp: false,
258
279
  },
259
280
  callback,
260
281
  };
@@ -275,6 +296,7 @@ test("should reject when label does not match", () => {
275
296
  foo: "bar",
276
297
  },
277
298
  annotations: {},
299
+ deletionTimestamp: false,
278
300
  },
279
301
  callback,
280
302
  };
@@ -290,7 +312,7 @@ test("should allow when label is match", () => {
290
312
  kind: podKind,
291
313
  filters: {
292
314
  name: "",
293
-
315
+ deletionTimestamp: false,
294
316
  namespaces: [],
295
317
  labels: {
296
318
  foo: "bar",
@@ -324,6 +346,7 @@ test("should reject when annotation does not match", () => {
324
346
  annotations: {
325
347
  foo: "bar",
326
348
  },
349
+ deletionTimestamp: false,
327
350
  },
328
351
  callback,
329
352
  };
@@ -345,6 +368,7 @@ test("should allow when annotation is match", () => {
345
368
  foo: "bar",
346
369
  test: "test1",
347
370
  },
371
+ deletionTimestamp: false,
348
372
  },
349
373
  callback,
350
374
  };
@@ -368,6 +392,7 @@ test("should use `oldObject` when the operation is `DELETE`", () => {
368
392
  filters: {
369
393
  name: "",
370
394
  namespaces: [],
395
+ deletionTimestamp: false,
371
396
  labels: {
372
397
  "app.kubernetes.io/name": "cool-name-podinfo",
373
398
  },
@@ -382,3 +407,62 @@ test("should use `oldObject` when the operation is `DELETE`", () => {
382
407
 
383
408
  expect(shouldSkipRequest(binding, pod, [])).toBe(false);
384
409
  });
410
+
411
+ test("should skip processing when deletionTimestamp is not present on pod", () => {
412
+ const binding = {
413
+ model: kind.Pod,
414
+ event: Event.Any,
415
+ kind: podKind,
416
+ filters: {
417
+ name: "",
418
+ namespaces: [],
419
+ labels: {},
420
+ annotations: {
421
+ foo: "bar",
422
+ test: "test1",
423
+ },
424
+ deletionTimestamp: true,
425
+ },
426
+ callback,
427
+ };
428
+
429
+ const pod = CreatePod();
430
+ pod.object.metadata = pod.object.metadata || {};
431
+ pod.object.metadata.annotations = {
432
+ foo: "bar",
433
+ test: "test1",
434
+ test2: "test2",
435
+ };
436
+
437
+ expect(shouldSkipRequest(binding, pod, [])).toBe(true);
438
+ });
439
+
440
+ test("should processing when deletionTimestamp is not present on pod", () => {
441
+ const binding = {
442
+ model: kind.Pod,
443
+ event: Event.Any,
444
+ kind: podKind,
445
+ filters: {
446
+ name: "",
447
+ namespaces: [],
448
+ labels: {},
449
+ annotations: {
450
+ foo: "bar",
451
+ test: "test1",
452
+ },
453
+ deletionTimestamp: true,
454
+ },
455
+ callback,
456
+ };
457
+
458
+ const pod = CreatePod();
459
+ pod.object.metadata = pod.object.metadata || {};
460
+ pod.object.metadata!.deletionTimestamp = new Date("2021-09-01T00:00:00Z");
461
+ pod.object.metadata.annotations = {
462
+ foo: "bar",
463
+ test: "test1",
464
+ test2: "test2",
465
+ };
466
+
467
+ expect(shouldSkipRequest(binding, pod, [])).toBe(false);
468
+ });
package/src/lib/filter.ts CHANGED
@@ -22,6 +22,15 @@ export function shouldSkipRequest(binding: Binding, req: AdmissionRequest, capab
22
22
  const { metadata } = srcObject || {};
23
23
  const combinedNamespaces = [...namespaces, ...capabilityNamespaces];
24
24
 
25
+ // Delete bindings do not work through admission with DeletionTimestamp
26
+ if (binding.event.includes(Event.Delete) && binding.filters?.deletionTimestamp) {
27
+ return true;
28
+ }
29
+
30
+ // Test for deletionTimestamp
31
+ if (binding.filters?.deletionTimestamp && !req.object.metadata?.deletionTimestamp) {
32
+ return true;
33
+ }
25
34
  // Test for matching operation
26
35
  if (!binding.event.includes(operation) && !binding.event.includes(Event.Any)) {
27
36
  return true;
@@ -1056,6 +1056,40 @@ describe("filterMatcher", () => {
1056
1056
  expect(result).toEqual("Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.");
1057
1057
  });
1058
1058
 
1059
+ test("return deletionTimestamp error when there is no deletionTimestamp in the object", () => {
1060
+ const binding = {
1061
+ filters: { deletionTimestamp: true },
1062
+ };
1063
+ const obj = {
1064
+ metadata: {},
1065
+ };
1066
+ const capabilityNamespaces: string[] = [];
1067
+ const result = filterNoMatchReason(
1068
+ binding as unknown as Partial<Binding>,
1069
+ obj as unknown as Partial<KubernetesObject>,
1070
+ capabilityNamespaces,
1071
+ );
1072
+ expect(result).toEqual("Ignoring Watch Callback: Object does not have a deletion timestamp.");
1073
+ });
1074
+
1075
+ test("return no deletionTimestamp error when there is a deletionTimestamp in the object", () => {
1076
+ const binding = {
1077
+ filters: { deletionTimestamp: true },
1078
+ };
1079
+ const obj = {
1080
+ metadata: {
1081
+ deletionTimestamp: "2021-01-01T00:00:00Z",
1082
+ },
1083
+ };
1084
+ const capabilityNamespaces: string[] = [];
1085
+ const result = filterNoMatchReason(
1086
+ binding as unknown as Partial<Binding>,
1087
+ obj as unknown as Partial<KubernetesObject>,
1088
+ capabilityNamespaces,
1089
+ );
1090
+ expect(result).not.toEqual("Ignoring Watch Callback: Object does not have a deletion timestamp.");
1091
+ });
1092
+
1059
1093
  test("returns label overlap error when there is no overlap between binding and object labels", () => {
1060
1094
  const binding = {
1061
1095
  filters: { labels: { key: "value" } },
@@ -73,6 +73,11 @@ export function filterNoMatchReason(
73
73
  obj: Partial<KubernetesObject>,
74
74
  capabilityNamespaces: string[],
75
75
  ): string {
76
+ // binding deletionTimestamp filter and object deletionTimestamp dont match
77
+ if (binding.filters?.deletionTimestamp && !obj.metadata?.deletionTimestamp) {
78
+ return `Ignoring Watch Callback: Object does not have a deletion timestamp.`;
79
+ }
80
+
76
81
  // binding kind is namespace with a InNamespace filter
77
82
  if (binding.kind && binding.kind.kind === "Namespace" && binding.filters && binding.filters.namespaces.length !== 0) {
78
83
  return `Ignoring Watch Callback: Cannot use a namespace filter in a namespace object.`;