pepr 0.38.3 → 0.39.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 (95) hide show
  1. package/dist/cli/build.d.ts +1 -1
  2. package/dist/cli/build.d.ts.map +1 -1
  3. package/dist/cli/build.helpers.d.ts +19 -0
  4. package/dist/cli/build.helpers.d.ts.map +1 -0
  5. package/dist/cli/deploy.d.ts.map +1 -1
  6. package/dist/cli/format.d.ts.map +1 -1
  7. package/dist/cli/init/index.d.ts.map +1 -1
  8. package/dist/cli/init/templates.d.ts +6 -2
  9. package/dist/cli/init/templates.d.ts.map +1 -1
  10. package/dist/cli/monitor.d.ts.map +1 -1
  11. package/dist/cli.js +294 -229
  12. package/dist/controller.js +53 -31
  13. package/dist/lib/assets/deploy.d.ts.map +1 -1
  14. package/dist/lib/assets/helm.d.ts +1 -0
  15. package/dist/lib/assets/helm.d.ts.map +1 -1
  16. package/dist/lib/assets/index.d.ts +1 -1
  17. package/dist/lib/assets/index.d.ts.map +1 -1
  18. package/dist/lib/assets/rbac.d.ts +31 -4
  19. package/dist/lib/assets/rbac.d.ts.map +1 -1
  20. package/dist/lib/assets/yaml.d.ts +2 -2
  21. package/dist/lib/assets/yaml.d.ts.map +1 -1
  22. package/dist/lib/capability.d.ts +2 -8
  23. package/dist/lib/capability.d.ts.map +1 -1
  24. package/dist/lib/controller/store.d.ts +1 -5
  25. package/dist/lib/controller/store.d.ts.map +1 -1
  26. package/dist/lib/controller/storeCache.d.ts +11 -0
  27. package/dist/lib/controller/storeCache.d.ts.map +1 -0
  28. package/dist/lib/enums.d.ts +17 -0
  29. package/dist/lib/enums.d.ts.map +1 -0
  30. package/dist/lib/{adjudicators.d.ts → filter/adjudicators.d.ts} +23 -18
  31. package/dist/lib/filter/adjudicators.d.ts.map +1 -0
  32. package/dist/lib/{filter.d.ts → filter/filter.d.ts} +1 -1
  33. package/dist/lib/filter/filter.d.ts.map +1 -0
  34. package/dist/lib/helpers.d.ts +1 -2
  35. package/dist/lib/helpers.d.ts.map +1 -1
  36. package/dist/lib/k8s.d.ts +1 -1
  37. package/dist/lib/k8s.d.ts.map +1 -1
  38. package/dist/lib/logger.d.ts +4 -0
  39. package/dist/lib/logger.d.ts.map +1 -1
  40. package/dist/lib/metrics.d.ts.map +1 -1
  41. package/dist/lib/module.d.ts +5 -0
  42. package/dist/lib/module.d.ts.map +1 -1
  43. package/dist/lib/mutate-processor.d.ts.map +1 -1
  44. package/dist/lib/mutate-request.d.ts +1 -60
  45. package/dist/lib/mutate-request.d.ts.map +1 -1
  46. package/dist/lib/types.d.ts +8 -24
  47. package/dist/lib/types.d.ts.map +1 -1
  48. package/dist/lib/validate-request.d.ts.map +1 -1
  49. package/dist/lib/watch-processor.d.ts.map +1 -1
  50. package/dist/lib.js +236 -299
  51. package/dist/lib.js.map +4 -4
  52. package/dist/sdk/cosign.d.ts +18 -0
  53. package/dist/sdk/cosign.d.ts.map +1 -0
  54. package/dist/sdk/heredoc.d.ts +2 -0
  55. package/dist/sdk/heredoc.d.ts.map +1 -0
  56. package/dist/sdk/sdk.d.ts +1 -2
  57. package/dist/sdk/sdk.d.ts.map +1 -1
  58. package/package.json +12 -8
  59. package/src/cli/build.helpers.ts +28 -0
  60. package/src/cli/build.ts +124 -121
  61. package/src/cli/deploy.ts +27 -24
  62. package/src/cli/dev.ts +3 -3
  63. package/src/cli/format.ts +3 -6
  64. package/src/cli/init/index.ts +23 -19
  65. package/src/cli/monitor.ts +34 -36
  66. package/src/lib/assets/deploy.ts +12 -3
  67. package/src/lib/assets/helm.ts +14 -0
  68. package/src/lib/assets/index.ts +12 -8
  69. package/src/lib/assets/rbac.ts +69 -17
  70. package/src/lib/assets/webhooks.ts +1 -1
  71. package/src/lib/assets/yaml.ts +8 -4
  72. package/src/lib/capability.ts +7 -12
  73. package/src/lib/controller/index.ts +3 -3
  74. package/src/lib/controller/store.ts +42 -202
  75. package/src/lib/controller/storeCache.ts +63 -0
  76. package/src/lib/enums.ts +21 -0
  77. package/src/lib/{adjudicators.ts → filter/adjudicators.ts} +56 -31
  78. package/src/lib/{filter.ts → filter/filter.ts} +3 -2
  79. package/src/lib/finalizer.ts +1 -1
  80. package/src/lib/helpers.ts +19 -15
  81. package/src/lib/k8s.ts +2 -2
  82. package/src/lib/logger.ts +41 -0
  83. package/src/lib/metrics.ts +3 -1
  84. package/src/lib/module.ts +5 -0
  85. package/src/lib/mutate-processor.ts +14 -12
  86. package/src/lib/mutate-request.ts +4 -69
  87. package/src/lib/types.ts +9 -28
  88. package/src/lib/validate-processor.ts +1 -1
  89. package/src/lib/validate-request.ts +2 -1
  90. package/src/lib/watch-processor.ts +34 -20
  91. package/src/sdk/cosign.ts +327 -0
  92. package/src/sdk/heredoc.ts +36 -0
  93. package/src/sdk/sdk.ts +1 -2
  94. package/dist/lib/adjudicators.d.ts.map +0 -1
  95. package/dist/lib/filter.d.ts.map +0 -1
@@ -28,7 +28,7 @@ import {
28
28
  mismatchedNamespaceRegex,
29
29
  unbindableNamespaces,
30
30
  uncarryableNamespace,
31
- } from "./adjudicators";
31
+ } from "./filter/adjudicators";
32
32
 
33
33
  export function matchesRegex(pattern: string, testString: string): boolean {
34
34
  // edge-case
@@ -61,7 +61,7 @@ export function validateHash(expectedHash: string): void {
61
61
  }
62
62
  }
63
63
 
64
- type RBACMap = {
64
+ export type RBACMap = {
65
65
  [key: string]: {
66
66
  verbs: string[];
67
67
  plural: string;
@@ -170,6 +170,14 @@ export function createRBACMap(capabilities: CapabilityExport[]): RBACMap {
170
170
  plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`,
171
171
  };
172
172
  }
173
+
174
+ // Add finalizer rbac
175
+ if (binding.isFinalize) {
176
+ acc[key] = {
177
+ verbs: ["patch"],
178
+ plural: binding.kind.plural || `${binding.kind.kind.toLowerCase()}s`,
179
+ };
180
+ }
173
181
  });
174
182
 
175
183
  return acc;
@@ -267,12 +275,9 @@ export function namespaceComplianceValidator(capability: CapabilityExport, ignor
267
275
  ) {
268
276
  for (const regexNamespace of bindingRegexNamespaces) {
269
277
  let matches = false;
270
- for (const capabilityNamespace of capabilityNamespaces) {
271
- if (regexNamespace !== "" && matchesRegex(regexNamespace, capabilityNamespace)) {
272
- matches = true;
273
- break;
274
- }
275
- }
278
+ matches =
279
+ regexNamespace !== "" &&
280
+ capabilityNamespaces.some(capabilityNamespace => matchesRegex(regexNamespace, capabilityNamespace));
276
281
  if (!matches) {
277
282
  throw new Error(
278
283
  `Ignoring Watch Callback: Object namespace does not match any capability namespace with regex ${regexNamespace}.`,
@@ -288,12 +293,11 @@ export function namespaceComplianceValidator(capability: CapabilityExport, ignor
288
293
  ignoredNamespaces.length > 0
289
294
  ) {
290
295
  for (const regexNamespace of bindingRegexNamespaces) {
291
- for (const ignoredNS of ignoredNamespaces) {
292
- if (matchesRegex(regexNamespace, ignoredNS)) {
293
- throw new Error(
294
- `Ignoring Watch Callback: Regex namespace: ${regexNamespace}, is an ignored namespace: ${ignoredNS}.`,
295
- );
296
- }
296
+ const matchedNS = ignoredNamespaces.find(ignoredNS => matchesRegex(regexNamespace, ignoredNS));
297
+ if (matchedNS) {
298
+ throw new Error(
299
+ `Ignoring Watch Callback: Regex namespace: ${regexNamespace}, is an ignored namespace: ${matchedNS}.`,
300
+ );
297
301
  }
298
302
  }
299
303
  }
@@ -381,7 +385,7 @@ export function dedent(file: string) {
381
385
  }
382
386
 
383
387
  export function replaceString(str: string, stringA: string, stringB: string) {
384
- //eslint-disable-next-line
388
+ // eslint-disable-next-line no-useless-escape
385
389
  const escapedStringA = stringA.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&");
386
390
  const regExp = new RegExp(escapedStringA, "g");
387
391
  return str.replace(regExp, stringB);
package/src/lib/k8s.ts CHANGED
@@ -6,7 +6,7 @@ import { GenericKind, RegisterKind } from "kubernetes-fluent-client";
6
6
  /**
7
7
  * PeprStore for internal use by Pepr. This is used to store arbitrary data in the cluster.
8
8
  */
9
- export class PeprStore extends GenericKind {
9
+ export class Store extends GenericKind {
10
10
  declare data: {
11
11
  [key: string]: string;
12
12
  };
@@ -18,7 +18,7 @@ export const peprStoreGVK = {
18
18
  group: "pepr.dev",
19
19
  };
20
20
 
21
- RegisterKind(PeprStore, peprStoreGVK);
21
+ RegisterKind(Store, peprStoreGVK);
22
22
 
23
23
  export interface MutateResponse {
24
24
  /** UID is an identifier for the individual request/response. This must be copied over from the corresponding AdmissionRequest. */
package/src/lib/logger.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
+ import { Operation } from "fast-json-patch";
4
5
  import { pino, stdTimeFunctions } from "pino";
6
+ import { Store } from "./k8s";
5
7
 
6
8
  const isPrettyLog = process.env.PEPR_PRETTY_LOGS === "true";
9
+ const redactedValue = "**redacted**";
7
10
 
8
11
  const pretty = {
9
12
  target: "pino-pretty",
@@ -24,4 +27,42 @@ const Log = pino({
24
27
  if (process.env.LOG_LEVEL) {
25
28
  Log.level = process.env.LOG_LEVEL;
26
29
  }
30
+
31
+ export function redactedStore(store: Store): Store {
32
+ const redacted = process.env.PEPR_STORE_REDACT_VALUES === "true";
33
+ return {
34
+ ...store,
35
+ data: Object.keys(store.data).reduce((acc: Record<string, string>, key: string) => {
36
+ acc[key] = redacted ? redactedValue : store.data[key];
37
+ return acc;
38
+ }, {}),
39
+ };
40
+ }
41
+
42
+ export function redactedPatch(patch: Record<string, Operation> = {}): Record<string, Operation> {
43
+ const redacted = process.env.PEPR_STORE_REDACT_VALUES === "true";
44
+
45
+ if (!redacted) {
46
+ return patch;
47
+ }
48
+
49
+ const redactedCache: Record<string, Operation> = {};
50
+
51
+ Object.entries(patch).forEach(([key, operation]) => {
52
+ const isRedacted = key.includes(":");
53
+ const targetKey = isRedacted ? `${key.substring(0, key.lastIndexOf(":"))}:**redacted**` : key;
54
+
55
+ const redactedOperation = isRedacted
56
+ ? {
57
+ ...operation,
58
+ ...(Object.hasOwn(operation, "value") ? { value: redactedValue } : {}),
59
+ }
60
+ : operation;
61
+
62
+ redactedCache[targetKey] = redactedOperation;
63
+ });
64
+
65
+ return redactedCache;
66
+ }
67
+
27
68
  export default Log;
@@ -173,7 +173,9 @@ export class MetricsCollector {
173
173
 
174
174
  if (maxCacheMissWindows !== undefined && this.#cacheMissWindows.size >= maxCacheMissWindows) {
175
175
  const firstKey = this.#cacheMissWindows.keys().next().value;
176
- this.#cacheMissWindows.delete(firstKey);
176
+ if (firstKey !== undefined) {
177
+ this.#cacheMissWindows.delete(firstKey);
178
+ }
177
179
  this.#gauges.get(this.#getMetricName(this.#metricNames.cacheMiss))?.remove({ window: firstKey });
178
180
  }
179
181
  };
package/src/lib/module.ts CHANGED
@@ -8,6 +8,7 @@ import { MutateResponse, ValidateResponse, WebhookIgnore } from "./k8s";
8
8
  import { CapabilityExport, AdmissionRequest } from "./types";
9
9
  import { setupWatch } from "./watch-processor";
10
10
  import { Log } from "../lib";
11
+ import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
11
12
 
12
13
  /** Custom Labels Type for package.json */
13
14
  export interface CustomLabels {
@@ -35,6 +36,10 @@ export type ModuleConfig = {
35
36
  env?: Record<string, string>;
36
37
  /** Custom Labels for Kubernetes Objects */
37
38
  customLabels?: CustomLabels;
39
+ /** Custom RBAC rules */
40
+ rbac?: PolicyRule[];
41
+ /** The RBAC mode; if "scoped", generates scoped rules, otherwise uses wildcard rules. */
42
+ rbacMode?: string;
38
43
  };
39
44
 
40
45
  export type PackageJSON = {
@@ -6,7 +6,7 @@ import { kind } from "kubernetes-fluent-client";
6
6
 
7
7
  import { Capability } from "./capability";
8
8
  import { Errors } from "./errors";
9
- import { shouldSkipRequest } from "./filter";
9
+ import { shouldSkipRequest } from "./filter/filter";
10
10
  import { MutateResponse } from "./k8s";
11
11
  import { AdmissionRequest } from "./types";
12
12
  import Log from "./logger";
@@ -89,17 +89,7 @@ export async function mutateProcessor(
89
89
  updateStatus("warning");
90
90
  response.warnings = response.warnings || [];
91
91
 
92
- let errorMessage = "";
93
-
94
- try {
95
- if (e.message && e.message !== "[object Object]") {
96
- errorMessage = e.message;
97
- } else {
98
- throw new Error("An error occurred in the mutate action.");
99
- }
100
- } catch (e) {
101
- errorMessage = "An error occurred with the mutate action.";
102
- }
92
+ const errorMessage = logMutateErrorMessage(e);
103
93
 
104
94
  // Log on failure
105
95
  Log.error(actionMetadata, `Action failed: ${errorMessage}`);
@@ -161,3 +151,15 @@ export async function mutateProcessor(
161
151
 
162
152
  return response;
163
153
  }
154
+
155
+ const logMutateErrorMessage = (e: Error): string => {
156
+ try {
157
+ if (e.message && e.message !== "[object Object]") {
158
+ return e.message;
159
+ } else {
160
+ throw new Error("An error occurred in the mutate action.");
161
+ }
162
+ } catch (e) {
163
+ return "An error occurred with the mutate action.";
164
+ }
165
+ };
@@ -1,54 +1,34 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
+ import { AdmissionRequest, DeepPartial } from "./types";
5
+ import { Operation } from "./enums";
4
6
  import { KubernetesObject } from "kubernetes-fluent-client";
5
7
  import { clone, mergeDeepRight } from "ramda";
6
- import { Operation, AdmissionRequest, DeepPartial } from "./types";
7
8
 
8
- /**
9
- * The RequestWrapper class provides methods to modify Kubernetes objects in the context
10
- * of a mutating webhook request.
11
- */
9
+ // PeprMutateRequest class for mutation request handling
12
10
  export class PeprMutateRequest<T extends KubernetesObject> {
13
11
  Raw: T;
14
-
15
12
  #input: AdmissionRequest<T>;
16
13
 
17
14
  get PermitSideEffects() {
18
15
  return !this.#input.dryRun;
19
16
  }
20
17
 
21
- /**
22
- * Indicates whether the request is a dry run.
23
- * @returns true if the request is a dry run, false otherwise.
24
- */
25
18
  get IsDryRun() {
26
19
  return this.#input.dryRun;
27
20
  }
28
21
 
29
- /**
30
- * Provides access to the old resource in the request if available.
31
- * @returns The old Kubernetes resource object or null if not available.
32
- */
33
22
  get OldResource() {
34
23
  return this.#input.oldObject;
35
24
  }
36
25
 
37
- /**
38
- * Provides access to the request object.
39
- * @returns The request object containing the Kubernetes resource.
40
- */
41
26
  get Request() {
42
27
  return this.#input;
43
28
  }
44
29
 
45
- /**
46
- * Creates a new instance of the action class.
47
- * @param input - The request object containing the Kubernetes resource to modify.
48
- */
49
30
  constructor(input: AdmissionRequest<T>) {
50
31
  this.#input = input;
51
-
52
32
  // If this is a DELETE operation, use the oldObject instead
53
33
  if (input.operation.toUpperCase() === Operation.DELETE) {
54
34
  this.Raw = clone(input.oldObject as T);
@@ -58,93 +38,48 @@ export class PeprMutateRequest<T extends KubernetesObject> {
58
38
  }
59
39
 
60
40
  if (!this.Raw) {
61
- throw new Error("unable to load the request object into PeprRequest.RawP");
41
+ throw new Error("Unable to load the request object into PeprRequest.Raw");
62
42
  }
63
43
  }
64
44
 
65
- /**
66
- * Deep merges the provided object with the current resource.
67
- *
68
- * @param obj - The object to merge with the current resource.
69
- */
70
45
  Merge = (obj: DeepPartial<T>) => {
71
46
  this.Raw = mergeDeepRight(this.Raw, obj) as unknown as T;
72
47
  };
73
48
 
74
- /**
75
- * Updates a label on the Kubernetes resource.
76
- * @param key - The key of the label to update.
77
- * @param value - The value of the label.
78
- * @returns The current action instance for method chaining.
79
- */
80
49
  SetLabel = (key: string, value: string) => {
81
50
  const ref = this.Raw;
82
-
83
51
  ref.metadata = ref.metadata ?? {};
84
52
  ref.metadata.labels = ref.metadata.labels ?? {};
85
53
  ref.metadata.labels[key] = value;
86
-
87
54
  return this;
88
55
  };
89
56
 
90
- /**
91
- * Updates an annotation on the Kubernetes resource.
92
- * @param key - The key of the annotation to update.
93
- * @param value - The value of the annotation.
94
- * @returns The current action instance for method chaining.
95
- */
96
57
  SetAnnotation = (key: string, value: string) => {
97
58
  const ref = this.Raw;
98
-
99
59
  ref.metadata = ref.metadata ?? {};
100
60
  ref.metadata.annotations = ref.metadata.annotations ?? {};
101
61
  ref.metadata.annotations[key] = value;
102
-
103
62
  return this;
104
63
  };
105
64
 
106
- /**
107
- * Removes a label from the Kubernetes resource.
108
- * @param key - The key of the label to remove.
109
- * @returns The current Action instance for method chaining.
110
- */
111
65
  RemoveLabel = (key: string) => {
112
66
  if (this.Raw.metadata?.labels?.[key]) {
113
67
  delete this.Raw.metadata.labels[key];
114
68
  }
115
-
116
69
  return this;
117
70
  };
118
71
 
119
- /**
120
- * Removes an annotation from the Kubernetes resource.
121
- * @param key - The key of the annotation to remove.
122
- * @returns The current Action instance for method chaining.
123
- */
124
72
  RemoveAnnotation = (key: string) => {
125
73
  if (this.Raw.metadata?.annotations?.[key]) {
126
74
  delete this.Raw.metadata.annotations[key];
127
75
  }
128
-
129
76
  return this;
130
77
  };
131
78
 
132
- /**
133
- * Check if a label exists on the Kubernetes resource.
134
- *
135
- * @param key the label key to check
136
- * @returns
137
- */
138
79
  HasLabel = (key: string) => {
139
80
  return this.Raw.metadata?.labels?.[key] !== undefined;
140
81
  };
141
82
 
142
- /**
143
- * Check if an annotation exists on the Kubernetes resource.
144
- *
145
- * @param key the annotation key to check
146
- * @returns
147
- */
148
83
  HasAnnotation = (key: string) => {
149
84
  return this.Raw.metadata?.annotations?.[key] !== undefined;
150
85
  };
package/src/lib/types.ts CHANGED
@@ -2,20 +2,13 @@
2
2
  // SPDX-FileCopyrightText: 2023-Present The Pepr Authors
3
3
 
4
4
  import { GenericClass, GroupVersionKind, KubernetesObject } from "kubernetes-fluent-client";
5
-
5
+ import { Event, Operation } from "./enums";
6
6
  import { WatchPhase } from "kubernetes-fluent-client/dist/fluent/types";
7
-
7
+ import { Logger } from "pino";
8
8
  import { PeprMutateRequest } from "./mutate-request";
9
9
  import { PeprValidateRequest } from "./validate-request";
10
+ import { V1PolicyRule as PolicyRule } from "@kubernetes/client-node";
10
11
 
11
- import { Logger } from "pino";
12
-
13
- export enum Operation {
14
- CREATE = "CREATE",
15
- UPDATE = "UPDATE",
16
- DELETE = "DELETE",
17
- CONNECT = "CONNECT",
18
- }
19
12
  /**
20
13
  * Specifically for deploying images with a private registry
21
14
  */
@@ -40,23 +33,6 @@ export interface ResponseItem {
40
33
  message: string;
41
34
  };
42
35
  }
43
- /**
44
- * Recursively make all properties in T optional.
45
- */
46
- export type DeepPartial<T> = {
47
- [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
48
- };
49
-
50
- /**
51
- * The type of Kubernetes mutating webhook event that the action is registered for.
52
- */
53
- export enum Event {
54
- Create = "CREATE",
55
- Update = "UPDATE",
56
- Delete = "DELETE",
57
- CreateOrUpdate = "CREATEORUPDATE",
58
- Any = "*",
59
- }
60
36
 
61
37
  export interface CapabilityCfg {
62
38
  /**
@@ -77,6 +53,7 @@ export interface CapabilityCfg {
77
53
  export interface CapabilityExport extends CapabilityCfg {
78
54
  bindings: Binding[];
79
55
  hasSchedule: boolean;
56
+ rbac?: PolicyRule[];
80
57
  }
81
58
 
82
59
  export type WhenSelector<T extends GenericClass> = {
@@ -267,7 +244,7 @@ export type ValidateActionResponse = {
267
244
  export type FinalizeAction<T extends GenericClass, K extends KubernetesObject = InstanceType<T>> = (
268
245
  update: K,
269
246
  logger?: Logger,
270
- ) => Promise<void> | void;
247
+ ) => Promise<boolean | void> | boolean | void;
271
248
 
272
249
  export type FinalizeActionChain<T extends GenericClass> = {
273
250
  /**
@@ -373,3 +350,7 @@ export interface GroupVersionResource {
373
350
  readonly version: string;
374
351
  readonly resource: string;
375
352
  }
353
+ // DeepPartial utility type for deep optional properties
354
+ export type DeepPartial<T> = {
355
+ [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
356
+ };
@@ -4,7 +4,7 @@
4
4
  import { kind } from "kubernetes-fluent-client";
5
5
 
6
6
  import { Capability } from "./capability";
7
- import { shouldSkipRequest } from "./filter";
7
+ import { shouldSkipRequest } from "./filter/filter";
8
8
  import { ValidateResponse } from "./k8s";
9
9
  import { AdmissionRequest } from "./types";
10
10
  import Log from "./logger";
@@ -6,8 +6,9 @@
6
6
  import { KubernetesObject } from "kubernetes-fluent-client";
7
7
 
8
8
  import { clone } from "ramda";
9
- import { AdmissionRequest, Operation } from "./types";
9
+ import { AdmissionRequest } from "./types";
10
10
  import { ValidateActionResponse } from "./types";
11
+ import { Operation } from "./enums";
11
12
 
12
13
  /**
13
14
  * The RequestWrapper class provides methods to modify Kubernetes objects in the context
@@ -7,7 +7,8 @@ import { filterNoMatchReason } from "./helpers";
7
7
  import { removeFinalizer } from "./finalizer";
8
8
  import Log from "./logger";
9
9
  import { Queue } from "./queue";
10
- import { Binding, Event } from "./types";
10
+ import { Binding } from "./types";
11
+ import { Event } from "./enums";
11
12
  import { metricsCollector } from "./metrics";
12
13
 
13
14
  // stores Queue instances
@@ -49,7 +50,7 @@ export function getOrCreateQueue(obj: KubernetesObject) {
49
50
 
50
51
  // Watch configuration
51
52
  const watchCfg: WatchCfg = {
52
- useHTTP2: process.env.PEPR_HTTP2_WATCH === "true",
53
+ useLegacyWatch: process.env.PEPR_USE_LEGACY_WATCH === "true",
53
54
  resyncFailureMax: process.env.PEPR_RESYNC_FAILURE_MAX ? parseInt(process.env.PEPR_RESYNC_FAILURE_MAX, 10) : 5,
54
55
  resyncDelaySec: process.env.PEPR_RESYNC_DELAY_SECONDS ? parseInt(process.env.PEPR_RESYNC_DELAY_SECONDS, 10) : 5,
55
56
  lastSeenLimitSeconds: process.env.PEPR_LAST_SEEN_LIMIT_SECONDS
@@ -95,29 +96,20 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[], igno
95
96
  // The watch callback is run when an object is received or dequeued
96
97
  Log.debug({ watchCfg }, "Effective WatchConfig");
97
98
 
98
- const watchCallback = async (obj: KubernetesObject, phase: WatchPhase) => {
99
+ const watchCallback = async (kubernetesObject: KubernetesObject, phase: WatchPhase) => {
99
100
  // First, filter the object based on the phase
100
101
  if (phaseMatch.includes(phase)) {
101
102
  try {
102
103
  // Then, check if the object matches the filter
103
- const filterMatch = filterNoMatchReason(binding, obj, capabilityNamespaces, ignoredNamespaces);
104
- if (filterMatch === "") {
105
- if (binding.isFinalize) {
106
- if (!obj.metadata?.deletionTimestamp) {
107
- return;
108
- }
109
- try {
110
- await binding.finalizeCallback?.(obj);
111
-
112
- // irrespective of callback success / failure, remove pepr finalizer
113
- } finally {
114
- await removeFinalizer(binding, obj);
115
- }
116
- } else {
117
- await binding.watchCallback?.(obj, phase);
118
- }
119
- } else {
104
+ const filterMatch = filterNoMatchReason(binding, kubernetesObject, capabilityNamespaces, ignoredNamespaces);
105
+ if (filterMatch !== "") {
120
106
  Log.debug(filterMatch);
107
+ return;
108
+ }
109
+ if (binding.isFinalize) {
110
+ await handleFinalizerRemoval(kubernetesObject);
111
+ } else {
112
+ await binding.watchCallback?.(kubernetesObject, phase);
121
113
  }
122
114
  } catch (e) {
123
115
  // Errors in the watch callback should not crash the controller
@@ -126,6 +118,28 @@ async function runBinding(binding: Binding, capabilityNamespaces: string[], igno
126
118
  }
127
119
  };
128
120
 
121
+ const handleFinalizerRemoval = async (kubernetesObject: KubernetesObject) => {
122
+ if (!kubernetesObject.metadata?.deletionTimestamp) {
123
+ return;
124
+ }
125
+ let shouldRemoveFinalizer: boolean | void | undefined = true;
126
+ try {
127
+ shouldRemoveFinalizer = await binding.finalizeCallback?.(kubernetesObject);
128
+
129
+ // if not opt'ed out of / if in error state, remove pepr finalizer
130
+ } finally {
131
+ const peprFinal = "pepr.dev/finalizer";
132
+ const meta = kubernetesObject.metadata!;
133
+ const resource = `${meta.namespace || "ClusterScoped"}/${meta.name}`;
134
+
135
+ // [ true, void, undefined ] SHOULD remove finalizer
136
+ // [ false ] should NOT remove finalizer
137
+ shouldRemoveFinalizer === false
138
+ ? Log.debug({ obj: kubernetesObject }, `Skipping removal of finalizer '${peprFinal}' from '${resource}'`)
139
+ : await removeFinalizer(binding, kubernetesObject);
140
+ }
141
+ };
142
+
129
143
  // Setup the resource watch
130
144
  const watcher = K8s(binding.model, binding.filters).Watch(async (obj, phase) => {
131
145
  Log.debug(obj, `Watch event ${phase} received`);