pepr 0.36.0 → 0.37.1

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 (99) hide show
  1. package/dist/cli/init/index.d.ts.map +1 -1
  2. package/dist/cli/init/templates.d.ts +4 -3
  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/types.d.ts +3 -0
  8. package/dist/cli/types.d.ts.map +1 -0
  9. package/dist/cli.js +253 -31
  10. package/dist/controller.js +138 -1
  11. package/dist/lib/adjudicators.d.ts +63 -0
  12. package/dist/lib/adjudicators.d.ts.map +1 -0
  13. package/dist/lib/adjudicators.test.d.ts +2 -0
  14. package/dist/lib/adjudicators.test.d.ts.map +1 -0
  15. package/dist/lib/assets/loader.d.ts.map +1 -1
  16. package/dist/lib/assets/pods.d.ts +1 -0
  17. package/dist/lib/assets/pods.d.ts.map +1 -1
  18. package/dist/lib/capability.d.ts +1 -0
  19. package/dist/lib/capability.d.ts.map +1 -1
  20. package/dist/lib/capability.test.d.ts +2 -0
  21. package/dist/lib/capability.test.d.ts.map +1 -0
  22. package/dist/lib/controller/index.d.ts.map +1 -1
  23. package/dist/lib/controller/store.d.ts +4 -0
  24. package/dist/lib/controller/store.d.ts.map +1 -1
  25. package/dist/lib/controller/store.test.d.ts +2 -0
  26. package/dist/lib/controller/store.test.d.ts.map +1 -0
  27. package/dist/lib/filter.d.ts +2 -3
  28. package/dist/lib/filter.d.ts.map +1 -1
  29. package/dist/lib/filter.test.d.ts +2 -1
  30. package/dist/lib/filter.test.d.ts.map +1 -1
  31. package/dist/lib/finalizer.d.ts +6 -0
  32. package/dist/lib/finalizer.d.ts.map +1 -0
  33. package/dist/lib/finalizer.test.d.ts +2 -0
  34. package/dist/lib/finalizer.test.d.ts.map +1 -0
  35. package/dist/lib/helpers.d.ts +2 -2
  36. package/dist/lib/helpers.d.ts.map +1 -1
  37. package/dist/lib/helpers.test.d.ts +1 -1
  38. package/dist/lib/helpers.test.d.ts.map +1 -1
  39. package/dist/lib/k8s.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/schedule.d.ts +1 -2
  47. package/dist/lib/schedule.d.ts.map +1 -1
  48. package/dist/lib/storage.d.ts.map +1 -1
  49. package/dist/lib/types.d.ts +113 -6
  50. package/dist/lib/types.d.ts.map +1 -1
  51. package/dist/lib/validate-processor.d.ts +4 -2
  52. package/dist/lib/validate-processor.d.ts.map +1 -1
  53. package/dist/lib/validate-request.d.ts +1 -1
  54. package/dist/lib/validate-request.d.ts.map +1 -1
  55. package/dist/lib/watch-processor.d.ts +1 -1
  56. package/dist/lib/watch-processor.d.ts.map +1 -1
  57. package/dist/lib.js +383 -204
  58. package/dist/lib.js.map +4 -4
  59. package/package.json +13 -12
  60. package/src/cli/build.ts +3 -3
  61. package/src/cli/init/index.ts +20 -11
  62. package/src/cli/init/templates.ts +1 -1
  63. package/src/cli/init/utils.test.ts +11 -20
  64. package/src/cli/init/utils.ts +5 -0
  65. package/src/cli/init/walkthrough.test.ts +92 -11
  66. package/src/cli/init/walkthrough.ts +71 -16
  67. package/src/cli/monitor.ts +1 -1
  68. package/src/cli/types.ts +3 -0
  69. package/src/cli.ts +4 -2
  70. package/src/fixtures/data/create-pod.json +1 -1
  71. package/src/fixtures/data/delete-pod.json +1 -1
  72. package/src/lib/adjudicators.test.ts +1232 -0
  73. package/src/lib/adjudicators.ts +235 -0
  74. package/src/lib/assets/index.ts +1 -1
  75. package/src/lib/assets/loader.ts +1 -0
  76. package/src/lib/assets/webhooks.ts +1 -1
  77. package/src/lib/capability.test.ts +655 -0
  78. package/src/lib/capability.ts +104 -11
  79. package/src/lib/controller/index.ts +7 -4
  80. package/src/lib/controller/store.test.ts +131 -0
  81. package/src/lib/controller/store.ts +43 -5
  82. package/src/lib/filter.test.ts +194 -8
  83. package/src/lib/filter.ts +46 -107
  84. package/src/lib/finalizer.test.ts +236 -0
  85. package/src/lib/finalizer.ts +63 -0
  86. package/src/lib/helpers.test.ts +329 -69
  87. package/src/lib/helpers.ts +141 -100
  88. package/src/lib/k8s.ts +4 -0
  89. package/src/lib/module.ts +3 -3
  90. package/src/lib/mutate-processor.ts +5 -4
  91. package/src/lib/mutate-request.test.ts +1 -2
  92. package/src/lib/mutate-request.ts +1 -3
  93. package/src/lib/schedule.ts +1 -1
  94. package/src/lib/storage.ts +5 -6
  95. package/src/lib/types.ts +148 -5
  96. package/src/lib/validate-processor.ts +5 -2
  97. package/src/lib/validate-request.test.ts +1 -4
  98. package/src/lib/validate-request.ts +1 -1
  99. package/src/lib/watch-processor.ts +19 -5
@@ -6,12 +6,11 @@ import { kind, modelToGroupVersionKind } from "kubernetes-fluent-client";
6
6
  import * as fc from "fast-check";
7
7
  import { CreatePod, DeletePod } from "../fixtures/loader";
8
8
  import { shouldSkipRequest } from "./filter";
9
- import { Event, Binding } from "./types";
10
- import { AdmissionRequest } from "./k8s";
9
+ import { AdmissionRequest, Binding, Event } from "./types";
11
10
 
12
- const callback = () => undefined;
11
+ export const callback = () => undefined;
13
12
 
14
- const podKind = modelToGroupVersionKind(kind.Pod.name);
13
+ export const podKind = modelToGroupVersionKind(kind.Pod.name);
15
14
 
16
15
  describe("Fuzzing shouldSkipRequest", () => {
17
16
  test("should handle random inputs without crashing", () => {
@@ -105,14 +104,35 @@ describe("Property-Based Testing shouldSkipRequest", () => {
105
104
  });
106
105
  });
107
106
 
108
- test("should reject when name does not match", () => {
107
+ test("create: should reject when regex name does not match", () => {
109
108
  const binding = {
110
109
  model: kind.Pod,
111
110
  event: Event.Any,
112
111
  kind: podKind,
113
112
  filters: {
114
- name: "bleh",
113
+ name: "",
114
+ namespaces: [],
115
+ regexNamespaces: [],
116
+ regexName: new RegExp(/^default$/).source,
117
+ labels: {},
118
+ annotations: {},
119
+ deletionTimestamp: false,
120
+ },
121
+ callback,
122
+ };
123
+ const pod = CreatePod();
124
+ expect(shouldSkipRequest(binding, pod, [])).toBe(true);
125
+ });
126
+ test("create: should not reject when regex name does match", () => {
127
+ const binding = {
128
+ model: kind.Pod,
129
+ event: Event.Any,
130
+ kind: podKind,
131
+ filters: {
132
+ name: "",
115
133
  namespaces: [],
134
+ regexNamespaces: [],
135
+ regexName: new RegExp(/^cool/).source,
116
136
  labels: {},
117
137
  annotations: {},
118
138
  deletionTimestamp: false,
@@ -120,10 +140,146 @@ test("should reject when name does not match", () => {
120
140
  callback,
121
141
  };
122
142
  const pod = CreatePod();
143
+ expect(shouldSkipRequest(binding, pod, [])).toBe(false);
144
+ });
145
+ test("delete: should reject when regex name does not match", () => {
146
+ const binding = {
147
+ model: kind.Pod,
148
+ event: Event.Any,
149
+ kind: podKind,
150
+ filters: {
151
+ name: "",
152
+ namespaces: [],
153
+ regexNamespaces: [],
154
+ regexName: new RegExp(/^default$/).source,
155
+ labels: {},
156
+ annotations: {},
157
+ deletionTimestamp: false,
158
+ },
159
+ callback,
160
+ };
161
+ const pod = DeletePod();
162
+ expect(shouldSkipRequest(binding, pod, [])).toBe(true);
163
+ });
164
+ test("delete: should not reject when regex name does match", () => {
165
+ const binding = {
166
+ model: kind.Pod,
167
+ event: Event.Any,
168
+ kind: podKind,
169
+ filters: {
170
+ name: "",
171
+ namespaces: [],
172
+ regexNamespaces: [],
173
+ regexName: new RegExp(/^cool/).source,
174
+ labels: {},
175
+ annotations: {},
176
+ deletionTimestamp: false,
177
+ },
178
+ callback,
179
+ };
180
+ const pod = DeletePod();
181
+ expect(shouldSkipRequest(binding, pod, [])).toBe(false);
182
+ });
123
183
 
184
+ test("create: should not reject when regex namespace does match", () => {
185
+ const binding = {
186
+ model: kind.Pod,
187
+ event: Event.Any,
188
+ kind: podKind,
189
+ filters: {
190
+ name: "",
191
+ namespaces: [],
192
+ regexNamespaces: [new RegExp(/^helm/).source],
193
+ regexName: "",
194
+ labels: {},
195
+ annotations: {},
196
+ deletionTimestamp: false,
197
+ },
198
+ callback,
199
+ };
200
+ const pod = CreatePod();
201
+ expect(shouldSkipRequest(binding, pod, [])).toBe(false);
202
+ });
203
+
204
+ test("create: should reject when regex namespace does not match", () => {
205
+ const binding = {
206
+ model: kind.Pod,
207
+ event: Event.Any,
208
+ kind: podKind,
209
+ filters: {
210
+ name: "",
211
+ namespaces: [],
212
+ regexNamespaces: [new RegExp(/^argo/).source],
213
+ regexName: "",
214
+ labels: {},
215
+ annotations: {},
216
+ deletionTimestamp: false,
217
+ },
218
+ callback,
219
+ };
220
+ const pod = CreatePod();
221
+ expect(shouldSkipRequest(binding, pod, [])).toBe(true);
222
+ });
223
+
224
+ test("delete: should reject when regex namespace does not match", () => {
225
+ const binding = {
226
+ model: kind.Pod,
227
+ event: Event.Any,
228
+ kind: podKind,
229
+ filters: {
230
+ name: "bleh",
231
+ namespaces: [],
232
+ regexNamespaces: [new RegExp(/^argo/).source],
233
+ regexName: "",
234
+ labels: {},
235
+ annotations: {},
236
+ deletionTimestamp: false,
237
+ },
238
+ callback,
239
+ };
240
+ const pod = DeletePod();
124
241
  expect(shouldSkipRequest(binding, pod, [])).toBe(true);
125
242
  });
126
243
 
244
+ test("delete: should not reject when regex namespace does match", () => {
245
+ const binding = {
246
+ model: kind.Pod,
247
+ event: Event.Any,
248
+ kind: podKind,
249
+ filters: {
250
+ name: "",
251
+ namespaces: [],
252
+ regexNamespaces: [new RegExp(/^helm/).source],
253
+ regexName: "",
254
+ labels: {},
255
+ annotations: {},
256
+ deletionTimestamp: false,
257
+ },
258
+ callback,
259
+ };
260
+ const pod = DeletePod();
261
+ expect(shouldSkipRequest(binding, pod, [])).toBe(false);
262
+ });
263
+
264
+ test("delete: should reject when name does not match", () => {
265
+ const binding = {
266
+ model: kind.Pod,
267
+ event: Event.Any,
268
+ kind: podKind,
269
+ filters: {
270
+ name: "bleh",
271
+ namespaces: [],
272
+ regexNamespaces: [],
273
+ regexName: new RegExp(/^not-cool/).source,
274
+ labels: {},
275
+ annotations: {},
276
+ deletionTimestamp: false,
277
+ },
278
+ callback,
279
+ };
280
+ const pod = DeletePod();
281
+ expect(shouldSkipRequest(binding, pod, [])).toBe(true);
282
+ });
127
283
  test("should reject when kind does not match", () => {
128
284
  const binding = {
129
285
  model: kind.Pod,
@@ -132,6 +288,8 @@ test("should reject when kind does not match", () => {
132
288
  filters: {
133
289
  name: "",
134
290
  namespaces: [],
291
+ regexNamespaces: [],
292
+ regexName: "",
135
293
  labels: {},
136
294
  annotations: {},
137
295
  deletionTimestamp: false,
@@ -154,6 +312,8 @@ test("should reject when group does not match", () => {
154
312
  labels: {},
155
313
  annotations: {},
156
314
  deletionTimestamp: false,
315
+ regexNamespaces: [],
316
+ regexName: "",
157
317
  },
158
318
  callback,
159
319
  };
@@ -177,6 +337,8 @@ test("should reject when version does not match", () => {
177
337
  labels: {},
178
338
  annotations: {},
179
339
  deletionTimestamp: false,
340
+ regexNamespaces: [],
341
+ regexName: "",
180
342
  },
181
343
  callback,
182
344
  };
@@ -196,6 +358,8 @@ test("should allow when group, version, and kind match", () => {
196
358
  labels: {},
197
359
  annotations: {},
198
360
  deletionTimestamp: false,
361
+ regexNamespaces: [],
362
+ regexName: "",
199
363
  },
200
364
  callback,
201
365
  };
@@ -219,6 +383,8 @@ test("should allow when kind match and others are empty", () => {
219
383
  labels: {},
220
384
  annotations: {},
221
385
  deletionTimestamp: false,
386
+ regexNamespaces: [],
387
+ regexName: "",
222
388
  },
223
389
  callback,
224
390
  };
@@ -227,7 +393,7 @@ test("should allow when kind match and others are empty", () => {
227
393
  expect(shouldSkipRequest(binding, pod, [])).toBe(false);
228
394
  });
229
395
 
230
- test("should reject when teh capability namespace does not match", () => {
396
+ test("should reject when the capability namespace does not match", () => {
231
397
  const binding = {
232
398
  model: kind.Pod,
233
399
  event: Event.Any,
@@ -238,6 +404,8 @@ test("should reject when teh capability namespace does not match", () => {
238
404
  labels: {},
239
405
  annotations: {},
240
406
  deletionTimestamp: false,
407
+ regexNamespaces: [],
408
+ regexName: "",
241
409
  },
242
410
  callback,
243
411
  };
@@ -257,6 +425,8 @@ test("should reject when namespace does not match", () => {
257
425
  labels: {},
258
426
  annotations: {},
259
427
  deletionTimestamp: false,
428
+ regexNamespaces: [],
429
+ regexName: "",
260
430
  },
261
431
  callback,
262
432
  };
@@ -272,10 +442,12 @@ test("should allow when namespace is match", () => {
272
442
  kind: podKind,
273
443
  filters: {
274
444
  name: "",
275
- namespaces: ["default", "unicorn", "things"],
445
+ namespaces: ["helm-releasename", "unicorn", "things"],
276
446
  labels: {},
277
447
  annotations: {},
278
448
  deletionTimestamp: false,
449
+ regexNamespaces: [],
450
+ regexName: "",
279
451
  },
280
452
  callback,
281
453
  };
@@ -297,6 +469,8 @@ test("should reject when label does not match", () => {
297
469
  },
298
470
  annotations: {},
299
471
  deletionTimestamp: false,
472
+ regexNamespaces: [],
473
+ regexName: "",
300
474
  },
301
475
  callback,
302
476
  };
@@ -314,6 +488,8 @@ test("should allow when label is match", () => {
314
488
  name: "",
315
489
  deletionTimestamp: false,
316
490
  namespaces: [],
491
+ regexNamespaces: [],
492
+ regexName: "",
317
493
  labels: {
318
494
  foo: "bar",
319
495
  test: "test1",
@@ -347,6 +523,8 @@ test("should reject when annotation does not match", () => {
347
523
  foo: "bar",
348
524
  },
349
525
  deletionTimestamp: false,
526
+ regexNamespaces: [],
527
+ regexName: "",
350
528
  },
351
529
  callback,
352
530
  };
@@ -369,6 +547,8 @@ test("should allow when annotation is match", () => {
369
547
  test: "test1",
370
548
  },
371
549
  deletionTimestamp: false,
550
+ regexNamespaces: [],
551
+ regexName: "",
372
552
  },
373
553
  callback,
374
554
  };
@@ -392,6 +572,8 @@ test("should use `oldObject` when the operation is `DELETE`", () => {
392
572
  filters: {
393
573
  name: "",
394
574
  namespaces: [],
575
+ regexNamespaces: [],
576
+ regexName: "",
395
577
  deletionTimestamp: false,
396
578
  labels: {
397
579
  "app.kubernetes.io/name": "cool-name-podinfo",
@@ -417,6 +599,8 @@ test("should skip processing when deletionTimestamp is not present on pod", () =
417
599
  name: "",
418
600
  namespaces: [],
419
601
  labels: {},
602
+ regexNamespaces: [],
603
+ regexName: "",
420
604
  annotations: {
421
605
  foo: "bar",
422
606
  test: "test1",
@@ -446,6 +630,8 @@ test("should processing when deletionTimestamp is not present on pod", () => {
446
630
  name: "",
447
631
  namespaces: [],
448
632
  labels: {},
633
+ regexNamespaces: [],
634
+ regexName: "",
449
635
  annotations: {
450
636
  foo: "bar",
451
637
  test: "test1",
package/src/lib/filter.ts CHANGED
@@ -1,9 +1,24 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
- import { AdmissionRequest, Operation } from "./k8s";
5
- import logger from "./logger";
6
- import { Binding, Event } from "./types";
4
+ import { AdmissionRequest, Binding, Operation } from "./types";
5
+ import {
6
+ carriesIgnoredNamespace,
7
+ misboundDeleteWithDeletionTimestamp,
8
+ mismatchedDeletionTimestamp,
9
+ mismatchedAnnotations,
10
+ mismatchedLabels,
11
+ mismatchedName,
12
+ mismatchedNameRegex,
13
+ mismatchedNamespace,
14
+ mismatchedNamespaceRegex,
15
+ mismatchedEvent,
16
+ mismatchedGroup,
17
+ mismatchedVersion,
18
+ mismatchedKind,
19
+ unbindableNamespaces,
20
+ uncarryableNamespace,
21
+ } from "./adjudicators";
7
22
 
8
23
  /**
9
24
  * shouldSkipRequest determines if a request should be skipped based on the binding filters.
@@ -12,108 +27,32 @@ import { Binding, Event } from "./types";
12
27
  * @param req the incoming request
13
28
  * @returns
14
29
  */
15
- export function shouldSkipRequest(binding: Binding, req: AdmissionRequest, capabilityNamespaces: string[]) {
16
- const { group, kind, version } = binding.kind || {};
17
- const { namespaces, labels, annotations, name } = binding.filters || {};
18
- const operation = req.operation.toUpperCase();
19
- const uid = req.uid;
20
- // Use the old object if the request is a DELETE operation
21
- const srcObject = operation === Operation.DELETE ? req.oldObject : req.object;
22
- const { metadata } = srcObject || {};
23
- const combinedNamespaces = [...namespaces, ...capabilityNamespaces];
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
- }
34
- // Test for matching operation
35
- if (!binding.event.includes(operation) && !binding.event.includes(Event.Any)) {
36
- return true;
37
- }
38
-
39
- // Test name first, since it's the most specific
40
- if (name && name !== req.name) {
41
- return true;
42
- }
43
-
44
- // Test for matching kinds
45
- if (kind !== req.kind.kind) {
46
- return true;
47
- }
48
-
49
- // Test for matching groups
50
- if (group && group !== req.kind.group) {
51
- return true;
52
- }
53
-
54
- // Test for matching versions
55
- if (version && version !== req.kind.version) {
56
- return true;
57
- }
58
-
59
- // Test for matching namespaces
60
- if (
61
- (combinedNamespaces.length && !combinedNamespaces.includes(req.namespace || "")) ||
62
- (!namespaces.includes(req.namespace || "") && capabilityNamespaces.length !== 0 && namespaces.length !== 0)
63
- ) {
64
- let type = "";
65
- let label = "";
66
-
67
- if (binding.isMutate) {
68
- type = "Mutate";
69
- label = binding.mutateCallback!.name;
70
- } else if (binding.isValidate) {
71
- type = "Validate";
72
- label = binding.validateCallback!.name;
73
- } else if (binding.isWatch) {
74
- type = "Watch";
75
- label = binding.watchCallback!.name;
76
- }
77
-
78
- logger.debug({ uid }, `${type} binding (${label}) does not match request namespace "${req.namespace}"`);
79
-
80
- return true;
81
- }
82
-
83
- // Test for matching labels
84
- for (const [key, value] of Object.entries(labels)) {
85
- const testKey = metadata?.labels?.[key];
86
-
87
- // First check if the label exists
88
- if (!testKey) {
89
- logger.debug({ uid }, `Label ${key} does not exist`);
90
- return true;
91
- }
92
-
93
- // Then check if the value matches, if specified
94
- if (value && testKey !== value) {
95
- logger.debug({ uid }, `${testKey} does not match ${value}`);
96
- return true;
97
- }
98
- }
99
-
100
- // Test for matching annotations
101
- for (const [key, value] of Object.entries(annotations)) {
102
- const testKey = metadata?.annotations?.[key];
103
-
104
- // First check if the annotation exists
105
- if (!testKey) {
106
- logger.debug({ uid }, `Annotation ${key} does not exist`);
107
- return true;
108
- }
109
-
110
- // Then check if the value matches, if specified
111
- if (value && testKey !== value) {
112
- logger.debug({ uid }, `${testKey} does not match ${value}`);
113
- return true;
114
- }
115
- }
116
-
117
- // No failed filters, so we should not skip this request
118
- return false;
30
+ export function shouldSkipRequest(
31
+ binding: Binding,
32
+ req: AdmissionRequest,
33
+ capabilityNamespaces: string[],
34
+ ignoredNamespaces?: string[],
35
+ ): boolean {
36
+ const obj = req.operation === Operation.DELETE ? req.oldObject : req.object;
37
+
38
+ // prettier-ignore
39
+ return (
40
+ misboundDeleteWithDeletionTimestamp(binding) ? true :
41
+ mismatchedDeletionTimestamp(binding, obj) ? true :
42
+ mismatchedEvent(binding, req) ? true :
43
+ mismatchedName(binding, obj) ? true :
44
+ mismatchedGroup(binding, req) ? true :
45
+ mismatchedVersion(binding, req) ? true :
46
+ mismatchedKind(binding, req) ? true :
47
+ unbindableNamespaces(capabilityNamespaces, binding) ? true :
48
+ uncarryableNamespace(capabilityNamespaces, obj) ? true :
49
+ mismatchedNamespace(binding, obj) ? true :
50
+ mismatchedLabels(binding, obj) ? true :
51
+ mismatchedAnnotations(binding, obj) ? true :
52
+ mismatchedNamespaceRegex(binding, obj) ? true :
53
+ mismatchedNameRegex(binding, obj) ? true :
54
+ carriesIgnoredNamespace(ignoredNamespaces, obj) ? true :
55
+
56
+ false
57
+ );
119
58
  }