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
@@ -0,0 +1,236 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { beforeEach, describe, expect, it, jest } from "@jest/globals";
5
+ import { addFinalizer, removeFinalizer } from "./finalizer";
6
+ import { KubernetesObject, K8s, GenericClass, RegisterKind } from "kubernetes-fluent-client";
7
+ import { K8sInit } from "kubernetes-fluent-client/dist/fluent/types";
8
+ import { AdmissionRequest, Operation } from "./types";
9
+ import { PeprMutateRequest } from "./mutate-request";
10
+ import { Binding } from "./types";
11
+
12
+ jest.mock("kubernetes-fluent-client");
13
+ const mockK8s = jest.mocked(K8s);
14
+ const mockRegisterKind = jest.mocked(RegisterKind);
15
+
16
+ const PEPR_FINALIZER = "pepr.dev/finalizer";
17
+
18
+ describe("addFinalizer", () => {
19
+ let request: PeprMutateRequest<KubernetesObject>;
20
+
21
+ const fakeAdmissionRequest = (op: Operation): AdmissionRequest<KubernetesObject> => {
22
+ const obj = {
23
+ apiVersion: "v1",
24
+ kind: "Pod",
25
+ metadata: {
26
+ name: "test-pod",
27
+ labels: {
28
+ "existing-label": "true",
29
+ },
30
+ annotations: {
31
+ "existing-annotation": "true",
32
+ },
33
+ },
34
+ };
35
+
36
+ return {
37
+ operation: op,
38
+ object: obj,
39
+ oldObject: obj,
40
+ dryRun: false,
41
+ uid: "uid",
42
+ name: "name",
43
+ kind: { group: "", version: "v1", kind: "Pod" },
44
+ resource: { group: "", version: "v1", resource: "pods" },
45
+ userInfo: {},
46
+ };
47
+ };
48
+
49
+ describe("on create", () => {
50
+ const op = Operation.CREATE;
51
+
52
+ beforeEach(() => {
53
+ request = new PeprMutateRequest(fakeAdmissionRequest(op));
54
+ });
55
+
56
+ it("adds pepr finalizer when no other finalizers are present", () => {
57
+ expect(request.Raw.metadata?.finalizers).toBeUndefined();
58
+
59
+ // impure function (acts via side effect instead of return result) --
60
+ // i.e. it directly modifies the passed-in request object
61
+ addFinalizer(request);
62
+
63
+ expect(request.Raw.metadata!.finalizers).toContain(PEPR_FINALIZER);
64
+ });
65
+
66
+ it("adds pepr finalizer when other finalizers are present", () => {
67
+ const original = ["i.am.a/macguffin", "i.am.one/too"];
68
+ request.Raw.metadata!.finalizers = [...original];
69
+
70
+ addFinalizer(request);
71
+
72
+ const expected = [...original, PEPR_FINALIZER];
73
+ expect(request.Raw.metadata!.finalizers).toHaveLength(expected.length);
74
+ expect(request.Raw.metadata!.finalizers).toEqual(expect.arrayContaining(expected));
75
+ });
76
+
77
+ it("does not add another pepr finalizer when one is already present", () => {
78
+ const original = ["i.am.a/macguffin", "i.am.one/too", PEPR_FINALIZER];
79
+ request.Raw.metadata!.finalizers = [...original];
80
+
81
+ addFinalizer(request);
82
+
83
+ const expected = [...original];
84
+ expect(request.Raw.metadata!.finalizers).toHaveLength(expected.length);
85
+ expect(request.Raw.metadata!.finalizers).toEqual(expect.arrayContaining(expected));
86
+ });
87
+ });
88
+
89
+ describe("on update", () => {
90
+ const op = Operation.UPDATE;
91
+
92
+ beforeEach(() => {
93
+ request = new PeprMutateRequest(fakeAdmissionRequest(op));
94
+ });
95
+
96
+ it("adds pepr finalizer when no other finalizers are present", () => {
97
+ expect(request.Raw.metadata?.finalizers).toBeUndefined();
98
+
99
+ addFinalizer(request);
100
+
101
+ expect(request.Raw.metadata!.finalizers).toContain(PEPR_FINALIZER);
102
+ });
103
+
104
+ it("adds pepr finalizer when other finalizers are present", () => {
105
+ const original = ["i.am.a/macguffin", "i.am.one/too"];
106
+ request.Raw.metadata!.finalizers = [...original];
107
+
108
+ addFinalizer(request);
109
+
110
+ const expected = [...original, PEPR_FINALIZER];
111
+ expect(request.Raw.metadata!.finalizers).toHaveLength(expected.length);
112
+ expect(request.Raw.metadata!.finalizers).toEqual(expect.arrayContaining(expected));
113
+ });
114
+
115
+ it("does not add another pepr finalizer when one is already present", () => {
116
+ const original = ["i.am.a/macguffin", "i.am.one/too", PEPR_FINALIZER];
117
+ request.Raw.metadata!.finalizers = [...original];
118
+
119
+ addFinalizer(request);
120
+
121
+ const expected = [...original];
122
+ expect(request.Raw.metadata!.finalizers).toHaveLength(expected.length);
123
+ expect(request.Raw.metadata!.finalizers).toEqual(expect.arrayContaining(expected));
124
+ });
125
+
126
+ it("does not add pepr finalizer if a deletetionTimestamp is present", () => {
127
+ const original = ["i.am.a/macguffin", "i.am.one/too"];
128
+ request.Raw.metadata!.finalizers = [...original];
129
+ request.Raw.metadata!.deletionTimestamp = new Date();
130
+
131
+ addFinalizer(request);
132
+
133
+ const expected = [...original];
134
+ expect(request.Raw.metadata!.finalizers).toHaveLength(expected.length);
135
+ expect(request.Raw.metadata!.finalizers).toEqual(expect.arrayContaining(expected));
136
+ });
137
+ });
138
+
139
+ describe("on delete", () => {
140
+ const op = Operation.DELETE;
141
+
142
+ beforeEach(() => {
143
+ request = new PeprMutateRequest(fakeAdmissionRequest(op));
144
+ });
145
+
146
+ it("does not add pepr finalizer", () => {
147
+ const original = ["i.am.a/macguffin", "i.am.one/too"];
148
+ request.Raw.metadata!.finalizers = [...original];
149
+
150
+ addFinalizer(request);
151
+
152
+ const expected = [...original];
153
+ expect(request.Raw.metadata!.finalizers).toHaveLength(expected.length);
154
+ expect(request.Raw.metadata!.finalizers).toEqual(expect.arrayContaining(expected));
155
+ });
156
+ });
157
+ });
158
+
159
+ describe("removeFinalizer", () => {
160
+ const mockPatch = jest.fn();
161
+
162
+ const fakeBinding = () =>
163
+ ({
164
+ model: {},
165
+ kind: {},
166
+ }) as unknown as Binding;
167
+
168
+ const fakeObject = () =>
169
+ ({
170
+ metadata: {
171
+ namespace: "test-namespace",
172
+ name: "test-name",
173
+ },
174
+ }) as KubernetesObject;
175
+
176
+ describe("when RegisterKind fails", () => {
177
+ beforeEach(() => {
178
+ jest.resetAllMocks();
179
+
180
+ mockK8s.mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
181
+ return { Patch: mockPatch } as unknown as K8sInit<T, K>;
182
+ });
183
+
184
+ mockRegisterKind.mockImplementation(() => {
185
+ throw "oops";
186
+ });
187
+ });
188
+
189
+ it("does NOT send a JSON Patch to remove pepr finalizers", async () => {
190
+ const object = fakeObject();
191
+ const binding = fakeBinding();
192
+
193
+ await removeFinalizer(binding, object);
194
+
195
+ expect(mockPatch.mock.calls).toHaveLength(0);
196
+ });
197
+ });
198
+
199
+ describe("when RegisterKind succeeds", () => {
200
+ beforeEach(() => {
201
+ jest.resetAllMocks();
202
+
203
+ mockPatch.mockImplementation(ops => {
204
+ return { ops };
205
+ });
206
+
207
+ mockK8s.mockImplementation(<T extends GenericClass, K extends KubernetesObject>() => {
208
+ return { Patch: mockPatch } as unknown as K8sInit<T, K>;
209
+ });
210
+
211
+ mockRegisterKind.mockImplementation(() => {});
212
+ });
213
+
214
+ it("sends a JSON Patch to remove pepr finalizers", async () => {
215
+ const originalFinalizers = ["ignore.me/now", "ignore.me/too", PEPR_FINALIZER];
216
+ const adjustedFinalizers = originalFinalizers.filter(f => f !== PEPR_FINALIZER);
217
+
218
+ const object = fakeObject();
219
+ object.metadata!.finalizers = [...originalFinalizers];
220
+ const binding = fakeBinding();
221
+
222
+ await removeFinalizer(binding, object);
223
+
224
+ const expected = [
225
+ {
226
+ op: "replace",
227
+ path: `/metadata/finalizers`,
228
+ value: adjustedFinalizers,
229
+ },
230
+ ];
231
+ expect(mockPatch.mock.calls).toHaveLength(1);
232
+ expect(mockPatch.mock.calls[0]).toHaveLength(1);
233
+ expect(mockPatch.mock.calls[0][0]).toEqual(expected);
234
+ });
235
+ });
236
+ });
@@ -0,0 +1,63 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
+
4
+ import { K8s, KubernetesObject, RegisterKind } from "kubernetes-fluent-client";
5
+ import Log from "./logger";
6
+ import { Binding, DeepPartial } from "./types";
7
+ import { Operation } from "./types";
8
+ import { PeprMutateRequest } from "./mutate-request";
9
+
10
+ export function addFinalizer<K extends KubernetesObject>(request: PeprMutateRequest<K>) {
11
+ // if a DELETE is being processed, don't add a finalizer
12
+ if (request.Request.operation === Operation.DELETE) {
13
+ return;
14
+ }
15
+
16
+ // if an UPDATE is being processed and it HAS a deletionTimestamp, the
17
+ // resource is going through a pre-delete flow so don't (re-)add a finalizer
18
+ if (request.Request.operation === Operation.UPDATE && request.Raw.metadata?.deletionTimestamp) {
19
+ return;
20
+ }
21
+
22
+ const peprFinal = "pepr.dev/finalizer";
23
+ const finalizers = request.Raw.metadata?.finalizers || [];
24
+ if (!finalizers.includes(peprFinal)) {
25
+ finalizers.push(peprFinal);
26
+ }
27
+
28
+ request.Merge({ metadata: { finalizers } } as DeepPartial<K>);
29
+ }
30
+
31
+ export async function removeFinalizer(binding: Binding, obj: KubernetesObject) {
32
+ const peprFinal = "pepr.dev/finalizer";
33
+ const meta = obj.metadata!;
34
+ const resource = `${meta.namespace || "ClusterScoped"}/${meta.name}`;
35
+
36
+ Log.debug({ obj }, `Removing finalizer '${peprFinal}' from '${resource}'`);
37
+
38
+ // ensure request model is registerd with KFC (for non-built in CRD's, etc.)
39
+ const { model, kind } = binding;
40
+ try {
41
+ RegisterKind(model, kind);
42
+ } catch (e) {
43
+ const expected = e.message === `GVK ${model.name} already registered`;
44
+ if (!expected) {
45
+ Log.error({ model, kind, error: e }, `Error registering "${kind}" during finalization.`);
46
+ return;
47
+ }
48
+ }
49
+
50
+ // remove pepr finalizers
51
+ const finalizers = meta.finalizers?.filter(f => f !== peprFinal) || [];
52
+
53
+ // JSON Patch - replace a key
54
+ // https://datatracker.ietf.org/doc/html/rfc6902/#section-4.3
55
+ obj = await K8s(model, meta).Patch([
56
+ {
57
+ op: "replace",
58
+ path: `/metadata/finalizers`,
59
+ value: finalizers,
60
+ },
61
+ ]);
62
+ Log.debug({ obj }, `Removed finalizer '${peprFinal}' from '${resource}'`);
63
+ }