pepr 0.46.0-nightly.5 → 0.46.0-nightly.6

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
@@ -15,7 +15,7 @@
15
15
  "!src/**/*.test.ts",
16
16
  "!dist/**/*.test.d.ts*"
17
17
  ],
18
- "version": "0.46.0-nightly.5",
18
+ "version": "0.46.0-nightly.6",
19
19
  "main": "dist/lib.js",
20
20
  "types": "dist/lib.d.ts",
21
21
  "scripts": {
@@ -76,7 +76,7 @@
76
76
  "jest": "29.7.0",
77
77
  "js-yaml": "^4.1.0",
78
78
  "shellcheck": "^3.0.0",
79
- "ts-jest": "29.2.5",
79
+ "ts-jest": "29.2.6",
80
80
  "undici": "^7.0.1"
81
81
  },
82
82
  "overrides": {
@@ -0,0 +1,533 @@
1
+ import { GenericClass } from "kubernetes-fluent-client";
2
+ import { Event } from "../enums";
3
+ import { CapabilityExport } from "../types";
4
+ import { describe, beforeEach, jest, it, expect } from "@jest/globals";
5
+ import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
6
+ import fs from "fs";
7
+ import { clusterRole } from "./rbac";
8
+ import * as helpers from "../helpers";
9
+
10
+ export const mockCapabilities: CapabilityExport[] = [
11
+ {
12
+ rbac: [
13
+ {
14
+ apiGroups: ["pepr.dev"],
15
+ resources: ["peprstores"],
16
+ verbs: ["create", "get", "patch", "watch"],
17
+ },
18
+ ],
19
+ bindings: [
20
+ {
21
+ kind: { group: "pepr.dev", version: "v1", kind: "peprstore", plural: "peprstores" },
22
+ isWatch: false,
23
+ event: Event.CREATE,
24
+ model: {} as GenericClass,
25
+ filters: {
26
+ name: "",
27
+ regexName: "",
28
+ namespaces: [],
29
+ regexNamespaces: [],
30
+ labels: {},
31
+ annotations: {},
32
+ deletionTimestamp: false,
33
+ },
34
+ },
35
+ ],
36
+ hasSchedule: false,
37
+ name: "",
38
+ description: "",
39
+ },
40
+ {
41
+ rbac: [
42
+ {
43
+ apiGroups: ["apiextensions.k8s.io"],
44
+ resources: ["customresourcedefinitions"],
45
+ verbs: ["patch", "create"],
46
+ },
47
+ ],
48
+ bindings: [
49
+ {
50
+ kind: {
51
+ group: "apiextensions.k8s.io",
52
+ version: "v1",
53
+ kind: "customresourcedefinition",
54
+ plural: "customresourcedefinitions",
55
+ },
56
+ isWatch: false,
57
+ isFinalize: false,
58
+ event: Event.CREATE,
59
+ model: {} as GenericClass,
60
+ filters: {
61
+ name: "",
62
+ regexName: "",
63
+ namespaces: [],
64
+ regexNamespaces: [],
65
+ labels: {},
66
+ annotations: {},
67
+ deletionTimestamp: false,
68
+ },
69
+ },
70
+ ],
71
+ hasSchedule: false,
72
+ name: "",
73
+ description: "",
74
+ },
75
+ {
76
+ rbac: [
77
+ {
78
+ apiGroups: [""],
79
+ resources: ["namespaces"],
80
+ verbs: ["watch"],
81
+ },
82
+ ],
83
+ bindings: [
84
+ {
85
+ kind: { group: "", version: "v1", kind: "namespace", plural: "namespaces" },
86
+ isWatch: true,
87
+ isFinalize: false,
88
+ event: Event.CREATE,
89
+ model: {} as GenericClass,
90
+ filters: {
91
+ name: "",
92
+ regexName: "",
93
+ namespaces: [],
94
+ regexNamespaces: [],
95
+ labels: {},
96
+ annotations: {},
97
+ deletionTimestamp: false,
98
+ },
99
+ },
100
+ ],
101
+ hasSchedule: false,
102
+ name: "",
103
+ description: "",
104
+ },
105
+ {
106
+ rbac: [
107
+ {
108
+ apiGroups: [""],
109
+ resources: ["configmaps"],
110
+ verbs: ["watch"],
111
+ },
112
+ ],
113
+ bindings: [
114
+ {
115
+ kind: { group: "", version: "v1", kind: "configmap", plural: "configmaps" },
116
+ isWatch: true,
117
+ isFinalize: false,
118
+ event: Event.CREATE,
119
+ model: {} as GenericClass,
120
+ filters: {
121
+ name: "",
122
+ regexName: "",
123
+ namespaces: [],
124
+ regexNamespaces: [],
125
+ labels: {},
126
+ annotations: {},
127
+ deletionTimestamp: false,
128
+ },
129
+ },
130
+ ],
131
+ hasSchedule: false,
132
+ name: "",
133
+ description: "",
134
+ },
135
+ ];
136
+ describe("RBAC generation", () => {
137
+ beforeEach(() => {
138
+ jest.clearAllMocks();
139
+ const mockPackageJsonRBAC = {};
140
+
141
+ jest.spyOn(fs, "readFileSync").mockImplementation((path: unknown) => {
142
+ if (typeof path === "string" && path.includes("package.json")) {
143
+ return JSON.stringify({ rbac: mockPackageJsonRBAC });
144
+ }
145
+ return "{}";
146
+ });
147
+ });
148
+
149
+ it("should generate correct ClusterRole rules in scoped mode", () => {
150
+ const result = clusterRole("test-role", mockCapabilities, "scoped", []);
151
+
152
+ expect(result.rules).toEqual([
153
+ {
154
+ apiGroups: ["pepr.dev"],
155
+ resources: ["peprstores"],
156
+ verbs: ["create", "get", "patch", "watch"],
157
+ },
158
+ {
159
+ apiGroups: ["apiextensions.k8s.io"],
160
+ resources: ["customresourcedefinitions"],
161
+ verbs: ["patch", "create"],
162
+ },
163
+ {
164
+ apiGroups: [""],
165
+ resources: ["namespaces"],
166
+ verbs: ["watch"],
167
+ },
168
+ {
169
+ apiGroups: [""],
170
+ resources: ["configmaps"],
171
+ verbs: ["watch"],
172
+ },
173
+ ]);
174
+ });
175
+
176
+ it("should generate a ClusterRole with wildcard rules when not in scoped mode", () => {
177
+ const expectedWildcardRules = [
178
+ {
179
+ apiGroups: ["*"],
180
+ resources: ["*"],
181
+ verbs: ["create", "delete", "get", "list", "patch", "update", "watch"],
182
+ },
183
+ ];
184
+
185
+ const result = clusterRole("test-role", mockCapabilities, "admin", []);
186
+
187
+ expect(result.rules).toEqual(expectedWildcardRules);
188
+ });
189
+
190
+ it("should return an empty rules array when capabilities are empty in scoped mode", () => {
191
+ const result = clusterRole("test-role", [], "scoped", []);
192
+
193
+ expect(result.rules).toEqual([]);
194
+ });
195
+
196
+ it("should include finalize verbs if isFinalize is true in scoped mode", () => {
197
+ const capabilitiesWithFinalize: CapabilityExport[] = [
198
+ {
199
+ rbac: [
200
+ {
201
+ apiGroups: ["pepr.dev"],
202
+ resources: ["peprstores"],
203
+ verbs: ["patch"],
204
+ },
205
+ ],
206
+ bindings: [
207
+ {
208
+ kind: { group: "pepr.dev", version: "v1", kind: "peprstore", plural: "peprstores" },
209
+ isWatch: false,
210
+ isFinalize: true,
211
+ event: Event.CREATE,
212
+ model: {} as GenericClass,
213
+ filters: {
214
+ name: "",
215
+ regexName: "",
216
+ namespaces: [],
217
+ regexNamespaces: [],
218
+ labels: {},
219
+ annotations: {},
220
+ deletionTimestamp: false,
221
+ },
222
+ },
223
+ ],
224
+ hasSchedule: false,
225
+ name: "",
226
+ description: "",
227
+ },
228
+ ];
229
+
230
+ const result = clusterRole(
231
+ "test-role",
232
+ capabilitiesWithFinalize,
233
+ "scoped",
234
+ capabilitiesWithFinalize.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
235
+ );
236
+
237
+ expect(result.rules).toEqual([
238
+ {
239
+ apiGroups: ["pepr.dev"],
240
+ resources: ["peprstores"],
241
+ verbs: ["patch"],
242
+ },
243
+ {
244
+ apiGroups: ["apiextensions.k8s.io"],
245
+ resources: ["customresourcedefinitions"],
246
+ verbs: ["patch", "create"],
247
+ },
248
+ ]);
249
+ });
250
+
251
+ it("should deduplicate verbs and resources in rules", () => {
252
+ const capabilitiesWithDuplicates: CapabilityExport[] = [
253
+ {
254
+ rbac: [
255
+ {
256
+ apiGroups: ["pepr.dev"],
257
+ resources: ["peprstores"],
258
+ verbs: ["create", "get"],
259
+ },
260
+ ],
261
+ bindings: [
262
+ {
263
+ kind: { group: "pepr.dev", version: "v1", kind: "peprlog", plural: "peprlogs" },
264
+ isWatch: false,
265
+ event: Event.CREATE,
266
+ model: {} as GenericClass,
267
+ filters: {
268
+ name: "",
269
+ regexName: "",
270
+ namespaces: [],
271
+ regexNamespaces: [],
272
+ labels: {},
273
+ annotations: {},
274
+ deletionTimestamp: false,
275
+ },
276
+ },
277
+ ],
278
+ hasSchedule: false,
279
+ name: "",
280
+ description: "",
281
+ },
282
+ {
283
+ rbac: [
284
+ {
285
+ apiGroups: ["pepr.dev"],
286
+ resources: ["peprstores"],
287
+ verbs: ["get", "patch"],
288
+ },
289
+ ],
290
+ bindings: [
291
+ {
292
+ kind: { group: "pepr.dev", version: "v1", kind: "peprlog", plural: "peprlogs" },
293
+ isWatch: false,
294
+ event: Event.CREATE,
295
+ model: {} as GenericClass,
296
+ filters: {
297
+ name: "",
298
+ regexName: "",
299
+ namespaces: [],
300
+ regexNamespaces: [],
301
+ labels: {},
302
+ annotations: {},
303
+ deletionTimestamp: false,
304
+ },
305
+ },
306
+ ],
307
+ hasSchedule: false,
308
+ name: "",
309
+ description: "",
310
+ },
311
+ ];
312
+
313
+ const result = clusterRole(
314
+ "test-role",
315
+ capabilitiesWithDuplicates,
316
+ "scoped",
317
+ capabilitiesWithDuplicates.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
318
+ );
319
+
320
+ // Filter out only the rules for 'pepr.dev' and 'peprstores'
321
+ const filteredRules = result.rules?.filter(
322
+ rule => rule.apiGroups?.includes("pepr.dev") && rule.resources?.includes("peprstores"),
323
+ );
324
+
325
+ expect(filteredRules).toEqual([
326
+ {
327
+ apiGroups: ["pepr.dev"],
328
+ resources: ["peprstores"],
329
+ verbs: ["create", "get", "patch", "watch"],
330
+ },
331
+ ]);
332
+ });
333
+ });
334
+ describe("clusterRole", () => {
335
+ // Mocking the readRBACFromPackageJson function to return null
336
+ jest.mock("./rbac", () => ({
337
+ ...(jest.requireActual("./rbac") as object),
338
+ readRBACFromPackageJson: jest.fn(() => null),
339
+ }));
340
+
341
+ // Mocking createRBACMap to isolate the behavior of clusterRole function
342
+ jest.mock("../helpers", () => ({
343
+ ...(jest.requireActual("../helpers") as object),
344
+ createRBACMap: jest.fn(),
345
+ }));
346
+
347
+ beforeEach(() => {
348
+ jest.clearAllMocks();
349
+ jest.restoreAllMocks();
350
+ });
351
+
352
+ it("should handle keys with less than 3 segments and set group to an empty string", () => {
353
+ jest.spyOn(helpers, "createRBACMap").mockReturnValue({
354
+ nodes: {
355
+ plural: "nodes",
356
+ verbs: ["get"],
357
+ },
358
+ });
359
+
360
+ const capabilitiesWithShortKey: CapabilityExport[] = [
361
+ {
362
+ rbac: [
363
+ {
364
+ apiGroups: [""],
365
+ resources: ["nodes"],
366
+ verbs: ["get"],
367
+ },
368
+ ],
369
+ bindings: [
370
+ {
371
+ kind: { group: "", version: "v1", kind: "node", plural: "nodes" },
372
+ isWatch: false,
373
+ event: Event.CREATE,
374
+ model: {} as GenericClass,
375
+ filters: {
376
+ name: "",
377
+ regexName: "",
378
+ namespaces: [],
379
+ regexNamespaces: [],
380
+ labels: {},
381
+ annotations: {},
382
+ deletionTimestamp: false,
383
+ },
384
+ },
385
+ ],
386
+ hasSchedule: false,
387
+ name: "",
388
+ description: "",
389
+ },
390
+ ];
391
+
392
+ const result = clusterRole(
393
+ "test-role",
394
+ capabilitiesWithShortKey,
395
+ "scoped",
396
+ capabilitiesWithShortKey.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
397
+ );
398
+
399
+ expect(result.rules).toEqual([
400
+ {
401
+ apiGroups: [""],
402
+ resources: ["nodes"],
403
+ verbs: ["get"],
404
+ },
405
+ ]);
406
+ });
407
+
408
+ it("should handle keys with 3 or more segments and set group correctly", () => {
409
+ jest.spyOn(helpers, "createRBACMap").mockReturnValue({
410
+ "apps/v1/deployments": {
411
+ plural: "deployments",
412
+ verbs: ["create"],
413
+ },
414
+ });
415
+
416
+ const capabilitiesWithLongKey: CapabilityExport[] = [
417
+ {
418
+ rbac: [
419
+ {
420
+ apiGroups: ["apps"],
421
+ resources: ["deployments"],
422
+ verbs: ["create"],
423
+ },
424
+ ],
425
+ bindings: [
426
+ {
427
+ kind: { group: "apps", version: "v1", kind: "deployment", plural: "deployments" },
428
+ isWatch: false,
429
+ event: Event.CREATE,
430
+ model: {} as GenericClass,
431
+ filters: {
432
+ name: "",
433
+ regexName: "",
434
+ namespaces: [],
435
+ regexNamespaces: [],
436
+ labels: {},
437
+ annotations: {},
438
+ deletionTimestamp: false,
439
+ },
440
+ },
441
+ ],
442
+ hasSchedule: false,
443
+ name: "",
444
+ description: "",
445
+ },
446
+ ];
447
+
448
+ const result = clusterRole(
449
+ "test-role",
450
+ capabilitiesWithLongKey,
451
+ "scoped",
452
+ capabilitiesWithLongKey.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
453
+ );
454
+
455
+ expect(result.rules).toEqual([
456
+ {
457
+ apiGroups: ["apps"],
458
+ resources: ["deployments"],
459
+ verbs: ["create"],
460
+ },
461
+ ]);
462
+ });
463
+
464
+ it("should handle non-array custom RBAC by defaulting to an empty array", () => {
465
+ // Mock readRBACFromPackageJson to return a non-array value
466
+ jest.spyOn(fs, "readFileSync").mockImplementation(() => {
467
+ return JSON.stringify({
468
+ pepr: {
469
+ rbac: "not-an-array", // Simulate invalid RBAC structure
470
+ },
471
+ });
472
+ });
473
+
474
+ const result = clusterRole(
475
+ "test-role",
476
+ mockCapabilities,
477
+ "scoped",
478
+ mockCapabilities.flatMap(c => c.rbac).filter((rule): rule is PolicyRule => rule !== undefined),
479
+ );
480
+
481
+ // The result should only contain rules from the capabilities, not from the invalid custom RBAC
482
+ expect(result.rules).toEqual([
483
+ {
484
+ apiGroups: ["pepr.dev"],
485
+ resources: ["peprstores"],
486
+ verbs: ["create", "get", "patch", "watch"],
487
+ },
488
+ {
489
+ apiGroups: ["apiextensions.k8s.io"],
490
+ resources: ["customresourcedefinitions"],
491
+ verbs: ["patch", "create"],
492
+ },
493
+ {
494
+ apiGroups: [""],
495
+ resources: ["namespaces"],
496
+ verbs: ["watch"],
497
+ },
498
+ {
499
+ apiGroups: [""],
500
+ resources: ["configmaps"],
501
+ verbs: ["watch"],
502
+ },
503
+ ]);
504
+ });
505
+
506
+ it("should default to an empty verbs array if rule.verbs is undefined", () => {
507
+ // Simulate a custom RBAC rule with empty verbs
508
+ const customRbacWithNoVerbs: PolicyRule[] = [
509
+ {
510
+ apiGroups: ["pepr.dev"],
511
+ resources: ["customresources"],
512
+ verbs: [], // Set verbs to an empty array to satisfy the V1PolicyRule type
513
+ },
514
+ ];
515
+
516
+ jest.spyOn(fs, "readFileSync").mockImplementation(() => {
517
+ return JSON.stringify({
518
+ pepr: {
519
+ rbac: customRbacWithNoVerbs,
520
+ },
521
+ });
522
+ });
523
+
524
+ const result = clusterRole("test-role", mockCapabilities, "scoped", customRbacWithNoVerbs);
525
+
526
+ // Check that the verbs array is empty for the custom RBAC rule
527
+ expect(result.rules).toContainEqual({
528
+ apiGroups: ["pepr.dev"],
529
+ resources: ["customresources"],
530
+ verbs: [],
531
+ });
532
+ });
533
+ });
@@ -147,7 +147,7 @@ async function runBinding(
147
147
  };
148
148
 
149
149
  // Setup the resource watch
150
- const watcher = K8s(binding.model, binding.filters).Watch(async (obj, phase) => {
150
+ const watcher = K8s(binding.model, { ...binding.filters, kindOverride: binding.kind }).Watch(async (obj, phase) => {
151
151
  Log.debug(obj, `Watch event ${phase} received`);
152
152
 
153
153
  if (binding.isQueue) {