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.
- package/dist/cli/build.d.ts +1 -1
- package/dist/cli/build.d.ts.map +1 -1
- package/dist/cli/build.helpers.d.ts +19 -0
- package/dist/cli/build.helpers.d.ts.map +1 -0
- package/dist/cli/deploy.d.ts.map +1 -1
- package/dist/cli/format.d.ts.map +1 -1
- package/dist/cli/init/index.d.ts.map +1 -1
- package/dist/cli/init/templates.d.ts +6 -2
- package/dist/cli/init/templates.d.ts.map +1 -1
- package/dist/cli/monitor.d.ts.map +1 -1
- package/dist/cli.js +294 -229
- package/dist/controller.js +53 -31
- package/dist/lib/assets/deploy.d.ts.map +1 -1
- package/dist/lib/assets/helm.d.ts +1 -0
- package/dist/lib/assets/helm.d.ts.map +1 -1
- package/dist/lib/assets/index.d.ts +1 -1
- package/dist/lib/assets/index.d.ts.map +1 -1
- package/dist/lib/assets/rbac.d.ts +31 -4
- package/dist/lib/assets/rbac.d.ts.map +1 -1
- package/dist/lib/assets/yaml.d.ts +2 -2
- package/dist/lib/assets/yaml.d.ts.map +1 -1
- package/dist/lib/capability.d.ts +2 -8
- package/dist/lib/capability.d.ts.map +1 -1
- package/dist/lib/controller/store.d.ts +1 -5
- package/dist/lib/controller/store.d.ts.map +1 -1
- package/dist/lib/controller/storeCache.d.ts +11 -0
- package/dist/lib/controller/storeCache.d.ts.map +1 -0
- package/dist/lib/enums.d.ts +17 -0
- package/dist/lib/enums.d.ts.map +1 -0
- package/dist/lib/{adjudicators.d.ts → filter/adjudicators.d.ts} +23 -18
- package/dist/lib/filter/adjudicators.d.ts.map +1 -0
- package/dist/lib/{filter.d.ts → filter/filter.d.ts} +1 -1
- package/dist/lib/filter/filter.d.ts.map +1 -0
- package/dist/lib/helpers.d.ts +1 -2
- package/dist/lib/helpers.d.ts.map +1 -1
- package/dist/lib/k8s.d.ts +1 -1
- package/dist/lib/k8s.d.ts.map +1 -1
- package/dist/lib/logger.d.ts +4 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/metrics.d.ts.map +1 -1
- package/dist/lib/module.d.ts +5 -0
- package/dist/lib/module.d.ts.map +1 -1
- package/dist/lib/mutate-processor.d.ts.map +1 -1
- package/dist/lib/mutate-request.d.ts +1 -60
- package/dist/lib/mutate-request.d.ts.map +1 -1
- package/dist/lib/types.d.ts +8 -24
- package/dist/lib/types.d.ts.map +1 -1
- package/dist/lib/validate-request.d.ts.map +1 -1
- package/dist/lib/watch-processor.d.ts.map +1 -1
- package/dist/lib.js +236 -299
- package/dist/lib.js.map +4 -4
- package/dist/sdk/cosign.d.ts +18 -0
- package/dist/sdk/cosign.d.ts.map +1 -0
- package/dist/sdk/heredoc.d.ts +2 -0
- package/dist/sdk/heredoc.d.ts.map +1 -0
- package/dist/sdk/sdk.d.ts +1 -2
- package/dist/sdk/sdk.d.ts.map +1 -1
- package/package.json +12 -8
- package/src/cli/build.helpers.ts +28 -0
- package/src/cli/build.ts +124 -121
- package/src/cli/deploy.ts +27 -24
- package/src/cli/dev.ts +3 -3
- package/src/cli/format.ts +3 -6
- package/src/cli/init/index.ts +23 -19
- package/src/cli/monitor.ts +34 -36
- package/src/lib/assets/deploy.ts +12 -3
- package/src/lib/assets/helm.ts +14 -0
- package/src/lib/assets/index.ts +12 -8
- package/src/lib/assets/rbac.ts +69 -17
- package/src/lib/assets/webhooks.ts +1 -1
- package/src/lib/assets/yaml.ts +8 -4
- package/src/lib/capability.ts +7 -12
- package/src/lib/controller/index.ts +3 -3
- package/src/lib/controller/store.ts +42 -202
- package/src/lib/controller/storeCache.ts +63 -0
- package/src/lib/enums.ts +21 -0
- package/src/lib/{adjudicators.ts → filter/adjudicators.ts} +56 -31
- package/src/lib/{filter.ts → filter/filter.ts} +3 -2
- package/src/lib/finalizer.ts +1 -1
- package/src/lib/helpers.ts +19 -15
- package/src/lib/k8s.ts +2 -2
- package/src/lib/logger.ts +41 -0
- package/src/lib/metrics.ts +3 -1
- package/src/lib/module.ts +5 -0
- package/src/lib/mutate-processor.ts +14 -12
- package/src/lib/mutate-request.ts +4 -69
- package/src/lib/types.ts +9 -28
- package/src/lib/validate-processor.ts +1 -1
- package/src/lib/validate-request.ts +2 -1
- package/src/lib/watch-processor.ts +34 -20
- package/src/sdk/cosign.ts +327 -0
- package/src/sdk/heredoc.ts +36 -0
- package/src/sdk/sdk.ts +1 -2
- package/dist/lib/adjudicators.d.ts.map +0 -1
- package/dist/lib/filter.d.ts.map +0 -1
package/src/lib/helpers.ts
CHANGED
|
@@ -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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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
|
|
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(
|
|
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;
|
package/src/lib/metrics.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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("
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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,
|
|
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`);
|